/*!
- * OOjs v2.0.0 optimised for jQuery
+ * OOjs v2.2.0 optimised for jQuery
* https://www.mediawiki.org/wiki/OOjs
*
- * Copyright 2011-2017 OOjs Team and other contributors.
+ * Copyright 2011-2018 OOjs Team and other contributors.
* Released under the MIT license
* https://oojs.mit-license.org
*
- * Date: 2017-04-05T02:18:04Z
+ * Date: 2018-04-03T19:45:13Z
*/
( function ( global ) {
'use strict';
-/* exported toString */
var
/**
* Namespace for all classes, static methods and static properties.
oo = {},
// Optimisation: Local reference to Object.prototype.hasOwnProperty
hasOwn = oo.hasOwnProperty,
+ // Marking this as "exported" doesn't work when parserOptions.sourceType is module
+ // eslint-disable-next-line no-unused-vars
toString = oo.toString;
/* Class Methods */
targetConstructor = targetFn.prototype.constructor;
- // Using ['super'] instead of .super because 'super' is not supported
- // by IE 8 and below (bug 63303).
- // Provide .parent as alias for code supporting older browsers which
+ // [DEPRECATED] Provide .parent as alias for code supporting older browsers which
// allows people to comply with their style guide.
- // eslint-disable-next-line dot-notation
- targetFn[ 'super' ] = targetFn.parent = originFn;
+ targetFn.super = targetFn.parent = originFn;
targetFn.prototype = Object.create( originFn.prototype, {
// Restore constructor property of targetFn
}
delete prop[ arguments[ i ] ];
// Walk back through props removing any plain empty objects
- while ( ( prop = props.pop() ) && oo.isPlainObject( prop ) && !Object.keys( prop ).length ) {
+ while ( props.length > 1 && ( prop = props.pop() ) && oo.isPlainObject( prop ) && !Object.keys( prop ).length ) {
delete props[ props.length - 1 ][ arguments[ props.length ] ];
}
};
for ( k in a ) {
if ( !hasOwn.call( a, k ) || a[ k ] === undefined || a[ k ] === b[ k ] ) {
- // Support es3-shim: Without the hasOwn filter, comparing [] to {} will be false in ES3
- // because the shimmed "forEach" is enumerable and shows up in Array but not Object.
- // Also ignore undefined values, because there is no conceptual difference between
+ // Ignore undefined values, because there is no conceptual difference between
// a key that is absent and a key that is present but whose value is undefined.
continue;
}
}
}
+ /**
+ * @private
+ * @param {OO.EventEmitter} eventEmitter Event emitter
+ * @param {string} event Event name
+ * @param {Object} binding
+ */
+ function addBinding( eventEmitter, event, binding ) {
+ var bindings;
+ // Auto-initialize bindings list
+ if ( hasOwn.call( eventEmitter.bindings, event ) ) {
+ bindings = eventEmitter.bindings[ event ];
+ } else {
+ bindings = eventEmitter.bindings[ event ] = [];
+ }
+ // Add binding
+ bindings.push( binding );
+ }
+
/* Methods */
/**
* @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] Context object for function or method call
- * @throws {Error} Listener argument is not a function or a valid method name
* @chainable
+ * @throws {Error} Listener argument is not a function or a valid method name
*/
oo.EventEmitter.prototype.on = function ( event, method, args, context ) {
- var bindings;
-
validateMethod( method, context );
- if ( hasOwn.call( this.bindings, event ) ) {
- bindings = this.bindings[ event ];
- } else {
- // Auto-initialize bindings list
- bindings = this.bindings[ event ] = [];
- }
- // Add binding
- bindings.push( {
+ // Ensure consistent object shape (optimisation)
+ addBinding( this, event, {
method: method,
args: args,
- context: ( arguments.length < 4 ) ? null : context
+ context: ( arguments.length < 4 ) ? null : context,
+ once: false
} );
return this;
};
* @chainable
*/
oo.EventEmitter.prototype.once = function ( event, listener ) {
- var eventEmitter = this,
- wrapper = function () {
- eventEmitter.off( event, wrapper );
- return listener.apply( this, arguments );
- };
- return this.on( event, wrapper );
+ validateMethod( listener );
+
+ // Ensure consistent object shape (optimisation)
+ addBinding( this, event, {
+ method: listener,
+ args: undefined,
+ context: null,
+ once: true
+ } );
+ return this;
};
/**
} else {
method = binding.method;
}
+ if ( binding.once ) {
+ // Must unbind before calling method to avoid
+ // any nested triggers.
+ this.off( event, method );
+ }
method.apply(
binding.context,
binding.args ? binding.args.concat( args ) : args
* @param {Object.<string,string>|Object.<string,Function>|Object.<string,Array>} methods List of
* event bindings keyed by event name containing either method names, functions or arrays containing
* method name or function followed by a list of arguments to be passed to callback before emitted
- * arguments
+ * arguments.
* @chainable
*/
oo.EventEmitter.prototype.connect = function ( context, methods ) {
*
* @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. Values can be either method names or functions, but must be
- * consistent with those used in the corresponding call to "connect".
+ * event bindings keyed by event name. Values can be either method names, functions or arrays
+ * containing a method name.
+ * NOTE: To allow matching call sites with connect(), array values are allowed to contain the
+ * parameters as well, but only the method name is used to find bindings. Tt is discouraged to
+ * have multiple bindings for the same event to the same listener, but if used (and only the
+ * parameters vary), disconnecting one variation of (event name, event listener, parameters)
+ * will disconnect other variations as well.
* @chainable
*/
oo.EventEmitter.prototype.disconnect = function ( context, methods ) {
// Remove the item from the current index
this.items.splice( existingIndex, 1 );
- // Adjust new index after removal
- newIndex--;
+ // If necessary, adjust new index after removal
+ if ( existingIndex < newIndex ) {
+ newIndex--;
+ }
// Move the item to the new index
this.items.splice( newIndex, 0, item );
/**
* Add items to the sorted list.
*
- * @chainable
* @param {OO.EventEmitter|OO.EventEmitter[]} items Item to add or
* an array of items to add
+ * @chainable
*/
oo.SortedEmitterList.prototype.addItems = function ( items ) {
var index, i, insertionIndex;
/* global hasOwn */
/**
+ * A map interface for associating arbitrary data with a symbolic name. Used in
+ * place of a plain object to provide additional {@link #method-register registration}
+ * or {@link #method-lookup lookup} functionality.
+ *
+ * See <https://www.mediawiki.org/wiki/OOjs/Registries_and_factories>.
+ *
* @class OO.Registry
* @mixins OO.EventEmitter
*
};
/* eslint-env node */
+
+/* istanbul ignore next */
if ( typeof module !== 'undefined' && module.exports ) {
module.exports = oo;
} else {