/*! selectize.js - v0.6.1 | https://github.com/brianreavis/selectize.js | Apache License (v2) */ (function(factory) { if (typeof exports === 'object') { factory(require('jquery')); } else if (typeof define === 'function' && define.amd) { define(['jquery'], factory); } else { factory(jQuery); } }(function ($) { "use strict"; /* --- file: "src/contrib/highlight.js" --- */ /** * highlight v3 | MIT license | Johann Burkard * Highlights arbitrary terms in a node. * * - Modified by Marshal 2011-6-24 (added regex) * - Modified by Brian Reavis 2012-8-27 (cleanup) */ var highlight = function($element, pattern) { if (typeof pattern === 'string' && !pattern.length) return; var regex = (typeof pattern === 'string') ? new RegExp(pattern, 'i') : pattern; var highlight = function(node) { var skip = 0; if (node.nodeType === 3) { var pos = node.data.search(regex); if (pos >= 0 && node.data.length > 0) { var match = node.data.match(regex); var spannode = document.createElement('span'); spannode.className = 'highlight'; var middlebit = node.splitText(pos); var endbit = middlebit.splitText(match[0].length); var middleclone = middlebit.cloneNode(true); spannode.appendChild(middleclone); middlebit.parentNode.replaceChild(spannode, middlebit); skip = 1; } } else if (node.nodeType === 1 && node.childNodes && !/(script|style)/i.test(node.tagName)) { for (var i = 0; i < node.childNodes.length; ++i) { i += highlight(node.childNodes[i]); } } return skip; }; return $element.each(function() { highlight(this); }); }; var unhighlight = function($element) { return $element.find('span.highlight').each(function() { var parent = this.parentNode; parent.replaceChild(parent.firstChild, parent); parent.normalize(); }).end(); }; /* --- file: "src/contrib/microevent.js" --- */ /** * MicroEvent - to make any js object an event emitter * * - pure javascript - server compatible, browser compatible * - dont rely on the browser doms * - super simple - you get it immediatly, no mistery, no magic involved * * @author Jerome Etienne (https://github.com/jeromeetienne) */ var MicroEvent = function() {}; MicroEvent.prototype = { on: function(event, fct){ this._events = this._events || {}; this._events[event] = this._events[event] || []; this._events[event].push(fct); }, off: function(event, fct){ this._events = this._events || {}; if (event in this._events === false) return; this._events[event].splice(this._events[event].indexOf(fct), 1); }, trigger: function(event /* , args... */){ this._events = this._events || {}; if (event in this._events === false) return; for (var i = 0; i < this._events[event].length; i++){ this._events[event][i].apply(this, Array.prototype.slice.call(arguments, 1)); } } }; /** * Mixin will delegate all MicroEvent.js function in the destination object. * * - MicroEvent.mixin(Foobar) will make Foobar able to use MicroEvent * * @param {object} the object which will support MicroEvent */ MicroEvent.mixin = function(destObject){ var props = ['on', 'off', 'trigger']; for (var i = 0; i < props.length; i++){ destObject.prototype[props[i]] = MicroEvent.prototype[props[i]]; } }; /* --- file: "src/constants.js" --- */ /** * selectize - A highly customizable select control with autocomplete. * Copyright (c) 2013 Brian Reavis & contributors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this * file except in compliance with the License. You may obtain a copy of the License at: * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF * ANY KIND, either express or implied. See the License for the specific language * governing permissions and limitations under the License. * * @author Brian Reavis */ var IS_MAC = /Mac/.test(navigator.userAgent); var KEY_A = 65; var KEY_COMMA = 188; var KEY_RETURN = 13; var KEY_ESC = 27; var KEY_LEFT = 37; var KEY_UP = 38; var KEY_RIGHT = 39; var KEY_DOWN = 40; var KEY_BACKSPACE = 8; var KEY_DELETE = 46; var KEY_SHIFT = 16; var KEY_CMD = IS_MAC ? 91 : 17; var KEY_CTRL = IS_MAC ? 18 : 17; var KEY_TAB = 9; var TAG_SELECT = 1; var TAG_INPUT = 2; var DIACRITICS = { 'a': '[aÀÁÂÃÄÅàáâãäå]', 'c': '[cÇç]', 'e': '[eÈÉÊËèéêë]', 'i': '[iÌÍÎÏìíîï]', 'n': '[nÑñ]', 'o': '[oÒÓÔÕÕÖØòóôõöø]', 's': '[sŠš]', 'u': '[uÙÚÛÜùúûü]', 'y': '[yŸÿý]', 'z': '[zŽž]' }; /* --- file: "src/plugins.js" --- */ var Plugins = {}; Plugins.mixin = function(Interface, interfaceName) { Interface.plugins = {}; /** * Initializes the provided functions. * Acceptable formats: * * List (without options): * ['a', 'b', 'c'] * * List (with options) * {'a': { ... }, 'b': { ... }, 'c': { ... }} * * @param {mixed} plugins */ Interface.prototype.loadPlugins = function(plugins) { var i, n, key; this.plugins = []; this.pluginSettings = {}; if ($.isArray(plugins)) { for (i = 0, n = plugins.length; i < n; i++) { this.loadPlugin(plugins[i]); } } else if (plugins) { this.pluginSettings = $.extend({}, plugins); for (key in plugins) { if (plugins.hasOwnProperty(key)) { this.loadPlugin(key); } } } }; /** * Initializes a plugin. * * @param {string} name */ Interface.prototype.loadPlugin = function(name) { var plugin, i, n; if (this.plugins.indexOf(name) !== -1) return; if (!Interface.plugins.hasOwnProperty(name)) { throw new Error(interfaceName + ' unable to find "' + name + '" plugin'); } plugin = Interface.plugins[name]; // initialize plugin and dependencies this.plugins.push(name); for (i = 0, n = plugin.dependencies.length; i < n; i++) { this.loadPlugin(plugin.dependencies[i]); } plugin.fn.apply(this, [this.pluginSettings[name] || {}]); }; /** * Registers a plugin. * * @param {string} name * @param {array} dependencies (optional) * @param {function} fn */ Interface.registerPlugin = function(name) { var args = arguments; Interface.plugins[name] = { 'name' : name, 'fn' : args[args.length - 1], 'dependencies' : args.length === 3 ? args[1] : [] }; }; }; /* --- file: "src/utils.js" --- */ var isset = function(object) { return typeof object !== 'undefined'; }; var htmlEntities = function(str) { return String(str).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); }; var quoteRegExp = function(str) { return (str + '').replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1'); }; var hook = {}; /** * Wraps `method` on `self` so that `fn` * is invoked before the original method. * * @param {object} self * @param {string} method * @param {function} fn */ hook.before = function(self, method, fn) { var original = self[method]; self[method] = function() { fn.apply(self, arguments); return original.apply(self, arguments); }; }; /** * Wraps `method` on `self` so that `fn` * is invoked after the original method. * * @param {object} self * @param {string} method * @param {function} fn */ hook.after = function(self, method, fn) { var original = self[method]; self[method] = function() { var result = original.apply(self, arguments); fn.apply(self, arguments); return result; }; }; var once = function(fn) { var called = false; return function() { if (called) return; called = true; fn.apply(this, arguments); }; }; var debounce = function(fn, delay) { var timeout; return function() { var self = this; var args = arguments; window.clearTimeout(timeout); timeout = window.setTimeout(function() { fn.apply(self, args); }, delay); }; }; /** * Debounce all fired events types listed in `types` * while executing the provided `fn`. * * @param {object} self * @param {array} types * @param {function} fn */ var debounce_events = function(self, types, fn) { var type; var trigger = self.trigger; var event_args = {}; // override trigger method self.trigger = function() { event_args[arguments[0]] = arguments; }; // invoke provided function fn.apply(self, []); self.trigger = trigger; // trigger queued events for (type in event_args) { if (event_args.hasOwnProperty(type)) { trigger.apply(self, event_args[type]); } } }; /** * A workaround for http://bugs.jquery.com/ticket/6696 * * @param {object} $parent - Parent element to listen on. * @param {string} event - Event name. * @param {string} selector - Descendant selector to filter by. * @param {function} fn - Event handler. */ var watchChildEvent = function($parent, event, selector, fn) { $parent.on(event, selector, function(e) { var child = e.target; while (child && child.parentNode !== $parent[0]) { child = child.parentNode; } e.currentTarget = child; return fn.apply(this, [e]); }); }; var getSelection = function(input) { var result = {}; if ('selectionStart' in input) { result.start = input.selectionStart; result.length = input.selectionEnd - result.start; } else if (document.selection) { input.focus(); var sel = document.selection.createRange(); var selLen = document.selection.createRange().text.length; sel.moveStart('character', -input.value.length); result.start = sel.text.length - selLen; result.length = selLen; } return result; }; var transferStyles = function($from, $to, properties) { var styles = {}; if (properties) { for (var i = 0; i < properties.length; i++) { styles[properties[i]] = $from.css(properties[i]); } } else { styles = $from.css(); } $to.css(styles); return $to; }; var measureString = function(str, $parent) { var $test = $('').css({ position: 'absolute', top: -99999, left: -99999, width: 'auto', padding: 0, whiteSpace: 'nowrap' }).text(str).appendTo('body'); transferStyles($parent, $test, [ 'letterSpacing', 'fontSize', 'fontFamily', 'fontWeight', 'textTransform' ]); var width = $test.width(); $test.remove(); return width; }; var autoGrow = function($input) { var update = function(e) { var value, keyCode, printable, placeholder, width; var shift, character, selection; e = e || window.event || {}; if (e.metaKey || e.altKey) return; if ($input.data('grow') === false) return; value = $input.val(); if (e.type && e.type.toLowerCase() === 'keydown') { keyCode = e.keyCode; printable = ( (keyCode >= 97 && keyCode <= 122) || // a-z (keyCode >= 65 && keyCode <= 90) || // A-Z (keyCode >= 48 && keyCode <= 57) || // 0-9 keyCode == 32 // space ); if (keyCode === KEY_DELETE || keyCode === KEY_BACKSPACE) { selection = getSelection($input[0]); if (selection.length) { value = value.substring(0, selection.start) + value.substring(selection.start + selection.length); } else if (keyCode === KEY_BACKSPACE && selection.start) { value = value.substring(0, selection.start - 1) + value.substring(selection.start + 1); } else if (keyCode === KEY_DELETE && typeof selection.start !== 'undefined') { value = value.substring(0, selection.start) + value.substring(selection.start + 1); } } else if (printable) { shift = e.shiftKey; character = String.fromCharCode(e.keyCode); if (shift) character = character.toUpperCase(); else character = character.toLowerCase(); value += character; } } placeholder = $input.attr('placeholder') || ''; if (!value.length && placeholder.length) { value = placeholder; } width = measureString(value, $input) + 4; if (width !== $input.width()) { $input.width(width); $input.triggerHandler('resize'); } }; $input.on('keydown keyup update blur', update); update(); }; /* --- file: "src/selectize.js" --- */ /** * selectize.js * Copyright (c) 2013 Brian Reavis & contributors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this * file except in compliance with the License. You may obtain a copy of the License at: * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF * ANY KIND, either express or implied. See the License for the specific language * governing permissions and limitations under the License. * * @author Brian Reavis */ var Selectize = function($input, settings) { var key, i, n; $input[0].selectize = this; this.$input = $input; this.tagType = $input[0].tagName.toLowerCase() === 'select' ? TAG_SELECT : TAG_INPUT; this.settings = settings; this.highlightedValue = null; this.isOpen = false; this.isDisabled = false; this.isLocked = false; this.isFocused = false; this.isInputFocused = false; this.isInputHidden = false; this.isSetup = false; this.isShiftDown = false; this.isCmdDown = false; this.isCtrlDown = false; this.ignoreFocus = false; this.ignoreHover = false; this.hasOptions = false; this.currentResults = null; this.lastValue = ''; this.caretPos = 0; this.loading = 0; this.loadedSearches = {}; this.$activeOption = null; this.$activeItems = []; this.optgroups = {}; this.options = {}; this.userOptions = {}; this.items = []; this.renderCache = {}; this.onSearchChange = debounce(this.onSearchChange, this.settings.loadThrottle); if ($.isArray(settings.options)) { key = settings.valueField; for (i = 0, n = settings.options.length; i < n; i++) { if (settings.options[i].hasOwnProperty(key)) { this.options[settings.options[i][key]] = settings.options[i]; } } } else if (typeof settings.options === 'object') { $.extend(this.options, settings.options); delete this.settings.options; } if ($.isArray(settings.optgroups)) { key = settings.optgroupValueField; for (i = 0, n = settings.optgroups.length; i < n; i++) { if (settings.optgroups[i].hasOwnProperty(key)) { this.optgroups[settings.optgroups[i][key]] = settings.optgroups[i]; } } } else if (typeof settings.optgroups === 'object') { $.extend(this.optgroups, settings.optgroups); delete this.settings.optgroups; } // option-dependent defaults this.settings.mode = this.settings.mode || (this.settings.maxItems === 1 ? 'single' : 'multi'); if (typeof this.settings.hideSelected !== 'boolean') { this.settings.hideSelected = this.settings.mode === 'multi'; } this.loadPlugins(this.settings.plugins); this.setupCallbacks(); this.setup(); }; // mixins // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - MicroEvent.mixin(Selectize); Plugins.mixin(Selectize, 'Selectize'); // methods // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - /** * Creates all elements and sets up event bindings. */ Selectize.prototype.setup = function() { var self = this; var $wrapper; var $control; var $control_input; var $dropdown; var $dropdown_content; var inputMode; var timeout_blur; var timeout_focus; var tab_index; var classes; tab_index = this.$input.attr('tabindex') || ''; classes = this.$input.attr('class') || ''; $wrapper = $('
').addClass(this.settings.theme).addClass(this.settings.wrapperClass).addClass(classes); $control = $('
').addClass(this.settings.inputClass).addClass('items').toggleClass('has-options', !$.isEmptyObject(this.options)).appendTo($wrapper); $control_input = $('').appendTo($control).attr('tabindex',tab_index); $dropdown = $('
').addClass(this.settings.dropdownClass).hide().appendTo($wrapper); $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'); if ((this.settings.maxItems === null || this.settings.maxItems > 1) && this.tagType === TAG_SELECT) { this.$input.attr('multiple', 'multiple'); } if (this.settings.placeholder) { $control_input.attr('placeholder', this.settings.placeholder); } this.$wrapper = $wrapper; this.$control = $control; this.$control_input = $control_input; this.$dropdown = $dropdown; this.$dropdown_content = $dropdown_content; $control.on('mousedown', function(e) { if (!e.isDefaultPrevented()) { window.setTimeout(function() { self.focus(true); }, 0); } }); $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); } }); $(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(); } } } }); $(window).on({ resize: function() { if (self.isOpen) { self.positionDropdown.apply(self, arguments); } }, mousemove: function() { self.ignoreHover = false; } }); this.$input.attr('tabindex',-1).hide().after(this.$wrapper); if ($.isArray(this.settings.items)) { this.setValue(this.settings.items); delete this.settings.items; } this.updateOriginalInput(); this.refreshItems(); this.updatePlaceholder(); this.isSetup = true; if (this.$input.is(':disabled')) { this.disable(); } // preload options if (this.settings.preload) { this.onSearchChange(''); } }; /** * 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' }; 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 */ 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); } }; /** * 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; } }; /** * Triggered on keydown. * * @param {object} e * @returns {boolean} */ Selectize.prototype.onKeyDown = function(e) { var isInput = e.target === this.$control_input[0]; if (this.isLocked) { if (e.keyCode !== KEY_TAB) { e.preventDefault(); } return; } switch (e.keyCode) { case KEY_A: if (this.isCmdDown) { this.selectAll(); 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(); e.preventDefault(); } return; case KEY_BACKSPACE: case KEY_DELETE: this.deleteSelection(e); 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); } }; /** * 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]); }); }; /** * 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; } if (this.ignoreFocus) return; this.showInput(); this.setActiveItem(null); this.refreshOptions(!!this.settings.openOnFocus); this.refreshClasses(); }; /** * 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(); }; /** * 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); }; /** * 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 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(); } }; /** * 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'); 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); }]); }; /** * 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; }; /** * Returns the value of the control. If multiple items * can be selected (e.g. or * element to reflect the current state. */ Selectize.prototype.updateOriginalInput = function() { var i, n, options; 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(''); } this.$input.html(options.join('')); } else { this.$input.val(this.getValue()); } this.$input.trigger('change'); if (this.isSetup) { this.trigger('change', this.$input.val()); } }; /** * 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 (this.items.length) { $input.removeAttr('placeholder'); } else { $input.attr('placeholder', this.settings.placeholder); } $input.triggerHandler('update'); }; /** * 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); }; /** * 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. */ Selectize.prototype.positionDropdown = function() { var $control = this.$control; var 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. */ 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'); }; /** * 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); }; /** * 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; direction = (e && e.keyCode === KEY_BACKSPACE) ? -1 : 1; selection = getSelection(this.$control_input[0]); // 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++; } 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]); } } // allow the callback to abort if (!values.length || (typeof this.settings.onDelete === 'function' && this.settings.onDelete(values) === false)) { return false; } // perform removal if (typeof caret !== 'undefined') { this.setCaret(caret); } while (values.length) { this.removeItem(values.pop()); } this.showInput(); this.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; if (direction === 0) return; tail = direction > 0 ? 'last' : 'first'; selection = getSelection(this.$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 (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(); } } }; /** * 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(); } } 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)); } // 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); } else { this.$control.append($child); } } this.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(); }; /** * Re-enables user input on the control. */ Selectize.prototype.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(); }; /** * Enables the control so that it can respond * to focus and user input. */ Selectize.prototype.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); } // pull markup from cache if it exists if (cache) { if (!isset(this.renderCache[templateName])) { this.renderCache[templateName] = {}; } if (this.renderCache[templateName].hasOwnProperty(value)) { return this.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; } } // 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 || '') + '"'); } // update cache if (cache) { this.renderCache[templateName][value] = html; } return html; }; Selectize.defaults = { plugins: [], delimiter: ',', persist: true, diacritics: true, create: false, highlight: true, openOnFocus: true, maxOptions: 1000, maxItems: null, hideSelected: null, preload: false, 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', load : null, // function(query, callback) score : null, // function(search) onChange : null, // function(value) onItemAdd : null, // function(value, $item) { ... } onItemRemove : null, // function(value) { ... } onClear : null, // function() { ... } onOptionAdd : null, // function(value, data) { ... } onOptionRemove : null, // function(value) { ... } onOptionClear : null, // function() { ... } onDropdownOpen : null, // function($dropdown) { ... } 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 } }; /* --- file: "src/selectize.jquery.js" --- */ $.fn.selectize = function(settings) { settings = settings || {}; var defaults = $.fn.selectize.defaults; var dataAttr = settings.dataAttr || defaults.dataAttr; /** * Initializes selectize from a element. * * @param {object} $input * @param {object} settings */ var init_textbox = function($input, settings_element) { var i, n, values, value = $.trim($input.val() || ''); if (!value.length) return; values = value.split(settings.delimiter || defaults.delimiter); for (i = 0, n = values.length; i < n; i++) { settings_element.options[values[i]] = { 'text' : values[i], 'value' : values[i] }; } settings_element.items = values; }; /** * Initializes selectize from a