From 9d6ab92ce6f61f9ed72b62677365c255fd68a04a Mon Sep 17 00:00:00 2001 From: Brian Reavis Date: Sun, 14 Jul 2013 14:53:36 -0700 Subject: [PATCH] Released 0.6.5. --- bower.json | 2 +- selectize.css | 2 +- selectize.jquery.json | 2 +- selectize.js | 3162 +++++++++++++++++++++-------------------- selectize.min.js | 129 +- 5 files changed, 1708 insertions(+), 1589 deletions(-) diff --git a/bower.json b/bower.json index 662823c62..d000286ff 100644 --- a/bower.json +++ b/bower.json @@ -2,7 +2,7 @@ "name": "selectize", "keywords": ["select", "ui", "form", "input", "control", "autocomplete", "tagging", "tag"], "description": "Selectize is a jQuery-based custom ').appendTo($control).attr('tabindex',tab_index); - $dropdown_parent = $(this.settings.dropdownParent || $wrapper); - $dropdown = $('
').addClass(this.settings.dropdownClass).hide().appendTo($dropdown_parent); - $dropdown_content = $('
').addClass(this.settings.dropdownContentClass).appendTo($dropdown); - - $wrapper.css({ - width: this.$input[0].style.width, - display: this.$input.css('display') - }); - - if (this.plugins.length) { - $wrapper.addClass('plugin-' + this.plugins.join(' plugin-')); - } - - inputMode = this.settings.mode; - $wrapper.toggleClass('single', inputMode === 'single'); - $wrapper.toggleClass('multi', inputMode === 'multi'); + $.extend(Selectize.prototype, { - if ((this.settings.maxItems === null || this.settings.maxItems > 1) && this.tagType === TAG_SELECT) { - this.$input.attr('multiple', 'multiple'); - } + /** + * Creates all elements and sets up event bindings. + */ + setup: function() { + var self = this; + var settings = self.settings; + var $wrapper; + var $control; + var $control_input; + var $dropdown; + var $dropdown_content; + var $dropdown_parent; + var inputMode; + var timeout_blur; + var timeout_focus; + var tab_index; + var classes; + + tab_index = self.$input.attr('tabindex') || ''; + classes = self.$input.attr('class') || ''; + $wrapper = $('
').addClass(settings.theme).addClass(settings.wrapperClass).addClass(classes); + $control = $('
').addClass(settings.inputClass).addClass('items').toggleClass('has-options', !$.isEmptyObject(self.options)).appendTo($wrapper); + $control_input = $('').appendTo($control).attr('tabindex',tab_index); + $dropdown_parent = $(settings.dropdownParent || $wrapper); + $dropdown = $('
').addClass(settings.dropdownClass).hide().appendTo($dropdown_parent); + $dropdown_content = $('
').addClass(settings.dropdownContentClass).appendTo($dropdown); + + $wrapper.css({ + width: self.$input[0].style.width, + display: self.$input.css('display') + }); - if (this.settings.placeholder) { - $control_input.attr('placeholder', this.settings.placeholder); - } + if (self.plugins.length) { + $wrapper.addClass('plugin-' + self.plugins.join(' plugin-')); + } - this.$wrapper = $wrapper; - this.$control = $control; - this.$control_input = $control_input; - this.$dropdown = $dropdown; - this.$dropdown_content = $dropdown_content; + inputMode = self.settings.mode; + $wrapper.toggleClass('single', inputMode === 'single'); + $wrapper.toggleClass('multi', inputMode === 'multi'); - $control.on('mousedown', function(e) { - if (!e.isDefaultPrevented()) { - window.setTimeout(function() { - self.focus(true); - }, 0); + if ((settings.maxItems === null || settings.maxItems > 1) && self.tagType === TAG_SELECT) { + self.$input.attr('multiple', 'multiple'); } - }); - // necessary for mobile webkit devices (manual focus triggering - // is ignored unless invoked within a click event) - $control.on('click', function(e) { - if (!self.isInputFocused) { - self.focus(true); + if (self.settings.placeholder) { + $control_input.attr('placeholder', settings.placeholder); } - }); - $dropdown.on('mouseenter', '[data-selectable]', function() { return self.onOptionHover.apply(self, arguments); }); - $dropdown.on('mousedown', '[data-selectable]', function() { return self.onOptionSelect.apply(self, arguments); }); - watchChildEvent($control, 'mousedown', '*:not(input)', function() { return self.onItemSelect.apply(self, arguments); }); - autoGrow($control_input); - - $control_input.on({ - mousedown : function(e) { e.stopPropagation(); }, - keydown : function() { return self.onKeyDown.apply(self, arguments); }, - keyup : function() { return self.onKeyUp.apply(self, arguments); }, - keypress : function() { return self.onKeyPress.apply(self, arguments); }, - resize : function() { self.positionDropdown.apply(self, []); }, - blur : function() { return self.onBlur.apply(self, arguments); }, - focus : function() { return self.onFocus.apply(self, arguments); } - }); + self.$wrapper = $wrapper; + self.$control = $control; + self.$control_input = $control_input; + self.$dropdown = $dropdown; + self.$dropdown_content = $dropdown_content; - $(document).on({ - keydown: function(e) { - self.isCmdDown = e[IS_MAC ? 'metaKey' : 'ctrlKey']; - self.isCtrlDown = e[IS_MAC ? 'altKey' : 'ctrlKey']; - self.isShiftDown = e.shiftKey; - }, - keyup: function(e) { - if (e.keyCode === KEY_CTRL) self.isCtrlDown = false; - if (e.keyCode === KEY_SHIFT) self.isShiftDown = false; - if (e.keyCode === KEY_CMD) self.isCmdDown = false; - }, - mousedown: function(e) { - if (self.isFocused) { - // prevent events on the dropdown scrollbar from causing the control to blur - if (e.target === self.$dropdown[0]) { - var ignoreFocus = self.ignoreFocus; - self.ignoreFocus = true; - window.setTimeout(function() { - self.ignoreFocus = ignoreFocus; - self.focus(false); - }, 0); - return; - } - // blur on click outside - if (!self.$control.has(e.target).length && e.target !== self.$control[0]) { - self.blur(); - } + $control.on('mousedown', function(e) { + if (!e.isDefaultPrevented()) { + window.setTimeout(function() { + self.focus(true); + }, 0); } - } - }); + }); - $(window).on({ - resize: function() { - if (self.isOpen) { - self.positionDropdown.apply(self, arguments); + // necessary for mobile webkit devices (manual focus triggering + // is ignored unless invoked within a click event) + $control.on('click', function(e) { + if (!self.isInputFocused) { + self.focus(true); } - }, - mousemove: function() { - self.ignoreHover = false; - } - }); + }); - this.$input.attr('tabindex',-1).hide().after(this.$wrapper); + $dropdown.on('mouseenter', '[data-selectable]', function() { return self.onOptionHover.apply(self, arguments); }); + $dropdown.on('mousedown', '[data-selectable]', function() { return self.onOptionSelect.apply(self, arguments); }); + watchChildEvent($control, 'mousedown', '*:not(input)', function() { return self.onItemSelect.apply(self, arguments); }); + autoGrow($control_input); + + $control_input.on({ + mousedown : function(e) { e.stopPropagation(); }, + keydown : function() { return self.onKeyDown.apply(self, arguments); }, + keyup : function() { return self.onKeyUp.apply(self, arguments); }, + keypress : function() { return self.onKeyPress.apply(self, arguments); }, + resize : function() { self.positionDropdown.apply(self, []); }, + blur : function() { return self.onBlur.apply(self, arguments); }, + focus : function() { return self.onFocus.apply(self, arguments); } + }); - if ($.isArray(this.settings.items)) { - this.setValue(this.settings.items); - delete this.settings.items; - } + $(document).on({ + keydown: function(e) { + self.isCmdDown = e[IS_MAC ? 'metaKey' : 'ctrlKey']; + self.isCtrlDown = e[IS_MAC ? 'altKey' : 'ctrlKey']; + self.isShiftDown = e.shiftKey; + }, + keyup: function(e) { + if (e.keyCode === KEY_CTRL) self.isCtrlDown = false; + if (e.keyCode === KEY_SHIFT) self.isShiftDown = false; + if (e.keyCode === KEY_CMD) self.isCmdDown = false; + }, + mousedown: function(e) { + if (self.isFocused) { + // prevent events on the dropdown scrollbar from causing the control to blur + if (e.target === self.$dropdown[0]) { + var ignoreFocus = self.ignoreFocus; + self.ignoreFocus = true; + window.setTimeout(function() { + self.ignoreFocus = ignoreFocus; + self.focus(false); + }, 0); + return; + } + // blur on click outside + if (!self.$control.has(e.target).length && e.target !== self.$control[0]) { + self.blur(); + } + } + } + }); - this.updateOriginalInput(); - this.refreshItems(); - this.updatePlaceholder(); - this.isSetup = true; + $(window).on({ + resize: function() { + if (self.isOpen) { + self.positionDropdown.apply(self, arguments); + } + }, + mousemove: function() { + self.ignoreHover = false; + } + }); - if (this.$input.is(':disabled')) { - this.disable(); - } + self.$input.attr('tabindex',-1).hide().after(self.$wrapper); - // preload options - if (this.settings.preload) { - this.onSearchChange(''); - } - }; + if ($.isArray(settings.items)) { + self.setValue(settings.items); + delete settings.items; + } - /** - * Maps fired events to callbacks provided - * in the settings used when creating the control. - */ - Selectize.prototype.setupCallbacks = function() { - var key, fn, callbacks = { - 'change' : 'onChange', - 'item_add' : 'onItemAdd', - 'item_remove' : 'onItemRemove', - 'clear' : 'onClear', - 'option_add' : 'onOptionAdd', - 'option_remove' : 'onOptionRemove', - 'option_clear' : 'onOptionClear', - 'dropdown_open' : 'onDropdownOpen', - 'dropdown_close' : 'onDropdownClose', - 'type' : 'onType' - }; + self.updateOriginalInput(); + self.refreshItems(); + self.updatePlaceholder(); + self.isSetup = true; - for (key in callbacks) { - if (callbacks.hasOwnProperty(key)) { - fn = this.settings[callbacks[key]]; - if (fn) this.on(key, fn); + if (self.$input.is(':disabled')) { + self.disable(); } - } - }; - /** - * Triggers a callback defined in the user-provided settings. - * Events: onItemAdd, onOptionAdd, etc - * - * @param {string} event - */ - Selectize.prototype.triggerCallback = function(event) { - var args; - if (typeof this.settings[event] === 'function') { - args = Array.prototype.slice.apply(arguments, [1]); - this.settings[event].apply(this, args); - } - }; + // preload options + if (settings.preload) { + self.onSearchChange(''); + } + }, - /** - * Triggered on keypress. - * - * @param {object} e - * @returns {boolean} - */ - Selectize.prototype.onKeyPress = function(e) { - if (this.isLocked) return e && e.preventDefault(); - var character = String.fromCharCode(e.keyCode || e.which); - if (this.settings.create && character === this.settings.delimiter) { - this.createItem(); - e.preventDefault(); - return false; - } - }; + /** + * Maps fired events to callbacks provided + * in the settings used when creating the control. + */ + setupCallbacks: function() { + var key, fn, callbacks = { + 'change' : 'onChange', + 'item_add' : 'onItemAdd', + 'item_remove' : 'onItemRemove', + 'clear' : 'onClear', + 'option_add' : 'onOptionAdd', + 'option_remove' : 'onOptionRemove', + 'option_clear' : 'onOptionClear', + 'dropdown_open' : 'onDropdownOpen', + 'dropdown_close' : 'onDropdownClose', + 'type' : 'onType' + }; - /** - * Triggered on keydown. - * - * @param {object} e - * @returns {boolean} - */ - Selectize.prototype.onKeyDown = function(e) { - var isInput = e.target === this.$control_input[0]; + for (key in callbacks) { + if (callbacks.hasOwnProperty(key)) { + fn = this.settings[callbacks[key]]; + if (fn) this.on(key, fn); + } + } + }, + + /** + * Triggers a callback defined in the user-provided settings. + * Events: onItemAdd, onOptionAdd, etc + * + * @param {string} event + */ + triggerCallback: function(event) { + var args; + if (typeof this.settings[event] === 'function') { + args = Array.prototype.slice.apply(arguments, [1]); + this.settings[event].apply(this, args); + } + }, - if (this.isLocked) { - if (e.keyCode !== KEY_TAB) { + /** + * Triggered on keypress. + * + * @param {object} e + * @returns {boolean} + */ + onKeyPress: function(e) { + if (this.isLocked) return e && e.preventDefault(); + var character = String.fromCharCode(e.keyCode || e.which); + if (this.settings.create && character === this.settings.delimiter) { + this.createItem(); e.preventDefault(); + return false; } - return; - } + }, + + /** + * Triggered on keydown. + * + * @param {object} e + * @returns {boolean} + */ + onKeyDown: function(e) { + var isInput = e.target === this.$control_input[0]; + var self = this; - switch (e.keyCode) { - case KEY_A: - if (this.isCmdDown) { - this.selectAll(); + if (self.isLocked) { + if (e.keyCode !== KEY_TAB) { e.preventDefault(); - return; - } - break; - case KEY_ESC: - this.blur(); - return; - case KEY_DOWN: - if (!this.isOpen && this.hasOptions) { - this.open(); - } else if (this.$activeOption) { - this.ignoreHover = true; - var $next = this.getAdjacentOption(this.$activeOption, 1); - if ($next.length) this.setActiveOption($next, true, true); - } - e.preventDefault(); - return; - case KEY_UP: - if (this.$activeOption) { - this.ignoreHover = true; - var $prev = this.getAdjacentOption(this.$activeOption, -1); - if ($prev.length) this.setActiveOption($prev, true, true); - } - e.preventDefault(); - return; - case KEY_RETURN: - if (this.$activeOption) { - this.onOptionSelect({currentTarget: this.$activeOption}); } - e.preventDefault(); - return; - case KEY_LEFT: - this.advanceSelection(-1, e); return; - case KEY_RIGHT: - this.advanceSelection(1, e); - return; - case KEY_TAB: - if (this.settings.create && $.trim(this.$control_input.val()).length) { - this.createItem(); + } + + switch (e.keyCode) { + case KEY_A: + if (self.isCmdDown) { + self.selectAll(); + e.preventDefault(); + return; + } + break; + case KEY_ESC: + self.blur(); + return; + case KEY_DOWN: + if (!self.isOpen && self.hasOptions) { + self.open(); + } else if (self.$activeOption) { + self.ignoreHover = true; + var $next = self.getAdjacentOption(self.$activeOption, 1); + if ($next.length) self.setActiveOption($next, true, true); + } e.preventDefault(); - } - return; - case KEY_BACKSPACE: - case KEY_DELETE: - this.deleteSelection(e); + return; + case KEY_UP: + if (self.$activeOption) { + self.ignoreHover = true; + var $prev = self.getAdjacentOption(self.$activeOption, -1); + if ($prev.length) self.setActiveOption($prev, true, true); + } + e.preventDefault(); + return; + case KEY_RETURN: + if (self.$activeOption) { + self.onOptionSelect({currentTarget: self.$activeOption}); + } + e.preventDefault(); + return; + case KEY_LEFT: + self.advanceSelection(-1, e); + return; + case KEY_RIGHT: + self.advanceSelection(1, e); + return; + case KEY_TAB: + if (self.settings.create && $.trim(self.$control_input.val()).length) { + self.createItem(); + e.preventDefault(); + } + return; + case KEY_BACKSPACE: + case KEY_DELETE: + self.deleteSelection(e); + return; + } + if (self.isFull() || self.isInputHidden) { + e.preventDefault(); return; - } - if (this.isFull() || this.isInputHidden) { - e.preventDefault(); - return; - } - }; + } + }, - /** - * Triggered on keyup. - * - * @param {object} e - * @returns {boolean} - */ - Selectize.prototype.onKeyUp = function(e) { - if (this.isLocked) return e && e.preventDefault(); - var value = this.$control_input.val() || ''; - if (this.lastValue !== value) { - this.lastValue = value; - this.onSearchChange(value); - this.refreshOptions(); - this.trigger('type', value); - } - }; + /** + * Triggered on keyup. + * + * @param {object} e + * @returns {boolean} + */ + onKeyUp: function(e) { + var self = this; - /** - * Invokes the user-provide option provider / loader. - * - * Note: this function is debounced in the Selectize - * constructor (by `settings.loadDelay` milliseconds) - * - * @param {string} value - */ - Selectize.prototype.onSearchChange = function(value) { - var self = this; - var fn = self.settings.load; - if (!fn) return; - if (self.loadedSearches.hasOwnProperty(value)) return; - self.loadedSearches[value] = true; - self.load(function(callback) { - fn.apply(self, [value, callback]); - }); - }; + if (self.isLocked) return e && e.preventDefault(); + var value = self.$control_input.val() || ''; + if (self.lastValue !== value) { + self.lastValue = value; + self.onSearchChange(value); + self.refreshOptions(); + self.trigger('type', value); + } + }, - /** - * Triggered on focus. - * - * @param {object} e (optional) - * @returns {boolean} - */ - Selectize.prototype.onFocus = function(e) { - this.isInputFocused = true; - this.isFocused = true; - if (this.isDisabled) { - this.blur(); - e.preventDefault(); - return false; - } + /** + * Invokes the user-provide option provider / loader. + * + * Note: this function is debounced in the Selectize + * constructor (by `settings.loadDelay` milliseconds) + * + * @param {string} value + */ + onSearchChange: function(value) { + var self = this; + var fn = self.settings.load; + if (!fn) return; + if (self.loadedSearches.hasOwnProperty(value)) return; + self.loadedSearches[value] = true; + self.load(function(callback) { + fn.apply(self, [value, callback]); + }); + }, - if (this.ignoreFocus) return; - if (this.settings.preload === 'focus') this.onSearchChange(''); + /** + * Triggered on focus. + * + * @param {object} e (optional) + * @returns {boolean} + */ + onFocus: function(e) { + var self = this; - this.showInput(); - this.setActiveItem(null); - this.refreshOptions(!!this.settings.openOnFocus); - this.refreshClasses(); - }; + self.isInputFocused = true; + self.isFocused = true; + if (self.isDisabled) { + self.blur(); + e.preventDefault(); + return false; + } - /** - * Triggered on blur. - * - * @param {object} e - * @returns {boolean} - */ - Selectize.prototype.onBlur = function(e) { - this.isInputFocused = false; - if (this.ignoreFocus) return; - - this.close(); - this.setTextboxValue(''); - this.setActiveItem(null); - this.setActiveOption(null); - this.setCaret(this.items.length); - this.isFocused = false; - this.refreshClasses(); - }; + if (self.ignoreFocus) return; + if (self.settings.preload === 'focus') self.onSearchChange(''); - /** - * Triggered when the user rolls over - * an option in the autocomplete dropdown menu. - * - * @param {object} e - * @returns {boolean} - */ - Selectize.prototype.onOptionHover = function(e) { - if (this.ignoreHover) return; - this.setActiveOption(e.currentTarget, false); - }; + self.showInput(); + self.setActiveItem(null); + self.refreshOptions(!!self.settings.openOnFocus); + self.refreshClasses(); + }, - /** - * Triggered when the user clicks on an option - * in the autocomplete dropdown menu. - * - * @param {object} e - * @returns {boolean} - */ - Selectize.prototype.onOptionSelect = function(e) { - e.preventDefault && e.preventDefault(); - e.stopPropagation && e.stopPropagation(); - this.focus(false); - - var $target = $(e.currentTarget); - if ($target.hasClass('create')) { - this.createItem(); - } else { - var value = $target.attr('data-value'); - if (value) { - this.setTextboxValue(''); - this.addItem(value); - } - } - }; + /** + * Triggered on blur. + * + * @param {object} e + * @returns {boolean} + */ + onBlur: function(e) { + var self = this; + self.isInputFocused = false; + if (self.ignoreFocus) return; - /** - * Triggered when the user clicks on an item - * that has been selected. - * - * @param {object} e - * @returns {boolean} - */ - Selectize.prototype.onItemSelect = function(e) { - if (this.settings.mode === 'multi') { - e.preventDefault(); - this.setActiveItem(e.currentTarget, e); - this.focus(false); - this.hideInput(); - } - }; + self.close(); + self.setTextboxValue(''); + self.setActiveItem(null); + self.setActiveOption(null); + self.setCaret(self.items.length); + self.isFocused = false; + self.refreshClasses(); + }, - /** - * Invokes the provided method that provides - * results to a callback---which are then added - * as options to the control. - * - * @param {function} fn - */ - Selectize.prototype.load = function(fn) { - var self = this; - var $wrapper = self.$wrapper.addClass('loading'); + /** + * Triggered when the user rolls over + * an option in the autocomplete dropdown menu. + * + * @param {object} e + * @returns {boolean} + */ + onOptionHover: function(e) { + if (this.ignoreHover) return; + this.setActiveOption(e.currentTarget, false); + }, - self.loading++; - fn.apply(self, [function(results) { - self.loading = Math.max(self.loading - 1, 0); - if (results && results.length) { - self.addOption(results); - self.refreshOptions(false); - if (self.isInputFocused) self.open(); + /** + * Triggered when the user clicks on an option + * in the autocomplete dropdown menu. + * + * @param {object} e + * @returns {boolean} + */ + onOptionSelect: function(e) { + e.preventDefault && e.preventDefault(); + e.stopPropagation && e.stopPropagation(); + this.focus(false); + + var $target = $(e.currentTarget); + if ($target.hasClass('create')) { + this.createItem(); + } else { + var value = $target.attr('data-value'); + if (value) { + this.setTextboxValue(''); + this.addItem(value); + } } - if (!self.loading) { - $wrapper.removeClass('loading'); + }, + + /** + * Triggered when the user clicks on an item + * that has been selected. + * + * @param {object} e + * @returns {boolean} + */ + onItemSelect: function(e) { + var self = this; + + if (self.settings.mode === 'multi') { + e.preventDefault(); + self.setActiveItem(e.currentTarget, e); + self.focus(false); + self.hideInput(); } - self.trigger('load', results); - }]); - }; + }, - /** - * Sets the input field of the control to the specified value. - * - * @param {string} value - */ - Selectize.prototype.setTextboxValue = function(value) { - this.$control_input.val(value).triggerHandler('update'); - this.lastValue = value; - }; + /** + * Invokes the provided method that provides + * results to a callback---which are then added + * as options to the control. + * + * @param {function} fn + */ + load: function(fn) { + var self = this; + var $wrapper = self.$wrapper.addClass('loading'); + + self.loading++; + fn.apply(self, [function(results) { + self.loading = Math.max(self.loading - 1, 0); + if (results && results.length) { + self.addOption(results); + self.refreshOptions(false); + if (self.isInputFocused) self.open(); + } + if (!self.loading) { + $wrapper.removeClass('loading'); + } + self.trigger('load', results); + }]); + }, - /** - * Returns the value of the control. If multiple items - * can be selected (e.g. ), this returns + * an array. If only one item can be selected, this + * returns a string. + * + * @returns {mixed} + */ + getValue: function() { + if (this.tagType === TAG_SELECT && this.$input.attr('multiple')) { + return this.items; + } else { + return this.items.join(this.settings.delimiter); } - }); - }; - - /** - * Sets the selected item. - * - * @param {object} $item - * @param {object} e (optional) - */ - Selectize.prototype.setActiveItem = function($item, e) { - var eventName; - var i, idx, begin, end, item, swap; - var $last; - - $item = $($item); - - // clear the active selection - if (!$item.length) { - $(this.$activeItems).removeClass('active'); - this.$activeItems = []; - this.isFocused = this.isInputFocused; - return; - } + }, - // modify selection - eventName = e && e.type.toLowerCase(); - - if (eventName === 'mousedown' && this.isShiftDown && this.$activeItems.length) { - $last = this.$control.children('.active:last'); - begin = Array.prototype.indexOf.apply(this.$control[0].childNodes, [$last[0]]); - end = Array.prototype.indexOf.apply(this.$control[0].childNodes, [$item[0]]); - if (begin > end) { - swap = begin; - begin = end; - end = swap; - } - for (i = begin; i <= end; i++) { - item = this.$control[0].childNodes[i]; - if (this.$activeItems.indexOf(item) === -1) { - $(item).addClass('active'); - this.$activeItems.push(item); + /** + * Resets the selected items to the given value. + * + * @param {mixed} value + */ + setValue: function(value) { + debounce_events(this, ['change'], function() { + this.clear(); + var items = $.isArray(value) ? value : [value]; + for (var i = 0, n = items.length; i < n; i++) { + this.addItem(items[i]); } + }); + }, + + /** + * Sets the selected item. + * + * @param {object} $item + * @param {object} e (optional) + */ + setActiveItem: function($item, e) { + var self = this; + var eventName; + var i, idx, begin, end, item, swap; + var $last; + + $item = $($item); + + // clear the active selection + if (!$item.length) { + $(self.$activeItems).removeClass('active'); + self.$activeItems = []; + self.isFocused = self.isInputFocused; + return; } - e.preventDefault(); - } else if ((eventName === 'mousedown' && this.isCtrlDown) || (eventName === 'keydown' && this.isShiftDown)) { - if ($item.hasClass('active')) { - idx = this.$activeItems.indexOf($item[0]); - this.$activeItems.splice(idx, 1); - $item.removeClass('active'); + + // modify selection + eventName = e && e.type.toLowerCase(); + + if (eventName === 'mousedown' && self.isShiftDown && self.$activeItems.length) { + $last = self.$control.children('.active:last'); + begin = Array.prototype.indexOf.apply(self.$control[0].childNodes, [$last[0]]); + end = Array.prototype.indexOf.apply(self.$control[0].childNodes, [$item[0]]); + if (begin > end) { + swap = begin; + begin = end; + end = swap; + } + for (i = begin; i <= end; i++) { + item = self.$control[0].childNodes[i]; + if (self.$activeItems.indexOf(item) === -1) { + $(item).addClass('active'); + self.$activeItems.push(item); + } + } + e.preventDefault(); + } else if ((eventName === 'mousedown' && self.isCtrlDown) || (eventName === 'keydown' && this.isShiftDown)) { + if ($item.hasClass('active')) { + idx = self.$activeItems.indexOf($item[0]); + self.$activeItems.splice(idx, 1); + $item.removeClass('active'); + } else { + self.$activeItems.push($item.addClass('active')[0]); + } } else { - this.$activeItems.push($item.addClass('active')[0]); + $(self.$activeItems).removeClass('active'); + self.$activeItems = [$item.addClass('active')[0]]; } - } else { - $(this.$activeItems).removeClass('active'); - this.$activeItems = [$item.addClass('active')[0]]; - } - this.isFocused = !!this.$activeItems.length || this.isInputFocused; - }; + self.isFocused = !!self.$activeItems.length || self.isInputFocused; + }, - /** - * Sets the selected item in the dropdown menu - * of available options. - * - * @param {object} $object - * @param {boolean} scroll - * @param {boolean} animate - */ - Selectize.prototype.setActiveOption = function($option, scroll, animate) { - var height_menu, height_item, y; - var scroll_top, scroll_bottom; + /** + * Sets the selected item in the dropdown menu + * of available options. + * + * @param {object} $object + * @param {boolean} scroll + * @param {boolean} animate + */ + setActiveOption: function($option, scroll, animate) { + var height_menu, height_item, y; + var scroll_top, scroll_bottom; + var self = this; - if (this.$activeOption) this.$activeOption.removeClass('active'); - this.$activeOption = null; + if (self.$activeOption) self.$activeOption.removeClass('active'); + self.$activeOption = null; - $option = $($option); - if (!$option.length) return; + $option = $($option); + if (!$option.length) return; - this.$activeOption = $option.addClass('active'); + self.$activeOption = $option.addClass('active'); - if (scroll || !isset(scroll)) { + if (scroll || !isset(scroll)) { - height_menu = this.$dropdown.height(); - height_item = this.$activeOption.outerHeight(true); - scroll = this.$dropdown.scrollTop() || 0; - y = this.$activeOption.offset().top - this.$dropdown.offset().top + scroll; - scroll_top = y; - scroll_bottom = y - height_menu + height_item; + height_menu = self.$dropdown.height(); + height_item = self.$activeOption.outerHeight(true); + scroll = self.$dropdown.scrollTop() || 0; + y = self.$activeOption.offset().top - self.$dropdown.offset().top + scroll; + scroll_top = y; + scroll_bottom = y - height_menu + height_item; + + if (y + height_item > height_menu - scroll) { + self.$dropdown.stop().animate({scrollTop: scroll_bottom}, animate ? self.settings.scrollDuration : 0); + } else if (y < scroll) { + self.$dropdown.stop().animate({scrollTop: scroll_top}, animate ? self.settings.scrollDuration : 0); + } - if (y + height_item > height_menu - scroll) { - this.$dropdown.stop().animate({scrollTop: scroll_bottom}, animate ? this.settings.scrollDuration : 0); - } else if (y < scroll) { - this.$dropdown.stop().animate({scrollTop: scroll_top}, animate ? this.settings.scrollDuration : 0); } + }, - } - }; + /** + * Selects all items (CTRL + A). + */ + selectAll: function() { + this.$activeItems = Array.prototype.slice.apply(this.$control.children(':not(input)').addClass('active')); + this.isFocused = true; + if (this.$activeItems.length) this.hideInput(); + }, - /** - * Selects all items (CTRL + A). - */ - Selectize.prototype.selectAll = function() { - this.$activeItems = Array.prototype.slice.apply(this.$control.children(':not(input)').addClass('active')); - this.isFocused = true; - if (this.$activeItems.length) this.hideInput(); - }; + /** + * Hides the input element out of view, while + * retaining its focus. + */ + hideInput: function() { + var self = this; - /** - * Hides the input element out of view, while - * retaining its focus. - */ - Selectize.prototype.hideInput = function() { - this.close(); - this.setTextboxValue(''); - this.$control_input.css({opacity: 0, position: 'absolute', left: -10000}); - this.isInputHidden = true; - }; + self.close(); + self.setTextboxValue(''); + self.$control_input.css({opacity: 0, position: 'absolute', left: -10000}); + self.isInputHidden = true; + }, - /** - * Restores input visibility. - */ - Selectize.prototype.showInput = function() { - this.$control_input.css({opacity: 1, position: 'relative', left: 0}); - this.isInputHidden = false; - }; + /** + * Restores input visibility. + */ + showInput: function() { + this.$control_input.css({opacity: 1, position: 'relative', left: 0}); + this.isInputHidden = false; + }, - /** - * Gives the control focus. If "trigger" is falsy, - * focus handlers won't be fired--causing the focus - * to happen silently in the background. - * - * @param {boolean} trigger - */ - Selectize.prototype.focus = function(trigger) { - if (this.isDisabled) return; - var self = this; - self.ignoreFocus = true; - self.$control_input[0].focus(); - self.isInputFocused = true; - window.setTimeout(function() { - self.ignoreFocus = false; - if (trigger) self.onFocus(); - }, 0); - }; + /** + * Gives the control focus. If "trigger" is falsy, + * focus handlers won't be fired--causing the focus + * to happen silently in the background. + * + * @param {boolean} trigger + */ + focus: function(trigger) { + var self = this; - /** - * Forces the control out of focus. - */ - Selectize.prototype.blur = function() { - this.$control_input.trigger('blur'); - }; + if (self.isDisabled) return; + self.ignoreFocus = true; + self.$control_input[0].focus(); + self.isInputFocused = true; + window.setTimeout(function() { + self.ignoreFocus = false; + if (trigger) self.onFocus(); + }, 0); + }, - /** - * Splits a search string into an array of - * individual regexps to be used to match results. - * - * @param {string} query - * @returns {array} - */ - Selectize.prototype.parseSearchTokens = function(query) { - query = $.trim(String(query || '').toLowerCase()); - if (!query || !query.length) return []; - - var i, n, regex, letter; - var tokens = []; - var words = query.split(/ +/); - - for (i = 0, n = words.length; i < n; i++) { - regex = quoteRegExp(words[i]); - if (this.settings.diacritics) { - for (letter in DIACRITICS) { - if (DIACRITICS.hasOwnProperty(letter)) { - regex = regex.replace(new RegExp(letter, 'g'), DIACRITICS[letter]); + /** + * Forces the control out of focus. + */ + blur: function() { + this.$control_input.trigger('blur'); + }, + + /** + * Splits a search string into an array of + * individual regexps to be used to match results. + * + * @param {string} query + * @returns {array} + */ + parseSearchTokens: function(query) { + query = $.trim(String(query || '').toLowerCase()); + if (!query || !query.length) return []; + + var i, n, regex, letter; + var tokens = []; + var words = query.split(/ +/); + + for (i = 0, n = words.length; i < n; i++) { + regex = escape_regex(words[i]); + if (this.settings.diacritics) { + for (letter in DIACRITICS) { + if (DIACRITICS.hasOwnProperty(letter)) { + regex = regex.replace(new RegExp(letter, 'g'), DIACRITICS[letter]); + } } } + tokens.push({ + string : words[i], + regex : new RegExp(regex, 'i') + }); } - tokens.push({ - string : words[i], - regex : new RegExp(regex, 'i') - }); - } - return tokens; - }; + return tokens; + }, - /** - * Returns a function to be used to score individual results. - * Results will be sorted by the score (descending). Scores less - * than or equal to zero (no match) will not be included in the results. - * - * @param {object} data - * @param {object} search - * @returns {function} - */ - Selectize.prototype.getScoreFunction = function(search) { - var self = this; - var tokens = search.tokens; - - var calculateFieldScore = (function() { - if (!tokens.length) { - return function() { return 0; }; - } else if (tokens.length === 1) { - return function(value) { - var score, pos; - - value = String(value || '').toLowerCase(); - pos = value.search(tokens[0].regex); - if (pos === -1) return 0; - score = tokens[0].string.length / value.length; - if (pos === 0) score += 0.5; - return score; - }; - } else { - return function(value) { - var score, pos, i, j; + /** + * Returns a function to be used to score individual results. + * Results will be sorted by the score (descending). Scores less + * than or equal to zero (no match) will not be included in the results. + * + * @param {object} data + * @param {object} search + * @returns {function} + */ + getScoreFunction: function(search) { + var self = this; + var tokens = search.tokens; - value = String(value || '').toLowerCase(); - score = 0; - for (i = 0, j = tokens.length; i < j; i++) { - pos = value.search(tokens[i].regex); + var calculateFieldScore = (function() { + if (!tokens.length) { + return function() { return 0; }; + } else if (tokens.length === 1) { + return function(value) { + var score, pos; + + value = String(value || '').toLowerCase(); + pos = value.search(tokens[0].regex); if (pos === -1) return 0; + score = tokens[0].string.length / value.length; if (pos === 0) score += 0.5; - score += tokens[i].string.length / value.length; - } - return score / tokens.length; - }; - } - })(); + return score; + }; + } else { + return function(value) { + var score, pos, i, j; + + value = String(value || '').toLowerCase(); + score = 0; + for (i = 0, j = tokens.length; i < j; i++) { + pos = value.search(tokens[i].regex); + if (pos === -1) return 0; + if (pos === 0) score += 0.5; + score += tokens[i].string.length / value.length; + } + return score / tokens.length; + }; + } + })(); - var calculateScore = (function() { - var fields = self.settings.searchField; - if (typeof fields === 'string') { - fields = [fields]; - } - if (!fields || !fields.length) { - return function() { return 0; }; - } else if (fields.length === 1) { - var field = fields[0]; - return function(data) { - if (!data.hasOwnProperty(field)) return 0; - return calculateFieldScore(data[field]); - }; - } else { - return function(data) { - var n = 0; - var score = 0; - for (var i = 0, j = fields.length; i < j; i++) { - if (data.hasOwnProperty(fields[i])) { - score += calculateFieldScore(data[fields[i]]); - n++; + var calculateScore = (function() { + var fields = self.settings.searchField; + if (typeof fields === 'string') { + fields = [fields]; + } + if (!fields || !fields.length) { + return function() { return 0; }; + } else if (fields.length === 1) { + var field = fields[0]; + return function(data) { + if (!data.hasOwnProperty(field)) return 0; + return calculateFieldScore(data[field]); + }; + } else { + return function(data) { + var n = 0; + var score = 0; + for (var i = 0, j = fields.length; i < j; i++) { + if (data.hasOwnProperty(fields[i])) { + score += calculateFieldScore(data[fields[i]]); + n++; + } } - } - return score / n; - }; - } - })(); + return score / n; + }; + } + })(); - return calculateScore; - }; + return calculateScore; + }, - /** - * Searches through available options and returns - * a sorted array of matches. Includes options that - * have already been selected. - * - * The `settings` parameter can contain: - * - * - searchField - * - sortField - * - sortDirection - * - * Returns an object containing: - * - * - query {string} - * - tokens {array} - * - total {int} - * - items {array} - * - * @param {string} query - * @param {object} settings - * @returns {object} - */ - Selectize.prototype.search = function(query, settings) { - var self = this; - var value, score, search, calculateScore; + /** + * Searches through available options and returns + * a sorted array of matches. Includes options that + * have already been selected. + * + * The `settings` parameter can contain: + * + * - searchField + * - sortField + * - sortDirection + * + * Returns an object containing: + * + * - query {string} + * - tokens {array} + * - total {int} + * - items {array} + * + * @param {string} query + * @param {object} settings + * @returns {object} + */ + search: function(query, settings) { + var self = this; + var value, score, search, calculateScore; - settings = settings || {}; - query = $.trim(String(query || '').toLowerCase()); + settings = settings || {}; + query = $.trim(String(query || '').toLowerCase()); - if (query !== this.lastQuery) { - this.lastQuery = query; + if (query !== self.lastQuery) { + self.lastQuery = query; - search = { - query : query, - tokens : this.parseSearchTokens(query), - total : 0, - items : [] - }; + search = { + query : query, + tokens : self.parseSearchTokens(query), + total : 0, + items : [] + }; - // generate result scoring function - if (this.settings.score) { - calculateScore = this.settings.score.apply(this, [search]); - if (typeof calculateScore !== 'function') { - throw new Error('Selectize "score" setting must be a function that returns a function'); + // generate result scoring function + if (self.settings.score) { + calculateScore = self.settings.score.apply(this, [search]); + if (typeof calculateScore !== 'function') { + throw new Error('Selectize "score" setting must be a function that returns a function'); + } + } else { + calculateScore = self.getScoreFunction(search); } - } else { - calculateScore = this.getScoreFunction(search); - } - // perform search and sort - if (query.length) { - for (value in this.options) { - if (this.options.hasOwnProperty(value)) { - score = calculateScore(this.options[value]); - if (score > 0) { + // perform search and sort + if (query.length) { + for (value in self.options) { + if (self.options.hasOwnProperty(value)) { + score = calculateScore(self.options[value]); + if (score > 0) { + search.items.push({ + score: score, + value: value + }); + } + } + } + search.items.sort(function(a, b) { + return b.score - a.score; + }); + } else { + for (value in self.options) { + if (self.options.hasOwnProperty(value)) { search.items.push({ - score: score, + score: 1, value: value }); } } - } - search.items.sort(function(a, b) { - return b.score - a.score; - }); - } else { - for (value in this.options) { - if (this.options.hasOwnProperty(value)) { - search.items.push({ - score: 1, - value: value - }); + if (self.settings.sortField) { + search.items.sort((function() { + var field = self.settings.sortField; + var multiplier = self.settings.sortDirection === 'desc' ? -1 : 1; + return function(a, b) { + a = a && String(self.options[a.value][field] || '').toLowerCase(); + b = b && String(self.options[b.value][field] || '').toLowerCase(); + if (a > b) return 1 * multiplier; + if (b > a) return -1 * multiplier; + return 0; + }; + })()); } } - if (this.settings.sortField) { - search.items.sort((function() { - var field = self.settings.sortField; - var multiplier = self.settings.sortDirection === 'desc' ? -1 : 1; - return function(a, b) { - a = a && String(self.options[a.value][field] || '').toLowerCase(); - b = b && String(self.options[b.value][field] || '').toLowerCase(); - if (a > b) return 1 * multiplier; - if (b > a) return -1 * multiplier; - return 0; - }; - })()); - } + self.currentResults = search; + } else { + search = $.extend(true, {}, self.currentResults); } - this.currentResults = search; - } else { - search = $.extend(true, {}, this.currentResults); - } - // apply limits and return - return this.prepareResults(search, settings); - }; + // apply limits and return + return self.prepareResults(search, settings); + }, - /** - * Filters out any items that have already been selected - * and applies search limits. - * - * @param {object} results - * @param {object} settings - * @returns {object} - */ - Selectize.prototype.prepareResults = function(search, settings) { - if (this.settings.hideSelected) { - for (var i = search.items.length - 1; i >= 0; i--) { - if (this.items.indexOf(String(search.items[i].value)) !== -1) { - search.items.splice(i, 1); + /** + * Filters out any items that have already been selected + * and applies search limits. + * + * @param {object} results + * @param {object} settings + * @returns {object} + */ + prepareResults: function(search, settings) { + if (this.settings.hideSelected) { + for (var i = search.items.length - 1; i >= 0; i--) { + if (this.items.indexOf(String(search.items[i].value)) !== -1) { + search.items.splice(i, 1); + } } } - } - - search.total = search.items.length; - if (typeof settings.limit === 'number') { - search.items = search.items.slice(0, settings.limit); - } - - return search; - }; - /** - * Refreshes the list of available options shown - * in the autocomplete dropdown menu. - * - * @param {boolean} triggerDropdown - */ - Selectize.prototype.refreshOptions = function(triggerDropdown) { - if (typeof triggerDropdown === 'undefined') { - triggerDropdown = true; - } - - var i, n, groups, groups_order, option, optgroup, html, html_children; - var hasCreateOption; - var query = this.$control_input.val(); - var results = this.search(query, {}); - var $active, $create; - var $dropdown_content = this.$dropdown_content; - - // build markup - n = results.items.length; - if (typeof this.settings.maxOptions === 'number') { - n = Math.min(n, this.settings.maxOptions); - } + search.total = search.items.length; + if (typeof settings.limit === 'number') { + search.items = search.items.slice(0, settings.limit); + } - // render and group available options individually - groups = {}; + return search; + }, - if (this.settings.optgroupOrder) { - groups_order = this.settings.optgroupOrder; - for (i = 0; i < groups_order.length; i++) { - groups[groups_order[i]] = []; + /** + * Refreshes the list of available options shown + * in the autocomplete dropdown menu. + * + * @param {boolean} triggerDropdown + */ + refreshOptions: function(triggerDropdown) { + if (typeof triggerDropdown === 'undefined') { + triggerDropdown = true; } - } else { - groups_order = []; - } - for (i = 0; i < n; i++) { - option = this.options[results.items[i].value]; - optgroup = option[this.settings.optgroupField] || ''; - if (!this.optgroups.hasOwnProperty(optgroup)) { - optgroup = ''; - } - if (!groups.hasOwnProperty(optgroup)) { - groups[optgroup] = []; - groups_order.push(optgroup); + var self = this; + var i, n, groups, groups_order, option, optgroup, html, html_children; + var hasCreateOption; + var query = self.$control_input.val(); + var results = self.search(query, {}); + var $active, $create; + var $dropdown_content = self.$dropdown_content; + + // build markup + n = results.items.length; + if (typeof self.settings.maxOptions === 'number') { + n = Math.min(n, self.settings.maxOptions); + } + + // render and group available options individually + groups = {}; + + if (self.settings.optgroupOrder) { + groups_order = self.settings.optgroupOrder; + for (i = 0; i < groups_order.length; i++) { + groups[groups_order[i]] = []; + } + } else { + groups_order = []; } - groups[optgroup].push(this.render('option', option)); - } - // render optgroup headers & join groups - html = []; - for (i = 0, n = groups_order.length; i < n; i++) { - optgroup = groups_order[i]; - if (this.optgroups.hasOwnProperty(optgroup) && groups[optgroup].length) { - // render the optgroup header and options within it, - // then pass it to the wrapper template - html_children = this.render('optgroup_header', this.optgroups[optgroup]) || ''; - html_children += groups[optgroup].join(''); - html.push(this.render('optgroup', $.extend({}, this.optgroups[optgroup], { - html: html_children - }))); - } else { - html.push(groups[optgroup].join('')); + for (i = 0; i < n; i++) { + option = self.options[results.items[i].value]; + optgroup = option[self.settings.optgroupField] || ''; + if (!self.optgroups.hasOwnProperty(optgroup)) { + optgroup = ''; + } + if (!groups.hasOwnProperty(optgroup)) { + groups[optgroup] = []; + groups_order.push(optgroup); + } + groups[optgroup].push(self.render('option', option)); + } + + // render optgroup headers & join groups + html = []; + for (i = 0, n = groups_order.length; i < n; i++) { + optgroup = groups_order[i]; + if (self.optgroups.hasOwnProperty(optgroup) && groups[optgroup].length) { + // render the optgroup header and options within it, + // then pass it to the wrapper template + html_children = self.render('optgroup_header', self.optgroups[optgroup]) || ''; + html_children += groups[optgroup].join(''); + html.push(self.render('optgroup', $.extend({}, self.optgroups[optgroup], { + html: html_children + }))); + } else { + html.push(groups[optgroup].join('')); + } } - } - $dropdown_content.html(html.join('')); + $dropdown_content.html(html.join('')); - // highlight matching terms inline - if (this.settings.highlight && results.query.length && results.tokens.length) { - for (i = 0, n = results.tokens.length; i < n; i++) { - highlight($dropdown_content, results.tokens[i].regex); + // highlight matching terms inline + if (self.settings.highlight && results.query.length && results.tokens.length) { + for (i = 0, n = results.tokens.length; i < n; i++) { + highlight($dropdown_content, results.tokens[i].regex); + } } - } - // add "selected" class to selected options - if (!this.settings.hideSelected) { - for (i = 0, n = this.items.length; i < n; i++) { - this.getOption(this.items[i]).addClass('selected'); + // add "selected" class to selected options + if (!self.settings.hideSelected) { + for (i = 0, n = self.items.length; i < n; i++) { + self.getOption(self.items[i]).addClass('selected'); + } } - } - // add create option - hasCreateOption = this.settings.create && results.query.length; - if (hasCreateOption) { - $dropdown_content.prepend(this.render('option_create', {input: query})); - $create = $($dropdown_content[0].childNodes[0]); - } + // add create option + hasCreateOption = self.settings.create && results.query.length; + if (hasCreateOption) { + $dropdown_content.prepend(self.render('option_create', {input: query})); + $create = $($dropdown_content[0].childNodes[0]); + } - // activate - this.hasOptions = results.items.length > 0 || hasCreateOption; - if (this.hasOptions) { - if (results.items.length > 0) { - if ($create) { - $active = this.getAdjacentOption($create, 1); + // activate + self.hasOptions = results.items.length > 0 || hasCreateOption; + if (self.hasOptions) { + if (results.items.length > 0) { + if ($create) { + $active = self.getAdjacentOption($create, 1); + } else { + $active = $dropdown_content.find("[data-selectable]").first(); + } } else { - $active = $dropdown_content.find("[data-selectable]").first(); + $active = $create; } + self.setActiveOption($active); + if (triggerDropdown && !self.isOpen) { self.open(); } } else { - $active = $create; + self.setActiveOption(null); + if (triggerDropdown && self.isOpen) { self.close(); } } - this.setActiveOption($active); - if (triggerDropdown && !this.isOpen) { this.open(); } - } else { - this.setActiveOption(null); - if (triggerDropdown && this.isOpen) { this.close(); } - } - }; + }, - /** - * Adds an available option. If it already exists, - * nothing will happen. Note: this does not refresh - * the options list dropdown (use `refreshOptions` - * for that). - * - * Usage: - * - * this.addOption(value, data) - * this.addOption(data) - * - * @param {string} value - * @param {object} data - */ - Selectize.prototype.addOption = function(value, data) { - var i, n, optgroup; + /** + * Adds an available option. If it already exists, + * nothing will happen. Note: this does not refresh + * the options list dropdown (use `refreshOptions` + * for that). + * + * Usage: + * + * this.addOption(value, data) + * this.addOption(data) + * + * @param {string} value + * @param {object} data + */ + addOption: function(value, data) { + var i, n, optgroup, self = this; - if ($.isArray(value)) { - for (i = 0, n = value.length; i < n; i++) { - this.addOption(value[i][this.settings.valueField], value[i]); + if ($.isArray(value)) { + for (i = 0, n = value.length; i < n; i++) { + self.addOption(value[i][self.settings.valueField], value[i]); + } + return; } - return; - } - value = value || ''; - if (this.options.hasOwnProperty(value)) return; + value = value || ''; + if (self.options.hasOwnProperty(value)) return; - this.userOptions[value] = true; - this.options[value] = data; - this.lastQuery = null; - this.trigger('option_add', value, data); - }; + self.userOptions[value] = true; + self.options[value] = data; + self.lastQuery = null; + self.trigger('option_add', value, data); + }, - /** - * Registers a new optgroup for options - * to be bucketed into. - * - * @param {string} id - * @param {object} data - */ - Selectize.prototype.addOptionGroup = function(id, data) { - this.optgroups[id] = data; - this.trigger('optgroup_add', value, data); - }; + /** + * Registers a new optgroup for options + * to be bucketed into. + * + * @param {string} id + * @param {object} data + */ + addOptionGroup: function(id, data) { + this.optgroups[id] = data; + this.trigger('optgroup_add', value, data); + }, - /** - * Updates an option available for selection. If - * it is visible in the selected items or options - * dropdown, it will be re-rendered automatically. - * - * @param {string} value - * @param {object} data - */ - Selectize.prototype.updateOption = function(value, data) { - value = String(value); - this.options[value] = data; - if (isset(this.renderCache['item'])) delete this.renderCache['item'][value]; - if (isset(this.renderCache['option'])) delete this.renderCache['option'][value]; - - if (this.items.indexOf(value) !== -1) { - var $item = this.getItem(value); - var $item_new = $(this.render('item', data)); - if ($item.hasClass('active')) $item_new.addClass('active'); - $item.replaceWith($item_new); - } + /** + * Updates an option available for selection. If + * it is visible in the selected items or options + * dropdown, it will be re-rendered automatically. + * + * @param {string} value + * @param {object} data + */ + updateOption: function(value, data) { + var self = this; + var $item, $item_new; - if (this.isOpen) { - this.refreshOptions(false); - } - }; + value = String(value); + self.options[value] = data; + if (isset(self.renderCache['item'])) delete self.renderCache['item'][value]; + if (isset(self.renderCache['option'])) delete self.renderCache['option'][value]; - /** - * Removes a single option. - * - * @param {string} value - */ - Selectize.prototype.removeOption = function(value) { - value = String(value); - delete this.userOptions[value]; - delete this.options[value]; - this.lastQuery = null; - this.trigger('option_remove', value); - this.removeItem(value); - }; + if (self.items.indexOf(value) !== -1) { + $item = self.getItem(value); + $item_new = $(self.render('item', data)); + if ($item.hasClass('active')) $item_new.addClass('active'); + $item.replaceWith($item_new); + } - /** - * Clears all options. - */ - Selectize.prototype.clearOptions = function() { - this.loadedSearches = {}; - this.userOptions = {}; - this.options = {}; - this.lastQuery = null; - this.trigger('option_clear'); - this.clear(); - }; + if (self.isOpen) { + self.refreshOptions(false); + } + }, - /** - * Returns the jQuery element of the option - * matching the given value. - * - * @param {string} value - * @returns {object} - */ - Selectize.prototype.getOption = function(value) { - return value ? this.$dropdown_content.find('[data-selectable]').filter('[data-value="' + value.replace(/(['"])/g, '\\$1') + '"]:first') : $(); - }; + /** + * Removes a single option. + * + * @param {string} value + */ + removeOption: function(value) { + var self = this; - /** - * Returns the jQuery element of the next or - * previous selectable option. - * - * @param {object} $option - * @param {int} direction can be 1 for next or -1 for previous - * @return {object} - */ - Selectize.prototype.getAdjacentOption = function($option, direction) { - var $options = this.$dropdown.find('[data-selectable]'); - var index = $options.index($option) + direction; + value = String(value); + delete self.userOptions[value]; + delete self.options[value]; + self.lastQuery = null; + self.trigger('option_remove', value); + self.removeItem(value); + }, - return index >= 0 && index < $options.length ? $options.eq(index) : $(); - }; + /** + * Clears all options. + */ + clearOptions: function() { + var self = this; - /** - * Returns the jQuery element of the item - * matching the given value. - * - * @param {string} value - * @returns {object} - */ - Selectize.prototype.getItem = function(value) { - var i = this.items.indexOf(value); - if (i !== -1) { - if (i >= this.caretPos) i++; - var $el = $(this.$control[0].childNodes[i]); - if ($el.attr('data-value') === value) { - return $el; + self.loadedSearches = {}; + self.userOptions = {}; + self.options = {}; + self.lastQuery = null; + self.trigger('option_clear'); + self.clear(); + }, + + /** + * Returns the jQuery element of the option + * matching the given value. + * + * @param {string} value + * @returns {object} + */ + getOption: function(value) { + return value ? this.$dropdown_content.find('[data-selectable]').filter('[data-value="' + value.replace(/(['"])/g, '\\$1') + '"]:first') : $(); + }, + + /** + * Returns the jQuery element of the next or + * previous selectable option. + * + * @param {object} $option + * @param {int} direction can be 1 for next or -1 for previous + * @return {object} + */ + getAdjacentOption: function($option, direction) { + var $options = this.$dropdown.find('[data-selectable]'); + var index = $options.index($option) + direction; + + return index >= 0 && index < $options.length ? $options.eq(index) : $(); + }, + + /** + * Returns the jQuery element of the item + * matching the given value. + * + * @param {string} value + * @returns {object} + */ + getItem: function(value) { + var i = this.items.indexOf(value); + if (i !== -1) { + if (i >= this.caretPos) i++; + var $el = $(this.$control[0].childNodes[i]); + if ($el.attr('data-value') === value) { + return $el; + } } - } - return $(); - }; + return $(); + }, - /** - * "Selects" an item. Adds it to the list - * at the current caret position. - * - * @param {string} value - */ - Selectize.prototype.addItem = function(value) { - debounce_events(this, ['change'], function() { - var $item, $option; + /** + * "Selects" an item. Adds it to the list + * at the current caret position. + * + * @param {string} value + */ + addItem: function(value) { + debounce_events(this, ['change'], function() { + var $item, $option; + var self = this; + var inputMode = self.settings.mode; + var i, active, options, value_next; + value = String(value); + + if (inputMode === 'single') self.clear(); + if (inputMode === 'multi' && self.isFull()) return; + if (self.items.indexOf(value) !== -1) return; + if (!self.options.hasOwnProperty(value)) return; + + $item = $(self.render('item', self.options[value])); + self.items.splice(self.caretPos, 0, value); + self.insertAtCaret($item); + self.refreshClasses(); + + if (self.isSetup) { + // remove the option from the menu + options = self.$dropdown_content.find('[data-selectable]'); + $option = self.getOption(value); + value_next = self.getAdjacentOption($option, 1).attr('data-value'); + self.refreshOptions(true); + if (value_next) { + self.setActiveOption(self.getOption(value_next)); + } + + // hide the menu if the maximum number of items have been selected or no options are left + if (!options.length || (self.settings.maxItems !== null && self.items.length >= self.settings.maxItems)) { + self.close(); + } else { + self.positionDropdown(); + } + + // restore focus to input + if (self.isFocused) { + window.setTimeout(function() { + if (inputMode === 'single') { + self.blur(); + self.focus(false); + self.hideInput(); + } else { + self.focus(false); + } + }, 0); + } + + self.updatePlaceholder(); + self.trigger('item_add', value, $item); + self.updateOriginalInput(); + } + }); + }, + + /** + * Removes the selected item matching + * the provided value. + * + * @param {string} value + */ + removeItem: function(value) { var self = this; - var inputMode = this.settings.mode; - var i, active, options, value_next; - value = String(value); - - if (inputMode === 'single') this.clear(); - if (inputMode === 'multi' && this.isFull()) return; - if (this.items.indexOf(value) !== -1) return; - if (!this.options.hasOwnProperty(value)) return; + var $item, i, idx; - $item = $(this.render('item', this.options[value])); - this.items.splice(this.caretPos, 0, value); - this.insertAtCaret($item); - this.refreshClasses(); + $item = (typeof value === 'object') ? value : self.getItem(value); + value = String($item.attr('data-value')); + i = self.items.indexOf(value); - if (this.isSetup) { - // remove the option from the menu - options = this.$dropdown_content.find('[data-selectable]'); - $option = this.getOption(value); - value_next = this.getAdjacentOption($option, 1).attr('data-value'); - this.refreshOptions(true); - if (value_next) { - this.setActiveOption(this.getOption(value_next)); + if (i !== -1) { + $item.remove(); + if ($item.hasClass('active')) { + idx = self.$activeItems.indexOf($item[0]); + self.$activeItems.splice(idx, 1); } - // hide the menu if the maximum number of items have been selected or no options are left - if (!options.length || (this.settings.maxItems !== null && this.items.length >= this.settings.maxItems)) { - this.close(); - } else { - this.positionDropdown(); + self.items.splice(i, 1); + self.lastQuery = null; + if (!self.settings.persist && self.userOptions.hasOwnProperty(value)) { + self.removeOption(value); } - // restore focus to input - if (this.isFocused) { - window.setTimeout(function() { - if (inputMode === 'single') { - self.blur(); - self.focus(false); - self.hideInput(); - } else { - self.focus(false); - } - }, 0); + if (i < self.caretPos) { + self.setCaret(self.caretPos - 1); } - this.updatePlaceholder(); - this.trigger('item_add', value, $item); - this.updateOriginalInput(); + self.refreshClasses(); + self.updatePlaceholder(); + self.updateOriginalInput(); + self.positionDropdown(); + self.trigger('item_remove', value); } - }); - }; + }, - /** - * Removes the selected item matching - * the provided value. - * - * @param {string} value - */ - Selectize.prototype.removeItem = function(value) { - var $item, i, idx; + /** + * Invokes the `create` method provided in the + * selectize options that should provide the data + * for the new item, given the user input. + * + * Once this completes, it will be added + * to the item list. + */ + createItem: function() { + var self = this; + var input = $.trim(self.$control_input.val() || ''); + var caret = self.caretPos; + if (!input.length) return; + self.lock(); + + var setup = (typeof self.settings.create === 'function') ? this.settings.create : function(input) { + var data = {}; + data[self.settings.labelField] = input; + data[self.settings.valueField] = input; + return data; + }; + + var create = once(function(data) { + self.unlock(); + self.focus(false); - $item = (typeof value === 'object') ? value : this.getItem(value); - value = String($item.attr('data-value')); - i = this.items.indexOf(value); + var value = data && data[self.settings.valueField]; + if (!value) return; - if (i !== -1) { - $item.remove(); - if ($item.hasClass('active')) { - idx = this.$activeItems.indexOf($item[0]); - this.$activeItems.splice(idx, 1); + self.setTextboxValue(''); + self.addOption(value, data); + self.setCaret(caret); + self.addItem(value); + self.refreshOptions(true); + self.focus(false); + }); + + var output = setup.apply(this, [input, create]); + if (typeof output !== 'undefined') { + create(output); } + }, - this.items.splice(i, 1); + /** + * Re-renders the selected item lists. + */ + refreshItems: function() { this.lastQuery = null; - if (!this.settings.persist && this.userOptions.hasOwnProperty(value)) { - this.removeOption(value); - } - if (i < this.caretPos) { - this.setCaret(this.caretPos - 1); + if (this.isSetup) { + for (var i = 0; i < this.items.length; i++) { + this.addItem(this.items); + } } this.refreshClasses(); - this.updatePlaceholder(); this.updateOriginalInput(); - this.positionDropdown(); - this.trigger('item_remove', value); - } - }; - - /** - * Invokes the `create` method provided in the - * selectize options that should provide the data - * for the new item, given the user input. - * - * Once this completes, it will be added - * to the item list. - */ - Selectize.prototype.createItem = function() { - var self = this; - var input = $.trim(this.$control_input.val() || ''); - var caret = this.caretPos; - if (!input.length) return; - this.lock(); - - var setup = (typeof this.settings.create === 'function') ? this.settings.create : function(input) { - var data = {}; - data[self.settings.labelField] = input; - data[self.settings.valueField] = input; - return data; - }; - - var create = once(function(data) { - self.unlock(); - self.focus(false); - - var value = data && data[self.settings.valueField]; - if (!value) return; + }, - self.setTextboxValue(''); - self.addOption(value, data); - self.setCaret(caret); - self.addItem(value); - self.refreshOptions(true); - self.focus(false); - }); + /** + * Updates all state-dependent CSS classes. + */ + refreshClasses: function() { + var isFull = this.isFull(); + var isLocked = this.isLocked; + this.$control + .toggleClass('focus', this.isFocused) + .toggleClass('disabled', this.isDisabled) + .toggleClass('locked', isLocked) + .toggleClass('full', isFull).toggleClass('not-full', !isFull) + .toggleClass('has-items', this.items.length > 0); + this.$control_input.data('grow', !isFull && !isLocked); + }, - var output = setup.apply(this, [input, create]); - if (typeof output !== 'undefined') { - create(output); - } - }; + /** + * Determines whether or not more items can be added + * to the control without exceeding the user-defined maximum. + * + * @returns {boolean} + */ + isFull: function() { + return this.settings.maxItems !== null && this.items.length >= this.settings.maxItems; + }, - /** - * Re-renders the selected item lists. - */ - Selectize.prototype.refreshItems = function() { - this.lastQuery = null; + /** + * Refreshes the original + * element to reflect the current state. + */ + updateOriginalInput: function() { + var i, n, options, self = this; - if (this.isSetup) { - for (var i = 0; i < this.items.length; i++) { - this.addItem(this.items); + if (self.$input[0].tagName.toLowerCase() === 'select') { + options = []; + for (i = 0, n = self.items.length; i < n; i++) { + options.push(''); + } + if (!options.length && !this.$input.attr('multiple')) { + options.push(''); + } + self.$input.html(options.join('')); + } else { + self.$input.val(self.getValue()); } - } - - this.refreshClasses(); - this.updateOriginalInput(); - }; - - /** - * Updates all state-dependent CSS classes. - */ - Selectize.prototype.refreshClasses = function() { - var isFull = this.isFull(); - var isLocked = this.isLocked; - this.$control - .toggleClass('focus', this.isFocused) - .toggleClass('disabled', this.isDisabled) - .toggleClass('locked', isLocked) - .toggleClass('full', isFull).toggleClass('not-full', !isFull) - .toggleClass('has-items', this.items.length > 0); - this.$control_input.data('grow', !isFull && !isLocked); - }; - /** - * Determines whether or not more items can be added - * to the control without exceeding the user-defined maximum. - * - * @returns {boolean} - */ - Selectize.prototype.isFull = function() { - return this.settings.maxItems !== null && this.items.length >= this.settings.maxItems; - }; + self.$input.trigger('change'); + if (self.isSetup) { + self.trigger('change', self.$input.val()); + } + }, - /** - * Refreshes the original - * element to reflect the current state. - */ - Selectize.prototype.updateOriginalInput = function() { - var i, n, options; + /** + * Shows/hide the input placeholder depending + * on if there items in the list already. + */ + updatePlaceholder: function() { + if (!this.settings.placeholder) return; + var $input = this.$control_input; - if (this.$input[0].tagName.toLowerCase() === 'select') { - options = []; - for (i = 0, n = this.items.length; i < n; i++) { - options.push(''); - } - if (!options.length && !this.$input.attr('multiple')) { - options.push(''); + if (this.items.length) { + $input.removeAttr('placeholder'); + } else { + $input.attr('placeholder', this.settings.placeholder); } - this.$input.html(options.join('')); - } else { - this.$input.val(this.getValue()); - } + $input.triggerHandler('update'); + }, - this.$input.trigger('change'); - if (this.isSetup) { - this.trigger('change', this.$input.val()); - } - }; + /** + * Shows the autocomplete dropdown containing + * the available options. + */ + open: function() { + var self = this; - /** - * Shows/hide the input placeholder depending - * on if there items in the list already. - */ - Selectize.prototype.updatePlaceholder = function() { - if (!this.settings.placeholder) return; - var $input = this.$control_input; + if (self.isLocked || self.isOpen || (self.settings.mode === 'multi' && self.isFull())) return; + self.focus(); + self.isOpen = true; + self.$dropdown.css({visibility: 'hidden', display: 'block'}); + self.$control.addClass('dropdown-active'); + self.positionDropdown(); + self.$dropdown.css({visibility: 'visible'}); + self.trigger('dropdown_open', this.$dropdown); + }, - if (this.items.length) { - $input.removeAttr('placeholder'); - } else { - $input.attr('placeholder', this.settings.placeholder); - } - $input.triggerHandler('update'); - }; + /** + * Closes the autocomplete dropdown menu. + */ + close: function() { + var self = this; - /** - * Shows the autocomplete dropdown containing - * the available options. - */ - Selectize.prototype.open = function() { - if (this.isLocked || this.isOpen || (this.settings.mode === 'multi' && this.isFull())) return; - this.focus(); - this.isOpen = true; - this.$dropdown.css({visibility: 'hidden', display: 'block'}); - this.$control.addClass('dropdown-active'); - this.positionDropdown(); - this.$dropdown.css({visibility: 'visible'}); - this.trigger('dropdown_open', this.$dropdown); - }; + if (!self.isOpen) return; + self.$dropdown.hide(); + self.$control.removeClass('dropdown-active'); + self.setActiveOption(null); + self.isOpen = false; + self.trigger('dropdown_close', self.$dropdown); + }, - /** - * Closes the autocomplete dropdown menu. - */ - Selectize.prototype.close = function() { - if (!this.isOpen) return; - this.$dropdown.hide(); - this.$control.removeClass('dropdown-active'); - this.setActiveOption(null); - this.isOpen = false; - this.trigger('dropdown_close', this.$dropdown); - }; + /** + * Calculates and applies the appropriate + * position of the dropdown. + */ + positionDropdown: function() { + var $control = this.$control; + var offset = this.settings.dropdownParent === 'body' ? $control.offset() : $control.position(); + offset.top += $control.outerHeight(true); + + this.$dropdown.css({ + width : $control.outerWidth(), + top : offset.top, + left : offset.left + }); + }, - /** - * Calculates and applies the appropriate - * position of the dropdown. - */ - Selectize.prototype.positionDropdown = function() { - var $control = this.$control; - var offset = this.settings.dropdownParent === 'body' ? $control.offset() : $control.position(); - offset.top += $control.outerHeight(true); - - this.$dropdown.css({ - width : $control.outerWidth(), - top : offset.top, - left : offset.left - }); - }; + /** + * Resets / clears all selected items + * from the control. + */ + clear: function() { + var self = this; - /** - * Resets / clears all selected items - * from the control. - */ - Selectize.prototype.clear = function() { - if (!this.items.length) return; - this.$control.children(':not(input)').remove(); - this.items = []; - this.setCaret(0); - this.updatePlaceholder(); - this.updateOriginalInput(); - this.refreshClasses(); - this.showInput(); - this.trigger('clear'); - }; + if (!self.items.length) return; + self.$control.children(':not(input)').remove(); + self.items = []; + self.setCaret(0); + self.updatePlaceholder(); + self.updateOriginalInput(); + self.refreshClasses(); + self.showInput(); + self.trigger('clear'); + }, - /** - * A helper method for inserting an element - * at the current caret position. - * - * @param {object} $el - */ - Selectize.prototype.insertAtCaret = function($el) { - var caret = Math.min(this.caretPos, this.items.length); - if (caret === 0) { - this.$control.prepend($el); - } else { - $(this.$control[0].childNodes[caret]).before($el); - } - this.setCaret(caret + 1); - }; + /** + * A helper method for inserting an element + * at the current caret position. + * + * @param {object} $el + */ + insertAtCaret: function($el) { + var caret = Math.min(this.caretPos, this.items.length); + if (caret === 0) { + this.$control.prepend($el); + } else { + $(this.$control[0].childNodes[caret]).before($el); + } + this.setCaret(caret + 1); + }, - /** - * Removes the current selected item(s). - * - * @param {object} e (optional) - * @returns {boolean} - */ - Selectize.prototype.deleteSelection = function(e) { - var i, n, direction, selection, values, caret, $tail; + /** + * Removes the current selected item(s). + * + * @param {object} e (optional) + * @returns {boolean} + */ + deleteSelection: function(e) { + var i, n, direction, selection, values, caret, $tail; + var self = this; - direction = (e && e.keyCode === KEY_BACKSPACE) ? -1 : 1; - selection = getSelection(this.$control_input[0]); + direction = (e && e.keyCode === KEY_BACKSPACE) ? -1 : 1; + selection = getSelection(self.$control_input[0]); - // determine items that will be removed - values = []; + // determine items that will be removed + values = []; - if (this.$activeItems.length) { - $tail = this.$control.children('.active:' + (direction > 0 ? 'last' : 'first')); - caret = this.$control.children(':not(input)').index($tail); - if (direction > 0) { caret++; } + if (self.$activeItems.length) { + $tail = self.$control.children('.active:' + (direction > 0 ? 'last' : 'first')); + caret = self.$control.children(':not(input)').index($tail); + if (direction > 0) { caret++; } - for (i = 0, n = this.$activeItems.length; i < n; i++) { - values.push($(this.$activeItems[i]).attr('data-value')); - } - if (e) { - e.preventDefault(); - e.stopPropagation(); - } - } else if ((this.isFocused || this.settings.mode === 'single') && this.items.length) { - if (direction < 0 && selection.start === 0 && selection.length === 0) { - values.push(this.items[this.caretPos - 1]); - } else if (direction > 0 && selection.start === this.$control_input.val().length) { - values.push(this.items[this.caretPos]); + for (i = 0, n = self.$activeItems.length; i < n; i++) { + values.push($(self.$activeItems[i]).attr('data-value')); + } + if (e) { + e.preventDefault(); + e.stopPropagation(); + } + } else if ((self.isFocused || self.settings.mode === 'single') && self.items.length) { + if (direction < 0 && selection.start === 0 && selection.length === 0) { + values.push(self.items[self.caretPos - 1]); + } else if (direction > 0 && selection.start === self.$control_input.val().length) { + values.push(self.items[self.caretPos]); + } } - } - // allow the callback to abort - if (!values.length || (typeof this.settings.onDelete === 'function' && this.settings.onDelete(values) === false)) { - return false; - } + // allow the callback to abort + if (!values.length || (typeof self.settings.onDelete === 'function' && self.settings.onDelete(values) === false)) { + return false; + } - // perform removal - if (typeof caret !== 'undefined') { - this.setCaret(caret); - } - while (values.length) { - this.removeItem(values.pop()); - } + // perform removal + if (typeof caret !== 'undefined') { + self.setCaret(caret); + } + while (values.length) { + self.removeItem(values.pop()); + } - this.showInput(); - this.refreshOptions(true); - return true; - }; + self.showInput(); + self.refreshOptions(true); + return true; + }, - /** - * Selects the previous / next item (depending - * on the `direction` argument). - * - * > 0 - right - * < 0 - left - * - * @param {int} direction - * @param {object} e (optional) - */ - Selectize.prototype.advanceSelection = function(direction, e) { - var tail, selection, idx, valueLength, cursorAtEdge, $tail; + /** + * Selects the previous / next item (depending + * on the `direction` argument). + * + * > 0 - right + * < 0 - left + * + * @param {int} direction + * @param {object} e (optional) + */ + advanceSelection: function(direction, e) { + var tail, selection, idx, valueLength, cursorAtEdge, $tail; + var self = this; - if (direction === 0) return; + if (direction === 0) return; - tail = direction > 0 ? 'last' : 'first'; - selection = getSelection(this.$control_input[0]); + tail = direction > 0 ? 'last' : 'first'; + selection = getSelection(self.$control_input[0]); - if (this.isInputFocused && !this.isInputHidden) { - valueLength = this.$control_input.val().length; - cursorAtEdge = direction < 0 - ? selection.start === 0 && selection.length === 0 - : selection.start === valueLength; + if (self.isInputFocused && !self.isInputHidden) { + valueLength = self.$control_input.val().length; + cursorAtEdge = direction < 0 + ? selection.start === 0 && selection.length === 0 + : selection.start === valueLength; - if (cursorAtEdge && !valueLength) { - this.advanceCaret(direction, e); - } - } else { - $tail = this.$control.children('.active:' + tail); - if ($tail.length) { - idx = this.$control.children(':not(input)').index($tail); - this.setActiveItem(null); - this.setCaret(direction > 0 ? idx + 1 : idx); - this.showInput(); + if (cursorAtEdge && !valueLength) { + self.advanceCaret(direction, e); + } + } else { + $tail = self.$control.children('.active:' + tail); + if ($tail.length) { + idx = self.$control.children(':not(input)').index($tail); + self.setActiveItem(null); + self.setCaret(direction > 0 ? idx + 1 : idx); + self.showInput(); + } } - } - }; + }, - /** - * Moves the caret left / right. - * - * @param {int} direction - * @param {object} e (optional) - */ - Selectize.prototype.advanceCaret = function(direction, e) { - if (direction === 0) return; - var fn = direction > 0 ? 'next' : 'prev'; - if (this.isShiftDown) { - var $adj = this.$control_input[fn](); - if ($adj.length) { - this.hideInput(); - this.setActiveItem($adj); - e && e.preventDefault(); + /** + * Moves the caret left / right. + * + * @param {int} direction + * @param {object} e (optional) + */ + advanceCaret: function(direction, e) { + if (direction === 0) return; + var self = this; + var fn = direction > 0 ? 'next' : 'prev'; + if (self.isShiftDown) { + var $adj = self.$control_input[fn](); + if ($adj.length) { + self.hideInput(); + self.setActiveItem($adj); + e && e.preventDefault(); + } + } else { + self.setCaret(self.caretPos + direction); } - } else { - this.setCaret(this.caretPos + direction); - } - }; + }, - /** - * Moves the caret to the specified index. - * - * @param {int} i - */ - Selectize.prototype.setCaret = function(i) { - if (this.settings.mode === 'single') { - i = this.items.length; - } else { - i = Math.max(0, Math.min(this.items.length, i)); - } + /** + * Moves the caret to the specified index. + * + * @param {int} i + */ + setCaret: function(i) { + var self = this; - // the input must be moved by leaving it in place and moving the - // siblings, due to the fact that focus cannot be restored once lost - // on mobile webkit devices - var j, n, fn, $children, $child; - $children = this.$control.children(':not(input)'); - for (j = 0, n = $children.length; j < n; j++) { - $child = $($children[j]).detach(); - if (j < i) { - this.$control_input.before($child); + if (self.settings.mode === 'single') { + i = self.items.length; } else { - this.$control.append($child); + i = Math.max(0, Math.min(self.items.length, i)); + } + + // the input must be moved by leaving it in place and moving the + // siblings, due to the fact that focus cannot be restored once lost + // on mobile webkit devices + var j, n, fn, $children, $child; + $children = self.$control.children(':not(input)'); + for (j = 0, n = $children.length; j < n; j++) { + $child = $($children[j]).detach(); + if (j < i) { + self.$control_input.before($child); + } else { + self.$control.append($child); + } } - } - this.caretPos = i; - }; + self.caretPos = i; + }, - /** - * Disables user input on the control. Used while - * items are being asynchronously created. - */ - Selectize.prototype.lock = function() { - this.close(); - this.isLocked = true; - this.refreshClasses(); - }; + /** + * Disables user input on the control. Used while + * items are being asynchronously created. + */ + lock: function() { + this.close(); + this.isLocked = true; + this.refreshClasses(); + }, - /** - * Re-enables user input on the control. - */ - Selectize.prototype.unlock = function() { - this.isLocked = false; - this.refreshClasses(); - }; + /** + * Re-enables user input on the control. + */ + unlock: function() { + this.isLocked = false; + this.refreshClasses(); + }, - /** - * Disables user input on the control completely. - * While disabled, it cannot receive focus. - */ - Selectize.prototype.disable = function() { - this.isDisabled = true; - this.lock(); - }; + /** + * Disables user input on the control completely. + * While disabled, it cannot receive focus. + */ + disable: function() { + this.isDisabled = true; + this.lock(); + }, - /** - * Enables the control so that it can respond - * to focus and user input. - */ - Selectize.prototype.enable = function() { - this.isDisabled = false; - this.unlock(); - }; + /** + * Enables the control so that it can respond + * to focus and user input. + */ + enable: function() { + this.isDisabled = false; + this.unlock(); + }, - /** - * A helper method for rendering "item" and - * "option" templates, given the data. - * - * @param {string} templateName - * @param {object} data - * @returns {string} - */ - Selectize.prototype.render = function(templateName, data) { - var value, id, label; - var html = ''; - var cache = false; - var regex_tag = /^[\ ]*<([a-z][a-z0-9\-_]*(?:\:[a-z][a-z0-9\-_]*)?)/i; - - if (templateName === 'option' || templateName === 'item') { - value = data[this.settings.valueField]; - cache = isset(value); - } + /** + * A helper method for rendering "item" and + * "option" templates, given the data. + * + * @param {string} templateName + * @param {object} data + * @returns {string} + */ + render: function(templateName, data) { + var value, id, label; + var html = ''; + var cache = false; + var self = this; + var regex_tag = /^[\ ]*<([a-z][a-z0-9\-_]*(?:\:[a-z][a-z0-9\-_]*)?)/i; - // pull markup from cache if it exists - if (cache) { - if (!isset(this.renderCache[templateName])) { - this.renderCache[templateName] = {}; + if (templateName === 'option' || templateName === 'item') { + value = data[self.settings.valueField]; + cache = isset(value); } - if (this.renderCache[templateName].hasOwnProperty(value)) { - return this.renderCache[templateName][value]; + + // pull markup from cache if it exists + if (cache) { + if (!isset(self.renderCache[templateName])) { + self.renderCache[templateName] = {}; + } + if (self.renderCache[templateName].hasOwnProperty(value)) { + return self.renderCache[templateName][value]; + } } - } - // render markup - if (this.settings.render && typeof this.settings.render[templateName] === 'function') { - html = this.settings.render[templateName].apply(this, [data, htmlEntities]); - } else { - label = data[this.settings.labelField]; - switch (templateName) { - case 'optgroup': - html = '
' + data.html + "
"; - break; - case 'optgroup_header': - label = data[this.settings.optgroupLabelField]; - html = '
' + htmlEntities(label) + '
'; - break; - case 'option': - html = '
' + htmlEntities(label) + '
'; - break; - case 'item': - html = '
' + htmlEntities(label) + '
'; - break; - case 'option_create': - html = '
Create ' + htmlEntities(data.input) + '
'; - break; + // render markup + if (self.settings.render && typeof self.settings.render[templateName] === 'function') { + html = self.settings.render[templateName].apply(this, [data, escape_html]); + } else { + label = data[self.settings.labelField]; + switch (templateName) { + case 'optgroup': + html = '
' + data.html + "
"; + break; + case 'optgroup_header': + label = data[self.settings.optgroupLabelField]; + html = '
' + escape_html(label) + '
'; + break; + case 'option': + html = '
' + escape_html(label) + '
'; + break; + case 'item': + html = '
' + escape_html(label) + '
'; + break; + case 'option_create': + html = '
Create ' + escape_html(data.input) + '
'; + break; + } } - } - // add mandatory attributes - if (templateName === 'option' || templateName === 'option_create') { - html = html.replace(regex_tag, '<$1 data-selectable'); - } - if (templateName === 'optgroup') { - id = data[this.settings.optgroupValueField] || ''; - html = html.replace(regex_tag, '<$1 data-group="' + htmlEntities(id) + '"'); - } - if (templateName === 'option' || templateName === 'item') { - html = html.replace(regex_tag, '<$1 data-value="' + htmlEntities(value || '') + '"'); - } + // add mandatory attributes + if (templateName === 'option' || templateName === 'option_create') { + html = html.replace(regex_tag, '<$1 data-selectable'); + } + if (templateName === 'optgroup') { + id = data[self.settings.optgroupValueField] || ''; + html = html.replace(regex_tag, '<$1 data-group="' + escape_html(id) + '"'); + } + if (templateName === 'option' || templateName === 'item') { + html = html.replace(regex_tag, '<$1 data-value="' + escape_html(value || '') + '"'); + } - // update cache - if (cache) { - this.renderCache[templateName][value] = html; + // update cache + if (cache) { + self.renderCache[templateName][value] = html; + } + + return html; } - return html; - }; + }); Selectize.defaults = { plugins: [], @@ -2255,6 +2373,7 @@ dropdownParent: null, + /* load : null, // function(query, callback) score : null, // function(search) onChange : null, // function(value) @@ -2268,13 +2387,16 @@ onDropdownClose : null, // function($dropdown) { ... } onType : null, // function(str) { ... } onDelete : null, // function(values) { ... } + */ render: { + /* item: null, optgroup: null, optgroup_header: null, option: null, option_create: null + */ } }; diff --git a/selectize.min.js b/selectize.min.js index a91b20f20..68316a1d3 100644 --- a/selectize.min.js +++ b/selectize.min.js @@ -1,66 +1,63 @@ -/*! selectize.js - v0.6.4 | https://github.com/brianreavis/selectize.js | Apache License (v2) */ -(function(g){"object"===typeof exports?g(require("jquery")):"function"===typeof define&&define.amd?define(["jquery"],g):g(jQuery)})(function(g){var x=function(a,b){if("string"!==typeof b||b.length){var c="string"===typeof b?RegExp(b,"i"):b,d=function(a){var b=0;if(3===a.nodeType){var k=a.data.search(c);if(0<=k&&0/g,">").replace(/"/g,""")},v={before:function(a,b,c){var d=a[b];a[b]=function(){c.apply(a,arguments);return d.apply(a,arguments)}},after:function(a, -b,c){var d=a[b];a[b]=function(){var b=d.apply(a,arguments);c.apply(a,arguments);return b}}},A=function(a){var b=!1;return function(){b||(b=!0,a.apply(this,arguments))}},B=function(a,b){var c;return function(){var d=this,e=arguments;window.clearTimeout(c);c=window.setTimeout(function(){a.apply(d,e)},b)}},w=function(a,b,c){var d;b=a.trigger;var e={};a.trigger=function(){e[arguments[0]]=arguments};c.apply(a,[]);a.trigger=b;for(d in e)e.hasOwnProperty(d)&&b.apply(a,e[d])},C=function(a,b,c,d){a.on(b,c, -function(b){for(var c=b.target;c&&c.parentNode!==a[0];)c=c.parentNode;b.currentTarget=c;return d.apply(this,[b])})},u=function(a){var b={};if("selectionStart"in a)b.start=a.selectionStart,b.length=a.selectionEnd-b.start;else if(document.selection){a.focus();var c=document.selection.createRange(),d=document.selection.createRange().text.length;c.moveStart("character",-a.value.length);b.start=c.text.length-d;b.length=d}return b},D=function(a){var b=function(b){var d,e,h;b=b||window.event||{};if(!b.metaKey&& -!b.altKey&&!1!==a.data("grow")){d=a.val();b.type&&"keydown"===b.type.toLowerCase()&&(e=b.keyCode,h=97<=e&&122>=e||65<=e&&90>=e||48<=e&&57>=e||32==e,46===e||8===e?(b=u(a[0]),b.length?d=d.substring(0,b.start)+d.substring(b.start+b.length):8===e&&b.start?d=d.substring(0,b.start-1)+d.substring(b.start+1):46===e&&"undefined"!==typeof b.start&&(d=d.substring(0,b.start)+d.substring(b.start+1))):h&&(e=b.shiftKey,b=String.fromCharCode(b.keyCode),b=e?b.toUpperCase():b.toLowerCase(),d+=b));b=a.attr("placeholder")|| -"";!d.length&&b.length&&(d=b);d=g("").css({position:"absolute",top:-99999,left:-99999,width:"auto",padding:0,whiteSpace:"nowrap"}).text(d).appendTo("body");b=["letterSpacing","fontSize","fontFamily","fontWeight","textTransform"];e={};if(b)for(h=0;h").addClass(this.settings.theme).addClass(this.settings.wrapperClass).addClass(b);c=g("
").addClass(this.settings.inputClass).addClass("items").toggleClass("has-options",!g.isEmptyObject(this.options)).appendTo(b);d=g('').appendTo(c).attr("tabindex",d);e=g(this.settings.dropdownParent|| -b);e=g("
").addClass(this.settings.dropdownClass).hide().appendTo(e);h=g("
").addClass(this.settings.dropdownContentClass).appendTo(e);b.css({width:this.$input[0].style.width,display:this.$input.css("display")});this.plugins.length&&b.addClass("plugin-"+this.plugins.join(" plugin-"));k=this.settings.mode;b.toggleClass("single","single"===k);b.toggleClass("multi","multi"===k);(null===this.settings.maxItems||1c&&(e=d,d=c,c=e);for(;d<=c;d++)e=this.$control[0].childNodes[d],-1===this.$activeItems.indexOf(e)&&(g(e).addClass("active"),this.$activeItems.push(e));b.preventDefault()}else"mousedown"===c&&this.isCtrlDown||"keydown"===c&&this.isShiftDown?a.hasClass("active")?(c=this.$activeItems.indexOf(a[0]),this.$activeItems.splice(c,1),a.removeClass("active")):this.$activeItems.push(a.addClass("active")[0]):(g(this.$activeItems).removeClass("active"),this.$activeItems=[a.addClass("active")[0]]);this.isFocused= -!!this.$activeItems.length||this.isInputFocused}else g(this.$activeItems).removeClass("active"),this.$activeItems=[],this.isFocused=this.isInputFocused};f.prototype.setActiveOption=function(a,b,c){var d,e,h;this.$activeOption&&this.$activeOption.removeClass("active");this.$activeOption=null;a=g(a);a.length&&(this.$activeOption=a.addClass("active"),b||!q(b))&&(a=this.$dropdown.height(),d=this.$activeOption.outerHeight(!0),b=this.$dropdown.scrollTop()||0,e=this.$activeOption.offset().top-this.$dropdown.offset().top+ -b,h=e-a+d,e+d>a-b?this.$dropdown.stop().animate({scrollTop:h},c?this.settings.scrollDuration:0):ee?1*b:e>d?-1*b:0}}())}this.currentResults=h}else h= -g.extend(!0,{},this.currentResults);return this.prepareResults(h,b)};f.prototype.prepareResults=function(a,b){if(this.settings.hideSelected)for(var c=a.items.length-1;0<=c;c--)-1!==this.items.indexOf(String(a.items[c].value))&&a.items.splice(c,1);a.total=a.items.length;"number"===typeof b.limit&&(a.items=a.items.slice(0,b.limit));return a};f.prototype.refreshOptions=function(a){"undefined"===typeof a&&(a=!0);var b,c,d,e,h,k,f,m=this.$control_input.val(),n=this.search(m,{}),l,p=this.$dropdown_content; -c=n.items.length;"number"===typeof this.settings.maxOptions&&(c=Math.min(c,this.settings.maxOptions));d={};if(this.settings.optgroupOrder)for(e=this.settings.optgroupOrder,b=0;b=this.caretPos&&b++,b=g(this.$control[0].childNodes[b]),b.attr("data-value")===a)?b:g()};f.prototype.addItem=function(a){w(this,["change"],function(){var b,c,d=this,e=this.settings.mode,h;a=String(a);"single"===e&&this.clear();"multi"===e&&this.isFull()||(-1!==this.items.indexOf(a)||!this.options.hasOwnProperty(a))|| -(b=g(this.render("item",this.options[a])),this.items.splice(this.caretPos,0,a),this.insertAtCaret(b),this.refreshClasses(),this.isSetup&&(h=this.$dropdown_content.find("[data-selectable]"),c=this.getOption(a),c=this.getAdjacentOption(c,1).attr("data-value"),this.refreshOptions(!0),c&&this.setActiveOption(this.getOption(c)),!h.length||null!==this.settings.maxItems&&this.items.length>=this.settings.maxItems?this.close():this.positionDropdown(),this.isFocused&&window.setTimeout(function(){"single"=== -e?(d.blur(),d.focus(!1),d.hideInput()):d.focus(!1)},0),this.updatePlaceholder(),this.trigger("item_add",a,b),this.updateOriginalInput()))})};f.prototype.removeItem=function(a){var b,c;b="object"===typeof a?a:this.getItem(a);a=String(b.attr("data-value"));c=this.items.indexOf(a);-1!==c&&(b.remove(),b.hasClass("active")&&(b=this.$activeItems.indexOf(b[0]),this.$activeItems.splice(b,1)),this.items.splice(c,1),this.lastQuery=null,!this.settings.persist&&this.userOptions.hasOwnProperty(a)&&this.removeOption(a), -c=this.settings.maxItems};f.prototype.updateOriginalInput=function(){var a,b,c;if("select"===this.$input[0].tagName.toLowerCase()){c=[];a=0;for(b=this.items.length;a'); -c.length||this.$input.attr("multiple")||c.push('');this.$input.html(c.join(""))}else this.$input.val(this.getValue());this.$input.trigger("change");this.isSetup&&this.trigger("change",this.$input.val())};f.prototype.updatePlaceholder=function(){if(this.settings.placeholder){var a=this.$control_input;this.items.length?a.removeAttr("placeholder"):a.attr("placeholder",this.settings.placeholder);a.triggerHandler("update")}};f.prototype.open=function(){this.isLocked|| -(this.isOpen||"multi"===this.settings.mode&&this.isFull())||(this.focus(),this.isOpen=!0,this.$dropdown.css({visibility:"hidden",display:"block"}),this.$control.addClass("dropdown-active"),this.positionDropdown(),this.$dropdown.css({visibility:"visible"}),this.trigger("dropdown_open",this.$dropdown))};f.prototype.close=function(){this.isOpen&&(this.$dropdown.hide(),this.$control.removeClass("dropdown-active"),this.setActiveOption(null),this.isOpen=!1,this.trigger("dropdown_close",this.$dropdown))}; -f.prototype.positionDropdown=function(){var a=this.$control,b="body"===this.settings.dropdownParent?a.offset():a.position();b.top+=a.outerHeight(!0);this.$dropdown.css({width:a.outerWidth(),top:b.top,left:b.left})};f.prototype.clear=function(){this.items.length&&(this.$control.children(":not(input)").remove(),this.items=[],this.setCaret(0),this.updatePlaceholder(),this.updateOriginalInput(),this.refreshClasses(),this.showInput(),this.trigger("clear"))};f.prototype.insertAtCaret=function(a){var b= -Math.min(this.caretPos,this.items.length);0===b?this.$control.prepend(a):g(this.$control[0].childNodes[b]).before(a);this.setCaret(b+1)};f.prototype.deleteSelection=function(a){var b,c,d,e;b=a&&8===a.keyCode?-1:1;c=u(this.$control_input[0]);d=[];if(this.$activeItems.length){e=this.$control.children(".active:"+(0b&&0===c.start&&0===c.length?d.push(this.items[this.caretPos-1]):0a?0===d.start&&0===d.length:d.start===c)&&!c&&this.advanceCaret(a,b)):(d=this.$control.children(".active:"+c),d.length&&(d=this.$control.children(":not(input)").index(d),this.setActiveItem(null),this.setCaret(0";break;case "optgroup_header":d=b[this.settings.optgroupLabelField];e='
'+l(d)+"
";break;case "option":e='
'+l(d)+"
";break;case "item":e='
'+l(d)+"
";break;case "option_create":e='
Create '+l(b.input)+"
"}if("option"=== -a||"option_create"===a)e=e.replace(f,"<$1 data-selectable");"optgroup"===a&&(d=b[this.settings.optgroupValueField]||"",e=e.replace(f,'<$1 data-group="'+l(d)+'"'));if("option"===a||"item"===a)e=e.replace(f,'<$1 data-value="'+l(c||"")+'"');h&&(this.renderCache[a][c]=e);return e};f.defaults={plugins:[],delimiter:",",persist:!0,diacritics:!0,create:!1,highlight:!0,openOnFocus:!0,maxOptions:1E3,maxItems:null,hideSelected:null,preload:!1,scrollDuration:60,loadThrottle:300,dataAttr:"data-data",optgroupField:"optgroup", -sortField:null,sortDirection:"asc",valueField:"value",labelField:"text",optgroupLabelField:"label",optgroupValueField:"value",optgroupOrder:null,searchField:["text"],mode:null,theme:"default",wrapperClass:"selectize-control",inputClass:"selectize-input",dropdownClass:"selectize-dropdown",dropdownContentClass:"selectize-dropdown-content",dropdownParent:null,load:null,score:null,onChange:null,onItemAdd:null,onItemRemove:null,onClear:null,onOptionAdd:null,onOptionRemove:null,onOptionClear:null,onDropdownOpen:null, -onDropdownClose:null,onType:null,onDelete:null,render:{item:null,optgroup:null,optgroup_header:null,option:null,option_create:null}};g.fn.selectize=function(a){a=a||{};var b=g.fn.selectize.defaults,c=a.dataAttr||b.dataAttr,d=function(a,b){var d,f,m,n;b.maxItems=a.attr("multiple")?null:1;var l=function(a){a=c&&a.attr(c);return"string"===typeof a&&a.length?JSON.parse(a):null},p=function(a,d){a=g(a);var c=a.attr("value")||"";c.length&&(b.options[c]=l(a)||{text:a.html(),value:c,optgroup:d},a.is(":selected")&& -b.items.push(c))},q=function(a){var d,c=g("option",a);a=g(a);var e=a.attr("label");e&&e.length&&(b.optgroups[e]=l(a)||{label:e});a=0;for(d=c.length;a'+a[b.settings.labelField]+' ×
'};this.setup=function(){var a=b.setup;return function(){a.apply(this,arguments);this.$control.on("click",".remove",function(a){a.preventDefault();a=g(a.target).parent();b.setActiveItem(a);b.deleteSelection()&&b.setCaret(b.items.length)})}}()});f.registerPlugin("restore_on_backspace",function(a){var b= -this;a.text=a.text||function(a){return a[this.settings.labelField]};this.onKeyDown=function(c){var d=b.onKeyDown;return function(b){var c;if(8===b.keyCode&&(""===this.$control_input.val()&&!this.$activeItems.length)&&(c=this.caretPos-1,0<=c&&c/g,">").replace(/"/g,""")},v={before:function(a,b,c){var d=a[b];a[b]=function(){c.apply(a,arguments);return d.apply(a,arguments)}},after:function(a, +b,c){var d=a[b];a[b]=function(){var b=d.apply(a,arguments);c.apply(a,arguments);return b}}},w=function(a,b){if(!f.isArray(b))return b;var c,d,e={};c=0;for(d=b.length;c=e||65<=e&&90>=e||48<=e&&57>=e||32==e,46===e||8===e?(b=u(a[0]),b.length?d=d.substring(0,b.start)+d.substring(b.start+b.length):8===e&&b.start?d=d.substring(0,b.start-1)+d.substring(b.start+1):46===e&&"undefined"!==typeof b.start&& +(d=d.substring(0,b.start)+d.substring(b.start+1))):g&&(e=b.shiftKey,b=String.fromCharCode(b.keyCode),b=e?b.toUpperCase():b.toLowerCase(),d+=b));b=a.attr("placeholder")||"";!d.length&&b.length&&(d=b);d=f("").css({position:"absolute",top:-99999,left:-99999,width:"auto",padding:0,whiteSpace:"nowrap"}).text(d).appendTo("body");b=["letterSpacing","fontSize","fontFamily","fontWeight","textTransform"];var h={};if(b)for(e=0,g=b.length;e").addClass(b.theme).addClass(b.wrapperClass).addClass(c); +d=f("
").addClass(b.inputClass).addClass("items").toggleClass("has-options",!f.isEmptyObject(a.options)).appendTo(c);e=f('').appendTo(d).attr("tabindex",e);g=f(b.dropdownParent||c);g=f("
").addClass(b.dropdownClass).hide().appendTo(g);h=f("
").addClass(b.dropdownContentClass).appendTo(g);c.css({width:a.$input[0].style.width,display:a.$input.css("display")});a.plugins.length&&c.addClass("plugin-"+a.plugins.join(" plugin-"));p=a.settings.mode;c.toggleClass("single","single"=== +p);c.toggleClass("multi","multi"===p);(null===b.maxItems||1c&&(e=d,d=c,c=e);for(;d<=c;d++)e=this.$control[0].childNodes[d],-1===this.$activeItems.indexOf(e)&&(f(e).addClass("active"),this.$activeItems.push(e));b.preventDefault()}else"mousedown"=== +c&&this.isCtrlDown||"keydown"===c&&this.isShiftDown?a.hasClass("active")?(c=this.$activeItems.indexOf(a[0]),this.$activeItems.splice(c,1),a.removeClass("active")):this.$activeItems.push(a.addClass("active")[0]):(f(this.$activeItems).removeClass("active"),this.$activeItems=[a.addClass("active")[0]]);this.isFocused=!!this.$activeItems.length||this.isInputFocused}else f(this.$activeItems).removeClass("active"),this.$activeItems=[],this.isFocused=this.isInputFocused},setActiveOption:function(a,b,c){var d, +e,g;this.$activeOption&&this.$activeOption.removeClass("active");this.$activeOption=null;a=f(a);a.length&&(this.$activeOption=a.addClass("active"),b||!q(b))&&(a=this.$dropdown.height(),d=this.$activeOption.outerHeight(!0),b=this.$dropdown.scrollTop()||0,e=this.$activeOption.offset().top-this.$dropdown.offset().top+b,g=e-a+d,e+d>a-b?this.$dropdown.stop().animate({scrollTop:g},c?this.settings.scrollDuration:0):ee?1*b:e>d?-1*b:0}}())}c.currentResults=g}else g=f.extend(!0,{},c.currentResults);return c.prepareResults(g,b)},prepareResults:function(a,b){if(this.settings.hideSelected)for(var c=a.items.length-1;0<=c;c--)-1!==this.items.indexOf(String(a.items[c].value))&&a.items.splice(c,1);a.total=a.items.length;"number"===typeof b.limit&&(a.items=a.items.slice(0,b.limit));return a},refreshOptions:function(a){"undefined"===typeof a&&(a=!0);var b,c,d,e,g,h,p,m=this.$control_input.val(), +n=this.search(m,{}),l,k=this.$dropdown_content;c=n.items.length;"number"===typeof this.settings.maxOptions&&(c=Math.min(c,this.settings.maxOptions));d={};if(this.settings.optgroupOrder)for(e=this.settings.optgroupOrder,b=0;b=this.caretPos&&b++,b=f(this.$control[0].childNodes[b]),b.attr("data-value")===a)?b:f()},addItem:function(a){x(this,["change"],function(){var b,c,d=this,e=d.settings.mode,g;a=String(a);"single"===e&&d.clear();"multi"===e&&d.isFull()||(-1!==d.items.indexOf(a)||!d.options.hasOwnProperty(a))||(b=f(d.render("item",d.options[a])),d.items.splice(d.caretPos,0,a),d.insertAtCaret(b),d.refreshClasses(), +d.isSetup&&(g=d.$dropdown_content.find("[data-selectable]"),c=d.getOption(a),c=d.getAdjacentOption(c,1).attr("data-value"),d.refreshOptions(!0),c&&d.setActiveOption(d.getOption(c)),!g.length||null!==d.settings.maxItems&&d.items.length>=d.settings.maxItems?d.close():d.positionDropdown(),d.isFocused&&window.setTimeout(function(){"single"===e?(d.blur(),d.focus(!1),d.hideInput()):d.focus(!1)},0),d.updatePlaceholder(),d.trigger("item_add",a,b),d.updateOriginalInput()))})},removeItem:function(a){var b, +c;b="object"===typeof a?a:this.getItem(a);a=String(b.attr("data-value"));c=this.items.indexOf(a);-1!==c&&(b.remove(),b.hasClass("active")&&(b=this.$activeItems.indexOf(b[0]),this.$activeItems.splice(b,1)),this.items.splice(c,1),this.lastQuery=null,!this.settings.persist&&this.userOptions.hasOwnProperty(a)&&this.removeOption(a),c= +this.settings.maxItems},updateOriginalInput:function(){var a,b,c;if("select"===this.$input[0].tagName.toLowerCase()){c=[];a=0;for(b=this.items.length;a');c.length||this.$input.attr("multiple")||c.push('');this.$input.html(c.join(""))}else this.$input.val(this.getValue());this.$input.trigger("change");this.isSetup&&this.trigger("change",this.$input.val())},updatePlaceholder:function(){if(this.settings.placeholder){var a= +this.$control_input;this.items.length?a.removeAttr("placeholder"):a.attr("placeholder",this.settings.placeholder);a.triggerHandler("update")}},open:function(){this.isLocked||(this.isOpen||"multi"===this.settings.mode&&this.isFull())||(this.focus(),this.isOpen=!0,this.$dropdown.css({visibility:"hidden",display:"block"}),this.$control.addClass("dropdown-active"),this.positionDropdown(),this.$dropdown.css({visibility:"visible"}),this.trigger("dropdown_open",this.$dropdown))},close:function(){this.isOpen&& +(this.$dropdown.hide(),this.$control.removeClass("dropdown-active"),this.setActiveOption(null),this.isOpen=!1,this.trigger("dropdown_close",this.$dropdown))},positionDropdown:function(){var a=this.$control,b="body"===this.settings.dropdownParent?a.offset():a.position();b.top+=a.outerHeight(!0);this.$dropdown.css({width:a.outerWidth(),top:b.top,left:b.left})},clear:function(){this.items.length&&(this.$control.children(":not(input)").remove(),this.items=[],this.setCaret(0),this.updatePlaceholder(), +this.updateOriginalInput(),this.refreshClasses(),this.showInput(),this.trigger("clear"))},insertAtCaret:function(a){var b=Math.min(this.caretPos,this.items.length);0===b?this.$control.prepend(a):f(this.$control[0].childNodes[b]).before(a);this.setCaret(b+1)},deleteSelection:function(a){var b,c,d,e;b=a&&8===a.keyCode?-1:1;c=u(this.$control_input[0]);d=[];if(this.$activeItems.length){e=this.$control.children(".active:"+(0b&&0===c.start&&0===c.length?d.push(this.items[this.caretPos-1]):0a?0===d.start&&0===d.length:d.start===c)&&!c&&this.advanceCaret(a,b)):(d=this.$control.children(".active:"+c),d.length&&(d=this.$control.children(":not(input)").index(d),this.setActiveItem(null),this.setCaret(0";break;case "optgroup_header":d=b[this.settings.optgroupLabelField];e='
'+k(d)+"
";break;case "option":e='
'+k(d)+"
";break;case "item":e='
'+k(d)+"
";break;case "option_create":e='
Create '+k(b.input)+"
"}if("option"=== +a||"option_create"===a)e=e.replace(h,"<$1 data-selectable");"optgroup"===a&&(d=b[this.settings.optgroupValueField]||"",e=e.replace(h,'<$1 data-group="'+k(d)+'"'));if("option"===a||"item"===a)e=e.replace(h,'<$1 data-value="'+k(c||"")+'"');g&&(this.renderCache[a][c]=e);return e}});l.defaults={plugins:[],delimiter:",",persist:!0,diacritics:!0,create:!1,highlight:!0,openOnFocus:!0,maxOptions:1E3,maxItems:null,hideSelected:null,preload:!1,scrollDuration:60,loadThrottle:300,dataAttr:"data-data",optgroupField:"optgroup", +sortField:null,sortDirection:"asc",valueField:"value",labelField:"text",optgroupLabelField:"label",optgroupValueField:"value",optgroupOrder:null,searchField:["text"],mode:null,theme:"default",wrapperClass:"selectize-control",inputClass:"selectize-input",dropdownClass:"selectize-dropdown",dropdownContentClass:"selectize-dropdown-content",dropdownParent:null,render:{}};f.fn.selectize=function(a){a=a||{};var b=f.fn.selectize.defaults,c=a.dataAttr||b.dataAttr,d=function(a,b){var d,l,m,n;b.maxItems=a.attr("multiple")? +null:1;var k=function(a){a=c&&a.attr(c);return"string"===typeof a&&a.length?JSON.parse(a):null},q=function(a,d){a=f(a);var c=a.attr("value")||"";c.length&&(b.options[c]=k(a)||{text:a.html(),value:c,optgroup:d},a.is(":selected")&&b.items.push(c))},r=function(a){var d,c=f("option",a);a=f(a);var e=a.attr("label");e&&e.length&&(b.optgroups[e]=k(a)||{label:e});a=0;for(d=c.length;a'+a[b.settings.labelField]+' ×
'};this.setup=function(){var a=b.setup;return function(){a.apply(this,arguments);this.$control.on("click",".remove",function(a){a.preventDefault();a=f(a.target).parent();b.setActiveItem(a); +b.deleteSelection()&&b.setCaret(b.items.length)})}}()});l.registerPlugin("restore_on_backspace",function(a){var b=this;a.text=a.text||function(a){return a[this.settings.labelField]};this.onKeyDown=function(c){var d=b.onKeyDown;return function(b){var c;if(8===b.keyCode&&(""===this.$control_input.val()&&!this.$activeItems.length)&&(c=this.caretPos-1,0<=c&&c