Fixed bug that cause ResourceLoader to generate mediaWiki.loader.implement calls...
[mediawiki.git] / resources / mediawiki / mediawiki.js
blob282c52ae64a6fcbacd84f372aa6f1bcc00327125
1 /*
2  * JavaScript backwards-compatibility and support
3  */
5 // Make calling .indexOf() on an array work on older browsers
6 if ( typeof Array.prototype.indexOf === 'undefined' ) { 
7         Array.prototype.indexOf = function( needle ) {
8                 for ( var i = 0; i < this.length; i++ ) {
9                         if ( this[i] === needle ) {
10                                 return i;
11                         }
12                 }
13                 return -1;
14         };
16 // Add array comparison functionality
17 if ( typeof Array.prototype.compare === 'undefined' ) { 
18         Array.prototype.compare = function( against ) {
19                 if ( this.length != against.length ) {
20                         return false;
21                 }
22                 for ( var i = 0; i < against.length; i++ ) {
23                         if ( this[i].compare ) { 
24                                 if ( !this[i].compare( against[i] ) ) {
25                                         return false;
26                                 }
27                         }
28                         if ( this[i] !== against[i] ) {
29                                 return false;
30                         }
31                 }
32                 return true;
33         };
37  * Core MediaWiki JavaScript Library
38  */
39 // Attach to window
40 window.mediaWiki = new ( function( $ ) {
41         
42         /* Constants */
43         
44         // This will not change until we are 100% ready to turn off legacy globals
45         var LEGACY_GLOBALS = true;
46         
47         /* Private Members */
48         
49         var that = this;
50         
51         /* Prototypes */
52         
53         this.prototypes = {
54                 /*
55                  * An object which allows single and multiple get/set/exists functionality on a list of key / value pairs
56                  * 
57                  * @param {boolean} global whether to get/set/exists values on the window object or a private object
58                  * @param {function} parser function to perform extra processing before while getting a value which accepts
59                  * value and options parameters where value is a string to be parsed and options is an object of options for the
60                  * parser
61                  */
62                 'configuration': function( global, parser ) {
63                         
64                         /* Private Members */
65                         
66                         var that = this;
67                         var values = global === true ? window : {};
68                         
69                         /* Public Methods */
70                         
71                         /**
72                          * Gets one or more values
73                          * 
74                          * If called with no arguments, all values will be returned. If a parser is in use, no parsing will take
75                          * place when calling with no arguments or calling with an array of names.
76                          * 
77                          * @param {mixed} selection string name of value to get, array of string names of values to get, or object
78                          * of name/option pairs
79                          * @param {object} options optional set of options which are also passed to a parser if in use; only used
80                          * when selection is a string
81                          * @format options
82                          *      {
83                          *              // Value to use if key does not exist
84                          *              'fallback': ''
85                          *      }
86                          */
87                         this.get = function( selection, options ) {
88                                 if ( typeof selection === 'object' ) {
89                                         var results = {};
90                                         for ( s in selection ) {
91                                                 if ( selection.hasOwnProperty( s ) ) {
92                                                         if ( typeof s === 'string' ) {
93                                                                 return that.get( values[s], selection[s] );
94                                                         } else {
95                                                                 return that.get( selection[s] );
96                                                         }
97                                                 }
98                                         }
99                                         return results;
100                                 } else if ( typeof selection === 'string' ) {
101                                         if ( typeof values[selection] === 'undefined' ) {
102                                                 return 'fallback' in options !== 'undefined' ? options.fallback : null;
103                                         } else {
104                                                 if ( typeof parser === 'function' ) {
105                                                         return parser( values[selection], options );
106                                                 } else {
107                                                         return values[selection];
108                                                 }
109                                         }
110                                 } else {
111                                         return values;
112                                 }
113                         };
114                         
115                         /**
116                          * Sets one or multiple configuration values using a key and a value or an object of keys and values
117                          * 
118                          * @param {mixed} key string of name by which value will be made accessible, or object of name/value pairs
119                          * @param {mixed} value optional value to set, only in use when key is a string
120                          */
121                         this.set = function( selection, value ) {
122                                 if ( typeof selection === 'object' ) {
123                                         for ( var s in selection ) {
124                                                 values[s] = selection[s];
125                                         }
126                                 } else if ( typeof selection === 'string' && typeof value !== 'undefined' ) {
127                                         values[selection] = value;
128                                 }
129                         };
130                         
131                         /**
132                          * Checks if one or multiple configuration fields exist
133                          */
134                         this.exists = function( selection ) {
135                                 if ( typeof keys === 'object' ) {
136                                         for ( var s = 0; s < selection.length; s++ ) {
137                                                 if ( !( selection[s] in values ) ) {
138                                                         return false;
139                                                 }
140                                         }
141                                         return true;
142                                 } else {
143                                         return selection in values;
144                                 }
145                         };
146                 }
147         };
148         
149         /* Methods */
150         
151         /*
152          * Dummy function which in debug mode can be replaced with a function that does something clever
153          */
154         this.log = function() { };
155         
156         /*
157          * List of configuration values
158          * 
159          * In legacy mode the values this object wraps will be in the global space
160          */
161         this.config = new this.prototypes.configuration( LEGACY_GLOBALS );
162         
163         /*
164          * Information about the current user
165          */
166         this.user = new ( function() {
167                 
168                 /* Public Members */
169                 
170                 this.options = new that.prototypes.configuration();
171         } )();
172         
173         /*
174          * Basic parser, can be replaced with something more robust
175          */
176         this.parser = function( text, options ) {
177                 if ( typeof options === 'object' && typeof options.parameters === 'object' ) {
178                         for ( var p = 0; p < options.parameters.length; p++ ) {
179                                 text = text.replace( '\$' + ( parseInt( p ) + 1 ), options.parameters[p] );
180                         }
181                 }
182                 return text;
183         };
184         
185         /*
186          * Localization system
187          */
188         this.msg = new that.prototypes.configuration( false, this.parser );
189         
190         /*
191          * Client-side module loader which integrates with the MediaWiki ResourceLoader
192          */
193         this.loader = new ( function() {
194                 
195                 /* Private Members */
196                 
197                 var that = this;
198                 /*
199                  * Mapping of registered modules
200                  * 
201                  * The jquery module is pre-registered, because it must have already been provided for this object to have
202                  * been built, and in debug mode jquery would have been provided through a unique loader request, making it
203                  * impossible to hold back registration of jquery until after mediawiki.
204                  * 
205                  * Format:
206                  *      {
207                  *              'moduleName': {
208                  *                      'dependencies': ['required module', 'required module', ...], (or) function() {}
209                  *                      'state': 'registered', 'loading', 'loaded', 'ready', or 'error'
210                  *                      'script': function() {},
211                  *                      'style': 'css code string',
212                  *                      'messages': { 'key': 'value' },
213                  *                      'version': ############## (unix timestamp)
214                  *              }
215                  *      }
216                  */
217                 var registry = {};
218                 // List of modules which will be loaded as when ready
219                 var batch = [];
220                 // List of modules to be loaded
221                 var queue = [];
222                 // List of callback functions waiting for modules to be ready to be called
223                 var jobs = [];
224                 // Flag indicating that requests should be suspended
225                 var suspended = true;
226                 // Flag inidicating that document ready has occured
227                 var ready = false;
228                 
229                 /* Private Methods */
230                 
231                 /**
232                  * Generates an ISO8601 string from a UNIX timestamp
233                  */
234                 function formatVersionNumber( timestamp ) {
235                         var date = new Date();
236                         date.setTime( timestamp * 1000 );
237                         function pad1( n ) {
238                                 return n < 10 ? '0' + n : n
239                         }
240                         function pad2( n ) {
241                                 return n < 10 ? '00' + n : ( n < 100 ? '0' + n : n );     
242                         }
243                         return date.getUTCFullYear() + '-' +
244                                 pad1( date.getUTCMonth() + 1 ) + '-' +
245                                 pad1( date.getUTCDate() ) + 'T' +
246                                 pad1( date.getUTCHours() ) + ':' +
247                                 pad1( date.getUTCMinutes() ) + ':' +
248                                 pad1( date.getUTCSeconds() ) +
249                                 'Z';
250                 }
251                 
252                 /**
253                  * Recursively resolves dependencies and detects circular references
254                  */
255                 function recurse( module, resolved, unresolved ) {
256                         unresolved[unresolved.length] = module;
257                         // Resolves dynamic loader function and replaces it with it's own results
258                         if ( typeof registry[module].dependencies === 'function' ) {
259                                 registry[module].dependencies = registry[module].dependencies();
260                                 // Gaurantees the module's dependencies are always in an array 
261                                 if ( typeof registry[module].dependencies !== 'object' ) {
262                                         registry[module].dependencies = [registry[module].dependencies];
263                                 }
264                         }
265                         // Tracks down dependencies
266                         for ( var n = 0; n < registry[module].dependencies.length; n++ ) {
267                                 if ( resolved.indexOf( registry[module].dependencies[n] ) === -1 ) {
268                                         if ( unresolved.indexOf( registry[module].dependencies[n] ) !== -1 ) {
269                                                 throw new Error(
270                                                         'Circular reference detected: ' + module + ' -> ' + registry[module].dependencies[n]
271                                                 );
272                                         }
273                                         recurse( registry[module].dependencies[n], resolved, unresolved );
274                                 }
275                         }
276                         resolved[resolved.length] = module;
277                         unresolved.splice( unresolved.indexOf( module ), 1 );
278                 }
279                 
280                 /**
281                  * Gets a list of modules names that a module dependencies in their proper dependency order
282                  * 
283                  * @param mixed string module name or array of string module names
284                  * @return list of dependencies
285                  * @throws Error if circular reference is detected
286                  */
287                 function resolve( module, resolved, unresolved ) {
288                         // Allow calling with an array of module names
289                         if ( typeof module === 'object' ) {
290                                 var modules = [];
291                                 for ( var m = 0; m < module.length; m++ ) {
292                                         var dependencies = resolve( module[m] );
293                                         for ( var n = 0; n < dependencies.length; n++ ) {
294                                                 modules[modules.length] = dependencies[n];
295                                         }
296                                 }
297                                 return modules;
298                         } else if ( typeof module === 'string' ) {
299                                 // Undefined modules have no dependencies
300                                 if ( !( module in registry ) ) {
301                                         return [];
302                                 }
303                                 var resolved = [];
304                                 recurse( module, resolved, [] );
305                                 return resolved;
306                         }
307                         throw new Error( 'Invalid module argument: ' + module );
308                 };
309                 
310                 /**
311                  * Narrows a list of module names down to those matching a specific state. Possible states are 'undefined',
312                  * 'registered', 'loading', 'loaded', or 'ready'
313                  * 
314                  * @param mixed string or array of strings of module states to filter by
315                  * @param array list of module names to filter (optional, all modules will be used by default)
316                  * @return array list of filtered module names
317                  */
318                 function filter( states, modules ) {
319                         // Allow states to be given as a string
320                         if ( typeof states === 'string' ) {
321                                 states = [states];
322                         }
323                         // If called without a list of modules, build and use a list of all modules
324                         var list = [];
325                         if ( typeof modules === 'undefined' ) {
326                                 modules = [];
327                                 for ( module in registry ) {
328                                         modules[modules.length] = module;
329                                 }
330                         }
331                         // Build a list of modules which are in one of the specified states
332                         for ( var s = 0; s < states.length; s++ ) {
333                                 for ( var m = 0; m < modules.length; m++ ) {
334                                         if (
335                                                 ( states[s] == 'undefined' && typeof registry[modules[m]] === 'undefined' ) ||
336                                                 ( typeof registry[modules[m]] === 'object' && registry[modules[m]].state === states[s] )
337                                         ) {
338                                                 list[list.length] = modules[m];
339                                         }
340                                 }
341                         }
342                         return list;
343                 }
344                 
345                 /**
346                  * Executes a loaded module, making it ready to use
347                  * 
348                  * @param string module name to execute
349                  */
350                 function execute( module ) {
351                         if ( typeof registry[module] === 'undefined' ) {
352                                 throw new Error( 'Module has not been registered yet: ' + module );
353                         } else if ( registry[module].state === 'registered' ) {
354                                 throw new Error( 'Module has not been requested from the server yet: ' + module );
355                         } else if ( registry[module].state === 'loading' ) {
356                                 throw new Error( 'Module has not completed loading yet: ' + module );
357                         } else if ( registry[module].state === 'ready' ) {
358                                 throw new Error( 'Module has already been loaded: ' + module );
359                         }
360                         // Add style sheet to document
361                         if ( typeof registry[module].style === 'string' && registry[module].style.length ) {
362                                 $( 'head' ).append( '<style type="text/css">' + registry[module].style + '</style>' );
363                         } else if ( typeof registry[module].style === 'object' && !( registry[module].style instanceof Array ) ) {
364                                 for ( var media in registry[module].style ) {
365                                         $( 'head' ).append(
366                                                 '<style type="text/css" media="' + media + '">' + registry[module].style[media] + '</style>'
367                                         );
368                                 }
369                         }
370                         // Add localizations to message system
371                         if ( typeof registry[module].messages === 'object' ) {
372                                 mediaWiki.msg.set( registry[module].messages );
373                         }
374                         // Execute script
375                         try {
376                                 registry[module].script();
377                                 registry[module].state = 'ready';
378                                 // Run jobs who's dependencies have just been met
379                                 for ( var j = 0; j < jobs.length; j++ ) {
380                                         if ( filter( 'ready', jobs[j].dependencies ).compare( jobs[j].dependencies ) ) {
381                                                 if ( typeof jobs[j].ready === 'function' ) {
382                                                         jobs[j].ready();
383                                                 }
384                                                 jobs.splice( j, 1 );
385                                                 j--;
386                                         }
387                                 }
388                                 // Execute modules who's dependencies have just been met
389                                 for ( r in registry ) {
390                                         if ( registry[r].state == 'loaded' ) {
391                                                 if ( filter( ['ready'], registry[r].dependencies ).compare( registry[r].dependencies ) ) {
392                                                         execute( r );
393                                                 }
394                                         }
395                                 }
396                         } catch ( e ) {
397                                 mediaWiki.log( 'Exception thrown by ' + module + ': ' + e.message );
398                                 mediaWiki.log( e );
399                                 registry[module].state = 'error';                               
400                                 // Run error callbacks of jobs affected by this condition
401                                 for ( var j = 0; j < jobs.length; j++ ) {
402                                         if ( jobs[j].dependencies.indexOf( module ) !== -1 ) {
403                                                 if ( typeof jobs[j].error === 'function' ) {
404                                                         jobs[j].error();
405                                                 }
406                                                 jobs.splice( j, 1 );
407                                                 j--;
408                                         }
409                                 }
410                         }
411                 }
412                 
413                 /**
414                  * Adds a dependencies to the queue with optional callbacks to be run when the dependencies are ready or fail
415                  * 
416                  * @param mixed string moulde name or array of string module names
417                  * @param function ready callback to execute when all dependencies are ready
418                  * @param function error callback to execute when any dependency fails
419                  */
420                 function request( dependencies, ready, error ) {
421                         // Allow calling by single module name
422                         if ( typeof dependencies === 'string' ) {
423                                 dependencies = [dependencies];
424                                 if ( dependencies[0] in registry ) {
425                                         for ( var n = 0; n < registry[dependencies[0]].dependencies.length; n++ ) {
426                                                 dependencies[dependencies.length] = registry[dependencies[0]].dependencies[n];
427                                         }
428                                 }
429                         }
430                         // Add ready and error callbacks if they were given
431                         if ( arguments.length > 1 ) {
432                                 jobs[jobs.length] = {
433                                         'dependencies': filter( ['undefined', 'registered', 'loading', 'loaded'], dependencies ),
434                                         'ready': ready,
435                                         'error': error
436                                 };
437                         }
438                         // Queue up any dependencies that are undefined or registered
439                         dependencies = filter( ['undefined', 'registered'], dependencies );
440                         for ( var n = 0; n < dependencies.length; n++ ) {
441                                 if ( queue.indexOf( dependencies[n] ) === -1 ) {
442                                         queue[queue.length] = dependencies[n];
443                                 }
444                         }
445                         // Work the queue
446                         that.work();
447                 }
448                 
449                 function sortQuery(o) {
450                         var sorted = {}, key, a = [];
451                         for ( key in o ) {
452                                 if ( o.hasOwnProperty( key ) ) {
453                                         a.push( key );
454                                 }
455                         }
456                         a.sort();
457                         for ( key = 0; key < a.length; key++ ) {
458                                 sorted[a[key]] = o[a[key]];
459                         }
460                         return sorted;
461                 }
462                 
463                 /* Public Methods */
464                 
465                 /**
466                  * Requests dependencies from server, loading and executing when things when ready.
467                  */
468                 this.work = function() {
469                         // Appends a list of modules to the batch
470                         for ( var q = 0; q < queue.length; q++ ) {
471                                 // Only request modules which are undefined or registered
472                                 if ( !( queue[q] in registry ) || registry[queue[q]].state == 'registered' ) {
473                                         // Prevent duplicate entries
474                                         if ( batch.indexOf( queue[q] ) === -1 ) {
475                                                 batch[batch.length] = queue[q];
476                                                 // Mark registered modules as loading
477                                                 if ( queue[q] in registry ) {
478                                                         registry[queue[q]].state = 'loading';
479                                                 }
480                                         }
481                                 }
482                         }
483                         // Clean up the queue
484                         queue = [];
485                         // After document ready, handle the batch
486                         if ( !suspended && batch.length ) {
487                                 // Always order modules alphabetically to help reduce cache misses for otherwise identical content
488                                 batch.sort();
489                                 // Build a list of request parameters
490                                 var base = {
491                                         'skin': mediaWiki.config.get( 'skin' ),
492                                         'lang': mediaWiki.config.get( 'wgUserLanguage' ),
493                                         'debug': mediaWiki.config.get( 'debug' )
494                                 };
495                                 // Extend request parameters with a list of modules in the batch
496                                 var requests = [];
497                                 if ( base.debug == '1' ) {
498                                         for ( var b = 0; b < batch.length; b++ ) {
499                                                 requests[requests.length] = $.extend(
500                                                         { 'modules': batch[b], 'version': registry[batch[b]].version }, base
501                                                 );
502                                         }
503                                 } else {
504                                         // Calculate the highest timestamp
505                                         var version = 0;
506                                         for ( var b = 0; b < batch.length; b++ ) {
507                                                 if ( registry[batch[b]].version > version ) {
508                                                         version = registry[batch[b]].version;
509                                                 }
510                                         }
511                                         requests[requests.length] = $.extend(
512                                                 { 'modules': batch.join( '|' ), 'version': formatVersionNumber( version ) }, base
513                                         );
514                                 }
515                                 // Clear the batch - this MUST happen before we append the script element to the body or it's
516                                 // possible that the script will be locally cached, instantly load, and work the batch again,
517                                 // all before we've cleared it causing each request to include modules which are already loaded
518                                 batch = [];
519                                 // Asynchronously append a script tag to the end of the body
520                                 function request() {
521                                         var html = '';
522                                         for ( var r = 0; r < requests.length; r++ ) {
523                                                 requests[r] = sortQuery( requests[r] );
524                                                 // Build out the HTML
525                                                 var src = mediaWiki.config.get( 'wgLoadScript' ) + '?' + $.param( requests[r] );
526                                                 html += '<script type="text/javascript" src="' + src + '"></script>';
527                                         }
528                                         return html;
529                                 }
530                                 // Load asynchronously after doumument ready
531                                 if ( ready ) {
532                                         setTimeout(  function() { $( 'body' ).append( request() ); }, 0 )
533                                 } else {
534                                         document.write( request() );
535                                 }
536                         }
537                 };
538                 
539                 /**
540                  * Registers a module, letting the system know about it and it's dependencies. loader.js files contain calls
541                  * to this function.
542                  */
543                 this.register = function( module, version, dependencies, status ) {
544                         // Allow multiple registration
545                         if ( typeof module === 'object' ) {
546                                 for ( var m = 0; m < module.length; m++ ) {
547                                         if ( typeof module[m] === 'string' ) {
548                                                 that.register( module[m] );
549                                         } else if ( typeof module[m] === 'object' ) {
550                                                 that.register.apply( that, module[m] );
551                                         }
552                                 }
553                                 return;
554                         }
555                         // Validate input
556                         if ( typeof module !== 'string' ) {
557                                 throw new Error( 'module must be a string, not a ' + typeof module );
558                         }
559                         if ( typeof registry[module] !== 'undefined' && typeof status === 'undefined' ) {
560                                 throw new Error( 'module already implemeneted: ' + module );
561                         }
562                         // List the module as registered
563                         registry[module] = {
564                                 'state': typeof status === 'string' ? status : 'registered',
565                                 'dependencies': [],
566                                 'version': typeof version !== 'undefined' ? parseInt( version ) : 0
567                         };
568                         if ( typeof dependencies === 'string' ) {
569                                 // Allow dependencies to be given as a single module name
570                                 registry[module].dependencies = [dependencies];
571                         } else if ( typeof dependencies === 'object' || typeof dependencies === 'function' ) {
572                                 // Allow dependencies to be given as an array of module names or a function which returns an array
573                                 registry[module].dependencies = dependencies;
574                         }
575                 };
576                 
577                 /**
578                  * Implements a module, giving the system a course of action to take upon loading. Results of a request for
579                  * one or more modules contain calls to this function.
580                  */
581                 this.implement = function( module, script, style, localization ) {
582                         // Automaically register module
583                         if ( typeof registry[module] === 'undefined' ) {
584                                 that.register( module );
585                         }
586                         // Validate input
587                         if ( typeof script !== 'function' ) {
588                                 throw new Error( 'script must be a function, not a ' + typeof script );
589                         }
590                         if ( typeof style !== 'undefined' && typeof style !== 'string' && typeof style !== 'object' ) {
591                                 throw new Error( 'style must be a string or object, not a ' + typeof style );
592                         }
593                         if ( typeof localization !== 'undefined' && typeof localization !== 'object' ) {
594                                 throw new Error( 'localization must be an object, not a ' + typeof localization );
595                         }
596                         if ( typeof registry[module] !== 'undefined' && typeof registry[module].script !== 'undefined' ) {
597                                 throw new Error( 'module already implemeneted: ' + module );
598                         }
599                         // Mark module as loaded
600                         registry[module].state = 'loaded';
601                         // Attach components
602                         registry[module].script = script;
603                         if ( typeof style === 'string' || typeof style === 'object' && !( style instanceof Array ) ) {
604                                 registry[module].style = style;
605                         }
606                         if ( typeof localization === 'object' ) {
607                                 registry[module].messages = localization;
608                         }
609                         // Execute or queue callback
610                         if ( filter( ['ready'], registry[module].dependencies ).compare( registry[module].dependencies ) ) {
611                                 execute( module );
612                         } else {
613                                 request( module );
614                         }
615                 };
616                 
617                 /**
618                  * Executes a function as soon as one or more required modules are ready
619                  * 
620                  * @param mixed string or array of strings of modules names the callback dependencies to be ready before
621                  * executing
622                  * @param function callback to execute when all dependencies are ready (optional)
623                  * @param function callback to execute when if dependencies have a errors (optional)
624                  */
625                 this.using = function( dependencies, ready, error ) {
626                         // Validate input
627                         if ( typeof dependencies !== 'object' && typeof dependencies !== 'string' ) {
628                                 throw new Error( 'dependencies must be a string or an array, not a ' + typeof dependencies )
629                         }
630                         // Allow calling with a single dependency as a string
631                         if ( typeof dependencies === 'string' ) {
632                                 dependencies = [dependencies];
633                         }
634                         // Resolve entire dependency map
635                         dependencies = resolve( dependencies );
636                         // If all dependencies are met, execute ready immediately
637                         if ( filter( ['ready'], dependencies ).compare( dependencies ) ) {
638                                 if ( typeof ready !== 'function' ) {
639                                         ready();
640                                 }
641                         }
642                         // If any dependencies have errors execute error immediately
643                         else if ( filter( ['error'], dependencies ).length ) {
644                                 if ( typeof error === 'function' ) {
645                                         error();
646                                 }
647                         }
648                         // Since some dependencies are not yet ready, queue up a request
649                         else {
650                                 request( dependencies, ready, error );
651                         }
652                 };
653                 
654                 /**
655                  * Loads an external script or one or more modules for future use
656                  * 
657                  * @param {mixed} modules either the name of a module, array of modules, or a URL of an external script or style
658                  * @param {string} type mime-type to use if calling with a URL of an external script or style; acceptable values
659                  * are "text/css" and "text/javascript"; if no type is provided, text/javascript is assumed
660                  */
661                 this.load = function( modules, type ) {
662                         // Validate input
663                         if ( typeof modules !== 'object' && typeof modules !== 'string' ) {
664                                 throw new Error( 'dependencies must be a string or an array, not a ' + typeof dependencies )
665                         }
666                         // Allow calling with an external script or single dependency as a string
667                         if ( typeof modules === 'string' ) {
668                                 // Support adding arbitrary external scripts
669                                 if ( modules.substr( 0, 7 ) == 'http://' || modules.substr( 0, 8 ) == 'https://' ) {
670                                         if ( type === 'text/css' ) {
671                                                 setTimeout(  function() {
672                                                         $( 'head' ).append( '<link rel="stylesheet" type="text/css" href="' + modules + '" />' );
673                                                 }, 0 );
674                                                 return true;
675                                         } else if ( type === 'text/javascript' || typeof type === 'undefined' ) {
676                                                 setTimeout(  function() {
677                                                         $( 'body' ).append( '<script type="text/javascript" src="' + modules + '"></script>' );
678                                                 }, 0 );
679                                                 return true;
680                                         }
681                                         // Unknown type
682                                         return false;
683                                 }
684                                 // Called with single module
685                                 modules = [modules];
686                         }
687                         // Resolve entire dependency map
688                         modules = resolve( modules );
689                         // If all modules are ready, nothing dependency be done
690                         if ( filter( ['ready'], modules ).compare( modules ) ) {
691                                 return true;
692                         }
693                         // If any modules have errors return false
694                         else if ( filter( ['error'], modules ).length ) {
695                                 return false;
696                         }
697                         // Since some modules are not yet ready, queue up a request
698                         else {
699                                 request( modules );
700                                 return true;
701                         }
702                 };
703                 
704                 /**
705                  * Flushes the request queue and begin executing load requests on demand
706                  */
707                 this.go = function() {
708                         suspended = false;
709                         that.work();
710                 };
711                 
712                 /**
713                  * Changes the state of a module
714                  * 
715                  * @param mixed module string module name or object of module name/state pairs
716                  * @param string state string state name
717                  */
718                 this.state = function( module, state ) {
719                         if ( typeof module === 'object' ) {
720                                 for ( var m in module ) {
721                                         that.state( m, module[m] );
722                                 }
723                                 return;
724                         }
725                         if ( module in registry ) {
726                                 registry[module].state = state;
727                         }
728                 };
729                 
730                 /* Cache document ready status */
731                 
732                 $(document).ready( function() { ready = true; } );
733         } )();
734         
735         /* Extension points */
736         
737         this.util = {};
738         this.legacy = {};
739         
740 } )( jQuery );
743 /* Auto-register from pre-loaded startup scripts */
745 if ( typeof window['startUp'] === 'function' ) {
746         window['startUp']();
747         delete window['startUp'];