r69445 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r69444‎ | r69445 | r69446 >
Date:21:57, 16 July 2010
Author:tparscal
Status:resolved (Comments)
Tags:
Comment:
Merged a few modules into mw.js
Modified paths:
  • /branches/resourceloader/phase3/resources/Resources.php (modified) (history)
  • /branches/resourceloader/phase3/resources/base/mw.js (modified) (history)
  • /branches/resourceloader/phase3/resources/base/mw/mw.config.js (deleted) (history)
  • /branches/resourceloader/phase3/resources/base/mw/mw.debug.js (modified) (history)
  • /branches/resourceloader/phase3/resources/base/mw/mw.loader.js (deleted) (history)
  • /branches/resourceloader/phase3/resources/base/mw/mw.log.js (deleted) (history)
  • /branches/resourceloader/phase3/resources/base/mw/mw.msg.js (deleted) (history)

Diff [purge]

Index: branches/resourceloader/phase3/resources/Resources.php
@@ -9,18 +9,6 @@
1010 'script' => 'resources/base/mw.js',
1111 'base' => true,
1212 ),
13 - 'mw.config' => array(
14 - 'script' => 'resources/base/mw/mw.config.js',
15 - 'base' => true,
16 - ),
17 - 'mw.loader' => array(
18 - 'script' => 'resources/base/mw/mw.loader.js',
19 - 'base' => true,
20 - ),
21 - 'mw.msg' => array(
22 - 'script' => 'resources/base/mw/mw.msg.js',
23 - 'base' => true,
24 - ),
2513 'mw.util' => array(
2614 'script' => 'resources/base/mw/mw.util.js',
2715 'base' => true,
@@ -30,11 +18,6 @@
3119 'base' => true,
3220 'debug' => true
3321 ),
34 - 'mw.log' => array(
35 - 'script' => 'resources/base/mw/mw.log.js',
36 - 'base' => true,
37 - 'debug' => true
38 - ),
3922 'test' => array(
4023 'script' => 'resources/test/test.js',
4124 'loader' => 'resources/test/loader.js',
Index: branches/resourceloader/phase3/resources/base/mw/mw.config.js
@@ -1,60 +0,0 @@
2 -/**
3 - * Configuration system
4 - */
5 -
6 -window.mw.config = new ( function() {
7 -
8 - /* Private Members */
9 -
10 - var that = this;
11 - // List of configuration values
12 - var values = {};
13 -
14 - /* Public Functions */
15 -
16 - /**
17 - * Sets one or multiple configuration values using a key and a value or an object of keys and values
18 - */
19 - this.set = function( keys, value ) {
20 - if ( typeof keys === 'object' ) {
21 - for ( var key in keys ) {
22 - values[key] = keys[key];
23 - }
24 - } else if ( typeof keys === 'string' && typeof value !== 'undefined' ) {
25 - values[keys] = value;
26 - }
27 - };
28 - /**
29 - * Gets one or multiple configuration values using a key and an optional fallback or an array of keys
30 - */
31 - this.get = function( keys, fallback ) {
32 - if ( typeof keys === 'object' ) {
33 - var result = {};
34 - for ( var k = 0; k < keys.length; k++ ) {
35 - if ( typeof values[keys[k]] !== 'undefined' ) {
36 - result[keys[k]] = values[keys[k]];
37 - }
38 - }
39 - return result;
40 - } else if ( typeof values[keys] === 'undefined' ) {
41 - return typeof fallback !== 'undefined' ? fallback : null;
42 - } else {
43 - return values[keys];
44 - }
45 - };
46 - /**
47 - * Checks if one or multiple configuration fields exist
48 - */
49 - this.exists = function( keys ) {
50 - if ( typeof keys === 'object' ) {
51 - for ( var k = 0; k < keys.length; k++ ) {
52 - if ( !( keys[k] in values ) ) {
53 - return false;
54 - }
55 - }
56 - return true;
57 - } else {
58 - return keys in values;
59 - }
60 - };
61 -} )();
\ No newline at end of file
Index: branches/resourceloader/phase3/resources/base/mw/mw.loader.js
@@ -1,318 +0,0 @@
2 -/**
3 - * Loader system
4 - */
5 -
6 -window.mw.loader = new ( function() {
7 -
8 - /* Private Members */
9 -
10 - var that = this;
11 - var server = 'load.php';
12 - /*
13 - * Mapping of registered modules
14 - *
15 - * Format:
16 - * {
17 - * 'moduleName': {
18 - * 'needs': ['required module', 'required module', ...],
19 - * 'state': 'registered, loading, loaded, or ready',
20 - * 'script': function() {},
21 - * 'style': 'css code string',
22 - * 'localization': { 'key': 'value' }
23 - * }
24 - * }
25 - */
26 - var registry = {};
27 - // List of callbacks waiting on dependent modules to be loaded so they can be executed
28 - var queue = [];
29 - // Until document ready, rather than flush the batch each time work is run, collect load requests in a batch queue
30 - var batch = [];
31 - // True after document ready occurs
32 - var ready = false;
33 -
34 - /* Private Functions */
35 -
36 - /**
37 - * Gets a list of modules names that a module needs in their proper dependency order
38 - *
39 - * @param string module name
40 - * @return
41 - * @throws Error if circular reference is detected
42 - */
43 - function needs( module ) {
44 - if ( !( module in registry ) ) {
45 - // Undefined modules have no needs
46 - return [];
47 - }
48 - var resolved = [];
49 - var unresolved = [];
50 - if ( arguments.length === 3 ) {
51 - // Use arguemnts on inner call
52 - resolved = arguments[1];
53 - unresolved = arguments[2];
54 - }
55 - unresolved[unresolved.length] = module;
56 - for ( n in registry[module].needs ) {
57 - if ( resolved.indexOf( registry[module].needs[n] ) === -1 ) {
58 - if ( unresolved.indexOf( registry[module].needs[n] ) !== -1 ) {
59 - throw new Error( 'Circular reference detected: ' + module + ' -> ' + registry[module].needs[n] )
60 - }
61 - needs( registry[module].needs[n], resolved, unresolved );
62 - }
63 - }
64 - resolved[resolved.length] = module;
65 - unresolved.slice( unresolved.indexOf( module ), 1 );
66 - if ( arguments.length === 1 ) {
67 - // Return resolved list on outer call
68 - return resolved;
69 - }
70 - };
71 - /**
72 - * Narrows a list of module names down to those matching a specific state. Possible states are 'undefined',
73 - * 'registered', 'loading', 'loaded', or 'ready'
74 - *
75 - * @param mixed string or array of strings of module states to filter by
76 - * @param array list of module names to filter (optional, all modules will be used by default)
77 - * @return array list of filtered module names
78 - */
79 - function filter( states, modules ) {
80 - var list = [];
81 - if ( typeof modules === 'undefined' ) {
82 - modules = [];
83 - for ( module in registry ) {
84 - modules[modules.length] = module;
85 - }
86 - }
87 - for ( var s in states ) {
88 - for ( var m in modules ) {
89 - if (
90 - ( states[s] == 'undefined' && typeof registry[modules[m]] === 'undefined' ) ||
91 - ( typeof registry[modules[m]] === 'object' && registry[modules[m]].state === states[s] )
92 - ) {
93 - list[list.length] = modules[m];
94 - }
95 - }
96 - }
97 - //mw.log( 'Filtered [' + modules.join( ', ' ) + '] down to [' + list.join( ', ' ) + '] using filters [' + states.join( ', ' ) + ']' );
98 - return list;
99 - }
100 - /**
101 - * Executes a loaded module, making it ready to use
102 - *
103 - * @param string module name to execute
104 - */
105 - function execute( module ) {
106 - if ( typeof registry[module] === 'undefined' ) {
107 - throw new Error( 'module has not been registered: ' + module );
108 - }
109 - switch ( registry[module].state ) {
110 - case 'registered':
111 - throw new Error( 'module has not completed loading: ' + module );
112 - break;
113 - case 'loading':
114 - throw new Error( 'module has not completed loading: ' + module );
115 - break;
116 - case 'ready':
117 - throw new Error( 'module has already been loaded: ' + module );
118 - break;
119 - }
120 - // Add style sheet to document
121 - if ( typeof registry[module].style === 'string' && registry[module].style.length ) {
122 - $( 'head' ).append( '<style type="text/css">' + registry[module].style + '</style>' );
123 - }
124 - // Add localizations to message system
125 - if ( typeof registry[module].localization === 'object' ) {
126 - mw.msg.set( registry[module].localization );
127 - }
128 - // Execute script
129 - try {
130 - registry[module].script();
131 - } catch( e ) {
132 - mw.log( 'Exception thrown by ' + module + ': ' + e.message );
133 - }
134 - // Change state
135 - registry[module].state = 'ready';
136 -
137 - // Execute all modules which were waiting for this to be ready
138 - for ( r in registry ) {
139 - if ( registry[r].state == 'loaded' ) {
140 - if ( filter( ['ready'], registry[r].needs ).length == registry[r].needs.length ) {
141 - execute( r );
142 - }
143 - }
144 - }
145 - }
146 - /**
147 - * Adds a callback and it's needs to the queue
148 - *
149 - * @param array list of module names the callback needs to be ready before being executed
150 - * @param function callback to execute when needs are met
151 - */
152 - function request( needs, callback ) {
153 - queue[queue.length] = { 'needs': filter( ['undefined', 'registered'], needs ), 'callback': callback };
154 - }
155 -
156 - /* Public Functions */
157 -
158 - /**
159 - * Processes the queue, loading and executing when things when ready.
160 - */
161 - this.work = function() {
162 - // Appends a list of modules to the batch
163 - function append( modules ) {
164 - for ( m in modules ) {
165 - // Prevent requesting modules which are loading, loaded or ready
166 - if ( modules[m] in registry && registry[modules[m]].state == 'registered' ) {
167 - // Since the batch can live between calls to work until document ready, we need to make sure we aren't
168 - // making a duplicate entry
169 - if ( batch.indexOf( modules[m] ) == -1 ) {
170 - batch[batch.length] = modules[m];
171 - registry[modules[m]].state = 'loading';
172 - }
173 - }
174 - }
175 - }
176 - // Fill batch with modules that need to be loaded
177 - for ( var q in queue ) {
178 - append( queue[q].needs );
179 - for ( n in queue[q].needs ) {
180 - append( needs( queue[q].needs[n] ) );
181 - }
182 - }
183 - // After document ready, handle the batch
184 - if ( ready && batch.length ) {
185 - // Always order modules alphabetically to help reduce cache misses for otherwise identical content
186 - batch.sort();
187 -
188 - var base = $.extend( {},
189 - // Pass configuration values through the URL
190 - mw.config.get( [ 'user', 'skin', 'space', 'view', 'language' ] ),
191 - // Ensure request comes back in the proper mode (debug or not)
192 - { 'debug': typeof mw.debug !== 'undefined' ? '1' : '0' }
193 - );
194 - var requests = [];
195 - if ( base.debug == '1' ) {
196 - for ( b in batch ) {
197 - requests[requests.length] = $.extend( { 'modules': batch[b] }, base );
198 - }
199 - } else {
200 - requests[requests.length] = $.extend( { 'modules': batch.join( '|' ) }, base );
201 - }
202 - // It may be more performant to do this with an Ajax call, but that's limited to same-domain, so we can
203 - // either auto-detect (if there really is any benefit) or just use this method, which is safe either way.
204 - setTimeout( function() {
205 - // Clear the batch - this MUST happen before we append the script element to the body or it's possible that
206 - // the script will be locally cached, instantly load, and work the batch again, all before we've cleared it
207 - // causing each request to include modules which have already been loaded
208 - batch = [];
209 - var html = '';
210 - for ( r in requests ) {
211 - // Build out the HTML
212 - var src = mw.util.buildUrlString( {
213 - 'path': mw.config.get( 'wgScriptPath' ) + '/load.php',
214 - 'query': requests[r]
215 - } );
216 - html += '<script type="text/javascript" src="' + src + '"></script>';
217 - }
218 - // Append script to head
219 - $( 'head' ).append( html );
220 - }, 0 )
221 - }
222 - };
223 - /**
224 - * Registers a module, letting the system know about it and it's dependencies. loader.js files contain calls
225 - * to this function.
226 - */
227 - this.register = function( name, needs ) {
228 - // Validate input
229 - if ( typeof name !== 'string' ) {
230 - throw new Error( 'name must be a string, not a ' + typeof name );
231 - }
232 - if ( typeof registry[name] !== 'undefined' ) {
233 - throw new Error( 'module already implemeneted: ' + name );
234 - }
235 - // List the module as registered
236 - registry[name] = { 'state': 'registered', 'needs': [] };
237 - // Allow needs to be given as a function which returns a string or array
238 - if ( typeof needs === 'function' ) {
239 - needs = needs();
240 - }
241 - if ( typeof needs === 'string' ) {
242 - // Allow needs to be given as a single module name
243 - registry[name].needs = [needs];
244 - } else if ( typeof needs === 'object' ) {
245 - // Allow needs to be given as an array of module names
246 - registry[name].needs = needs;
247 - }
248 - };
249 - /**
250 - * Implements a module, giving the system a course of action to take upon loading. Results of a request for one
251 - * or more modules contain calls to this function.
252 - */
253 - this.implement = function( name, script, style, localization ) {
254 - // Automaically register module
255 - if ( typeof registry[name] === 'undefined' ) {
256 - that.register( name, needs );
257 - }
258 - // Validate input
259 - if ( typeof script !== 'function' ) {
260 - throw new Error( 'script must be a function, not a ' + typeof script );
261 - }
262 - if ( typeof style !== 'undefined' && typeof style !== 'string' ) {
263 - throw new Error( 'style must be a string, not a ' + typeof style );
264 - }
265 - if ( typeof localization !== 'undefined' && typeof localization !== 'object' ) {
266 - throw new Error( 'localization must be an object, not a ' + typeof localization );
267 - }
268 - if ( typeof registry[name] !== 'undefined' && typeof registry[name].script !== 'undefined' ) {
269 - throw new Error( 'module already implemeneted: ' + name );
270 - }
271 - // Mark module as loaded
272 - registry[name].state = 'loaded';
273 - // Attach components
274 - registry[name].script = script;
275 - if ( typeof style === 'string' ) {
276 - registry[name].style = style;
277 - }
278 - if ( typeof localization === 'object' ) {
279 - registry[name].localization = localization;
280 - }
281 - // Execute or queue callback
282 - if ( filter( ['ready'], registry[name].needs ).length == registry[name].needs.length ) {
283 - execute( name );
284 - } else {
285 - request( registry[name].needs, function() { execute( name ); } );
286 - }
287 - };
288 - /**
289 - * Executes a function as soon as one or more required modules are ready
290 - *
291 - * @param mixed string or array of strings of names of modules the callback needs to be ready before executing
292 - * @param function callback to execute when all needs are met
293 - */
294 - this.using = function( needs, callback ) {
295 - // Validate input
296 - if ( typeof needs !== 'object' && typeof needs !== 'string' ) {
297 - throw new Error( 'needs must be a string or an array, not a ' + typeof needs )
298 - }
299 - if ( typeof callback !== 'function' ) {
300 - throw new Error( 'callback must be a function, not a ' + typeof callback )
301 - }
302 - if ( typeof needs === 'string' ) {
303 - needs = [needs];
304 - }
305 - // Execute or queue callback
306 - if ( filter( ['ready'], needs ).length == needs.length ) {
307 - callback();
308 - } else {
309 - request( needs, callback );
310 - }
311 - };
312 -
313 - /* Event Bindings */
314 -
315 - $( document ).ready( function() {
316 - ready = true;
317 - mw.loader.work();
318 - } );
319 -} )();
\ No newline at end of file
Index: branches/resourceloader/phase3/resources/base/mw/mw.msg.js
@@ -1,38 +0,0 @@
2 -/**
3 - * Localization system
4 - */
5 -
6 -window.mw.msg = new ( function() {
7 -
8 - /* Private Members */
9 -
10 - var that = this;
11 - // List of localized messages
12 - var messages = {};
13 -
14 - /* Public Functions */
15 -
16 - this.set = function( keys, value ) {
17 - if ( typeof keys === 'object' ) {
18 - for ( var key in keys ) {
19 - messages[key] = keys[key];
20 - }
21 - } else if ( typeof keys === 'string' && typeof value !== 'undefined' ) {
22 - messages[keys] = value;
23 - }
24 - };
25 - this.get = function( key, args ) {
26 - if ( !( key in messages ) ) {
27 - return '<' + key + '>';
28 - }
29 - var msg = messages[key];
30 - if ( typeof args == 'object' || typeof args == 'array' ) {
31 - for ( var argKey in args ) {
32 - msg = msg.replace( '\$' + ( parseInt( argKey ) + 1 ), args[argKey] );
33 - }
34 - } else if ( typeof args == 'string' || typeof args == 'number' ) {
35 - msg = msg.replace( '$1', args );
36 - }
37 - return msg;
38 - };
39 -} )();
\ No newline at end of file
Index: branches/resourceloader/phase3/resources/base/mw/mw.log.js
@@ -1,55 +0,0 @@
2 -/**
3 - * Loader system
4 - */
5 -
6 -/**
7 -* Log a string msg to the console
8 -*
9 -* All mw.log statements will be removed on minification so lots of mw.log calls will not impact performance in non-debug
10 -* mode. This is done using simple regular expressions, so the input of this function needs to not contain things like a
11 -* self-executing closure. In the case that the browser does not have a console available, one is created by appending a
12 -* <div> element to the bottom of the body and then appending a <div> element to that for each message. In the case that
13 -* the browser does have a console available
14 -*
15 -* @author Michael Dale <mdale@wikimedia.org>, Trevor Parscal <tparscal@wikimedia.org>
16 -* @param {String} string String to output to console
17 -*/
18 -window.mw.log = function( string ) {
19 - // Allow log messages to use a configured prefix
20 - if ( mw.config.exists( 'mw.log.prefix' ) ) {
21 - string = mw.config.get( 'mw.log.prefix' ) + string;
22 - }
23 - // Try to use an existing console
24 - if ( typeof window.console !== 'undefined' && typeof window.console.log == 'function' ) {
25 - window.console.log( string );
26 - } else {
27 - // Show a log box for console-less browsers
28 - var $log = $( '#mw_log_console' );
29 - if ( !$log.length ) {
30 - $log = $( '<div id="mw_log_console"></div>' )
31 - .css( {
32 - 'position': 'absolute',
33 - 'overflow': 'auto',
34 - 'z-index': 500,
35 - 'bottom': '0px',
36 - 'left': '0px',
37 - 'right': '0px',
38 - 'height': '150px',
39 - 'background-color': 'white',
40 - 'border-top': 'solid 1px #DDDDDD'
41 - } )
42 - .appendTo( $( 'body' ) );
43 - }
44 - if ( $log.length ) {
45 - $log.append(
46 - $( '<div>' + string + '</div>' )
47 - .css( {
48 - 'border-bottom': 'solid 1px #DDDDDD',
49 - 'font-size': 'small',
50 - 'font-family': 'monospace',
51 - 'padding': '0.125em 0.25em'
52 - } )
53 - );
54 - }
55 - }
56 -};
\ No newline at end of file
Index: branches/resourceloader/phase3/resources/base/mw/mw.debug.js
@@ -2,4 +2,55 @@
33 * Debug system
44 */
55
6 -window.mw.debug = true;
\ No newline at end of file
 6+window.mw.debug = true;
 7+/**
 8+* Log a string msg to the console
 9+*
 10+* All mw.log statements will be removed on minification so lots of mw.log calls will not impact performance in non-debug
 11+* mode. This is done using simple regular expressions, so the input of this function needs to not contain things like a
 12+* self-executing closure. In the case that the browser does not have a console available, one is created by appending a
 13+* <div> element to the bottom of the body and then appending a <div> element to that for each message. In the case that
 14+* the browser does have a console available
 15+*
 16+* @author Michael Dale <mdale@wikimedia.org>, Trevor Parscal <tparscal@wikimedia.org>
 17+* @param {String} string String to output to console
 18+*/
 19+window.mw.log = function( string ) {
 20+ // Allow log messages to use a configured prefix
 21+ if ( mw.config.exists( 'mw.log.prefix' ) ) {
 22+ string = mw.config.get( 'mw.log.prefix' ) + string;
 23+ }
 24+ // Try to use an existing console
 25+ if ( typeof window.console !== 'undefined' && typeof window.console.log == 'function' ) {
 26+ window.console.log( string );
 27+ } else {
 28+ // Show a log box for console-less browsers
 29+ var $log = $( '#mw_log_console' );
 30+ if ( !$log.length ) {
 31+ $log = $( '<div id="mw_log_console"></div>' )
 32+ .css( {
 33+ 'position': 'absolute',
 34+ 'overflow': 'auto',
 35+ 'z-index': 500,
 36+ 'bottom': '0px',
 37+ 'left': '0px',
 38+ 'right': '0px',
 39+ 'height': '150px',
 40+ 'background-color': 'white',
 41+ 'border-top': 'solid 1px #DDDDDD'
 42+ } )
 43+ .appendTo( $( 'body' ) );
 44+ }
 45+ if ( $log.length ) {
 46+ $log.append(
 47+ $( '<div>' + string + '</div>' )
 48+ .css( {
 49+ 'border-bottom': 'solid 1px #DDDDDD',
 50+ 'font-size': 'small',
 51+ 'font-family': 'monospace',
 52+ 'padding': '0.125em 0.25em'
 53+ } )
 54+ );
 55+ }
 56+ }
 57+};
\ No newline at end of file
Index: branches/resourceloader/phase3/resources/base/mw.js
@@ -1,4 +1,4 @@
2 -/**
 2+/*
33 * JavaScript Backwards Compatibility
44 */
55
@@ -14,10 +14,437 @@
1515 };
1616 }
1717
18 -/**
 18+/*
1919 * Core MediaWiki JavaScript Library
2020 */
2121
2222 window.mw = $.extend( typeof window.mw === 'undefined' ? {} : window.mw, {
23 - // Core stuff
24 -} );
\ No newline at end of file
 23+ 'prototypes': {
 24+ /*
 25+ * An object which allows single and multiple existence, setting and getting on a list of key / value pairs
 26+ */
 27+ 'Collection': function() {
 28+
 29+ /* Private Members */
 30+
 31+ var that = this;
 32+ // List of configuration values
 33+ var values = {};
 34+
 35+ /* Public Functions */
 36+
 37+ /**
 38+ * Sets one or multiple configuration values using a key and a value or an object of keys and values
 39+ */
 40+ this.set = function( keys, value ) {
 41+ if ( typeof keys === 'object' ) {
 42+ for ( var key in keys ) {
 43+ values[key] = keys[key];
 44+ }
 45+ } else if ( typeof keys === 'string' && typeof value !== 'undefined' ) {
 46+ values[keys] = value;
 47+ }
 48+ };
 49+ /**
 50+ * Gets one or multiple configuration values using a key and an optional fallback or an array of keys
 51+ */
 52+ this.get = function( keys, fallback ) {
 53+ if ( typeof keys === 'object' ) {
 54+ var result = {};
 55+ for ( var k = 0; k < keys.length; k++ ) {
 56+ if ( typeof values[keys[k]] !== 'undefined' ) {
 57+ result[keys[k]] = values[keys[k]];
 58+ }
 59+ }
 60+ return result;
 61+ } else if ( typeof values[keys] === 'undefined' ) {
 62+ return typeof fallback !== 'undefined' ? fallback : null;
 63+ } else {
 64+ return values[keys];
 65+ }
 66+ };
 67+ /**
 68+ * Checks if one or multiple configuration fields exist
 69+ */
 70+ this.exists = function( keys ) {
 71+ if ( typeof keys === 'object' ) {
 72+ for ( var k = 0; k < keys.length; k++ ) {
 73+ if ( !( keys[k] in values ) ) {
 74+ return false;
 75+ }
 76+ }
 77+ return true;
 78+ } else {
 79+ return keys in values;
 80+ }
 81+ };
 82+ },
 83+ /*
 84+ * Localization system
 85+ */
 86+ 'Language': function() {
 87+
 88+ /* Private Members */
 89+
 90+ var that = this;
 91+ // List of localized messages
 92+ var messages = {};
 93+
 94+ /* Public Functions */
 95+
 96+ this.set = function( keys, value ) {
 97+ if ( typeof keys === 'object' ) {
 98+ for ( var key in keys ) {
 99+ messages[key] = keys[key];
 100+ }
 101+ } else if ( typeof keys === 'string' && typeof value !== 'undefined' ) {
 102+ messages[keys] = value;
 103+ }
 104+ };
 105+ this.get = function( key, args ) {
 106+ if ( !( key in messages ) ) {
 107+ return '<' + key + '>';
 108+ }
 109+ var msg = messages[key];
 110+ if ( typeof args == 'object' || typeof args == 'array' ) {
 111+ for ( var argKey in args ) {
 112+ msg = msg.replace( '\$' + ( parseInt( argKey ) + 1 ), args[argKey] );
 113+ }
 114+ } else if ( typeof args == 'string' || typeof args == 'number' ) {
 115+ msg = msg.replace( '$1', args );
 116+ }
 117+ return msg;
 118+ };
 119+ },
 120+ /*
 121+ * Client-side module loader which integrates with the MediaWiki ResourceLoader
 122+ */
 123+ 'ResourceLoader': function() {
 124+
 125+ /* Private Members */
 126+
 127+ var that = this;
 128+ var server = 'load.php';
 129+ /*
 130+ * Mapping of registered modules
 131+ *
 132+ * Format:
 133+ * {
 134+ * 'moduleName': {
 135+ * 'needs': ['required module', 'required module', ...],
 136+ * 'state': 'registered, loading, loaded, or ready',
 137+ * 'script': function() {},
 138+ * 'style': 'css code string',
 139+ * 'localization': { 'key': 'value' }
 140+ * }
 141+ * }
 142+ */
 143+ var registry = {};
 144+ // List of callbacks waiting on dependent modules to be loaded so they can be executed
 145+ var queue = [];
 146+ // Until document ready, load requests will be collected in a batch queue
 147+ var batch = [];
 148+ // True after document ready occurs
 149+ var ready = false;
 150+
 151+ /* Private Functions */
 152+
 153+ /**
 154+ * Gets a list of modules names that a module needs in their proper dependency order
 155+ *
 156+ * @param string module name
 157+ * @return
 158+ * @throws Error if circular reference is detected
 159+ */
 160+ function needs( module ) {
 161+ if ( !( module in registry ) ) {
 162+ // Undefined modules have no needs
 163+ return [];
 164+ }
 165+ var resolved = [];
 166+ var unresolved = [];
 167+ if ( arguments.length === 3 ) {
 168+ // Use arguemnts on inner call
 169+ resolved = arguments[1];
 170+ unresolved = arguments[2];
 171+ }
 172+ unresolved[unresolved.length] = module;
 173+ for ( n in registry[module].needs ) {
 174+ if ( resolved.indexOf( registry[module].needs[n] ) === -1 ) {
 175+ if ( unresolved.indexOf( registry[module].needs[n] ) !== -1 ) {
 176+ throw new Error(
 177+ 'Circular reference detected: ' + module + ' -> ' + registry[module].needs[n]
 178+ );
 179+ }
 180+ needs( registry[module].needs[n], resolved, unresolved );
 181+ }
 182+ }
 183+ resolved[resolved.length] = module;
 184+ unresolved.slice( unresolved.indexOf( module ), 1 );
 185+ if ( arguments.length === 1 ) {
 186+ // Return resolved list on outer call
 187+ return resolved;
 188+ }
 189+ };
 190+ /**
 191+ * Narrows a list of module names down to those matching a specific state. Possible states are 'undefined',
 192+ * 'registered', 'loading', 'loaded', or 'ready'
 193+ *
 194+ * @param mixed string or array of strings of module states to filter by
 195+ * @param array list of module names to filter (optional, all modules will be used by default)
 196+ * @return array list of filtered module names
 197+ */
 198+ function filter( states, modules ) {
 199+ var list = [];
 200+ if ( typeof modules === 'undefined' ) {
 201+ modules = [];
 202+ for ( module in registry ) {
 203+ modules[modules.length] = module;
 204+ }
 205+ }
 206+ for ( var s in states ) {
 207+ for ( var m in modules ) {
 208+ if (
 209+ ( states[s] == 'undefined' && typeof registry[modules[m]] === 'undefined' ) ||
 210+ ( typeof registry[modules[m]] === 'object' && registry[modules[m]].state === states[s] )
 211+ ) {
 212+ list[list.length] = modules[m];
 213+ }
 214+ }
 215+ }
 216+ return list;
 217+ }
 218+ /**
 219+ * Executes a loaded module, making it ready to use
 220+ *
 221+ * @param string module name to execute
 222+ */
 223+ function execute( module ) {
 224+ if ( typeof registry[module] === 'undefined' ) {
 225+ throw new Error( 'module has not been registered: ' + module );
 226+ }
 227+ switch ( registry[module].state ) {
 228+ case 'registered':
 229+ throw new Error( 'module has not completed loading: ' + module );
 230+ break;
 231+ case 'loading':
 232+ throw new Error( 'module has not completed loading: ' + module );
 233+ break;
 234+ case 'ready':
 235+ throw new Error( 'module has already been loaded: ' + module );
 236+ break;
 237+ }
 238+ // Add style sheet to document
 239+ if ( typeof registry[module].style === 'string' && registry[module].style.length ) {
 240+ $( 'head' ).append( '<style type="text/css">' + registry[module].style + '</style>' );
 241+ }
 242+ // Add localizations to message system
 243+ if ( typeof registry[module].localization === 'object' ) {
 244+ mw.msg.set( registry[module].localization );
 245+ }
 246+ // Execute script
 247+ try {
 248+ registry[module].script();
 249+ } catch( e ) {
 250+ mw.log( 'Exception thrown by ' + module + ': ' + e.message );
 251+ }
 252+ // Change state
 253+ registry[module].state = 'ready';
 254+
 255+ // Execute all modules which were waiting for this to be ready
 256+ for ( r in registry ) {
 257+ if ( registry[r].state == 'loaded' ) {
 258+ if ( filter( ['ready'], registry[r].needs ).length == registry[r].needs.length ) {
 259+ execute( r );
 260+ }
 261+ }
 262+ }
 263+ }
 264+ /**
 265+ * Adds a callback and it's needs to the queue
 266+ *
 267+ * @param array list of module names the callback needs to be ready before being executed
 268+ * @param function callback to execute when needs are met
 269+ */
 270+ function request( needs, callback ) {
 271+ queue[queue.length] = { 'needs': filter( ['undefined', 'registered'], needs ), 'callback': callback };
 272+ }
 273+
 274+ /* Public Functions */
 275+
 276+ /**
 277+ * Processes the queue, loading and executing when things when ready.
 278+ */
 279+ this.work = function() {
 280+ // Appends a list of modules to the batch
 281+ function append( modules ) {
 282+ for ( m in modules ) {
 283+ // Prevent requesting modules which are loading, loaded or ready
 284+ if ( modules[m] in registry && registry[modules[m]].state == 'registered' ) {
 285+ // Since the batch can live between calls to work until document ready, we need to make sure
 286+ // we aren't making a duplicate entry
 287+ if ( batch.indexOf( modules[m] ) == -1 ) {
 288+ batch[batch.length] = modules[m];
 289+ registry[modules[m]].state = 'loading';
 290+ }
 291+ }
 292+ }
 293+ }
 294+ // Fill batch with modules that need to be loaded
 295+ for ( var q in queue ) {
 296+ append( queue[q].needs );
 297+ for ( n in queue[q].needs ) {
 298+ append( needs( queue[q].needs[n] ) );
 299+ }
 300+ }
 301+ // After document ready, handle the batch
 302+ if ( ready && batch.length ) {
 303+ // Always order modules alphabetically to help reduce cache misses for otherwise identical content
 304+ batch.sort();
 305+
 306+ var base = $.extend( {},
 307+ // Pass configuration values through the URL
 308+ mw.config.get( [ 'user', 'skin', 'space', 'view', 'language' ] ),
 309+ // Ensure request comes back in the proper mode (debug or not)
 310+ { 'debug': typeof mw.debug !== 'undefined' ? '1' : '0' }
 311+ );
 312+ var requests = [];
 313+ if ( base.debug == '1' ) {
 314+ for ( b in batch ) {
 315+ requests[requests.length] = $.extend( { 'modules': batch[b] }, base );
 316+ }
 317+ } else {
 318+ requests[requests.length] = $.extend( { 'modules': batch.join( '|' ) }, base );
 319+ }
 320+ // It may be more performant to do this with an Ajax call, but that's limited to same-domain, so we
 321+ // can either auto-detect (if there really is any benefit) or just use this method, which is safe
 322+ setTimeout( function() {
 323+ // Clear the batch - this MUST happen before we append the script element to the body or it's
 324+ // possible that the script will be locally cached, instantly load, and work the batch again,
 325+ // all before we've cleared it causing each request to include modules which are already loaded
 326+ batch = [];
 327+ var html = '';
 328+ for ( r in requests ) {
 329+ // Build out the HTML
 330+ var src = mw.util.buildUrlString( {
 331+ 'path': mw.config.get( 'wgScriptPath' ) + '/load.php',
 332+ 'query': requests[r]
 333+ } );
 334+ html += '<script type="text/javascript" src="' + src + '"></script>';
 335+ }
 336+ // Append script to head
 337+ $( 'head' ).append( html );
 338+ }, 0 )
 339+ }
 340+ };
 341+ /**
 342+ * Registers a module, letting the system know about it and it's dependencies. loader.js files contain calls
 343+ * to this function.
 344+ */
 345+ this.register = function( name, needs ) {
 346+ // Validate input
 347+ if ( typeof name !== 'string' ) {
 348+ throw new Error( 'name must be a string, not a ' + typeof name );
 349+ }
 350+ if ( typeof registry[name] !== 'undefined' ) {
 351+ throw new Error( 'module already implemeneted: ' + name );
 352+ }
 353+ // List the module as registered
 354+ registry[name] = { 'state': 'registered', 'needs': [] };
 355+ // Allow needs to be given as a function which returns a string or array
 356+ if ( typeof needs === 'function' ) {
 357+ needs = needs();
 358+ }
 359+ if ( typeof needs === 'string' ) {
 360+ // Allow needs to be given as a single module name
 361+ registry[name].needs = [needs];
 362+ } else if ( typeof needs === 'object' ) {
 363+ // Allow needs to be given as an array of module names
 364+ registry[name].needs = needs;
 365+ }
 366+ };
 367+ /**
 368+ * Implements a module, giving the system a course of action to take upon loading. Results of a request for
 369+ * one or more modules contain calls to this function.
 370+ */
 371+ this.implement = function( name, script, style, localization ) {
 372+ // Automaically register module
 373+ if ( typeof registry[name] === 'undefined' ) {
 374+ that.register( name, needs );
 375+ }
 376+ // Validate input
 377+ if ( typeof script !== 'function' ) {
 378+ throw new Error( 'script must be a function, not a ' + typeof script );
 379+ }
 380+ if ( typeof style !== 'undefined' && typeof style !== 'string' ) {
 381+ throw new Error( 'style must be a string, not a ' + typeof style );
 382+ }
 383+ if ( typeof localization !== 'undefined' && typeof localization !== 'object' ) {
 384+ throw new Error( 'localization must be an object, not a ' + typeof localization );
 385+ }
 386+ if ( typeof registry[name] !== 'undefined' && typeof registry[name].script !== 'undefined' ) {
 387+ throw new Error( 'module already implemeneted: ' + name );
 388+ }
 389+ // Mark module as loaded
 390+ registry[name].state = 'loaded';
 391+ // Attach components
 392+ registry[name].script = script;
 393+ if ( typeof style === 'string' ) {
 394+ registry[name].style = style;
 395+ }
 396+ if ( typeof localization === 'object' ) {
 397+ registry[name].localization = localization;
 398+ }
 399+ // Execute or queue callback
 400+ if ( filter( ['ready'], registry[name].needs ).length == registry[name].needs.length ) {
 401+ execute( name );
 402+ } else {
 403+ request( registry[name].needs, function() { execute( name ); } );
 404+ }
 405+ };
 406+ /**
 407+ * Executes a function as soon as one or more required modules are ready
 408+ *
 409+ * @param mixed string or array of strings of modules names the callback needs to be ready before executing
 410+ * @param function callback to execute when all needs are met
 411+ */
 412+ this.using = function( needs, callback ) {
 413+ // Validate input
 414+ if ( typeof needs !== 'object' && typeof needs !== 'string' ) {
 415+ throw new Error( 'needs must be a string or an array, not a ' + typeof needs )
 416+ }
 417+ if ( typeof callback !== 'function' ) {
 418+ throw new Error( 'callback must be a function, not a ' + typeof callback )
 419+ }
 420+ if ( typeof needs === 'string' ) {
 421+ needs = [needs];
 422+ }
 423+ // Execute or queue callback
 424+ if ( filter( ['ready'], needs ).length == needs.length ) {
 425+ callback();
 426+ } else {
 427+ request( needs, callback );
 428+ }
 429+ };
 430+
 431+ /* Event Bindings */
 432+
 433+ $( document ).ready( function() {
 434+ ready = true;
 435+ that.work();
 436+ } );
 437+ }
 438+ }
 439+} );
 440+/*
 441+ * Read-write access to site and user configurations
 442+ */
 443+mw.config = new mw.prototypes.Collection();
 444+/*
 445+ * MediaWiki ResourceLoader client
 446+ */
 447+mw.loader = new mw.prototypes.ResourceLoader();
 448+/*
 449+ * Provides read-write access to localizations
 450+ */
 451+mw.msg = new mw.prototypes.Language();
\ No newline at end of file

Follow-up revisions

RevisionCommit summaryAuthorDate
r74682Improves on r69445 by removing unneeded conditional. Also fixes a comment typo.tparscal18:15, 12 October 2010

Comments

#Comment by Catrope (talk | contribs)   18:46, 19 July 2010

By moving code and refactoring it at the same time, you haven't exactly produced a reviewable commit. Also, you split then merged these files, which doesn't make a great deal of sense to me.

+				} else if ( typeof values[keys] === 'undefined' ) {
+					return typeof fallback !== 'undefined' ? fallback : null;
+				} else {
+					return values[keys];
+				}

How about:

} else {
	return values[keys] || fallback;
}
+		if ( $log.length ) {

This check is useless because $log is set if needed right before.

+				$( '<div>' + string + '</div>' )

You may want to do $( '<div>' ).text( string ) instead so you can't mess stuff up with HTML tags in an mw.log() call.

+					for ( var argKey in args ) {
+						msg = msg.replace( '\$' + ( parseInt( argKey ) + 1 ), args[argKey] );
+					}

This uses an object-style loop to iterate over an array. It should also be processing the array backwards, otherwise $10 would never be expanded. Another problem with a series of .replace() calls is that (assuming reverse processing) $2 could expand to foo$1bar, which would then have its $1 expanded again.

#Comment by Trevor Parscal (WMF) (talk | contribs)   18:18, 12 October 2010

The parsing function has been fixed up in r73048. Other comments are resolved in r74682.

Status & tagging log