/**
* 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;
};