| Current File : /home/jvzmxxx/wiki1/extensions/Wikibase/view/resources/jquery/wikibase/jquery.wikibase.listview.js |
( function( $ ) {
'use strict';
var PARENT = $.ui.TemplatedWidget;
/**
* View for displaying and editing list items, each represented by a single random widget.
* @class jQuery.wikibase.listview
* @extends jQuery.ui.TemplatedWidget
* @since 0.4
* @license GPL-2.0+
* @author Daniel Werner < daniel.werner@wikimedia.de >
* @author H. Snater < mediawiki@snater.com >
*
* @constructor
*
* @param {Object} options
* @param {*[]} [options.value=null]
* The values displayed by this view. More specifically, a list of each list item widget's
* value.
* @param {jQuery.wikibase.listview.ListItemAdapter} options.listItemAdapter
* Interfaces the actual widget instances to be used by the `listview`. Cannot be changed
* after initialization.
* @param {string} [options.listItemNodeName='DIV']
* Node name of the base node of new list items.
*/
/**
* @event itemadded
* Triggered after a list item got added to the list.
* @param {jQuery.Event} event
* @param {*|null} value The value the new list item is representing. `null` for empty value.
* @param {jQuery} $li The DOM node of the widget representing the value.
*/
/**
* @event itemremoved
* Triggered after a list got removed from the list.
* @param {jQuery.Event} event
* @param {*|null} value The value of the list item which will be removed. `null` for empty value.
* @param {jQuery} $li The list item's DOM node that was removed.
*/
/**
* @event enternewitem
* Triggered when initializing the process of adding a new item to the list.
* @param {jQuery.Event} event
* @param {jQuery} $li The DOM node pending to be added permanently to the list.
*/
/**
* @event afteritemmove
* Triggered when an item node is moved within the list.
* @param {jQuery.Event} event
* @param {number} The item node's new index.
* @param {number} Number of items in the list.
*/
/**
* @event destroy
* Triggered when the widget has been destroyed.
* @param {jQuery.Event} event
*/
$.widget( 'wikibase.listview', PARENT, {
/**
* @inheritdoc
* @protected
*/
options: {
template: 'wikibase-listview',
templateParams: [
'' // list items
],
value: null,
listItemAdapter: null,
listItemNodeName: 'DIV'
},
/**
* Short-cut for `this.options.listItemAdapter`.
* @property {jQuery.wikibase.listview.ListItemAdapter}
* @private
*/
_lia: null,
/**
* The DOM elements this `listview`'s element contained when it was initialized. These DOM
* elements are reused in `this.addItem` until the array is empty.
* @property [HTMLElement[]]
* @private
*/
_reusedItems: [],
/**
* @inheritdoc
* @protected
*
* @throws {Error} if a required option is not specified properly.
*/
_create: function() {
this._lia = this.options.listItemAdapter;
if ( typeof this._lia !== 'object'
|| !( this._lia instanceof $.wikibase.listview.ListItemAdapter )
) {
throw new Error( 'Option "listItemAdapter" has to be an instance of '
+ 'jQuery.wikibase.listview.ListItemAdapter' );
}
this._reusedItems = $.makeArray( this.element.children( this.options.listItemNodeName ) );
PARENT.prototype._create.call( this );
this._createList();
},
/**
* @inheritdoc
*/
destroy: function() {
this._lia = null;
this._reusedItems = null;
PARENT.prototype.destroy.call( this );
this._trigger( 'destroy' );
},
/**
* @inheritdoc
* @protected
*
* @throws {Error} when trying to set `listItemAdapter` option.
*/
_setOption: function( key, value ) {
var self = this;
if ( key === 'listItemAdapter' ) {
throw new Error( 'Can not change the ListItemAdapter after initialization' );
} else if ( key === 'value' ) {
this.items().each( function() {
var $node = $( this );
self._lia.liInstance( $node ).destroy();
$node.remove();
} );
for ( var i = 0; i < value.length; i++ ) {
this._addLiValue( value[i] );
}
}
var response = PARENT.prototype._setOption.apply( this, arguments );
if ( key === 'disabled' ) {
this.items().each( function() {
var liInstance = self._lia.liInstance( $( this ) );
// Check if instance got destroyed in the meantime:
if ( liInstance ) {
liInstance.option( key, value );
}
} );
}
return response;
},
/**
* Fills the list element with DOM structure for each list item.
*
* @private
*/
_createList: function() {
var i, items = this.option( 'value' );
if ( items === null ) {
for ( i = this._reusedItems.length; i--; ) {
this.addItem( null );
}
} else {
for ( i in items ) {
this.addItem( items[i] );
}
}
},
/**
* Sets the widget's value or gets the widget's current value. The widget's non-pending value
* (the value the widget was initialized with) may be retrieved via `this.option( 'value' )`.
*
* @param {*[]} [value] List containing a value for each list item widget.
* @return {*[]|undefined}
*/
value: function( value ) {
if ( value !== undefined ) {
return this.option( 'value', value );
}
var self = this,
values = [];
this.items().each( function() {
values.push( self._lia.liInstance( $( this ) ) );
} );
return values;
},
/**
* Returns all list item nodes. The `listItemAdapter` may be used to retrieve the list item
* instance.
*
* @return {jQuery}
*/
items: function() {
return this.element.children( '.' + this.widgetName + '-item' );
},
/**
* Returns all list items which have a value not considered empty (not `null`).
*
* @return {jQuery}
*/
nonEmptyItems: function() {
var lia = this._lia;
return this.items().filter( function() {
var item = lia.liInstance( $( this ) );
return !!item.value();
} );
},
/**
* Returns the index of a given item node within the list managed by the `listview`. Returns
* `-1` if the node could not be found.
*
* @param {jQuery} $itemNode
* @return {number}
*/
indexOf: function( $itemNode ) {
var $items = this.items(),
itemNode = $itemNode.get( 0 );
for ( var i = 0; i < $items.length; i++ ) {
if ( $items.get( i ) === itemNode ) {
return i;
}
}
return -1;
},
/**
* Moves a list item to a new index.
*
* @param {jQuery} $itemNode
* @param {number} toIndex
*/
move: function( $itemNode, toIndex ) {
var currIndex = this.indexOf( $itemNode ),
items = this.items();
// No need to move if the item has the index already or if it should be moved to after the
// last item although it is at the end already:
if ( currIndex < 0
|| currIndex === toIndex
|| currIndex === items.length - 1 && toIndex >= items.length
) {
return;
}
if ( toIndex >= items.length ) {
$itemNode.insertAfter( items.last() );
} else if ( items.eq( toIndex ).prev().get( 0 ) === $itemNode.get( 0 ) ) {
// Item already is at the position it shall be moved to.
return;
} else {
$itemNode.insertBefore( items.eq( toIndex ) );
}
this._trigger( 'afteritemmove', null, [ this.indexOf( $itemNode ), items.length ] );
},
/**
* Moves an item node one index towards the top of the list.
*
* @param {jQuery} $itemNode
*/
moveUp: function( $itemNode ) {
if ( this.indexOf( $itemNode ) !== 0 ) {
this.move( $itemNode, this.indexOf( $itemNode ) - 1 );
}
},
/**
* Moves an item node one index towards the bottom of the list.
*
* @param {jQuery} $itemNode
*/
moveDown: function( $itemNode ) {
// Adding 2 to the index to move the element to before the element after the next element:
this.move( $itemNode, this.indexOf( $itemNode ) + 2 );
},
/**
* Returns the list item adapter object interfacing to this list's list items.
*
* @return {jQuery.wikibase.listview.ListItemAdapter}
*/
listItemAdapter: function() {
return this._lia;
},
/**
* Adds one list item into the list and renders it in the view.
*
* @param {*} liValue One list item widget's value.
* @return {jQuery} New list item's node.
*/
addItem: function( liValue ) {
var $li = this._addLiValue( liValue );
this._trigger( 'itemadded', null, [liValue, $li] );
return $li;
},
/**
* Adds one list item into the list and renders it in the view.
*
* @private
*
* @param {*} liValue One list item widget's value.
* @return {jQuery} New list item's node.
*/
_addLiValue: function( liValue ) {
var $newLi = this._reusedItems.length > 0
? $( this._reusedItems.shift() )
: $( '<' + this.option( 'listItemNodeName' ) + '/>' );
$newLi.addClass( this.widgetName + '-item' );
if ( !$newLi.parent( this.element ).length ) {
// Insert DOM first, to allow events bubbling up the DOM tree.
var items = this.items();
if ( items.length ) {
items.last().after( $newLi );
} else {
this.element.append( $newLi );
}
}
this._lia.newListItem( $newLi, liValue );
return $newLi;
},
/**
* Removes one list item from the list and renders the update in the view.
*
* @param {jQuery} $li The list item's node to be removed.
*
* @throws {Error} if the node provided is not a list item.
*/
removeItem: function( $li ) {
if ( !$li.parent( this.element ).length ) {
throw new Error( 'The given node is not an element in this list' );
}
var liValue = this._lia.liInstance( $li ).value();
this._lia.liInstance( $li ).destroy();
$li.remove();
this._trigger( 'itemremoved', null, [liValue, $li] );
},
/**
* Inserts a new list item into the list. The new list item will be a widget instance of the
* type set on the list, but without any value.
*
* @return {Object} jQuery.Promise
* @return {Function} return.done
* @return {jQuery} return.done.$newLi The new list item node. Use
* `listItemAdapter().liInstance( $newLi )` to receive the widget instance.
*/
enterNewItem: function() {
var $newLi = this.addItem();
this._trigger( 'enternewitem', null, [$newLi] );
return $.Deferred().resolve( $newLi ).promise();
},
/**
* @inheritdoc
*/
focus: function() {
var $items = this.items();
if ( $items.length ) {
var item = this._lia.liInstance( $items.first() );
if ( item.focus ) {
item.focus();
return;
}
}
this.element.focus();
}
} );
}( jQuery ) );