/** * Any OOjs UI widget that contains other widgets (such as {@link OO.ui.ButtonWidget buttons} or * {@link OO.ui.OptionWidget options}) mixes in GroupElement. Adding, removing, and clearing * items from the group is done through the interface the class provides. * For more information, please see the [OOjs UI documentation on MediaWiki] [1]. * * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Elements/Groups * * @abstract * @class * * @constructor * @param {Object} [config] Configuration options * @cfg {jQuery} [$group] The container element created by the class. If this configuration * is omitted, the group element will use a generated `
`. */ OO.ui.GroupElement = function OoUiGroupElement( config ) { // Configuration initialization config = config || {}; // Properties this.$group = null; this.items = []; this.aggregateItemEvents = {}; // Initialization this.setGroupElement( config.$group || $( '
' ) ); }; /* Methods */ /** * Set the group element. * * If an element is already set, items will be moved to the new element. * * @param {jQuery} $group Element to use as group */ OO.ui.GroupElement.prototype.setGroupElement = function ( $group ) { var i, len; this.$group = $group; 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.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.GroupElement.prototype.getItems = function () { return this.items.slice( 0 ); }; /** * Get an item by its data. * * Only the first item with matching data will be returned. To return all matching items, * use the #getItemsFromData method. * * @param {Object} data Item data to search for * @return {OO.ui.Element|null} Item with equivalent data, `null` if none exists */ OO.ui.GroupElement.prototype.getItemFromData = function ( data ) { var i, len, item, hash = OO.getHash( data ); for ( i = 0, len = this.items.length; i < len; i++ ) { item = this.items[ i ]; if ( hash === OO.getHash( item.getData() ) ) { return item; } } return null; }; /** * Get items by their data. * * All items with matching data will be returned. To return only the first match, use the #getItemFromData method instead. * * @param {Object} data Item data to search for * @return {OO.ui.Element[]} Items with equivalent data */ OO.ui.GroupElement.prototype.getItemsFromData = function ( data ) { var i, len, item, hash = OO.getHash( data ), items = []; for ( i = 0, len = this.items.length; i < len; i++ ) { item = this.items[ i ]; if ( hash === OO.getHash( item.getData() ) ) { items.push( item ); } } return items; }; /** * Aggregate the events emitted by the group. * * 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.} 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.GroupElement.prototype.aggregate = function ( events ) { var i, len, item, add, remove, itemEvent, groupEvent; for ( itemEvent in events ) { groupEvent = events[ itemEvent ]; // 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', groupEvent, item ]; item.disconnect( this, remove ); } } // Prevent future items from aggregating event delete this.aggregateItemEvents[ itemEvent ]; } // 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 ); } } } } }; /** * 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.GroupElement.prototype.addItems = function ( items, index ) { var i, len, item, event, events, currentIndex, itemElements = []; for ( i = 0, len = items.length; i < len; i++ ) { item = items[ i ]; // Check if item exists then remove it first, effectively "moving" it currentIndex = $.inArray( item, this.items ); 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 ( event in this.aggregateItemEvents ) { events[ event ] = [ 'emit', this.aggregateItemEvents[ event ], 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 ) ); } return this; }; /** * 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.GroupElement.prototype.removeItems = function ( items ) { var i, len, item, index, remove, itemEvent; // Remove specific items for ( i = 0, len = items.length; i < len; i++ ) { item = items[ i ]; index = $.inArray( item, this.items ); if ( index !== -1 ) { 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 ); this.items.splice( index, 1 ); item.$element.detach(); } } 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.GroupElement.prototype.clearItems = function () { 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(); } this.items = []; return this; };