Jforrester has uploaded a new change for review. https://gerrit.wikimedia.org/r/306296
Change subject: Revert "Have GroupElement use OO.EmitterList" ...................................................................... Revert "Have GroupElement use OO.EmitterList" This reverts commit 6278adf528abd334c353031cbb66b146696dfc74. Breaks the toolbars demo, didn't notice in testing on other widgets. Change-Id: I2cb6e4722f3ac7eaf38c2142f771acdae2bdddec --- M src/mixins/GroupElement.js 1 file changed, 171 insertions(+), 80 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/oojs/ui refs/changes/96/306296/1 diff --git a/src/mixins/GroupElement.js b/src/mixins/GroupElement.js index d07bb35..49908d4 100644 --- a/src/mixins/GroupElement.js +++ b/src/mixins/GroupElement.js @@ -8,7 +8,6 @@ * * @abstract * @class - * @mixins OO.EmitterList * * @constructor * @param {Object} [config] Configuration options @@ -19,17 +18,14 @@ // Configuration initialization config = config || {}; - // Mixin constructors - OO.EmitterList.call( this ); - // Properties this.$group = null; + this.items = []; + this.aggregateItemEvents = {}; // Initialization this.setGroupElement( config.$group || $( '<div>' ) ); }; - -OO.mixinClass( OO.ui.mixin.GroupElement, OO.EmitterList ); /* Events */ @@ -57,6 +53,28 @@ for ( i = 0, len = this.items.length; i < len; i++ ) { this.$group.append( this.items[ i ].$element ); } +}; + +/** + * Check if a group contains no items. + * + * @return {boolean} Group is empty + */ +OO.ui.mixin.GroupElement.prototype.isEmpty = function () { + return !this.items.length; +}; + +/** + * Get all items in the group. + * + * The method returns an array of item references (e.g., [button1, button2, button3]) and is useful + * when synchronizing groups of items, or whenever the references are required (e.g., when removing items + * from a group). + * + * @return {OO.ui.Element[]} An array of items. + */ +OO.ui.mixin.GroupElement.prototype.getItems = function () { + return this.items.slice( 0 ); }; /** @@ -106,104 +124,177 @@ }; /** - * @inheritdoc - */ -OO.ui.mixin.GroupElement.prototype.addItems = function ( items, index ) { - // Mixin method - OO.EmitterList.prototype.addItems.call( this, items, index ); - - // Event - this.emit( 'change', this.getItems() ); - - return this; -}; - -/** - * @inheritdoc - */ -OO.ui.mixin.GroupElement.prototype.moveItem = function ( item, newIndex ) { - // Get the normalized index for the move by calling the parent - var index = OO.EmitterList.prototype.insertItem.call( this, item, newIndex ); - - this.attachElementToDom( item, index ); -}; - -/** - * @inheritdoc - */ -OO.ui.mixin.GroupElement.prototype.insertItem = function ( item, index ) { - // Get the normalized index for the move by calling the parent - index = OO.EmitterList.prototype.insertItem.call( this, item, index ); - - item.setElementGroup( this ); - this.attachElementToDom( item, index ); - - return index; -}; - -/** - * Attach the item element into the DOM in its proper place. + * Aggregate the events emitted by the group. * - * @private - * @param {OO.EventEmitter} item Item - * @param {number} index Insertion index + * When events are aggregated, the group will listen to all contained items for the event, + * and then emit the event under a new name. The new event will contain an additional leading + * parameter containing the item that emitted the original event. Other arguments emitted from + * the original event are passed through. + * + * @param {Object.<string,string|null>} events An object keyed by the name of the event that should be + * aggregated (e.g., ‘click’) and the value of the new name to use (e.g., ‘groupClick’). + * A `null` value will remove aggregated events. + + * @throws {Error} An error is thrown if aggregation already exists. */ -OO.ui.mixin.GroupElement.prototype.attachElementToDom = function ( item, index ) { - if ( index === undefined || index < 0 || index >= this.items.length - 1 ) { - this.$group.append( item.$element.get( 0 ) ); - } else { - this.items[ index + 1 ].$element.before( item.$element.get( 0 ) ); - } -}; +OO.ui.mixin.GroupElement.prototype.aggregate = function ( events ) { + var i, len, item, add, remove, itemEvent, groupEvent; -/** - * @inheritdoc - */ -OO.ui.mixin.GroupElement.prototype.removeItems = function ( items ) { - var i, item, index; + for ( itemEvent in events ) { + groupEvent = events[ itemEvent ]; - if ( !Array.isArray( items ) ) { - items = [ items ]; - } + // Remove existing aggregated event + if ( Object.prototype.hasOwnProperty.call( this.aggregateItemEvents, itemEvent ) ) { + // Don't allow duplicate aggregations + if ( groupEvent ) { + throw new Error( 'Duplicate item event aggregation for ' + itemEvent ); + } + // Remove event aggregation from existing items + for ( i = 0, len = this.items.length; i < len; i++ ) { + item = this.items[ i ]; + if ( item.connect && item.disconnect ) { + remove = {}; + remove[ itemEvent ] = [ 'emit', this.aggregateItemEvents[ itemEvent ], item ]; + item.disconnect( this, remove ); + } + } + // Prevent future items from aggregating event + delete this.aggregateItemEvents[ itemEvent ]; + } - if ( items.length > 0 ) { - // Remove specific items - for ( i = 0; i < items.length; i++ ) { - item = items[ i ]; - index = this.items.indexOf( item ); - if ( index !== -1 ) { - item.setElementGroup( null ); - item.$element.detach(); + // Add new aggregate event + if ( groupEvent ) { + // Make future items aggregate event + this.aggregateItemEvents[ itemEvent ] = groupEvent; + // Add event aggregation to existing items + for ( i = 0, len = this.items.length; i < len; i++ ) { + item = this.items[ i ]; + if ( item.connect && item.disconnect ) { + add = {}; + add[ itemEvent ] = [ 'emit', groupEvent, item ]; + item.connect( this, add ); + } } } } +}; - // Mixin method - OO.EmitterList.prototype.removeItems.call( this, items ); +/** + * Add items to the group. + * + * Items will be added to the end of the group array unless the optional `index` parameter specifies + * a different insertion point. Adding an existing item will move it to the end of the array or the point specified by the `index`. + * + * @param {OO.ui.Element[]} items An array of items to add to the group + * @param {number} [index] Index of the insertion point + * @chainable + */ +OO.ui.mixin.GroupElement.prototype.addItems = function ( items, index ) { + var i, len, item, itemEvent, events, currentIndex, + itemElements = []; - // Event + for ( i = 0, len = items.length; i < len; i++ ) { + item = items[ i ]; + + // Check if item exists then remove it first, effectively "moving" it + currentIndex = this.items.indexOf( item ); + if ( currentIndex >= 0 ) { + this.removeItems( [ item ] ); + // Adjust index to compensate for removal + if ( currentIndex < index ) { + index--; + } + } + // Add the item + if ( item.connect && item.disconnect && !$.isEmptyObject( this.aggregateItemEvents ) ) { + events = {}; + for ( itemEvent in this.aggregateItemEvents ) { + events[ itemEvent ] = [ 'emit', this.aggregateItemEvents[ itemEvent ], item ]; + } + item.connect( this, events ); + } + item.setElementGroup( this ); + itemElements.push( item.$element.get( 0 ) ); + } + + if ( index === undefined || index < 0 || index >= this.items.length ) { + this.$group.append( itemElements ); + this.items.push.apply( this.items, items ); + } else if ( index === 0 ) { + this.$group.prepend( itemElements ); + this.items.unshift.apply( this.items, items ); + } else { + this.items[ index ].$element.before( itemElements ); + this.items.splice.apply( this.items, [ index, 0 ].concat( items ) ); + } + this.emit( 'change', this.getItems() ); - return this; }; /** - * @inheritdoc + * Remove the specified items from a group. + * + * Removed items are detached (not removed) from the DOM so that they may be reused. + * To remove all items from a group, you may wish to use the #clearItems method instead. + * + * @param {OO.ui.Element[]} items An array of items to remove + * @chainable + */ +OO.ui.mixin.GroupElement.prototype.removeItems = function ( items ) { + var i, len, item, index, events, itemEvent; + + // Remove specific items + for ( i = 0, len = items.length; i < len; i++ ) { + item = items[ i ]; + index = this.items.indexOf( item ); + if ( index !== -1 ) { + if ( item.connect && item.disconnect && !$.isEmptyObject( this.aggregateItemEvents ) ) { + events = {}; + for ( itemEvent in this.aggregateItemEvents ) { + events[ itemEvent ] = [ 'emit', this.aggregateItemEvents[ itemEvent ], item ]; + } + item.disconnect( this, events ); + } + item.setElementGroup( null ); + this.items.splice( index, 1 ); + item.$element.detach(); + } + } + + this.emit( 'change', this.getItems() ); + return this; +}; + +/** + * Clear all items from the group. + * + * Cleared items are detached from the DOM, not removed, so that they may be reused. + * To remove only a subset of items from a group, use the #removeItems method. + * + * @chainable */ OO.ui.mixin.GroupElement.prototype.clearItems = function () { - var i, len, item; + var i, len, item, remove, itemEvent; + // Remove all items for ( i = 0, len = this.items.length; i < len; i++ ) { item = this.items[ i ]; + if ( + item.connect && item.disconnect && + !$.isEmptyObject( this.aggregateItemEvents ) + ) { + remove = {}; + if ( Object.prototype.hasOwnProperty.call( this.aggregateItemEvents, itemEvent ) ) { + remove[ itemEvent ] = [ 'emit', this.aggregateItemEvents[ itemEvent ], item ]; + } + item.disconnect( this, remove ); + } item.setElementGroup( null ); item.$element.detach(); } - // Mixin method - OO.EmitterList.prototype.clearItems.call( this ); - - // Event this.emit( 'change', this.getItems() ); - + this.items = []; return this; }; -- To view, visit https://gerrit.wikimedia.org/r/306296 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I2cb6e4722f3ac7eaf38c2142f771acdae2bdddec Gerrit-PatchSet: 1 Gerrit-Project: oojs/ui Gerrit-Branch: master Gerrit-Owner: Jforrester <jforres...@wikimedia.org> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits