r107405 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r107404‎ | r107405 | r107406 >
Date:18:48, 27 December 2011
Author:krinkle
Status:ok (Comments)
Tags:
Comment:
[mediawiki.js] use simple IIFE closure with object literal
* Remove weird new () syntax. Simply use a IIFE and return an object literal
* Some blocks had to be moved
-- $(document).ready in mw.loader to between vars and functions (couldn't be after the return)
-- mw.legacy to near other place holders
* Follows-up r107402

(view diff with whitespace ignored: $ svn diff -x -wu)
Modified paths:
  • /trunk/phase3/resources/mediawiki/mediawiki.js (modified) (history)

Diff [purge]

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

Sign-offs

UserFlagDate
Nikerabbitinspected07:15, 28 December 2011

Follow-up revisions

RevisionCommit summaryAuthorDate
r107407[mediawiki.js] function order...krinkle18:51, 27 December 2011

Past revisions this follows-up on

RevisionCommit summaryAuthorDate
r107402[mediawiki.js] code quality and clean up...krinkle18:25, 27 December 2011

Comments

#Comment by Nikerabbit (talk | contribs)   07:15, 28 December 2011

Why has there to be space after "function" ?

#Comment by Krinkle (talk | contribs)   15:23, 28 December 2011

Now that you mention it, we've been mostly not doing this, but our conventions already imply that we should and I believe we should do it this way (I think me watching Douglas Crockford material recently has brought back this way of writing anonymous functions). It's kinda specific to JavaScript so this specific case isn't very clear in the conventions (as they were mostly written for PHP).

So, two reasons:

  1. Per our conventions we always put a spaces after operator and statement keywords (such as if, while, for, etc. Reason: Clear disctinction between statements and function calls. foo() vs. foo (). Since function falls in the same category of operators. Also "function() looks like calling a function called "function" (which as of ES5 is actually theoratically valid and possible!).

  2. To emphasize that this function does not have a name (since function statements are like function nameHere( .. ) { .. }, an anonymous function would be function ( .. ) { .. }.

Sidenote:
Also note that in JavaScript all var statements are hoisted to the top and function declaration are basically internally expanded to var statements with function expressions:

function foo( .. ) {
  /* */
}

// becomes:

var foo = function foo( .. ) {
  /* */
};
#Comment by Nikerabbit (talk | contribs)   15:49, 28 December 2011

That's convincing explanation.

Status & tagging log