123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026 |
- /*!
- * bootstrap-tokenfield
- * https://github.com/sliptree/bootstrap-tokenfield
- * Copyright 2013-2014 Sliptree and other contributors; Licensed MIT
- */
- (function (factory) {
- if (typeof define === 'function' && define.amd) {
- // AMD. Register as an anonymous module.
- define(['jquery'], factory);
- } else if (typeof exports === 'object') {
- // For CommonJS and CommonJS-like environments where a window with jQuery
- // is present, execute the factory with the jQuery instance from the window object
- // For environments that do not inherently posses a window with a document
- // (such as Node.js), expose a Tokenfield-making factory as module.exports
- // This accentuates the need for the creation of a real window or passing in a jQuery instance
- // e.g. require("bootstrap-tokenfield")(window); or require("bootstrap-tokenfield")($);
- module.exports = global.window && global.window.$ ?
- factory( global.window.$ ) :
- function( input ) {
- if ( !input.$ && !input.fn ) {
- throw new Error( "Tokenfield requires a window object with jQuery or a jQuery instance" );
- }
- return factory( input.$ || input );
- };
- } else {
- // Browser globals
- factory(jQuery);
- }
- }(function ($, window) {
- "use strict"; // jshint ;_;
- /* TOKENFIELD PUBLIC CLASS DEFINITION
- * ============================== */
- var Tokenfield = function (element, options) {
- var _self = this
- this.$element = $(element)
- this.textDirection = this.$element.css('direction');
- // Extend options
- this.options = $.extend(true, {}, $.fn.tokenfield.defaults, { tokens: this.$element.val() }, this.$element.data(), options)
-
- // Setup delimiters and trigger keys
- this._delimiters = (typeof this.options.delimiter === 'string') ? [this.options.delimiter] : this.options.delimiter
- this._triggerKeys = $.map(this._delimiters, function (delimiter) {
- return delimiter.charCodeAt(0);
- });
- this._firstDelimiter = this._delimiters[0];
- // Check for whitespace, dash and special characters
- var whitespace = $.inArray(' ', this._delimiters)
- , dash = $.inArray('-', this._delimiters)
- if (whitespace >= 0)
- this._delimiters[whitespace] = '\\s'
- if (dash >= 0) {
- delete this._delimiters[dash]
- this._delimiters.unshift('-')
- }
- var specialCharacters = ['\\', '$', '[', '{', '^', '.', '|', '?', '*', '+', '(', ')']
- $.each(this._delimiters, function (index, char) {
- var pos = $.inArray(char, specialCharacters)
- if (pos >= 0) _self._delimiters[index] = '\\' + char;
- });
- // Store original input width
- var elRules = (window && typeof window.getMatchedCSSRules === 'function') ? window.getMatchedCSSRules( element ) : null
- , elStyleWidth = element.style.width
- , elCSSWidth
- , elWidth = this.$element.width()
- if (elRules) {
- $.each( elRules, function (i, rule) {
- if (rule.style.width) {
- elCSSWidth = rule.style.width;
- }
- });
- }
- // Move original input out of the way
- var hidingPosition = $('body').css('direction') === 'rtl' ? 'right' : 'left',
- originalStyles = { position: this.$element.css('position') };
- originalStyles[hidingPosition] = this.$element.css(hidingPosition);
-
- this.$element
- .data('original-styles', originalStyles)
- .data('original-tabindex', this.$element.prop('tabindex'))
- .css('position', 'absolute')
- .css(hidingPosition, '-10000px')
- .prop('tabindex', -1)
- // Create a wrapper
- this.$wrapper = $('<div class="tokenfield form-control" />')
- if (this.$element.hasClass('input-lg')) this.$wrapper.addClass('input-lg')
- if (this.$element.hasClass('input-sm')) this.$wrapper.addClass('input-sm')
- if (this.textDirection === 'rtl') this.$wrapper.addClass('rtl')
- // Create a new input
- var id = this.$element.prop('id') || new Date().getTime() + '' + Math.floor((1 + Math.random()) * 100)
- this.$input = $('<input type="text" class="token-input" autocomplete="off" />')
- .appendTo( this.$wrapper )
- .prop( 'placeholder', this.$element.prop('placeholder') )
- .prop( 'id', id + '-tokenfield' )
- .prop( 'tabindex', this.$element.data('original-tabindex') )
- // Re-route original input label to new input
- var $label = $( 'label[for="' + this.$element.prop('id') + '"]' )
- if ( $label.length ) {
- $label.prop( 'for', this.$input.prop('id') )
- }
- // Set up a copy helper to handle copy & paste
- this.$copyHelper = $('<input type="text" />').css('position', 'absolute').css(hidingPosition, '-10000px').prop('tabindex', -1).prependTo( this.$wrapper )
-
- // Set wrapper width
- if (elStyleWidth) {
- this.$wrapper.css('width', elStyleWidth);
- }
- else if (elCSSWidth) {
- this.$wrapper.css('width', elCSSWidth);
- }
- // If input is inside inline-form with no width set, set fixed width
- else if (this.$element.parents('.form-inline').length) {
- this.$wrapper.width( elWidth )
- }
- // Set tokenfield disabled, if original or fieldset input is disabled
- if (this.$element.prop('disabled') || this.$element.parents('fieldset[disabled]').length) {
- this.disable();
- }
- // Set tokenfield readonly, if original input is readonly
- if (this.$element.prop('readonly')) {
- this.readonly();
- }
- // Set up mirror for input auto-sizing
- this.$mirror = $('<span style="position:absolute; top:-999px; left:0; white-space:pre;"/>');
- this.$input.css('min-width', this.options.minWidth + 'px')
- $.each([
- 'fontFamily',
- 'fontSize',
- 'fontWeight',
- 'fontStyle',
- 'letterSpacing',
- 'textTransform',
- 'wordSpacing',
- 'textIndent'
- ], function (i, val) {
- _self.$mirror[0].style[val] = _self.$input.css(val);
- });
- this.$mirror.appendTo( 'body' )
- // Insert tokenfield to HTML
- this.$wrapper.insertBefore( this.$element )
- this.$element.prependTo( this.$wrapper )
- // Calculate inner input width
- this.update()
-
- // Create initial tokens, if any
- this.setTokens(this.options.tokens, false, false)
- // Start listening to events
- this.listen()
- // Initialize autocomplete, if necessary
- if ( ! $.isEmptyObject( this.options.autocomplete ) ) {
- var side = this.textDirection === 'rtl' ? 'right' : 'left'
- , autocompleteOptions = $.extend({
- minLength: this.options.showAutocompleteOnFocus ? 0 : null,
- position: { my: side + " top", at: side + " bottom", of: this.$wrapper }
- }, this.options.autocomplete )
-
- this.$input.autocomplete( autocompleteOptions )
- }
- // Initialize typeahead, if necessary
- if ( ! $.isEmptyObject( this.options.typeahead ) ) {
-
- var typeaheadOptions = this.options.typeahead
- , defaults = {
- minLength: this.options.showAutocompleteOnFocus ? 0 : null
- }
- , args = $.isArray( typeaheadOptions ) ? typeaheadOptions : [typeaheadOptions, typeaheadOptions]
-
- args[0] = $.extend( {}, defaults, args[0] )
- this.$input.typeahead.apply( this.$input, args )
- this.typeahead = true
- }
- this.$element.trigger('tokenfield:initialize')
- }
- Tokenfield.prototype = {
- constructor: Tokenfield
- , createToken: function (attrs, triggerChange) {
- var _self = this
- if (typeof attrs === 'string') {
- attrs = { value: attrs, label: attrs }
- }
- if (typeof triggerChange === 'undefined') {
- triggerChange = true
- }
- // Normalize label and value
- attrs.value = $.trim(attrs.value);
- attrs.label = attrs.label && attrs.label.length ? $.trim(attrs.label) : attrs.value
- // Bail out if has no value or label, or label is too short
- if (!attrs.value.length || !attrs.label.length || attrs.label.length <= this.options.minLength) return
- // Bail out if maximum number of tokens is reached
- if (this.options.limit && this.getTokens().length >= this.options.limit) return
- // Allow changing token data before creating it
- var createEvent = $.Event('tokenfield:createtoken', { attrs: attrs })
- this.$element.trigger(createEvent)
- // Bail out if there if attributes are empty or event was defaultPrevented
- if (!createEvent.attrs || createEvent.isDefaultPrevented()) return
- var $token = $('<div class="token" />')
- .attr('data-value', attrs.value)
- .append('<span class="token-label" />')
- .append('<a href="#" class="close" tabindex="-1">×</a>')
- // Insert token into HTML
- if (this.$input.hasClass('tt-input')) {
- // If the input has typeahead enabled, insert token before it's parent
- this.$input.parent().before( $token )
- } else {
- this.$input.before( $token )
- }
- // Temporarily set input width to minimum
- this.$input.css('width', this.options.minWidth + 'px')
- var $tokenLabel = $token.find('.token-label')
- , $closeButton = $token.find('.close')
- // Determine maximum possible token label width
- if (!this.maxTokenWidth) {
- this.maxTokenWidth =
- this.$wrapper.width() - $closeButton.outerWidth() -
- parseInt($closeButton.css('margin-left'), 10) -
- parseInt($closeButton.css('margin-right'), 10) -
- parseInt($token.css('border-left-width'), 10) -
- parseInt($token.css('border-right-width'), 10) -
- parseInt($token.css('padding-left'), 10) -
- parseInt($token.css('padding-right'), 10)
- parseInt($tokenLabel.css('border-left-width'), 10) -
- parseInt($tokenLabel.css('border-right-width'), 10) -
- parseInt($tokenLabel.css('padding-left'), 10) -
- parseInt($tokenLabel.css('padding-right'), 10)
- parseInt($tokenLabel.css('margin-left'), 10) -
- parseInt($tokenLabel.css('margin-right'), 10)
- }
- $tokenLabel
- .text(attrs.label)
- .css('max-width', this.maxTokenWidth)
- // Listen to events on token
- $token
- .on('mousedown', function (e) {
- if (_self._disabled || _self._readonly) return false
- _self.preventDeactivation = true
- })
- .on('click', function (e) {
- if (_self._disabled || _self._readonly) return false
- _self.preventDeactivation = false
- if (e.ctrlKey || e.metaKey) {
- e.preventDefault()
- return _self.toggle( $token )
- }
-
- _self.activate( $token, e.shiftKey, e.shiftKey )
- })
- .on('dblclick', function (e) {
- if (_self._disabled || _self._readonly || !_self.options.allowEditing ) return false
- _self.edit( $token )
- })
- $closeButton
- .on('click', $.proxy(this.remove, this))
- // Trigger createdtoken event on the original field
- // indicating that the token is now in the DOM
- this.$element.trigger($.Event('tokenfield:createdtoken', {
- attrs: attrs,
- relatedTarget: $token.get(0)
- }))
- // Trigger change event on the original field
- if (triggerChange) {
- this.$element.val( this.getTokensList() ).trigger( $.Event('change', { initiator: 'tokenfield' }) )
- }
- // Update tokenfield dimensions
- this.update()
- // Return original element
- return this.$element.get(0)
- }
- , setTokens: function (tokens, add, triggerChange) {
- if (!tokens) return
- if (!add) this.$wrapper.find('.token').remove()
- if (typeof triggerChange === 'undefined') {
- triggerChange = true
- }
- if (typeof tokens === 'string') {
- if (this._delimiters.length) {
- // Split based on delimiters
- tokens = tokens.split( new RegExp( '[' + this._delimiters.join('') + ']' ) )
- } else {
- tokens = [tokens];
- }
- }
- var _self = this
- $.each(tokens, function (i, attrs) {
- _self.createToken(attrs, triggerChange)
- })
- return this.$element.get(0)
- }
- , getTokenData: function($token) {
- var data = $token.map(function() {
- var $token = $(this);
- return {
- value: $token.attr('data-value'),
- label: $token.find('.token-label').text()
- }
- }).get();
- if (data.length == 1) {
- data = data[0];
- }
- return data;
- }
- , getTokens: function(active) {
- var self = this
- , tokens = []
- , activeClass = active ? '.active' : '' // get active tokens only
- this.$wrapper.find( '.token' + activeClass ).each( function() {
- tokens.push( self.getTokenData( $(this) ) )
- })
- return tokens
- }
- , getTokensList: function(delimiter, beautify, active) {
- delimiter = delimiter || this._firstDelimiter
- beautify = ( typeof beautify !== 'undefined' && beautify !== null ) ? beautify : this.options.beautify
-
- var separator = delimiter + ( beautify && delimiter !== ' ' ? ' ' : '')
- return $.map( this.getTokens(active), function (token) {
- return token.value
- }).join(separator)
- }
- , getInput: function() {
- return this.$input.val()
- }
- , listen: function () {
- var _self = this
- this.$element
- .on('change', $.proxy(this.change, this))
- this.$wrapper
- .on('mousedown',$.proxy(this.focusInput, this))
- this.$input
- .on('focus', $.proxy(this.focus, this))
- .on('blur', $.proxy(this.blur, this))
- .on('paste', $.proxy(this.paste, this))
- .on('keydown', $.proxy(this.keydown, this))
- .on('keypress', $.proxy(this.keypress, this))
- .on('keyup', $.proxy(this.keyup, this))
- this.$copyHelper
- .on('focus', $.proxy(this.focus, this))
- .on('blur', $.proxy(this.blur, this))
- .on('keydown', $.proxy(this.keydown, this))
- .on('keyup', $.proxy(this.keyup, this))
- // Secondary listeners for input width calculation
- this.$input
- .on('keypress', $.proxy(this.update, this))
- .on('keyup', $.proxy(this.update, this))
- this.$input
- .on('autocompletecreate', function() {
- // Set minimum autocomplete menu width
- var $_menuElement = $(this).data('ui-autocomplete').menu.element
-
- var minWidth = _self.$wrapper.outerWidth() -
- parseInt( $_menuElement.css('border-left-width'), 10 ) -
- parseInt( $_menuElement.css('border-right-width'), 10 )
- $_menuElement.css( 'min-width', minWidth + 'px' )
- })
- .on('autocompleteselect', function (e, ui) {
- if (_self.createToken( ui.item )) {
- _self.$input.val('')
- if (_self.$input.data( 'edit' )) {
- _self.unedit(true)
- }
- }
- return false
- })
- .on('typeahead:selected typeahead:autocompleted', function (e, datum, dataset) {
- // Create token
- if (_self.createToken( datum )) {
- _self.$input.typeahead('val', '')
- if (_self.$input.data( 'edit' )) {
- _self.unedit(true)
- }
- }
- })
- // Listen to window resize
- $(window).on('resize', $.proxy(this.update, this ))
- }
- , keydown: function (e) {
- if (!this.focused) return
- var _self = this
- switch(e.keyCode) {
- case 8: // backspace
- if (!this.$input.is(document.activeElement)) break
- this.lastInputValue = this.$input.val()
- break
- case 37: // left arrow
- leftRight( this.textDirection === 'rtl' ? 'next': 'prev' )
- break
- case 38: // up arrow
- upDown('prev')
- break
- case 39: // right arrow
- leftRight( this.textDirection === 'rtl' ? 'prev': 'next' )
- break
- case 40: // down arrow
- upDown('next')
- break
- case 65: // a (to handle ctrl + a)
- if (this.$input.val().length > 0 || !(e.ctrlKey || e.metaKey)) break
- this.activateAll()
- e.preventDefault()
- break
- case 9: // tab
- case 13: // enter
- // We will handle creating tokens from autocomplete in autocomplete events
- if (this.$input.data('ui-autocomplete') && this.$input.data('ui-autocomplete').menu.element.find("li:has(a.ui-state-focus)").length) break
-
- // We will handle creating tokens from typeahead in typeahead events
- if (this.$input.hasClass('tt-input') && this.$wrapper.find('.tt-cursor').length ) break
- if (this.$input.hasClass('tt-input') && this.$wrapper.find('.tt-hint').val().length) break
-
- // Create token
- if (this.$input.is(document.activeElement) && this.$input.val().length || this.$input.data('edit')) {
- return this.createTokensFromInput(e, this.$input.data('edit'));
- }
- // Edit token
- if (e.keyCode === 13) {
- if (!this.$copyHelper.is(document.activeElement) || this.$wrapper.find('.token.active').length !== 1) break
- if (!_self.options.allowEditing) break
- this.edit( this.$wrapper.find('.token.active') )
- }
- }
- function leftRight(direction) {
- if (_self.$input.is(document.activeElement)) {
- if (_self.$input.val().length > 0) return
- direction += 'All'
- var $token = _self.$input.hasClass('tt-input') ? _self.$input.parent()[direction]('.token:first') : _self.$input[direction]('.token:first')
- if (!$token.length) return
- _self.preventInputFocus = true
- _self.preventDeactivation = true
- _self.activate( $token )
- e.preventDefault()
- } else {
- _self[direction]( e.shiftKey )
- e.preventDefault()
- }
- }
- function upDown(direction) {
- if (!e.shiftKey) return
- if (_self.$input.is(document.activeElement)) {
- if (_self.$input.val().length > 0) return
- var $token = _self.$input.hasClass('tt-input') ? _self.$input.parent()[direction + 'All']('.token:first') : _self.$input[direction + 'All']('.token:first')
- if (!$token.length) return
- _self.activate( $token )
- }
- var opposite = direction === 'prev' ? 'next' : 'prev'
- , position = direction === 'prev' ? 'first' : 'last'
- _self.firstActiveToken[opposite + 'All']('.token').each(function() {
- _self.deactivate( $(this) )
- })
- _self.activate( _self.$wrapper.find('.token:' + position), true, true )
- e.preventDefault()
- }
- this.lastKeyDown = e.keyCode
- }
- , keypress: function(e) {
- this.lastKeyPressCode = e.keyCode
- this.lastKeyPressCharCode = e.charCode
- // Comma
- if ($.inArray( e.charCode, this._triggerKeys) !== -1 && this.$input.is(document.activeElement)) {
- if (this.$input.val()) {
- this.createTokensFromInput(e)
- }
- return false;
- }
- }
- , keyup: function (e) {
- this.preventInputFocus = false
- if (!this.focused) return
- switch(e.keyCode) {
- case 8: // backspace
- if (this.$input.is(document.activeElement)) {
- if (this.$input.val().length || this.lastInputValue.length && this.lastKeyDown === 8) break
-
- this.preventDeactivation = true
- var $prevToken = this.$input.hasClass('tt-input') ? this.$input.parent().prevAll('.token:first') : this.$input.prevAll('.token:first')
- if (!$prevToken.length) break
- this.activate( $prevToken )
- } else {
- this.remove(e)
- }
- break
- case 46: // delete
- this.remove(e, 'next')
- break
- }
- this.lastKeyUp = e.keyCode
- }
- , focus: function (e) {
- this.focused = true
- this.$wrapper.addClass('focus')
- if (this.$input.is(document.activeElement)) {
- this.$wrapper.find('.active').removeClass('active')
- this.$firstActiveToken = null
- if (this.options.showAutocompleteOnFocus) {
- this.search()
- }
- }
- }
- , blur: function (e) {
- this.focused = false
- this.$wrapper.removeClass('focus')
- if (!this.preventDeactivation && !this.$element.is(document.activeElement)) {
- this.$wrapper.find('.active').removeClass('active')
- this.$firstActiveToken = null
- }
- if (!this.preventCreateTokens && (this.$input.data('edit') && !this.$input.is(document.activeElement) || this.options.createTokensOnBlur )) {
- this.createTokensFromInput(e)
- }
-
- this.preventDeactivation = false
- this.preventCreateTokens = false
- }
- , paste: function (e) {
- var _self = this
-
- // Add tokens to existing ones
- setTimeout(function () {
- _self.createTokensFromInput(e)
- }, 1)
- }
- , change: function (e) {
- if ( e.initiator === 'tokenfield' ) return // Prevent loops
-
- this.setTokens( this.$element.val() )
- }
- , createTokensFromInput: function (e, focus) {
- if (this.$input.val().length < this.options.minLength)
- return // No input, simply return
- var tokensBefore = this.getTokensList()
- this.setTokens( this.$input.val(), true )
-
- if (tokensBefore == this.getTokensList() && this.$input.val().length)
- return false // No tokens were added, do nothing (prevent form submit)
- if (this.$input.hasClass('tt-input')) {
- // Typeahead acts weird when simply setting input value to empty,
- // so we set the query to empty instead
- this.$input.typeahead('val', '')
- } else {
- this.$input.val('')
- }
- if (this.$input.data( 'edit' )) {
- this.unedit(focus)
- }
- return false // Prevent form being submitted
- }
- , next: function (add) {
- if (add) {
- var $firstActiveToken = this.$wrapper.find('.active:first')
- , deactivate = $firstActiveToken && this.$firstActiveToken ? $firstActiveToken.index() < this.$firstActiveToken.index() : false
- if (deactivate) return this.deactivate( $firstActiveToken )
- }
- var $lastActiveToken = this.$wrapper.find('.active:last')
- , $nextToken = $lastActiveToken.nextAll('.token:first')
- if (!$nextToken.length) {
- this.$input.focus()
- return
- }
- this.activate($nextToken, add)
- }
- , prev: function (add) {
- if (add) {
- var $lastActiveToken = this.$wrapper.find('.active:last')
- , deactivate = $lastActiveToken && this.$firstActiveToken ? $lastActiveToken.index() > this.$firstActiveToken.index() : false
- if (deactivate) return this.deactivate( $lastActiveToken )
- }
- var $firstActiveToken = this.$wrapper.find('.active:first')
- , $prevToken = $firstActiveToken.prevAll('.token:first')
- if (!$prevToken.length) {
- $prevToken = this.$wrapper.find('.token:first')
- }
- if (!$prevToken.length && !add) {
- this.$input.focus()
- return
- }
- this.activate( $prevToken, add )
- }
- , activate: function ($token, add, multi, remember) {
- if (!$token) return
- if (typeof remember === 'undefined') var remember = true
- if (multi) var add = true
- this.$copyHelper.focus()
- if (!add) {
- this.$wrapper.find('.active').removeClass('active')
- if (remember) {
- this.$firstActiveToken = $token
- } else {
- delete this.$firstActiveToken
- }
- }
- if (multi && this.$firstActiveToken) {
- // Determine first active token and the current tokens indicies
- // Account for the 1 hidden textarea by subtracting 1 from both
- var i = this.$firstActiveToken.index() - 2
- , a = $token.index() - 2
- , _self = this
- this.$wrapper.find('.token').slice( Math.min(i, a) + 1, Math.max(i, a) ).each( function() {
- _self.activate( $(this), true )
- })
- }
- $token.addClass('active')
- this.$copyHelper.val( this.getTokensList( null, null, true ) ).select()
- }
- , activateAll: function() {
- var _self = this
- this.$wrapper.find('.token').each( function (i) {
- _self.activate($(this), i !== 0, false, false)
- })
- }
- , deactivate: function($token) {
- if (!$token) return
- $token.removeClass('active')
- this.$copyHelper.val( this.getTokensList( null, null, true ) ).select()
- }
- , toggle: function($token) {
- if (!$token) return
- $token.toggleClass('active')
- this.$copyHelper.val( this.getTokensList( null, null, true ) ).select()
- }
- , edit: function ($token) {
- if (!$token) return
- var attrs = {
- value: $token.data('value'),
- label: $token.find('.token-label').text()
- }
- // Allow changing input value before editing
- var options = { attrs: attrs, relatedTarget: $token.get(0) }
- var editEvent = $.Event('tokenfield:edittoken', options)
- this.$element.trigger( editEvent )
-
- // Edit event can be cancelled if default is prevented
- if (editEvent.isDefaultPrevented()) return
- $token.find('.token-label').text(attrs.value)
- var tokenWidth = $token.outerWidth()
- var $_input = this.$input.hasClass('tt-input') ? this.$input.parent() : this.$input
- $token.replaceWith( $_input )
- this.preventCreateTokens = true
- this.$input.val( attrs.value )
- .select()
- .data( 'edit', true )
- .width( tokenWidth )
- this.update();
- // Indicate that token in snow being edited, and is replaced with an input field in the DOM
- this.$element.trigger($.Event('tokenfield:editedtoken', options ))
- }
- , unedit: function (focus) {
- var $_input = this.$input.hasClass('tt-input') ? this.$input.parent() : this.$input
- $_input.appendTo( this.$wrapper )
-
- this.$input.data('edit', false)
- this.$mirror.text('')
- this.update()
- // Because moving the input element around in DOM
- // will cause it to lose focus, we provide an option
- // to re-focus the input after appending it to the wrapper
- if (focus) {
- var _self = this
- setTimeout(function () {
- _self.$input.focus()
- }, 1)
- }
- }
- , remove: function (e, direction) {
- if (this.$input.is(document.activeElement) || this._disabled || this._readonly) return
- var $token = (e.type === 'click') ? $(e.target).closest('.token') : this.$wrapper.find('.token.active')
-
- if (e.type !== 'click') {
- if (!direction) var direction = 'prev'
- this[direction]()
- // Was it the first token?
- if (direction === 'prev') var firstToken = $token.first().prevAll('.token:first').length === 0
- }
- // Prepare events and their options
- var options = { attrs: this.getTokenData( $token ), relatedTarget: $token.get(0) }
- , removeEvent = $.Event('tokenfield:removetoken', options)
-
- this.$element.trigger(removeEvent);
- // Remove event can be intercepted and cancelled
- if (removeEvent.isDefaultPrevented()) return
- var removedEvent = $.Event('tokenfield:removedtoken', options)
- , changeEvent = $.Event('change', { initiator: 'tokenfield' })
- // Remove token from DOM
- $token.remove()
- // Trigger events
- this.$element.val( this.getTokensList() ).trigger( removedEvent ).trigger( changeEvent )
- // Focus, when necessary:
- // When there are no more tokens, or if this was the first token
- // and it was removed with backspace or it was clicked on
- if (!this.$wrapper.find('.token').length || e.type === 'click' || firstToken) this.$input.focus()
- // Adjust input width
- this.$input.css('width', this.options.minWidth + 'px')
- this.update()
- // Cancel original event handlers
- e.preventDefault()
- e.stopPropagation()
- }
- /**
- * Update tokenfield dimensions
- */
- , update: function (e) {
- var value = this.$input.val()
- , inputPaddingLeft = parseInt(this.$input.css('padding-left'), 10)
- , inputPaddingRight = parseInt(this.$input.css('padding-right'), 10)
- , inputPadding = inputPaddingLeft + inputPaddingRight
- if (this.$input.data('edit')) {
- if (!value) {
- value = this.$input.prop("placeholder")
- }
- if (value === this.$mirror.text()) return
- this.$mirror.text(value)
-
- var mirrorWidth = this.$mirror.width() + 10;
- if ( mirrorWidth > this.$wrapper.width() ) {
- return this.$input.width( this.$wrapper.width() )
- }
- this.$input.width( mirrorWidth )
- }
- else {
- this.$input.css( 'width', this.options.minWidth + 'px' )
- if (this.textDirection === 'rtl') {
- return this.$input.width( this.$input.offset().left + this.$input.outerWidth() - this.$wrapper.offset().left - parseInt(this.$wrapper.css('padding-left'), 10) - inputPadding - 1 )
- }
- this.$input.width( this.$wrapper.offset().left + this.$wrapper.width() + parseInt(this.$wrapper.css('padding-left'), 10) - this.$input.offset().left - inputPadding )
- }
- }
- , focusInput: function (e) {
- if ( $(e.target).closest('.token').length || $(e.target).closest('.token-input').length || $(e.target).closest('.tt-dropdown-menu').length ) return
- // Focus only after the current call stack has cleared,
- // otherwise has no effect.
- // Reason: mousedown is too early - input will lose focus
- // after mousedown. However, since the input may be moved
- // in DOM, there may be no click or mouseup event triggered.
- var _self = this
- setTimeout(function() {
- _self.$input.focus()
- }, 0)
- }
- , search: function () {
- if ( this.$input.data('ui-autocomplete') ) {
- this.$input.autocomplete('search')
- }
- }
- , disable: function () {
- this.setProperty('disabled', true);
- }
- , enable: function () {
- this.setProperty('disabled', false);
- }
- , readonly: function () {
- this.setProperty('readonly', true);
- }
- , writeable: function () {
- this.setProperty('readonly', false);
- }
- , setProperty: function(property, value) {
- this['_' + property] = value;
- this.$input.prop(property, value);
- this.$element.prop(property, value);
- this.$wrapper[ value ? 'addClass' : 'removeClass' ](property);
- }
- , destroy: function() {
- // Set field value
- this.$element.val( this.getTokensList() );
- // Restore styles and properties
- this.$element.css( this.$element.data('original-styles') );
- this.$element.prop( 'tabindex', this.$element.data('original-tabindex') );
-
- // Re-route tokenfield labele to original input
- var $label = $( 'label[for="' + this.$input.prop('id') + '"]' )
- if ( $label.length ) {
- $label.prop( 'for', this.$element.prop('id') )
- }
- // Move original element outside of tokenfield wrapper
- this.$element.insertBefore( this.$wrapper );
- // Remove tokenfield-related data
- this.$element.removeData('original-styles')
- .removeData('original-tabindex')
- .removeData('bs.tokenfield');
- // Remove tokenfield from DOM
- this.$wrapper.remove();
- var $_element = this.$element;
- delete this;
- return $_element;
- }
- }
- /* TOKENFIELD PLUGIN DEFINITION
- * ======================== */
- var old = $.fn.tokenfield
- $.fn.tokenfield = function (option, param) {
- var value
- , args = []
-
- Array.prototype.push.apply( args, arguments );
- var elements = this.each(function () {
- var $this = $(this)
- , data = $this.data('bs.tokenfield')
- , options = typeof option == 'object' && option
- if (typeof option === 'string' && data && data[option]) {
- args.shift()
- value = data[option].apply(data, args)
- } else {
- if (!data && typeof option !== 'string' && !param) $this.data('bs.tokenfield', (data = new Tokenfield(this, options)))
- }
- })
- return typeof value !== 'undefined' ? value : elements;
- }
- $.fn.tokenfield.defaults = {
- minWidth: 60,
- minLength: 0,
- allowEditing: true,
- limit: 0,
- autocomplete: {},
- typeahead: {},
- showAutocompleteOnFocus: false,
- createTokensOnBlur: false,
- delimiter: ',',
- beautify: true
- }
- $.fn.tokenfield.Constructor = Tokenfield
- /* TOKENFIELD NO CONFLICT
- * ================== */
- $.fn.tokenfield.noConflict = function () {
- $.fn.tokenfield = old
- return this
- }
- return Tokenfield;
- }));
|