Fix for r74134: copy-paste error
[mediawiki.git] / resources / mediawiki / mediawiki.js
blob0c4422accbf79fb7cfc4a3f2a59e64604baeed97
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; in the form of function( value, options )
59                  * where value is the data to be parsed and options is additional data passed through to the parser
60                  */
61                 'configuration': function( global, parser ) {
62                         
63                         /* Private Members */
64                         
65                         var that = this;
66                         var values = global === true ? window : {};
67                         
68                         /* Public Methods */
69                         
70                         /**
71                          * Gets one or more values
72                          * 
73                          * If called with no arguments, all values will be returned. If a parser is in use, no parsing will take
74                          * place when calling with no arguments or calling with an array of names.
75                          * 
76                          * @param {mixed} selection string name of value to get, array of string names of values to get, or object
77                          * of name/option pairs
78                          * @param {object} options optional set of options which are also passed to a parser if in use; only used
79                          * when selection is a string
80                          * @format options
81                          *      {
82                          *              // Value to use if key does not exist
83                          *              'fallback': ''
84                          *      }
85                          */
86                         this.get = function( selection, options ) {
87                                 if ( typeof selection === 'object' ) {
88                                         var results = {};
89                                         for ( var s in selection ) {
90                                                 if ( selection.hasOwnProperty( s ) ) {
91                                                         if ( typeof s === 'string' ) {
92                                                                 return that.get( values[s], selection[s] );
93                                                         } else {
94                                                                 return that.get( selection[s] );
95                                                         }
96                                                 }
97                                         }
98                                         return results;
99                                 } else if ( typeof selection === 'string' ) {
100                                         if ( typeof values[selection] === 'undefined' ) {
101                                                 return typeof options === 'object' && 'fallback' in options ?
102                                                         options.fallback : '<' + selection + '>';
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 "basic" string from a UNIX timestamp
233                  */
234                 function formatVersionNumber( timestamp ) {
235                         function pad( a, b, c ) {
236                                 return [a < 10 ? '0' + a : a, b < 10 ? '0' + b : b, c < 10 ? '0' + c : c].join( '' );
237                         }
238                         var d = new Date()
239                         d.setTime( timestamp * 1000 );
240                         return [
241                                 pad( d.getUTCFullYear(), d.getUTCMonth() + 1, d.getUTCDate() ), 'T',
242                                 pad( d.getUTCHours(), d.getUTCMinutes(), d.getUTCSeconds() ), 'Z'
243                         ].join( '' );
244                 }
245                 
246                 /**
247                  * Recursively resolves dependencies and detects circular references
248                  */
249                 function recurse( module, resolved, unresolved ) {
250                         unresolved[unresolved.length] = module;
251                         // Resolves dynamic loader function and replaces it with it's own results
252                         if ( typeof registry[module].dependencies === 'function' ) {
253                                 registry[module].dependencies = registry[module].dependencies();
254                                 // Gaurantees the module's dependencies are always in an array 
255                                 if ( typeof registry[module].dependencies !== 'object' ) {
256                                         registry[module].dependencies = [registry[module].dependencies];
257                                 }
258                         }
259                         // Tracks down dependencies
260                         for ( var n = 0; n < registry[module].dependencies.length; n++ ) {
261                                 if ( resolved.indexOf( registry[module].dependencies[n] ) === -1 ) {
262                                         if ( unresolved.indexOf( registry[module].dependencies[n] ) !== -1 ) {
263                                                 throw new Error(
264                                                         'Circular reference detected: ' + module + ' -> ' + registry[module].dependencies[n]
265                                                 );
266                                         }
267                                         recurse( registry[module].dependencies[n], resolved, unresolved );
268                                 }
269                         }
270                         resolved[resolved.length] = module;
271                         unresolved.splice( unresolved.indexOf( module ), 1 );
272                 }
273                 
274                 /**
275                  * Gets a list of modules names that a module dependencies in their proper dependency order
276                  * 
277                  * @param mixed string module name or array of string module names
278                  * @return list of dependencies
279                  * @throws Error if circular reference is detected
280                  */
281                 function resolve( module, resolved, unresolved ) {
282                         // Allow calling with an array of module names
283                         if ( typeof module === 'object' ) {
284                                 var modules = [];
285                                 for ( var m = 0; m < module.length; m++ ) {
286                                         var dependencies = resolve( module[m] );
287                                         for ( var n = 0; n < dependencies.length; n++ ) {
288                                                 modules[modules.length] = dependencies[n];
289                                         }
290                                 }
291                                 return modules;
292                         } else if ( typeof module === 'string' ) {
293                                 // Undefined modules have no dependencies
294                                 if ( !( module in registry ) ) {
295                                         return [];
296                                 }
297                                 var resolved = [];
298                                 recurse( module, resolved, [] );
299                                 return resolved;
300                         }
301                         throw new Error( 'Invalid module argument: ' + module );
302                 };
303                 
304                 /**
305                  * Narrows a list of module names down to those matching a specific state. Possible states are 'undefined',
306                  * 'registered', 'loading', 'loaded', or 'ready'
307                  * 
308                  * @param mixed string or array of strings of module states to filter by
309                  * @param array list of module names to filter (optional, all modules will be used by default)
310                  * @return array list of filtered module names
311                  */
312                 function filter( states, modules ) {
313                         // Allow states to be given as a string
314                         if ( typeof states === 'string' ) {
315                                 states = [states];
316                         }
317                         // If called without a list of modules, build and use a list of all modules
318                         var list = [];
319                         if ( typeof modules === 'undefined' ) {
320                                 modules = [];
321                                 for ( module in registry ) {
322                                         modules[modules.length] = module;
323                                 }
324                         }
325                         // Build a list of modules which are in one of the specified states
326                         for ( var s = 0; s < states.length; s++ ) {
327                                 for ( var m = 0; m < modules.length; m++ ) {
328                                         if (
329                                                 ( states[s] == 'undefined' && typeof registry[modules[m]] === 'undefined' ) ||
330                                                 ( typeof registry[modules[m]] === 'object' && registry[modules[m]].state === states[s] )
331                                         ) {
332                                                 list[list.length] = modules[m];
333                                         }
334                                 }
335                         }
336                         return list;
337                 }
338                 
339                 /**
340                  * Executes a loaded module, making it ready to use
341                  * 
342                  * @param string module name to execute
343                  */
344                 function execute( module ) {
345                         if ( typeof registry[module] === 'undefined' ) {
346                                 throw new Error( 'Module has not been registered yet: ' + module );
347                         } else if ( registry[module].state === 'registered' ) {
348                                 throw new Error( 'Module has not been requested from the server yet: ' + module );
349                         } else if ( registry[module].state === 'loading' ) {
350                                 throw new Error( 'Module has not completed loading yet: ' + module );
351                         } else if ( registry[module].state === 'ready' ) {
352                                 throw new Error( 'Module has already been loaded: ' + module );
353                         }
354                         // Add style sheet to document
355                         if ( typeof registry[module].style === 'string' && registry[module].style.length ) {
356                                 $( 'head' ).append( '<style type="text/css">' + registry[module].style + '</style>' );
357                         } else if ( typeof registry[module].style === 'object' && !( registry[module].style instanceof Array ) ) {
358                                 for ( var media in registry[module].style ) {
359                                         $( 'head' ).append(
360                                                 '<style type="text/css" media="' + media + '">' + registry[module].style[media] + '</style>'
361                                         );
362                                 }
363                         }
364                         // Add localizations to message system
365                         if ( typeof registry[module].messages === 'object' ) {
366                                 mediaWiki.msg.set( registry[module].messages );
367                         }
368                         // Execute script
369                         try {
370                                 registry[module].script();
371                                 registry[module].state = 'ready';
372                                 // Run jobs who's dependencies have just been met
373                                 for ( var j = 0; j < jobs.length; j++ ) {
374                                         if ( filter( 'ready', jobs[j].dependencies ).compare( jobs[j].dependencies ) ) {
375                                                 if ( typeof jobs[j].ready === 'function' ) {
376                                                         jobs[j].ready();
377                                                 }
378                                                 jobs.splice( j, 1 );
379                                                 j--;
380                                         }
381                                 }
382                                 // Execute modules who's dependencies have just been met
383                                 for ( r in registry ) {
384                                         if ( registry[r].state == 'loaded' ) {
385                                                 if ( filter( ['ready'], registry[r].dependencies ).compare( registry[r].dependencies ) ) {
386                                                         execute( r );
387                                                 }
388                                         }
389                                 }
390                         } catch ( e ) {
391                                 mediaWiki.log( 'Exception thrown by ' + module + ': ' + e.message );
392                                 mediaWiki.log( e );
393                                 registry[module].state = 'error';                               
394                                 // Run error callbacks of jobs affected by this condition
395                                 for ( var j = 0; j < jobs.length; j++ ) {
396                                         if ( jobs[j].dependencies.indexOf( module ) !== -1 ) {
397                                                 if ( typeof jobs[j].error === 'function' ) {
398                                                         jobs[j].error();
399                                                 }
400                                                 jobs.splice( j, 1 );
401                                                 j--;
402                                         }
403                                 }
404                         }
405                 }
406                 
407                 /**
408                  * Adds a dependencies to the queue with optional callbacks to be run when the dependencies are ready or fail
409                  * 
410                  * @param mixed string moulde name or array of string module names
411                  * @param function ready callback to execute when all dependencies are ready
412                  * @param function error callback to execute when any dependency fails
413                  */
414                 function request( dependencies, ready, error ) {
415                         // Allow calling by single module name
416                         if ( typeof dependencies === 'string' ) {
417                                 dependencies = [dependencies];
418                                 if ( dependencies[0] in registry ) {
419                                         for ( var n = 0; n < registry[dependencies[0]].dependencies.length; n++ ) {
420                                                 dependencies[dependencies.length] = registry[dependencies[0]].dependencies[n];
421                                         }
422                                 }
423                         }
424                         // Add ready and error callbacks if they were given
425                         if ( arguments.length > 1 ) {
426                                 jobs[jobs.length] = {
427                                         'dependencies': filter( ['undefined', 'registered', 'loading', 'loaded'], dependencies ),
428                                         'ready': ready,
429                                         'error': error
430                                 };
431                         }
432                         // Queue up any dependencies that are undefined or registered
433                         dependencies = filter( ['undefined', 'registered'], dependencies );
434                         for ( var n = 0; n < dependencies.length; n++ ) {
435                                 if ( queue.indexOf( dependencies[n] ) === -1 ) {
436                                         queue[queue.length] = dependencies[n];
437                                 }
438                         }
439                         // Work the queue
440                         that.work();
441                 }
442                 
443                 function sortQuery(o) {
444                         var sorted = {}, key, a = [];
445                         for ( key in o ) {
446                                 if ( o.hasOwnProperty( key ) ) {
447                                         a.push( key );
448                                 }
449                         }
450                         a.sort();
451                         for ( key = 0; key < a.length; key++ ) {
452                                 sorted[a[key]] = o[a[key]];
453                         }
454                         return sorted;
455                 }
456                 
457                 /* Public Methods */
458                 
459                 /**
460                  * Requests dependencies from server, loading and executing when things when ready.
461                  */
462                 this.work = function() {
463                         // Appends a list of modules to the batch
464                         for ( var q = 0; q < queue.length; q++ ) {
465                                 // Only request modules which are undefined or registered
466                                 if ( !( queue[q] in registry ) || registry[queue[q]].state == 'registered' ) {
467                                         // Prevent duplicate entries
468                                         if ( batch.indexOf( queue[q] ) === -1 ) {
469                                                 batch[batch.length] = queue[q];
470                                                 // Mark registered modules as loading
471                                                 if ( queue[q] in registry ) {
472                                                         registry[queue[q]].state = 'loading';
473                                                 }
474                                         }
475                                 }
476                         }
477                         // Clean up the queue
478                         queue = [];
479                         // After document ready, handle the batch
480                         if ( !suspended && batch.length ) {
481                                 // Always order modules alphabetically to help reduce cache misses for otherwise identical content
482                                 batch.sort();
483                                 // Build a list of request parameters
484                                 var base = {
485                                         'skin': mediaWiki.config.get( 'skin' ),
486                                         'lang': mediaWiki.config.get( 'wgUserLanguage' ),
487                                         'debug': mediaWiki.config.get( 'debug' )
488                                 };
489                                 // Extend request parameters with a list of modules in the batch
490                                 var requests = [];
491                                 if ( base.debug == '1' ) {
492                                         for ( var b = 0; b < batch.length; b++ ) {
493                                                 requests[requests.length] = $.extend(
494                                                         { 'modules': batch[b], 'version': registry[batch[b]].version }, base
495                                                 );
496                                         }
497                                 } else {
498                                         // Split into groups
499                                         var groups = {};
500                                         for ( var b = 0; b < batch.length; b++ ) {
501                                                 var group = registry[batch[b]].group;
502                                                 if ( !( group in groups ) ) {
503                                                         groups[group] = [];
504                                                 }
505                                                 groups[group][groups[group].length] = batch[b];
506                                         }
507                                         for ( var group in groups ) {
508                                                 // Calculate the highest timestamp
509                                                 var version = 0;
510                                                 for ( var g = 0; g < groups[group].length; g++ ) {
511                                                         if ( registry[groups[group][g]].version > version ) {
512                                                                 version = registry[groups[group][g]].version;
513                                                         }
514                                                 }
515                                                 requests[requests.length] = $.extend(
516                                                         { 'modules': groups[group].join( '|' ), 'version': formatVersionNumber( version ) }, base
517                                                 );
518                                         }
519                                 }
520                                 // Clear the batch - this MUST happen before we append the script element to the body or it's
521                                 // possible that the script will be locally cached, instantly load, and work the batch again,
522                                 // all before we've cleared it causing each request to include modules which are already loaded
523                                 batch = [];
524                                 // Asynchronously append a script tag to the end of the body
525                                 function request() {
526                                         var html = '';
527                                         for ( var r = 0; r < requests.length; r++ ) {
528                                                 requests[r] = sortQuery( requests[r] );
529                                                 // Build out the HTML
530                                                 var src = mediaWiki.config.get( 'wgLoadScript' ) + '?' + $.param( requests[r] );
531                                                 html += '<script type="text/javascript" src="' + src + '"></script>';
532                                         }
533                                         return html;
534                                 }
535                                 // Load asynchronously after doumument ready
536                                 if ( ready ) {
537                                         setTimeout(  function() { $( 'body' ).append( request() ); }, 0 )
538                                 } else {
539                                         document.write( request() );
540                                 }
541                         }
542                 };
543                 
544                 /**
545                  * Registers a module, letting the system know about it and it's dependencies. loader.js files contain calls
546                  * to this function.
547                  */
548                 this.register = function( module, version, dependencies, group ) {
549                         // Allow multiple registration
550                         if ( typeof module === 'object' ) {
551                                 for ( var m = 0; m < module.length; m++ ) {
552                                         if ( typeof module[m] === 'string' ) {
553                                                 that.register( module[m] );
554                                         } else if ( typeof module[m] === 'object' ) {
555                                                 that.register.apply( that, module[m] );
556                                         }
557                                 }
558                                 return;
559                         }
560                         // Validate input
561                         if ( typeof module !== 'string' ) {
562                                 throw new Error( 'module must be a string, not a ' + typeof module );
563                         }
564                         if ( typeof registry[module] !== 'undefined' ) {
565                                 throw new Error( 'module already implemeneted: ' + module );
566                         }
567                         // List the module as registered
568                         registry[module] = {
569                                 'state': 'registered',
570                                 'group': typeof group === 'string' ? group : null,
571                                 'dependencies': [],
572                                 'version': typeof version !== 'undefined' ? parseInt( version ) : 0
573                         };
574                         if ( typeof dependencies === 'string' ) {
575                                 // Allow dependencies to be given as a single module name
576                                 registry[module].dependencies = [dependencies];
577                         } else if ( typeof dependencies === 'object' || typeof dependencies === 'function' ) {
578                                 // Allow dependencies to be given as an array of module names or a function which returns an array
579                                 registry[module].dependencies = dependencies;
580                         }
581                 };
582                 
583                 /**
584                  * Implements a module, giving the system a course of action to take upon loading. Results of a request for
585                  * one or more modules contain calls to this function.
586                  */
587                 this.implement = function( module, script, style, localization ) {
588                         // Automaically register module
589                         if ( typeof registry[module] === 'undefined' ) {
590                                 that.register( module );
591                         }
592                         // Validate input
593                         if ( typeof script !== 'function' ) {
594                                 throw new Error( 'script must be a function, not a ' + typeof script );
595                         }
596                         if ( typeof style !== 'undefined' && typeof style !== 'string' && typeof style !== 'object' ) {
597                                 throw new Error( 'style must be a string or object, not a ' + typeof style );
598                         }
599                         if ( typeof localization !== 'undefined' && typeof localization !== 'object' ) {
600                                 throw new Error( 'localization must be an object, not a ' + typeof localization );
601                         }
602                         if ( typeof registry[module] !== 'undefined' && typeof registry[module].script !== 'undefined' ) {
603                                 throw new Error( 'module already implemeneted: ' + module );
604                         }
605                         // Mark module as loaded
606                         registry[module].state = 'loaded';
607                         // Attach components
608                         registry[module].script = script;
609                         if ( typeof style === 'string' || typeof style === 'object' && !( style instanceof Array ) ) {
610                                 registry[module].style = style;
611                         }
612                         if ( typeof localization === 'object' ) {
613                                 registry[module].messages = localization;
614                         }
615                         // Execute or queue callback
616                         if ( filter( ['ready'], registry[module].dependencies ).compare( registry[module].dependencies ) ) {
617                                 execute( module );
618                         } else {
619                                 request( module );
620                         }
621                 };
622                 
623                 /**
624                  * Executes a function as soon as one or more required modules are ready
625                  * 
626                  * @param mixed string or array of strings of modules names the callback dependencies to be ready before
627                  * executing
628                  * @param function callback to execute when all dependencies are ready (optional)
629                  * @param function callback to execute when if dependencies have a errors (optional)
630                  */
631                 this.using = function( dependencies, ready, error ) {
632                         // Validate input
633                         if ( typeof dependencies !== 'object' && typeof dependencies !== 'string' ) {
634                                 throw new Error( 'dependencies must be a string or an array, not a ' + typeof dependencies )
635                         }
636                         // Allow calling with a single dependency as a string
637                         if ( typeof dependencies === 'string' ) {
638                                 dependencies = [dependencies];
639                         }
640                         // Resolve entire dependency map
641                         dependencies = resolve( dependencies );
642                         // If all dependencies are met, execute ready immediately
643                         if ( filter( ['ready'], dependencies ).compare( dependencies ) ) {
644                                 if ( typeof ready === 'function' ) {
645                                         ready();
646                                 }
647                         }
648                         // If any dependencies have errors execute error immediately
649                         else if ( filter( ['error'], dependencies ).length ) {
650                                 if ( typeof error === 'function' ) {
651                                         error();
652                                 }
653                         }
654                         // Since some dependencies are not yet ready, queue up a request
655                         else {
656                                 request( dependencies, ready, error );
657                         }
658                 };
659                 
660                 /**
661                  * Loads an external script or one or more modules for future use
662                  * 
663                  * @param {mixed} modules either the name of a module, array of modules, or a URL of an external script or style
664                  * @param {string} type mime-type to use if calling with a URL of an external script or style; acceptable values
665                  * are "text/css" and "text/javascript"; if no type is provided, text/javascript is assumed
666                  */
667                 this.load = function( modules, type ) {
668                         // Validate input
669                         if ( typeof modules !== 'object' && typeof modules !== 'string' ) {
670                                 throw new Error( 'dependencies must be a string or an array, not a ' + typeof dependencies )
671                         }
672                         // Allow calling with an external script or single dependency as a string
673                         if ( typeof modules === 'string' ) {
674                                 // Support adding arbitrary external scripts
675                                 if ( modules.substr( 0, 7 ) == 'http://' || modules.substr( 0, 8 ) == 'https://' ) {
676                                         if ( type === 'text/css' ) {
677                                                 setTimeout(  function() {
678                                                         $( 'head' ).append( '<link rel="stylesheet" type="text/css" />' ).attr( 'href', modules );
679                                                 }, 0 );
680                                                 return true;
681                                         } else if ( type === 'text/javascript' || typeof type === 'undefined' ) {
682                                                 setTimeout(  function() {
683                                                         $( 'body' ).append( '<script type="text/javascript"></script>'  ).attr( 'src', modules )
684                                                 }, 0 );
685                                                 return true;
686                                         }
687                                         // Unknown type
688                                         return false;
689                                 }
690                                 // Called with single module
691                                 modules = [modules];
692                         }
693                         // Resolve entire dependency map
694                         modules = resolve( modules );
695                         // If all modules are ready, nothing dependency be done
696                         if ( filter( ['ready'], modules ).compare( modules ) ) {
697                                 return true;
698                         }
699                         // If any modules have errors return false
700                         else if ( filter( ['error'], modules ).length ) {
701                                 return false;
702                         }
703                         // Since some modules are not yet ready, queue up a request
704                         else {
705                                 request( modules );
706                                 return true;
707                         }
708                 };
709                 
710                 /**
711                  * Flushes the request queue and begin executing load requests on demand
712                  */
713                 this.go = function() {
714                         suspended = false;
715                         that.work();
716                 };
717                 
718                 /**
719                  * Changes the state of a module
720                  * 
721                  * @param mixed module string module name or object of module name/state pairs
722                  * @param string state string state name
723                  */
724                 this.state = function( module, state ) {
725                         if ( typeof module === 'object' ) {
726                                 for ( var m in module ) {
727                                         that.state( m, module[m] );
728                                 }
729                                 return;
730                         }
731                         if ( !( module in registry ) ) {
732                                 that.register( module );
733                         }
734                         registry[module].state = state;
735                 };
736                 
737                 /**
738                  * Gets the version of a module
739                  * 
740                  * @param string module name of module to get version for
741                  */
742                 this.version = function( module ) {
743                         if ( module in registry && 'version' in registry[module] ) {
744                                 return formatVersionNumber( registry[module].version );
745                         }
746                         return null;
747                 }
748                 
749                 /* Cache document ready status */
750                 
751                 $(document).ready( function() { ready = true; } );
752         } )();
753         
754         /* Extension points */
755         
756         this.util = {};
757         this.legacy = {};
758         
759 } )( jQuery );
762 /* Auto-register from pre-loaded startup scripts */
764 if ( typeof window['startUp'] === 'function' ) {
765         window['startUp']();
766         delete window['startUp'];