| Current File : /home/jvzmxxx/wiki1/extensions/Wikibase/view/resources/jquery/ui/jquery.ui.tagadata.js |
/**
* 'tag-a-data' jQuery widget
*
* 'tag-a-data' is based on the original jQuery extension 'tag-it' v2.0 (06/2011) by
* Levy Carneiro Jr.
* Martin Rehfeld
* Tobias Schmidt
* Skylar Challand
* Alex Ehlke
* See http://aehlke.github.com/tag-it/ for details.
*
* Copyright 2011, Levy Carneiro Jr.
* Released under the MIT license.
* http://aehlke.github.com/tag-it/LICENSE
*
* @license MIT license
* @author Daniel Werner < daniel.werner@wikimedia.de >
* @author H. Snater < mediawiki@snater.com >
*
* @option {string} [inputName]
* Name of the "name" attribute assigned to the input elements.
* Default: 'entity'
*
* @option {boolean} [editableTags]
* Defines whether the tags can be altered at all times. If true, the tags contain input
* boxes, so it can be tabbed over them or clicked inside to alter the value.
* Default: true
*
* @option {boolean} [caseSensitive]
* If true, tags with the same text but different capitalization can be inserted.
* Default: true
*
* @option {string} [placeholderText]
* Text used as placeholder in the input field if no text has been typed yet.
* Default: ''
*
* @option {boolean} [anmiate]
* Whether to animate tag removals or not.
* Default: true
*
* @option {number[]} [triggerKeys]
* Keys which - when pressed in the input area - will trigger the current input to be added
* as tag. $.ui.keyCode members can be used for convenience.
* Default: []
*
* @event tagclicked
* Triggered when a tag is clicked.
* - {jQuery.Event}
* - {jQuery} $tag
*
* @event tagchanged
* Triggered when a tag's label is changed.
* - {jQuery.Event}
* - {jQuery} $tag
* - {string} Former label
*
* @event tagadded
* Triggered when a new tag featuring a value is added.
* - {jQuery.Event}
* - {jQuery} $tag
*
* @event taginserted
* Triggered when a new tag is added (event is triggered for empty tags as well).
* - {jQuery.Event}
* - {jQuery} $tag
*
* @event beforetagremoved
* Triggered before a particular tag is removed from the list of tags.
* - {jQuery.Event}
* - {jQuery} $tag
*
* @event tagremoved
* Triggered after a tag has been removed.
* - {jQuery.Event}
* - {jQuery} $tag
*
* @event helpertagadded
* Triggered when a helper tag is added.
* - {jQuery.Event}
* - {jQuery} $tag
*/
( function( $ ) {
'use strict';
$.widget( 'ui.tagadata', {
/**
* @see jQuery.Widget.options
*/
options: {
itemName: 'entity',
editableTags: true,
caseSensitive: true,
placeholderText: null,
animate: true,
triggerKeys: []
},
/**
* @type {jQuery}
*/
_$tagList: null,
/**
* @type {string[]}
*/
_initialTagLabels: null,
/**
* @see jQuery.Widget._create
*/
_create: function() {
var self = this;
this._$tagList = this.element.find( 'ul, ol' ).addBack().last();
this._initialTagLabels = [];
this._$tagList
.addClass( 'tagadata' )
.addClass( 'ui-widget ui-widget-content ui-corner-all' )
.on( 'click.tagadata', function( event ) {
var target = $( event.target );
if ( target.hasClass( 'tagadata-label' ) ) {
self._trigger( 'tagClicked', event, target.closest( '.tagadata-choice' ) );
}
} );
// Add existing tags from the list, if any
this._$tagList.children( 'li' ).each( function() {
var newTagLabel = $( this ).text();
if ( self._formatLabel( newTagLabel ) !== '' ) { // don't initialize empty tags here
var $newTag = self.createTag( newTagLabel, $( this ).attr( 'class' ) );
self._initialTagLabels.push( self.getTagLabel( $newTag ) );
$( this ).remove(); // remove empty tag
}
} );
// Create an empty input tag at the end:
this.getHelperTag();
},
/**
* @see jQuery.Widget.destroy
*/
destroy: function() {
var self = this;
this._$tagList
.removeClass( 'tagadata ui-widget ui-widget-content ui-corner-all tagadata-enabled '
+ 'tagadata-disabled' )
.off( '.tagadata' );
this._$tagList.children( 'li' ).each( function() {
var $tag = $( this ),
text = self.getTagLabel( $tag );
if ( text === '' ) {
$tag.remove(); // completely remove helper
} else {
$tag
.removeClass( 'tagadata-choice tagadata-choice-removed ui-widget-content '
+ 'ui-state-default ui-corner-all ui-state-highlight remove' )
.empty()
.off( '.' + this.widgetName )
.text( text );
$tag.find( 'input' ).off( '.' + this.widgetName );
}
} );
return $.Widget.prototype.destroy.call( this );
},
/**
* Returns the nodes of all tags currently assigned. To get the actual text, use
* getTagLabel() on them.
* Empty tags are not returned, getHelperTag() may be used to receive empty tags.
* If tags conflict (same tag exists twice) only one of the corresponding DOM nodes is
* returned.
*
* @return {jQuery}
*/
getTags: function() {
var self = this,
$tags = $(),
usedLabels = [];
this._$tagList.children( '.tagadata-choice' ).each( function() {
var $tag = $( this );
// Check if already removed but still assigned till animations end:
if ( !$tag.hasClass( 'tagadata-choice-removed' ) ) {
var tagLabel = self.getTagLabel( $tag );
if ( tagLabel !== '' && $.inArray( tagLabel, usedLabels ) === -1 ) {
$tags = $tags.add( this );
usedLabels.push( tagLabel );
}
}
} );
return $tags;
},
/**
* Returns a tag's element by its label. If the tag is not in the list, null will be returned.
*
* @param {string} label
* @return {jQuery|null}
*/
getTag: function( label ) {
var self = this,
result = null;
this._$tagList.children( '.tagadata-choice' ).each( function() {
var $tag = $( this );
if ( self._formatLabel( label ) === self._formatLabel( self.getTagLabel( $tag ) ) ) {
result = $tag;
}
return result === null;
} );
return result;
},
/**
* Helper function to return all tags currently having the same value.
*
* @param {string} label
* @return {jQuery}
*/
_getTags: function( label ) {
var self = this;
label = this._formatLabel( label );
return this._$tagList.children( '.tagadata-choice' ).filter( function() {
return self.getTagLabel( $( this ) ) === label;
} );
},
/**
* Returns the label of a tag represented by a DOM node.
*
* @param {jQuery} $tag
* @return {string}
*/
getTagLabel: function( $tag ) {
var $input = $tag.find( 'input[type=text]' );
return this._formatLabel(
$input.length ? $input.val() : $tag.find( '.tagadata-label' ).text()
);
},
/**
* @param {string} string
* @return {string}
*/
_formatLabel: function( string ) {
string = $.trim( string );
if ( this.options.caseSensitive ) {
return string;
}
return string.toLowerCase();
},
/**
* Highlights a tag for a short time.
*
* @param {jQuery} $tag
*/
highlightTag: function( $tag ) {
$tag.switchClass(
'',
'tagadata-choice-existing ui-state-highlight',
150,
'linear',
function() {
// also remove 'remove' class to avoid confusion
$tag.switchClass(
'tagadata-choice-existing ui-state-highlight remove',
'',
750,
'linear'
);
}
);
},
/**
* Adds a new tag to the list of tags. If the tag exists in the list already, the existing tag
* will be returned.
*
* @param {string} value
* @param {string} [additionalClasses]
* @param {boolean} [forceTextInput]
* @return {jQuery}
*/
createTag: function( value, additionalClasses, forceTextInput ) {
value = this._formatLabel( value );
var $tag = this.getTag( value );
if ( $tag ) {
if ( value !== '' ) {
// highlight the already existing tag, except if it is the new tag input
this.highlightTag( $tag );
}
return $tag;
}
var $label = $( '<span/>' ).addClass( 'tagadata-label' ),
$input = $( '<input />' )
.attr( 'name', this.options.itemName + '[]' )
.prop( 'lang', this.element.prop( 'lang' ) || '' )
.prop( 'dir', this.element.prop( 'dir' ) || 'auto' );
$tag = this._createTagNode().addClass( additionalClasses || '' ).append( $label );
if ( this.options.editableTags || forceTextInput ) {
$input.attr( {
type: 'text',
value: value,
'class': 'tagadata-label-text'
} )
.appendTo( $label );
this._initTagEvents( $tag );
} else {
// we need input only for the form to contain the data
$input.attr( {
type: 'hidden',
style: 'display:none;'
} )
.appendTo( $tag );
$label.text( value )
.addClass( 'tagadata-label-text' );
}
this._$tagList.append( $tag );
if ( value !== '' ) {
// only trigger if this isn't the helper tag
this._trigger( 'tagAdded', null, $tag );
}
this._trigger( 'tagInserted', null, $tag ); // event fired for both, helper and normal tags
return $tag;
},
/**
* @return {jQuery}
*/
_createTagNode: function() {
var self = this;
var $tag = $( '<li>' )
.addClass( 'tagadata-choice ui-widget-content ui-state-default ui-corner-all' );
var $removeTag = $( '<a><span class="text-icon">\xd7</span></a>' )// \xd7 is an X
.addClass( 'tagadata-close' )
.append( $( '<span/>' ).addClass( 'ui-icon ui-icon-close' ) )
.click( function() {
if ( !self.option( 'disabled' ) ) {
self.removeTag( $tag );
}
} )
.appendTo( $tag );
return $tag.append( $removeTag );
},
/**
* @param {jQuery} $tag
*/
_initTagEvents: function( $tag ) {
var self = this,
$input = $tag.find( 'input' );
$input
.on( 'focus.' + this.widgetName, function() {
$tag.addClass( 'tagadata-choice-active' );
} )
.on( 'blur.' + this.widgetName, function() {
// remove tag if it is empty already:
if ( self._formatLabel( $input.val() ) === ''
&& self.getTags().length > 1
&& !$tag.is( self.element.children( '.tagadata-choice' ).last() )
) {
self.removeTag( $tag );
}
} )
.on( 'eachchange.' + this.widgetName, function( event, oldValue ) {
// input change registered, check whether tag was really changed...
var oldNormalValue = self._formatLabel( oldValue ),
newNormalValue = self._formatLabel( $input.val() );
if ( oldNormalValue !== newNormalValue ) {
// trigger once for widget, once for tag itself
$tag.triggerHandler( self.widgetEventPrefix + 'tagchanged', oldNormalValue );
self._trigger( 'tagChanged', null, [$tag, oldNormalValue] );
}
} )
.on( 'keydown.' + this.widgetName, function( event ) {
if ( $.inArray( event.which, self.options.triggerKeys ) > -1 ) {
// Key for finishing tag input was hit (e.g. ENTER)
event.preventDefault();
var $targetTag = self.getHelperTag();
if ( self.getTagLabel( $tag ) === '' ) {
// Remove tag if hit ENTER on an empty tag, except for the helper tag.
if ( $targetTag.get( 0 ) !== $tag.get( 0 ) ) {
self.removeTag( $tag );
self.highlightTag( $targetTag );
}
}
$targetTag.find( 'input' ).focus();
}
} );
$tag
.on( this.widgetEventPrefix + 'tagchanged.' + this.widgetName,
function( event, oldValue ) {
var tagLabel = self.getTagLabel( $tag );
// Handle non-unique tags (conflicts):
var equalTags = self._getTags( oldValue ).add( $tag );
( equalTags.length <= 2
? equalTags // only two tags WERE equal, so the conflict is resolved for both
: $tag // the other nodes still have the conflict, but this one doesn't
).removeClass( 'tagadata-choice-equal' );
equalTags = tagLabel !== ''
? self._getTags( tagLabel )
: $(); // don't highlight anything if empty (will be removed anyhow)
if ( equalTags.length > 1 ) {
equalTags.addClass( 'tagadata-choice-equal' );
}
// if this is the tag before the helper and its value has just been emptied, remove
// it and jump into the helper:
if ( tagLabel === '' && self.getHelperTag().prev( $tag ).length ) {
self.removeTag( $tag );
self.getHelperTag().find( 'input' ).focus();
return;
}
// Check whether the tag is modified/new compared to initial state:
if ( $.inArray( tagLabel, self._initialTagLabels ) === -1 ) {
$tag.addClass( 'tagadata-choice-modified' );
} else {
$tag.removeClass( 'tagadata-choice-modified' );
}
}
);
},
/**
* Returns an empty tag at the end of the tag list. If none exists, a new one will be created.
*
* @return {jQuery}
*/
getHelperTag: function() {
var $tag = this._$tagList.find( '.tagadata-choice:last' );
if ( !$tag.length || this.getTagLabel( $tag ) !== '' ) {
$tag = this._createHelperTag();
}
$tag.appendTo( this._$tagList );
this._$tagList.children().removeClass( 'tagadata-choice-empty' );
$tag.addClass( 'tagadata-choice-empty' );
this._trigger( 'helperTagAdded', null, $tag );
return $tag;
},
/**
* @return {jQuery}
*/
_createHelperTag: function() {
var $tag = this.createTag( '', '', true ),
input = $tag.find( 'input' );
// Add placeholder and auto-expand afterwards:
if ( this.options.placeholderText ) {
input.attr( 'placeholder', this.options.placeholderText );
if ( input.inputautoexpand ) {
input.inputautoexpand( {
expandOnResize: false
} );
}
}
// Make sure a new helper will be created when something is inserted into helper:
var self = this;
var detectHelperInput = function() {
if ( self.getTagLabel( $tag ) !== '' ) {
// Remove placeholder.
// NOTE: Can't do this on blurring the input because when clicking a button, the
// click might fail because of the input box resizing.
if ( self.options.placeholderText && input.val() !== '' ) {
input.removeAttr( 'placeholder' );
if ( input.inputautoexpand ) {
input.inputautoexpand( {
expandOnResize: false
} );
}
}
$tag.removeClass( 'tagadata-choice-empty' );
self._trigger( 'tagAdded', null, $tag );
self.getHelperTag();
$tag.off( self.widgetEventPrefix + 'tagchanged', detectHelperInput );
}
};
$tag.on( this.widgetEventPrefix + 'tagchanged.' + this.widgetName, detectHelperInput );
return $tag;
},
/**
* Removes a tag.
*
* @param {jQuery} $tag
* @param {boolean} [animate]
* @return {boolean}
*/
removeTag: function( $tag, animate ) {
var self = this;
animate = animate || this.options.animate;
if ( !$tag.hasClass( 'tagadata-choice' )
|| !this._$tagList[0].contains( $tag[0] )
) {
return false;
}
this._trigger( 'beforeTagRemoved', null, $tag );
// Resolve label conflicts:
var equalTags = this._getTags( this.getTagLabel( $tag ) );
if ( equalTags.length === 2 ) {
equalTags.removeClass( 'tagadata-choice-equal' );
}
if ( animate ) {
$tag.addClass( 'tagadata-choice-removed' );
$tag.fadeOut( 'fast' ).hide( 'blind', { direction: 'horizontal' }, 'fast', function() {
$tag.remove();
self._trigger( 'tagRemoved', null, $tag );
} );
} else {
$tag.remove();
this._trigger( 'tagRemoved', null, $tag );
}
return true;
},
/**
* Removes all tags.
*/
removeAll: function() {
var self = this;
this._$tagList.children( '.tagadata-choice' ).each( function() {
self.removeTag( $( this ), false );
} );
},
/**
* Returns whether two tags conflict by containing the same text.
*
* @return {boolean}
*/
hasConflict: function() {
var self = this,
hasConflict = false;
this.getTags().each( function() {
var $tag = $( this ),
label = self.getTagLabel( $tag );
if ( self._getTags( label ).length > 1 ) {
hasConflict = true;
}
return hasConflict === false;
} );
return hasConflict;
},
/**
* @see jQuery.Widget._setOption
*/
_setOption: function( key, value ) {
if ( key === 'disabled' ) {
var $input = this._$tagList.find( 'input' );
if ( value ) {
this.getHelperTag().remove();
$input.blur();
} else {
// Create helper tag if it does not exist already:
this.getHelperTag();
}
$input.prop( 'disabled', value );
this._$tagList[value ? 'addClass' : 'removeClass']( 'tagadata-disabled' );
}
return $.Widget.prototype._setOption.apply( this, arguments );
}
} );
} )( jQuery );