Index: branches/resourceloader/phase3/resources/Resources.php |
— | — | @@ -9,18 +9,6 @@ |
10 | 10 | 'script' => 'resources/base/mw.js', |
11 | 11 | 'base' => true, |
12 | 12 | ), |
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 | | - ), |
25 | 13 | 'mw.util' => array( |
26 | 14 | 'script' => 'resources/base/mw/mw.util.js', |
27 | 15 | 'base' => true, |
— | — | @@ -30,11 +18,6 @@ |
31 | 19 | 'base' => true, |
32 | 20 | 'debug' => true |
33 | 21 | ), |
34 | | - 'mw.log' => array( |
35 | | - 'script' => 'resources/base/mw/mw.log.js', |
36 | | - 'base' => true, |
37 | | - 'debug' => true |
38 | | - ), |
39 | 22 | 'test' => array( |
40 | 23 | 'script' => 'resources/test/test.js', |
41 | 24 | '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 @@ |
3 | 3 | * Debug system |
4 | 4 | */ |
5 | 5 | |
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 | +/* |
3 | 3 | * JavaScript Backwards Compatibility |
4 | 4 | */ |
5 | 5 | |
— | — | @@ -14,10 +14,437 @@ |
15 | 15 | }; |
16 | 16 | } |
17 | 17 | |
18 | | -/** |
| 18 | +/* |
19 | 19 | * Core MediaWiki JavaScript Library |
20 | 20 | */ |
21 | 21 | |
22 | 22 | 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 |