bv-ui-core 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/.eslintrc +40 -0
  2. package/.travis.yml +5 -0
  3. package/CONTRIBUTING.md +98 -0
  4. package/LICENSE.md +13 -0
  5. package/OWNERS +2 -0
  6. package/README.md +63 -0
  7. package/catalog-info.yaml +30 -0
  8. package/karma.conf.js +130 -0
  9. package/lib/README.md +61 -0
  10. package/lib/api/.babelrc +3 -0
  11. package/lib/api/.eslintrc +9 -0
  12. package/lib/api/README.md +134 -0
  13. package/lib/api/index.js +15 -0
  14. package/lib/api/statistics.js +148 -0
  15. package/lib/application/README.md +96 -0
  16. package/lib/application/index.js +171 -0
  17. package/lib/body/README.md +13 -0
  18. package/lib/body/index.js +19 -0
  19. package/lib/bvFetch/README.md +101 -0
  20. package/lib/bvFetch/index.js +383 -0
  21. package/lib/checkHighContrast/README.md +26 -0
  22. package/lib/checkHighContrast/index.js +36 -0
  23. package/lib/cookie/README.md +29 -0
  24. package/lib/cookie/index.js +111 -0
  25. package/lib/cookieConsent/README.md +41 -0
  26. package/lib/cookieConsent/index.js +227 -0
  27. package/lib/cssLoadChecker/README.md +19 -0
  28. package/lib/cssLoadChecker/index.js +72 -0
  29. package/lib/date.now/README.md +11 -0
  30. package/lib/date.now/index.js +40 -0
  31. package/lib/domainPolice/README.md +66 -0
  32. package/lib/domainPolice/index.js +95 -0
  33. package/lib/evented/README.md +42 -0
  34. package/lib/evented/index.js +135 -0
  35. package/lib/getOriginalConstructor/README.md +16 -0
  36. package/lib/getOriginalConstructor/index.js +52 -0
  37. package/lib/global/README.md +10 -0
  38. package/lib/global/index.js +48 -0
  39. package/lib/ie/README.md +13 -0
  40. package/lib/ie/index.js +25 -0
  41. package/lib/loader/README.md +73 -0
  42. package/lib/loader/index.js +358 -0
  43. package/lib/logger/README.md +82 -0
  44. package/lib/logger/index.js +180 -0
  45. package/lib/messageFormat/README.md +10 -0
  46. package/lib/messageFormat/index.js +42 -0
  47. package/lib/namespacer/README.md +57 -0
  48. package/lib/namespacer/index.js +98 -0
  49. package/lib/performance/README.md +35 -0
  50. package/lib/performance/entries.js +5 -0
  51. package/lib/performance/getEntries.js +42 -0
  52. package/lib/performance/getEntriesByName.js +61 -0
  53. package/lib/performance/getEntriesByType.js +55 -0
  54. package/lib/performance/index.js +11 -0
  55. package/lib/performance/mark.js +97 -0
  56. package/lib/performance/measure.js +106 -0
  57. package/lib/performance/now.js +63 -0
  58. package/lib/pixelsDisplayed/README.md +19 -0
  59. package/lib/pixelsDisplayed/index.js +113 -0
  60. package/lib/polyfills/README.md +39 -0
  61. package/lib/polyfills/fetch/Headers.js +3 -0
  62. package/lib/polyfills/fetch/Request.js +3 -0
  63. package/lib/polyfills/fetch/Response.js +3 -0
  64. package/lib/polyfills/fetch/fetch.js +3 -0
  65. package/lib/polyfills/fetch/index.js +57 -0
  66. package/lib/polyfills/promise.js +265 -0
  67. package/lib/queryShadowDom/README.md +50 -0
  68. package/lib/queryShadowDom/index.js +93 -0
  69. package/lib/region/README.md +64 -0
  70. package/lib/region/europe/index.js +94 -0
  71. package/lib/region/europe/territories.js +40 -0
  72. package/lib/region/index.js +123 -0
  73. package/lib/staticAssetLoader/README.md +116 -0
  74. package/lib/staticAssetLoader/index.js +248 -0
  75. package/lib/waitForBody/README.md +21 -0
  76. package/lib/waitForBody/index.js +36 -0
  77. package/mkdocs.yaml +5 -0
  78. package/package.json +55 -0
  79. package/test/.eslintrc +5 -0
  80. package/test/fixtures/lib.loader.document-is-wrong-in-ie8.js +5 -0
  81. package/test/fixtures/lib.loader.loadscript-attribute.js +3 -0
  82. package/test/fixtures/lib.loader.loadscript.js +3 -0
  83. package/test/fixtures/lib.loader.loadstylesheet.css +3 -0
  84. package/test/unit/api/.eslintrc +6 -0
  85. package/test/unit/api/index.spec.js +37 -0
  86. package/test/unit/api/response.json +67 -0
  87. package/test/unit/api/statistics.spec.js +218 -0
  88. package/test/unit/application/index.spec.js +313 -0
  89. package/test/unit/body/index.spec.js +15 -0
  90. package/test/unit/bvFetch/index.spec.js +246 -0
  91. package/test/unit/checkHighContrast/index.spec.js +16 -0
  92. package/test/unit/cookie/index.spec.js +191 -0
  93. package/test/unit/cookieConsent/index.spec.js +119 -0
  94. package/test/unit/cssLoadChecker/index.spec.js +75 -0
  95. package/test/unit/date.now/index.spec.js +21 -0
  96. package/test/unit/domainPolice/index.spec.js +141 -0
  97. package/test/unit/evented/index.spec.js +59 -0
  98. package/test/unit/getOriginalConstructor/index.spec.js +60 -0
  99. package/test/unit/global/index.spec.js +84 -0
  100. package/test/unit/ie/index.spec.js +14 -0
  101. package/test/unit/loader/index.spec.js +644 -0
  102. package/test/unit/logger/index.spec.js +141 -0
  103. package/test/unit/messageFormat/index.spec.js +31 -0
  104. package/test/unit/mochaInit.js +8 -0
  105. package/test/unit/namespacer/index.spec.js +100 -0
  106. package/test/unit/performance/getEntries.spec.js +30 -0
  107. package/test/unit/performance/getEntriesByName.spec.js +88 -0
  108. package/test/unit/performance/getEntriesByType.spec.js +48 -0
  109. package/test/unit/performance/index.spec.js +35 -0
  110. package/test/unit/performance/mark.spec.js +40 -0
  111. package/test/unit/performance/measure.spec.js +50 -0
  112. package/test/unit/performance/now.spec.js +21 -0
  113. package/test/unit/pixelsDisplayed/index.spec.js +271 -0
  114. package/test/unit/polyfills/fetch/Headers.js +12 -0
  115. package/test/unit/polyfills/fetch/Request.js +12 -0
  116. package/test/unit/polyfills/fetch/Response.js +12 -0
  117. package/test/unit/polyfills/fetch/fetch.js +12 -0
  118. package/test/unit/polyfills/fetch/index.js +61 -0
  119. package/test/unit/polyfills/promise.spec.js +48 -0
  120. package/test/unit/region/europe/index.spec.js +70 -0
  121. package/test/unit/region/index.spec.js +113 -0
  122. package/test/unit/staticAssetLoader/index.spec.js +215 -0
@@ -0,0 +1,64 @@
1
+ # Region
2
+
3
+ The `region` module is a module meant to standardize the detection of various
4
+ regions, such as EU or APAC. It exposes functions that can test if a locale
5
+ belongs to various regions. It can also simply lookup the region for a given
6
+ locale.
7
+
8
+ So far, the only region supported is EU, but the module is designed to be
9
+ easily extendable when the need to support other regions pops up.
10
+
11
+ ## Usage Examples
12
+
13
+ Loading the root module:
14
+ ```javascript
15
+ var region = require('bv-ui-core/lib/region');
16
+
17
+ region.hasLocale('europe', 'en_US'); // false
18
+ region.hasLocale('europe', 'en_GB'); // true
19
+
20
+ region.hasTerritory('europe', 'US'); // false
21
+ region.hasTerritory('europe', 'GB'); // true
22
+
23
+ region.lookupTerritory('US'); // unsupported
24
+ region.lookupTerritory('GB'); // europe
25
+
26
+ region.lookupLocale('en_US'); // unsupported
27
+ region.lookupLocale('en_GB'); // europe
28
+
29
+ ```
30
+ This can be useful when you'll have to support detection of multiple regions.
31
+ As only the EU region is supported for now, it's not particularly useful yet.
32
+
33
+ Loading a specific region module:
34
+ ```javascript
35
+ var europe = require('bv-ui-core/lib/region/europe');
36
+
37
+ europe.hasLocale('en_US'); // false
38
+ europe.hasLocale('en_GB'); // true
39
+
40
+ europe.hasTerritory('US'); // false
41
+ europe.hasTerritory('GB'); // true
42
+
43
+ // Get an Array with all the region's country codes.
44
+ var europeanTerritories = europe.listTerritories();
45
+
46
+ // If you prefer a map, you can require it directly.
47
+ var europeanTerritoryMap = require('bv-ui-core/lib/region/europe/territories');
48
+ ```
49
+ This can be useful if you've no need for other regions and would like to shave
50
+ every last byte from your client-side code.
51
+
52
+ Errors will be thrown if invalid locale or territory codes are provided. This
53
+ is to protect the user against mistaking language codes for territory codes.
54
+ For example, 'fr' is the code for the French language, and 'FR' is the code
55
+ for France. To avoid the case where `region.hasTerritory('europe', 'fr');`
56
+ returns false, we throw an error.
57
+
58
+ ```javascript
59
+ var region = require('bv-ui-core/lib/region');
60
+
61
+ // The user likely meant to check 'FR'. To avoid confusion, we throw an error
62
+ // instead of just returning false.
63
+ region.hasTerritory('europe', 'fr');
64
+ ```
@@ -0,0 +1,94 @@
1
+ /**
2
+ * @module Module for determining if a locale is in the EU.
3
+ *
4
+ * Examples:
5
+ *
6
+ * // Example 1, loading this module directly:
7
+ * var europe = require('bv-ui-core/lib/region/europe');
8
+ * europe.hasLocale('en_US'); // false
9
+ * europe.hasLocale('en_GB'); // true
10
+ *
11
+ * europe.hasTerritory('US'); // false
12
+ * europe.hasTerritory('GB'); // true
13
+ *
14
+ * // Example 2, loading the region module:
15
+ * var region = require('bv-ui-core/lib/region');
16
+ * region.hasLocale('europe', 'en_US'); // false
17
+ * region.hasLocale('europe', 'en_GB'); // true
18
+ *
19
+ * region.hasTerritory('europe', 'US'); // false
20
+ * region.hasTerritory('europe', 'GB'); // true
21
+ *
22
+ * Errors will be thrown if an invalid locale or territory code is provided.
23
+ *
24
+ * Example 3, providing invalid input:
25
+ * var europe = require('bv-ui-core/lib/region/europe');
26
+ * europe.hasLocale('banana'); // throws an error
27
+ *
28
+ * // 'fr' is a language code. The correct French territory code is 'FR'.
29
+ * europe.hasTerritory('fr'); // throws an error.
30
+ */
31
+
32
+ // territories is a map that contains an entry for each valid EU country
33
+ // code, so territories[territory] will return either true or undefined.
34
+ // We just need to coerce that into a boolean value and we're good to go.
35
+ var territories = require('./territories');
36
+
37
+ // Some constant patterns for valid values.
38
+ var VALID_TERRITORY = /^[A-Z]{2}$/;
39
+ var VALID_LOCALE = /^[a-z]{2}[_-][A-Z]{2}$/;
40
+
41
+ module.exports = {
42
+
43
+ /**
44
+ * Determine if a given territory is an EU territory.
45
+ *
46
+ * @param {String} territory - The locale to be tested. This function accepts
47
+ * territory codes of the XX format. Three letter territory codes are not
48
+ * (yet) supported.
49
+ *
50
+ * @return {Boolean} Returns true if the territory is an EU territory.
51
+ * Returns false otherwise.
52
+ *
53
+ * @throws
54
+ */
55
+ hasTerritory: function (territory) {
56
+
57
+ if (!VALID_TERRITORY.test(territory)) {
58
+ throw new Error('Invalid territory code passed to hasTerritory.');
59
+ }
60
+
61
+ return !!territories[territory];
62
+ },
63
+
64
+ /**
65
+ * Determine if a given locale is an EU locale based on the territory code.
66
+ *
67
+ * @param {String} locale - The locale to be tested. This function accepts
68
+ * locales of the xx_XX format only.
69
+ *
70
+ * @return {Boolean} Returns true if the locale is an EU territory.
71
+ * Returns false otherwise.
72
+ *
73
+ * @throws
74
+ */
75
+ hasLocale: function (locale) {
76
+
77
+ if (!VALID_LOCALE.test(locale)) {
78
+ throw new Error('Invalid locale code passed to hasLocale.');
79
+ }
80
+
81
+ var territory = locale.split(/[\_\-]/).pop();
82
+
83
+ return !!territories[territory];
84
+ },
85
+
86
+ /**
87
+ * Return a list of country codes in this region.
88
+ *
89
+ * @return {Array} - A list of country codes in the european region.
90
+ */
91
+ listTerritories: function () {
92
+ return Object.keys(territories);
93
+ }
94
+ };
@@ -0,0 +1,40 @@
1
+ /**
2
+ * @file List of country codes legally considered part of the EU region.
3
+ */
4
+
5
+ var countryCodes = {
6
+ AT: true, // Austria
7
+ BE: true, // Belgium
8
+ BG: true, // Bulgaria
9
+ CH: true, // Switzerland
10
+ CY: true, // Republic of Cyprus
11
+ CZ: true, // Czech Republic
12
+ DE: true, // Germany
13
+ DK: true, // Denmark
14
+ ES: true, // Spain
15
+ EE: true, // Estonia
16
+ FI: true, // Finland
17
+ FR: true, // France
18
+ GB: true, // Great Britain / UK
19
+ GR: true, // Greece
20
+ HR: true, // Croatia
21
+ HU: true, // Hungary
22
+ IE: true, // Ireland
23
+ IS: true, // Iceland
24
+ IT: true, // Italy
25
+ LI: true, // Liechtenstein
26
+ LT: true, // Lithuania
27
+ LU: true, // Luxembourg
28
+ LV: true, // Latvia
29
+ MT: true, // Malta
30
+ NL: true, // Netherlands
31
+ NO: true, // Norway
32
+ PL: true, // Poland
33
+ PT: true, // Portugal
34
+ RO: true, // Romania
35
+ SE: true, // Sweden
36
+ SI: true, // Slovenia
37
+ SK: true // Slovakia
38
+ };
39
+
40
+ module.exports = countryCodes;
@@ -0,0 +1,123 @@
1
+ /**
2
+ * @module Used to determine if a given locale is part of a given region.
3
+ *
4
+ * Currently, the only supported region is EU, because it's the only region
5
+ * we have a need to support. However, this module was designed so that it'd
6
+ * be easy to add new regions, such as APAC, NA, LA, etc.
7
+ *
8
+ * This module is the "generic" module, and can be used to detect any region
9
+ * we support. If your project only needs to detect a specific region, you can
10
+ * require that region's module on its own to reduce file size.
11
+ *
12
+ * Examples:
13
+ *
14
+ * var region = require('bv-ui-core/lib/region');
15
+ * region.hasLocale('europe', 'en_US'); // false
16
+ * region.hasLocale('europe', 'en_GB'); // true
17
+ *
18
+ * region.hasTerritory('europe', 'US'); // false
19
+ * region.hasTerritory('europe', 'GB'); // true
20
+ *
21
+ * Errors will be thrown if an invalid locale or territory code is provided.
22
+ *
23
+ * Example 3, providing invalid input:
24
+ * var region = require('bv-ui-core/lib/region');
25
+ * region.hasLocale('europe', 'banana'); // throws an error
26
+ *
27
+ * // 'fr' is a language code. The correct French territory code is 'FR'.
28
+ * region.hasTerritory('europe', 'fr'); // throws an error.
29
+ */
30
+
31
+ var regions = {
32
+ europe: require('./europe')
33
+ };
34
+
35
+ module.exports = {
36
+
37
+ /**
38
+ * Determine if a given territory is an EU territory.
39
+ *
40
+ * @param {String} region - The region to check for the territory in.
41
+ *
42
+ * @param {String} territory - The locale to be tested. This function accepts
43
+ * territory codes of the XX format. Three letter territory codes are not
44
+ * (yet) supported.
45
+ *
46
+ * @return {Boolean} Returns true if the territory is an EU territory.
47
+ * Returns false otherwise.
48
+ */
49
+ hasTerritory: function (region, territory) {
50
+
51
+ if (!regions[region]) {
52
+ throw new Error('Invalid region passed to region.hasTerritory');
53
+ }
54
+
55
+ // Simply wrap around the individual region modules.
56
+ return regions[region].hasTerritory(territory);
57
+ },
58
+
59
+ /**
60
+ * Determine if a given locale is an EU locale based on the territory code.
61
+ *
62
+ * @param {String} region - The region to check for the territory in.
63
+ *
64
+ * @param {String} locale - The locale to be tested. This function accepts
65
+ * locales of the xx_XX format only.
66
+ *
67
+ * @return {Boolean} Returns true if the locale is an EU territory.
68
+ * Returns false otherwise.
69
+ */
70
+ hasLocale: function (region, locale) {
71
+
72
+ if (!regions[region]) {
73
+ throw new Error('Invalid region passed to region.hasLocale');
74
+ }
75
+
76
+ // Simply wrap around the individual region modules.
77
+ return regions[region].hasLocale(locale);
78
+ },
79
+
80
+ /**
81
+ * Return the region of a specified territory.
82
+ *
83
+ * @param {String} territory - The name of the territory.
84
+ *
85
+ * @return {String} Returns the name of the region, or 'unsupported' if the
86
+ * territory is not part of a supported region.
87
+ */
88
+ lookupTerritory: function (territory) {
89
+
90
+ // Loop through the regions looking for the territory.
91
+ var region;
92
+ for (region in regions) {
93
+ if (regions[region].hasTerritory(territory)) {
94
+ return region;
95
+ }
96
+ }
97
+
98
+ // If we get here, the territory isn't in any currently supported region.
99
+ return 'unsupported';
100
+ },
101
+
102
+ /**
103
+ * Return the region of a specified locale.
104
+ *
105
+ * @param {String} locale - The name of the locale.
106
+ *
107
+ * @return {String} Returns the name of the region, or 'unsupported' if the
108
+ * locale is not part of a supported region.
109
+ */
110
+ lookupLocale: function (locale) {
111
+
112
+ // Loop through the regions looking for the locale.
113
+ var region;
114
+ for (region in regions) {
115
+ if (regions[region].hasLocale(locale)) {
116
+ return region;
117
+ }
118
+ }
119
+
120
+ // If we get here, the locale isn't in any currently supported region.
121
+ return 'unsupported';
122
+ }
123
+ };
@@ -0,0 +1,116 @@
1
+ # Static Asset Loader
2
+
3
+ This can be used by client-side web applications to request and receive the
4
+ static resources they require from a suitable static asset server. The
5
+ primary objective is to allow multiple discrete web applications on the same
6
+ page to converge on shared dependencies. Each dependency is only loaded
7
+ once and this reduces the overall page footprint.
8
+
9
+ ## Basic Usage
10
+
11
+ ### Configuration
12
+
13
+ The static asset loader must be configured with the following:
14
+
15
+ * A function to generate asset URLs from asset name and version strings.
16
+ * The name of the application namespace.
17
+
18
+ Applications sharing static assets must use the same namespace.
19
+
20
+ ```javascript
21
+ var staticAssetLoaderFactory = require('bv-ui-core/lib/staticAssetLoader');
22
+
23
+ var staticAssetLoader = staticAssetLoaderFactory.create({
24
+ /**
25
+ * Convert one or more asset strings into a static asset service URL.
26
+ *
27
+ * The details depend on the static asset server capabilities. This is an
28
+ * example implementation for a server capable of delivering asset bundles.
29
+ *
30
+ * @param {String[]} An array of asset names and version: ['jQuery@2.0.0'].
31
+ * @return {String} The URL for the asset.
32
+ */
33
+ generateUrl: function (assetNames, namespaceName) {
34
+ var filename = encodeURIComponent(assetNames.slice(0).sort().join('+')) + '.js';
35
+
36
+ namespaceName = encodeURIComponent(namespaceName);
37
+ return 'https://static.example.com/1/asset/' + namespaceName + '/' + filename;
38
+ },
39
+
40
+ /**
41
+ * Provide the name of a namespace to hold the _staticAssetRegistry properties
42
+ * and methods. Applications sharing static assets must use the same
43
+ * namespace.
44
+ */
45
+ namespaceName: 'EXAMPLE'
46
+ });
47
+ ```
48
+
49
+ ### Require Assets
50
+
51
+ When the application requires a resource of a supported type, use the client to
52
+ request that resource via the `require` method.
53
+
54
+ ```javascript
55
+ // Request the desired assets. For example:
56
+ staticAssetLoader.require([
57
+ 'underscore@1.0.0',
58
+ 'jquery-custom@1.11.1',
59
+ 'backbone@1.2.0'
60
+ ], function (_, $, Backbone) {
61
+ // This callback will be invoked when all of the required assets have been
62
+ // resolved.
63
+ });
64
+ ```
65
+
66
+ Note that when requesting an asset that has dependency assets - for example,
67
+ Backbone depends on jQuery and either Underscore or Lodash - it is necessary to
68
+ request those dependencies in the same `require` call.
69
+
70
+ ### Define Assets
71
+
72
+ Applications that ship with resources that might be useful to other applications
73
+ on the page can provide those resources via the static asset loader. This again
74
+ allows for a convergence of dependencies and reduced page weight.
75
+
76
+ ```javascript
77
+ // After definition, staticAssetLoader.require('jquery-custom@1.11.1', callback)
78
+ // will call back immediately with the provided instance of jQuery.
79
+ staticAssetLoader.define('jquery-custom@1.11.1', jQuery);
80
+ ```
81
+
82
+ ### Order and Timing of Calls
83
+
84
+ Calls to `require` and `define` can be made in any order and at any time, for so
85
+ long as each call to `require` includes all needed dependency assets. Obviously
86
+ it is usually better to make these calls as early as possible in the application
87
+ initialization process so as to minimize lead time.
88
+
89
+ ## Form of the Static Asset Service
90
+
91
+ The static assert service is a web service that can respond to requests such as:
92
+
93
+ `https://static.example.com/1/asset/namespace/jquery@1.11.1.js`
94
+
95
+ Importantly, the static asset service must be capable of serving bundles of
96
+ assets in response to a single request, with the assets listed in alphabetical
97
+ order. E.g.:
98
+
99
+ `https://static.example.com/1/asset/namespace/backbone@1.2.0+jquery@1.11.1.js`
100
+
101
+ The returned Javascript must both define the assets themselves and then provide
102
+ the asset to the static asset client in the same way as described above. For
103
+ example:
104
+
105
+ ```javascript
106
+ (function (define) {
107
+
108
+ // Asset source code goes here.
109
+ //
110
+ // ...
111
+
112
+ // Then provide the asset or assets to the client.
113
+ define('asset@1.0.0', asset);
114
+
115
+ }(window[namespace]._staticAssetRegistry.define));
116
+ ```
@@ -0,0 +1,248 @@
1
+ /**
2
+ * @fileOverview
3
+ * A static asset loader to pull resources at particular versions from a static
4
+ * asset service. This could be as simple as an S3-backed CDN, or a webserver
5
+ * serving from a directory of files. E.g. a server that can respond to requests
6
+ * such as:
7
+ *
8
+ * https://static.example.com/1/asset/jquery@1.11.1.js
9
+ *
10
+ * Importantly, the static asset service must be capable of serving bundles of
11
+ * assets in a single request, where these assets are listed in alphabetical
12
+ * order. E.g.:
13
+ *
14
+ * https://static.example.com/1/asset/backbone@1.2.0+jquery@1.11.1.js
15
+ *
16
+ * This is primarily intended to reduce asset duplication among multiple
17
+ * distinct client applications running on the same page.
18
+ *
19
+ * The returned Javascript must both define the assets themselves and then
20
+ * provide the asset to the static asset client. For example:
21
+ *
22
+ * (function (define) {
23
+ *
24
+ * // Asset source code goes here.
25
+ * //
26
+ * // ...
27
+ *
28
+ * // Then provide the asset or assets to the client.
29
+ * define('asset@1.0.0', ['dependency@1.0.0'], asset);
30
+ *
31
+ * }(window[namespace]._staticAssetRegistry.define));
32
+ */
33
+
34
+ var loader = require('../loader');
35
+ var namespacer = require('../namespacer');
36
+
37
+ /**
38
+ * Invoke the provided function on each element of the array.
39
+ *
40
+ * @param {Array} arr An array.
41
+ * @param {Function} fn Function to invoke as fn(element, index, array).
42
+ */
43
+ function forEach (arr, fn) {
44
+ if (!arr || !arr.length) {
45
+ return;
46
+ }
47
+
48
+ for (var index = 0; index < arr.length; index++) {
49
+ fn(arr[index], index, arr);
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Create a new array containing the results of applying the provided function
55
+ * to each element.
56
+ *
57
+ * @param {Array} arr An array.
58
+ * @param {Function} fn Function to invoke as fn(element, index, array).
59
+ * @return {Array} The mapped array.
60
+ */
61
+ function map (arr, fn) {
62
+ var mappedArray = [];
63
+
64
+ forEach(arr, function (val, index, originalArray) {
65
+ mappedArray.push(fn(val, index, originalArray));
66
+ });
67
+
68
+ return mappedArray;
69
+ }
70
+
71
+ module.exports = {
72
+ /**
73
+ * A factory function for static asset loaders.
74
+ *
75
+ * See the documentation for the properties required for the configuration
76
+ * object.
77
+ *
78
+ * @param {Object} config Configuration.
79
+ * @return {Object} A static asset loader instance.
80
+ */
81
+ create: function staticAssetLoaderFactory (config) {
82
+ var generateUrl = config.generateUrl;
83
+ var namespaceName = config.namespaceName;
84
+
85
+ if (!namespaceName) {
86
+ throw new Error('Cannot initialize staticAssetLoader without a namespace name.');
87
+ }
88
+
89
+ if (typeof generateUrl !== 'function') {
90
+ throw new Error('Cannot initialize staticAssetLoader without a URL generation function.');
91
+ }
92
+
93
+ var loaderOptions = config.loaderOptions || {};
94
+ var NS = namespacer.namespace(namespaceName);
95
+ var registry = NS._staticAssetRegistry = NS._staticAssetRegistry || {
96
+ // Stores defined entities by asset name. E.g.:
97
+ //
98
+ // {
99
+ // 'jquery@1.11.1': jquery,
100
+ // ...
101
+ // }
102
+ assets: {},
103
+ // Stores requests indexed by included asset, each of which can specify
104
+ // multiple assets. Has the form:
105
+ //
106
+ // {
107
+ // 'jquery@1.11.1': {
108
+ // assetNames: ['jquery@1.11.1', ...],
109
+ // callback: function (jQuery, ...),
110
+ // complete: false
111
+ // },
112
+ // ...
113
+ // }
114
+ requests: {},
115
+ // Set below to the _define function, so that loaded asset wrappers can
116
+ // use it to define assets.
117
+ define: undefined
118
+ };
119
+
120
+ /**
121
+ * Given a request object, call back with the list of requested assets, in
122
+ * the same order as requested, if all assets are defined and the callback
123
+ * has not already occurred.
124
+ *
125
+ * @param {Object} request
126
+ */
127
+ function callbackIfComplete (request) {
128
+ // Already called back?
129
+ if (request.complete) {
130
+ return;
131
+ }
132
+
133
+ var complete = true;
134
+ var assets = map(request.assetNames, function (assetName) {
135
+ var asset = registry.assets[assetName];
136
+
137
+ if (!asset) {
138
+ complete = false;
139
+ }
140
+
141
+ return asset;
142
+ });
143
+
144
+ if (complete) {
145
+ request.complete = true;
146
+ if (typeof request.callback === 'function') {
147
+ request.callback.apply(null, assets);
148
+ }
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Define assets. Usage is:
154
+ *
155
+ * _define('x@1.0.0', x);
156
+ *
157
+ * Later calls to _require() for this asset name will yield the defined
158
+ * asset.
159
+ *
160
+ * Any currently pending calls to _require() now satisfied will resolve.
161
+ *
162
+ * @param {String} assetName The name of the asset. E.g. 'jquery@1.11.1'.
163
+ * @param {Mixed} asset The asset.
164
+ */
165
+ function _define (assetName, asset) {
166
+ // Ignore an attempt to redefine an asset. This can cause potentially
167
+ // interesting problems when some references point to instance A and
168
+ // others to instance B.
169
+ if (registry.assets[assetName]) {
170
+ return;
171
+ }
172
+
173
+ registry.assets[assetName] = asset;
174
+ forEach(registry.requests[assetName], callbackIfComplete);
175
+ }
176
+
177
+ /**
178
+ * Require assets and provide a callback to be invoked when the assets are
179
+ * resolved.
180
+ *
181
+ * @param {String[]} assetNames An array of supported asset names.
182
+ * @param {Function} callback The callback to be called when the assets are
183
+ * resolved, with the assets as its arguments in the same order as the
184
+ * provided assets array.
185
+ */
186
+ function _require (assetNames, callback) {
187
+ var request = {
188
+ assetNames: assetNames,
189
+ callback: callback,
190
+ complete: false
191
+ };
192
+
193
+ forEach(assetNames, function (assetName) {
194
+ registry.requests[assetName] = registry.requests[assetName] || [];
195
+ registry.requests[assetName].push(request);
196
+ });
197
+
198
+ // Are all the dependencies already in place, and so we can skip the
199
+ // loading of assets?
200
+ callbackIfComplete(request);
201
+ if (request.complete) {
202
+ return;
203
+ }
204
+
205
+ var assetNamesToBeRequested = [];
206
+
207
+ forEach(assetNames, function (assetName) {
208
+ // The asset already exists.
209
+ if (registry.assets[assetName]) {
210
+ return;
211
+ }
212
+ // There is already a pending request for this asset. So don't issue
213
+ // another request, just ensure that a response is delivered to this
214
+ // request when the pending request completes.
215
+ else if (registry.requests[assetName].length > 1) {
216
+ return;
217
+ }
218
+ // There is no pending request; we need to queue it for requesting.
219
+ else {
220
+ assetNamesToBeRequested.push(assetName);
221
+ }
222
+ });
223
+
224
+ // If we queued any requests, now we need to make them.
225
+ if (assetNamesToBeRequested.length) {
226
+ loader.loadScript(
227
+ generateUrl(assetNamesToBeRequested, namespaceName),
228
+ loaderOptions,
229
+ function (error) {
230
+ if (error) {
231
+ throw new Error(
232
+ 'Failed to load ' + assetNamesToBeRequested.join(', ') + ': ' + error.toString());
233
+ }
234
+ }
235
+ );
236
+ }
237
+ }
238
+
239
+ // This is made available for responses from the static asset server to use.
240
+ registry.define = _define;
241
+
242
+ // Return the static asset loader interface.
243
+ return {
244
+ require: _require,
245
+ define: _define
246
+ };
247
+ }
248
+ };