/* Private Members */
var hasOwn = Object.prototype.hasOwnProperty;
-
/* Object constructors */
/**
}
Message.prototype = {
+ /**
+ * Simple message parser, does $N replacement and nothing else.
+ * This may be overridden to provide a more complex message parser.
+ *
+ * This function will not be called for nonexistent messages.
+ */
+ parser: function() {
+ var parameters = this.parameters;
+ return this.map.get( this.key ).replace( /\$(\d+)/g, function ( str, match ) {
+ var index = parseInt( match, 10 ) - 1;
+ return parameters[index] !== undefined ? parameters[index] : '$' + match;
+ } );
+ },
+
/**
* Appends (does not replace) parameters for replacement to the .parameters property.
*
* @return string Message as a string in the current form or <key> if key does not exist.
*/
toString: function() {
- if ( !this.map.exists( this.key ) ) {
+ var text;
+
+ if ( !this.exists() ) {
// Use <key> as text if key does not exist
if ( this.format !== 'plain' ) {
// format 'escape' and 'parse' need to have the brackets and key html escaped
}
return '<' + this.key + '>';
}
- var text = this.map.get( this.key ),
- parameters = this.parameters;
-
- text = text.replace( /\$(\d+)/g, function ( str, match ) {
- var index = parseInt( match, 10 ) - 1;
- return parameters[index] !== undefined ? parameters[index] : '$' + match;
- } );
if ( this.format === 'plain' ) {
- return text;
+ // @todo FIXME: Although not applicable to core Message,
+ // Plugins like jQueryMsg should be able to distinguish
+ // between 'plain' (only variable replacement and plural/gender)
+ // and actually parsing wikitext to HTML.
+ text = this.parser();
}
+
if ( this.format === 'escaped' ) {
- // According to Message.php this needs {{-transformation, which is
- // still todo
- return mw.html.escape( text );
+ text = this.parser();
+ text = mw.html.escape( text );
}
-
- /* This should be fixed up when we have a parser
- if ( this.format === 'parse' && 'language' in mw ) {
- text = mw.language.parse( text );
+
+ if ( this.format === 'parse' ) {
+ text = this.parser();
}
- */
+
return text;
},
* @var constructor Make the Map constructor publicly available.
*/
Map: Map,
+
+ /**
+ * @var constructor Make the Message constructor publicly available.
+ */
+ Message: Message,
/**
* List of configuration values
* 'dependencies': ['required.foo', 'bar.also', ...], (or) function() {}
* 'group': 'somegroup', (or) null,
* 'source': 'local', 'someforeignwiki', (or) null
- * 'state': 'registered', 'loading', 'loaded', 'ready', or 'error'
+ * 'state': 'registered', 'loading', 'loaded', 'ready', 'error' or 'missing'
* 'script': ...,
* 'style': ...,
* 'messages': { 'key': 'value' },
queue = [],
// List of callback functions waiting for modules to be ready to be called
jobs = [],
- // Flag inidicating that document ready has occured
+ // Flag indicating that document ready has occured
ready = false,
+ // Whether we should try to load scripts in a blocking way
+ // Set with setBlocking()
+ blocking = false,
// Selector cache for the marker element. Use getMarker() to get/use the marker!
$marker = null;
' -> ' + deps[n]
);
}
+
+ // Add to unresolved
+ unresolved[unresolved.length] = module;
recurse( deps[n], resolved, unresolved );
+ // module is at the end of unresolved
+ unresolved.pop();
}
}
resolved[resolved.length] = module;
- unresolved.splice( $.inArray( module, unresolved ), 1 );
}
/**
* Gets a list of module names that a module depends on in their proper dependency order
*
* @param module string module name or array of string module names
- * @return list of dependencies
+ * @return list of dependencies, including 'module'.
* @throws Error if circular reference is detected
*/
function resolve( module ) {
}
return modules;
} else if ( typeof module === 'string' ) {
- // Undefined modules have no dependencies
- if ( registry[module] === undefined ) {
- return [];
- }
resolved = [];
recurse( module, resolved, [] );
return resolved;
/**
* Narrows a list of module names down to those matching a specific
- * state. Possible states are 'undefined', 'registered', 'loading',
- * 'loaded', or 'ready'
+ * state (see comment on top of this scope for a list of valid states).
+ * One can also filter for 'unregistered', which will return the
+ * modules names that don't have a registry entry.
*
* @param states string or array of strings of module states to filter by
- * @param modules array list of module names to filter (optional, all modules
- * will be used by default)
+ * @param modules array list of module names to filter (optional, by default the entire
+ * registry is used)
* @return array list of filtered module names
*/
function filter( states, modules ) {
for ( m = 0; m < modules.length; m += 1 ) {
if ( registry[modules[m]] === undefined ) {
// Module does not exist
- if ( states[s] === 'undefined' ) {
+ if ( states[s] === 'unregistered' ) {
// OK, undefined
list[list.length] = modules[m];
}
}
/**
- * Adds a script tag to the body, either using document.write or low-level DOM manipulation,
- * depending on whether document-ready has occured yet.
+ * Adds a script tag to the DOM, either using document.write or low-level DOM manipulation,
+ * depending on whether document-ready has occured yet and whether we are in blocking mode.
*
* @param src String: URL to script, will be used as the src attribute in the script tag
* @param callback Function: Optional callback which will be run when the script is done
*/
function addScript( src, callback ) {
- var done = false, script;
- if ( ready ) {
+ var done = false, script, head;
+ if ( ready || !blocking ) {
// jQuery's getScript method is NOT better than doing this the old-fashioned way
// because jQuery will eval the script's code, and errors will not have sane
// line numbers.
}
};
}
- document.body.appendChild( script );
+
+ if ( window.opera ) {
+ // Appending to the <head> blocks rendering completely in Opera,
+ // so append to the <body> after document ready. This means the
+ // scripts only start loading after the document has been rendered,
+ // but so be it. Opera users don't deserve faster web pages if their
+ // browser makes it impossible
+ $( function() { document.body.appendChild( script ); } );
+ } else {
+ // IE-safe way of getting the <head> . document.documentElement.head doesn't
+ // work in scripts that run in the <head>
+ head = document.getElementsByTagName( 'head' )[0];
+ ( document.body || head ).appendChild( script );
+ }
} else {
document.write( mw.html.element(
'script', { 'type': 'text/javascript', 'src': src }, ''
) );
if ( $.isFunction( callback ) ) {
// Document.write is synchronous, so this is called when it's done
+ // FIXME: that's a lie. doc.write isn't actually synchronous
callback();
}
}
if ( arguments.length > 1 ) {
jobs[jobs.length] = {
'dependencies': filter(
- ['undefined', 'registered', 'loading', 'loaded'],
+ ['registered', 'loading', 'loaded'],
dependencies
),
'ready': ready,
'error': error
};
}
- // Queue up any dependencies that are undefined or registered
- dependencies = filter( ['undefined', 'registered'], dependencies );
+ // Queue up any dependencies that are registered
+ dependencies = filter( ['registered'], dependencies );
for ( n = 0; n < dependencies.length; n += 1 ) {
if ( $.inArray( dependencies[n], queue ) === -1 ) {
queue[queue.length] = dependencies[n];
// Appends a list of modules from the queue to the batch
for ( q = 0; q < queue.length; q += 1 ) {
- // Only request modules which are undefined or registered
- if ( registry[queue[q]] === undefined || registry[queue[q]].state === 'registered' ) {
+ // Only request modules which are registered
+ if ( registry[queue[q]] !== undefined && registry[queue[q]].state === 'registered' ) {
// Prevent duplicate entries
if ( $.inArray( queue[q], batch ) === -1 ) {
batch[batch.length] = queue[q];
// Mark registered modules as loading
- if ( registry[queue[q]] !== undefined ) {
- registry[queue[q]].state = 'loading';
- }
+ registry[queue[q]].state = 'loading';
}
}
}
throw new Error( 'module must be a string, not a ' + typeof module );
}
if ( registry[module] !== undefined ) {
- throw new Error( 'module already implemented: ' + module );
+ throw new Error( 'module already registered: ' + module );
}
// List the module as registered
registry[module] = {
registry[module].dependencies ) )
{
execute( module );
- } else {
- request( module );
}
},
* "text/javascript"; if no type is provided, text/javascript is assumed.
*/
load: function ( modules, type ) {
+ var filtered, m;
+
// Validate input
if ( typeof modules !== 'object' && typeof modules !== 'string' ) {
throw new Error( 'modules must be a string or an array, not a ' + typeof modules );
}
- // Allow calling with an external script or single dependency as a string
+ // Allow calling with an external url or single dependency as a string
if ( typeof modules === 'string' ) {
// Support adding arbitrary external scripts
if ( /^(https?:)?\/\//.test( modules ) ) {
if ( type === 'text/css' ) {
- $( 'head' ).append( $( '<link/>', {
+ $( 'head' ).append( $( '<link>', {
rel: 'stylesheet',
type: 'text/css',
href: modules
} ) );
- return true;
+ return;
} else if ( type === 'text/javascript' || type === undefined ) {
addScript( modules );
- return true;
+ return;
}
// Unknown type
- return false;
+ throw new Error( 'invalid type for external url, must be text/css or text/javascript. not ' + type );
}
// Called with single module
modules = [modules];
}
+
+ // Filter out undefined modules, otherwise resolve() will throw
+ // an exception for trying to load an undefined module.
+ // Undefined modules are acceptable here in load(), because load() takes
+ // an array of unrelated modules, whereas the modules passed to
+ // using() are related and must all be loaded.
+ for ( filtered = [], m = 0; m < modules.length; m += 1 ) {
+ if ( registry[modules[m]] !== undefined ) {
+ filtered[filtered.length] = modules[m];
+ }
+ }
+
// Resolve entire dependency map
- modules = resolve( modules );
+ filtered = resolve( filtered );
// If all modules are ready, nothing dependency be done
- if ( compare( filter( ['ready'], modules ), modules ) ) {
- return true;
+ if ( compare( filter( ['ready'], filtered ), filtered ) ) {
+ return;
}
- // If any modules have errors return false
- else if ( filter( ['error'], modules ).length ) {
- return false;
+ // If any modules have errors
+ else if ( filter( ['error'], filtered ).length ) {
+ return;
}
// Since some modules are not yet ready, queue up a request
else {
- request( modules );
- return true;
+ request( filtered );
+ return;
}
},
return key;
} );
},
+
+ /**
+ * Enable or disable blocking. If blocking is enabled and
+ * document ready has not yet occurred, scripts will be loaded
+ * in a blocking way (using document.write) rather than
+ * asynchronously using DOM manipulation
+ *
+ * @param b {Boolean} True to enable blocking, false to disable it
+ */
+ setBlocking: function( b ) {
+ blocking = b;
+ },
/**
* For backwards-compatibility with Squid-cached pages. Loads mw.user