/*! * 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, window); } }(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, character) { var pos = $.inArray(character, specialCharacters) if (pos >= 0) _self._delimiters[index] = '\\' + character; }); // 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 = $('
') 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 = $('') .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 = $('').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 = $(''); 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, ! this.$element.val() && this.options.tokens ) // 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 } } Tokenfield.prototype = { constructor: Tokenfield , createToken: function (attrs, triggerChange) { var _self = this if (typeof attrs === 'string') { attrs = { value: attrs, label: attrs } } else { // Copy objects to prevent contamination of data sources. attrs = $.extend( {}, attrs ) } if (typeof triggerChange === 'undefined') { triggerChange = true } // Normalize label and value attrs.value = $.trim(attrs.value.toString()); 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 = $('') .append('') .append('×') .data('attrs', attrs) // 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.css('max-width', this.maxTokenWidth) if (this.options.html) $tokenLabel.html(attrs.label) else $tokenLabel.text(attrs.label) // 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 var _self = this setTimeout(function () { _self.update() }, 0) // Return original element return this.$element.get(0) } , setTokens: function (tokens, add, triggerChange) { if (!add) this.$wrapper.find('.token').remove() if (!tokens) return 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 $token.data('attrs') }).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() } , setInput: function (val) { 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', val) } else { this.$input.val(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), li.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() && 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) { // Comma if ($.inArray( e.which, 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 if (_self.options.allowPasting) { 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) this.setInput('') 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 = $token.data('attrs') // 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 is now 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 { //temporary reset width to minimal value to get proper results this.$input.width(this.options.minWidth); var w = (this.textDirection === 'rtl') ? this.$input.offset().left + this.$input.outerWidth() - this.$wrapper.offset().left - parseInt(this.$wrapper.css('padding-left'), 10) - inputPadding - 1 : this.$wrapper.offset().left + this.$wrapper.width() + parseInt(this.$wrapper.css('padding-left'), 10) - this.$input.offset().left - inputPadding; // // some usecases pre-render widget before attaching to DOM, // dimensions returned by jquery will be NaN -> we default to 100% // so placeholder won't be cut off. isNaN(w) ? this.$input.width('100%') : this.$input.width(w); } } , 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 label 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(); this.$mirror.remove(); var $_element = this.$element; 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))) $this.trigger('tokenfield:initialize') } } }) return typeof value !== 'undefined' ? value : elements; } $.fn.tokenfield.defaults = { minWidth: 60, minLength: 0, html: true, allowEditing: true, allowPasting: true, limit: 0, autocomplete: {}, typeahead: {}, showAutocompleteOnFocus: false, createTokensOnBlur: false, delimiter: ',', beautify: true, inputType: 'text' } $.fn.tokenfield.Constructor = Tokenfield /* TOKENFIELD NO CONFLICT * ================== */ $.fn.tokenfield.noConflict = function () { $.fn.tokenfield = old return this } return Tokenfield; }));