kojac 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.gitignore +7 -0
- data/Gemfile +17 -0
- data/Gemfile.lock +158 -0
- data/MIT-LICENSE +20 -0
- data/README.md +69 -0
- data/Rakefile +27 -0
- data/app/assets/javascripts/can_extensions.js +45 -0
- data/app/assets/javascripts/kojac.js +1230 -0
- data/app/assets/javascripts/kojac_canjs.js +191 -0
- data/app/assets/javascripts/kojac_ember.js +463 -0
- data/app/controllers/kojac_controller.rb +70 -0
- data/app/serializers/default_kojac_serializer.rb +10 -0
- data/diagram.odg +0 -0
- data/kojac.gemspec +36 -0
- data/lib/kojac/app_serialize.rb +29 -0
- data/lib/kojac/kojac_rails.rb +432 -0
- data/lib/kojac/ring_strong_parameters.rb +195 -0
- data/lib/kojac/version.rb +3 -0
- data/lib/kojac.rb +8 -0
- data/lib/tasks/kojac_tasks.rake +4 -0
- data/notes.txt +48 -0
- data/spec/.DS_Store +0 -0
- data/spec/can_cache_spec.js +87 -0
- data/spec/can_factory_spec.js +144 -0
- data/spec/can_model_spec.js +127 -0
- data/spec/demo/README.rdoc +261 -0
- data/spec/demo/Rakefile +7 -0
- data/spec/demo/app/assets/javascripts/application.js +15 -0
- data/spec/demo/app/assets/stylesheets/application.css +13 -0
- data/spec/demo/app/controllers/application_controller.rb +3 -0
- data/spec/demo/app/helpers/application_helper.rb +2 -0
- data/spec/demo/app/mailers/.gitkeep +0 -0
- data/spec/demo/app/models/.gitkeep +0 -0
- data/spec/demo/app/views/layouts/application.html.erb +14 -0
- data/spec/demo/config/application.rb +65 -0
- data/spec/demo/config/boot.rb +10 -0
- data/spec/demo/config/database.yml +25 -0
- data/spec/demo/config/environment.rb +5 -0
- data/spec/demo/config/environments/development.rb +37 -0
- data/spec/demo/config/environments/production.rb +67 -0
- data/spec/demo/config/environments/test.rb +37 -0
- data/spec/demo/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/demo/config/initializers/inflections.rb +15 -0
- data/spec/demo/config/initializers/mime_types.rb +5 -0
- data/spec/demo/config/initializers/secret_token.rb +7 -0
- data/spec/demo/config/initializers/session_store.rb +8 -0
- data/spec/demo/config/initializers/wrap_parameters.rb +14 -0
- data/spec/demo/config/locales/en.yml +5 -0
- data/spec/demo/config/routes.rb +58 -0
- data/spec/demo/config.ru +4 -0
- data/spec/demo/lib/assets/.gitkeep +0 -0
- data/spec/demo/log/.gitkeep +0 -0
- data/spec/demo/public/404.html +26 -0
- data/spec/demo/public/422.html +26 -0
- data/spec/demo/public/500.html +25 -0
- data/spec/demo/public/favicon.ico +0 -0
- data/spec/demo/script/rails +6 -0
- data/spec/ember_factory_spec.js +157 -0
- data/spec/ember_model_spec.js +179 -0
- data/spec/external/.DS_Store +0 -0
- data/spec/external/ember/.DS_Store +0 -0
- data/spec/external/ember/ember-1.0.0-rc.6.js +30970 -0
- data/spec/external/ember/handlebars-1.0.0-rc.4.js +2239 -0
- data/spec/external/jasmine/MIT.LICENSE +20 -0
- data/spec/external/jasmine/jasmine-html.js +616 -0
- data/spec/external/jasmine/jasmine.css +81 -0
- data/spec/external/jasmine/jasmine.js +2529 -0
- data/spec/external/jasmine.async.js +51 -0
- data/spec/external/jquery/jquery-1.7.2.js +9404 -0
- data/spec/external/jquery/jquery-1.7.2.min.js +4 -0
- data/spec/external/jquery/jquery-1.9.1.js +9597 -0
- data/spec/external/json2.js +480 -0
- data/spec/external/steal/steal-121115.js +2747 -0
- data/spec/external/steal/steal-3.2.3.js +2098 -0
- data/spec/external/steal/steal-3.2.3.min.js +27 -0
- data/spec/external/steal/steal.js +2466 -0
- data/spec/external/steal/steal.min.js +32 -0
- data/spec/external/steal/stealconfig.js +19 -0
- data/spec/external/underscore.js +1223 -0
- data/spec/external/underscore_plus.js +261 -0
- data/spec/external.zip +0 -0
- data/spec/handler_stack_spec.js +143 -0
- data/spec/helpers/SpecHelper.js +10 -0
- data/spec/kojac_caching_spec.js +105 -0
- data/spec/kojac_mock_spec.js +230 -0
- data/spec/kojac_model_spec.js +126 -0
- data/spec/kojac_object_spec.js +171 -0
- data/spec/kojac_operations_spec.js +41 -0
- data/spec/mockjson/order_item.js +37 -0
- data/spec/mockjson/order_item__49.js +15 -0
- data/spec/mockjson/order_item__50.js +15 -0
- data/spec/mockjson/order_item__51.js +15 -0
- data/spec/mockjson/product.js +82 -0
- data/spec/mockjson/product__3.js +22 -0
- data/spec/model_ring_spec.rb +52 -0
- data/spec/operation_include_spec.js +77 -0
- data/spec/run.html +81 -0
- data/spec/spec.js +2 -0
- data/spec/support/jasmine.yml +86 -0
- metadata +380 -0
@@ -0,0 +1,2098 @@
|
|
1
|
+
(function(){
|
2
|
+
|
3
|
+
// Gets the window (even if there is none)
|
4
|
+
var win = (function(){return this}).call(null),
|
5
|
+
// String constants (for better minification)
|
6
|
+
STR_ONLOAD = "onload",
|
7
|
+
STR_ONERROR = "onerror",
|
8
|
+
STR_ONREADYSTATECHANGE = "onreadystatechange",
|
9
|
+
STR_CREATE_ELEMENT = 'createElement',
|
10
|
+
STR_GET_BY_TAG = 'getElementsByTagName',
|
11
|
+
|
12
|
+
// the document ( might not exist in rhino )
|
13
|
+
doc = win.document,
|
14
|
+
|
15
|
+
// creates a script tag
|
16
|
+
scriptTag = function() {
|
17
|
+
var start = doc[STR_CREATE_ELEMENT]('script');
|
18
|
+
start.type = 'text/javascript';
|
19
|
+
return start;
|
20
|
+
},
|
21
|
+
// a function that returns the head element
|
22
|
+
// creates and caches the lookup if necessary
|
23
|
+
head = function() {
|
24
|
+
var d = doc,
|
25
|
+
de = d.documentElement,
|
26
|
+
heads = d[STR_GET_BY_TAG]("head"),
|
27
|
+
hd = heads[0];
|
28
|
+
if (! hd ) {
|
29
|
+
hd = d[STR_CREATE_ELEMENT]('head');
|
30
|
+
de.insertBefore(hd, de.firstChild);
|
31
|
+
}
|
32
|
+
// replace head so it runs fast next time.
|
33
|
+
head = function(){
|
34
|
+
return hd;
|
35
|
+
}
|
36
|
+
return hd;
|
37
|
+
},
|
38
|
+
// extends one object with another
|
39
|
+
extend = function( d, s ) {
|
40
|
+
for ( var p in s ) {
|
41
|
+
d[p] = s[p];
|
42
|
+
}
|
43
|
+
return d;
|
44
|
+
},
|
45
|
+
// a jQuery-like $.each
|
46
|
+
each = function(arr, cb){
|
47
|
+
for(var i =0, len = arr.length; i <len; i++){
|
48
|
+
cb.call(arr[i],i,arr[i])
|
49
|
+
}
|
50
|
+
return arr;
|
51
|
+
},
|
52
|
+
// makes an array of things
|
53
|
+
makeArray = function(args){
|
54
|
+
var arr = [];
|
55
|
+
each(args, function(i, str){arr[i] = str});
|
56
|
+
return arr;
|
57
|
+
},
|
58
|
+
// testing support for various browser behaviors
|
59
|
+
support = {
|
60
|
+
// does onerror work in script tags?
|
61
|
+
error: doc && (function(){
|
62
|
+
var script = scriptTag();
|
63
|
+
script.setAttribute( "onerror", "return;" );
|
64
|
+
return typeof script["onerror"] === "function" ?
|
65
|
+
true : "onerror" in script
|
66
|
+
})(),
|
67
|
+
// If scripts support interactive ready state.
|
68
|
+
// This is set later.
|
69
|
+
interactive: false,
|
70
|
+
attachEvent : doc && scriptTag().attachEvent
|
71
|
+
},
|
72
|
+
// a startup function that will be called when steal is ready
|
73
|
+
startup = function(){},
|
74
|
+
// the old steal value
|
75
|
+
oldsteal = win.steal,
|
76
|
+
// if oldsteal is an object
|
77
|
+
// we use it as options to configure steal
|
78
|
+
opts = typeof oldsteal == 'object' ? oldsteal : {};
|
79
|
+
|
80
|
+
// =============================== STEAL ===============================
|
81
|
+
|
82
|
+
/**
|
83
|
+
* @class steal
|
84
|
+
* @parent stealjs
|
85
|
+
*
|
86
|
+
* __steal__ is a function that loads scripts, css, and
|
87
|
+
* other resources into your application.
|
88
|
+
*
|
89
|
+
* steal(FILE_or_FUNCTION, ...)
|
90
|
+
*
|
91
|
+
* ## Quick Walkthrough
|
92
|
+
*
|
93
|
+
* Add a script tag that loads <code>steal/steal.js</code> and add
|
94
|
+
* the path to the first file to load in the query string like:
|
95
|
+
*
|
96
|
+
* <script type='text/javascript'
|
97
|
+
* src='../steal/steal.js?myapp/myapp.js'>
|
98
|
+
* </script>
|
99
|
+
*
|
100
|
+
* Then, start loading things and using them like:
|
101
|
+
*
|
102
|
+
* steal('myapp/tabs.js',
|
103
|
+
* 'myapp/slider.js',
|
104
|
+
* 'myapp/style.css',function(){
|
105
|
+
*
|
106
|
+
* // tabs and slider have loaded
|
107
|
+
* $('#tabs').tabs();
|
108
|
+
* $('#slider').slider()
|
109
|
+
* })
|
110
|
+
*
|
111
|
+
* Make sure your widgets load their dependencies too:
|
112
|
+
*
|
113
|
+
* // myapp/tabs.js
|
114
|
+
* steal('jquery', function(){
|
115
|
+
* $.fn.tabs = function(){
|
116
|
+
* ...
|
117
|
+
* }
|
118
|
+
* })
|
119
|
+
*
|
120
|
+
* ## Examples:
|
121
|
+
*
|
122
|
+
* // Loads ROOT/jquery/controller/controller.js
|
123
|
+
* steal('jquery/controller')
|
124
|
+
* steal('jquery/controller/controller.js')
|
125
|
+
*
|
126
|
+
* // Loads coffee script type and a coffee file relative to
|
127
|
+
* // the current file
|
128
|
+
* steal('steal/coffee').then('./mycoffee.coffee')
|
129
|
+
*
|
130
|
+
* // Load 2 files and dependencies in parallel and
|
131
|
+
* // callback when both have completed
|
132
|
+
* steal('jquery/controller','jquery/model', function(){
|
133
|
+
* // $.Controller and $.Model are available
|
134
|
+
* })
|
135
|
+
*
|
136
|
+
* // Loads a coffee script with a non-standard extension (cf)
|
137
|
+
* // relative to the current page and instructs the build
|
138
|
+
* // system to not package it (but it will still be loaded).
|
139
|
+
* steal({
|
140
|
+
* src: "./foo.cf",
|
141
|
+
* packaged: false,
|
142
|
+
* type: "coffee"
|
143
|
+
* })
|
144
|
+
*
|
145
|
+
* The following is a longer walkthrough of how to install
|
146
|
+
* and use steal:
|
147
|
+
*
|
148
|
+
* ## Adding steal to a page
|
149
|
+
*
|
150
|
+
* After installing StealJS (or JavaScriptMVC),
|
151
|
+
* find the <code>steal</code> folder with
|
152
|
+
* <code>steal/steal.js</code>.
|
153
|
+
*
|
154
|
+
* To use steal, add a script tag
|
155
|
+
* to <code>steal/steal.js</code> to your
|
156
|
+
* html pages.
|
157
|
+
*
|
158
|
+
* This walkthrough assumes you have the steal script
|
159
|
+
* in <code>public/steal/steal.js</code> and a directory
|
160
|
+
* structure like:
|
161
|
+
*
|
162
|
+
* @codestart text
|
163
|
+
* /public
|
164
|
+
* /steal
|
165
|
+
* /pages
|
166
|
+
* myapp.html
|
167
|
+
* /myapp
|
168
|
+
* myapp.js
|
169
|
+
* jquery.js
|
170
|
+
* jquery.ui.tabs.js
|
171
|
+
* @codeend
|
172
|
+
*
|
173
|
+
* To use steal in <code>public/pages/myapp.html</code>,
|
174
|
+
* add a script tag in <code>myapp.html</code>:
|
175
|
+
*
|
176
|
+
* @codestart html
|
177
|
+
* <script type='text/javascript'
|
178
|
+
* src='../steal/steal.js'>
|
179
|
+
* </script>
|
180
|
+
* @codeend
|
181
|
+
*
|
182
|
+
* <div class='whisper'>PRO TIP: Bottom load your scripts. It
|
183
|
+
* will increase your application's percieved response time.</div>
|
184
|
+
*
|
185
|
+
* ## Loading the First Script
|
186
|
+
*
|
187
|
+
* Once steal has been added to your page, it's time
|
188
|
+
* to load scripts. We want to load <code>myapp.js</code>
|
189
|
+
* and have it load <code>jquery.js</code> and
|
190
|
+
* <code>jquery.ui.tabs.js</code>.
|
191
|
+
*
|
192
|
+
* By default, steal likes your scripts
|
193
|
+
* to be within in the [steal.static.root steal.root] folder. The [steal.root] the
|
194
|
+
* folder contains the <code>steal</code> folder. In this example,
|
195
|
+
* it is the <code>public</code> folder.
|
196
|
+
*
|
197
|
+
* To load <code>myapp/myapp.js</code>, we have two options:
|
198
|
+
*
|
199
|
+
* #### Add a script tag
|
200
|
+
*
|
201
|
+
* Add a script tag after the steal
|
202
|
+
* script that 'steals' <code>myapp.js</code> like:
|
203
|
+
*
|
204
|
+
* @codestart html
|
205
|
+
* <script type='text/javascript'>
|
206
|
+
* steal('myapp/myapp.js')
|
207
|
+
* </script>
|
208
|
+
* @codeend
|
209
|
+
*
|
210
|
+
* #### Add the script parameter
|
211
|
+
*
|
212
|
+
* The most common (and shortest) way to load <code>myapp.js</code>
|
213
|
+
* is to add the script path to the steal script's src after in the
|
214
|
+
* query params. So, instead of adding a script, we change
|
215
|
+
* the steal script from:
|
216
|
+
*
|
217
|
+
* @codestart html
|
218
|
+
* <script type='text/javascript'
|
219
|
+
* src='../steal/steal.js'>
|
220
|
+
* </script>
|
221
|
+
* @codeend
|
222
|
+
*
|
223
|
+
* To
|
224
|
+
*
|
225
|
+
* @codestart html
|
226
|
+
* <script type='text/javascript'
|
227
|
+
* src='../steal/steal.js?<b>myapp/myapp.js</b>'>
|
228
|
+
* </script>
|
229
|
+
* @codeend
|
230
|
+
*
|
231
|
+
* <div class='whisper'>PRO TIP: You can also just add
|
232
|
+
* <code>?myapp</code> to the query string.</div>
|
233
|
+
*
|
234
|
+
* ## Loading Scripts
|
235
|
+
*
|
236
|
+
* We want to load <code>jquery.js</code> and
|
237
|
+
* <code>jquery.ui.tabs.js</code> into the page and then
|
238
|
+
* add then create a tabs widget. First we need to load
|
239
|
+
* <code>jquery.js</code>.
|
240
|
+
*
|
241
|
+
* By default, steal loads script relative to [steal.root]. To
|
242
|
+
* load <code>myapp/jquery.js</code> we can the following to
|
243
|
+
* <code>myapp.js</code>:
|
244
|
+
*
|
245
|
+
* steal('myapp/jquery.js');
|
246
|
+
*
|
247
|
+
* But, we can also load relative to <code>myapp.js</code> like:
|
248
|
+
*
|
249
|
+
* steal('./jquery.js');
|
250
|
+
*
|
251
|
+
* Next, we need to load <code>jquery.ui.tabs.js</code>. You
|
252
|
+
* might expect something like:
|
253
|
+
*
|
254
|
+
* steal('./jquery.js','./jquery.ui.tabs.js')
|
255
|
+
*
|
256
|
+
* to work. But there are two problems / complications:
|
257
|
+
*
|
258
|
+
* - steal loads scripts in parallel and runs out of order
|
259
|
+
* - <code>jquery.ui.tabs.js</code> depends on jQuery being loaded
|
260
|
+
*
|
261
|
+
* This means that steal might load <code>jquery.ui.tabs.js</code>
|
262
|
+
* before <code>jquery.js</code>. But this is easily fixed.
|
263
|
+
*
|
264
|
+
* [steal.static.then] waits until all previous scripts have loaded and
|
265
|
+
* run before loading scripts after it. We can load <code>jquery.ui.tabs.js</code>
|
266
|
+
* after <code>jquery.js</code> like:
|
267
|
+
*
|
268
|
+
* steal('./jquery.js').then('./jquery.ui.tabs.js')
|
269
|
+
*
|
270
|
+
* Finally, we need to add tabs to the page after
|
271
|
+
* the tabs's widget has loaded. We can add a callback function to
|
272
|
+
* steal that will get called when all previous scripts have finished
|
273
|
+
* loading:
|
274
|
+
*
|
275
|
+
* steal('./jquery.js').then('./jquery.ui.tabs.js', function($){
|
276
|
+
* $('#tabs').tabs();
|
277
|
+
* })
|
278
|
+
*
|
279
|
+
* ## Other Info
|
280
|
+
*
|
281
|
+
* ### Exclude Code Blocks From Production
|
282
|
+
*
|
283
|
+
* To exclude code blocks from being included in
|
284
|
+
* production builds, add the following around
|
285
|
+
* the code blocks.
|
286
|
+
*
|
287
|
+
* //!steal-remove-start
|
288
|
+
* code to be removed at build
|
289
|
+
* //!steal-remove-end
|
290
|
+
*
|
291
|
+
* ### Lookup Paths
|
292
|
+
*
|
293
|
+
* By default steal loads resources relative
|
294
|
+
* to [steal.static.root steal.root]. For example, the following
|
295
|
+
* loads foo.js in <code>steal.root</code>:
|
296
|
+
*
|
297
|
+
* steal('foo.js'); // loads //foo.js
|
298
|
+
*
|
299
|
+
* This is the same as writing:
|
300
|
+
*
|
301
|
+
* steal('//foo.js');
|
302
|
+
*
|
303
|
+
* Steal uses <code>'//'</code> to designate the [steal.static.root steal.root]
|
304
|
+
* folder.
|
305
|
+
*
|
306
|
+
* To load relative to the current file, add <code>"./"</code> or
|
307
|
+
* <code>"../"</code>:
|
308
|
+
*
|
309
|
+
* steal("./bar.js","../folder/zed.js");
|
310
|
+
*
|
311
|
+
* Often, scripts can be found in a folder within the same
|
312
|
+
* name. For example, [jQuery.Controller $.Controller] is
|
313
|
+
* in <code>//jquery/controller/controller.js</code>. For convience,
|
314
|
+
* if steal is provided a path without an extension like:
|
315
|
+
*
|
316
|
+
* steal('FOLDER/PLUGIN');
|
317
|
+
*
|
318
|
+
* It is the same as writing:
|
319
|
+
*
|
320
|
+
* steal('FOLDER/PLUGIN/PLUGIN.js')
|
321
|
+
*
|
322
|
+
* This means that <code>//jquery/controller/controller.js</code>
|
323
|
+
* can be loaded like:
|
324
|
+
*
|
325
|
+
* steal('jquery/controller')
|
326
|
+
*
|
327
|
+
* ### Types
|
328
|
+
*
|
329
|
+
* steal can load resources other than JavaScript.
|
330
|
+
*
|
331
|
+
*
|
332
|
+
* @constructor
|
333
|
+
*
|
334
|
+
* Loads resources specified by each argument. By default, resources
|
335
|
+
* are loaded in parallel and run in any order.
|
336
|
+
*
|
337
|
+
*
|
338
|
+
* @param {String|Function|Object} resource...
|
339
|
+
*
|
340
|
+
* Each argument specifies a resource. Resources can
|
341
|
+
* be given as a:
|
342
|
+
*
|
343
|
+
* ### Object
|
344
|
+
*
|
345
|
+
* An object that specifies the loading and build
|
346
|
+
* behavior of a resource.
|
347
|
+
*
|
348
|
+
* steal({
|
349
|
+
* src: "myfile.cf",
|
350
|
+
* type: "coffee",
|
351
|
+
* packaged: true,
|
352
|
+
* unique: true,
|
353
|
+
* ignore: false,
|
354
|
+
* waits: false
|
355
|
+
* })
|
356
|
+
*
|
357
|
+
* The available options are:
|
358
|
+
*
|
359
|
+
* - __src__ {*String*} - the path to the resource.
|
360
|
+
*
|
361
|
+
* - __waits__ {*Boolean default=false*} - true the resource should wait
|
362
|
+
* for prior steals to load and run. False if the resource should load and run in
|
363
|
+
* parallel. This defaults to true for functions.
|
364
|
+
*
|
365
|
+
* - __unique__ {*Boolean default=true*} - true if this is a unique resource
|
366
|
+
* that 'owns' this url. This is true for files, false for functions.
|
367
|
+
*
|
368
|
+
* - __ignore__ {*Boolean default=false*} - true if this resource should
|
369
|
+
* not be built into a production file and not loaded in
|
370
|
+
* production. This is great for script that should only be available
|
371
|
+
* in development mode.
|
372
|
+
*
|
373
|
+
* - __packaged__ {*Boolean default=true*} - true if the script should be built
|
374
|
+
* into the production file. false if the script should not be built
|
375
|
+
* into the production file, but still loaded. This is useful for
|
376
|
+
* loading 'packages'.
|
377
|
+
*
|
378
|
+
* - __type__ {*String default="js"*} - the type of the resource. This
|
379
|
+
* is typically inferred from the src.
|
380
|
+
*
|
381
|
+
* ### __String__
|
382
|
+
*
|
383
|
+
* Specifies src of the resource. For example:
|
384
|
+
*
|
385
|
+
* steal('./file.js')
|
386
|
+
*
|
387
|
+
* Is the same as calling:
|
388
|
+
*
|
389
|
+
* steal({src: './file.js'})
|
390
|
+
*
|
391
|
+
* ### __Function__
|
392
|
+
*
|
393
|
+
* A callback function that runs when all previous steals
|
394
|
+
* have completed.
|
395
|
+
*
|
396
|
+
* steal('jquery', 'foo',function(){
|
397
|
+
* // jquery and foo have finished loading
|
398
|
+
* // and runing
|
399
|
+
* })
|
400
|
+
*
|
401
|
+
* @return {steal} the steal object for chaining
|
402
|
+
*/
|
403
|
+
function steal() {
|
404
|
+
// convert arguments into an array
|
405
|
+
var args = makeArray(arguments);
|
406
|
+
pending.push.apply(pending, args);
|
407
|
+
// steal.after is called everytime steal is called
|
408
|
+
// it kicks off loading these files
|
409
|
+
steal.after(args);
|
410
|
+
// return steal for chaining
|
411
|
+
return steal;
|
412
|
+
};
|
413
|
+
|
414
|
+
|
415
|
+
// =============================== PATHS .8 ============================
|
416
|
+
|
417
|
+
// things that matter ...
|
418
|
+
// - the current file being loaded
|
419
|
+
// - the location of the page
|
420
|
+
// - the file
|
421
|
+
|
422
|
+
/**
|
423
|
+
* @class
|
424
|
+
* Used for getting information out of a path
|
425
|
+
* @constructor
|
426
|
+
* Takes a path
|
427
|
+
* @param {String} path
|
428
|
+
*/
|
429
|
+
steal.File = function( path ) {
|
430
|
+
// if new was not used, use it.
|
431
|
+
if ( this.constructor != steal.File ) {
|
432
|
+
return new steal.File(path);
|
433
|
+
}
|
434
|
+
// save the path
|
435
|
+
this.path = typeof path == 'string'? path : path.path;
|
436
|
+
};
|
437
|
+
// alias steal.File to File
|
438
|
+
var File = steal.File,
|
439
|
+
|
440
|
+
// a reference to the current file
|
441
|
+
curFile;
|
442
|
+
|
443
|
+
// get and sets the current file
|
444
|
+
File.cur = function(newCurFile){
|
445
|
+
if(newCurFile !== undefined){
|
446
|
+
curFile = File(newCurFile);
|
447
|
+
}else{
|
448
|
+
return curFile || File("");
|
449
|
+
}
|
450
|
+
};
|
451
|
+
|
452
|
+
|
453
|
+
extend(File.prototype,
|
454
|
+
/**
|
455
|
+
* @prototype
|
456
|
+
*/
|
457
|
+
{
|
458
|
+
// Removes hash and querystring
|
459
|
+
clean: function() {
|
460
|
+
return this.path.match(/([^\?#]*)/)[1];
|
461
|
+
},
|
462
|
+
// gets the files extension
|
463
|
+
ext : function(){
|
464
|
+
var match = this.clean().match(/\.([\w\d]+)$/)
|
465
|
+
return match ? match[1] : "";
|
466
|
+
},
|
467
|
+
// Returns everything before the last /
|
468
|
+
dir: function() {
|
469
|
+
// remove any query string
|
470
|
+
var cleaned = this.clean(),
|
471
|
+
// get the last /
|
472
|
+
last = cleaned.lastIndexOf('/'),
|
473
|
+
// if there is a last /, get everything up to that point
|
474
|
+
dir = (last != -1) ? cleaned.substring(0, last) : '';
|
475
|
+
// make sure we aren't left with just https/ or file:/
|
476
|
+
return /^(https?:\/|file:\/)$/.test(dir) ? cleaned : dir;
|
477
|
+
},
|
478
|
+
// Returns everything after the last /
|
479
|
+
filename: function() {
|
480
|
+
var cleaned = this.clean(),
|
481
|
+
last = cleaned.lastIndexOf('/'),
|
482
|
+
filename = (last != -1) ? cleaned.substring(last+1, cleaned.length) : cleaned;
|
483
|
+
return /^(https?:\/|file:\/)$/.test(filename) ? cleaned : filename;
|
484
|
+
},
|
485
|
+
// Returns the domain for the current path.
|
486
|
+
// Returns null if the domain is a file.
|
487
|
+
domain: function() {
|
488
|
+
var http = this.path.match(/^(?:https?:\/\/)([^\/]*)/);
|
489
|
+
return http ? http[1] : null;
|
490
|
+
},
|
491
|
+
/**
|
492
|
+
* Joins a url onto a path. One way of understanding this is that your File object represents your current location, and calling join() is analogous to "cd" on a command line.
|
493
|
+
* @codestart
|
494
|
+
* new steal.File("d/e").join("../a/b/c"); // Yields the path "d/a/b/c"
|
495
|
+
* @codeend
|
496
|
+
* @param {String} url
|
497
|
+
*/
|
498
|
+
join: function( url ) {
|
499
|
+
return File(url).joinFrom(this.path);
|
500
|
+
},
|
501
|
+
|
502
|
+
/**
|
503
|
+
* Returns the path of this file referenced from another url or path.
|
504
|
+
*
|
505
|
+
* new steal.File('a/b.c').joinFrom('/d/e')//-> /d/e/a/b.c
|
506
|
+
*
|
507
|
+
* @param {String} url
|
508
|
+
* @param {Boolean} expand if the path should be expanded
|
509
|
+
* @return {String}
|
510
|
+
*/
|
511
|
+
joinFrom: function( url, expand ) {
|
512
|
+
var u = File(url);
|
513
|
+
|
514
|
+
// if this.path is absolutely referenced
|
515
|
+
if ( this.protocol() ) { //if we are absolutely referenced
|
516
|
+
|
517
|
+
//try to shorten the path as much as possible:
|
518
|
+
var firstDomain = this.domain(),
|
519
|
+
secondDomain = u.domain();
|
520
|
+
|
521
|
+
// if domains are equal, shorten
|
522
|
+
if ( firstDomain && firstDomain == secondDomain ) {
|
523
|
+
|
524
|
+
return this.toReferenceFromSameDomain(url);
|
525
|
+
} else {
|
526
|
+
// if there is no domain or not equal, use our path
|
527
|
+
return this.path;
|
528
|
+
}
|
529
|
+
|
530
|
+
// the path is the same as the folder the page is in
|
531
|
+
} else if ( url === steal.pageUrl().dir() && !expand ) {
|
532
|
+
|
533
|
+
return this.path;
|
534
|
+
|
535
|
+
} else if ( this.isLocalAbsolute() ) { // we are a path like /page.js
|
536
|
+
|
537
|
+
return (u.domain() ? u.protocol() + "//" + u.domain() : "" )+ this.path;
|
538
|
+
|
539
|
+
} else { //we have 2 relative paths, remove folders with every ../
|
540
|
+
|
541
|
+
if ( url === '' ) {
|
542
|
+
return this.path.replace(/\/$/, '');
|
543
|
+
}
|
544
|
+
|
545
|
+
var urls = url.split('/'),
|
546
|
+
paths = this.path.split('/'),
|
547
|
+
path = paths[0];
|
548
|
+
|
549
|
+
//if we are joining from a folder like cookbook/, remove the last empty part
|
550
|
+
if ( url.match(/\/$/) ) {
|
551
|
+
urls.pop();
|
552
|
+
}
|
553
|
+
// for each .. remove one folder
|
554
|
+
while ( path == '..' && paths.length > 0 ) {
|
555
|
+
// if we've emptied out, folders, just break
|
556
|
+
// leaving any additional ../s
|
557
|
+
if(! urls.pop() ){
|
558
|
+
break;
|
559
|
+
}
|
560
|
+
paths.shift();
|
561
|
+
|
562
|
+
path = paths[0];
|
563
|
+
}
|
564
|
+
return urls.concat(paths).join('/');
|
565
|
+
}
|
566
|
+
},
|
567
|
+
// Returns true if the file is relative to a domain or a protocol
|
568
|
+
relative: function() {
|
569
|
+
return this.path.match(/^(https?:|file:|\/)/) === null;
|
570
|
+
},
|
571
|
+
/**
|
572
|
+
* Returns the relative path between two paths with common folders.
|
573
|
+
* @codestart
|
574
|
+
* new steal.File('a/b/c/x/y').toReferenceFromSameDomain('a/b/c/d/e')//-> ../../x/y
|
575
|
+
* @codeend
|
576
|
+
* @param {Object} url
|
577
|
+
* @return {String}
|
578
|
+
*/
|
579
|
+
toReferenceFromSameDomain: function( url ) {
|
580
|
+
var parts = this.path.split('/'),
|
581
|
+
other_parts = url.split('/'),
|
582
|
+
result = '';
|
583
|
+
while ( parts.length > 0 && other_parts.length > 0 && parts[0] == other_parts[0] ) {
|
584
|
+
parts.shift();
|
585
|
+
other_parts.shift();
|
586
|
+
}
|
587
|
+
each(other_parts, function(){ result += '../'; })
|
588
|
+
return result + parts.join('/');
|
589
|
+
},
|
590
|
+
/**
|
591
|
+
* Is the file on the same domain as our page.
|
592
|
+
*/
|
593
|
+
isCrossDomain: function() {
|
594
|
+
return this.isLocalAbsolute() ? false : this.domain() != File(win.location.href).domain();
|
595
|
+
},
|
596
|
+
isLocalAbsolute: function() {
|
597
|
+
return this.path.indexOf('/') === 0;
|
598
|
+
},
|
599
|
+
protocol: function() {
|
600
|
+
var match = this.path.match(/^(https?:|file:)/);
|
601
|
+
return match && match[0];
|
602
|
+
},
|
603
|
+
/**
|
604
|
+
* For a given path, a given working directory, and file location, update the path so
|
605
|
+
* it points to a location relative to steal's root.
|
606
|
+
*
|
607
|
+
* We want everything relative to steal's root so the same app can work in multiple pages.
|
608
|
+
*
|
609
|
+
* ./files/a.js = steals a.js
|
610
|
+
* ./files/a = a/a.js
|
611
|
+
* files/a = //files/a/a.js
|
612
|
+
* files/a.js = loads //files/a.js
|
613
|
+
*/
|
614
|
+
normalize: function() {
|
615
|
+
|
616
|
+
var current = File.cur().dir(),
|
617
|
+
//if you are cross domain from the page, and providing a path that doesn't have an domain
|
618
|
+
path = this.path;
|
619
|
+
if (/^\/\//.test(this.path)) { //if path is rooted from steal's root (DEPRECATED)
|
620
|
+
path = this.path.substr(2);
|
621
|
+
}
|
622
|
+
else if (/^\.\//.test(this.path)) { // should be relative
|
623
|
+
this.path = this.path.substr(2);
|
624
|
+
path = this.joinFrom(current);
|
625
|
+
this.path = "./" + this.path;
|
626
|
+
}
|
627
|
+
else if (/^[^\.|\/]/.test(this.path)) {}
|
628
|
+
else {
|
629
|
+
if (this.relative() ||
|
630
|
+
File.cur().isCrossDomain() && //if current file is on another domain and
|
631
|
+
!this.protocol()) { //this file doesn't have a protocol
|
632
|
+
path = this.joinFrom(current);
|
633
|
+
}
|
634
|
+
}
|
635
|
+
|
636
|
+
return path;
|
637
|
+
}
|
638
|
+
});
|
639
|
+
|
640
|
+
var pending = [],
|
641
|
+
s = steal,
|
642
|
+
id = 0,
|
643
|
+
steals = {};
|
644
|
+
|
645
|
+
// this is for methods on a 'steal instance'. A file can be in one of a few states:
|
646
|
+
// created - the steal instance is created, but we haven't started loading it yet
|
647
|
+
// this happens when thens are used
|
648
|
+
// loading - (loading=true) By calling load, this will tell steal to load a file
|
649
|
+
// loaded - (isLoaded=true) The file has been run, but its dependency files have been completed
|
650
|
+
// complete - all of this files dependencies have loaded and completed.
|
651
|
+
steal.p = {
|
652
|
+
// adds a new steal and throws an error if the script doesn't load
|
653
|
+
// this also checks the steals map
|
654
|
+
make: function(options){
|
655
|
+
|
656
|
+
var stel = new steal.p.init(options),
|
657
|
+
rootSrc = stel.options.rootSrc;
|
658
|
+
|
659
|
+
if(stel.unique && rootSrc){
|
660
|
+
// the .js is b/c we are not adding that automatically until
|
661
|
+
// load because we defer 'type' determination until then
|
662
|
+
if(!steals[rootSrc] && ! steals[rootSrc+".js"]){ //if we haven't loaded it before
|
663
|
+
steals[rootSrc] = stel;
|
664
|
+
} else{ // already have this steal
|
665
|
+
stel = steals[rootSrc];
|
666
|
+
// extend the old stolen file with any new options
|
667
|
+
extend(stel.options, typeof options === "string" ? {} : options)
|
668
|
+
}
|
669
|
+
}
|
670
|
+
|
671
|
+
return stel;
|
672
|
+
},
|
673
|
+
init: function( options ) {
|
674
|
+
this.dependencies = [];
|
675
|
+
// id for debugging
|
676
|
+
this.id = (++id);
|
677
|
+
|
678
|
+
// if we have no options, we are the global init ... set ourselves up ...
|
679
|
+
if(!options){ //global init cur ...
|
680
|
+
this.options = {};
|
681
|
+
this.waits = false;
|
682
|
+
this.pack = "production.js";
|
683
|
+
}
|
684
|
+
//handle callback functions
|
685
|
+
else if ( typeof options == 'function' ) {
|
686
|
+
var path = File.cur().path;
|
687
|
+
|
688
|
+
this.options = {
|
689
|
+
fn : function() {
|
690
|
+
|
691
|
+
//set the path ..
|
692
|
+
File.cur(path);
|
693
|
+
|
694
|
+
// call the function, someday soon this will be requireJS-like
|
695
|
+
options(steal.send || win.jQuery || steal);
|
696
|
+
},
|
697
|
+
rootSrc: path,
|
698
|
+
orig: options,
|
699
|
+
type: "fn"
|
700
|
+
}
|
701
|
+
// this has nothing to do with 'loading' options
|
702
|
+
this.waits = true;
|
703
|
+
this.unique = false;
|
704
|
+
} else {
|
705
|
+
|
706
|
+
// save the original options
|
707
|
+
this.orig = options;
|
708
|
+
|
709
|
+
this.options = steal.makeOptions(extend({},
|
710
|
+
typeof options == 'string' ? { src: options } : options));
|
711
|
+
|
712
|
+
this.waits = this.options.waits || false;
|
713
|
+
this.unique = true;
|
714
|
+
}
|
715
|
+
},
|
716
|
+
complete : function(){
|
717
|
+
this.completed = true;
|
718
|
+
},
|
719
|
+
/**
|
720
|
+
* @hide
|
721
|
+
* After the script has been loaded and run
|
722
|
+
*
|
723
|
+
* - check what else has been stolen, load them
|
724
|
+
* - mark yourself as complete when everything is completed
|
725
|
+
* - this is where all the actions is
|
726
|
+
*/
|
727
|
+
|
728
|
+
loaded: function(script){
|
729
|
+
var myqueue,
|
730
|
+
stel,
|
731
|
+
src = (script && script.src) || this.options.src,
|
732
|
+
rootSrc = this.options.rootSrc;
|
733
|
+
|
734
|
+
//set yourself as the current file
|
735
|
+
File.cur(rootSrc);
|
736
|
+
|
737
|
+
// mark yourself as 'loaded'.
|
738
|
+
this.isLoaded = true;
|
739
|
+
|
740
|
+
// If we are IE, get the queue from interactives
|
741
|
+
// TODO move this out of this function
|
742
|
+
if (support.interactive && src) {
|
743
|
+
myqueue = interactives[src];
|
744
|
+
}
|
745
|
+
// is there a case in IE where, this makes sense?
|
746
|
+
// in other browsers, the queue of items to load is
|
747
|
+
// what is in pending
|
748
|
+
if(!myqueue){
|
749
|
+
myqueue = pending.slice(0);
|
750
|
+
pending = [];
|
751
|
+
}
|
752
|
+
|
753
|
+
// if we have nothing, mark us as complete (resolve if deferred)
|
754
|
+
if(!myqueue.length){
|
755
|
+
this.complete();
|
756
|
+
return;
|
757
|
+
}
|
758
|
+
|
759
|
+
// now we have to figure out how to wire up our pending steals
|
760
|
+
var self = this,
|
761
|
+
set = [],
|
762
|
+
// the current
|
763
|
+
joiner,
|
764
|
+
initial = [],
|
765
|
+
|
766
|
+
isProduction = steal.options.env == 'production',
|
767
|
+
|
768
|
+
files = [],
|
769
|
+
|
770
|
+
// a helper that basically does a join
|
771
|
+
// when everything in arr's func method is called,
|
772
|
+
// call func2 on obj
|
773
|
+
whenEach = function(arr, func, obj, func2){
|
774
|
+
var big = [obj, func2];
|
775
|
+
each(arr, function(i, item){
|
776
|
+
big.unshift(item, func)
|
777
|
+
});
|
778
|
+
when.apply(steal, big);
|
779
|
+
},
|
780
|
+
// a helper that does the oposite of a join. When
|
781
|
+
// obj's func method is called, call func2 on all items.
|
782
|
+
whenThe = function(obj, func, items, func2){
|
783
|
+
each(items, function(i, item){
|
784
|
+
when(obj, func, item, func2)
|
785
|
+
})
|
786
|
+
};
|
787
|
+
|
788
|
+
|
789
|
+
//now go through what you stole and hook everything up
|
790
|
+
//BUT, we go through backwards
|
791
|
+
each(myqueue.reverse(), function(i, item){
|
792
|
+
|
793
|
+
//in production, ignore ignored items (like steal/dev
|
794
|
+
if(isProduction && item.ignore){
|
795
|
+
return;
|
796
|
+
}
|
797
|
+
|
798
|
+
// make a steal object
|
799
|
+
stel = steal.p.make( item );
|
800
|
+
|
801
|
+
// add it as a dependency, circular are not allowed
|
802
|
+
self.dependencies.unshift(stel)
|
803
|
+
|
804
|
+
|
805
|
+
if(stel.waits === false){ // file
|
806
|
+
// on the current
|
807
|
+
files.push(stel);
|
808
|
+
|
809
|
+
}else{ // function
|
810
|
+
|
811
|
+
// essentially have to bind current files to call previous joiner's load
|
812
|
+
// and to wait for current stel's complete
|
813
|
+
|
814
|
+
if(!joiner){ // if no previous joiner, then we are at the start of a file
|
815
|
+
|
816
|
+
// when they are complete, complete the file
|
817
|
+
whenEach( files.concat(stel), "complete", self, "complete");
|
818
|
+
|
819
|
+
// if there was a function then files, then end, function loads all files
|
820
|
+
if(files.length){
|
821
|
+
whenThe(stel,"complete", files ,"load")
|
822
|
+
}
|
823
|
+
|
824
|
+
} else { // function, file1, file2, file3, joiner function
|
825
|
+
|
826
|
+
whenEach(files.concat(stel) , "complete", joiner, "load");
|
827
|
+
|
828
|
+
// make stel complete load files
|
829
|
+
whenThe(stel,"complete", files.length ? files : [joiner] ,"load")
|
830
|
+
|
831
|
+
}
|
832
|
+
// the joiner is the previous thing
|
833
|
+
joiner = stel;
|
834
|
+
files = [];
|
835
|
+
|
836
|
+
}
|
837
|
+
});
|
838
|
+
|
839
|
+
// now we should be left with the starting files
|
840
|
+
if(files.length){
|
841
|
+
// we have initial files
|
842
|
+
// if there is a joiner, we need to load it when the initial files are complete
|
843
|
+
if(joiner){
|
844
|
+
whenEach(files, "complete", joiner, "load");
|
845
|
+
} else {
|
846
|
+
whenEach(files, "complete", self, "complete");
|
847
|
+
}
|
848
|
+
// reverse it back and load each initial file
|
849
|
+
each(files.reverse(), function(){
|
850
|
+
this.load();
|
851
|
+
});
|
852
|
+
} else if(joiner){
|
853
|
+
// we have inital function
|
854
|
+
joiner.load()
|
855
|
+
} else {
|
856
|
+
// we had nothing
|
857
|
+
self.complete();
|
858
|
+
}
|
859
|
+
|
860
|
+
},
|
861
|
+
/**
|
862
|
+
* Loads this steal
|
863
|
+
*/
|
864
|
+
load: function(returnScript) {
|
865
|
+
// if we are already loading / loaded
|
866
|
+
if(this.loading || this.isLoaded){
|
867
|
+
return;
|
868
|
+
}
|
869
|
+
this.loading = true;
|
870
|
+
var self = this;
|
871
|
+
// get yourself
|
872
|
+
steal.require(this.options, function load_calling_loaded(script){
|
873
|
+
self.loaded(script);
|
874
|
+
}, function(error, src){
|
875
|
+
win.clearTimeout && win.clearTimeout(self.completeTimeout)
|
876
|
+
throw "steal.js : "+self.options.src+" not completed"
|
877
|
+
});
|
878
|
+
|
879
|
+
}
|
880
|
+
|
881
|
+
};
|
882
|
+
steal.p.init.prototype = steal.p;
|
883
|
+
/**
|
884
|
+
* @add steal
|
885
|
+
*/
|
886
|
+
// =============================== STATIC API ===============================
|
887
|
+
var page;
|
888
|
+
/**
|
889
|
+
* @static
|
890
|
+
*/
|
891
|
+
extend(steal,{
|
892
|
+
/**
|
893
|
+
* @attribute root
|
894
|
+
* The location of the steal folder.
|
895
|
+
*/
|
896
|
+
root : File(""),
|
897
|
+
/**
|
898
|
+
* Gets or sets the path from the current page to
|
899
|
+
* steal's (or JavaScriptMVC's) root folder. When passed a src, it sets the root folder.
|
900
|
+
* Otherwise, it returns the path to the root folder.
|
901
|
+
*
|
902
|
+
* This is the path from which
|
903
|
+
* all plugins are stolen. When you steal a plugin like steal("jquery/controller"),
|
904
|
+
* the plugin path is joined with this rootUrl to create a full path
|
905
|
+
* to the controller.js file.
|
906
|
+
*
|
907
|
+
* By default, the rootUrl is calculated from the
|
908
|
+
* steal script and the window location. For example, if the
|
909
|
+
* script tag looks like this:
|
910
|
+
*
|
911
|
+
* <script type='text/javascript' src='../../steal/steal.js?ui/app'></script>
|
912
|
+
*
|
913
|
+
* rootUrl will be set to "../../".
|
914
|
+
* Setting the rootUrl can be useful if you want to have
|
915
|
+
* steal.js in a different location.
|
916
|
+
*
|
917
|
+
* ## Example
|
918
|
+
*
|
919
|
+
* The following sets steal root to a different folder.
|
920
|
+
*
|
921
|
+
* steal.rootUrl("../../jmvc/")
|
922
|
+
*
|
923
|
+
* This appends <code>"../../jmvc"</code> to paths
|
924
|
+
* loaded from [steal.static.root]. In some strange cases this might be desirable if
|
925
|
+
* plugin folders are in a different location from the steal directory.
|
926
|
+
*
|
927
|
+
* It also sets the current url to this directory so the first calls to steal work relative to the root JMVC directory.
|
928
|
+
*
|
929
|
+
* @param {String} [src] a relative path from the current page to the root directory of JMVC, like ../../
|
930
|
+
* @return {String} returns the last path passed to rootUrl
|
931
|
+
*/
|
932
|
+
rootUrl : function(src){
|
933
|
+
if (src !== undefined) {
|
934
|
+
steal.root = File(src);
|
935
|
+
|
936
|
+
// set cur with the location
|
937
|
+
var cleaned = steal.pageUrl(),
|
938
|
+
loc = cleaned.join(src);
|
939
|
+
|
940
|
+
File.cur( cleaned.toReferenceFromSameDomain(loc) );
|
941
|
+
return steal;
|
942
|
+
} else {
|
943
|
+
return steal.root.path;
|
944
|
+
}
|
945
|
+
},
|
946
|
+
extend : extend,
|
947
|
+
/**
|
948
|
+
* @function pageUrl
|
949
|
+
* Gets or sets the location of the page using
|
950
|
+
* steal. This defaults to the window's location.
|
951
|
+
*
|
952
|
+
* However, sometimes it is necessary to
|
953
|
+
* have steal believe it is making requests from
|
954
|
+
* another page.
|
955
|
+
*
|
956
|
+
* @param {String} [newPage] a path to the page using steal (probably the windows location)
|
957
|
+
* @return {steal.File} returns the last path to a page passed to pageUrl, converted to a steal.File object
|
958
|
+
*/
|
959
|
+
pageUrl : function(newPage){
|
960
|
+
if(newPage){
|
961
|
+
page = File( File(newPage).clean() );
|
962
|
+
return steal;
|
963
|
+
} else{
|
964
|
+
return page || File("");
|
965
|
+
}
|
966
|
+
},
|
967
|
+
//gets and sets which page steal thinks it's at
|
968
|
+
// TODO: make location change-able ...
|
969
|
+
/**
|
970
|
+
* Gets the currently running script location.
|
971
|
+
*
|
972
|
+
* @return String
|
973
|
+
*/
|
974
|
+
cur: function( file ) {
|
975
|
+
if (file === undefined) {
|
976
|
+
return File.cur();
|
977
|
+
} else {
|
978
|
+
File.cur(file);
|
979
|
+
return steal;
|
980
|
+
}
|
981
|
+
},
|
982
|
+
isRhino: win.load && win.readUrl && win.readFile,
|
983
|
+
/**
|
984
|
+
* @attribute options
|
985
|
+
* Configurable options
|
986
|
+
*/
|
987
|
+
options : {
|
988
|
+
env : 'development',
|
989
|
+
// TODO: document this
|
990
|
+
loadProduction : true
|
991
|
+
},
|
992
|
+
/**
|
993
|
+
* @hide
|
994
|
+
* when a 'unique' steal gets added ...
|
995
|
+
* @param {Object} stel
|
996
|
+
*/
|
997
|
+
add : function(stel){
|
998
|
+
steals[stel.rootSrc] = stel;
|
999
|
+
},
|
1000
|
+
/**
|
1001
|
+
* @hide
|
1002
|
+
* Makes options
|
1003
|
+
* @param {Object} options
|
1004
|
+
*/
|
1005
|
+
makeOptions : function(options){
|
1006
|
+
|
1007
|
+
var ext = File(options.src).ext();
|
1008
|
+
if (!ext) {
|
1009
|
+
// if first character of path is a . or /, just load this file
|
1010
|
+
if (options.src.indexOf(".") == 0 || options.src.indexOf("/") == 0) {
|
1011
|
+
options.src = options.src + ".js"
|
1012
|
+
}
|
1013
|
+
// else, load as a plugin
|
1014
|
+
else {
|
1015
|
+
options.src = options.src + "/" + File(options.src).filename() + ".js";
|
1016
|
+
}
|
1017
|
+
}
|
1018
|
+
|
1019
|
+
var orig = options.src,
|
1020
|
+
// path relative to the current files path
|
1021
|
+
// this is done relative to jmvcroot
|
1022
|
+
normalized = steal.File(orig).normalize(),
|
1023
|
+
protocol = steal.File(options.src).protocol();
|
1024
|
+
|
1025
|
+
extend(options,{
|
1026
|
+
originalSrc : options.src,
|
1027
|
+
rootSrc : normalized,
|
1028
|
+
// path from the page
|
1029
|
+
src : steal.root.join(normalized),
|
1030
|
+
// "file:" or "http:" depending on what protocol the request uses
|
1031
|
+
protocol: protocol || (doc? location.protocol: "file:")
|
1032
|
+
});
|
1033
|
+
options.originalSrc = options.src;
|
1034
|
+
|
1035
|
+
return options;
|
1036
|
+
},
|
1037
|
+
/**
|
1038
|
+
* Calls steal, but waits until all previous steals
|
1039
|
+
* have completed loading until loading the
|
1040
|
+
* files passed to the arguments.
|
1041
|
+
*/
|
1042
|
+
then : function(){
|
1043
|
+
var args = typeof arguments[0] == 'function' ?
|
1044
|
+
arguments : [function(){}].concat(makeArray( arguments ) )
|
1045
|
+
return steal.apply(win, args );
|
1046
|
+
},
|
1047
|
+
/**
|
1048
|
+
* Listens to events on Steal
|
1049
|
+
* @param {String} event
|
1050
|
+
* @param {Function} listener
|
1051
|
+
*/
|
1052
|
+
bind: function(event, listener){
|
1053
|
+
if(!events[event]){
|
1054
|
+
events[event] = []
|
1055
|
+
}
|
1056
|
+
var special = steal.events[event]
|
1057
|
+
if(special && special.add){
|
1058
|
+
listener = special.add(listener);
|
1059
|
+
}
|
1060
|
+
listener && events[event].push(listener);
|
1061
|
+
return steal;
|
1062
|
+
},
|
1063
|
+
one : function(event, listener){
|
1064
|
+
steal.bind(event,function(){
|
1065
|
+
listener.apply(this, arguments);
|
1066
|
+
steal.unbind(event, arguments.callee);
|
1067
|
+
});
|
1068
|
+
return steal;
|
1069
|
+
},
|
1070
|
+
events : {},
|
1071
|
+
/**
|
1072
|
+
* Unbinds an event listener on steal
|
1073
|
+
* @param {Object} event
|
1074
|
+
* @param {Object} listener
|
1075
|
+
*/
|
1076
|
+
unbind : function(event, listener){
|
1077
|
+
var evs = events[event] || [],
|
1078
|
+
i = 0;
|
1079
|
+
while(i < evs.length){
|
1080
|
+
if(listener === evs[i]){
|
1081
|
+
evs.splice(i,1);
|
1082
|
+
}else{
|
1083
|
+
i++;
|
1084
|
+
}
|
1085
|
+
}
|
1086
|
+
},
|
1087
|
+
trigger : function(event, arg){
|
1088
|
+
var arr = events[event] || [],
|
1089
|
+
copy = [];
|
1090
|
+
// array items might be removed during each iteration (with unbind), so we iterate over a copy
|
1091
|
+
for(var i =0, len = arr.length; i <len; i++){
|
1092
|
+
copy[i] = arr[i];
|
1093
|
+
}
|
1094
|
+
each(copy, function(i,f){
|
1095
|
+
f(arg);
|
1096
|
+
})
|
1097
|
+
},
|
1098
|
+
/**
|
1099
|
+
* @hide
|
1100
|
+
* Used to tell steal that it is loading a number of plugins
|
1101
|
+
*/
|
1102
|
+
loading : function(){
|
1103
|
+
// we don't use IE's interactive script functionality while production scripts are loading
|
1104
|
+
useInteractive = false;
|
1105
|
+
each(arguments, function(i, arg){
|
1106
|
+
var stel = steal.p.make( arg );
|
1107
|
+
stel.loading = true;
|
1108
|
+
});
|
1109
|
+
},
|
1110
|
+
// a dummy function to add things to after the stel is created, but before
|
1111
|
+
// loaded is called
|
1112
|
+
preloaded : function(){},
|
1113
|
+
// called when a script has loaded via production
|
1114
|
+
loaded: function(name){
|
1115
|
+
// create the steal, mark it as loading, then as loaded
|
1116
|
+
var stel = steal.p.make( name );
|
1117
|
+
stel.loading = true;
|
1118
|
+
convert(stel, "complete");
|
1119
|
+
|
1120
|
+
steal.preloaded(stel);
|
1121
|
+
stel.loaded()
|
1122
|
+
return steal;
|
1123
|
+
}
|
1124
|
+
});
|
1125
|
+
|
1126
|
+
var events = {};
|
1127
|
+
startup = before(startup, function(){
|
1128
|
+
|
1129
|
+
steal.pageUrl(win.location ? win.location.href : "");
|
1130
|
+
|
1131
|
+
})
|
1132
|
+
|
1133
|
+
|
1134
|
+
// =============================== TYPE SYSTEM ===============================
|
1135
|
+
|
1136
|
+
var types = steal.types = {};
|
1137
|
+
|
1138
|
+
|
1139
|
+
steal.
|
1140
|
+
/**
|
1141
|
+
* Registers a type. You define the type of the file, the basic type it converts to, and a
|
1142
|
+
* conversion function where you convert the original file to JS or CSS. This is modeled after the
|
1143
|
+
* [http://api.jquery.com/extending-ajax/#Converters AJAX converters] in jQuery.
|
1144
|
+
*
|
1145
|
+
* Types are designed to make it simple to switch between steal's development and production modes. In development mode, the types are converted
|
1146
|
+
* in the browser to allow devs to see changes as they work. When the app is built, these converter functions are run by the build process,
|
1147
|
+
* and the processed text is inserted into the production script, optimized for performance.
|
1148
|
+
*
|
1149
|
+
* Here's an example converting files of type .foo to JavaScript. Foo is a fake language that saves global variables defined like. A .foo file might
|
1150
|
+
* look like this:
|
1151
|
+
*
|
1152
|
+
* REQUIRED FOO
|
1153
|
+
*
|
1154
|
+
* To define this type, you'd call steal.type like this:
|
1155
|
+
*
|
1156
|
+
* steal.type("foo js", function(options, original, success, error){
|
1157
|
+
* var parts = options.text.split(" ")
|
1158
|
+
* options.text = parts[0]+"='"+parts[1]+"'";
|
1159
|
+
* success();
|
1160
|
+
* });
|
1161
|
+
*
|
1162
|
+
* The method we provide is called with the text of .foo files in options.text. We parse the file, create
|
1163
|
+
* JavaScript and put it in options.text. Couldn't be simpler.
|
1164
|
+
*
|
1165
|
+
* Here's an example, converting [http://jashkenas.github.com/coffee-script/ coffeescript] to JavaScript:
|
1166
|
+
*
|
1167
|
+
* steal.type("coffee js", function(options, original, success, error){
|
1168
|
+
* options.text = CoffeeScript.compile(options.text);
|
1169
|
+
* success();
|
1170
|
+
* });
|
1171
|
+
*
|
1172
|
+
* In this example, any time steal encounters a file with
|
1173
|
+
* extension .coffee, it will call the given
|
1174
|
+
* converter method. CoffeeScript.compile takes the text of the file, converts it from coffeescript to javascript,
|
1175
|
+
* and saves the JavaScript text in options.text.
|
1176
|
+
*
|
1177
|
+
* Similarly, languages on top of CSS, like [http://lesscss.org/ LESS], can be converted to CSS:
|
1178
|
+
*
|
1179
|
+
* steal.type("less css", function(options, original, success, error){
|
1180
|
+
* new (less.Parser)({
|
1181
|
+
* optimization: less.optimization,
|
1182
|
+
* paths: []
|
1183
|
+
* }).parse(options.text, function (e, root) {
|
1184
|
+
* options.text = root.toCSS();
|
1185
|
+
* success();
|
1186
|
+
* });
|
1187
|
+
* });
|
1188
|
+
*
|
1189
|
+
* This simple type system could be used to convert any file type to be used in your JavaScript app. For example,
|
1190
|
+
* [http://fdik.org/yml/ yml] could be used for configuration. jQueryMX uses steal.type to support JS templates, such as EJS, TMPL, and others.
|
1191
|
+
*
|
1192
|
+
* @param {String} type A string that defines the new type being defined and the type being converted to,
|
1193
|
+
* separated by a space, like "coffee js".
|
1194
|
+
*
|
1195
|
+
* There can be more than two steps used in conversion, such as "ejs view
|
1196
|
+
* js". This will define a method that converts .ejs files to .view files. There should be another converter for "view js"
|
1197
|
+
* that makes this final conversion to JS.
|
1198
|
+
*
|
1199
|
+
* @param {Function} cb( options, original, success, error ) a callback function that converts the new file type
|
1200
|
+
* to a basic type. This method needs to do two things: 1) save the text of the converted file in options.text
|
1201
|
+
* and 2) call success() when the conversion is done (it can work asynchronously).
|
1202
|
+
*
|
1203
|
+
* - __options__ - the steal options for this file, including path information
|
1204
|
+
* - __original__ - the original argument passed to steal, which might be a path or a function
|
1205
|
+
* - __success__ - a method to call when the file is converted and processed successfully
|
1206
|
+
* - __error__ - a method called if the conversion fails or the file doesn't exist
|
1207
|
+
*/
|
1208
|
+
type = function(type, cb){
|
1209
|
+
var typs = type.split(" ");
|
1210
|
+
|
1211
|
+
if(!cb){
|
1212
|
+
return types[typs.shift()].require
|
1213
|
+
}
|
1214
|
+
|
1215
|
+
types[typs.shift()] = {
|
1216
|
+
require : cb,
|
1217
|
+
convert: typs
|
1218
|
+
};
|
1219
|
+
};
|
1220
|
+
// adds a type (js by default) and buildType (css, js)
|
1221
|
+
// this should happen right before loading
|
1222
|
+
// however, what if urls are different
|
1223
|
+
// because one file has JS and another does not?
|
1224
|
+
// we could check if it matches something with .js because foo.less.js SHOULD
|
1225
|
+
// be rare
|
1226
|
+
steal.p.load = before(steal.p.load, function(){
|
1227
|
+
var raw = this.options;
|
1228
|
+
|
1229
|
+
// if it's a string, get it's extension and check if
|
1230
|
+
// it is a registered type, if it is ... set the type
|
1231
|
+
if(!raw.type){
|
1232
|
+
var ext = File(raw.src).ext();
|
1233
|
+
if(!ext && !types[ext]){
|
1234
|
+
ext = "js";
|
1235
|
+
}
|
1236
|
+
raw.type = ext;
|
1237
|
+
}
|
1238
|
+
if (!types[raw.type]){
|
1239
|
+
throw "steal.js - type " + raw.type + " has not been loaded.";
|
1240
|
+
}
|
1241
|
+
var converters = types[raw.type].convert;
|
1242
|
+
raw.buildType = converters.length ? converters[converters.length - 1] : raw.type;
|
1243
|
+
});
|
1244
|
+
|
1245
|
+
steal.
|
1246
|
+
/**
|
1247
|
+
* Called for every file that is loaded. It sets up a string of methods called for each type in the conversion chain and calls each type one by one.
|
1248
|
+
*
|
1249
|
+
* For example, if the file is a coffeescript file, here's what happens:
|
1250
|
+
*
|
1251
|
+
* - The "text" type converter is called first. This will perform an AJAX request for the file and save it in options.text.
|
1252
|
+
* - Then the coffee type converter is called (the user provided method). This converts the text from coffeescript to JavaScript.
|
1253
|
+
* - Finally the "js" type converter is called, which inserts the JavaScript in the page as a script tag that is executed.
|
1254
|
+
*
|
1255
|
+
* @param {Object} options the steal options for this file, including path information
|
1256
|
+
* @param {Function} success a method to call when the file is converted and processed successfully
|
1257
|
+
* @param {Function} error a method called if the conversion fails or the file doesn't exist
|
1258
|
+
*/
|
1259
|
+
require = function(options, success, error){
|
1260
|
+
// get the type
|
1261
|
+
var type = types[options.type],
|
1262
|
+
converters;
|
1263
|
+
|
1264
|
+
// if this has converters, make it get the text first, then pass it to the type
|
1265
|
+
if(type.convert.length){
|
1266
|
+
converters = type.convert.slice(0);
|
1267
|
+
converters.unshift('text', options.type)
|
1268
|
+
} else {
|
1269
|
+
converters = [options.type]
|
1270
|
+
}
|
1271
|
+
require(options, converters, success, error)
|
1272
|
+
};
|
1273
|
+
function require(options, converters, success, error){
|
1274
|
+
|
1275
|
+
var type = types[converters.shift()];
|
1276
|
+
|
1277
|
+
type.require(options, function require_continue_check(){
|
1278
|
+
// if we have more types to convert
|
1279
|
+
if(converters.length){
|
1280
|
+
require(options, converters, success, error)
|
1281
|
+
} else { // otherwise this is the final
|
1282
|
+
success.apply(this, arguments);
|
1283
|
+
}
|
1284
|
+
}, error)
|
1285
|
+
};
|
1286
|
+
|
1287
|
+
|
1288
|
+
// =============================== TYPES ===============================
|
1289
|
+
|
1290
|
+
// a clean up script that prevents memory leaks and removes the
|
1291
|
+
// script
|
1292
|
+
var cleanUp = function(script) {
|
1293
|
+
script[ STR_ONREADYSTATECHANGE ]
|
1294
|
+
= script[ STR_ONLOAD ]
|
1295
|
+
= script[STR_ONERROR]
|
1296
|
+
= null;
|
1297
|
+
|
1298
|
+
head().removeChild( script );
|
1299
|
+
},
|
1300
|
+
// the last inserted script, needed for IE
|
1301
|
+
lastInserted,
|
1302
|
+
// if the state is done
|
1303
|
+
stateCheck = /loaded|complete/;
|
1304
|
+
steal.type("js", function(options, success, error){
|
1305
|
+
// create a script tag
|
1306
|
+
var script = scriptTag(),
|
1307
|
+
deps;
|
1308
|
+
// if we have text, just set and insert text
|
1309
|
+
if (options.text) {
|
1310
|
+
// insert
|
1311
|
+
script.text = options.text;
|
1312
|
+
|
1313
|
+
}
|
1314
|
+
else {
|
1315
|
+
|
1316
|
+
var callback = function(evt){
|
1317
|
+
if (!script.readyState || stateCheck.test(script.readyState)) {
|
1318
|
+
cleanUp(script);
|
1319
|
+
success(script);
|
1320
|
+
}
|
1321
|
+
}
|
1322
|
+
// listen to loaded
|
1323
|
+
if (support.attachEvent) {
|
1324
|
+
script.attachEvent(STR_ONREADYSTATECHANGE, callback)
|
1325
|
+
} else {
|
1326
|
+
script[STR_ONLOAD] = callback;
|
1327
|
+
}
|
1328
|
+
|
1329
|
+
// error handling doesn't work on firefox on the filesystem
|
1330
|
+
if (support.error && error && options.protocol !== "file:") {
|
1331
|
+
if(support.attachEvent){
|
1332
|
+
script.attachEvent(STR_ONERROR, error);
|
1333
|
+
} else {
|
1334
|
+
script[ STR_ONERROR ] = error;
|
1335
|
+
}
|
1336
|
+
}
|
1337
|
+
script.src = options.src;
|
1338
|
+
script.onSuccess = success;
|
1339
|
+
}
|
1340
|
+
|
1341
|
+
// insert the script
|
1342
|
+
lastInserted = script;
|
1343
|
+
head().insertBefore(script, head().firstChild);
|
1344
|
+
|
1345
|
+
// if text, just call success right away, and clean up
|
1346
|
+
if (options.text) {
|
1347
|
+
success();
|
1348
|
+
cleanUp(script);
|
1349
|
+
}
|
1350
|
+
});
|
1351
|
+
|
1352
|
+
steal.type("fn", function(options, success, error){
|
1353
|
+
success(options.fn());
|
1354
|
+
});
|
1355
|
+
steal.type("text", function(options, success, error){
|
1356
|
+
steal.request(options, function(text){
|
1357
|
+
options.text = text;
|
1358
|
+
success(text);
|
1359
|
+
}, error)
|
1360
|
+
});
|
1361
|
+
|
1362
|
+
var cssCount = 0,
|
1363
|
+
createSheet = doc && doc.createStyleSheet,
|
1364
|
+
lastSheet;
|
1365
|
+
|
1366
|
+
steal.type("css", function css_type(options, success, error){
|
1367
|
+
if(options.text){ // less
|
1368
|
+
var css = doc[STR_CREATE_ELEMENT]('style');
|
1369
|
+
css.type = 'text/css';
|
1370
|
+
if (css.styleSheet) { // IE
|
1371
|
+
css.styleSheet.cssText = options.text;
|
1372
|
+
} else {
|
1373
|
+
(function (node) {
|
1374
|
+
if (css.childNodes.length > 0) {
|
1375
|
+
if (css.firstChild.nodeValue !== node.nodeValue) {
|
1376
|
+
css.replaceChild(node, css.firstChild);
|
1377
|
+
}
|
1378
|
+
} else {
|
1379
|
+
css.appendChild(node);
|
1380
|
+
}
|
1381
|
+
})(doc.createTextNode(options.text));
|
1382
|
+
}
|
1383
|
+
head().appendChild(css);
|
1384
|
+
} else {
|
1385
|
+
if( createSheet ){
|
1386
|
+
// IE has a 31 sheet and 31 import per sheet limit
|
1387
|
+
if(cssCount == 30 || !lastSheet){
|
1388
|
+
lastSheet = document.createStyleSheet();
|
1389
|
+
cssCount = 0;
|
1390
|
+
}
|
1391
|
+
|
1392
|
+
lastSheet.addImport(options.src);
|
1393
|
+
cssCount++;
|
1394
|
+
} else {
|
1395
|
+
options = options || {};
|
1396
|
+
var link = doc[STR_CREATE_ELEMENT]('link');
|
1397
|
+
link.rel = options.rel || "stylesheet";
|
1398
|
+
link.href = options.src;
|
1399
|
+
link.type = 'text/css';
|
1400
|
+
head().appendChild(link);
|
1401
|
+
}
|
1402
|
+
}
|
1403
|
+
|
1404
|
+
success();
|
1405
|
+
});
|
1406
|
+
|
1407
|
+
// Overwrite
|
1408
|
+
if(opts.types){
|
1409
|
+
for(var type in opts.types){
|
1410
|
+
steal.type(type, opts.types[type]);
|
1411
|
+
}
|
1412
|
+
}
|
1413
|
+
|
1414
|
+
|
1415
|
+
// =============================== HELPERS ===============================
|
1416
|
+
var factory = function() {
|
1417
|
+
return win.ActiveXObject ? new ActiveXObject("Microsoft.XMLHTTP") : new XMLHttpRequest();
|
1418
|
+
};
|
1419
|
+
|
1420
|
+
|
1421
|
+
steal.
|
1422
|
+
/**
|
1423
|
+
* Performs an XHR request
|
1424
|
+
* @param {Object} options
|
1425
|
+
* @param {Function} success
|
1426
|
+
* @param {Function} error
|
1427
|
+
*/
|
1428
|
+
request = function(options, success, error){
|
1429
|
+
var request = new factory(),
|
1430
|
+
contentType = (options.contentType || "application/x-www-form-urlencoded; charset=utf-8"),
|
1431
|
+
clean = function(){
|
1432
|
+
request = check = clean = null;
|
1433
|
+
},
|
1434
|
+
check = function(){
|
1435
|
+
if ( request.readyState === 4 ) {
|
1436
|
+
if ( request.status === 500 || request.status === 404 ||
|
1437
|
+
request.status === 2 || request.status < 0 ||
|
1438
|
+
(request.status === 0 && request.responseText === '') ) {
|
1439
|
+
error && error(request.status);
|
1440
|
+
clean();
|
1441
|
+
} else {
|
1442
|
+
success(request.responseText);
|
1443
|
+
clean();
|
1444
|
+
}
|
1445
|
+
return;
|
1446
|
+
}
|
1447
|
+
};
|
1448
|
+
|
1449
|
+
request.open("GET", options.src, options.async === false ? false : true);
|
1450
|
+
request.setRequestHeader('Content-type', contentType);
|
1451
|
+
if ( request.overrideMimeType ) {
|
1452
|
+
request.overrideMimeType(contentType);
|
1453
|
+
}
|
1454
|
+
|
1455
|
+
request.onreadystatechange = function(){
|
1456
|
+
check();
|
1457
|
+
}
|
1458
|
+
try {
|
1459
|
+
request.send(null);
|
1460
|
+
}
|
1461
|
+
catch (e) {
|
1462
|
+
if (clean) {
|
1463
|
+
console.error(e);
|
1464
|
+
error && error();
|
1465
|
+
clean();
|
1466
|
+
}
|
1467
|
+
}
|
1468
|
+
|
1469
|
+
};
|
1470
|
+
|
1471
|
+
|
1472
|
+
|
1473
|
+
|
1474
|
+
// =============================== MAPPING ===============================
|
1475
|
+
var insertMapping = function(p){
|
1476
|
+
// don't worry about // rooted paths
|
1477
|
+
var mapName,
|
1478
|
+
map;
|
1479
|
+
|
1480
|
+
// go through mappings
|
1481
|
+
for(var mapName in steal.mappings){
|
1482
|
+
map = steal.mappings[mapName]
|
1483
|
+
if(map.test.test(p)){
|
1484
|
+
return p.replace(mapName, map.path);
|
1485
|
+
}
|
1486
|
+
}
|
1487
|
+
return p;
|
1488
|
+
};
|
1489
|
+
File.prototype.mapJoin = function( url ){
|
1490
|
+
url = insertMapping(url);
|
1491
|
+
return File(url).joinFrom(this.path);
|
1492
|
+
};
|
1493
|
+
// modifies src
|
1494
|
+
steal.makeOptions = after(steal.makeOptions,function(raw){
|
1495
|
+
raw.src = steal.root.join(raw.rootSrc = insertMapping(raw.rootSrc));
|
1496
|
+
});
|
1497
|
+
|
1498
|
+
//root mappings to other locations
|
1499
|
+
steal.mappings = {};
|
1500
|
+
|
1501
|
+
steal.
|
1502
|
+
/**
|
1503
|
+
* Maps a 'rooted' folder to another location.
|
1504
|
+
* @param {String|Object} from the location you want to map from. For example:
|
1505
|
+
* 'foo/bar'
|
1506
|
+
* @param {String} [to] where you want to map this folder too. Ex: 'http://foo.cdn/bar'
|
1507
|
+
* @return {steal}
|
1508
|
+
*/
|
1509
|
+
map = function(from, to){
|
1510
|
+
if(typeof from == "string"){
|
1511
|
+
steal.mappings[from] = {
|
1512
|
+
test : new RegExp("^("+from+")([/.]|$)"),
|
1513
|
+
path: to
|
1514
|
+
};
|
1515
|
+
} else { // its an object
|
1516
|
+
for(var key in from){
|
1517
|
+
steal.map(key, from[key]);
|
1518
|
+
}
|
1519
|
+
}
|
1520
|
+
return this;
|
1521
|
+
}
|
1522
|
+
|
1523
|
+
// =============================== STARTUP ===============================
|
1524
|
+
|
1525
|
+
|
1526
|
+
var currentCollection;
|
1527
|
+
|
1528
|
+
// essentially ... we need to know when we are on our first steal
|
1529
|
+
// then we need to know when the collection of those steals ends ...
|
1530
|
+
// and, it helps if we use a 'collection' steal because of it's natural
|
1531
|
+
// use for going through the pending queue
|
1532
|
+
//
|
1533
|
+
extend(steal,{
|
1534
|
+
// called after steals are added to the pending queue
|
1535
|
+
after: function(){
|
1536
|
+
// if we don't have a current 'top' steal
|
1537
|
+
// we create one and set it up
|
1538
|
+
// to start loading its dependencies (the current pending steals)
|
1539
|
+
if(! currentCollection ){
|
1540
|
+
currentCollection = new steal.p.init();
|
1541
|
+
|
1542
|
+
// keep a reference in case it disappears
|
1543
|
+
var cur = currentCollection,
|
1544
|
+
// runs when a steal is starting
|
1545
|
+
go = function(){
|
1546
|
+
// indicates that a collection of steals has started
|
1547
|
+
steal.trigger("start", cur);
|
1548
|
+
when(cur,"complete", function(){
|
1549
|
+
steal.trigger("end", cur);
|
1550
|
+
});
|
1551
|
+
cur.loaded();
|
1552
|
+
};
|
1553
|
+
// if we are in rhino, start loading dependencies right away
|
1554
|
+
if(!win.setTimeout){
|
1555
|
+
go()
|
1556
|
+
}else{
|
1557
|
+
// otherwise wait a small timeout to make
|
1558
|
+
// sure we get all steals in the current file
|
1559
|
+
setTimeout(go,0)
|
1560
|
+
}
|
1561
|
+
}
|
1562
|
+
},
|
1563
|
+
_before : before,
|
1564
|
+
_after: after
|
1565
|
+
});
|
1566
|
+
|
1567
|
+
// this can probably move above
|
1568
|
+
steal.p.complete = before(steal.p.complete, function(){
|
1569
|
+
if(this === currentCollection){ // this is the last steal
|
1570
|
+
currentCollection = null;
|
1571
|
+
}
|
1572
|
+
});
|
1573
|
+
|
1574
|
+
|
1575
|
+
|
1576
|
+
// =============================== jQuery ===============================
|
1577
|
+
(function(){
|
1578
|
+
var jQueryIncremented = false,
|
1579
|
+
jQ,
|
1580
|
+
ready = false;
|
1581
|
+
|
1582
|
+
// check if jQuery loaded after every script load ...
|
1583
|
+
steal.p.loaded = before(steal.p.loaded, function(){
|
1584
|
+
|
1585
|
+
var $ = typeof jQuery !== "undefined" ? jQuery : null;
|
1586
|
+
if ($ && "readyWait" in $) {
|
1587
|
+
|
1588
|
+
//Increment jQuery readyWait if ncecessary.
|
1589
|
+
if (!jQueryIncremented) {
|
1590
|
+
jQ = $;
|
1591
|
+
$.readyWait += 1;
|
1592
|
+
jQueryIncremented = true;
|
1593
|
+
}
|
1594
|
+
}
|
1595
|
+
});
|
1596
|
+
|
1597
|
+
// once the current batch is done, fire ready if it hasn't already been done
|
1598
|
+
steal.bind('end', function(){
|
1599
|
+
if (jQueryIncremented && !ready) {
|
1600
|
+
jQ.ready(true);
|
1601
|
+
ready = true;
|
1602
|
+
}
|
1603
|
+
})
|
1604
|
+
|
1605
|
+
|
1606
|
+
})();
|
1607
|
+
|
1608
|
+
// =============================== ERROR HANDLING ===============================
|
1609
|
+
|
1610
|
+
steal.p.load = after(steal.p.load, function(stel){
|
1611
|
+
if(win.document && !this.completed && !this.completeTimeout && !steal.isRhino &&
|
1612
|
+
(this.options.protocol == "file:" || !support.error)){
|
1613
|
+
var self = this;
|
1614
|
+
this.completeTimeout = setTimeout(function(){
|
1615
|
+
throw "steal.js : "+self.options.src+" not completed"
|
1616
|
+
},5000);
|
1617
|
+
}
|
1618
|
+
});
|
1619
|
+
steal.p.complete = after(steal.p.complete, function(){
|
1620
|
+
this.completeTimeout && clearTimeout(this.completeTimeout)
|
1621
|
+
})
|
1622
|
+
|
1623
|
+
|
1624
|
+
// =============================== AOP ===============================
|
1625
|
+
function before(f, before, changeArgs){
|
1626
|
+
return changeArgs ?
|
1627
|
+
function before_changeArgs(){
|
1628
|
+
return f.apply(this,before.apply(this,arguments));
|
1629
|
+
}:
|
1630
|
+
function before_args(){
|
1631
|
+
before.apply(this,arguments);
|
1632
|
+
return f.apply(this,arguments);
|
1633
|
+
}
|
1634
|
+
}
|
1635
|
+
/**
|
1636
|
+
* Set up a function that runs after the first param.
|
1637
|
+
* @param {Object} f
|
1638
|
+
* @param {Object} after
|
1639
|
+
* @param {Object} changeRet If true, the result of the function will be passed to the after function as the first param. If false, the after function's params are the
|
1640
|
+
* same as the original function's params
|
1641
|
+
*/
|
1642
|
+
function after(f, after, changeRet){
|
1643
|
+
|
1644
|
+
return changeRet ?
|
1645
|
+
function after_CRet(){
|
1646
|
+
return after.apply(this,[f.apply(this,arguments)].concat(makeArray(arguments)));
|
1647
|
+
}:
|
1648
|
+
function after_Ret(){
|
1649
|
+
var ret = f.apply(this,arguments);
|
1650
|
+
after.apply(this,arguments);
|
1651
|
+
return ret;
|
1652
|
+
}
|
1653
|
+
}
|
1654
|
+
|
1655
|
+
// converts a function to work with when
|
1656
|
+
function convert(ob, func){
|
1657
|
+
|
1658
|
+
var oldFunc = ob[func];
|
1659
|
+
|
1660
|
+
// if we don't have callbacks
|
1661
|
+
if(!ob[func].callbacks){
|
1662
|
+
//replace start with a function that will call ob2's method
|
1663
|
+
ob[func] = function(){
|
1664
|
+
var me = arguments.callee,
|
1665
|
+
ret;
|
1666
|
+
|
1667
|
+
// call the original function
|
1668
|
+
ret = oldFunc.apply(ob,arguments);
|
1669
|
+
|
1670
|
+
var cbs = me.callbacks,
|
1671
|
+
len = cbs.length;
|
1672
|
+
|
1673
|
+
//mark as called so any callees added to this caller will
|
1674
|
+
//automatically get called
|
1675
|
+
me.called = true;
|
1676
|
+
// call other callbacks
|
1677
|
+
for(var i =0; i < len; i++){
|
1678
|
+
cbs[i].called()
|
1679
|
+
}
|
1680
|
+
return ret;
|
1681
|
+
|
1682
|
+
}
|
1683
|
+
ob[func].callbacks = [];
|
1684
|
+
}
|
1685
|
+
|
1686
|
+
return ob[func];
|
1687
|
+
};
|
1688
|
+
|
1689
|
+
// maintains
|
1690
|
+
function join(obj, meth){
|
1691
|
+
this.obj = obj;
|
1692
|
+
this.meth = meth;
|
1693
|
+
convert(obj, meth);
|
1694
|
+
this.calls = 0
|
1695
|
+
};
|
1696
|
+
|
1697
|
+
extend(join.prototype,{
|
1698
|
+
called : function(){
|
1699
|
+
this.calls--;
|
1700
|
+
this.go();
|
1701
|
+
},
|
1702
|
+
// adds functions that will call this join
|
1703
|
+
add : function(obj, meth){
|
1704
|
+
// converts the function to be able to call
|
1705
|
+
// this join
|
1706
|
+
var f = convert(obj, meth);
|
1707
|
+
if(!f.called){
|
1708
|
+
|
1709
|
+
// adds us to the callback ... the callback will call
|
1710
|
+
// called
|
1711
|
+
f.callbacks.push(this);
|
1712
|
+
this.calls++;
|
1713
|
+
}
|
1714
|
+
},
|
1715
|
+
// call go every time the funtion is called
|
1716
|
+
|
1717
|
+
go : function(){
|
1718
|
+
if(this.calls === 0){
|
1719
|
+
this.obj[this.meth]()
|
1720
|
+
}
|
1721
|
+
}
|
1722
|
+
})
|
1723
|
+
// chains two functions. When the first one is called,
|
1724
|
+
// it calls the second function.
|
1725
|
+
// If the second function has multiple callers, it waits until all have been called
|
1726
|
+
//
|
1727
|
+
// when(parent,"start", steal, "start")
|
1728
|
+
//
|
1729
|
+
function when(){
|
1730
|
+
// handle if we get called with a function
|
1731
|
+
var args = makeArray(arguments),
|
1732
|
+
last = args[args.length -1];
|
1733
|
+
|
1734
|
+
if(typeof last === 'function' ){
|
1735
|
+
args[args.length -1] = {
|
1736
|
+
'fn' : last
|
1737
|
+
}
|
1738
|
+
args.push("fn");
|
1739
|
+
};
|
1740
|
+
|
1741
|
+
var waitMeth = args.pop(),
|
1742
|
+
waitObj = args.pop(),
|
1743
|
+
joined = new join(waitObj, waitMeth);
|
1744
|
+
|
1745
|
+
for(var i =0; i < args.length; i = i+2){
|
1746
|
+
joined.add(args[i], args[i+1])
|
1747
|
+
}
|
1748
|
+
|
1749
|
+
// call right away if it should
|
1750
|
+
joined.go();
|
1751
|
+
}
|
1752
|
+
|
1753
|
+
// =========== DEBUG =========
|
1754
|
+
|
1755
|
+
/*var name = function(stel){
|
1756
|
+
if(stel.options && stel.options.type == "fn"){
|
1757
|
+
return stel.options.orig.toString().substr(0,50)
|
1758
|
+
}
|
1759
|
+
return stel.options ? stel.options.rootSrc : "CONTAINER"
|
1760
|
+
}
|
1761
|
+
|
1762
|
+
|
1763
|
+
steal.p.load = before(steal.p.load, function(){
|
1764
|
+
console.log("load", name(this), this.loading, this.id)
|
1765
|
+
})
|
1766
|
+
|
1767
|
+
steal.p.loaded = before(steal.p.loaded, function(){
|
1768
|
+
console.log("loaded", name(this), this.id)
|
1769
|
+
})
|
1770
|
+
steal.p.complete = before(steal.p.complete, function(){
|
1771
|
+
console.log("complete", name(this), this.id)
|
1772
|
+
})*/
|
1773
|
+
|
1774
|
+
// ============= WINDOW LOAD ========
|
1775
|
+
var addEvent = function(elem, type, fn) {
|
1776
|
+
if ( elem.addEventListener ) {
|
1777
|
+
elem.addEventListener( type, fn, false );
|
1778
|
+
} else if ( elem.attachEvent ) {
|
1779
|
+
elem.attachEvent( "on" + type, fn );
|
1780
|
+
} else {
|
1781
|
+
fn();
|
1782
|
+
}
|
1783
|
+
};
|
1784
|
+
var loaded = {
|
1785
|
+
load : function(){},
|
1786
|
+
end : function(){}
|
1787
|
+
};
|
1788
|
+
|
1789
|
+
var firstEnd = false;
|
1790
|
+
addEvent(win, "load", function(){
|
1791
|
+
loaded.load();
|
1792
|
+
});
|
1793
|
+
steal.one("end", function(collection){
|
1794
|
+
loaded.end();
|
1795
|
+
firstEnd = collection;
|
1796
|
+
steal.trigger("done", firstEnd)
|
1797
|
+
})
|
1798
|
+
when(loaded,"load",loaded,"end", function(){
|
1799
|
+
steal.trigger("ready")
|
1800
|
+
steal.isReady = true;
|
1801
|
+
});
|
1802
|
+
|
1803
|
+
steal.events.done = {
|
1804
|
+
add : function(cb){
|
1805
|
+
if(firstEnd){
|
1806
|
+
cb(firstEnd);
|
1807
|
+
return false;
|
1808
|
+
} else {
|
1809
|
+
return cb;
|
1810
|
+
}
|
1811
|
+
}
|
1812
|
+
};
|
1813
|
+
|
1814
|
+
// =========== HAS ARRAY STUFF ============
|
1815
|
+
// Logic that deals with files that have collections of other files within them. This is usually a production.css file,
|
1816
|
+
// which when it loads, needs to mark several CSS and LESS files it represents as being "loaded". This is done
|
1817
|
+
// by the production.js file having steal({src: "production.css", has: ["file1.css", "file2.css"]
|
1818
|
+
|
1819
|
+
// after a steal is created, if its been loaded already and has a "has", mark those files as loaded
|
1820
|
+
steal.p.make = after(steal.p.make, function(stel){
|
1821
|
+
// if we have things
|
1822
|
+
if( stel.options.has ) {
|
1823
|
+
// if we have loaded this already (and we are adding has's)
|
1824
|
+
if( stel.isLoaded ) {
|
1825
|
+
stel.loadHas();
|
1826
|
+
} else {
|
1827
|
+
// have to mark has as loading
|
1828
|
+
steal.loading.apply(steal,stel.options.has)
|
1829
|
+
}
|
1830
|
+
}
|
1831
|
+
return stel;
|
1832
|
+
}, true)
|
1833
|
+
|
1834
|
+
// if we're about to mark a file as loaded, mark its "has" array files as loaded also
|
1835
|
+
steal.p.loaded = before(steal.p.loaded, function(){
|
1836
|
+
if(this.options.has){
|
1837
|
+
this.loadHas();
|
1838
|
+
}
|
1839
|
+
})
|
1840
|
+
|
1841
|
+
steal.p.
|
1842
|
+
/**
|
1843
|
+
* @hide
|
1844
|
+
* Goes through the array of files listed in this.options.has, marks them all as loaded.
|
1845
|
+
* This is used for files like production.css, which, once they load, need to mark the files they
|
1846
|
+
* contain as loaded.
|
1847
|
+
*/
|
1848
|
+
loadHas = function(){
|
1849
|
+
var stel, i,
|
1850
|
+
current = File.cur();
|
1851
|
+
|
1852
|
+
// mark everything in has loaded
|
1853
|
+
for(i=0; i<this.options.has.length; i++){
|
1854
|
+
// don't want the current file to change, since we're just marking files as loaded
|
1855
|
+
File.cur(current)
|
1856
|
+
stel = steal.p.make( this.options.has[i] );
|
1857
|
+
// need to set up a "complete" callback for this file, so later waits know its already
|
1858
|
+
// been completed
|
1859
|
+
convert(stel, "complete")
|
1860
|
+
stel.loaded();
|
1861
|
+
|
1862
|
+
}
|
1863
|
+
}
|
1864
|
+
|
1865
|
+
// =========== INTERACTIVE STUFF ===========
|
1866
|
+
// Logic that deals with making steal work with IE. IE executes scripts out of order, so in order to tell which scripts are
|
1867
|
+
// dependencies of another, steal needs to check which is the currently "interactive" script.
|
1868
|
+
|
1869
|
+
|
1870
|
+
var interactiveScript,
|
1871
|
+
// key is script name, value is array of pending items
|
1872
|
+
interactives = {},
|
1873
|
+
getInteractiveScript = function(){
|
1874
|
+
var i, script,
|
1875
|
+
scripts = doc[STR_GET_BY_TAG]('script');
|
1876
|
+
for (i = scripts.length - 1; i > -1 && (script = scripts[i]); i--) {
|
1877
|
+
if (script.readyState === 'interactive') {
|
1878
|
+
return script;
|
1879
|
+
}
|
1880
|
+
}
|
1881
|
+
},
|
1882
|
+
getCachedInteractiveScript = function() {
|
1883
|
+
var scripts, i, script;
|
1884
|
+
if (interactiveScript && interactiveScript.readyState === 'interactive') {
|
1885
|
+
return interactiveScript;
|
1886
|
+
}
|
1887
|
+
|
1888
|
+
if(script = getInteractiveScript()){
|
1889
|
+
interactiveScript = script;
|
1890
|
+
return script;
|
1891
|
+
}
|
1892
|
+
|
1893
|
+
// check last inserted
|
1894
|
+
if(lastInserted && lastInserted.readyState == 'interactive'){
|
1895
|
+
return lastInserted;
|
1896
|
+
}
|
1897
|
+
|
1898
|
+
return null;
|
1899
|
+
};
|
1900
|
+
|
1901
|
+
support.interactive = doc && !!getInteractiveScript();
|
1902
|
+
|
1903
|
+
|
1904
|
+
if (support.interactive) {
|
1905
|
+
|
1906
|
+
// after steal is called, check which script is "interactive" (for IE)
|
1907
|
+
steal.after = after(steal.after, function(){
|
1908
|
+
var interactive = getCachedInteractiveScript();
|
1909
|
+
// if no interactive script, this is a steal coming from inside a steal, let complete handle it
|
1910
|
+
if (!interactive || !interactive.src || /steal\.(production|production\.[a-zA-Z0-9\-\.\_]*)*js/.test(interactive.src)) {
|
1911
|
+
return;
|
1912
|
+
}
|
1913
|
+
// get the source of the script
|
1914
|
+
var src = interactive.src;
|
1915
|
+
// create an array to hold all steal calls for this script
|
1916
|
+
if (!interactives[src]) {
|
1917
|
+
interactives[src] = []
|
1918
|
+
}
|
1919
|
+
// add to the list of steals for this script tag
|
1920
|
+
if (src) {
|
1921
|
+
interactives[src].push.apply(interactives[src], pending);
|
1922
|
+
pending = [];
|
1923
|
+
}
|
1924
|
+
})
|
1925
|
+
|
1926
|
+
// This is used for packaged scripts. As the packaged script executes, we grab the
|
1927
|
+
// dependencies that have come so far and assign them to the loaded script
|
1928
|
+
steal.preloaded = before(steal.preloaded, function(stel){
|
1929
|
+
// get the src name
|
1930
|
+
var src = stel.options.src,
|
1931
|
+
// and the src of the current interactive script
|
1932
|
+
interactiveSrc = getCachedInteractiveScript().src;
|
1933
|
+
|
1934
|
+
|
1935
|
+
interactives[src] = interactives[interactiveSrc];
|
1936
|
+
interactives[interactiveSrc] = null;
|
1937
|
+
});
|
1938
|
+
|
1939
|
+
}
|
1940
|
+
|
1941
|
+
// =========== OPTIONS ==========
|
1942
|
+
|
1943
|
+
var stealCheck = /steal\.(production\.)?js.*/,
|
1944
|
+
getStealScriptSrc = function(){
|
1945
|
+
if(!doc){
|
1946
|
+
return;
|
1947
|
+
}
|
1948
|
+
var scripts = doc[STR_GET_BY_TAG]("script"),
|
1949
|
+
i = 0,
|
1950
|
+
len = scripts.length;
|
1951
|
+
|
1952
|
+
|
1953
|
+
//find the steal script and setup initial paths.
|
1954
|
+
for ( ; i < len; i++ ) {
|
1955
|
+
var src = scripts[i].src;
|
1956
|
+
if ( src && stealCheck.test(src) ) { //if script has steal.js
|
1957
|
+
return scripts[i];
|
1958
|
+
}
|
1959
|
+
|
1960
|
+
}
|
1961
|
+
return;
|
1962
|
+
};
|
1963
|
+
steal.getScriptOptions = function(script){
|
1964
|
+
var script = script || getStealScriptSrc(),
|
1965
|
+
src,
|
1966
|
+
scriptOptions,
|
1967
|
+
options = {},
|
1968
|
+
commaSplit;
|
1969
|
+
|
1970
|
+
if(script){
|
1971
|
+
var src = script.src,
|
1972
|
+
start = src.replace(stealCheck,"");
|
1973
|
+
if(/steal\/$/.test(start)){
|
1974
|
+
options.rootUrl = start.substr(0, start.length - 6);
|
1975
|
+
} else {
|
1976
|
+
options.rootUrl = start+"../"
|
1977
|
+
}
|
1978
|
+
if ( /steal\.production\.js/.test(src) ) {
|
1979
|
+
options.env = "production";
|
1980
|
+
}
|
1981
|
+
if ( src.indexOf('?') !== -1 ) {
|
1982
|
+
|
1983
|
+
scriptOptions = src.split('?')[1];
|
1984
|
+
commaSplit = scriptOptions.split(",");
|
1985
|
+
|
1986
|
+
if ( commaSplit[0] ) {
|
1987
|
+
options.startFile = commaSplit[0];
|
1988
|
+
}
|
1989
|
+
if ( commaSplit[1] && steal.options.env != "production" ) {
|
1990
|
+
options.env = commaSplit[1];
|
1991
|
+
}
|
1992
|
+
|
1993
|
+
}
|
1994
|
+
|
1995
|
+
}
|
1996
|
+
return options;
|
1997
|
+
};
|
1998
|
+
|
1999
|
+
startup = after(startup, function(){
|
2000
|
+
var options = steal.options,
|
2001
|
+
startFiles = [];
|
2002
|
+
extend(options, steal.getScriptOptions());
|
2003
|
+
// a steal that existed before this steal
|
2004
|
+
if(typeof oldsteal == 'object'){
|
2005
|
+
extend(options, oldsteal);
|
2006
|
+
}
|
2007
|
+
|
2008
|
+
// if it looks like steal[xyz]=bar, add those to the options
|
2009
|
+
var search = win.location && decodeURIComponent(win.location.search);
|
2010
|
+
search && search.replace(/steal\[([^\]]+)\]=([^&]+)/g, function( whoe, prop, val ) {
|
2011
|
+
// support array like params
|
2012
|
+
var commaSeparated = val.split(",");
|
2013
|
+
if(commaSeparated.length > 1){
|
2014
|
+
val = commaSeparated;
|
2015
|
+
}
|
2016
|
+
options[prop] = val;
|
2017
|
+
});
|
2018
|
+
|
2019
|
+
// CALCULATE CURRENT LOCATION OF THINGS ...
|
2020
|
+
steal.rootUrl(options.rootUrl);
|
2021
|
+
|
2022
|
+
// CLEAN UP OPTIONS
|
2023
|
+
// make startFile have .js ending
|
2024
|
+
if(options.startFile && options.startFile.indexOf(".") == '-1'){
|
2025
|
+
options.startFile = options.startFile + "/" + options.startFile.match(/[^\/]+$/)[0] + ".js";
|
2026
|
+
}
|
2027
|
+
|
2028
|
+
if(!options.logLevel){
|
2029
|
+
options.logLevel = 0;
|
2030
|
+
}
|
2031
|
+
|
2032
|
+
//calculate production location;
|
2033
|
+
if (!options.production && options.startFile ) {
|
2034
|
+
options.production = File(options.startFile).dir() + '/production.js';
|
2035
|
+
}
|
2036
|
+
if ( options.production ) {
|
2037
|
+
options.production = options.production + (options.production.indexOf('.js') == -1 ? '.js' : '');
|
2038
|
+
}
|
2039
|
+
each(options.loaded || [], function(i, stel){
|
2040
|
+
steal.loaded(stel)
|
2041
|
+
})
|
2042
|
+
|
2043
|
+
if(typeof options.startFiles === "string"){
|
2044
|
+
startFiles.push(options.startFiles);
|
2045
|
+
}
|
2046
|
+
else if(options.startFiles && options.startFiles.length){
|
2047
|
+
startFiles = options.startFiles;
|
2048
|
+
}
|
2049
|
+
var steals = [];
|
2050
|
+
// need to load startFiles in dev or production mode (to run funcunit in production)
|
2051
|
+
if( startFiles.length ){
|
2052
|
+
steal.options.startFiles = startFiles;
|
2053
|
+
steals.push.apply(steals, startFiles)
|
2054
|
+
}
|
2055
|
+
// either instrument is in this page (if we're the window opened from steal.browser), or its opener has it
|
2056
|
+
if ( options.instrument || (!options.browser && win.top && win.top.opener &&
|
2057
|
+
win.top.opener.steal && win.top.opener.steal.options.instrument) ) {
|
2058
|
+
// force startFiles to load before instrument
|
2059
|
+
steals.push(function(){}, {
|
2060
|
+
src: "steal/instrument",
|
2061
|
+
waits: true
|
2062
|
+
});
|
2063
|
+
}
|
2064
|
+
//we only load things with force = true
|
2065
|
+
if (options.env == 'production' && options.loadProduction) {
|
2066
|
+
if (options.production) {
|
2067
|
+
//steal(steal.options.startFile);
|
2068
|
+
steal({
|
2069
|
+
src: options.production,
|
2070
|
+
force: true
|
2071
|
+
});
|
2072
|
+
}
|
2073
|
+
}
|
2074
|
+
else {
|
2075
|
+
if (options.loadDev !== false) {
|
2076
|
+
steals.unshift({
|
2077
|
+
src: 'steal/dev/dev.js',
|
2078
|
+
ignore: true
|
2079
|
+
});
|
2080
|
+
}
|
2081
|
+
|
2082
|
+
if (options.startFile) {
|
2083
|
+
steals.push(options.startFile)
|
2084
|
+
}
|
2085
|
+
}
|
2086
|
+
if (steals.length) {
|
2087
|
+
steal.apply(null, steals);
|
2088
|
+
}
|
2089
|
+
});
|
2090
|
+
|
2091
|
+
|
2092
|
+
steal.when = when;
|
2093
|
+
// make steal public
|
2094
|
+
win.steal = steal;
|
2095
|
+
|
2096
|
+
startup();
|
2097
|
+
|
2098
|
+
})()
|