Jforrester has uploaded a new change for review. https://gerrit.wikimedia.org/r/155476
Change subject: Update OOjs to v1.0.12 ...................................................................... Update OOjs to v1.0.12 Release notes: https://git.wikimedia.org/blob/oojs%2Fcore.git/v1.0.12/History.md Change-Id: I5542d1cf5aad4d4fd78a04f6bea7a27cbabf055a --- M .docs/eg-iframe.html M build/modules.json M demos/trigger/index.html M demos/ve/desktop-dist.html M demos/ve/desktop.html M demos/ve/eventLogger.html M demos/ve/eventSequencer.html M demos/ve/mobile-dist.html M demos/ve/mobile.html R lib/oojs/oojs.jquery.js M tests/index.html 11 files changed, 156 insertions(+), 149 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/VisualEditor/VisualEditor refs/changes/76/155476/1 diff --git a/.docs/eg-iframe.html b/.docs/eg-iframe.html index 449c622..99b6fe6 100644 --- a/.docs/eg-iframe.html +++ b/.docs/eg-iframe.html @@ -66,7 +66,7 @@ <script src="../lib/jquery/jquery.js"></script> <!-- oojs --> - <script src="../lib/oojs/oojs.js"></script> + <script src="../lib/oojs/oojs.jquery.js"></script> <!-- oojs-ui --> <script src="../lib/oojs-ui/oojs-ui.js"></script> diff --git a/build/modules.json b/build/modules.json index 390b3fa..6fc2921 100644 --- a/build/modules.json +++ b/build/modules.json @@ -23,7 +23,7 @@ }, "oojs": { "scripts": [ - "lib/oojs/oojs.js" + "lib/oojs/oojs.jquery.js" ] }, "oojs-ui": { diff --git a/demos/trigger/index.html b/demos/trigger/index.html index 81c97d1..22bf238 100644 --- a/demos/trigger/index.html +++ b/demos/trigger/index.html @@ -97,7 +97,7 @@ </tr> </table> <script src="../../lib/jquery/jquery.js"></script> - <script src="../../lib/oojs/oojs.js"></script> + <script src="../../lib/oojs/oojs.jquery.js"></script> <script src="../../lib/unicodejs/unicodejs.js"></script> <script src="../../src/ve.js"></script> <script src="../../src/ui/ve.ui.js"></script> diff --git a/demos/ve/desktop-dist.html b/demos/ve/desktop-dist.html index aefe3b7..3a2e730 100644 --- a/demos/ve/desktop-dist.html +++ b/demos/ve/desktop-dist.html @@ -42,7 +42,7 @@ <script src="../../lib/jquery/jquery.js"></script> <!-- oojs --> - <script src="../../lib/oojs/oojs.js"></script> + <script src="../../lib/oojs/oojs.jquery.js"></script> <!-- oojs-ui --> <script src="../../lib/oojs-ui/oojs-ui.js"></script> diff --git a/demos/ve/desktop.html b/demos/ve/desktop.html index 6bdeda6..7500f92 100644 --- a/demos/ve/desktop.html +++ b/demos/ve/desktop.html @@ -77,7 +77,7 @@ <script src="../../lib/jquery/jquery.js"></script> <!-- oojs --> - <script src="../../lib/oojs/oojs.js"></script> + <script src="../../lib/oojs/oojs.jquery.js"></script> <!-- oojs-ui --> <script src="../../lib/oojs-ui/oojs-ui.js"></script> diff --git a/demos/ve/eventLogger.html b/demos/ve/eventLogger.html index f1dd46a..4e213b2 100644 --- a/demos/ve/eventLogger.html +++ b/demos/ve/eventLogger.html @@ -51,7 +51,7 @@ <textarea id="ve-demo-log"></textarea> <script src="../../lib/jquery/jquery.js"></script> - <script src="../../lib/oojs/oojs.js"></script> + <script src="../../lib/oojs/oojs.jquery.js"></script> <script src="../../lib/rangy/rangy-core-1.3.js"></script> <script src="../../lib/rangy/rangy-position-1.3.js"></script> <script src="../../lib/unicodejs/unicodejs.js"></script> diff --git a/demos/ve/eventSequencer.html b/demos/ve/eventSequencer.html index 0c8eceb..607d593 100644 --- a/demos/ve/eventSequencer.html +++ b/demos/ve/eventSequencer.html @@ -9,7 +9,7 @@ } </style> <script src="../../lib/jquery/jquery.js"></script> - <script src="../../lib/oojs/oojs.js"></script> + <script src="../../lib/oojs/oojs.jquery.js"></script> <script src="../../lib/unicodejs/unicodejs.js"></script> <script src="../../src/ve.js"></script> <script src="../../src/ve.EventSequencer.js"></script> diff --git a/demos/ve/mobile-dist.html b/demos/ve/mobile-dist.html index 744c205..b4a4fba 100644 --- a/demos/ve/mobile-dist.html +++ b/demos/ve/mobile-dist.html @@ -42,7 +42,7 @@ <script src="../../lib/jquery/jquery.js"></script> <!-- oojs --> - <script src="../../lib/oojs/oojs.js"></script> + <script src="../../lib/oojs/oojs.jquery.js"></script> <!-- oojs-ui --> <script src="../../lib/oojs-ui/oojs-ui.js"></script> diff --git a/demos/ve/mobile.html b/demos/ve/mobile.html index 1104f70..2f97a4a 100644 --- a/demos/ve/mobile.html +++ b/demos/ve/mobile.html @@ -78,7 +78,7 @@ <script src="../../lib/jquery/jquery.js"></script> <!-- oojs --> - <script src="../../lib/oojs/oojs.js"></script> + <script src="../../lib/oojs/oojs.jquery.js"></script> <!-- oojs-ui --> <script src="../../lib/oojs-ui/oojs-ui.js"></script> diff --git a/lib/oojs/oojs.js b/lib/oojs/oojs.jquery.js similarity index 78% rename from lib/oojs/oojs.js rename to lib/oojs/oojs.jquery.js index e684cb8..71f2f55 100644 --- a/lib/oojs/oojs.js +++ b/lib/oojs/oojs.jquery.js @@ -1,12 +1,12 @@ /*! - * OOjs v1.0.11 + * OOjs v1.0.12 optimised for jQuery * https://www.mediawiki.org/wiki/OOjs * * Copyright 2011-2014 OOjs Team and other contributors. * Released under the MIT license * http://oojs.mit-license.org * - * Date: 2014-07-23T20:15:47Z + * Date: 2014-08-20T22:33:41Z */ ( function ( global ) { @@ -222,8 +222,10 @@ * the other. An asymmetrical test may also be performed, which checks only that properties in the * first object are present in the second object, but not the inverse. * - * @param {Object} a First object to compare - * @param {Object} b Second object to compare + * If either a or b is null or undefined it will be treated as an empty object. + * + * @param {Object|undefined} a First object to compare + * @param {Object|undefined} b Second object to compare * @param {boolean} [asymmetrical] Whether to check only that b contains values from a * @return {boolean} If the objects contain the same values as each other */ @@ -233,6 +235,9 @@ if ( a === b ) { return true; } + + a = a || {}; + b = b || {}; for ( k in a ) { if ( !hasOwn.call( a, k ) ) { @@ -261,41 +266,46 @@ * Copies are deep, and will either be an object or an array depending on `source`. * * @param {Object} source Object to copy - * @param {Function} [callback] Applied to leaf values before they added to the clone + * @param {Function} [leafCallback] Applied to leaf values after they are cloned but before they are added to the clone + * @param {Function} [nodeCallback] Applied to all values before they are cloned. If the nodeCallback returns a value other than undefined, the returned value is used instead of attempting to clone. * @return {Object} Copy of source object */ -oo.copy = function ( source, callback ) { - var key, sourceValue, sourceType, destination; +oo.copy = function ( source, leafCallback, nodeCallback ) { + var key, destination; - if ( typeof source.clone === 'function' ) { - return source.clone(); - } - - destination = Array.isArray( source ) ? new Array( source.length ) : {}; - - for ( key in source ) { - sourceValue = source[key]; - sourceType = typeof sourceValue; - if ( Array.isArray( sourceValue ) ) { - // Array - destination[key] = oo.copy( sourceValue, callback ); - } else if ( sourceValue && typeof sourceValue.clone === 'function' ) { - // Duck type object with custom clone method - destination[key] = callback ? - callback( sourceValue.clone() ) : sourceValue.clone(); - } else if ( sourceValue && typeof sourceValue.cloneNode === 'function' ) { - // DOM Node - destination[key] = callback ? - callback( sourceValue.cloneNode( true ) ) : sourceValue.cloneNode( true ); - } else if ( oo.isPlainObject( sourceValue ) ) { - // Plain objects - destination[key] = oo.copy( sourceValue, callback ); - } else { - // Non-plain objects (incl. functions) and primitive values - destination[key] = callback ? callback( sourceValue ) : sourceValue; + if ( nodeCallback ) { + // Extensibility: check before attempting to clone source. + destination = nodeCallback( source ); + if ( destination !== undefined ) { + return destination; } } + if ( Array.isArray( source ) ) { + // Array (fall through) + destination = new Array( source.length ); + } else if ( source && typeof source.clone === 'function' ) { + // Duck type object with custom clone method + return leafCallback ? leafCallback( source.clone() ) : source.clone(); + } else if ( source && typeof source.cloneNode === 'function' ) { + // DOM Node + return leafCallback ? + leafCallback( source.cloneNode( true ) ) : + source.cloneNode( true ); + } else if ( oo.isPlainObject( source ) ) { + // Plain objects (fall through) + destination = {}; + } else { + // Non-plain objects (incl. functions) and primitive values + return leafCallback ? leafCallback( source ) : source; + } + + // source is an array or a plain object + for ( key in source ) { + destination[key] = oo.copy( source[key], leafCallback, nodeCallback ); + } + + // This is an internal node, so we don't apply the leafCallback. return destination; }; @@ -444,38 +454,9 @@ return simpleArrayCombine( a, b, false ); }; -/*global hasOwn, toString */ +/*global $ */ -/** - * Assert whether a value is a plain object or not. - * - * @param {Mixed} obj - * @return {boolean} - */ -oo.isPlainObject = function ( obj ) { - /*jshint eqnull:true, eqeqeq:false */ - - // Any object or value whose internal [[Class]] property is not "[object Object]" - // Support IE8: Explicitly filter out DOM nodes - // Support IE8: Explicitly filter out Window object (needs loose comparison) - if ( !obj || toString.call( obj ) !== '[object Object]' || obj.nodeType || ( obj != null && obj == obj.window ) ) { - return false; - } - - // The try/catch suppresses exceptions thrown when attempting to access - // the "constructor" property of certain host objects suich as window.location - // in Firefox < 20 (https://bugzilla.mozilla.org/814622) - try { - if ( obj.constructor && - !hasOwn.call( obj.constructor.prototype, 'isPrototypeOf' ) ) { - return false; - } - } catch ( e ) { - return false; - } - - return true; -}; +oo.isPlainObject = $.isPlainObject; /*global hasOwn */ @@ -495,29 +476,28 @@ this.bindings = {}; }; +oo.initClass( oo.EventEmitter ); + /* Methods */ /** * Add a listener to events of a specific event. * + * The listener can be a function or the string name of a method; if the latter, then the + * name lookup happens at the time the listener is called. + * * @param {string} event Type of event to listen to - * @param {Function} callback Function to call when event occurs + * @param {Function|string} method Function or method name to call when event occurs * @param {Array} [args] Arguments to pass to listener, will be prepended to emitted arguments - * @param {Object} [context=null] Object to use as context for callback function or call method on - * @throws {Error} Listener argument is not a function or method name + * @param {Object} [context=null] Context object for function or method call + * @throws {Error} Listener argument is not a function or a valid method name * @chainable */ -oo.EventEmitter.prototype.on = function ( event, callback, args, context ) { +oo.EventEmitter.prototype.on = function ( event, method, args, context ) { var bindings; - // Validate callback - if ( typeof callback !== 'function' ) { - throw new Error( 'Invalid callback. Function or method name expected.' ); - } - // Fallback to null context - if ( arguments.length < 4 ) { - context = null; - } + this.constructor.static.validateMethod( method, context ); + if ( hasOwn.call( this.bindings, event ) ) { bindings = this.bindings[event]; } else { @@ -526,9 +506,9 @@ } // Add binding bindings.push( { - callback: callback, + method: method, args: args, - context: context + context: ( arguments.length < 4 ) ? null : context } ); return this; }; @@ -553,41 +533,45 @@ * Remove a specific listener from a specific event. * * @param {string} event Type of event to remove listener from - * @param {Function} [callback] Listener to remove, omit to remove all - * @param {Object} [context=null] Object used context for callback function or method + * @param {Function|string} [method] Listener to remove. Must be in the same form as was passed + * to "on". Omit to remove all listeners. + * @param {Object} [context=null] Context object function or method call * @chainable - * @throws {Error} Listener argument is not a function + * @throws {Error} Listener argument is not a function or a valid method name */ -oo.EventEmitter.prototype.off = function ( event, callback, context ) { +oo.EventEmitter.prototype.off = function ( event, method, context ) { var i, bindings; if ( arguments.length === 1 ) { // Remove all bindings for event delete this.bindings[event]; - } else { - if ( typeof callback !== 'function' ) { - throw new Error( 'Invalid callback. Function expected.' ); + return this; + } + + this.constructor.static.validateMethod( method, context ); + + if ( !( event in this.bindings ) || !this.bindings[event].length ) { + // No matching bindings + return this; + } + + // Default to null context + if ( arguments.length < 3 ) { + context = null; + } + + // Remove matching handlers + bindings = this.bindings[event]; + i = bindings.length; + while ( i-- ) { + if ( bindings[i].method === method && bindings[i].context === context ) { + bindings.splice( i, 1 ); } - if ( !( event in this.bindings ) || !this.bindings[event].length ) { - // No matching bindings - return this; - } - // Fallback to null context - if ( arguments.length < 3 ) { - context = null; - } - // Remove matching handlers - bindings = this.bindings[event]; - i = bindings.length; - while ( i-- ) { - if ( bindings[i].callback === callback && bindings[i].context === context ) { - bindings.splice( i, 1 ); - } - } - // Cleanup if now empty - if ( bindings.length === 0 ) { - delete this.bindings[event]; - } + } + + // Cleanup if now empty + if ( bindings.length === 0 ) { + delete this.bindings[event]; } return this; }; @@ -603,7 +587,7 @@ * @return {boolean} If event was handled by at least one listener */ oo.EventEmitter.prototype.emit = function ( event ) { - var i, len, binding, bindings, args; + var i, len, binding, bindings, args, method; if ( event in this.bindings ) { // Slicing ensures that we don't get tripped up by event handlers that add/remove bindings @@ -611,7 +595,13 @@ args = Array.prototype.slice.call( arguments, 1 ); for ( i = 0, len = bindings.length; i < len; i++ ) { binding = bindings[i]; - binding.callback.apply( + if ( typeof binding.method === 'string' ) { + // Lookup method by name (late binding) + method = binding.context[ binding.method ]; + } else { + method = binding.method; + } + method.apply( binding.context, binding.args ? binding.args.concat( args ) : args ); @@ -632,7 +622,7 @@ * @chainable */ oo.EventEmitter.prototype.connect = function ( context, methods ) { - var method, callback, args, event; + var method, args, event; for ( event in methods ) { method = methods[event]; @@ -643,19 +633,8 @@ } else { args = []; } - // Allow callback to be a method name - if ( typeof method === 'string' ) { - // Validate method - if ( !context[method] || typeof context[method] !== 'function' ) { - throw new Error( 'Method not found: ' + method ); - } - // Resolve to function - callback = context[method]; - } else { - callback = method; - } // Add binding - this.on.apply( this, [ event, callback, args, context ] ); + this.on( event, method, args, context ); } return this; }; @@ -665,27 +644,17 @@ * * @param {Object} context Object to disconnect methods from * @param {Object.<string,string>|Object.<string,Function>|Object.<string,Array>} [methods] List of - * event bindings keyed by event name containing either method names or functions + * event bindings keyed by event name. Values can be either method names or functions, but must be + * consistent with those used in the corresponding call to "connect". * @chainable */ oo.EventEmitter.prototype.disconnect = function ( context, methods ) { - var i, method, callback, event, bindings; + var i, event, bindings; if ( methods ) { // Remove specific connections to the context for ( event in methods ) { - method = methods[event]; - if ( typeof method === 'string' ) { - // Validate method - if ( !context[method] || typeof context[method] !== 'function' ) { - throw new Error( 'Method not found: ' + method ); - } - // Resolve to function - callback = context[method]; - } else { - callback = method; - } - this.off( event, callback, context ); + this.off( event, methods[event], context ); } } else { // Remove all connections to the context @@ -696,7 +665,7 @@ // bindings[i] may have been removed by the previous step's // this.off so check it still exists if ( bindings[i] && bindings[i].context === context ) { - this.off( event, bindings[i].callback, context ); + this.off( event, bindings[i].method, context ); } } } @@ -704,6 +673,42 @@ return this; }; + +/** + * Validate a function or method call in a context + * + * For a method name, check that it names a function in the context object + * + * @static + * @param {Function|string} method Function or method name + * @param {Mixed} context The context of the call + * @throws {Error} A method name is given but there is no context + * @throws {Error} In the context object, no property exists with the given name + * @throws {Error} In the context object, the named property is not a function + */ +oo.EventEmitter.static.validateMethod = function ( method, context ) { + // Validate method and context + if ( typeof method === 'string' ) { + // Validate method + if ( context === undefined || context === null ) { + throw new Error( 'Method name "' + method + '" has no context.' ); + } + if ( !( method in context ) ) { + // Technically the method does not need to exist yet: it could be + // added before call time. But this probably signals a typo. + throw new Error( 'Method not found: "' + method + '"' ); + } + if ( typeof context[method] !== 'function' ) { + // Technically the property could be replaced by a function before + // call time. But this probably signals a typo. + throw new Error( 'Property "' + method + '" is not a function' ); + } + } else if ( typeof method !== 'function' ) { + throw new Error( 'Invalid callback. Function or method name expected.' ); + } +}; + +/*global hasOwn */ /** * @class OO.Registry @@ -766,7 +771,9 @@ * @return {Mixed|undefined} Data associated with symbolic name */ oo.Registry.prototype.lookup = function ( name ) { - return this.registry[name]; + if ( hasOwn.call( this.registry, name ) ) { + return this.registry[name]; + } }; /** @@ -831,12 +838,12 @@ * @throws {Error} Unknown object name */ oo.Factory.prototype.create = function ( name ) { - var args, obj, constructor; + var args, obj, + constructor = this.lookup( name ); - if ( !this.registry.hasOwnProperty( name ) ) { + if ( !constructor ) { throw new Error( 'No class registered by that name: ' + name ); } - constructor = this.registry[name]; // Convert arguments to array and shift the first argument (name) off args = Array.prototype.slice.call( arguments, 1 ); diff --git a/tests/index.html b/tests/index.html index 3af3dbe..01c0114 100644 --- a/tests/index.html +++ b/tests/index.html @@ -26,7 +26,7 @@ <script src="../lib/jquery/jquery.js"></script> <!-- oojs --> - <script src="../lib/oojs/oojs.js"></script> + <script src="../lib/oojs/oojs.jquery.js"></script> <!-- oojs-ui --> <script src="../lib/oojs-ui/oojs-ui.js"></script> -- To view, visit https://gerrit.wikimedia.org/r/155476 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I5542d1cf5aad4d4fd78a04f6bea7a27cbabf055a Gerrit-PatchSet: 1 Gerrit-Project: VisualEditor/VisualEditor Gerrit-Branch: master Gerrit-Owner: Jforrester <[email protected]> _______________________________________________ MediaWiki-commits mailing list [email protected] https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits
