es-selectize
Version:
Selectize is a jQuery-based custom <select> UI control. Useful for tagging, contact lists, country selectors, etc.
1 lines • 171 kB
Source Map (JSON)
{"version":3,"file":"selectize.mjs","sources":["../../src/contrib/microevent.js","../../src/contrib/highlight.js","../../src/defaults.js","../../src/consts.js","../../src/utils.js","../../src/selectize.js","../../src/index.js"],"sourcesContent":["/**\n * MicroEvent - to make any js object an event emitter\n *\n * - pure javascript - server compatible, browser compatible\n * - dont rely on the browser doms\n * - super simple - you get it immediatly, no mistery, no magic involved\n *\n * @author Jerome Etienne (https://github.com/jeromeetienne)\n */\n\nexport default function MicroEvent() {}\n\nMicroEvent.prototype = {\n\ton: function(event, fct){\n\t\tthis._events = this._events || {};\n\t\tthis._events[event] = this._events[event] || [];\n\t\tthis._events[event].push(fct);\n\t},\n\toff: function(event, fct){\n\t\tvar n = arguments.length;\n\t\tif (n === 0) return delete this._events;\n\t\tif (n === 1) return delete this._events[event];\n\n\t\tthis._events = this._events || {};\n\t\tif (event in this._events === false) return;\n\t\tthis._events[event].splice(this._events[event].indexOf(fct), 1);\n\t},\n\ttrigger: function(event /* , args... */){\n\t\tthis._events = this._events || {};\n\t\tif (event in this._events === false) return;\n\t\tfor (var i = 0; i < this._events[event].length; i++){\n\t\t\tthis._events[event][i].apply(this, Array.prototype.slice.call(arguments, 1));\n\t\t}\n\t}\n};\n\n/**\n * Mixin will delegate all MicroEvent.js function in the destination object.\n *\n * - MicroEvent.mixin(Foobar) will make Foobar able to use MicroEvent\n *\n * @param {object} the object which will support MicroEvent\n */\nMicroEvent.mixin = function(destObject){\n\tvar props = ['on', 'off', 'trigger'];\n\tfor (var i = 0; i < props.length; i++){\n\t\tdestObject.prototype[props[i]] = MicroEvent.prototype[props[i]];\n\t}\n};\n","/**\n * highlight v3 | MIT license | Johann Burkard <jb@eaio.com>\n * Highlights arbitrary terms in a node.\n *\n * - Modified by Marshal <beatgates@gmail.com> 2011-6-24 (added regex)\n * - Modified by Brian Reavis <brian@thirdroute.com> 2012-8-27 (cleanup)\n */\n\nexport default function highlight($element, pattern) {\n\tif (typeof pattern === 'string' && !pattern.length) return;\n\tvar regex = (typeof pattern === 'string') ? new RegExp(pattern, 'i') : pattern;\n\n\tvar highlight = function(node) {\n\t\tvar skip = 0;\n\t\tif (node.nodeType === 3) {\n\t\t\tvar pos = node.data.search(regex);\n\t\t\tif (pos >= 0 && node.data.length > 0) {\n\t\t\t\t// var match = node.data.match(regex);\n\t\t\t\tvar spannode = document.createElement('span');\n\t\t\t\tspannode.className = 'highlight';\n\t\t\t\tvar middlebit = node.splitText(pos);\n\t\t\t\t// var endbit = middlebit.splitText(match[0].length);\n\t\t\t\tvar middleclone = middlebit.cloneNode(true);\n\t\t\t\tspannode.appendChild(middleclone);\n\t\t\t\tmiddlebit.parentNode.replaceChild(spannode, middlebit);\n\t\t\t\tskip = 1;\n\t\t\t}\n\t\t} else if (node.nodeType === 1 && node.childNodes && !/(script|style)/i.test(node.tagName)) {\n\t\t\tfor (var i = 0; i < node.childNodes.length; ++i) {\n\t\t\t\ti += highlight(node.childNodes[i]);\n\t\t\t}\n\t\t}\n\t\treturn skip;\n\t};\n\n\treturn $element.each(function() {\n\t\thighlight(this);\n\t});\n}\n\n/**\n * removeHighlight fn copied from highlight v5 and\n * edited to remove with() and pass js strict mode\n */\nexport function init() {\n $.fn.removeHighlight = function() {\n return this.find('span.highlight').each(function() {\n // this.parentNode.firstChild.nodeName;\n var parent = this.parentNode;\n parent.replaceChild(this.firstChild, this);\n parent.normalize();\n }).end();\n };\n}\n","export default {\n\toptions: [],\n\toptgroups: [],\n\n\tplugins: [],\n\tdelimiter: ',',\n\tsplitOn: null, // regexp or string for splitting up values from a paste command\n\tpersist: true,\n\tdiacritics: true,\n\tcreate: false,\n\tcreateOnBlur: false,\n\tcreateFilter: null,\n\thighlight: true,\n\topenOnFocus: true,\n\tmaxOptions: 1000,\n\tmaxItems: null,\n\thideSelected: null,\n\taddPrecedence: false,\n\tselectOnTab: false,\n\tpreload: false,\n\tallowEmptyOption: false,\n\tcloseAfterSelect: false,\n\n\tscrollDuration: 60,\n\tloadThrottle: 300,\n\tloadingClass: 'loading',\n\n\tdataAttr: 'data-data',\n\toptgroupField: 'optgroup',\n\tvalueField: 'value',\n\tlabelField: 'text',\n\toptgroupLabelField: 'label',\n\toptgroupValueField: 'value',\n\tlockOptgroupOrder: false,\n\n\tsortField: '$order',\n\tsearchField: ['text'],\n\tsearchConjunction: 'and',\n\n\tmode: null,\n\twrapperClass: 'selectize-control',\n\tinputClass: 'selectize-input',\n\tdropdownClass: 'selectize-dropdown',\n\tdropdownContentClass: 'selectize-dropdown-content',\n\n\tdropdownParent: null,\n\n\tcopyClassesToDropdown: true,\n\n\t/*\n\tload : null, // function(query, callback) { ... }\n\tscore : null, // function(search) { ... }\n\tonInitialize : null, // function() { ... }\n\tonChange : null, // function(value) { ... }\n\tonItemAdd : null, // function(value, $item) { ... }\n\tonItemRemove : null, // function(value) { ... }\n\tonClear : null, // function() { ... }\n\tonOptionAdd : null, // function(value, data) { ... }\n\tonOptionRemove : null, // function(value) { ... }\n\tonOptionClear : null, // function() { ... }\n\tonOptionGroupAdd : null, // function(id, data) { ... }\n\tonOptionGroupRemove : null, // function(id) { ... }\n\tonOptionGroupClear : null, // function() { ... }\n\tonDropdownOpen : null, // function($dropdown) { ... }\n\tonDropdownClose : null, // function($dropdown) { ... }\n\tonType : null, // function(str) { ... }\n\tonDelete : null, // function(values) { ... }\n\t*/\n\n\trender: {\n\t\t/*\n\t\titem: null,\n\t\toptgroup: null,\n\t\toptgroup_header: null,\n\t\toption: null,\n\t\toption_create: null\n\t\t*/\n\t}\n}\n","export var IS_MAC = /Mac/.test(navigator.userAgent);\n\nexport var KEY_A = 65;\nexport var KEY_COMMA = 188;\nexport var KEY_RETURN = 13;\nexport var KEY_ESC = 27;\nexport var KEY_LEFT = 37;\nexport var KEY_UP = 38;\nexport var KEY_P = 80;\nexport var KEY_RIGHT = 39;\nexport var KEY_DOWN = 40;\nexport var KEY_N = 78;\nexport var KEY_BACKSPACE = 8;\nexport var KEY_DELETE = 46;\nexport var KEY_SHIFT = 16;\nexport var KEY_CMD = IS_MAC ? 91 : 17;\nexport var KEY_CTRL = IS_MAC ? 18 : 17;\nexport var KEY_TAB = 9;\n\nexport var TAG_SELECT = 1;\nexport var TAG_INPUT = 2;\n\n// for now, android support in general is too spotty to support validity\nexport var SUPPORTS_VALIDITY_API = !/android/i.test(window.navigator.userAgent) && !!document.createElement('input').validity;\n","import {KEY_BACKSPACE, KEY_DELETE} from './consts'\n\n/**\n * Determines if the provided value has been defined.\n *\n * @param {mixed} object\n * @returns {boolean}\n */\nexport function isSet(object) {\n\treturn typeof object !== 'undefined';\n}\n\n/**\n * Converts a scalar to its best string representation\n * for hash keys and HTML attribute values.\n *\n * Transformations:\n * 'str' -> 'str'\n * null -> ''\n * undefined -> ''\n * true -> '1'\n * false -> '0'\n * 0 -> '0'\n * 1 -> '1'\n *\n * @param {string} value\n * @returns {string|null}\n */\nexport function hashKey(value) {\n\tif (typeof value === 'undefined' || value === null) return null;\n\tif (typeof value === 'boolean') return value ? '1' : '0';\n\treturn value + '';\n}\n\n/**\n * Escapes a string for use within HTML.\n *\n * @param {string} str\n * @returns {string}\n */\nexport function escapeHtml(str) {\n\treturn (str + '')\n\t\t.replace(/&/g, '&')\n\t\t.replace(/</g, '<')\n\t\t.replace(/>/g, '>')\n\t\t.replace(/\"/g, '"');\n}\n\n/**\n * Escapes \"$\" characters in replacement strings.\n *\n * @param {string} str\n * @returns {string}\n */\nexport function escapeReplace(str) {\n\treturn (str + '').replace(/\\$/g, '$$$$');\n}\n\nexport var hook = {};\n\n/**\n * Wraps `method` on `self` so that `fn`\n * is invoked before the original method.\n *\n * @param {object} self\n * @param {string} method\n * @param {function} fn\n */\nhook.before = function(self, method, fn) {\n\tvar original = self[method];\n\tself[method] = function() {\n\t\tfn.apply(self, arguments);\n\t\treturn original.apply(self, arguments);\n\t};\n};\n\n/**\n * Wraps `method` on `self` so that `fn`\n * is invoked after the original method.\n *\n * @param {object} self\n * @param {string} method\n * @param {function} fn\n */\nhook.after = function(self, method, fn) {\n\tvar original = self[method];\n\tself[method] = function() {\n\t\tvar result = original.apply(self, arguments);\n\t\tfn.apply(self, arguments);\n\t\treturn result;\n\t};\n};\n\n/**\n * Wraps `fn` so that it can only be invoked once.\n *\n * @param {function} fn\n * @returns {function}\n */\nexport function once(fn) {\n\tvar called = false;\n\treturn function() {\n\t\tif (called) return;\n\t\tcalled = true;\n\t\tfn.apply(this, arguments);\n\t};\n}\n\n/**\n * Wraps `fn` so that it can only be called once\n * every `delay` milliseconds (invoked on the falling edge).\n *\n * @param {function} fn\n * @param {int} delay\n * @returns {function}\n */\nexport function debounce(fn, delay) {\n\tvar timeout;\n\treturn function() {\n\t\tvar self = this;\n\t\tvar args = arguments;\n\t\twindow.clearTimeout(timeout);\n\t\ttimeout = window.setTimeout(function() {\n\t\t\tfn.apply(self, args);\n\t\t}, delay);\n\t};\n}\n\n/**\n * Debounce all fired events types listed in `types`\n * while executing the provided `fn`.\n *\n * @param {object} self\n * @param {array} types\n * @param {function} fn\n */\nexport function debounceEvents(self, types, fn) {\n\tvar type;\n\tvar trigger = self.trigger;\n\tvar eventArgs = {};\n\n\t// override trigger method\n\tself.trigger = function() {\n\t\tvar type = arguments[0];\n\t\tif (types.indexOf(type) !== -1) {\n\t\t\teventArgs[type] = arguments;\n\t\t} else {\n\t\t\treturn trigger.apply(self, arguments);\n\t\t}\n\t};\n\n\t// invoke provided function\n\tfn.apply(self, []);\n\tself.trigger = trigger;\n\n\t// trigger queued events\n\tfor (type in eventArgs) {\n\t\tif (eventArgs.hasOwnProperty(type)) {\n\t\t\ttrigger.apply(self, eventArgs[type]);\n\t\t}\n\t}\n}\n\n/**\n * A workaround for http://bugs.jquery.com/ticket/6696\n *\n * @param {object} $parent - Parent element to listen on.\n * @param {string} event - Event name.\n * @param {string} selector - Descendant selector to filter by.\n * @param {function} fn - Event handler.\n */\nexport function watchChildEvent($parent, event, selector, fn) {\n\t$parent.on(event, selector, function(e) {\n\t\tvar child = e.target;\n\t\twhile (child && child.parentNode !== $parent[0]) {\n\t\t\tchild = child.parentNode;\n\t\t}\n\t\te.currentTarget = child;\n\t\treturn fn.apply(this, [e]);\n\t});\n}\n\n/**\n * Determines the current selection within a text input control.\n * Returns an object containing:\n * - start\n * - length\n *\n * @param {object} input\n * @returns {object}\n */\nexport function getSelection(input) {\n\tvar result = {};\n\tif ('selectionStart' in input) {\n\t\tresult.start = input.selectionStart;\n\t\tresult.length = input.selectionEnd - result.start;\n\t} else if (document.selection) {\n\t\tinput.focus();\n\t\tvar sel = document.selection.createRange();\n\t\tvar selLen = document.selection.createRange().text.length;\n\t\tsel.moveStart('character', -input.value.length);\n\t\tresult.start = sel.text.length - selLen;\n\t\tresult.length = selLen;\n\t}\n\treturn result;\n}\n\n/**\n * Copies CSS properties from one element to another.\n *\n * @param {object} $from\n * @param {object} $to\n * @param {array} properties\n */\nexport function transferStyles($from, $to, properties) {\n\tvar i, n, styles = {};\n\tif (properties) {\n\t\tfor (i = 0, n = properties.length; i < n; i++) {\n\t\t\tstyles[properties[i]] = $from.css(properties[i]);\n\t\t}\n\t} else {\n\t\tstyles = $from.css();\n\t}\n\t$to.css(styles);\n}\n\n/**\n * Measures the width of a string within a\n * parent element (in pixels).\n *\n * @param {string} str\n * @param {object} $parent\n * @returns {int}\n */\nexport function measureString(str, $parent) {\n\tif (!str) {\n\t\treturn 0;\n\t}\n\n\tvar $test = $('<test>').css({\n\t\tposition: 'absolute',\n\t\ttop: -99999,\n\t\tleft: -99999,\n\t\twidth: 'auto',\n\t\tpadding: 0,\n\t\twhiteSpace: 'pre'\n\t}).text(str).appendTo('body');\n\n\ttransferStyles($parent, $test, [\n\t\t'letterSpacing',\n\t\t'fontSize',\n\t\t'fontFamily',\n\t\t'fontWeight',\n\t\t'textTransform'\n\t]);\n\n\tvar width = $test.width();\n\t$test.remove();\n\n\treturn width;\n}\n\n/**\n * Sets up an input to grow horizontally as the user\n * types. If the value is changed manually, you can\n * trigger the \"update\" handler to resize:\n *\n * $input.trigger('update');\n *\n * @param {object} $input\n */\nexport function autoGrow($input) {\n\tvar currentWidth = null;\n\n\tvar update = function(e, options) {\n\t\tvar value, keyCode, printable, placeholder, width;\n\t\tvar shift, character, selection;\n\t\te = e || window.event || {};\n\t\toptions = options || {};\n\n\t\tif (e.metaKey || e.altKey) return;\n\t\tif (!options.force && $input.data('grow') === false) return;\n\n\t\tvalue = $input.val();\n\t\tif (e.type && e.type.toLowerCase() === 'keydown') {\n\t\t\tkeyCode = e.keyCode;\n\t\t\tprintable = (\n\t\t\t\t(keyCode >= 97 && keyCode <= 122) || // a-z\n\t\t\t\t(keyCode >= 65 && keyCode <= 90) || // A-Z\n\t\t\t\t(keyCode >= 48 && keyCode <= 57) || // 0-9\n\t\t\t\tkeyCode === 32 // space\n\t\t\t);\n\n\t\t\tif (keyCode === KEY_DELETE || keyCode === KEY_BACKSPACE) {\n\t\t\t\tselection = getSelection($input[0]);\n\t\t\t\tif (selection.length) {\n\t\t\t\t\tvalue = value.substring(0, selection.start) + value.substring(selection.start + selection.length);\n\t\t\t\t} else if (keyCode === KEY_BACKSPACE && selection.start) {\n\t\t\t\t\tvalue = value.substring(0, selection.start - 1) + value.substring(selection.start + 1);\n\t\t\t\t} else if (keyCode === KEY_DELETE && typeof selection.start !== 'undefined') {\n\t\t\t\t\tvalue = value.substring(0, selection.start) + value.substring(selection.start + 1);\n\t\t\t\t}\n\t\t\t} else if (printable) {\n\t\t\t\tshift = e.shiftKey;\n\t\t\t\tcharacter = String.fromCharCode(e.keyCode);\n\t\t\t\tif (shift) character = character.toUpperCase();\n\t\t\t\telse character = character.toLowerCase();\n\t\t\t\tvalue += character;\n\t\t\t}\n\t\t}\n\n\t\tplaceholder = $input.attr('placeholder');\n\t\tif (!value && placeholder) {\n\t\t\tvalue = placeholder;\n\t\t}\n\n\t\twidth = measureString(value, $input) + 4;\n\t\tif (width !== currentWidth) {\n\t\t\tcurrentWidth = width;\n\t\t\t$input.width(width);\n\t\t\t$input.triggerHandler('resize');\n\t\t}\n\t};\n\n\t$input.on('keydown keyup update blur', update);\n\tupdate();\n}\n\nexport function domToString(d) {\n\tvar tmp = document.createElement('div');\n\n\ttmp.appendChild(d.cloneNode(true));\n\n\treturn tmp.innerHTML;\n}\n\nexport function logError(message, options){\n\tif(!options) options = {};\n\tvar component = 'Selectize';\n\n\tconsole.error(component + ': ' + message)\n\n\tif(options.explanation){\n\t\t// console.group is undefined in <IE11\n\t\tif(console.group) console.group();\n\t\tconsole.error(options.explanation);\n\t\tif(console.group) console.groupEnd();\n\t}\n}\n","import Sifter from 'es-sifter'\nimport MicroPlugin from 'es-microplugin'\n\nimport MicroEvent from './contrib/microevent'\nimport highlight from './contrib/highlight'\nimport { init as highlightInit } from './contrib/highlight'\nimport defaults from './defaults'\n\nimport {\n IS_MAC,\n\n KEY_A, KEY_RETURN, KEY_ESC, KEY_LEFT, KEY_UP, KEY_P, KEY_RIGHT, KEY_DOWN,\n KEY_N, KEY_BACKSPACE, KEY_DELETE, KEY_SHIFT, KEY_CMD, KEY_CTRL, KEY_TAB,\n\n TAG_SELECT, TAG_INPUT,\n SUPPORTS_VALIDITY_API\n} from './consts'\n\nimport {\n autoGrow, debounce, debounceEvents, domToString, escapeHtml, getSelection,\n hashKey, isSet, once, watchChildEvent\n} from './utils'\n\n\nvar inited = false\n\nexport default function Selectize($input, settings) {\n if (!inited) {\n init()\n inited = true\n }\n\n\tvar i, n, dir, input, self = this;\n\tinput = $input[0];\n\tinput.selectize = self;\n\n\t// detect rtl environment\n\tvar computedStyle = window.getComputedStyle && window.getComputedStyle(input, null);\n\tdir = computedStyle ? computedStyle.getPropertyValue('direction') : input.currentStyle && input.currentStyle.direction;\n\tdir = dir || $input.parents('[dir]:first').attr('dir') || '';\n\n\t// setup default state\n\t$.extend(self, {\n\t\torder : 0,\n\t\tsettings : settings,\n\t\t$input : $input,\n\t\ttabIndex : $input.attr('tabindex') || '',\n\t\ttagType : input.tagName.toLowerCase() === 'select' ? TAG_SELECT : TAG_INPUT,\n\t\trtl : /rtl/i.test(dir),\n\n\t\teventNS : '.selectize' + (++Selectize.count),\n\t\thighlightedValue : null,\n\t\tisOpen : false,\n\t\tisDisabled : false,\n\t\tisRequired : $input.is('[required]'),\n\t\tisInvalid : false,\n\t\tisLocked : false,\n\t\tisFocused : false,\n\t\tisInputHidden : false,\n\t\tisSetup : false,\n\t\tisShiftDown : false,\n\t\tisCmdDown : false,\n\t\tisCtrlDown : false,\n\t\tignoreFocus : false,\n\t\tignoreBlur : false,\n\t\tignoreHover : false,\n\t\thasOptions : false,\n\t\tcurrentResults : null,\n\t\tlastValue : '',\n\t\tcaretPos : 0,\n\t\tloading : 0,\n\t\tloadedSearches : {},\n\n\t\t$activeOption : null,\n\t\t$activeItems : [],\n\n\t\toptgroups : {},\n\t\toptions : {},\n\t\tuserOptions : {},\n\t\titems : [],\n\t\trenderCache : {},\n\t\tonSearchChange : settings.loadThrottle === null ? self.onSearchChange : debounce(self.onSearchChange, settings.loadThrottle)\n\t});\n\n\t// search system\n\tself.sifter = new Sifter(this.options, {diacritics: settings.diacritics});\n\n\t// build options table\n\tif (self.settings.options) {\n\t\tfor (i = 0, n = self.settings.options.length; i < n; i++) {\n\t\t\tself.registerOption(self.settings.options[i]);\n\t\t}\n\t\tdelete self.settings.options;\n\t}\n\n\t// build optgroup table\n\tif (self.settings.optgroups) {\n\t\tfor (i = 0, n = self.settings.optgroups.length; i < n; i++) {\n\t\t\tself.registerOptionGroup(self.settings.optgroups[i]);\n\t\t}\n\t\tdelete self.settings.optgroups;\n\t}\n\n\t// option-dependent defaults\n\tself.settings.mode = self.settings.mode || (self.settings.maxItems === 1 ? 'single' : 'multi');\n\tif (typeof self.settings.hideSelected !== 'boolean') {\n\t\tself.settings.hideSelected = self.settings.mode === 'multi';\n\t}\n\n\tself.initializePlugins(self.settings.plugins);\n\tself.setupCallbacks();\n\tself.setupTemplates();\n\tself.setup();\n}\n\nfunction init() {\n // initialize highlight\n highlightInit()\n\n // defaults\n // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n Selectize.defaults = defaults\n Selectize.count = 0\n\n // mixins\n // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\n MicroEvent.mixin(Selectize);\n MicroPlugin.mixin(Selectize);\n\n // methods\n // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\n $.extend(Selectize.prototype, {\n\n /**\n * Creates all elements and sets up event bindings.\n */\n setup: function() {\n var self = this;\n var settings = self.settings;\n var eventNS = self.eventNS;\n var $window = $(window);\n var $document = $(document);\n var $input = self.$input;\n\n var $wrapper;\n var $control;\n var $controlInput;\n var $dropdown;\n var $dropdownContent;\n var $dropdownParent;\n var inputMode;\n var classes;\n var classesPlugins;\n var inputId;\n\n inputMode = self.settings.mode;\n classes = $input.attr('class') || '';\n\n $wrapper = $('<div>').addClass(settings.wrapperClass).addClass(classes).addClass(inputMode);\n $control = $('<div>').addClass(settings.inputClass).addClass('items').appendTo($wrapper);\n $controlInput = $('<input type=\"text\" autocomplete=\"off\" />').appendTo($control).attr('tabindex', $input.is(':disabled') ? '-1' : self.tabIndex);\n $dropdownParent = $(settings.dropdownParent || $wrapper);\n $dropdown = $('<div>').addClass(settings.dropdownClass).addClass(inputMode).hide().appendTo($dropdownParent);\n $dropdownContent = $('<div>').addClass(settings.dropdownContentClass).appendTo($dropdown);\n\n if(inputId = $input.attr('id')) {\n $controlInput.attr('id', inputId + '-selectized');\n $('label[for=\"'+inputId+'\"]').attr('for', inputId + '-selectized');\n }\n\n if(self.settings.copyClassesToDropdown) {\n $dropdown.addClass(classes);\n }\n\n $wrapper.css({\n width: $input[0].style.width\n });\n\n if (self.plugins.names.length) {\n classesPlugins = 'plugin-' + self.plugins.names.join(' plugin-');\n $wrapper.addClass(classesPlugins);\n $dropdown.addClass(classesPlugins);\n }\n\n if ((settings.maxItems === null || settings.maxItems > 1) && self.tagType === TAG_SELECT) {\n $input.attr('multiple', 'multiple');\n }\n\n if (self.settings.placeholder) {\n $controlInput.attr('placeholder', settings.placeholder);\n }\n\n // if splitOn was not passed in, construct it from the delimiter to allow pasting universally\n if (!self.settings.splitOn && self.settings.delimiter) {\n var delimiterEscaped = self.settings.delimiter.replace(/[-\\/\\\\^$*+?.()|[\\]{}]/g, '\\\\$&');\n self.settings.splitOn = new RegExp('\\\\s*' + delimiterEscaped + '+\\\\s*');\n }\n\n if ($input.attr('autocorrect')) {\n $controlInput.attr('autocorrect', $input.attr('autocorrect'));\n }\n\n if ($input.attr('autocapitalize')) {\n $controlInput.attr('autocapitalize', $input.attr('autocapitalize'));\n }\n\n self.$wrapper = $wrapper;\n self.$control = $control;\n self.$controlInput = $controlInput;\n self.$dropdown = $dropdown;\n self.$dropdownContent = $dropdownContent;\n\n $dropdown.on('mouseenter', '[data-selectable]', function() { return self.onOptionHover.apply(self, arguments); });\n $dropdown.on('mousedown click', '[data-selectable]', function() { return self.onOptionSelect.apply(self, arguments); });\n watchChildEvent($control, 'mousedown', '*:not(input)', function() { return self.onItemSelect.apply(self, arguments); });\n autoGrow($controlInput);\n\n $control.on({\n mousedown : function() { return self.onMouseDown.apply(self, arguments); },\n click : function() { return self.onClick.apply(self, arguments); }\n });\n\n $controlInput.on({\n mousedown : function(e) { e.stopPropagation(); },\n keydown : function() { return self.onKeyDown.apply(self, arguments); },\n keyup : function() { return self.onKeyUp.apply(self, arguments); },\n keypress : function() { return self.onKeyPress.apply(self, arguments); },\n resize : function() { self.positionDropdown.apply(self, []); },\n blur : function() { return self.onBlur.apply(self, arguments); },\n focus : function() { self.ignoreBlur = false; return self.onFocus.apply(self, arguments); },\n paste : function() { return self.onPaste.apply(self, arguments); }\n });\n\n $document.on('keydown' + eventNS, function(e) {\n self.isCmdDown = e[IS_MAC ? 'metaKey' : 'ctrlKey'];\n self.isCtrlDown = e[IS_MAC ? 'altKey' : 'ctrlKey'];\n self.isShiftDown = e.shiftKey;\n });\n\n $document.on('keyup' + eventNS, function(e) {\n if (e.keyCode === KEY_CTRL) self.isCtrlDown = false;\n if (e.keyCode === KEY_SHIFT) self.isShiftDown = false;\n if (e.keyCode === KEY_CMD) self.isCmdDown = false;\n });\n\n $document.on('mousedown' + eventNS, function(e) {\n if (self.isFocused) {\n // prevent events on the dropdown scrollbar from causing the control to blur\n if (e.target === self.$dropdown[0] || e.target.parentNode === self.$dropdown[0]) {\n return false;\n }\n // blur on click outside\n if (!self.$control.has(e.target).length && e.target !== self.$control[0]) {\n self.blur(e.target);\n }\n }\n });\n\n $window.on(['scroll' + eventNS, 'resize' + eventNS].join(' '), function() {\n if (self.isOpen) {\n self.positionDropdown.apply(self, arguments);\n }\n });\n $window.on('mousemove' + eventNS, function() {\n self.ignoreHover = false;\n });\n\n // store original children and tab index so that they can be\n // restored when the destroy() method is called.\n this.revertSettings = {\n $children : $input.children().detach(),\n tabindex : $input.attr('tabindex')\n };\n\n $input.attr('tabindex', -1).hide().after(self.$wrapper);\n\n if ($.isArray(settings.items)) {\n self.setValue(settings.items);\n delete settings.items;\n }\n\n // feature detect for the validation API\n if (SUPPORTS_VALIDITY_API) {\n $input.on('invalid' + eventNS, function(e) {\n e.preventDefault();\n self.isInvalid = true;\n self.refreshState();\n });\n }\n\n self.updateOriginalInput();\n self.refreshItems();\n self.refreshState();\n self.updatePlaceholder();\n self.isSetup = true;\n\n if ($input.is(':disabled')) {\n self.disable();\n }\n\n self.on('change', this.onChange);\n\n $input.data('selectize', self);\n $input.addClass('selectized');\n self.trigger('initialize');\n\n // preload options\n if (settings.preload === true) {\n self.onSearchChange('');\n }\n\n },\n\n /**\n * Sets up default rendering functions.\n */\n setupTemplates: function() {\n var self = this;\n var fieldLabel = self.settings.labelField;\n var fieldOptgroup = self.settings.optgroupLabelField;\n\n var templates = {\n 'optgroup': function(data) {\n return '<div class=\"optgroup\">' + data.html + '</div>';\n },\n 'optgroupHeader': function(data, escape) {\n return '<div class=\"optgroup-header\">' + escape(data[fieldOptgroup]) + '</div>';\n },\n 'option': function(data, escape) {\n return '<div class=\"option\">' + escape(data[fieldLabel]) + '</div>';\n },\n 'item': function(data, escape) {\n return '<div class=\"item\">' + escape(data[fieldLabel]) + '</div>';\n },\n 'optionCreate': function(data, escape) {\n return '<div class=\"create\">Add <strong>' + escape(data.input) + '</strong>…</div>';\n }\n };\n\n self.settings.render = $.extend({}, templates, self.settings.render);\n },\n\n /**\n * Maps fired events to callbacks provided\n * in the settings used when creating the control.\n */\n setupCallbacks: function() {\n var key, fn, callbacks = {\n 'initialize' : 'onInitialize',\n 'change' : 'onChange',\n 'itemAdd' : 'onItemAdd',\n 'itemRemove' : 'onItemRemove',\n 'clear' : 'onClear',\n 'optionAdd' : 'onOptionAdd',\n 'optionRemove' : 'onOptionRemove',\n 'optionClear' : 'onOptionClear',\n 'optgroupAdd' : 'onOptionGroupAdd',\n 'optgroupRemove' : 'onOptionGroupRemove',\n 'optgroupClear' : 'onOptionGroupClear',\n 'dropdownOpen' : 'onDropdownOpen',\n 'dropdownClose' : 'onDropdownClose',\n 'type' : 'onType',\n 'load' : 'onLoad',\n 'focus' : 'onFocus',\n 'blur' : 'onBlur'\n };\n\n for (key in callbacks) {\n if (callbacks.hasOwnProperty(key)) {\n fn = this.settings[callbacks[key]];\n if (fn) this.on(key, fn);\n }\n }\n },\n\n /**\n * Triggered when the main control element\n * has a click event.\n *\n * @param {object} e\n * @return {boolean}\n */\n onClick: function(e) {\n var self = this;\n\n // necessary for mobile webkit devices (manual focus triggering\n // is ignored unless invoked within a click event)\n if (!self.isFocused) {\n self.focus();\n e.preventDefault();\n }\n },\n\n /**\n * Triggered when the main control element\n * has a mouse down event.\n *\n * @param {object} e\n * @return {boolean}\n */\n onMouseDown: function(e) {\n var self = this;\n var defaultPrevented = e.isDefaultPrevented();\n\n if (self.isFocused) {\n // retain focus by preventing native handling. if the\n // event target is the input it should not be modified.\n // otherwise, text selection within the input won't work.\n if (e.target !== self.$controlInput[0]) {\n if (self.settings.mode === 'single') {\n // toggle dropdown\n if (self.isOpen)\n self.close()\n else\n self.open()\n } else if (!defaultPrevented) {\n self.setActiveItem(null)\n }\n return false\n }\n } else {\n // give control focus\n if (!defaultPrevented) {\n window.setTimeout(function() {\n self.focus()\n }, 0)\n }\n }\n },\n\n /**\n * Triggered when the value of the control has been changed.\n * This should propagate the event to the original DOM\n * input / select element.\n */\n onChange: function() {\n this.$input.trigger('change');\n },\n\n /**\n * Triggered on <input> paste.\n *\n * @param {object} e\n * @returns {boolean}\n */\n onPaste: function(e) {\n var self = this;\n\n if (self.isFull() || self.isInputHidden || self.isLocked) {\n e.preventDefault();\n return;\n }\n\n // If a regex or string is included, this will split the pasted\n // input and create Items for each separate value\n if (self.settings.splitOn) {\n\n // Wait for pasted text to be recognized in value\n setTimeout(function() {\n var pastedText = self.$controlInput.val();\n if(!pastedText.match(self.settings.splitOn)){ return }\n\n var splitInput = $.trim(pastedText).split(self.settings.splitOn);\n for (var i = 0, n = splitInput.length; i < n; i++) {\n self.createItem(splitInput[i]);\n }\n }, 0);\n }\n },\n\n /**\n * Triggered on <input> keypress.\n *\n * @param {object} e\n * @returns {boolean}\n */\n onKeyPress: function(e) {\n if (this.isLocked) return e && e.preventDefault();\n var character = String.fromCharCode(e.keyCode || e.which);\n if (this.settings.create && this.settings.mode === 'multi' && character === this.settings.delimiter) {\n this.createItem();\n e.preventDefault();\n return false;\n }\n },\n\n /**\n * Triggered on <input> keydown.\n *\n * @param {object} e\n * @returns {boolean}\n */\n onKeyDown: function(e) {\n var self = this;\n\n if (self.isLocked) {\n if (e.keyCode !== KEY_TAB) {\n e.preventDefault();\n }\n return;\n }\n\n switch (e.keyCode) {\n case KEY_A:\n if (self.isCmdDown) {\n self.selectAll();\n return;\n }\n break;\n case KEY_ESC:\n if (self.isOpen) {\n e.preventDefault();\n e.stopPropagation();\n self.close();\n }\n return;\n case KEY_N:\n if (!e.ctrlKey || e.altKey) break;\n case KEY_DOWN:\n if (!self.isOpen && self.hasOptions) {\n self.open();\n } else if (self.$activeOption) {\n self.ignoreHover = true;\n var $next = self.getAdjacentOption(self.$activeOption, 1);\n if ($next.length) self.setActiveOption($next, true, true);\n }\n e.preventDefault();\n return;\n case KEY_P:\n if (!e.ctrlKey || e.altKey) break;\n case KEY_UP:\n if (self.$activeOption) {\n self.ignoreHover = true;\n var $prev = self.getAdjacentOption(self.$activeOption, -1);\n if ($prev.length) self.setActiveOption($prev, true, true);\n }\n e.preventDefault();\n return;\n case KEY_RETURN:\n if (self.isOpen && self.$activeOption) {\n self.onOptionSelect({currentTarget: self.$activeOption});\n e.preventDefault();\n }\n return;\n case KEY_LEFT:\n self.advanceSelection(-1, e);\n return;\n case KEY_RIGHT:\n self.advanceSelection(1, e);\n return;\n case KEY_TAB:\n if (self.settings.selectOnTab && self.isOpen && self.$activeOption) {\n self.onOptionSelect({currentTarget: self.$activeOption});\n\n // Default behaviour is to jump to the next field, we only want this\n // if the current field doesn't accept any more entries\n if (!self.isFull()) {\n e.preventDefault();\n }\n }\n if (self.settings.create && self.createItem()) {\n e.preventDefault();\n }\n return;\n case KEY_BACKSPACE:\n case KEY_DELETE:\n self.deleteSelection(e);\n return;\n }\n\n if ((self.isFull() || self.isInputHidden) && !(IS_MAC ? e.metaKey : e.ctrlKey)) {\n e.preventDefault();\n return;\n }\n },\n\n /**\n * Triggered on <input> keyup.\n *\n * @param {object} e\n * @returns {boolean}\n */\n onKeyUp: function(e) {\n var self = this;\n\n if (self.isLocked) return e && e.preventDefault();\n var value = self.$controlInput.val() || '';\n if (self.lastValue !== value) {\n self.lastValue = value;\n self.onSearchChange(value);\n self.refreshOptions();\n self.trigger('type', value);\n }\n },\n\n /**\n * Invokes the user-provide option provider / loader.\n *\n * Note: this function is debounced in the Selectize\n * constructor (by `settings.loadThrottle` milliseconds)\n *\n * @param {string} value\n */\n onSearchChange: function(value) {\n var self = this;\n var fn = self.settings.load;\n if (!fn) return;\n if (self.loadedSearches.hasOwnProperty(value)) return;\n self.loadedSearches[value] = true;\n self.load(function(callback) {\n fn.apply(self, [value, callback]);\n });\n },\n\n /**\n * Triggered on <input> focus.\n *\n * @param {object} e (optional)\n * @returns {boolean}\n */\n onFocus: function(e) {\n var self = this;\n var wasFocused = self.isFocused;\n\n if (self.isDisabled) {\n self.blur();\n if (e) e.preventDefault()\n return false;\n }\n\n if (self.ignoreFocus) return;\n self.isFocused = true;\n if (self.settings.preload === 'focus') self.onSearchChange('');\n\n if (!wasFocused) self.trigger('focus');\n\n if (!self.$activeItems.length) {\n self.showInput();\n self.setActiveItem(null);\n self.refreshOptions(!!self.settings.openOnFocus);\n }\n\n self.refreshState();\n },\n\n /**\n * Triggered on <input> blur.\n *\n * @param {object} e\n * @param {Element} dest\n */\n onBlur: function(e, dest) {\n var self = this;\n if (!self.isFocused) return;\n self.isFocused = false;\n\n if (self.ignoreFocus) {\n return;\n } else if (!self.ignoreBlur && document.activeElement === self.$dropdownContent[0]) {\n // necessary to prevent IE closing the dropdown when the scrollbar is clicked\n self.ignoreBlur = true;\n self.onFocus(e);\n return;\n }\n\n var deactivate = function() {\n self.close();\n self.setTextboxValue('');\n self.setActiveItem(null);\n self.setActiveOption(null);\n self.setCaret(self.items.length);\n self.refreshState();\n\n // IE11 bug: element still marked as active\n if (dest && dest.focus) dest.focus();\n\n self.ignoreFocus = false;\n self.trigger('blur');\n };\n\n self.ignoreFocus = true;\n if (self.settings.create && self.settings.createOnBlur) {\n self.createItem(null, false, deactivate);\n } else {\n deactivate();\n }\n },\n\n /**\n * Triggered when the user rolls over\n * an option in the autocomplete dropdown menu.\n *\n * @param {object} e\n * @returns {boolean}\n */\n onOptionHover: function(e) {\n if (this.ignoreHover) return;\n this.setActiveOption(e.currentTarget, false);\n },\n\n /**\n * Triggered when the user clicks on an option\n * in the autocomplete dropdown menu.\n *\n * @param {object} e\n * @returns {boolean}\n */\n onOptionSelect: function(e) {\n var value, $target, self = this;\n\n if (e.preventDefault) {\n e.preventDefault();\n e.stopPropagation();\n }\n\n $target = $(e.currentTarget);\n if ($target.hasClass('create')) {\n self.createItem(null, function() {\n if (self.settings.closeAfterSelect) {\n self.close();\n }\n });\n } else {\n value = $target.attr('data-value');\n if (typeof value !== 'undefined') {\n self.lastQuery = null;\n self.setTextboxValue('');\n self.addItem(value);\n if (self.settings.closeAfterSelect) {\n self.close();\n } else if (!self.settings.hideSelected && e.type && /mouse/.test(e.type)) {\n self.setActiveOption(self.getOption(value));\n }\n }\n }\n },\n\n /**\n * Triggered when the user clicks on an item\n * that has been selected.\n *\n * @param {object} e\n * @returns {boolean}\n */\n onItemSelect: function(e) {\n var self = this;\n\n if (self.isLocked) return;\n if (self.settings.mode === 'multi') {\n e.preventDefault();\n self.setActiveItem(e.currentTarget, e);\n }\n },\n\n /**\n * Invokes the provided method that provides\n * results to a callback---which are then added\n * as options to the control.\n *\n * @param {function} fn\n */\n load: function(fn) {\n var self = this;\n var $wrapper = self.$wrapper.addClass(self.settings.loadingClass);\n\n self.loading++;\n fn.apply(self, [function(results) {\n self.loading = Math.max(self.loading - 1, 0);\n if (results && results.length) {\n self.addOption(results);\n self.refreshOptions(self.isFocused && !self.isInputHidden);\n }\n if (!self.loading) {\n $wrapper.removeClass(self.settings.loadingClass);\n }\n self.trigger('load', results);\n }]);\n },\n\n /**\n * Sets the input field of the control to the specified value.\n *\n * @param {string} value\n */\n setTextboxValue: function(value) {\n var $input = this.$controlInput;\n var changed = $input.val() !== value;\n if (changed) {\n $input.val(value).triggerHandler('update');\n this.lastValue = value;\n }\n },\n\n /**\n * Returns the value of the control. If multiple items\n * can be selected (e.g. <select multiple>), this returns\n * an array. If only one item can be selected, this\n * returns a string.\n *\n * @returns {mixed}\n */\n getValue: function() {\n if (this.tagType === TAG_SELECT && this.$input.attr('multiple')) {\n return this.items;\n } else {\n return this.items.join(this.settings.delimiter);\n }\n },\n\n /**\n * Resets the selected items to the given value.\n *\n * @param {mixed} value\n */\n setValue: function(value, silent) {\n var events = silent ? [] : ['change'];\n\n debounceEvents(this, events, function() {\n this.clear(silent);\n this.addItems(value, silent);\n });\n },\n\n /**\n * Sets the selected item.\n *\n * @param {object} $item\n * @param {object} e (optional)\n */\n setActiveItem: function($item, e) {\n var self = this;\n var eventName;\n var i, idx, begin, end, item, swap;\n var $last;\n\n if (self.settings.mode === 'single') return;\n $item = $($item);\n\n // clear the active selection\n if (!$item.length) {\n $(self.$activeItems).removeClass('active');\n self.$activeItems = [];\n if (self.isFocused) {\n self.showInput();\n }\n return;\n }\n\n // modify selection\n eventName = e && e.type.toLowerCase();\n\n if (eventName === 'mousedown' && self.isShiftDown && self.$activeItems.length) {\n $last = self.$control.children('.active:last');\n begin = Array.prototype.indexOf.apply(self.$control[0].childNodes, [$last[0]]);\n end = Array.prototype.indexOf.apply(self.$control[0].childNodes, [$item[0]]);\n if (begin > end) {\n swap = begin;\n begin = end;\n end = swap;\n }\n for (i = begin; i <= end; i++) {\n item = self.$control[0].childNodes[i];\n if (self.$activeItems.indexOf(item) === -1) {\n $(item).addClass('active');\n self.$activeItems.push(item);\n }\n }\n e.preventDefault();\n } else if ((eventName === 'mousedown' && self.isCtrlDown) || (eventName === 'keydown' && this.isShiftDown)) {\n if ($item.hasClass('active')) {\n idx = self.$activeItems.indexOf($item[0]);\n self.$activeItems.splice(idx, 1);\n $item.removeClass('active');\n } else {\n self.$activeItems.push($item.addClass('active')[0]);\n }\n } else {\n $(self.$activeItems).removeClass('active');\n self.$activeItems = [$item.addClass('active')[0]];\n }\n\n // ensure control has focus\n self.hideInput();\n if (!this.isFocused) {\n self.focus();\n }\n },\n\n /**\n * Sets the selected item in the dropdown menu\n * of available options.\n *\n * @param {object} $object\n * @param {boolean} scroll\n * @param {boolean} animate\n */\n setActiveOption: function($option, scroll, animate) {\n var heightMenu, heightItem, y;\n var scrollTop, scrollBottom;\n var self = this;\n\n if (self.$activeOption) self.$activeOption.removeClass('active');\n self.$activeOption = null;\n\n $option = $($option);\n if (!$option.length) return;\n\n self.$activeOption = $option.addClass('active');\n\n if (scroll || !isSet(scroll)) {\n\n heightMenu = self.$dropdownContent.height();\n heightItem = self.$activeOption.outerHeight(true);\n scroll = self.$dropdownContent.scrollTop() || 0;\n y = self.$activeOption.offset().top - self.$dropdownContent.offset().top + scroll;\n scrollTop = y;\n scrollBottom = y - heightMenu + heightItem;\n\n if (y + heightItem > heightMenu + scroll) {\n self.$dropdownContent.stop().animate({scrollTop: scrollBottom}, animate ? self.settings.scrollDuration : 0);\n } else if (y < scroll) {\n self.$dropdownContent.stop().animate({scrollTop: scrollTop}, animate ? self.settings.scrollDuration : 0);\n }\n\n }\n },\n\n /**\n * Selects all items (CTRL + A).\n */\n selectAll: function() {\n var self = this;\n if (self.settings.mode === 'single') return;\n\n self.$activeItems = Array.prototype.slice.apply(self.$control.children(':not(input)').addClass('active'));\n if (self.$activeItems.length) {\n self.hideInput();\n self.close();\n }\n self.focus();\n },\n\n /**\n * Hides the input element out of view