r97387 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r97386‎ | r97387 | r97388 >
Date:20:21, 17 September 2011
Author:krinkle
Status:ok
Tags:
Comment:
Revert r97386, way to soon. loader constructor has dependencies and assumptions about mw.log, mw.html and mw.messages
Modified paths:
  • /trunk/phase3/resources/ResourceLoader.js (deleted) (history)

Diff [purge]

Index: trunk/phase3/resources/ResourceLoader.js
@@ -1,1304 +0,0 @@
2 -/*
3 - * Core MediaWiki JavaScript Library
4 - */
5 -
6 -// Attach to window and globally alias
7 -window.mw = window.mediaWiki = new ( function( $ ) {
8 -
9 - /* Private Members */
10 -
11 - /**
12 - * @var object List of messages that have been requested to be loaded.
13 - */
14 - var messageQueue = {};
15 -
16 - /* Object constructors */
17 -
18 - /**
19 - * Map
20 - *
21 - * Creates an object that can be read from or written to from prototype functions
22 - * that allow both single and multiple variables at once.
23 - *
24 - * @param global boolean Whether to store the values in the global window
25 - * object or a exclusively in the object property 'values'.
26 - * @return Map
27 - */
28 - function Map( global ) {
29 - this.values = ( global === true ) ? window : {};
30 - return this;
31 - }
32 -
33 - Map.prototype = {
34 - /**
35 - * Get the value of one or multiple a keys.
36 - *
37 - * If called with no arguments, all values will be returned.
38 - *
39 - * @param selection mixed String key or array of keys to get values for.
40 - * @param fallback mixed Value to use in case key(s) do not exist (optional).
41 - * @return mixed If selection was a string returns the value or null,
42 - * If selection was an array, returns an object of key/values (value is null if not found),
43 - * If selection was not passed or invalid, will return the 'values' object member (be careful as
44 - * objects are always passed by reference in JavaScript!).
45 - * @return Values as a string or object, null if invalid/inexistant.
46 - */
47 - get: function( selection, fallback ) {
48 - if ( $.isArray( selection ) ) {
49 - selection = $.makeArray( selection );
50 - var results = {};
51 - for ( var i = 0; i < selection.length; i++ ) {
52 - results[selection[i]] = this.get( selection[i], fallback );
53 - }
54 - return results;
55 - } else if ( typeof selection === 'string' ) {
56 - if ( this.values[selection] === undefined ) {
57 - if ( fallback !== undefined ) {
58 - return fallback;
59 - }
60 - return null;
61 - }
62 - return this.values[selection];
63 - }
64 - if ( selection === undefined ) {
65 - return this.values;
66 - } else {
67 - return null; // invalid selection key
68 - }
69 - },
70 -
71 - /**
72 - * Sets one or multiple key/value pairs.
73 - *
74 - * @param selection mixed String key or array of keys to set values for.
75 - * @param value mixed Value to set (optional, only in use when key is a string)
76 - * @return bool This returns true on success, false on failure.
77 - */
78 - set: function( selection, value ) {
79 - if ( $.isPlainObject( selection ) ) {
80 - for ( var s in selection ) {
81 - this.values[s] = selection[s];
82 - }
83 - return true;
84 - } else if ( typeof selection === 'string' && value !== undefined ) {
85 - this.values[selection] = value;
86 - return true;
87 - }
88 - return false;
89 - },
90 -
91 - /**
92 - * Checks if one or multiple keys exist.
93 - *
94 - * @param selection mixed String key or array of keys to check
95 - * @return boolean Existence of key(s)
96 - */
97 - exists: function( selection ) {
98 - if ( typeof selection === 'object' ) {
99 - for ( var s = 0; s < selection.length; s++ ) {
100 - if ( !( selection[s] in this.values ) ) {
101 - return false;
102 - }
103 - }
104 - return true;
105 - } else {
106 - return selection in this.values;
107 - }
108 - }
109 - };
110 -
111 - /**
112 - * Message
113 - *
114 - * Object constructor for messages,
115 - * similar to the Message class in MediaWiki PHP.
116 - *
117 - * @param map Map Instance of mw.Map
118 - * @param key String
119 - * @param parameters Array
120 - * @return Message
121 - */
122 - function Message( map, key, parameters ) {
123 - this.format = 'plain';
124 - this.map = map;
125 - this.key = key;
126 - this.parameters = parameters === undefined ? [] : $.makeArray( parameters );
127 - return this;
128 - }
129 -
130 - Message.prototype = {
131 - /**
132 - * Appends (does not replace) parameters for replacement to the .parameters property.
133 - *
134 - * @param parameters Array
135 - * @return Message
136 - */
137 - params: function( parameters ) {
138 - for ( var i = 0; i < parameters.length; i++ ) {
139 - this.parameters.push( parameters[i] );
140 - }
141 - return this;
142 - },
143 -
144 - /**
145 - * Converts message object to it's string form based on the state of format.
146 - *
147 - * @return string Message as a string in the current form or <key> if key does not exist.
148 - */
149 - toString: function() {
150 -
151 - if ( !this.map.exists( this.key ) ) {
152 - // Use <key> as text if key does not exist
153 - if ( this.format !== 'plain' ) {
154 - // format 'escape' and 'parse' need to have the brackets and key html escaped
155 - return mw.html.escape( '<' + this.key + '>' );
156 - }
157 - return '<' + this.key + '>';
158 - }
159 - var text = this.map.get( this.key ),
160 - parameters = this.parameters;
161 -
162 - text = text.replace( /\$(\d+)/g, function( string, match ) {
163 - var index = parseInt( match, 10 ) - 1;
164 - return index in parameters ? parameters[index] : '$' + match;
165 - } );
166 -
167 - if ( this.format === 'plain' ) {
168 - return text;
169 - }
170 - if ( this.format === 'escaped' ) {
171 - // According to Message.php this needs {{-transformation, which is
172 - // still todo
173 - return mw.html.escape( text );
174 - }
175 -
176 - /* This should be fixed up when we have a parser
177 - if ( this.format === 'parse' && 'language' in mw ) {
178 - text = mw.language.parse( text );
179 - }
180 - */
181 - return text;
182 - },
183 -
184 - /**
185 - * Changes format to parse and converts message to string
186 - *
187 - * @return {string} String form of parsed message
188 - */
189 - parse: function() {
190 - this.format = 'parse';
191 - return this.toString();
192 - },
193 -
194 - /**
195 - * Changes format to plain and converts message to string
196 - *
197 - * @return {string} String form of plain message
198 - */
199 - plain: function() {
200 - this.format = 'plain';
201 - return this.toString();
202 - },
203 -
204 - /**
205 - * Changes the format to html escaped and converts message to string
206 - *
207 - * @return {string} String form of html escaped message
208 - */
209 - escaped: function() {
210 - this.format = 'escaped';
211 - return this.toString();
212 - },
213 -
214 - /**
215 - * Checks if message exists
216 - *
217 - * @return {string} String form of parsed message
218 - */
219 - exists: function() {
220 - return this.map.exists( this.key );
221 - }
222 - };
223 -
224 - /* Public Members */
225 -
226 - /*
227 - * Dummy function which in debug mode can be replaced with a function that
228 - * emulates console.log in console-less environments.
229 - */
230 - this.log = function() { };
231 -
232 - /**
233 - * @var constructor Make the Map constructor publicly available.
234 - */
235 - this.Map = Map;
236 -
237 - /**
238 - * List of configuration values
239 - *
240 - * Dummy placeholder. Initiated in startUp module as a new instance of mw.Map().
241 - * If $wgLegacyJavaScriptGlobals is true, this Map will have its values
242 - * in the global window object.
243 - */
244 - this.config = null;
245 -
246 - /**
247 - * @var object
248 - *
249 - * Empty object that plugins can be installed in.
250 - */
251 - this.libs = {};
252 -
253 - /*
254 - * Localization system
255 - */
256 - this.messages = new this.Map();
257 -
258 - /* Public Methods */
259 -
260 - /**
261 - * Gets a message object, similar to wfMessage()
262 - *
263 - * @param key string Key of message to get
264 - * @param parameter_1 mixed First argument in a list of variadic arguments,
265 - * each a parameter for $N replacement in messages.
266 - * @return Message
267 - */
268 - this.message = function( key, parameter_1 /* [, parameter_2] */ ) {
269 - var parameters;
270 - // Support variadic arguments
271 - if ( parameter_1 !== undefined ) {
272 - parameters = $.makeArray( arguments );
273 - parameters.shift();
274 - } else {
275 - parameters = [];
276 - }
277 - return new Message( mw.messages, key, parameters );
278 - };
279 -
280 - /**
281 - * Gets a message string, similar to wfMsg()
282 - *
283 - * @param key string Key of message to get
284 - * @param parameters mixed First argument in a list of variadic arguments,
285 - * each a parameter for $N replacement in messages.
286 - * @return String.
287 - */
288 - this.msg = function( key, parameters ) {
289 - return mw.message.apply( mw.message, arguments ).toString();
290 - };
291 -
292 - /**
293 - * Client-side module loader which integrates with the MediaWiki ResourceLoader
294 - */
295 - this.loader = new ( function() {
296 -
297 - /* Private Members */
298 -
299 - /**
300 - * Mapping of registered modules
301 - *
302 - * The jquery module is pre-registered, because it must have already
303 - * been provided for this object to have been built, and in debug mode
304 - * jquery would have been provided through a unique loader request,
305 - * making it impossible to hold back registration of jquery until after
306 - * mediawiki.
307 - *
308 - * For exact details on support for script, style and messages, look at
309 - * mw.loader.implement.
310 - *
311 - * Format:
312 - * {
313 - * 'moduleName': {
314 - * 'version': ############## (unix timestamp),
315 - * 'dependencies': ['required.foo', 'bar.also', ...], (or) function() {}
316 - * 'group': 'somegroup', (or) null,
317 - * 'source': 'local', 'someforeignwiki', (or) null
318 - * 'state': 'registered', 'loading', 'loaded', 'ready', or 'error'
319 - * 'script': ...,
320 - * 'style': ...,
321 - * 'messages': { 'key': 'value' },
322 - * }
323 - * }
324 - */
325 - var registry = {},
326 - /**
327 - * Mapping of sources, keyed by source-id, values are objects.
328 - * Format:
329 - * {
330 - * 'sourceId': {
331 - * 'loadScript': 'http://foo.bar/w/load.php'
332 - * }
333 - * }
334 - */
335 - sources = {},
336 - // List of modules which will be loaded as when ready
337 - batch = [],
338 - // List of modules to be loaded
339 - queue = [],
340 - // List of callback functions waiting for modules to be ready to be called
341 - jobs = [],
342 - // Flag inidicating that document ready has occured
343 - ready = false,
344 - // Selector cache for the marker element. Use getMarker() to get/use the marker!
345 - $marker = null;
346 -
347 - /* Private Methods */
348 -
349 - function getMarker(){
350 - // Cached ?
351 - if ( $marker ) {
352 - return $marker;
353 - } else {
354 - $marker = $( 'meta[name="ResourceLoaderDynamicStyles"]' );
355 - if ( $marker.length ) {
356 - return $marker;
357 - }
358 - mw.log( 'getMarker> No <meta name="ResourceLoaderDynamicStyles"> found, inserting dynamically.' );
359 - $marker = $( '<meta>' ).attr( 'name', 'ResourceLoaderDynamicStyles' ).appendTo( 'head' );
360 - return $marker;
361 - }
362 - }
363 -
364 - function compare( a, b ) {
365 - if ( a.length !== b.length ) {
366 - return false;
367 - }
368 - for ( var i = 0; i < b.length; i++ ) {
369 - if ( $.isArray( a[i] ) ) {
370 - if ( !compare( a[i], b[i] ) ) {
371 - return false;
372 - }
373 - }
374 - if ( a[i] !== b[i] ) {
375 - return false;
376 - }
377 - }
378 - return true;
379 - }
380 -
381 - /**
382 - * Generates an ISO8601 "basic" string from a UNIX timestamp
383 - */
384 - function formatVersionNumber( timestamp ) {
385 - var pad = function( a, b, c ) {
386 - return [a < 10 ? '0' + a : a, b < 10 ? '0' + b : b, c < 10 ? '0' + c : c].join( '' );
387 - },
388 - d = new Date();
389 - d.setTime( timestamp * 1000 );
390 - return [
391 - pad( d.getUTCFullYear(), d.getUTCMonth() + 1, d.getUTCDate() ), 'T',
392 - pad( d.getUTCHours(), d.getUTCMinutes(), d.getUTCSeconds() ), 'Z'
393 - ].join( '' );
394 - }
395 -
396 - /**
397 - * Recursively resolves dependencies and detects circular references
398 - */
399 - function recurse( module, resolved, unresolved ) {
400 - if ( registry[module] === undefined ) {
401 - throw new Error( 'Unknown dependency: ' + module );
402 - }
403 - // Resolves dynamic loader function and replaces it with its own results
404 - if ( $.isFunction( registry[module].dependencies ) ) {
405 - registry[module].dependencies = registry[module].dependencies();
406 - // Ensures the module's dependencies are always in an array
407 - if ( typeof registry[module].dependencies !== 'object' ) {
408 - registry[module].dependencies = [registry[module].dependencies];
409 - }
410 - }
411 - // Tracks down dependencies
412 - for ( var n = 0; n < registry[module].dependencies.length; n++ ) {
413 - if ( $.inArray( registry[module].dependencies[n], resolved ) === -1 ) {
414 - if ( $.inArray( registry[module].dependencies[n], unresolved ) !== -1 ) {
415 - throw new Error(
416 - 'Circular reference detected: ' + module +
417 - ' -> ' + registry[module].dependencies[n]
418 - );
419 - }
420 - recurse( registry[module].dependencies[n], resolved, unresolved );
421 - }
422 - }
423 - resolved[resolved.length] = module;
424 - unresolved.splice( $.inArray( module, unresolved ), 1 );
425 - }
426 -
427 - /**
428 - * Gets a list of module names that a module depends on in their proper dependency order
429 - *
430 - * @param module string module name or array of string module names
431 - * @return list of dependencies
432 - * @throws Error if circular reference is detected
433 - */
434 - function resolve( module ) {
435 - // Allow calling with an array of module names
436 - if ( typeof module === 'object' ) {
437 - var modules = [];
438 - for ( var m = 0; m < module.length; m++ ) {
439 - var dependencies = resolve( module[m] );
440 - for ( var n = 0; n < dependencies.length; n++ ) {
441 - modules[modules.length] = dependencies[n];
442 - }
443 - }
444 - return modules;
445 - } else if ( typeof module === 'string' ) {
446 - // Undefined modules have no dependencies
447 - if ( !( module in registry ) ) {
448 - return [];
449 - }
450 - var resolved = [];
451 - recurse( module, resolved, [] );
452 - return resolved;
453 - }
454 - throw new Error( 'Invalid module argument: ' + module );
455 - }
456 -
457 - /**
458 - * Narrows a list of module names down to those matching a specific
459 - * state. Possible states are 'undefined', 'registered', 'loading',
460 - * 'loaded', or 'ready'
461 - *
462 - * @param states string or array of strings of module states to filter by
463 - * @param modules array list of module names to filter (optional, all modules
464 - * will be used by default)
465 - * @return array list of filtered module names
466 - */
467 - function filter( states, modules ) {
468 - // Allow states to be given as a string
469 - if ( typeof states === 'string' ) {
470 - states = [states];
471 - }
472 - // If called without a list of modules, build and use a list of all modules
473 - var list = [], module;
474 - if ( modules === undefined ) {
475 - modules = [];
476 - for ( module in registry ) {
477 - modules[modules.length] = module;
478 - }
479 - }
480 - // Build a list of modules which are in one of the specified states
481 - for ( var s = 0; s < states.length; s++ ) {
482 - for ( var m = 0; m < modules.length; m++ ) {
483 - if ( registry[modules[m]] === undefined ) {
484 - // Module does not exist
485 - if ( states[s] === 'undefined' ) {
486 - // OK, undefined
487 - list[list.length] = modules[m];
488 - }
489 - } else {
490 - // Module exists, check state
491 - if ( registry[modules[m]].state === states[s] ) {
492 - // OK, correct state
493 - list[list.length] = modules[m];
494 - }
495 - }
496 - }
497 - }
498 - return list;
499 - }
500 -
501 - /**
502 - * Executes a loaded module, making it ready to use
503 - *
504 - * @param module string module name to execute
505 - */
506 - function execute( module, callback ) {
507 - if ( registry[module] === undefined ) {
508 - throw new Error( 'Module has not been registered yet: ' + module );
509 - } else if ( registry[module].state === 'registered' ) {
510 - throw new Error( 'Module has not been requested from the server yet: ' + module );
511 - } else if ( registry[module].state === 'loading' ) {
512 - throw new Error( 'Module has not completed loading yet: ' + module );
513 - } else if ( registry[module].state === 'ready' ) {
514 - throw new Error( 'Module has already been loaded: ' + module );
515 - }
516 - // Add styles
517 - var style;
518 - if ( $.isPlainObject( registry[module].style ) ) {
519 - for ( var media in registry[module].style ) {
520 - style = registry[module].style[media];
521 - if ( $.isArray( style ) ) {
522 - for ( var i = 0; i < style.length; i++ ) {
523 - getMarker().before( mw.html.element( 'link', {
524 - 'type': 'text/css',
525 - 'media': media,
526 - 'rel': 'stylesheet',
527 - 'href': style[i]
528 - } ) );
529 - }
530 - } else if ( typeof style === 'string' ) {
531 - getMarker().before( mw.html.element( 'style', {
532 - 'type': 'text/css',
533 - 'media': media
534 - }, new mw.html.Cdata( style ) ) );
535 - }
536 - }
537 - }
538 - // Add localizations to message system
539 - if ( $.isPlainObject( registry[module].messages ) ) {
540 - mw.messages.set( registry[module].messages );
541 - }
542 - // Execute script
543 - try {
544 - var script = registry[module].script,
545 - markModuleReady = function() {
546 - registry[module].state = 'ready';
547 - handlePending( module );
548 - if ( $.isFunction( callback ) ) {
549 - callback();
550 - }
551 - },
552 - nestedAddScript = function( arr, callback, i ) {
553 - // Recursively call addScript() in its own callback
554 - // for each element of arr.
555 - if ( i >= arr.length ) {
556 - // We're at the end of the array
557 - callback();
558 - return;
559 - }
560 -
561 - addScript( arr[i], function() {
562 - nestedAddScript( arr, callback, i + 1 );
563 - } );
564 - };
565 -
566 - if ( $.isArray( script ) ) {
567 - registry[module].state = 'loading';
568 - nestedAddScript( script, markModuleReady, 0 );
569 - } else if ( $.isFunction( script ) ) {
570 - script( $ );
571 - markModuleReady();
572 - }
573 - } catch ( e ) {
574 - // This needs to NOT use mw.log because these errors are common in production mode
575 - // and not in debug mode, such as when a symbol that should be global isn't exported
576 - if ( window.console && typeof window.console.log === 'function' ) {
577 - console.log( 'mw.loader::execute> Exception thrown by ' + module + ': ' + e.message );
578 - }
579 - registry[module].state = 'error';
580 - throw e;
581 - }
582 - }
583 -
584 - /**
585 - * Automatically executes jobs and modules which are pending with satistifed dependencies.
586 - *
587 - * This is used when dependencies are satisfied, such as when a module is executed.
588 - */
589 - function handlePending( module ) {
590 - try {
591 - // Run jobs who's dependencies have just been met
592 - for ( var j = 0; j < jobs.length; j++ ) {
593 - if ( compare(
594 - filter( 'ready', jobs[j].dependencies ),
595 - jobs[j].dependencies ) )
596 - {
597 - if ( $.isFunction( jobs[j].ready ) ) {
598 - jobs[j].ready();
599 - }
600 - jobs.splice( j, 1 );
601 - j--;
602 - }
603 - }
604 - // Execute modules who's dependencies have just been met
605 - for ( var r in registry ) {
606 - if ( registry[r].state === 'loaded' ) {
607 - if ( compare(
608 - filter( ['ready'], registry[r].dependencies ),
609 - registry[r].dependencies ) )
610 - {
611 - execute( r );
612 - }
613 - }
614 - }
615 - } catch ( e ) {
616 - // Run error callbacks of jobs affected by this condition
617 - for ( var j = 0; j < jobs.length; j++ ) {
618 - if ( $.inArray( module, jobs[j].dependencies ) !== -1 ) {
619 - if ( $.isFunction( jobs[j].error ) ) {
620 - jobs[j].error();
621 - }
622 - jobs.splice( j, 1 );
623 - j--;
624 - }
625 - }
626 - }
627 - }
628 -
629 - /**
630 - * Adds a dependencies to the queue with optional callbacks to be run
631 - * when the dependencies are ready or fail
632 - *
633 - * @param dependencies string module name or array of string module names
634 - * @param ready function callback to execute when all dependencies are ready
635 - * @param error function callback to execute when any dependency fails
636 - */
637 - function request( dependencies, ready, error ) {
638 - // Allow calling by single module name
639 - if ( typeof dependencies === 'string' ) {
640 - dependencies = [dependencies];
641 - if ( dependencies[0] in registry ) {
642 - // Cache repetitively accessed deep level object member
643 - var regItemDeps = registry[dependencies[0]].dependencies,
644 - // Cache to avoid looped access to length property
645 - regItemDepLen = regItemDeps.length;
646 - for ( var n = 0; n < regItemDepLen; n++ ) {
647 - dependencies[dependencies.length] = regItemDeps[n];
648 - }
649 - }
650 - }
651 - // Add ready and error callbacks if they were given
652 - if ( arguments.length > 1 ) {
653 - jobs[jobs.length] = {
654 - 'dependencies': filter(
655 - ['undefined', 'registered', 'loading', 'loaded'],
656 - dependencies
657 - ),
658 - 'ready': ready,
659 - 'error': error
660 - };
661 - }
662 - // Queue up any dependencies that are undefined or registered
663 - dependencies = filter( ['undefined', 'registered'], dependencies );
664 - for ( var n = 0; n < dependencies.length; n++ ) {
665 - if ( $.inArray( dependencies[n], queue ) === -1 ) {
666 - queue[queue.length] = dependencies[n];
667 - }
668 - }
669 - // Work the queue
670 - mw.loader.work();
671 - }
672 -
673 - function sortQuery(o) {
674 - var sorted = {}, key, a = [];
675 - for ( key in o ) {
676 - if ( o.hasOwnProperty( key ) ) {
677 - a.push( key );
678 - }
679 - }
680 - a.sort();
681 - for ( key = 0; key < a.length; key++ ) {
682 - sorted[a[key]] = o[a[key]];
683 - }
684 - return sorted;
685 - }
686 -
687 - /**
688 - * Converts a module map of the form { foo: [ 'bar', 'baz' ], bar: [ 'baz, 'quux' ] }
689 - * to a query string of the form foo.bar,baz|bar.baz,quux
690 - */
691 - function buildModulesString( moduleMap ) {
692 - var arr = [], p;
693 - for ( var prefix in moduleMap ) {
694 - p = prefix === '' ? '' : prefix + '.';
695 - arr.push( p + moduleMap[prefix].join( ',' ) );
696 - }
697 - return arr.join( '|' );
698 - }
699 -
700 - /**
701 - * Adds a script tag to the body, either using document.write or low-level DOM manipulation,
702 - * depending on whether document-ready has occured yet.
703 - *
704 - * @param src String: URL to script, will be used as the src attribute in the script tag
705 - * @param callback Function: Optional callback which will be run when the script is done
706 - */
707 - function addScript( src, callback ) {
708 - var done = false, script;
709 - if ( ready ) {
710 - // jQuery's getScript method is NOT better than doing this the old-fashioned way
711 - // because jQuery will eval the script's code, and errors will not have sane
712 - // line numbers.
713 - script = document.createElement( 'script' );
714 - script.setAttribute( 'src', src );
715 - script.setAttribute( 'type', 'text/javascript' );
716 - if ( $.isFunction( callback ) ) {
717 - // Attach handlers for all browsers -- this is based on jQuery.ajax
718 - script.onload = script.onreadystatechange = function() {
719 -
720 - if (
721 - !done
722 - && (
723 - !script.readyState
724 - || /loaded|complete/.test( script.readyState )
725 - )
726 - ) {
727 -
728 - done = true;
729 -
730 - // Handle memory leak in IE
731 - script.onload = script.onreadystatechange = null;
732 -
733 - callback();
734 -
735 - if ( script.parentNode ) {
736 - script.parentNode.removeChild( script );
737 - }
738 -
739 - // Dereference the script
740 - script = undefined;
741 - }
742 - };
743 - }
744 - document.body.appendChild( script );
745 - } else {
746 - document.write( mw.html.element(
747 - 'script', { 'type': 'text/javascript', 'src': src }, ''
748 - ) );
749 - if ( $.isFunction( callback ) ) {
750 - // Document.write is synchronous, so this is called when it's done
751 - callback();
752 - }
753 - }
754 - }
755 -
756 - /**
757 - * Asynchronously append a script tag to the end of the body
758 - * that invokes load.php
759 - * @param moduleMap {Object}: Module map, see buildModulesString()
760 - * @param currReqBase {Object}: Object with other parameters (other than 'modules') to use in the request
761 - * @param sourceLoadScript {String}: URL of load.php
762 - */
763 - function doRequest( moduleMap, currReqBase, sourceLoadScript ) {
764 - var request = $.extend(
765 - { 'modules': buildModulesString( moduleMap ) },
766 - currReqBase
767 - );
768 - request = sortQuery( request );
769 - // Asynchronously append a script tag to the end of the body
770 - // Append &* to avoid triggering the IE6 extension check
771 - addScript( sourceLoadScript + '?' + $.param( request ) + '&*' );
772 - }
773 -
774 - /* Public Methods */
775 -
776 - /**
777 - * Requests dependencies from server, loading and executing when things when ready.
778 - */
779 - this.work = function() {
780 - // Build a list of request parameters common to all requests.
781 - var reqBase = {
782 - skin: mw.config.get( 'skin' ),
783 - lang: mw.config.get( 'wgUserLanguage' ),
784 - debug: mw.config.get( 'debug' )
785 - },
786 - // Split module batch by source and by group.
787 - splits = {},
788 - maxQueryLength = mw.config.get( 'wgResourceLoaderMaxQueryLength', -1 );
789 -
790 - // Appends a list of modules from the queue to the batch
791 - for ( var q = 0; q < queue.length; q++ ) {
792 - // Only request modules which are undefined or registered
793 - if ( !( queue[q] in registry ) || registry[queue[q]].state === 'registered' ) {
794 - // Prevent duplicate entries
795 - if ( $.inArray( queue[q], batch ) === -1 ) {
796 - batch[batch.length] = queue[q];
797 - // Mark registered modules as loading
798 - if ( queue[q] in registry ) {
799 - registry[queue[q]].state = 'loading';
800 - }
801 - }
802 - }
803 - }
804 - // Early exit if there's nothing to load...
805 - if ( !batch.length ) {
806 - return;
807 - }
808 -
809 - // The queue has been processed into the batch, clear up the queue.
810 - queue = [];
811 -
812 - // Always order modules alphabetically to help reduce cache
813 - // misses for otherwise identical content.
814 - batch.sort();
815 -
816 - // Split batch by source and by group.
817 - for ( var b = 0; b < batch.length; b++ ) {
818 - var bSource = registry[batch[b]].source,
819 - bGroup = registry[batch[b]].group;
820 - if ( !( bSource in splits ) ) {
821 - splits[bSource] = {};
822 - }
823 - if ( !( bGroup in splits[bSource] ) ) {
824 - splits[bSource][bGroup] = [];
825 - }
826 - var bSourceGroup = splits[bSource][bGroup];
827 - bSourceGroup[bSourceGroup.length] = batch[b];
828 - }
829 -
830 - // Clear the batch - this MUST happen before we append any
831 - // script elements to the body or it's possible that a script
832 - // will be locally cached, instantly load, and work the batch
833 - // again, all before we've cleared it causing each request to
834 - // include modules which are already loaded.
835 - batch = [];
836 -
837 - var source, group, modules, maxVersion, sourceLoadScript;
838 -
839 - for ( source in splits ) {
840 -
841 - sourceLoadScript = sources[source].loadScript;
842 -
843 - for ( group in splits[source] ) {
844 -
845 - // Cache access to currently selected list of
846 - // modules for this group from this source.
847 - modules = splits[source][group];
848 -
849 - // Calculate the highest timestamp
850 - maxVersion = 0;
851 - for ( var g = 0; g < modules.length; g++ ) {
852 - if ( registry[modules[g]].version > maxVersion ) {
853 - maxVersion = registry[modules[g]].version;
854 - }
855 - }
856 -
857 - var currReqBase = $.extend( { 'version': formatVersionNumber( maxVersion ) }, reqBase ),
858 - currReqBaseLength = $.param( currReqBase ).length,
859 - moduleMap = {},
860 - // We may need to split up the request to honor the query string length limit,
861 - // so build it piece by piece.
862 - l = currReqBaseLength + 9; // '&modules='.length == 9
863 -
864 - moduleMap = {}; // { prefix: [ suffixes ] }
865 -
866 - for ( var i = 0; i < modules.length; i++ ) {
867 - // Determine how many bytes this module would add to the query string
868 - var lastDotIndex = modules[i].lastIndexOf( '.' ),
869 - // Note that these substr() calls work even if lastDotIndex == -1
870 - prefix = modules[i].substr( 0, lastDotIndex ),
871 - suffix = modules[i].substr( lastDotIndex + 1 ),
872 - bytesAdded = prefix in moduleMap
873 - ? suffix.length + 3 // '%2C'.length == 3
874 - : modules[i].length + 3; // '%7C'.length == 3
875 -
876 - // If the request would become too long, create a new one,
877 - // but don't create empty requests
878 - if ( maxQueryLength > 0 && !$.isEmptyObject( moduleMap ) && l + bytesAdded > maxQueryLength ) {
879 - // This request would become too long, create a new one
880 - // and fire off the old one
881 - doRequest( moduleMap, currReqBase, sourceLoadScript );
882 - moduleMap = {};
883 - l = currReqBaseLength + 9;
884 - }
885 - if ( !( prefix in moduleMap ) ) {
886 - moduleMap[prefix] = [];
887 - }
888 - moduleMap[prefix].push( suffix );
889 - l += bytesAdded;
890 - }
891 - // If there's anything left in moduleMap, request that too
892 - if ( !$.isEmptyObject( moduleMap ) ) {
893 - doRequest( moduleMap, currReqBase, sourceLoadScript );
894 - }
895 - }
896 - }
897 - };
898 -
899 - /**
900 - * Register a source.
901 - *
902 - * @param id {String}: Short lowercase a-Z string representing a source, only used internally.
903 - * @param props {Object}: Object containing only the loadScript property which is a url to
904 - * the load.php location of the source.
905 - * @return {Boolean}
906 - */
907 - this.addSource = function( id, props ) {
908 - // Allow multiple additions
909 - if ( typeof id === 'object' ) {
910 - for ( var source in id ) {
911 - mw.loader.addSource( source, id[source] );
912 - }
913 - return true;
914 - }
915 -
916 - if ( sources[id] !== undefined ) {
917 - throw new Error( 'source already registered: ' + id );
918 - }
919 -
920 - sources[id] = props;
921 -
922 - return true;
923 - };
924 -
925 - /**
926 - * Registers a module, letting the system know about it and its
927 - * properties. Startup modules contain calls to this function.
928 - *
929 - * @param module {String}: Module name
930 - * @param version {Number}: Module version number as a timestamp (falls backs to 0)
931 - * @param dependencies {String|Array|Function}: One string or array of strings of module
932 - * names on which this module depends, or a function that returns that array.
933 - * @param group {String}: Group which the module is in (optional, defaults to null)
934 - * @param source {String}: Name of the source. Defaults to local.
935 - */
936 - this.register = function( module, version, dependencies, group, source ) {
937 - // Allow multiple registration
938 - if ( typeof module === 'object' ) {
939 - for ( var m = 0; m < module.length; m++ ) {
940 - // module is an array of module names
941 - if ( typeof module[m] === 'string' ) {
942 - mw.loader.register( module[m] );
943 - // module is an array of arrays
944 - } else if ( typeof module[m] === 'object' ) {
945 - mw.loader.register.apply( mw.loader, module[m] );
946 - }
947 - }
948 - return;
949 - }
950 - // Validate input
951 - if ( typeof module !== 'string' ) {
952 - throw new Error( 'module must be a string, not a ' + typeof module );
953 - }
954 - if ( registry[module] !== undefined ) {
955 - throw new Error( 'module already implemented: ' + module );
956 - }
957 - // List the module as registered
958 - registry[module] = {
959 - 'version': version !== undefined ? parseInt( version, 10 ) : 0,
960 - 'dependencies': [],
961 - 'group': typeof group === 'string' ? group : null,
962 - 'source': typeof source === 'string' ? source: 'local',
963 - 'state': 'registered'
964 - };
965 - if ( typeof dependencies === 'string' ) {
966 - // Allow dependencies to be given as a single module name
967 - registry[module].dependencies = [dependencies];
968 - } else if ( typeof dependencies === 'object' || $.isFunction( dependencies ) ) {
969 - // Allow dependencies to be given as an array of module names
970 - // or a function which returns an array
971 - registry[module].dependencies = dependencies;
972 - }
973 - };
974 -
975 - /**
976 - * Implements a module, giving the system a course of action to take
977 - * upon loading. Results of a request for one or more modules contain
978 - * calls to this function.
979 - *
980 - * All arguments are required.
981 - *
982 - * @param module String: Name of module
983 - * @param script Mixed: Function of module code or String of URL to be used as the src
984 - * attribute when adding a script element to the body
985 - * @param style Object: Object of CSS strings keyed by media-type or Object of lists of URLs
986 - * keyed by media-type
987 - * @param msgs Object: List of key/value pairs to be passed through mw.messages.set
988 - */
989 - this.implement = function( module, script, style, msgs ) {
990 - // Validate input
991 - if ( typeof module !== 'string' ) {
992 - throw new Error( 'module must be a string, not a ' + typeof module );
993 - }
994 - if ( !$.isFunction( script ) && !$.isArray( script ) ) {
995 - throw new Error( 'script must be a function or an array, not a ' + typeof script );
996 - }
997 - if ( !$.isPlainObject( style ) ) {
998 - throw new Error( 'style must be an object, not a ' + typeof style );
999 - }
1000 - if ( !$.isPlainObject( msgs ) ) {
1001 - throw new Error( 'msgs must be an object, not a ' + typeof msgs );
1002 - }
1003 - // Automatically register module
1004 - if ( registry[module] === undefined ) {
1005 - mw.loader.register( module );
1006 - }
1007 - // Check for duplicate implementation
1008 - if ( registry[module] !== undefined && registry[module].script !== undefined ) {
1009 - throw new Error( 'module already implemented: ' + module );
1010 - }
1011 - // Mark module as loaded
1012 - registry[module].state = 'loaded';
1013 - // Attach components
1014 - registry[module].script = script;
1015 - registry[module].style = style;
1016 - registry[module].messages = msgs;
1017 - // Execute or queue callback
1018 - if ( compare(
1019 - filter( ['ready'], registry[module].dependencies ),
1020 - registry[module].dependencies ) )
1021 - {
1022 - execute( module );
1023 - } else {
1024 - request( module );
1025 - }
1026 - };
1027 -
1028 - /**
1029 - * Executes a function as soon as one or more required modules are ready
1030 - *
1031 - * @param dependencies string or array of strings of modules names the callback
1032 - * dependencies to be ready before executing
1033 - * @param ready function callback to execute when all dependencies are ready (optional)
1034 - * @param error function callback to execute when if dependencies have a errors (optional)
1035 - */
1036 - this.using = function( dependencies, ready, error ) {
1037 - var tod = typeof dependencies;
1038 - // Validate input
1039 - if ( tod !== 'object' && tod !== 'string' ) {
1040 - throw new Error( 'dependencies must be a string or an array, not a ' + tod );
1041 - }
1042 - // Allow calling with a single dependency as a string
1043 - if ( tod === 'string' ) {
1044 - dependencies = [dependencies];
1045 - }
1046 - // Resolve entire dependency map
1047 - dependencies = resolve( dependencies );
1048 - // If all dependencies are met, execute ready immediately
1049 - if ( compare( filter( ['ready'], dependencies ), dependencies ) ) {
1050 - if ( $.isFunction( ready ) ) {
1051 - ready();
1052 - }
1053 - }
1054 - // If any dependencies have errors execute error immediately
1055 - else if ( filter( ['error'], dependencies ).length ) {
1056 - if ( $.isFunction( error ) ) {
1057 - error();
1058 - }
1059 - }
1060 - // Since some dependencies are not yet ready, queue up a request
1061 - else {
1062 - request( dependencies, ready, error );
1063 - }
1064 - };
1065 -
1066 - /**
1067 - * Loads an external script or one or more modules for future use
1068 - *
1069 - * @param modules mixed either the name of a module, array of modules,
1070 - * or a URL of an external script or style
1071 - * @param type string mime-type to use if calling with a URL of an
1072 - * external script or style; acceptable values are "text/css" and
1073 - * "text/javascript"; if no type is provided, text/javascript is assumed.
1074 - */
1075 - this.load = function( modules, type ) {
1076 - // Validate input
1077 - if ( typeof modules !== 'object' && typeof modules !== 'string' ) {
1078 - throw new Error( 'modules must be a string or an array, not a ' + typeof modules );
1079 - }
1080 - // Allow calling with an external script or single dependency as a string
1081 - if ( typeof modules === 'string' ) {
1082 - // Support adding arbitrary external scripts
1083 - if ( /^(https?:)?\/\//.test( modules ) ) {
1084 - if ( type === 'text/css' ) {
1085 - $( 'head' ).append( $( '<link/>', {
1086 - rel: 'stylesheet',
1087 - type: 'text/css',
1088 - href: modules
1089 - } ) );
1090 - return true;
1091 - } else if ( type === 'text/javascript' || type === undefined ) {
1092 - addScript( modules );
1093 - return true;
1094 - }
1095 - // Unknown type
1096 - return false;
1097 - }
1098 - // Called with single module
1099 - modules = [modules];
1100 - }
1101 - // Resolve entire dependency map
1102 - modules = resolve( modules );
1103 - // If all modules are ready, nothing dependency be done
1104 - if ( compare( filter( ['ready'], modules ), modules ) ) {
1105 - return true;
1106 - }
1107 - // If any modules have errors return false
1108 - else if ( filter( ['error'], modules ).length ) {
1109 - return false;
1110 - }
1111 - // Since some modules are not yet ready, queue up a request
1112 - else {
1113 - request( modules );
1114 - return true;
1115 - }
1116 - };
1117 -
1118 - /**
1119 - * Changes the state of a module
1120 - *
1121 - * @param module string module name or object of module name/state pairs
1122 - * @param state string state name
1123 - */
1124 - this.state = function( module, state ) {
1125 - if ( typeof module === 'object' ) {
1126 - for ( var m in module ) {
1127 - mw.loader.state( m, module[m] );
1128 - }
1129 - return;
1130 - }
1131 - if ( !( module in registry ) ) {
1132 - mw.loader.register( module );
1133 - }
1134 - registry[module].state = state;
1135 - };
1136 -
1137 - /**
1138 - * Gets the version of a module
1139 - *
1140 - * @param module string name of module to get version for
1141 - */
1142 - this.getVersion = function( module ) {
1143 - if ( module in registry && 'version' in registry[module] ) {
1144 - return formatVersionNumber( registry[module].version );
1145 - }
1146 - return null;
1147 - };
1148 -
1149 - /**
1150 - * @deprecated use mw.loader.getVersion() instead
1151 - */
1152 - this.version = function() {
1153 - return mw.loader.getVersion.apply( mw.loader, arguments );
1154 - };
1155 -
1156 - /**
1157 - * Gets the state of a module
1158 - *
1159 - * @param module string name of module to get state for
1160 - */
1161 - this.getState = function( module ) {
1162 - if ( module in registry && 'state' in registry[module] ) {
1163 - return registry[module].state;
1164 - }
1165 - return null;
1166 - };
1167 -
1168 - /**
1169 - * Get names of all registered modules.
1170 - *
1171 - * @return {Array}
1172 - */
1173 - this.getModuleNames = function() {
1174 - var names = $.map( registry, function( i, key ) {
1175 - return key;
1176 - } );
1177 - return names;
1178 - };
1179 -
1180 - /* Cache document ready status */
1181 -
1182 - $(document).ready( function() { ready = true; } );
1183 - } )();
1184 -
1185 - /** HTML construction helper functions */
1186 - this.html = new ( function () {
1187 - var escapeCallback = function( s ) {
1188 - switch ( s ) {
1189 - case "'":
1190 - return '&#039;';
1191 - case '"':
1192 - return '&quot;';
1193 - case '<':
1194 - return '&lt;';
1195 - case '>':
1196 - return '&gt;';
1197 - case '&':
1198 - return '&amp;';
1199 - }
1200 - };
1201 -
1202 - /**
1203 - * Escape a string for HTML. Converts special characters to HTML entities.
1204 - * @param s The string to escape
1205 - */
1206 - this.escape = function( s ) {
1207 - return s.replace( /['"<>&]/g, escapeCallback );
1208 - };
1209 -
1210 - /**
1211 - * Wrapper object for raw HTML passed to mw.html.element().
1212 - */
1213 - this.Raw = function( value ) {
1214 - this.value = value;
1215 - };
1216 -
1217 - /**
1218 - * Wrapper object for CDATA element contents passed to mw.html.element()
1219 - */
1220 - this.Cdata = function( value ) {
1221 - this.value = value;
1222 - };
1223 -
1224 - /**
1225 - * Create an HTML element string, with safe escaping.
1226 - *
1227 - * @param name The tag name.
1228 - * @param attrs An object with members mapping element names to values
1229 - * @param contents The contents of the element. May be either:
1230 - * - string: The string is escaped.
1231 - * - null or undefined: The short closing form is used, e.g. <br/>.
1232 - * - this.Raw: The value attribute is included without escaping.
1233 - * - this.Cdata: The value attribute is included, and an exception is
1234 - * thrown if it contains an illegal ETAGO delimiter.
1235 - * See http://www.w3.org/TR/1999/REC-html401-19991224/appendix/notes.html#h-B.3.2
1236 - *
1237 - * Example:
1238 - * var h = mw.html;
1239 - * return h.element( 'div', {},
1240 - * new h.Raw( h.element( 'img', {src: '<'} ) ) );
1241 - * Returns <div><img src="&lt;"/></div>
1242 - */
1243 - this.element = function( name, attrs, contents ) {
1244 - var v, s = '<' + name;
1245 - for ( var attrName in attrs ) {
1246 - v = attrs[attrName];
1247 - // Convert name=true, to name=name
1248 - if ( v === true ) {
1249 - v = attrName;
1250 - // Skip name=false
1251 - } else if ( v === false ) {
1252 - continue;
1253 - }
1254 - s += ' ' + attrName + '="' + this.escape( '' + v ) + '"';
1255 - }
1256 - if ( contents === undefined || contents === null ) {
1257 - // Self close tag
1258 - s += '/>';
1259 - return s;
1260 - }
1261 - // Regular open tag
1262 - s += '>';
1263 - switch ( typeof contents ) {
1264 - case 'string':
1265 - // Escaped
1266 - s += this.escape( contents );
1267 - break;
1268 - case 'number':
1269 - case 'boolean':
1270 - // Convert to string
1271 - s += '' + contents;
1272 - break;
1273 - default:
1274 - if ( contents instanceof this.Raw ) {
1275 - // Raw HTML inclusion
1276 - s += contents.value;
1277 - } else if ( contents instanceof this.Cdata ) {
1278 - // CDATA
1279 - if ( /<\/[a-zA-z]/.test( contents.value ) ) {
1280 - throw new Error( 'mw.html.element: Illegal end tag found in CDATA' );
1281 - }
1282 - s += contents.value;
1283 - } else {
1284 - throw new Error( 'mw.html.element: Invalid type of contents' );
1285 - }
1286 - }
1287 - s += '</' + name + '>';
1288 - return s;
1289 - };
1290 - } )();
1291 -
1292 - /* Extension points */
1293 -
1294 - this.legacy = {};
1295 -
1296 -} )( jQuery );
1297 -
1298 -// Alias $j to jQuery for backwards compatibility
1299 -window.$j = jQuery;
1300 -
1301 -// Auto-register from pre-loaded startup scripts
1302 -if ( typeof startUp !== 'undefined' && jQuery.isFunction( startUp ) ) {
1303 - startUp();
1304 - delete startUp;
1305 -}

Past revisions this follows-up on

RevisionCommit summaryAuthorDate
r97386[RL] Stand-alone ResourceLoader front-end...krinkle20:15, 17 September 2011

Status & tagging log