diff options
Diffstat (limited to 'vendor/oojs/oojs-ui/src/elements/GroupElement.js')
-rw-r--r-- | vendor/oojs/oojs-ui/src/elements/GroupElement.js | 290 |
1 files changed, 290 insertions, 0 deletions
diff --git a/vendor/oojs/oojs-ui/src/elements/GroupElement.js b/vendor/oojs/oojs-ui/src/elements/GroupElement.js new file mode 100644 index 00000000..51cf5d25 --- /dev/null +++ b/vendor/oojs/oojs-ui/src/elements/GroupElement.js @@ -0,0 +1,290 @@ +/** + * 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 `<div>`. + */ +OO.ui.GroupElement = function OoUiGroupElement( config ) { + // Configuration initialization + config = config || {}; + + // Properties + this.$group = null; + this.items = []; + this.aggregateItemEvents = {}; + + // Initialization + this.setGroupElement( config.$group || $( '<div>' ) ); +}; + +/* 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.<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.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; +}; |