UNPKG

rbgkew-bootstrap-tokenfield

Version:

Advanced tagging/tokenizing plugin for input fields with a focus on keyboard and copy-paste support.

1,006 lines (780 loc) 37.5 kB
/*! * bootstrap-tokenfield * https://github.com/Open-Xchange-Frontend/bootstrap-tokenfield * Copyright 2013-2016 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'; /* 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 elStyleWidth = element.style.width, elWidth = this.$element.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="' + this.options.inputType + '" 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 (this.$element.parents('.form-inline').length) { // If input is inside inline-form with no width set, set fixed width 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:-9999px;left:-9999px;white-space:pre;"/>'); this.$input.css('min-width', this.options.minWidth + 'px'); // Insert tokenfield to HTML this.$wrapper.insertBefore(this.$element); this.$element.prependTo(this.$wrapper); // Append mirror to tokenfield wrapper this.$mirror.appendTo(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 = $('<div class="token" />') .append('<span class="token-label" />') .append('<a href="#" class="close" tabindex="-1" aria-label="Remove">&times;</a>') .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'); 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 change event on the original field if (triggerChange) { this.$element.val(this.getTokensList()).trigger($.Event('change', { initiator: 'tokenfield' })); } // Update tokenfield dimensions setTimeout(function () { if (!_self.maxTokenWidth) { _self.maxTokenWidth = _self.$wrapper.width() - $closeButton.width() - 10; } $tokenLabel.css('max-width', _self.maxTokenWidth); _self.update(); // Trigger createdtoken event on the original field // indicating that the token is now in the DOM _self.$element.trigger($.Event('tokenfield:createdtoken', { attrs: attrs, relatedTarget: $token.get(0) })); }, 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.tokenfield', $.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, 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')); } else if (this.$input.is(document.activeElement) && (e.keyCode === 13)) { e.preventDefault(); this.$element.trigger('tokenfield:next'); } // 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')); } // no default } 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)) { var val = this.$input.val(), quoting = /^"[^"]*$/.test(val); if (quoting) return; if (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; // no default } 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') remember = true; if (multi) 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(), $_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 firstToken, $token = (e.type === 'click') ? $(e.target).closest('.token') : this.$wrapper.find('.token.active'); if (e.type !== 'click') { if (!direction) direction = 'prev'; this[direction](); // Was it the first token? if (direction === 'prev') 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. if (isNaN(w)) { this.$input.width('100%'); } else { 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 events this.$element.off('.tokenfield'); // 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; }));