loada 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
data/lib/loada.min.js ADDED
@@ -0,0 +1 @@
1
+ !function(){var a,b=function(a,b){return function(){return a.apply(b,arguments)}},c=[].slice;a=document.getElementsByTagName("head")[0],this.Loada=function(){function d(a,c){var d,e;if(this.set=a,this._loadGroup=b(this._loadGroup,this),this.options={prefix:"loada",localStorage:!0},this.requires={input:[],set:{},length:0},this.set||(this.set="*"),c)for(d in c)e=c[d],this.options[d]=e;this.key=""+this.options.prefix+"."+this.set,this.setup()}return d.prototype.Progress=function(){function a(a,b){this.count=a,this.progressCallback=b,this.data={}}return a.prototype.set=function(a,b){return this.data[a]=Math.min(b,100),"function"==typeof this.progressCallback?this.progressCallback(this.total()):void 0},a.prototype.total=function(){var a,b,c,d;c=0,d=this.data;for(a in d)b=d[a],c+=Math.round(100*(b/this.count))/100;return c},a}(),d.set=function(){return function(a,b,c){c.prototype=a.prototype;var d=new c,e=a.apply(d,b);return Object(e)===e?e:d}(this,arguments,function(){})},d.prototype.setup=function(){return this.options.localStorage&&(this.storage=localStorage[this.key]||{},"string"==typeof this.storage)?this.storage=JSON.parse(localStorage[this.key]):void 0},d.prototype.save=function(){return localStorage[this.key]=JSON.stringify(this.storage)},d.prototype.clear=function(){return delete localStorage[this.key]},d.prototype.get=function(a){var b;return null!=(b=this.storage[a])?b.source:void 0},d.prototype.expire=function(){var a,b,c,d,e,f,g,h,i=this;f=new Date,a=function(a){return a.expirationDate&&new Date(a.expirationDate)<=f},b=function(){return!i.requires.set[d]},c=function(a){return i.requires.set[d].revision!==a.revision},g=this.storage,h=[];for(d in g)e=g[d],a(e)||b(e)||c(e)?h.push(delete this.storage[d]):h.push(void 0);return h},d.prototype.require=function(){var a,b,d,e,f,g;for(a=1<=arguments.length?c.call(arguments,0):[],e=0,f=a.length;f>e;e++)b=a[e],b.key||(b.key=b.url),b.type||(b.type=null!=(g=b.url)?g.split(".").pop():void 0),null==b.localStorage&&(b.localStorage=!0),null==b.require&&(b.require=!0),b.expires&&(d=new Date,b.expirationDate=d.setTime(d.getTime()+1e3*60*60*b.expires)),"js"===b.type||"css"===b.type||"text"===b.type?this.requires.set[b.key]=b:console.error("Unknown asset type for "+b.url+" – skipped");return this.requires.length+=a.length,this.requires.input.push(a)},d.prototype.load=function(a){var b,c,d=this;return a||(a={}),this.options.localStorage&&this.expire(),c=new this.Progress(this.requires.length,a.progress),b=0,this._ensureSizes(null!=a.progress,function(){var e,f,g,h,i;for(h=d.requires.input,i=[],f=0,g=h.length;g>f;f++)e=h[f],b++,i.push(d._loadGroup(e.slice(0),c,function(){return b--,0===b?(d.save(),"function"==typeof a.success?a.success():void 0):void 0}));return i})},d.prototype._ensureSizes=function(a,b){var c,d,e,f,g,h,i=this;if(!a){f=this.requires.set;for(c in f)d=f[c],d.size=0;return b()}e=0,g=this.requires.set,h=[];for(c in g)d=g[c],h.push(function(a){return null==a.size?(e++,i._ajax("HEAD",a.url,function(c){var d;return e--,d=c.getResponseHeader("Content-Length"),d&&(d=parseInt(d)),a.size=d||0,0===e?b():void 0})):void 0}(d));return h},d.prototype._loadGroup=function(a,b,c){var d,e,f=this;return d=a.shift(),d?this.options.localStorage&&this.storage[d.key]&&d.localStorage?(null!=b&&b.set(d.key,100),this.storage[d.key].require&&this._inject(this.storage[d.key]),this._loadGroup(a,b,c)):(e=this.options.localStorage?"_loadAJAX":"_loadInline",this[e](d,b,function(){return f.options.localStorage&&(f.storage[d.key]=d),d.require&&f._inject(d),f._loadGroup(a,b,c)})):c()},d.prototype._loadAJAX=function(a,b,c){var d,e;return e=this._ajax("GET",a.url,function(e){return a.source=e.responseText,clearInterval(d),null!=b&&b.set(a.key,100),c()}),a.size>0?d=setInterval(function(){var c;return c=Math.round(100*100*(e.responseText.length/a.size))/100,null!=b?b.set(a.key,c):void 0},100):void 0},d.prototype._loadInline=function(b,c,d){var e,f,g;return"js"!==b.type?(console.error("Attempt to load something other than JS without localStorage."),console.error(""+b.url+" is not loaded!"),null!=c&&c.set(b.key,100),d()):(g=document.createElement("script"),e=!1,f=function(){return e||null!=this.readyState&&"loaded"!==this.readyState&&"complete"!==this.readyState?void 0:(e=!0,null!=c&&c.set(b.key,100),"function"==typeof d&&d(),g.onload=g.onreadystatechange=null)},g.onload=g.onreadystatechange=f,g.src=b.url,a.appendChild(g))},d.prototype._inject=function(b){var c,d;return"js"===b.type?(c=document.createElement("script"),c.defer=!0,c.text=b.source,a.appendChild(c)):"css"===b.type?(d=document.createElement("style"),d.innerHTML=b.source,a.appendChild(d)):void 0},d.prototype._ajax=function(a,b,c){var d;return d=window.XMLHttpRequest?new XMLHttpRequest:new ActiveXObject("Microsoft.XMLHTTP"),d.open(a,b,1),d.onreadystatechange=function(){return d.readyState>3?"function"==typeof c?c(d):void 0:void 0},d.send(),d},d}()}.call(this);
data/lib/loada.rb ADDED
@@ -0,0 +1,14 @@
1
+ require 'json'
2
+
3
+ module Loada
4
+ PACKAGE = File.expand_path("../../package.json", __FILE__)
5
+
6
+ # Converting semver to the notation compatible with rubygems
7
+ VERSION = JSON.parse(File.read(PACKAGE))['version'].gsub '-', '.'
8
+
9
+ def self.assets_paths
10
+ [
11
+ File.expand_path('../../src', __FILE__)
12
+ ]
13
+ end
14
+ end
data/loada.gemspec ADDED
@@ -0,0 +1,17 @@
1
+ require File.expand_path("../lib/loada.rb", __FILE__)
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "loada"
5
+ s.version = Loada::VERSION
6
+ s.platform = Gem::Platform::RUBY
7
+ s.summary = "The Loada"
8
+ s.email = "boris@staal.io"
9
+ s.homepage = "http://github.com/inossidabile/loada"
10
+ s.description = "A gem wrapper to include Loada via the asset pipeline"
11
+ s.authors = ['Boris Staal']
12
+
13
+ s.files = `git ls-files`.split("\n")
14
+ s.require_paths = ["lib"]
15
+
16
+ s.add_dependency 'sprockets'
17
+ end
data/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "loada",
3
+ "version": "0.1.3",
4
+ "description": "Browser assets preloader with the support of localStorage-based caching and progress tracking",
5
+ "repository": "",
6
+ "keywords": [
7
+ "browser",
8
+ "preloader",
9
+ "assets"
10
+ ],
11
+ "author": "Boris Staal",
12
+ "license": "MIT",
13
+ "devDependencies": {
14
+ "grunt": "~0.4.1",
15
+ "grunt-contrib-coffee": "~0.7.0",
16
+ "grunt-contrib-jasmine": "~0.5.1",
17
+ "grunt-contrib-connect": "~0.3.0",
18
+ "grunt-release": "~0.3.5",
19
+ "grunt-coffeelint": "0.0.6",
20
+ "grunt-contrib-watch": "~0.4.4",
21
+ "bower": "~0.9.2",
22
+ "grunt-contrib-uglify": "~0.2.2"
23
+ }
24
+ }
@@ -0,0 +1,2 @@
1
+ afterEach ->
2
+ localStorage.clear()
@@ -0,0 +1,318 @@
1
+ describe "Loada", ->
2
+
3
+ it "initializes properly", ->
4
+ set = Loada.set(undefined, localStorage: false)
5
+ expect(set.set).toEqual '*'
6
+ expect(set.options.localStorage).toBeFalsy()
7
+ expect(set.storage).toBeUndefined()
8
+
9
+ localStorage['loada.foo'] = JSON.stringify('foo': 'bar')
10
+
11
+ set = Loada.set 'foo'
12
+ expect(set.set).toEqual 'foo'
13
+ expect(set.options.localStorage).toBeTruthy()
14
+ expect(set.storage).toEqual {'foo': 'bar'}
15
+
16
+ describe "expiration", ->
17
+ it "cuts by date", ->
18
+ expired = new Date
19
+ unexpired = new Date
20
+ unexpired.setTime(unexpired.getTime() + 60*1000)
21
+
22
+ libraries =
23
+ 'foo.js': {expirationDate: expired}
24
+ 'bar.js': {expirationDate: unexpired}
25
+ localStorage['loada.*'] = JSON.stringify libraries
26
+
27
+ set = Loada.set()
28
+ set.require url: 'foo.js'
29
+ set.require url: 'bar.js'
30
+
31
+ expect(set.storage['foo.js']).toBeDefined()
32
+ expect(set.storage['bar.js']).toBeDefined()
33
+
34
+ set.expire()
35
+ expect(set.storage['foo.js']).toBeUndefined()
36
+ expect(set.storage['bar.js']).toBeDefined()
37
+
38
+ it "cuts by existance", ->
39
+ localStorage['loada.*'] = JSON.stringify { 'foo.js': {}, 'bar.js': {} }
40
+
41
+ set = Loada.set()
42
+ set.require url: 'bar.js'
43
+
44
+ expect(set.storage['foo.js']).toBeDefined()
45
+ expect(set.storage['bar.js']).toBeDefined()
46
+
47
+ set.expire()
48
+ expect(set.storage['foo.js']).toBeUndefined()
49
+ expect(set.storage['bar.js']).toBeDefined()
50
+
51
+ it "cuts by revision", ->
52
+ localStorage['loada.*'] = JSON.stringify
53
+ 'foo.js': {revision: 1}
54
+ 'bar.js': {}
55
+ 'baz.js': {}
56
+
57
+ set = Loada.set()
58
+ set.require url: 'foo.js', revision: 1
59
+ set.require url: 'bar.js', revision: 1
60
+ set.require url: 'baz.js'
61
+
62
+ expect(set.storage['foo.js']).toBeDefined()
63
+ expect(set.storage['bar.js']).toBeDefined()
64
+ expect(set.storage['baz.js']).toBeDefined()
65
+
66
+ set.expire()
67
+ expect(set.storage['foo.js']).toBeDefined()
68
+ expect(set.storage['bar.js']).toBeUndefined()
69
+ expect(set.storage['baz.js']).toBeDefined()
70
+
71
+ describe "size ensurer", ->
72
+ beforeEach ->
73
+ @set = Loada.set()
74
+ @set.require url: 'foo.js', size: 100
75
+ @set.require url: 'bar.js'
76
+ @set.require url: 'baz.js'
77
+
78
+ it "zerofills with no progress tracking", ->
79
+ callback = sinon.spy()
80
+ @set._ensureSizes false, callback
81
+ expect(callback.callCount).toEqual 1
82
+ expect(@set.requires.set['foo.js'].size).toEqual 0
83
+
84
+ it "gets sizes with progress tracking", ->
85
+ callback = sinon.spy()
86
+ server = sinon.fakeServer.create()
87
+ @set._ensureSizes true, callback
88
+
89
+ waits 0
90
+
91
+ runs ->
92
+ expect(server.requests[0].url).toEqual 'bar.js'
93
+ expect(server.requests[1].url).toEqual 'baz.js'
94
+
95
+ server.requests[0].respond 200, {'Content-Length': '100'}, ''
96
+ server.requests[1].respond 200, {}, ''
97
+ server.restore()
98
+
99
+ waits 0
100
+
101
+ runs ->
102
+ expect(callback.callCount).toEqual 1
103
+ expect(@set.requires.set['foo.js'].size).toEqual 100
104
+ expect(@set.requires.set['bar.js'].size).toEqual 100
105
+ expect(@set.requires.set['baz.js'].size).toEqual(0)
106
+
107
+ describe "loader", ->
108
+ beforeEach ->
109
+ @server = sinon.fakeServer.create()
110
+ @set = Loada.set()
111
+ sinon.stub @set, '_inject'
112
+
113
+ afterEach ->
114
+ @set._inject.restore()
115
+ @server.restore()
116
+
117
+ it "gets single from cache", ->
118
+ localStorage['loada.*'] = JSON.stringify
119
+ 'foo.js': {require: true}
120
+
121
+ @set.setup()
122
+ callback = sinon.spy()
123
+ @set.require url: 'foo.js'
124
+
125
+ @set._loadGroup @set.requires.input[0], null, callback
126
+
127
+ waits 0
128
+
129
+ runs ->
130
+ expect(@server.requests.length).toEqual 0
131
+ expect(callback.callCount).toEqual 1
132
+ expect(@set._inject.callCount).toEqual 1
133
+ expect(@set._inject.args[0][0]).toEqual {require: true}
134
+
135
+ it "gets single from net", ->
136
+ callback = sinon.spy()
137
+ @set.require url: 'foo.js'
138
+
139
+ @set._loadGroup @set.requires.input[0], null, callback
140
+
141
+ waits 0
142
+
143
+ runs ->
144
+ @server.requests[0].respond 200, {}, 'foobar'
145
+
146
+ waits 0
147
+
148
+ runs ->
149
+ library =
150
+ url: 'foo.js'
151
+ key: 'foo.js'
152
+ type: 'js'
153
+ source: 'foobar'
154
+ localStorage: true
155
+ require: true
156
+
157
+ expect(@server.requests.length).toEqual 1
158
+ expect(@set.storage['foo.js']).toEqual library
159
+ expect(callback.callCount).toEqual 1
160
+ expect(@set._inject.callCount).toEqual 1
161
+ expect(@set._inject.args[0][0]).toEqual library
162
+
163
+ it "orders properly", ->
164
+ @set.require(
165
+ { url: 'foo.js' },
166
+ { url: 'bar.js' }
167
+ )
168
+ @set.require url: 'baz.js'
169
+
170
+ @set._loadGroup @set.requires.input[0], null, ->
171
+ @set._loadGroup @set.requires.input[1], null, ->
172
+
173
+ waits 0
174
+
175
+ runs ->
176
+ @server.requests[0].respond 200, {}, 'foobar'
177
+ @server.requests[1].respond 200, {}, 'foobar'
178
+ @server.requests[2].respond 200, {}, 'foobar'
179
+
180
+ waits 0
181
+
182
+ runs ->
183
+ expect(@server.requests[0].url).toEqual 'foo.js'
184
+ expect(@server.requests[1].url).toEqual 'baz.js'
185
+ expect(@server.requests[2].url).toEqual 'bar.js'
186
+
187
+ describe "progress", ->
188
+ it "tracks with cache", ->
189
+ progress = {set: sinon.spy()}
190
+
191
+ localStorage['loada.*'] = JSON.stringify
192
+ 'foo.js': {}
193
+ 'bar.js': {}
194
+
195
+ @set.setup()
196
+ @set.require(
197
+ { url: 'foo.js' },
198
+ { url: 'bar.js' }
199
+ )
200
+
201
+ @set._loadGroup @set.requires.input[0], progress, ->
202
+
203
+ expect(progress.set.callCount).toEqual 2
204
+ expect(progress.set.args[0]).toEqual ['foo.js', 100]
205
+ expect(progress.set.args[1]).toEqual ['bar.js', 100]
206
+
207
+ it "tracks with net", ->
208
+ progress = {set: sinon.spy()}
209
+
210
+ @set.require(
211
+ { url: 'foo.js' },
212
+ { url: 'bar.js' }
213
+ )
214
+
215
+ @set._loadGroup @set.requires.input[0], progress, ->
216
+
217
+ waits 0
218
+
219
+ runs ->
220
+ @server.requests[0].respond 200, {}, 'foobar'
221
+ @server.requests[1].respond 200, {}, 'foobar'
222
+
223
+ waits 0
224
+
225
+ runs ->
226
+ expect(progress.set.callCount).toEqual 2
227
+ expect(progress.set.args[0]).toEqual ['foo.js', 100]
228
+ expect(progress.set.args[1]).toEqual ['bar.js', 100]
229
+
230
+ it "loads through inlining", ->
231
+ window.TEST = 0
232
+
233
+ set = Loada.set()
234
+ server = sinon.fakeServer.create()
235
+ callback = sinon.spy()
236
+
237
+ set._loadInline {url: 'spec/support/test.js', key: 'test.js', type: 'js'}, null, callback
238
+
239
+ waits 100
240
+
241
+ runs ->
242
+ expect(callback.callCount).toEqual 1
243
+ expect(window.TEST).toEqual 1
244
+ delete window.TEST
245
+
246
+ it "injects", ->
247
+ window.TEST = 0
248
+
249
+ set = Loada.set()
250
+ set._inject source: 'window.TEST = 1', type: 'js'
251
+
252
+ expect(window.TEST).toEqual 1
253
+ delete window.TEST
254
+
255
+ it "caches", ->
256
+ set = Loada.set()
257
+ server = sinon.fakeServer.create()
258
+ sinon.stub set, '_inject'
259
+ set.require url: 'foo.js'
260
+ set.require url: 'bar.js'
261
+
262
+ set.load()
263
+
264
+ waits 0
265
+
266
+ runs ->
267
+ expect(server.requests.length).toEqual 2
268
+ server.requests[0].respond 200, {}, 'foobar'
269
+ server.requests[1].respond 200, {}, 'foobar'
270
+
271
+ waits 0
272
+
273
+ runs ->
274
+ set.load()
275
+
276
+ runs ->
277
+ expect(server.requests.length).toEqual 2
278
+
279
+ runs ->
280
+ server.restore()
281
+ set._inject.restore()
282
+
283
+ it "loads", ->
284
+ window.TEST = 0
285
+
286
+ progress = sinon.spy()
287
+ success = sinon.spy()
288
+
289
+ set = Loada.set()
290
+ set.require url: 'spec/support/test.js'
291
+ set.load
292
+ progress: progress
293
+ success: success
294
+
295
+ waits 100
296
+
297
+ runs ->
298
+ expect(progress.callCount).toEqual 1
299
+ expect(progress.args[0][0]).toEqual 100
300
+ expect(success.callCount).toEqual 1
301
+ expect(window.TEST).toEqual 1
302
+
303
+ delete window.TEST
304
+
305
+ it "loads text", ->
306
+ set = Loada.set()
307
+ server = sinon.fakeServer.create()
308
+ set.require url: 'foo', type: 'text'
309
+
310
+ set.load()
311
+
312
+ waits 0
313
+
314
+ runs ->
315
+ expect(server.requests.length).toEqual 1
316
+ server.requests[0].respond 200, {}, 'foobar'
317
+
318
+ expect(set.get 'foo').toEqual 'foobar'
@@ -0,0 +1 @@
1
+ window.TEST = 1
data/src/loada.coffee ADDED
@@ -0,0 +1,294 @@
1
+ $head = document.getElementsByTagName("head")[0]
2
+
3
+ #
4
+ # Loada is the kickass in-browser assets (JS/CSS) loader supporting localStorage
5
+ # and progress tracking.
6
+ #
7
+ # @see https://github.com/inossidabile/loada/
8
+ #
9
+ class @Loada
10
+ Progress: class
11
+ constructor: (@count, @progressCallback) ->
12
+ @data = {}
13
+
14
+ set: (key, percent) ->
15
+ @data[key] = Math.min(percent, 100.0)
16
+ @progressCallback? @total()
17
+
18
+ total: ->
19
+ total = 0
20
+ total += Math.round(percent/@count*100)/100 for key, percent of @data
21
+ total
22
+
23
+ #
24
+ # Alias for the #{constructor}
25
+ #
26
+ @set: ->
27
+ new @ arguments...
28
+
29
+ #
30
+ # @param [String] Name of the libraries set
31
+ # @param [Object]
32
+ #
33
+ constructor: (@set, options) ->
34
+ @options =
35
+ prefix: 'loada'
36
+ localStorage: true
37
+
38
+ @requires =
39
+ input: []
40
+ set: {}
41
+ length: 0
42
+
43
+ @set ||= '*'
44
+ @options[k] = v for k,v of options if options
45
+ @key = "#{@options.prefix}.#{@set}"
46
+
47
+ @setup()
48
+
49
+ #
50
+ # Loads current available cache for the current set into the instance
51
+ #
52
+ setup: ->
53
+ if @options.localStorage
54
+ @storage = localStorage[@key] || {}
55
+
56
+ if typeof(@storage) == 'string'
57
+ @storage = JSON.parse(localStorage[@key])
58
+
59
+ #
60
+ # Saves current set state to localStorage
61
+ #
62
+ save: ->
63
+ localStorage[@key] = JSON.stringify @storage
64
+
65
+ #
66
+ # Removes localStorage entry bound to current set
67
+ #
68
+ clear: -> delete localStorage[@key]
69
+
70
+ #
71
+ # Gets raw source of an asset
72
+ #
73
+ # @param key [String] Key of the asset
74
+ #
75
+ get: (key) ->
76
+ @storage[key]?.source
77
+
78
+ #
79
+ # Cleans up current state
80
+ #
81
+ # Removes entries that are:
82
+ # * expired by date (`expires` option)
83
+ # * got new revision (`revision` option)
84
+ # * not found in the current requires list
85
+ #
86
+ expire: ->
87
+ now = new Date
88
+
89
+ byDate = (library) =>
90
+ library.expirationDate && new Date(library.expirationDate) <= now
91
+
92
+ byExistance = (library) =>
93
+ !@requires.set[key]
94
+
95
+ byRevision = (library) =>
96
+ @requires.set[key].revision != library.revision
97
+
98
+ for key, library of @storage
99
+
100
+ if byDate(library) || byExistance(library) || byRevision(library)
101
+ delete @storage[key]
102
+
103
+ #
104
+ # Adds library that should be loaded
105
+ #
106
+ # All the libraries passed within one call will be loaded
107
+ # successive. Separate {#require} calls are loading in parallel.
108
+ #
109
+ # Library object consists of the following options:
110
+ # * **url**: location of the asset to load
111
+ # * **key**: storage key (default to url)
112
+ # * **type**: js/css (defaults to url extension parsing)
113
+ # * **revision**: manual revision number – triggers cache bust whenever it changes
114
+ # * **expires**: amount of hours to keep entry for (defaults to unlimited cache)
115
+ # * **cache**: whether localStorage should be used for particular asset
116
+ # * **size**: asset download size (defaults to additional HEAD request result)
117
+ #
118
+ # @param libraries [Object] Library object
119
+ #
120
+ require: (libraries...) ->
121
+ for library in libraries
122
+ library.key ||= library.url
123
+ library.type ||= library.url?.split('.').pop()
124
+ library.localStorage = true unless library.localStorage?
125
+ library.require = true unless library.require?
126
+
127
+ if library.expires
128
+ now = new Date
129
+ library.expirationDate = now.setTime(now.getTime() + library.expires*60*60*1000)
130
+
131
+ if library.type == 'js' || library.type == 'css' || library.type == 'text'
132
+ @requires.set[library.key] = library
133
+ else
134
+ console.error "Unknown asset type for #{library.url} – skipped"
135
+
136
+ @requires.length += libraries.length
137
+ @requires.input.push libraries
138
+
139
+ #
140
+ # Starts the inclusion of required libraries
141
+ #
142
+ # Accepts following options:
143
+ # * **success**: gets called as soon as all the assets are loaded
144
+ # * **progress(percent)**: ticks from time to time passing you curent download progress
145
+ #
146
+ # @param [Object] callbacks List of callbacks
147
+ #
148
+ load: (callbacks) ->
149
+ callbacks ||= {}
150
+ @expire() if @options.localStorage
151
+
152
+ progress = new @Progress(@requires.length, callbacks.progress)
153
+ loaders = 0
154
+
155
+ @_ensureSizes callbacks.progress?, =>
156
+ for group in @requires.input
157
+ loaders++
158
+
159
+ @_loadGroup group.slice(0), progress, =>
160
+ loaders--
161
+ if loaders == 0
162
+ @save()
163
+ callbacks.success?()
164
+
165
+ #
166
+ # Ensures every asset has 'size' option
167
+ #
168
+ # Runs HEAD request trying to parse Content-Length header for
169
+ # those that don't have.
170
+ #
171
+ # @private
172
+ #
173
+ _ensureSizes: (perform, callback) ->
174
+ unless perform
175
+ library.size = 0 for key, library of @requires.set
176
+ return callback()
177
+
178
+ requests = 0
179
+
180
+ for key, library of @requires.set
181
+ do (library) =>
182
+ unless library.size?
183
+ requests++
184
+
185
+ @_ajax 'HEAD', library.url, (xhr) ->
186
+ requests--
187
+ size = xhr.getResponseHeader('Content-Length')
188
+ size = parseInt(size) if size
189
+ library.size = size || 0
190
+ callback() if requests == 0
191
+
192
+ #
193
+ # Loads one require group successively
194
+ #
195
+ # @private
196
+ #
197
+ _loadGroup: (group, progress, callback) =>
198
+ library = group.shift()
199
+
200
+ return callback() unless library
201
+
202
+ if @options.localStorage && @storage[library.key] && library.localStorage
203
+ progress?.set library.key, 100
204
+ @_inject @storage[library.key] if @storage[library.key].require
205
+ @_loadGroup group, progress, callback
206
+ else
207
+ method = if @options.localStorage
208
+ "_loadAJAX"
209
+ else
210
+ "_loadInline"
211
+
212
+ @[method] library, progress, =>
213
+ @storage[library.key] = library if @options.localStorage
214
+ @_inject library if library.require
215
+ @_loadGroup group, progress, callback
216
+
217
+ #
218
+ # Loads one asset by AJAX query and stores it into instance
219
+ #
220
+ # @private
221
+ #
222
+ _loadAJAX: (library, progress, callback) ->
223
+ xhr = @_ajax 'GET', library.url, (xhr) =>
224
+ library.source = xhr.responseText
225
+ clearInterval poller
226
+ progress?.set library.key, 100
227
+ callback()
228
+
229
+ if library.size > 0
230
+ poller = setInterval (->
231
+ percent = Math.round(xhr.responseText.length / library.size * 100 * 100) / 100
232
+ progress?.set library.key, percent
233
+ ),
234
+ 100
235
+
236
+ #
237
+ # Loads one asset by inlining it into DOM
238
+ #
239
+ # @private
240
+ #
241
+ _loadInline: (library, progress, callback) ->
242
+ if library.type != 'js'
243
+ console.error "Attempt to load something other than JS without localStorage."
244
+ console.error "#{library.url} is not loaded!"
245
+ progress?.set library.key, 100
246
+ return callback()
247
+
248
+ script = document.createElement "script"
249
+ done = false
250
+
251
+ proceed = ->
252
+ if !done && (!@readyState? || @readyState == "loaded" || @readyState == "complete")
253
+ done = true
254
+ progress?.set library.key, 100
255
+ callback?()
256
+ script.onload = script.onreadystatechange = null
257
+
258
+ script.onload = script.onreadystatechange = proceed
259
+
260
+ script.src = library.url
261
+ $head.appendChild script
262
+
263
+ #
264
+ # Activates downloaded asset
265
+ #
266
+ # @private
267
+ #
268
+ _inject: (library) ->
269
+ if library.type == 'js'
270
+ script = document.createElement "script"
271
+ script.defer = true
272
+ script.text = library.source
273
+ $head.appendChild script
274
+ else if library.type == 'css'
275
+ style = document.createElement "style"
276
+ style.innerHTML = library.source
277
+ $head.appendChild style
278
+
279
+ #
280
+ # Starring custom XHR wrapper!
281
+ #
282
+ # @private
283
+ #
284
+ _ajax: (method, url, callback) ->
285
+ if window.XMLHttpRequest
286
+ xhr = new XMLHttpRequest
287
+ else
288
+ xhr = new ActiveXObject 'Microsoft.XMLHTTP'
289
+
290
+ xhr.open method, url, 1
291
+ xhr.onreadystatechange = -> callback?(xhr) if xhr.readyState > 3
292
+ xhr.send()
293
+
294
+ xhr