PageSourceSearch

https://www.nsu.ru/local/templates/nsu_ru/js/selectize.js

js nsu.ru collected 2026-09-24 08:47:18 UTC 102,268 bytes, 3,667 lines download raw bytes

vendor: 31,801 bytes, lines 1-1177
1/**
2 * sifter.js
3 * Copyright (c) 2013 Brian Reavis & contributors
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
6 * file except in compliance with the License. You may obtain a copy of the License at:
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software distributed under
10 * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
11 * ANY KIND, either express or implied. See the License for the specific language
12 * governing permissions and limitations under the License.
13 *
14 * @author Brian Reavis <[email protected]>
15 */
16
17(function(root, factory) {
18	if (typeof define === 'function' && define.amd) {
19		define('sifter', factory);
20	} else if (typeof exports === 'object') {
21		module.exports = factory();
22	} else {
23		root.Sifter = factory();
24	}
25}(this, function() {
26
27	/**
28	 * Textually searches arrays and hashes of objects
29	 * by property (or multiple properties). Designed
30	 * specifically for autocomplete.
31	 *
32	 * @constructor
33	 * @param {array|object} items
34	 * @param {object} items
35	 */
36	var Sifter = function(items, settings) {
37		this.items = items;
38		this.settings = settings || {diacritics: true};
39	};
40
41	/**
42	 * Splits a search string into an array of individual
43	 * regexps to be used to match results.
44	 *
45	 * @param {string} query
46	 * @returns {array}
47	 */
48	Sifter.prototype.tokenize = function(query) {
49		query = trim(String(query || '').toLowerCase());
50		if (!query || !query.length) return [];
51
52		var i, n, regex, letter;
53		var tokens = [];
54		var words = query.split(/ +/);
55
56		for (i = 0, n = words.length; i < n; i++) {
57			regex = escape_regex(words[i]);
58			if (this.settings.diacritics) {
59				for (letter in DIACRITICS) {
60					if (DIACRITICS.hasOwnProperty(letter)) {
61						regex = regex.replace(new RegExp(letter, 'g'), DIACRITICS[letter]);
62					}
63				}
64			}
65			tokens.push({
66				string : words[i],
67				regex  : new RegExp(regex, 'i')
68			});
69		}
70
71		return tokens;
72	};
73
74	/**
75	 * Iterates over arrays and hashes.
76	 *
77	 * ```
78	 * this.iterator(this.items, function(item, id) {
79	 *    // invoked for each item
80	 * });
81	 * ```
82	 *
83	 * @param {array|object} object
84	 */
85	Sifter.prototype.iterator = function(object, callback) {
86		var iterator;
87		if (is_array(object)) {
88			iterator = Array.prototype.forEach || function(callback) {
89				for (var i = 0, n = this.length; i < n; i++) {
90					callback(this[i], i, this);
91				}
92			};
93		} else {
94			iterator = function(callback) {
95				for (var key in this) {
96					if (this.hasOwnProperty(key)) {
97						callback(this[key], key, this);
98					}
99				}
100			};
101		}
102
103		iterator.apply(object, [callback]);
104	};
105
106	/**
107	 * Returns a function to be used to score individual results.
108	 *
109	 * Good matches will have a higher score than poor matches.
110	 * If an item is not a match, 0 will be returned by the function.
111	 *
112	 * @param {object|string} search
113	 * @param {object} options (optional)
114	 * @returns {function}
115	 */
116	Sifter.prototype.getScoreFunction = function(search, options) {
117		var self, fields, tokens, token_count;
118
119		self        = this;
120		search      = self.prepareSearch(search, options);
121		tokens      = search.tokens;
122		fields      = search.options.fields;
123		token_count = tokens.length;
124
125		/**
126		 * Calculates how close of a match the
127		 * given value is against a search token.
128		 *
129		 * @param {mixed} value
130		 * @param {object} token
131		 * @return {number}
132		 */
133		var scoreValue = function(value, token) {
134			var score, pos;
135
136			if (!value) return 0;
137			value = String(value || '');
138			pos = value.search(token.regex);
139			if (pos === -1) return 0;
140			score = token.string.length / value.length;
141			if (pos === 0) score += 0.5;
142			return score;
143		};
144
145		/**
146		 * Calculates the score of an object
147		 * against the search query.
148		 *
149		 * @param {object} token
150		 * @param {object} data
151		 * @return {number}
152		 */
153		var scoreObject = (function() {
154			var field_count = fields.length;
155			if (!field_count) {
156				return function() { return 0; };
157			}
158			if (field_count === 1) {
159				return function(token, data) {
160					return scoreValue(data[fields[0]], token);
161				};
162			}
163			return function(token, data) {
164				for (var i = 0, sum = 0; i < field_count; i++) {
165					sum += scoreValue(data[fields[i]], token);
166				}
167				return sum / field_count;
168			};
169		})();
170
171		if (!token_count) {
172			return function() { return 0; };
173		}
174		if (token_count === 1) {
175			return function(data) {
176				return scoreObject(tokens[0], data);
177			};
178		}
179
180		if (search.options.conjunction === 'and') {
181			return function(data) {
182				var score;
183				for (var i = 0, sum = 0; i < token_count; i++) {
184					score = scoreObject(tokens[i], data);
185					if (score <= 0) return 0;
186					sum += score;
187				}
188				return sum / token_count;
189			};
190		} else {
191			return function(data) {
192				for (var i = 0, sum = 0; i < token_count; i++) {
193					sum += scoreObject(tokens[i], data);
194				}
195				return sum / token_count;
196			};
197		}
198	};
199
200	/**
201	 * Returns a function that can be used to compare two
202	 * results, for sorting purposes. If no sorting should
203	 * be performed, `null` will be returned.
204	 *
205	 * @param {string|object} search
206	 * @param {object} options
207	 * @return function(a,b)
208	 */
209	Sifter.prototype.getSortFunction = function(search, options) {
210		var i, n, self, field, fields, fields_count, multiplier, multipliers, get_field, implicit_score, sort;
211
212		self   = this;
213		search = self.prepareSearch(search, options);
214		sort   = (!search.query && options.sort_empty) || options.sort;
215
216		/**
217		 * Fetches the specified sort field value
218		 * from a search result item.
219		 *
220		 * @param  {string} name
221		 * @param  {object} result
222		 * @return {mixed}
223		 */
224		get_field = function(name, result) {
225			if (name === '$score') return result.score;
226			return self.items[result.id][name];
227		};
228
229		// parse options
230		fields = [];
231		if (sort) {
232			for (i = 0, n = sort.length; i < n; i++) {
233				if (search.query || sort[i].field !== '$score') {
234					fields.push(sort[i]);
235				}
236			}
237		}
238
239		// the "$score" field is implied to be the primary
240		// sort field, unless it's manually specified
241		if (search.query) {
242			implicit_score = true;
243			for (i = 0, n = fields.length; i < n; i++) {
244				if (fields[i].field === '$score') {
245					implicit_score = false;
246					break;
247				}
248			}
249			if (implicit_score) {
250				fields.unshift({field: '$score', direction: 'desc'});
251			}
252		} else {
253			for (i = 0, n = fields.length; i < n; i++) {
254				if (fields[i].field === '$score') {
255					fields.splice(i, 1);
256					break;
257				}
258			}
259		}
260
261		multipliers = [];
262		for (i = 0, n = fields.length; i < n; i++) {
263			multipliers.push(fields[i].direction === 'desc' ? -1 : 1);
264		}
265
266		// build function
267		fields_count = fields.length;
268		if (!fields_count) {
269			return null;
270		} else if (fields_count === 1) {
271			field = fields[0].field;
272			multiplier = multipliers[0];
273			return function(a, b) {
274				return multiplier * cmp(
275					get_field(field, a),
276					get_field(field, b)
277				);
278			};
279		} else {
280			return function(a, b) {
281				var i, result, a_value, b_value, field;
282				for (i = 0; i < fields_count; i++) {
283					field = fields[i].field;
284					result = multipliers[i] * cmp(
285						get_field(field, a),
286						get_field(field, b)
287					);
288					if (result) return result;
289				}
290				return 0;
291			};
292		}
293	};
294
295	/**
296	 * Parses a search query and returns an object
297	 * with tokens and fields ready to be populated
298	 * with results.
299	 *
300	 * @param {string} query
301	 * @param {object} options
302	 * @returns {object}
303	 */
304	Sifter.prototype.prepareSearch = function(query, options) {
305		if (typeof query === 'object') return query;
306
307		options = extend({}, options);
308
309		var option_fields     = options.fields;
310		var option_sort       = options.sort;
311		var option_sort_empty = options.sort_empty;
312
313		if (option_fields && !is_array(option_fields)) options.fields = [option_fields];
314		if (option_sort && !is_array(option_sort)) options.sort = [option_sort];
315		if (option_sort_empty && !is_array(option_sort_empty)) options.sort_empty = [option_sort_empty];
316
317		return {
318			options : options,
319			query   : String(query || '').toLowerCase(),
320			tokens  : this.tokenize(query),
321			total   : 0,
322			items   : []
323		};
324	};
325
326	/**
327	 * Searches through all items and returns a sorted array of matches.
328	 *
329	 * The `options` parameter can contain:
330	 *
331	 *   - fields {string|array}
332	 *   - sort {array}
333	 *   - score {function}
334	 *   - filter {bool}
335	 *   - limit {integer}
336	 *
337	 * Returns an object containing:
338	 *
339	 *   - options {object}
340	 *   - query {string}
341	 *   - tokens {array}
342	 *   - total {int}
343	 *   - items {array}
344	 *
345	 * @param {string} query
346	 * @param {object} options
347	 * @returns {object}
348	 */
349	Sifter.prototype.search = function(query, options) {
350		var self = this, value, score, search, calculateScore;
351		var fn_sort;
352		var fn_score;
353
354		search  = this.prepareSearch(query, options);
355		options = search.options;
356		query   = search.query;
357
358		// generate result scoring function
359		fn_score = options.score || self.getScoreFunction(search);
360
361		// perform search and sort
362		if (query.length) {
363			self.iterator(self.items, function(item, id) {
364				score = fn_score(item);
365				if (options.filter === false || score > 0) {
366					search.items.push({'score': score, 'id': id});
367				}
368			});
369		} else {
370			self.iterator(self.items, function(item, id) {
371				search.items.push({'score': 1, 'id': id});
372			});
373		}
374
375		fn_sort = self.getSortFunction(search, options);
376		if (fn_sort) search.items.sort(fn_sort);
377
378		// apply limits
379		search.total = search.items.length;
380		if (typeof options.limit === 'number') {
381			search.items = search.items.slice(0, options.limit);
382		}
383
384		return search;
385	};
386
387	// utilities
388	// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
389
390	var cmp = function(a, b) {
391		if (typeof a === 'number' && typeof b === 'number') {
392			return a > b ? 1 : (a < b ? -1 : 0);
393		}
394		a = asciifold(String(a || ''));
395		b = asciifold(String(b || ''));
396		if (a > b) return 1;
397		if (b > a) return -1;
398		return 0;
399	};
400
401	var extend = function(a, b) {
402		var i, n, k, object;
403		for (i = 1, n = arguments.length; i < n; i++) {
404			object = arguments[i];
405			if (!object) continue;
406			for (k in object) {
407				if (object.hasOwnProperty(k)) {
408					a[k] = object[k];
409				}
410			}
411		}
412		return a;
413	};
414
415	var trim = function(str) {
416		return (str + '').replace(/^\s+|\s+$|/g, '');
417	};
418
419	var escape_regex = function(str) {
420		return (str + '').replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1');
421	};
422
423	var is_array = Array.isArray || ($ && $.isArray) || function(object) {
424		return Object.prototype.toString.call(object) === '[object Array]';
425	};
426
427	var DIACRITICS = {
428		'a': '[aÀÁÂÃÄÅàáâãäåĀāąĄ]',
429		'c': '[cÇçćĆčČ]',
430		'd': '[dđĐďĎ]',
431		'e': '[eÈÉÊËèéêëěĚĒēęĘ]',
432		'i': '[iÌÍÎÏìíîïĪī]',
433		'l': '[lłŁ]',
434		'n': '[nÑñňŇńŃ]',
435		'o': '[oÒÓÔÕÕÖØòóôõöøŌō]',
436		'r': '[rřŘ]',
437		's': '[sŠšśŚ]',
438		't': '[tťŤ]',
439		'u': '[uÙÚÛÜùúûüůŮŪū]',
440		'y': '[yŸÿýÝ]',
441		'z': '[zŽžżŻźŹ]'
442	};
443
444	var asciifold = (function() {
445		var i, n, k, chunk;
446		var foreignletters = '';
447		var lookup = {};
448		for (k in DIACRITICS) {
449			if (DIACRITICS.hasOwnProperty(k)) {
450				chunk = DIACRITICS[k].substring(2, DIACRITICS[k].length - 1);
451				foreignletters += chunk;
452				for (i = 0, n = chunk.length; i < n; i++) {
453					lookup[chunk.charAt(i)] = k;
454				}
455			}
456		}
457		var regexp = new RegExp('[' +  foreignletters + ']', 'g');
458		return function(str) {
459			return str.replace(regexp, function(foreignletter) {
460				return lookup[foreignletter];
461			}).toLowerCase();
462		};
463	})();
464
465
466	// export
467	// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
468
469	return Sifter;
470}));
471
472
473
474/**
475 * microplugin.js
476 * Copyright (c) 2013 Brian Reavis & contributors
477 *
478 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
479 * file except in compliance with the License. You may obtain a copy of the License at:
480 * http://www.apache.org/licenses/LICENSE-2.0
481 *
482 * Unless required by applicable law or agreed to in writing, software distributed under
483 * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
484 * ANY KIND, either express or implied. See the License for the specific language
485 * governing permissions and limitations under the License.
486 *
487 * @author Brian Reavis <[email protected]>
488 */
489
490(function(root, factory) {
491	if (typeof define === 'function' && define.amd) {
492		define('microplugin', factory);
493	} else if (typeof exports === 'object') {
494		module.exports = factory();
495	} else {
496		root.MicroPlugin = factory();
497	}
498}(this, function() {
499	var MicroPlugin = {};
500
501	MicroPlugin.mixin = function(Interface) {
502		Interface.plugins = {};
503
504		/**
505		 * Initializes the listed plugins (with options).
506		 * Acceptable formats:
507		 *
508		 * List (without options):
509		 *   ['a', 'b', 'c']
510		 *
511		 * List (with options):
512		 *   [{'name': 'a', options: {}}, {'name': 'b', options: {}}]
513		 *
514		 * Hash (with options):
515		 *   {'a': { ... }, 'b': { ... }, 'c': { ... }}
516		 *
517		 * @param {mixed} plugins
518		 */
519		Interface.prototype.initializePlugins = function(plugins) {
520			var i, n, key;
521			var self  = this;
522			var queue = [];
523
524			self.plugins = {
525				names     : [],
526				settings  : {},
527				requested : {},
528				loaded    : {}
529			};
530
531			if (utils.isArray(plugins)) {
532				for (i = 0, n = plugins.length; i < n; i++) {
533					if (typeof plugins[i] === 'string') {
534						queue.push(plugins[i]);
535					} else {
536						self.plugins.settings[plugins[i].name] = plugins[i].options;
537						queue.push(plugins[i].name);
538					}
539				}
540			} else if (plugins) {
541				for (key in plugins) {
542					if (plugins.hasOwnProperty(key)) {
543						self.plugins.settings[key] = plugins[key];
544						queue.push(key);
545					}
546				}
547			}
548
549			while (queue.length) {
550				self.require(queue.shift());
551			}
552		};
553
554		Interface.prototype.loadPlugin = function(name) {
555			var self    = this;
556			var plugins = self.plugins;
557			var plugin  = Interface.plugins[name];
558
559			if (!Interface.plugins.hasOwnProperty(name)) {
560				throw new Error('Unable to find "' +  name + '" plugin');
561			}
562
563			plugins.requested[name] = true;
564			plugins.loaded[name] = plugin.fn.apply(self, [self.plugins.settings[name] || {}]);
565			plugins.names.push(name);
566		};
567
568		/**
569		 * Initializes a plugin.
570		 *
571		 * @param {string} name
572		 */
573		Interface.prototype.require = function(name) {
574			var self = this;
575			var plugins = self.plugins;
576
577			if (!self.plugins.loaded.hasOwnProperty(name)) {
578				if (plugins.requested[name]) {
579					throw new Error('Plugin has circular dependency ("' + name + '")');
580				}
581				self.loadPlugin(name);
582			}
583
584			return plugins.loaded[name];
585		};
586
587		/**
588		 * Registers a plugin.
589		 *
590		 * @param {string} name
591		 * @param {function} fn
592		 */
593		Interface.define = function(name, fn) {
594			Interface.plugins[name] = {
595				'name' : name,
596				'fn'   : fn
597			};
598		};
599	};
600
601	var utils = {
602		isArray: Array.isArray || function(vArg) {
603			return Object.prototype.toString.call(vArg) === '[object Array]';
604		}
605	};
606
607	return MicroPlugin;
608}));
609
610/**
611 * selectize.js (v0.12.1)
612 * Copyright (c) 2013–2015 Brian Reavis & contributors
613 *
614 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
615 * file except in compliance with the License. You may obtain a copy of the License at:
616 * http://www.apache.org/licenses/LICENSE-2.0
617 *
618 * Unless required by applicable law or agreed to in writing, software distributed under
619 * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
620 * ANY KIND, either express or implied. See the License for the specific language
621 * governing permissions and limitations under the License.
622 *
623 * @author Brian Reavis <[email protected]>
624 */
625
626/*jshint curly:false */
627/*jshint browser:true */
628
629(function(root, factory) {
630	if (typeof define === 'function' && define.amd) {
631		define('selectize', ['jquery','sifter','microplugin'], factory);
632	} else if (typeof exports === 'object') {
633		module.exports = factory(require('jquery'), require('sifter'), require('microplugin'));
634	} else {
635		root.Selectize = factory(root.jQuery, root.Sifter, root.MicroPlugin);
636	}
637}(this, function($, Sifter, MicroPlugin) {
638	'use strict';
639
640	var highlight = function($element, pattern) {
641		if (typeof pattern === 'string' && !pattern.length) return;
642		var regex = (typeof pattern === 'string') ? new RegExp(pattern, 'i') : pattern;
643	
644		var highlight = function(node) {
645			var skip = 0;
646			if (node.nodeType === 3) {
647				var pos = node.data.search(regex);
648				if (pos >= 0 && node.data.length > 0) {
649					var match = node.data.match(regex);
650					var spannode = document.createElement('span');
651					spannode.className = 'highlight';
652					var middlebit = node.splitText(pos);
653					var endbit = middlebit.splitText(match[0].length);
654					var middleclone = middlebit.cloneNode(true);
655					spannode.appendChild(middleclone);
656					middlebit.parentNode.replaceChild(spannode, middlebit);
657					skip = 1;
658				}
659			} else if (node.nodeType === 1 && node.childNodes && !/(script|style)/i.test(node.tagName)) {
660				for (var i = 0; i < node.childNodes.length; ++i) {
661					i += highlight(node.childNodes[i]);
662				}
663			}
664			return skip;
665		};
666	
667		return $element.each(function() {
668			highlight(this);
669		});
670	};
671	
672	var MicroEvent = function() {};
673	MicroEvent.prototype = {
674		on: function(event, fct){
675			this._events = this._events || {};
676			this._events[event] = this._events[event] || [];
677			this._events[event].push(fct);
678		},
679		off: function(event, fct){
680			var n = arguments.length;
681			if (n === 0) return delete this._events;
682			if (n === 1) return delete this._events[event];
683	
684			this._events = this._events || {};
685			if (event in this._events === false) return;
686			this._events[event].splice(this._events[event].indexOf(fct), 1);
687		},
688		trigger: function(event /* , args... */){
689			this._events = this._events || {};
690			if (event in this._events === false) return;
691			for (var i = 0; i < this._events[event].length; i++){
692				this._events[event][i].apply(this, Array.prototype.slice.call(arguments, 1));
693			}
694		}
695	};
696	
697	/**
698	 * Mixin will delegate all MicroEvent.js function in the destination object.
699	 *
700	 * - MicroEvent.mixin(Foobar) will make Foobar able to use MicroEvent
701	 *
702	 * @param {object} the object which will support MicroEvent
703	 */
704	MicroEvent.mixin = function(destObject){
705		var props = ['on', 'off', 'trigger'];
706		for (var i = 0; i < props.length; i++){
707			destObject.prototype[props[i]] = MicroEvent.prototype[props[i]];
708		}
709	};
710	
711	var IS_MAC        = /Mac/.test(navigator.userAgent);
712	
713	var KEY_A         = 65;
714	var KEY_COMMA     = 188;
715	var KEY_RETURN    = 13;
716	var KEY_ESC       = 27;
717	var KEY_LEFT      = 37;
718	var KEY_UP        = 38;
719	var KEY_P         = 80;
720	var KEY_RIGHT     = 39;
721	var KEY_DOWN      = 40;
722	var KEY_N         = 78;
723	var KEY_BACKSPACE = 8;
724	var KEY_DELETE    = 46;
725	var KEY_SHIFT     = 16;
726	var KEY_CMD       = IS_MAC ? 91 : 17;
727	var KEY_CTRL      = IS_MAC ? 18 : 17;
728	var KEY_TAB       = 9;
729	
730	var TAG_SELECT    = 1;
731	var TAG_INPUT     = 2;
732	
733	// for now, android support in general is too spotty to support validity
734	var SUPPORTS_VALIDITY_API = !/android/i.test(window.navigator.userAgent) && !!document.createElement('form').validity;
735	
736	var isset = function(object) {
737		return typeof object !== 'undefined';
738	};
739	
740	/**
741	 * Converts a scalar to its best string representation
742	 * for hash keys and HTML attribute values.
743	 *
744	 * Transformations:
745	 *   'str'     -> 'str'
746	 *   null      -> ''
747	 *   undefined -> ''
748	 *   true      -> '1'
749	 *   false     -> '0'
750	 *   0         -> '0'
751	 *   1         -> '1'
752	 *
753	 * @param {string} value
754	 * @returns {string|null}
755	 */
756	var hash_key = function(value) {
757		if (typeof value === 'undefined' || value === null) return null;
758		if (typeof value === 'boolean') return value ? '1' : '0';
759		return value + '';
760	};
761	
762	/**
763	 * Escapes a string for use within HTML.
764	 *
765	 * @param {string} str
766	 * @returns {string}
767	 */
768	var escape_html = function(str) {
769		return (str + '')
770			.replace(/&/g, '&amp;')
771			.replace(/</g, '&lt;')
772			.replace(/>/g, '&gt;')
773			.replace(/"/g, '&quot;');
774	};
775	
776	/**
777	 * Escapes "$" characters in replacement strings.
778	 *
779	 * @param {string} str
780	 * @returns {string}
781	 */
782	var escape_replace = function(str) {
783		return (str + '').replace(/\$/g, '$$$$');
784	};
785	
786	var hook = {};
787	
788	/**
789	 * Wraps `method` on `self` so that `fn`
790	 * is invoked before the original method.
791	 *
792	 * @param {object} self
793	 * @param {string} method
794	 * @param {function} fn
795	 */
796	hook.before = function(self, method, fn) {
797		var original = self[method];
798		self[method] = function() {
799			fn.apply(self, arguments);
800			return original.apply(self, arguments);
801		};
802	};
803	
804	/**
805	 * Wraps `method` on `self` so that `fn`
806	 * is invoked after the original method.
807	 *
808	 * @param {object} self
809	 * @param {string} method
810	 * @param {function} fn
811	 */
812	hook.after = function(self, method, fn) {
813		var original = self[method];
814		self[method] = function() {
815			var result = original.apply(self, arguments);
816			fn.apply(self, arguments);
817			return result;
818		};
819	};
820	
821	/**
822	 * Wraps `fn` so that it can only be invoked once.
823	 *
824	 * @param {function} fn
825	 * @returns {function}
826	 */
827	var once = function(fn) {
828		var called = false;
829		return function() {
830			if (called) return;
831			called = true;
832			fn.apply(this, arguments);
833		};
834	};
835	
836	/**
837	 * Wraps `fn` so that it can only be called once
838	 * every `delay` milliseconds (invoked on the falling edge).
839	 *
840	 * @param {function} fn
841	 * @param {int} delay
842	 * @returns {function}
843	 */
844	var debounce = function(fn, delay) {
845		var timeout;
846		return function() {
847			var self = this;
848			var args = arguments;
849			window.clearTimeout(timeout);
850			timeout = window.setTimeout(function() {
851				fn.apply(self, args);
852			}, delay);
853		};
854	};
855	
856	/**
857	 * Debounce all fired events types listed in `types`
858	 * while executing the provided `fn`.
859	 *
860	 * @param {object} self
861	 * @param {array} types
862	 * @param {function} fn
863	 */
864	var debounce_events = function(self, types, fn) {
865		var type;
866		var trigger = self.trigger;
867		var event_args = {};
868	
869		// override trigger method
870		self.trigger = function() {
871			var type = arguments[0];
872			if (types.indexOf(type) !== -1) {
873				event_args[type] = arguments;
874			} else {
875				return trigger.apply(self, arguments);
876			}
877		};
878	
879		// invoke provided function
880		fn.apply(self, []);
881		self.trigger = trigger;
882	
883		// trigger queued events
884		for (type in event_args) {
885			if (event_args.hasOwnProperty(type)) {
886				trigger.apply(self, event_args[type]);
887			}
888		}
889	};
890	
891	/**
892	 * A workaround for http://bugs.jquery.com/ticket/6696
893	 *
894	 * @param {object} $parent - Parent element to listen on.
895	 * @param {string} event - Event name.
896	 * @param {string} selector - Descendant selector to filter by.
897	 * @param {function} fn - Event handler.
898	 */
899	var watchChildEvent = function($parent, event, selector, fn) {
900		$parent.on(event, selector, function(e) {
901			var child = e.target;
902			while (child && child.parentNode !== $parent[0]) {
903				child = child.parentNode;
904			}
905			e.currentTarget = child;
906			return fn.apply(this, [e]);
907		});
908	};
909	
910	/**
911	 * Determines the current selection within a text input control.
912	 * Returns an object containing:
913	 *   - start
914	 *   - length
915	 *
916	 * @param {object} input
917	 * @returns {object}
918	 */
919	var getSelection = function(input) {
920		var result = {};
921		if ('selectionStart' in input) {
922			result.start = input.selectionStart;
923			result.length = input.selectionEnd - result.start;
924		} else if (document.selection) {
925			input.focus();
926			var sel = document.selection.createRange();
927			var selLen = document.selection.createRange().text.length;
928			sel.moveStart('character', -input.value.length);
929			result.start = sel.text.length - selLen;
930			result.length = selLen;
931		}
932		return result;
933	};
934	
935	/**
936	 * Copies CSS properties from one element to another.
937	 *
938	 * @param {object} $from
939	 * @param {object} $to
940	 * @param {array} properties
941	 */
942	var transferStyles = function($from, $to, properties) {
943		var i, n, styles = {};
944		if (properties) {
945			for (i = 0, n = properties.length; i < n; i++) {
946				styles[properties[i]] = $from.css(properties[i]);
947			}
948		} else {
949			styles = $from.css();
950		}
951		$to.css(styles);
952	};
953	
954	/**
955	 * Measures the width of a string within a
956	 * parent element (in pixels).
957	 *
958	 * @param {string} str
959	 * @param {object} $parent
960	 * @returns {int}
961	 */
962	var measureString = function(str, $parent) {
963		if (!str) {
964			return 0;
965		}
966	
967		var $test = $('<test>').css({
968			position: 'absolute',
969			top: -99999,
970			left: -99999,
971			width: 'auto',
972			padding: 0,
973			whiteSpace: 'pre'
974		}).text(str).appendTo('body');
975	
976		transferStyles($parent, $test, [
977			'letterSpacing',
978			'fontSize',
979			'fontFamily',
980			'fontWeight',
981			'textTransform'
982		]);
983	
984		var width = $test.width();
985		$test.remove();
986	
987		return width;
988	};
989	
990	/**
991	 * Sets up an input to grow horizontally as the user
992	 * types. If the value is changed manually, you can
993	 * trigger the "update" handler to resize:
994	 *
995	 * $input.trigger('update');
996	 *
997	 * @param {object} $input
998	 */
999	var autoGrow = function($input) {
1000		var currentWidth = null;
1001	
1002		var update = function(e, options) {
1003			var value, keyCode, printable, placeholder, width;
1004			var shift, character, selection;
1005			e = e || window.event || {};
1006			options = options || {};
1007	
1008			if (e.metaKey || e.altKey) return;
1009			if (!options.force && $input.data('grow') === false) return;
1010	
1011			value = $input.val();
1012			if (e.type && e.type.toLowerCase() === 'keydown') {
1013				keyCode = e.keyCode;
1014				printable = (
1015					(keyCode >= 97 && keyCode <= 122) || // a-z
1016					(keyCode >= 65 && keyCode <= 90)  || // A-Z
1017					(keyCode >= 48 && keyCode <= 57)  || // 0-9
1018					keyCode === 32 // space
1019				);
1020	
1021				if (keyCode === KEY_DELETE || keyCode === KEY_BACKSPACE) {
1022					selection = getSelection($input[0]);
1023					if (selection.length) {
1024						value = value.substring(0, selection.start) + value.substring(selection.start + selection.length);
1025					} else if (keyCode === KEY_BACKSPACE && selection.start) {
1026						value = value.substring(0, selection.start - 1) + value.substring(selection.start + 1);
1027					} else if (keyCode === KEY_DELETE && typeof selection.start !== 'undefined') {
1028						value = value.substring(0, selection.start) + value.substring(selection.start + 1);
1029					}
1030				} else if (printable) {
1031					shift = e.shiftKey;
1032					character = String.fromCharCode(e.keyCode);
1033					if (shift) character = character.toUpperCase();
1034					else character = character.toLowerCase();
1035					value += character;
1036				}
1037			}
1038	
1039			placeholder = $input.attr('placeholder');
1040			if (!value && placeholder) {
1041				value = placeholder;
1042			}
1043	
1044			width = measureString(value, $input) + 4;
1045			if (width !== currentWidth) {
1046				currentWidth = width;
1047				$input.width(width);
1048				$input.triggerHandler('resize');
1049			}
1050		};
1051	
1052		$input.on('keydown keyup update blur', update);
1053		update();
1054	};
1055	
1056	var Selectize = function($input, settings) {
1057		var key, i, n, dir, input, self = this;
1058		input = $input[0];
1059		input.selectize = self;
1060	
1061		// detect rtl environment
1062		var computedStyle = window.getComputedStyle && window.getComputedStyle(input, null);
1063		dir = computedStyle ? computedStyle.getPropertyValue('direction') : input.currentStyle && input.currentStyle.direction;
1064		dir = dir || $input.parents('[dir]:first').attr('dir') || '';
1065	
1066		// setup default state
1067		$.extend(self, {
1068			order            : 0,
1069			settings         : settings,
1070			$input           : $input,
1071			tabIndex         : $input.attr('tabindex') || '',
1072			tagType          : input.tagName.toLowerCase() === 'select' ? TAG_SELECT : TAG_INPUT,
1073			rtl              : /rtl/i.test(dir),
1074	
1075			eventNS          : '.selectize' + (++Selectize.count),
1076			highlightedValue : null,
1077			isOpen           : false,
1078			isDisabled       : false,
1079			isRequired       : $input.is('[required]'),
1080			isInvalid        : false,
1081			isLocked         : false,
1082			isFocused        : false,
1083			isInputHidden    : false,
1084			isSetup          : false,
1085			isShiftDown      : false,
1086			isCmdDown        : false,
1087			isCtrlDown       : false,
1088			ignoreFocus      : false,
1089			ignoreBlur       : false,
1090			ignoreHover      : false,
1091			hasOptions       : false,
1092			currentResults   : null,
1093			lastValue        : '',
1094			caretPos         : 0,
1095			loading          : 0,
1096			loadedSearches   : {},
1097	
1098			$activeOption    : null,
1099			$activeItems     : [],
1100	
1101			optgroups        : {},
1102			options          : {},
1103			userOptions      : {},
1104			items            : [],
1105			renderCache      : {},
1106			onSearchChange   : settings.loadThrottle === null ? self.onSearchChange : debounce(self.onSearchChange, settings.loadThrottle)
1107		});
1108	
1109		// search system
1110		self.sifter = new Sifter(this.options, {diacritics: settings.diacritics});
1111	
1112		// build options table
1113		if (self.settings.options) {
1114			for (i = 0, n = self.settings.options.length; i < n; i++) {
1115				self.registerOption(self.settings.options[i]);
1116			}
1117			delete self.settings.options;
1118		}
1119	
1120		// build optgroup table
1121		if (self.settings.optgroups) {
1122			for (i = 0, n = self.settings.optgroups.length; i < n; i++) {
1123				self.registerOptionGroup(self.settings.optgroups[i]);
1124			}
1125			delete self.settings.optgroups;
1126		}
1127	
1128		// option-dependent defaults
1129		self.settings.mode = self.settings.mode || (self.settings.maxItems === 1 ? 'single' : 'multi');
1130		if (typeof self.settings.hideSelected !== 'boolean') {
1131			self.settings.hideSelected = self.settings.mode === 'multi';
1132		}
1133	
1134		self.initializePlugins(self.settings.plugins);
1135		self.setupCallbacks();
1136		self.setupTemplates();
1137		self.setup();
1138	};
1139	
1140	// mixins
1141	// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1142	
1143	MicroEvent.mixin(Selectize);
1144	MicroPlugin.mixin(Selectize);
1145	
1146	// methods
1147	// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1148	
1149	$.extend(Selectize.prototype, {
1150	
1151		/**
1152		 * Creates all elements and sets up event bindings.
1153		 */
1154		setup: function() {
1155			var self      = this;
1156			var settings  = self.settings;
1157			var eventNS   = self.eventNS;
1158			var $window   = $(window);
1159			var $document = $(document);
1160			var $input    = self.$input;
1161	
1162			var $wrapper;
1163			var $control;
1164			var $control_input;
1165			var $dropdown;
1166			var $dropdown_content;
1167			var $dropdown_parent;
1168			var inputMode;
1169			var timeout_blur;
1170			var timeout_focus;
1171			var classes;
1172			var classes_plugins;
1173	
1174			inputMode         = self.settings.mode;
1175			classes           = $input.attr('class') || '';
1176	
1177			$wrapper          = $('<div>
vendor: 23,415 bytes, lines 1177-2002
1177').addClass(settings.wrapperClass).addClass(classes).addClass(inputMode);
1178			$control          = $('<div>').addClass(settings.inputClass).addClass('items').appendTo($wrapper);
1179			$control_input    = $('<input type="text" autocomplete="off" />').appendTo($control).attr('tabindex', $input.is(':disabled') ? '-1' : self.tabIndex);
1180			$dropdown_parent  = $(settings.dropdownParent || $wrapper);
1181			$dropdown         = $('<div>').addClass(settings.dropdownClass).addClass(inputMode).hide().appendTo($dropdown_parent);
1182			$dropdown_content = $('<div>').addClass(settings.dropdownContentClass).appendTo($dropdown);
1183	
1184			if(self.settings.copyClassesToDropdown) {
1185				$dropdown.addClass(classes);
1186			}
1187	
1188			$wrapper.css({
1189				width: $input[0].style.width
1190			});
1191	
1192			if (self.plugins.names.length) {
1193				classes_plugins = 'plugin-' + self.plugins.names.join(' plugin-');
1194				$wrapper.addClass(classes_plugins);
1195				$dropdown.addClass(classes_plugins);
1196			}
1197	
1198			if ((settings.maxItems === null || settings.maxItems > 1) && self.tagType === TAG_SELECT) {
1199				$input.attr('multiple', 'multiple');
1200			}
1201	
1202			if (self.settings.placeholder) {
1203				$control_input.attr('placeholder', settings.placeholder);
1204			}
1205	
1206			// if splitOn was not passed in, construct it from the delimiter to allow pasting universally
1207			if (!self.settings.splitOn && self.settings.delimiter) {
1208				var delimiterEscaped = self.settings.delimiter.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
1209				self.settings.splitOn = new RegExp('\\s*' + delimiterEscaped + '+\\s*');
1210			}
1211	
1212			if ($input.attr('autocorrect')) {
1213				$control_input.attr('autocorrect', $input.attr('autocorrect'));
1214			}
1215	
1216			if ($input.attr('autocapitalize')) {
1217				$control_input.attr('autocapitalize', $input.attr('autocapitalize'));
1218			}
1219	
1220			self.$wrapper          = $wrapper;
1221			self.$control          = $control;
1222			self.$control_input    = $control_input;
1223			self.$dropdown         = $dropdown;
1224			self.$dropdown_content = $dropdown_content;
1225	
1226			$dropdown.on('mouseenter', '[data-selectable]', function() { return self.onOptionHover.apply(self, arguments); });
1227			$dropdown.on('mousedown click', '[data-selectable]', function() { return self.onOptionSelect.apply(self, arguments); });
1228			watchChildEvent($control, 'mousedown', '*:not(input)', function() { return self.onItemSelect.apply(self, arguments); });
1229			autoGrow($control_input);
1230	
1231			$control.on({
1232				mousedown : function() { return self.onMouseDown.apply(self, arguments); },
1233				click     : function() { return self.onClick.apply(self, arguments); }
1234			});
1235	
1236			$control_input.on({
1237				mousedown : function(e) { e.stopPropagation(); },
1238				keydown   : function() { return self.onKeyDown.apply(self, arguments); },
1239				keyup     : function() { return self.onKeyUp.apply(self, arguments); },
1240				keypress  : function() { return self.onKeyPress.apply(self, arguments); },
1241				resize    : function() { self.positionDropdown.apply(self, []); },
1242				blur      : function() { return self.onBlur.apply(self, arguments); },
1243				focus     : function() { self.ignoreBlur = false; return self.onFocus.apply(self, arguments); },
1244				paste     : function() { return self.onPaste.apply(self, arguments); }
1245			});
1246	
1247			$document.on('keydown' + eventNS, function(e) {
1248				self.isCmdDown = e[IS_MAC ? 'metaKey' : 'ctrlKey'];
1249				self.isCtrlDown = e[IS_MAC ? 'altKey' : 'ctrlKey'];
1250				self.isShiftDown = e.shiftKey;
1251			});
1252	
1253			$document.on('keyup' + eventNS, function(e) {
1254				if (e.keyCode === KEY_CTRL) self.isCtrlDown = false;
1255				if (e.keyCode === KEY_SHIFT) self.isShiftDown = false;
1256				if (e.keyCode === KEY_CMD) self.isCmdDown = false;
1257			});
1258	
1259			$document.on('mousedown' + eventNS, function(e) {
1260				if (self.isFocused) {
1261					// prevent events on the dropdown scrollbar from causing the control to blur
1262					if (e.target === self.$dropdown[0] || e.target.parentNode === self.$dropdown[0]) {
1263						return false;
1264					}
1265					// blur on click outside
1266					if (!self.$control.has(e.target).length && e.target !== self.$control[0]) {
1267						self.blur(e.target);
1268					}
1269				}
1270			});
1271	
1272			$window.on(['scroll' + eventNS, 'resize' + eventNS].join(' '), function() {
1273				if (self.isOpen) {
1274					self.positionDropdown.apply(self, arguments);
1275				}
1276			});
1277			$window.on('mousemove' + eventNS, function() {
1278				self.ignoreHover = false;
1279			});
1280	
1281			// store original children and tab index so that they can be
1282			// restored when the destroy() method is called.
1283			this.revertSettings = {
1284				$children : $input.children().detach(),
1285				tabindex  : $input.attr('tabindex')
1286			};
1287	
1288			$input.attr('tabindex', -1).hide().after(self.$wrapper);
1289	
1290			if ($.isArray(settings.items)) {
1291				self.setValue(settings.items);
1292				delete settings.items;
1293			}
1294	
1295			// feature detect for the validation API
1296			if (SUPPORTS_VALIDITY_API) {
1297				$input.on('invalid' + eventNS, function(e) {
1298					e.preventDefault();
1299					self.isInvalid = true;
1300					self.refreshState();
1301				});
1302			}
1303	
1304			self.updateOriginalInput();
1305			self.refreshItems();
1306			self.refreshState();
1307			self.updatePlaceholder();
1308			self.isSetup = true;
1309	
1310			if ($input.is(':disabled')) {
1311				self.disable();
1312			}
1313	
1314			self.on('change', this.onChange);
1315	
1316			$input.data('selectize', self);
1317			$input.addClass('selectized');
1318			self.trigger('initialize');
1319	
1320			// preload options
1321			if (settings.preload === true) {
1322				self.onSearchChange('');
1323			}
1324	
1325		},
1326	
1327		/**
1328		 * Sets up default rendering functions.
1329		 */
1330		setupTemplates: function() {
1331			var self = this;
1332			var field_label = self.settings.labelField;
1333			var field_optgroup = self.settings.optgroupLabelField;
1334	
1335			var templates = {
1336				'optgroup': function(data) {
1337					return '<div class="optgroup">' + data.html + '</div>';
1338				},
1339				'optgroup_header': function(data, escape) {
1340					return '<div class="optgroup-header">' + escape(data[field_optgroup]) + '</div>';
1341				},
1342				'option': function(data, escape) {
1343					return '<div class="option">' + escape(data[field_label]) + '</div>';
1344				},
1345				'item': function(data, escape) {
1346					return '<div class="item">' + escape(data[field_label]) + '</div>';
1347				},
1348				'option_create': function(data, escape) {
1349					return '<div class="create">Add <strong>' + escape(data.input) + '</strong>&hellip;</div>';
1350				}
1351			};
1352	
1353			self.settings.render = $.extend({}, templates, self.settings.render);
1354		},
1355	
1356		/**
1357		 * Maps fired events to callbacks provided
1358		 * in the settings used when creating the control.
1359		 */
1360		setupCallbacks: function() {
1361			var key, fn, callbacks = {
1362				'initialize'      : 'onInitialize',
1363				'change'          : 'onChange',
1364				'item_add'        : 'onItemAdd',
1365				'item_remove'     : 'onItemRemove',
1366				'clear'           : 'onClear',
1367				'option_add'      : 'onOptionAdd',
1368				'option_remove'   : 'onOptionRemove',
1369				'option_clear'    : 'onOptionClear',
1370				'optgroup_add'    : 'onOptionGroupAdd',
1371				'optgroup_remove' : 'onOptionGroupRemove',
1372				'optgroup_clear'  : 'onOptionGroupClear',
1373				'dropdown_open'   : 'onDropdownOpen',
1374				'dropdown_close'  : 'onDropdownClose',
1375				'type'            : 'onType',
1376				'load'            : 'onLoad',
1377				'focus'           : 'onFocus',
1378				'blur'            : 'onBlur'
1379			};
1380	
1381			for (key in callbacks) {
1382				if (callbacks.hasOwnProperty(key)) {
1383					fn = this.settings[callbacks[key]];
1384					if (fn) this.on(key, fn);
1385				}
1386			}
1387		},
1388	
1389		/**
1390		 * Triggered when the main control element
1391		 * has a click event.
1392		 *
1393		 * @param {object} e
1394		 * @return {boolean}
1395		 */
1396		onClick: function(e) {
1397			var self = this;
1398	
1399			// necessary for mobile webkit devices (manual focus triggering
1400			// is ignored unless invoked within a click event)
1401			if (!self.isFocused) {
1402				self.focus();
1403				e.preventDefault();
1404			}
1405		},
1406	
1407		/**
1408		 * Triggered when the main control element
1409		 * has a mouse down event.
1410		 *
1411		 * @param {object} e
1412		 * @return {boolean}
1413		 */
1414		onMouseDown: function(e) {
1415			var self = this;
1416			var defaultPrevented = e.isDefaultPrevented();
1417			var $target = $(e.target);
1418	
1419			if (self.isFocused) {
1420				// retain focus by preventing native handling. if the
1421				// event target is the input it should not be modified.
1422				// otherwise, text selection within the input won't work.
1423				if (e.target !== self.$control_input[0]) {
1424					if (self.settings.mode === 'single') {
1425						// toggle dropdown
1426						self.isOpen ? self.close() : self.open();
1427					} else if (!defaultPrevented) {
1428						self.setActiveItem(null);
1429					}
1430					return false;
1431				}
1432			} else {
1433				// give control focus
1434				if (!defaultPrevented) {
1435					window.setTimeout(function() {
1436						self.focus();
1437					}, 0);
1438				}
1439			}
1440		},
1441	
1442		/**
1443		 * Triggered when the value of the control has been changed.
1444		 * This should propagate the event to the original DOM
1445		 * input / select element.
1446		 */
1447		onChange: function() {
1448			this.$input.trigger('change');
1449		},
1450	
1451		/**
1452		 * Triggered on <input> paste.
1453		 *
1454		 * @param {object} e
1455		 * @returns {boolean}
1456		 */
1457		onPaste: function(e) {
1458			var self = this;
1459			if (self.isFull() || self.isInputHidden || self.isLocked) {
1460				e.preventDefault();
1461			} else {
1462				// If a regex or string is included, this will split the pasted
1463				// input and create Items for each separate value
1464				if (self.settings.splitOn) {
1465					setTimeout(function() {
1466						var splitInput = $.trim(self.$control_input.val() || '').split(self.settings.splitOn);
1467						for (var i = 0, n = splitInput.length; i < n; i++) {
1468							self.createItem(splitInput[i]);
1469						}
1470					}, 0);
1471				}
1472			}
1473		},
1474	
1475		/**
1476		 * Triggered on <input> keypress.
1477		 *
1478		 * @param {object} e
1479		 * @returns {boolean}
1480		 */
1481		onKeyPress: function(e) {
1482			if (this.isLocked) return e && e.preventDefault();
1483			var character = String.fromCharCode(e.keyCode || e.which);
1484			if (this.settings.create && this.settings.mode === 'multi' && character === this.settings.delimiter) {
1485				this.createItem();
1486				e.preventDefault();
1487				return false;
1488			}
1489		},
1490	
1491		/**
1492		 * Triggered on <input> keydown.
1493		 *
1494		 * @param {object} e
1495		 * @returns {boolean}
1496		 */
1497		onKeyDown: function(e) {
1498			var isInput = e.target === this.$control_input[0];
1499			var self = this;
1500	
1501			if (self.isLocked) {
1502				if (e.keyCode !== KEY_TAB) {
1503					e.preventDefault();
1504				}
1505				return;
1506			}
1507	
1508			switch (e.keyCode) {
1509				case KEY_A:
1510					if (self.isCmdDown) {
1511						self.selectAll();
1512						return;
1513					}
1514					break;
1515				case KEY_ESC:
1516					if (self.isOpen) {
1517						e.preventDefault();
1518						e.stopPropagation();
1519						self.close();
1520					}
1521					return;
1522				case KEY_N:
1523					if (!e.ctrlKey || e.altKey) break;
1524				case KEY_DOWN:
1525					if (!self.isOpen && self.hasOptions) {
1526						self.open();
1527					} else if (self.$activeOption) {
1528						self.ignoreHover = true;
1529						var $next = self.getAdjacentOption(self.$activeOption, 1);
1530						if ($next.length) self.setActiveOption($next, true, true);
1531					}
1532					e.preventDefault();
1533					return;
1534				case KEY_P:
1535					if (!e.ctrlKey || e.altKey) break;
1536				case KEY_UP:
1537					if (self.$activeOption) {
1538						self.ignoreHover = true;
1539						var $prev = self.getAdjacentOption(self.$activeOption, -1);
1540						if ($prev.length) self.setActiveOption($prev, true, true);
1541					}
1542					e.preventDefault();
1543					return;
1544				case KEY_RETURN:
1545					if (self.isOpen && self.$activeOption) {
1546						self.onOptionSelect({currentTarget: self.$activeOption});
1547						e.preventDefault();
1548					}
1549					return;
1550				case KEY_LEFT:
1551					self.advanceSelection(-1, e);
1552					return;
1553				case KEY_RIGHT:
1554					self.advanceSelection(1, e);
1555					return;
1556				case KEY_TAB:
1557					if (self.settings.selectOnTab && self.isOpen && self.$activeOption) {
1558						self.onOptionSelect({currentTarget: self.$activeOption});
1559	
1560						// Default behaviour is to jump to the next field, we only want this
1561						// if the current field doesn't accept any more entries
1562						if (!self.isFull()) {
1563							e.preventDefault();
1564						}
1565					}
1566					if (self.settings.create && self.createItem()) {
1567						e.preventDefault();
1568					}
1569					return;
1570				case KEY_BACKSPACE:
1571				case KEY_DELETE:
1572					self.deleteSelection(e);
1573					return;
1574			}
1575	
1576			if ((self.isFull() || self.isInputHidden) && !(IS_MAC ? e.metaKey : e.ctrlKey)) {
1577				e.preventDefault();
1578				return;
1579			}
1580		},
1581	
1582		/**
1583		 * Triggered on <input> keyup.
1584		 *
1585		 * @param {object} e
1586		 * @returns {boolean}
1587		 */
1588		onKeyUp: function(e) {
1589			var self = this;
1590	
1591			if (self.isLocked) return e && e.preventDefault();
1592			var value = self.$control_input.val() || '';
1593			if (self.lastValue !== value) {
1594				self.lastValue = value;
1595				self.onSearchChange(value);
1596				self.refreshOptions();
1597				self.trigger('type', value);
1598			}
1599		},
1600	
1601		/**
1602		 * Invokes the user-provide option provider / loader.
1603		 *
1604		 * Note: this function is debounced in the Selectize
1605		 * constructor (by `settings.loadDelay` milliseconds)
1606		 *
1607		 * @param {string} value
1608		 */
1609		onSearchChange: function(value) {
1610			var self = this;
1611			var fn = self.settings.load;
1612			if (!fn) return;
1613			if (self.loadedSearches.hasOwnProperty(value)) return;
1614			self.loadedSearches[value] = true;
1615			self.load(function(callback) {
1616				fn.apply(self, [value, callback]);
1617			});
1618		},
1619	
1620		/**
1621		 * Triggered on <input> focus.
1622		 *
1623		 * @param {object} e (optional)
1624		 * @returns {boolean}
1625		 */
1626		onFocus: function(e) {
1627			var self = this;
1628			var wasFocused = self.isFocused;
1629	
1630			if (self.isDisabled) {
1631				self.blur();
1632				e && e.preventDefault();
1633				return false;
1634			}
1635	
1636			if (self.ignoreFocus) return;
1637			self.isFocused = true;
1638			if (self.settings.preload === 'focus') self.onSearchChange('');
1639	
1640			if (!wasFocused) self.trigger('focus');
1641	
1642			if (!self.$activeItems.length) {
1643				self.showInput();
1644				self.setActiveItem(null);
1645				self.refreshOptions(!!self.settings.openOnFocus);
1646			}
1647	
1648			self.refreshState();
1649		},
1650	
1651		/**
1652		 * Triggered on <input> blur.
1653		 *
1654		 * @param {object} e
1655		 * @param {Element} dest
1656		 */
1657		onBlur: function(e, dest) {
1658			var self = this;
1659			if (!self.isFocused) return;
1660			self.isFocused = false;
1661	
1662			if (self.ignoreFocus) {
1663				return;
1664			} else if (!self.ignoreBlur && document.activeElement === self.$dropdown_content[0]) {
1665				// necessary to prevent IE closing the dropdown when the scrollbar is clicked
1666				self.ignoreBlur = true;
1667				self.onFocus(e);
1668				return;
1669			}
1670	
1671			var deactivate = function() {
1672				self.close();
1673				self.setTextboxValue('');
1674				self.setActiveItem(null);
1675				self.setActiveOption(null);
1676				self.setCaret(self.items.length);
1677				self.refreshState();
1678	
1679				// IE11 bug: element still marked as active
1680				(dest || document.body).focus();
1681	
1682				self.ignoreFocus = false;
1683				self.trigger('blur');
1684			};
1685	
1686			self.ignoreFocus = true;
1687			if (self.settings.create && self.settings.createOnBlur) {
1688				self.createItem(null, false, deactivate);
1689			} else {
1690				deactivate();
1691			}
1692		},
1693	
1694		/**
1695		 * Triggered when the user rolls over
1696		 * an option in the autocomplete dropdown menu.
1697		 *
1698		 * @param {object} e
1699		 * @returns {boolean}
1700		 */
1701		onOptionHover: function(e) {
1702			if (this.ignoreHover) return;
1703			this.setActiveOption(e.currentTarget, false);
1704		},
1705	
1706		/**
1707		 * Triggered when the user clicks on an option
1708		 * in the autocomplete dropdown menu.
1709		 *
1710		 * @param {object} e
1711		 * @returns {boolean}
1712		 */
1713		onOptionSelect: function(e) {
1714			var value, $target, $option, self = this;
1715	
1716			if (e.preventDefault) {
1717				e.preventDefault();
1718				e.stopPropagation();
1719			}
1720	
1721			$target = $(e.currentTarget);
1722			if ($target.hasClass('create')) {
1723				self.createItem(null, function() {
1724					if (self.settings.closeAfterSelect) {
1725						self.close();
1726					}
1727				});
1728			} else {
1729				value = $target.attr('data-value');
1730				if (typeof value !== 'undefined') {
1731					self.lastQuery = null;
1732					self.setTextboxValue('');
1733					self.addItem(value);
1734					if (self.settings.closeAfterSelect) {
1735						self.close();
1736					} else if (!self.settings.hideSelected && e.type && /mouse/.test(e.type)) {
1737						self.setActiveOption(self.getOption(value));
1738					}
1739				}
1740			}
1741		},
1742	
1743		/**
1744		 * Triggered when the user clicks on an item
1745		 * that has been selected.
1746		 *
1747		 * @param {object} e
1748		 * @returns {boolean}
1749		 */
1750		onItemSelect: function(e) {
1751			var self = this;
1752	
1753			if (self.isLocked) return;
1754			if (self.settings.mode === 'multi') {
1755				e.preventDefault();
1756				self.setActiveItem(e.currentTarget, e);
1757			}
1758		},
1759	
1760		/**
1761		 * Invokes the provided method that provides
1762		 * results to a callback---which are then added
1763		 * as options to the control.
1764		 *
1765		 * @param {function} fn
1766		 */
1767		load: function(fn) {
1768			var self = this;
1769			var $wrapper = self.$wrapper.addClass(self.settings.loadingClass);
1770	
1771			self.loading++;
1772			fn.apply(self, [function(results) {
1773				self.loading = Math.max(self.loading - 1, 0);
1774				if (results && results.length) {
1775					self.addOption(results);
1776					self.refreshOptions(self.isFocused && !self.isInputHidden);
1777				}
1778				if (!self.loading) {
1779					$wrapper.removeClass(self.settings.loadingClass);
1780				}
1781				self.trigger('load', results);
1782			}]);
1783		},
1784	
1785		/**
1786		 * Sets the input field of the control to the specified value.
1787		 *
1788		 * @param {string} value
1789		 */
1790		setTextboxValue: function(value) {
1791			var $input = this.$control_input;
1792			var changed = $input.val() !== value;
1793			if (changed) {
1794				$input.val(value).triggerHandler('update');
1795				this.lastValue = value;
1796			}
1797		},
1798	
1799		/**
1800		 * Returns the value of the control. If multiple items
1801		 * can be selected (e.g. <select multiple>), this returns
1802		 * an array. If only one item can be selected, this
1803		 * returns a string.
1804		 *
1805		 * @returns {mixed}
1806		 */
1807		getValue: function() {
1808			if (this.tagType === TAG_SELECT && this.$input.attr('multiple')) {
1809				return this.items;
1810			} else {
1811				return this.items.join(this.settings.delimiter);
1812			}
1813		},
1814	
1815		/**
1816		 * Resets the selected items to the given value.
1817		 *
1818		 * @param {mixed} value
1819		 */
1820		setValue: function(value, silent) {
1821			var events = silent ? [] : ['change'];
1822	
1823			debounce_events(this, events, function() {
1824				this.clear(silent);
1825				this.addItems(value, silent);
1826			});
1827		},
1828	
1829		/**
1830		 * Sets the selected item.
1831		 *
1832		 * @param {object} $item
1833		 * @param {object} e (optional)
1834		 */
1835		setActiveItem: function($item, e) {
1836			var self = this;
1837			var eventName;
1838			var i, idx, begin, end, item, swap;
1839			var $last;
1840	
1841			if (self.settings.mode === 'single') return;
1842			$item = $($item);
1843	
1844			// clear the active selection
1845			if (!$item.length) {
1846				$(self.$activeItems).removeClass('active');
1847				self.$activeItems = [];
1848				if (self.isFocused) {
1849					self.showInput();
1850				}
1851				return;
1852			}
1853	
1854			// modify selection
1855			eventName = e && e.type.toLowerCase();
1856	
1857			if (eventName === 'mousedown' && self.isShiftDown && self.$activeItems.length) {
1858				$last = self.$control.children('.active:last');
1859				begin = Array.prototype.indexOf.apply(self.$control[0].childNodes, [$last[0]]);
1860				end   = Array.prototype.indexOf.apply(self.$control[0].childNodes, [$item[0]]);
1861				if (begin > end) {
1862					swap  = begin;
1863					begin = end;
1864					end   = swap;
1865				}
1866				for (i = begin; i <= end; i++) {
1867					item = self.$control[0].childNodes[i];
1868					if (self.$activeItems.indexOf(item) === -1) {
1869						$(item).addClass('active');
1870						self.$activeItems.push(item);
1871					}
1872				}
1873				e.preventDefault();
1874			} else if ((eventName === 'mousedown' && self.isCtrlDown) || (eventName === 'keydown' && this.isShiftDown)) {
1875				if ($item.hasClass('active')) {
1876					idx = self.$activeItems.indexOf($item[0]);
1877					self.$activeItems.splice(idx, 1);
1878					$item.removeClass('active');
1879				} else {
1880					self.$activeItems.push($item.addClass('active')[0]);
1881				}
1882			} else {
1883				$(self.$activeItems).removeClass('active');
1884				self.$activeItems = [$item.addClass('active')[0]];
1885			}
1886	
1887			// ensure control has focus
1888			self.hideInput();
1889			if (!this.isFocused) {
1890				self.focus();
1891			}
1892		},
1893	
1894		/**
1895		 * Sets the selected item in the dropdown menu
1896		 * of available options.
1897		 *
1898		 * @param {object} $object
1899		 * @param {boolean} scroll
1900		 * @param {boolean} animate
1901		 */
1902		setActiveOption: function($option, scroll, animate) {
1903			var height_menu, height_item, y;
1904			var scroll_top, scroll_bottom;
1905			var self = this;
1906	
1907			if (self.$activeOption) self.$activeOption.removeClass('active');
1908			self.$activeOption = null;
1909	
1910			$option = $($option);
1911			if (!$option.length) return;
1912	
1913			self.$activeOption = $option.addClass('active');
1914	
1915			if (scroll || !isset(scroll)) {
1916	
1917				height_menu   = self.$dropdown_content.height();
1918				height_item   = self.$activeOption.outerHeight(true);
1919				scroll        = self.$dropdown_content.scrollTop() || 0;
1920				y             = self.$activeOption.offset().top - self.$dropdown_content.offset().top + scroll;
1921				scroll_top    = y;
1922				scroll_bottom = y - height_menu + height_item;
1923	
1924				if (y + height_item > height_menu + scroll) {
1925					self.$dropdown_content.stop().animate({scrollTop: scroll_bottom}, animate ? self.settings.scrollDuration : 0);
1926				} else if (y < scroll) {
1927					self.$dropdown_content.stop().animate({scrollTop: scroll_top}, animate ? self.settings.scrollDuration : 0);
1928				}
1929	
1930			}
1931		},
1932	
1933		/**
1934		 * Selects all items (CTRL + A).
1935		 */
1936		selectAll: function() {
1937			var self = this;
1938			if (self.settings.mode === 'single') return;
1939	
1940			self.$activeItems = Array.prototype.slice.apply(self.$control.children(':not(input)').addClass('active'));
1941			if (self.$activeItems.length) {
1942				self.hideInput();
1943				self.close();
1944			}
1945			self.focus();
1946		},
1947	
1948		/**
1949		 * Hides the input element out of view, while
1950		 * retaining its focus.
1951		 */
1952		hideInput: function() {
1953			var self = this;
1954	
1955			self.setTextboxValue('');
1956			self.$control_input.css({opacity: 0, position: 'absolute', left: self.rtl ? 10000 : -10000});
1957			self.isInputHidden = true;
1958		},
1959	
1960		/**
1961		 * Restores input visibility.
1962		 */
1963		showInput: function() {
1964			this.$control_input.css({opacity: 1, position: 'relative', left: 0});
1965			this.isInputHidden = false;
1966		},
1967	
1968		/**
1969		 * Gives the control focus.
1970		 */
1971		focus: function() {
1972			var self = this;
1973			if (self.isDisabled) return;
1974	
1975			self.ignoreFocus = true;
1976			self.$control_input[0].focus();
1977			window.setTimeout(function() {
1978				self.ignoreFocus = false;
1979				self.onFocus();
1980			}, 0);
1981		},
1982	
1983		/**
1984		 * Forces the control out of focus.
1985		 *
1986		 * @param {Element} dest
1987		 */
1988		blur: function(dest) {
1989			this.$control_input[0].blur();
1990			this.onBlur(null, dest);
1991		},
1992	
1993		/**
1994		 * Returns a function that scores an object
1995		 * to show how good of a match it is to the
1996		 * provided query.
1997		 *
1998		 * @param {string} query
1999		 * @param {object} options
2000		 * @return {function}
2001		 */
2002		getScoreFunction: function(query) {
2003			return this.sifter.getScoreFunction(query, this.getSearchOptions());
2004		},
2005	
2006		/**
2007		 * Returns search options for sifter (the system
2008		 * for scoring and sorting results).
2009		 *
2010		 * @see https://github.com/brianreavis/sifter.js
2011		 * @return {object}
2012		 */
2013		getSearchOptions: function() {
2014			var settings = this.settings;
2015			var sort = settings.sortField;
2016			if (typeof sort === 'string') {
2017				sort = [{field: sort}];
2018			}
2019	
2020			return {
2021				fields      : settings.searchField,
2022				conjunction : settings.searchConjunction,
2023				sort        : sort
2024			};
2025		},
2026	
2027		/**
2028		 * Searches through available options and returns
2029		 * a sorted array of matches.
2030		 *
2031		 * Returns an object containing:
2032		 *
2033		 *   - query {string}
2034		 *   - tokens {array}
2035		 *   - total {int}
2036		 *   - items {array}
2037		 *
2038		 * @param {string} query
2039		 * @returns {object}
2040		 */
2041		search: function(query) {
2042			var i, value, score, result, calculateScore;
2043			var self     = this;
2044			var settings = self.settings;
2045			var options  = this.getSearchOptions();
2046	
2047			// validate user-provided result scoring function
2048			if (settings.score) {
2049				calculateScore = self.settings.score.apply(this, [query]);
2050				if (typeof calculateScore !== 'function') {
2051					throw new Error('Selectize "score" setting must be a function that returns a function');
2052				}
2053			}
2054	
2055			// perform search
2056			if (query !== self.lastQuery) {
2057				self.lastQuery = query;
2058				result = self.sifter.search(query, $.extend(options, {score: calculateScore}));
2059				self.currentResults = result;
2060			} else {
2061				result = $.extend(true, {}, self.currentResults);
2062			}
2063	
2064			// filter out selected items
2065			if (settings.hideSelected) {
2066				for (i = result.items.length - 1; i >= 0; i--) {
2067					if (self.items.indexOf(hash_key(result.items[i].id)) !== -1) {
2068						result.items.splice(i, 1);
2069					}
2070				}
2071			}
2072	
2073			return result;
2074		},
2075	
2076		/**
2077		 * Refreshes the list of available options shown
2078		 * in the autocomplete dropdown menu.
2079		 *
2080		 * @param {boolean} triggerDropdown
2081		 */
2082		refreshOptions: function(triggerDropdown) {
2083			var i, j, k, n, groups, groups_order, option, option_html, optgroup, optgroups, html, html_children, has_create_option;
2084			var $active, $active_before, $create;
2085	
2086			if (typeof triggerDropdown === 'undefined') {
2087				triggerDropdown = true;
2088			}
2089	
2090			var self              = this;
2091			var query             = $.trim(self.$control_input.val());
2092			var results           = self.search(query);
2093			var $dropdown_content = self.$dropdown_content;
2094			var active_before     = self.$activeOption && hash_key(self.$activeOption.attr('data-value'));
2095	
2096			// build markup
2097			n = results.items.length;
2098			if (typeof self.settings.maxOptions === 'number') {
2099				n = Math.min(n, self.settings.maxOptions);
2100			}
2101	
2102			// render and group available options individually
2103			groups = {};
2104			groups_order = [];
2105	
2106			for (i = 0; i < n; i++) {
2107				option      = self.options[results.items[i].id];
2108				option_html = self.render('option', option);
2109				optgroup    = option[self.settings.optgroupField] || '';
2110				optgroups   = $.isArray(optgroup) ? optgroup : [optgroup];
2111	
2112				for (j = 0, k = optgroups && optgroups.length; j < k; j++) {
2113					optgroup = optgroups[j];
2114					if (!self.optgroups.hasOwnProperty(optgroup)) {
2115						optgroup = '';
2116					}
2117					if (!groups.hasOwnProperty(optgroup)) {
2118						groups[optgroup] = [];
2119						groups_order.push(optgroup);
2120					}
2121					groups[optgroup].push(option_html);
2122				}
2123			}
2124	
2125			// sort optgroups
2126			if (this.settings.lockOptgroupOrder) {
2127				groups_order.sort(function(a, b) {
2128					var a_order = self.optgroups[a].$order || 0;
2129					var b_order = self.optgroups[b].$order || 0;
2130					return a_order - b_order;
2131				});
2132			}
2133	
2134			// render optgroup headers & join groups
2135			html = [];
2136			for (i = 0, n = groups_order.length; i < n; i++) {
2137				optgroup = groups_order[i];
2138				if (self.optgroups.hasOwnProperty(optgroup) && groups[optgroup].length) {
2139					// render the optgroup header and options within it,
2140					// then pass it to the wrapper template
2141					html_children = self.render('optgroup_header', self.optgroups[optgroup]) || '';
2142					html_children += groups[optgroup].join('');
2143					html.push(self.render('optgroup', $.extend({}, self.optgroups[optgroup], {
2144						html: html_children
2145					}
vendor: 42,639 bytes, lines 2145-3667
2145)));
2146				} else {
2147					html.push(groups[optgroup].join(''));
2148				}
2149			}
2150	
2151			$dropdown_content.html(html.join(''));
2152	
2153			// highlight matching terms inline
2154			if (self.settings.highlight && results.query.length && results.tokens.length) {
2155				for (i = 0, n = results.tokens.length; i < n; i++) {
2156					highlight($dropdown_content, results.tokens[i].regex);
2157				}
2158			}
2159	
2160			// add "selected" class to selected options
2161			if (!self.settings.hideSelected) {
2162				for (i = 0, n = self.items.length; i < n; i++) {
2163					self.getOption(self.items[i]).addClass('selected');
2164				}
2165			}
2166	
2167			// add create option
2168			has_create_option = self.canCreate(query);
2169			if (has_create_option) {
2170				$dropdown_content.prepend(self.render('option_create', {input: query}));
2171				$create = $($dropdown_content[0].childNodes[0]);
2172			}
2173	
2174			// activate
2175			self.hasOptions = results.items.length > 0 || has_create_option;
2176			if (self.hasOptions) {
2177				if (results.items.length > 0) {
2178					$active_before = active_before && self.getOption(active_before);
2179					if ($active_before && $active_before.length) {
2180						$active = $active_before;
2181					} else if (self.settings.mode === 'single' && self.items.length) {
2182						$active = self.getOption(self.items[0]);
2183					}
2184					if (!$active || !$active.length) {
2185						if ($create && !self.settings.addPrecedence) {
2186							$active = self.getAdjacentOption($create, 1);
2187						} else {
2188							$active = $dropdown_content.find('[data-selectable]:first');
2189						}
2190					}
2191				} else {
2192					$active = $create;
2193				}
2194				self.setActiveOption($active);
2195				if (triggerDropdown && !self.isOpen) { self.open(); }
2196			} else {
2197				self.setActiveOption(null);
2198				if (triggerDropdown && self.isOpen) { self.close(); }
2199			}
2200		},
2201	
2202		/**
2203		 * Adds an available option. If it already exists,
2204		 * nothing will happen. Note: this does not refresh
2205		 * the options list dropdown (use `refreshOptions`
2206		 * for that).
2207		 *
2208		 * Usage:
2209		 *
2210		 *   this.addOption(data)
2211		 *
2212		 * @param {object|array} data
2213		 */
2214		addOption: function(data) {
2215			var i, n, value, self = this;
2216	
2217			if ($.isArray(data)) {
2218				for (i = 0, n = data.length; i < n; i++) {
2219					self.addOption(data[i]);
2220				}
2221				return;
2222			}
2223	
2224			if (value = self.registerOption(data)) {
2225				self.userOptions[value] = true;
2226				self.lastQuery = null;
2227				self.trigger('option_add', value, data);
2228			}
2229		},
2230	
2231		/**
2232		 * Registers an option to the pool of options.
2233		 *
2234		 * @param {object} data
2235		 * @return {boolean|string}
2236		 */
2237		registerOption: function(data) {
2238			var key = hash_key(data[this.settings.valueField]);
2239			if (!key || this.options.hasOwnProperty(key)) return false;
2240			data.$order = data.$order || ++this.order;
2241			this.options[key] = data;
2242			return key;
2243		},
2244	
2245		/**
2246		 * Registers an option group to the pool of option groups.
2247		 *
2248		 * @param {object} data
2249		 * @return {boolean|string}
2250		 */
2251		registerOptionGroup: function(data) {
2252			var key = hash_key(data[this.settings.optgroupValueField]);
2253			if (!key) return false;
2254	
2255			data.$order = data.$order || ++this.order;
2256			this.optgroups[key] = data;
2257			return key;
2258		},
2259	
2260		/**
2261		 * Registers a new optgroup for options
2262		 * to be bucketed into.
2263		 *
2264		 * @param {string} id
2265		 * @param {object} data
2266		 */
2267		addOptionGroup: function(id, data) {
2268			data[this.settings.optgroupValueField] = id;
2269			if (id = this.registerOptionGroup(data)) {
2270				this.trigger('optgroup_add', id, data);
2271			}
2272		},
2273	
2274		/**
2275		 * Removes an existing option group.
2276		 *
2277		 * @param {string} id
2278		 */
2279		removeOptionGroup: function(id) {
2280			if (this.optgroups.hasOwnProperty(id)) {
2281				delete this.optgroups[id];
2282				this.renderCache = {};
2283				this.trigger('optgroup_remove', id);
2284			}
2285		},
2286	
2287		/**
2288		 * Clears all existing option groups.
2289		 */
2290		clearOptionGroups: function() {
2291			this.optgroups = {};
2292			this.renderCache = {};
2293			this.trigger('optgroup_clear');
2294		},
2295	
2296		/**
2297		 * Updates an option available for selection. If
2298		 * it is visible in the selected items or options
2299		 * dropdown, it will be re-rendered automatically.
2300		 *
2301		 * @param {string} value
2302		 * @param {object} data
2303		 */
2304		updateOption: function(value, data) {
2305			var self = this;
2306			var $item, $item_new;
2307			var value_new, index_item, cache_items, cache_options, order_old;
2308	
2309			value     = hash_key(value);
2310			value_new = hash_key(data[self.settings.valueField]);
2311	
2312			// sanity checks
2313			if (value === null) return;
2314			if (!self.options.hasOwnProperty(value)) return;
2315			if (typeof value_new !== 'string') throw new Error('Value must be set in option data');
2316	
2317			order_old = self.options[value].$order;
2318	
2319			// update references
2320			if (value_new !== value) {
2321				delete self.options[value];
2322				index_item = self.items.indexOf(value);
2323				if (index_item !== -1) {
2324					self.items.splice(index_item, 1, value_new);
2325				}
2326			}
2327			data.$order = data.$order || order_old;
2328			self.options[value_new] = data;
2329	
2330			// invalidate render cache
2331			cache_items = self.renderCache['item'];
2332			cache_options = self.renderCache['option'];
2333	
2334			if (cache_items) {
2335				delete cache_items[value];
2336				delete cache_items[value_new];
2337			}
2338			if (cache_options) {
2339				delete cache_options[value];
2340				delete cache_options[value_new];
2341			}
2342	
2343			// update the item if it's selected
2344			if (self.items.indexOf(value_new) !== -1) {
2345				$item = self.getItem(value);
2346				$item_new = $(self.render('item', data));
2347				if ($item.hasClass('active')) $item_new.addClass('active');
2348				$item.replaceWith($item_new);
2349			}
2350	
2351			// invalidate last query because we might have updated the sortField
2352			self.lastQuery = null;
2353	
2354			// update dropdown contents
2355			if (self.isOpen) {
2356				self.refreshOptions(false);
2357			}
2358		},
2359	
2360		/**
2361		 * Removes a single option.
2362		 *
2363		 * @param {string} value
2364		 * @param {boolean} silent
2365		 */
2366		removeOption: function(value, silent) {
2367			var self = this;
2368			value = hash_key(value);
2369	
2370			var cache_items = self.renderCache['item'];
2371			var cache_options = self.renderCache['option'];
2372			if (cache_items) delete cache_items[value];
2373			if (cache_options) delete cache_options[value];
2374	
2375			delete self.userOptions[value];
2376			delete self.options[value];
2377			self.lastQuery = null;
2378			self.trigger('option_remove', value);
2379			self.removeItem(value, silent);
2380		},
2381	
2382		/**
2383		 * Clears all options.
2384		 */
2385		clearOptions: function() {
2386			var self = this;
2387	
2388			self.loadedSearches = {};
2389			self.userOptions = {};
2390			self.renderCache = {};
2391			self.options = self.sifter.items = {};
2392			self.lastQuery = null;
2393			self.trigger('option_clear');
2394			self.clear();
2395		},
2396	
2397		/**
2398		 * Returns the jQuery element of the option
2399		 * matching the given value.
2400		 *
2401		 * @param {string} value
2402		 * @returns {object}
2403		 */
2404		getOption: function(value) {
2405			return this.getElementWithValue(value, this.$dropdown_content.find('[data-selectable]'));
2406		},
2407	
2408		/**
2409		 * Returns the jQuery element of the next or
2410		 * previous selectable option.
2411		 *
2412		 * @param {object} $option
2413		 * @param {int} direction  can be 1 for next or -1 for previous
2414		 * @return {object}
2415		 */
2416		getAdjacentOption: function($option, direction) {
2417			var $options = this.$dropdown.find('[data-selectable]');
2418			var index    = $options.index($option) + direction;
2419	
2420			return index >= 0 && index < $options.length ? $options.eq(index) : $();
2421		},
2422	
2423		/**
2424		 * Finds the first element with a "data-value" attribute
2425		 * that matches the given value.
2426		 *
2427		 * @param {mixed} value
2428		 * @param {object} $els
2429		 * @return {object}
2430		 */
2431		getElementWithValue: function(value, $els) {
2432			value = hash_key(value);
2433	
2434			if (typeof value !== 'undefined' && value !== null) {
2435				for (var i = 0, n = $els.length; i < n; i++) {
2436					if ($els[i].getAttribute('data-value') === value) {
2437						return $($els[i]);
2438					}
2439				}
2440			}
2441	
2442			return $();
2443		},
2444	
2445		/**
2446		 * Returns the jQuery element of the item
2447		 * matching the given value.
2448		 *
2449		 * @param {string} value
2450		 * @returns {object}
2451		 */
2452		getItem: function(value) {
2453			return this.getElementWithValue(value, this.$control.children());
2454		},
2455	
2456		/**
2457		 * "Selects" multiple items at once. Adds them to the list
2458		 * at the current caret position.
2459		 *
2460		 * @param {string} value
2461		 * @param {boolean} silent
2462		 */
2463		addItems: function(values, silent) {
2464			var items = $.isArray(values) ? values : [values];
2465			for (var i = 0, n = items.length; i < n; i++) {
2466				this.isPending = (i < n - 1);
2467				this.addItem(items[i], silent);
2468			}
2469		},
2470	
2471		/**
2472		 * "Selects" an item. Adds it to the list
2473		 * at the current caret position.
2474		 *
2475		 * @param {string} value
2476		 * @param {boolean} silent
2477		 */
2478		addItem: function(value, silent) {
2479			var events = silent ? [] : ['change'];
2480	
2481			debounce_events(this, events, function() {
2482				var $item, $option, $options;
2483				var self = this;
2484				var inputMode = self.settings.mode;
2485				var i, active, value_next, wasFull;
2486				value = hash_key(value);
2487	
2488				if (self.items.indexOf(value) !== -1) {
2489					if (inputMode === 'single') self.close();
2490					return;
2491				}
2492	
2493				if (!self.options.hasOwnProperty(value)) return;
2494				if (inputMode === 'single') self.clear(silent);
2495				if (inputMode === 'multi' && self.isFull()) return;
2496	
2497				$item = $(self.render('item', self.options[value]));
2498				wasFull = self.isFull();
2499				self.items.splice(self.caretPos, 0, value);
2500				self.insertAtCaret($item);
2501				if (!self.isPending || (!wasFull && self.isFull())) {
2502					self.refreshState();
2503				}
2504	
2505				if (self.isSetup) {
2506					$options = self.$dropdown_content.find('[data-selectable]');
2507	
2508					// update menu / remove the option (if this is not one item being added as part of series)
2509					if (!self.isPending) {
2510						$option = self.getOption(value);
2511						value_next = self.getAdjacentOption($option, 1).attr('data-value');
2512						self.refreshOptions(self.isFocused && inputMode !== 'single');
2513						if (value_next) {
2514							self.setActiveOption(self.getOption(value_next));
2515						}
2516					}
2517	
2518					// hide the menu if the maximum number of items have been selected or no options are left
2519					if (!$options.length || self.isFull()) {
2520						self.close();
2521					} else {
2522						self.positionDropdown();
2523					}
2524	
2525					self.updatePlaceholder();
2526					self.trigger('item_add', value, $item);
2527					self.updateOriginalInput({silent: silent});
2528				}
2529			});
2530		},
2531	
2532		/**
2533		 * Removes the selected item matching
2534		 * the provided value.
2535		 *
2536		 * @param {string} value
2537		 */
2538		removeItem: function(value, silent) {
2539			var self = this;
2540			var $item, i, idx;
2541	
2542			$item = (typeof value === 'object') ? value : self.getItem(value);
2543			value = hash_key($item.attr('data-value'));
2544			i = self.items.indexOf(value);
2545	
2546			if (i !== -1) {
2547				$item.remove();
2548				if ($item.hasClass('active')) {
2549					idx = self.$activeItems.indexOf($item[0]);
2550					self.$activeItems.splice(idx, 1);
2551				}
2552	
2553				self.items.splice(i, 1);
2554				self.lastQuery = null;
2555				if (!self.settings.persist && self.userOptions.hasOwnProperty(value)) {
2556					self.removeOption(value, silent);
2557				}
2558	
2559				if (i < self.caretPos) {
2560					self.setCaret(self.caretPos - 1);
2561				}
2562	
2563				self.refreshState();
2564				self.updatePlaceholder();
2565				self.updateOriginalInput({silent: silent});
2566				self.positionDropdown();
2567				self.trigger('item_remove', value, $item);
2568			}
2569		},
2570	
2571		/**
2572		 * Invokes the `create` method provided in the
2573		 * selectize options that should provide the data
2574		 * for the new item, given the user input.
2575		 *
2576		 * Once this completes, it will be added
2577		 * to the item list.
2578		 *
2579		 * @param {string} value
2580		 * @param {boolean} [triggerDropdown]
2581		 * @param {function} [callback]
2582		 * @return {boolean}
2583		 */
2584		createItem: function(input, triggerDropdown) {
2585			var self  = this;
2586			var caret = self.caretPos;
2587			input = input || $.trim(self.$control_input.val() || '');
2588	
2589			var callback = arguments[arguments.length - 1];
2590			if (typeof callback !== 'function') callback = function() {};
2591	
2592			if (typeof triggerDropdown !== 'boolean') {
2593				triggerDropdown = true;
2594			}
2595	
2596			if (!self.canCreate(input)) {
2597				callback();
2598				return false;
2599			}
2600	
2601			self.lock();
2602	
2603			var setup = (typeof self.settings.create === 'function') ? this.settings.create : function(input) {
2604				var data = {};
2605				data[self.settings.labelField] = input;
2606				data[self.settings.valueField] = input;
2607				return data;
2608			};
2609	
2610			var create = once(function(data) {
2611				self.unlock();
2612	
2613				if (!data || typeof data !== 'object') return callback();
2614				var value = hash_key(data[self.settings.valueField]);
2615				if (typeof value !== 'string') return callback();
2616	
2617				self.setTextboxValue('');
2618				self.addOption(data);
2619				self.setCaret(caret);
2620				self.addItem(value);
2621				self.refreshOptions(triggerDropdown && self.settings.mode !== 'single');
2622				callback(data);
2623			});
2624	
2625			var output = setup.apply(this, [input, create]);
2626			if (typeof output !== 'undefined') {
2627				create(output);
2628			}
2629	
2630			return true;
2631		},
2632	
2633		/**
2634		 * Re-renders the selected item lists.
2635		 */
2636		refreshItems: function() {
2637			this.lastQuery = null;
2638	
2639			if (this.isSetup) {
2640				this.addItem(this.items);
2641			}
2642	
2643			this.refreshState();
2644			this.updateOriginalInput();
2645		},
2646	
2647		/**
2648		 * Updates all state-dependent attributes
2649		 * and CSS classes.
2650		 */
2651		refreshState: function() {
2652			var invalid, self = this;
2653			if (self.isRequired) {
2654				if (self.items.length) self.isInvalid = false;
2655				self.$control_input.prop('required', invalid);
2656			}
2657			self.refreshClasses();
2658		},
2659	
2660		/**
2661		 * Updates all state-dependent CSS classes.
2662		 */
2663		refreshClasses: function() {
2664			var self     = this;
2665			var isFull   = self.isFull();
2666			var isLocked = self.isLocked;
2667	
2668			self.$wrapper
2669				.toggleClass('rtl', self.rtl);
2670	
2671			self.$control
2672				.toggleClass('focus', self.isFocused)
2673				.toggleClass('disabled', self.isDisabled)
2674				.toggleClass('required', self.isRequired)
2675				.toggleClass('invalid', self.isInvalid)
2676				.toggleClass('locked', isLocked)
2677				.toggleClass('full', isFull).toggleClass('not-full', !isFull)
2678				.toggleClass('input-active', self.isFocused && !self.isInputHidden)
2679				.toggleClass('dropdown-active', self.isOpen)
2680				.toggleClass('has-options', !$.isEmptyObject(self.options))
2681				.toggleClass('has-items', self.items.length > 0);
2682	
2683			self.$control_input.data('grow', !isFull && !isLocked);
2684		},
2685	
2686		/**
2687		 * Determines whether or not more items can be added
2688		 * to the control without exceeding the user-defined maximum.
2689		 *
2690		 * @returns {boolean}
2691		 */
2692		isFull: function() {
2693			return this.settings.maxItems !== null && this.items.length >= this.settings.maxItems;
2694		},
2695	
2696		/**
2697		 * Refreshes the original <select> or <input>
2698		 * element to reflect the current state.
2699		 */
2700		updateOriginalInput: function(opts) {
2701			var i, n, options, label, self = this;
2702			opts = opts || {};
2703	
2704			if (self.tagType === TAG_SELECT) {
2705				options = [];
2706				for (i = 0, n = self.items.length; i < n; i++) {
2707					label = self.options[self.items[i]][self.settings.labelField] || '';
2708					options.push('<option value="' + escape_html(self.items[i]) + '" selected="selected">' + escape_html(label) + '</option>');
2709				}
2710				if (!options.length && !this.$input.attr('multiple')) {
2711					options.push('<option value="" selected="selected"></option>');
2712				}
2713				self.$input.html(options.join(''));
2714			} else {
2715				self.$input.val(self.getValue());
2716				self.$input.attr('value',self.$input.val());
2717			}
2718	
2719			if (self.isSetup) {
2720				if (!opts.silent) {
2721					self.trigger('change', self.$input.val());
2722				}
2723			}
2724		},
2725	
2726		/**
2727		 * Shows/hide the input placeholder depending
2728		 * on if there items in the list already.
2729		 */
2730		updatePlaceholder: function() {
2731			if (!this.settings.placeholder) return;
2732			var $input = this.$control_input;
2733	
2734			if (this.items.length) {
2735				$input.removeAttr('placeholder');
2736			} else {
2737				$input.attr('placeholder', this.settings.placeholder);
2738			}
2739			$input.triggerHandler('update', {force: true});
2740		},
2741	
2742		/**
2743		 * Shows the autocomplete dropdown containing
2744		 * the available options.
2745		 */
2746		open: function() {
2747			var self = this;
2748	
2749			if (self.isLocked || self.isOpen || (self.settings.mode === 'multi' && self.isFull())) return;
2750			self.focus();
2751			self.isOpen = true;
2752			self.refreshState();
2753			self.$dropdown.css({visibility: 'hidden', display: 'block'});
2754			self.positionDropdown();
2755			self.$dropdown.css({visibility: 'visible'});
2756			self.trigger('dropdown_open', self.$dropdown);
2757		},
2758	
2759		/**
2760		 * Closes the autocomplete dropdown menu.
2761		 */
2762		close: function() {
2763			var self = this;
2764			var trigger = self.isOpen;
2765	
2766			if (self.settings.mode === 'single' && self.items.length) {
2767				self.hideInput();
2768			}
2769	
2770			self.isOpen = false;
2771			self.$dropdown.hide();
2772			self.setActiveOption(null);
2773			self.refreshState();
2774	
2775			if (trigger) self.trigger('dropdown_close', self.$dropdown);
2776		},
2777	
2778		/**
2779		 * Calculates and applies the appropriate
2780		 * position of the dropdown.
2781		 */
2782		positionDropdown: function() {
2783			var $control = this.$control;
2784			var offset = this.settings.dropdownParent === 'body' ? $control.offset() : $control.position();
2785			offset.top += $control.outerHeight(true);
2786	
2787			this.$dropdown.css({
2788				width : $control.outerWidth(),
2789				top   : offset.top,
2790				left  : offset.left
2791			});
2792		},
2793	
2794		/**
2795		 * Resets / clears all selected items
2796		 * from the control.
2797		 *
2798		 * @param {boolean} silent
2799		 */
2800		clear: function(silent) {
2801			var self = this;
2802	
2803			if (!self.items.length) return;
2804			self.$control.children(':not(input)').remove();
2805			self.items = [];
2806			self.lastQuery = null;
2807			self.setCaret(0);
2808			self.setActiveItem(null);
2809			self.updatePlaceholder();
2810			self.updateOriginalInput({silent: silent});
2811			self.refreshState();
2812			self.showInput();
2813			self.trigger('clear');
2814		},
2815	
2816		/**
2817		 * A helper method for inserting an element
2818		 * at the current caret position.
2819		 *
2820		 * @param {object} $el
2821		 */
2822		insertAtCaret: function($el) {
2823			var caret = Math.min(this.caretPos, this.items.length);
2824			if (caret === 0) {
2825				this.$control.prepend($el);
2826			} else {
2827				$(this.$control[0].childNodes[caret]).before($el);
2828			}
2829			this.setCaret(caret + 1);
2830		},
2831	
2832		/**
2833		 * Removes the current selected item(s).
2834		 *
2835		 * @param {object} e (optional)
2836		 * @returns {boolean}
2837		 */
2838		deleteSelection: function(e) {
2839			var i, n, direction, selection, values, caret, option_select, $option_select, $tail;
2840			var self = this;
2841	
2842			direction = (e && e.keyCode === KEY_BACKSPACE) ? -1 : 1;
2843			selection = getSelection(self.$control_input[0]);
2844	
2845			if (self.$activeOption && !self.settings.hideSelected) {
2846				option_select = self.getAdjacentOption(self.$activeOption, -1).attr('data-value');
2847			}
2848	
2849			// determine items that will be removed
2850			values = [];
2851	
2852			if (self.$activeItems.length) {
2853				$tail = self.$control.children('.active:' + (direction > 0 ? 'last' : 'first'));
2854				caret = self.$control.children(':not(input)').index($tail);
2855				if (direction > 0) { caret++; }
2856	
2857				for (i = 0, n = self.$activeItems.length; i < n; i++) {
2858					values.push($(self.$activeItems[i]).attr('data-value'));
2859				}
2860				if (e) {
2861					e.preventDefault();
2862					e.stopPropagation();
2863				}
2864			} else if ((self.isFocused || self.settings.mode === 'single') && self.items.length) {
2865				if (direction < 0 && selection.start === 0 && selection.length === 0) {
2866					values.push(self.items[self.caretPos - 1]);
2867				} else if (direction > 0 && selection.start === self.$control_input.val().length) {
2868					values.push(self.items[self.caretPos]);
2869				}
2870			}
2871	
2872			// allow the callback to abort
2873			if (!values.length || (typeof self.settings.onDelete === 'function' && self.settings.onDelete.apply(self, [values]) === false)) {
2874				return false;
2875			}
2876	
2877			// perform removal
2878			if (typeof caret !== 'undefined') {
2879				self.setCaret(caret);
2880			}
2881			while (values.length) {
2882				self.removeItem(values.pop());
2883			}
2884	
2885			self.showInput();
2886			self.positionDropdown();
2887			self.refreshOptions(true);
2888	
2889			// select previous option
2890			if (option_select) {
2891				$option_select = self.getOption(option_select);
2892				if ($option_select.length) {
2893					self.setActiveOption($option_select);
2894				}
2895			}
2896	
2897			return true;
2898		},
2899	
2900		/**
2901		 * Selects the previous / next item (depending
2902		 * on the `direction` argument).
2903		 *
2904		 * > 0 - right
2905		 * < 0 - left
2906		 *
2907		 * @param {int} direction
2908		 * @param {object} e (optional)
2909		 */
2910		advanceSelection: function(direction, e) {
2911			var tail, selection, idx, valueLength, cursorAtEdge, $tail;
2912			var self = this;
2913	
2914			if (direction === 0) return;
2915			if (self.rtl) direction *= -1;
2916	
2917			tail = direction > 0 ? 'last' : 'first';
2918			selection = getSelection(self.$control_input[0]);
2919	
2920			if (self.isFocused && !self.isInputHidden) {
2921				valueLength = self.$control_input.val().length;
2922				cursorAtEdge = direction < 0
2923					? selection.start === 0 && selection.length === 0
2924					: selection.start === valueLength;
2925	
2926				if (cursorAtEdge && !valueLength) {
2927					self.advanceCaret(direction, e);
2928				}
2929			} else {
2930				$tail = self.$control.children('.active:' + tail);
2931				if ($tail.length) {
2932					idx = self.$control.children(':not(input)').index($tail);
2933					self.setActiveItem(null);
2934					self.setCaret(direction > 0 ? idx + 1 : idx);
2935				}
2936			}
2937		},
2938	
2939		/**
2940		 * Moves the caret left / right.
2941		 *
2942		 * @param {int} direction
2943		 * @param {object} e (optional)
2944		 */
2945		advanceCaret: function(direction, e) {
2946			var self = this, fn, $adj;
2947	
2948			if (direction === 0) return;
2949	
2950			fn = direction > 0 ? 'next' : 'prev';
2951			if (self.isShiftDown) {
2952				$adj = self.$control_input[fn]();
2953				if ($adj.length) {
2954					self.hideInput();
2955					self.setActiveItem($adj);
2956					e && e.preventDefault();
2957				}
2958			} else {
2959				self.setCaret(self.caretPos + direction);
2960			}
2961		},
2962	
2963		/**
2964		 * Moves the caret to the specified index.
2965		 *
2966		 * @param {int} i
2967		 */
2968		setCaret: function(i) {
2969			var self = this;
2970	
2971			if (self.settings.mode === 'single') {
2972				i = self.items.length;
2973			} else {
2974				i = Math.max(0, Math.min(self.items.length, i));
2975			}
2976	
2977			if(!self.isPending) {
2978				// the input must be moved by leaving it in place and moving the
2979				// siblings, due to the fact that focus cannot be restored once lost
2980				// on mobile webkit devices
2981				var j, n, fn, $children, $child;
2982				$children = self.$control.children(':not(input)');
2983				for (j = 0, n = $children.length; j < n; j++) {
2984					$child = $($children[j]).detach();
2985					if (j <  i) {
2986						self.$control_input.before($child);
2987					} else {
2988						self.$control.append($child);
2989					}
2990				}
2991			}
2992	
2993			self.caretPos = i;
2994		},
2995	
2996		/**
2997		 * Disables user input on the control. Used while
2998		 * items are being asynchronously created.
2999		 */
3000		lock: function() {
3001			this.close();
3002			this.isLocked = true;
3003			this.refreshState();
3004		},
3005	
3006		/**
3007		 * Re-enables user input on the control.
3008		 */
3009		unlock: function() {
3010			this.isLocked = false;
3011			this.refreshState();
3012		},
3013	
3014		/**
3015		 * Disables user input on the control completely.
3016		 * While disabled, it cannot receive focus.
3017		 */
3018		disable: function() {
3019			var self = this;
3020			self.$input.prop('disabled', true);
3021			self.$control_input.prop('disabled', true).prop('tabindex', -1);
3022			self.isDisabled = true;
3023			self.lock();
3024		},
3025	
3026		/**
3027		 * Enables the control so that it can respond
3028		 * to focus and user input.
3029		 */
3030		enable: function() {
3031			var self = this;
3032			self.$input.prop('disabled', false);
3033			self.$control_input.prop('disabled', false).prop('tabindex', self.tabIndex);
3034			self.isDisabled = false;
3035			self.unlock();
3036		},
3037	
3038		/**
3039		 * Completely destroys the control and
3040		 * unbinds all event listeners so that it can
3041		 * be garbage collected.
3042		 */
3043		destroy: function() {
3044			var self = this;
3045			var eventNS = self.eventNS;
3046			var revertSettings = self.revertSettings;
3047	
3048			self.trigger('destroy');
3049			self.off();
3050			self.$wrapper.remove();
3051			self.$dropdown.remove();
3052	
3053			self.$input
3054				.html('')
3055				.append(revertSettings.$children)
3056				.removeAttr('tabindex')
3057				.removeClass('selectized')
3058				.attr({tabindex: revertSettings.tabindex})
3059				.show();
3060	
3061			self.$control_input.removeData('grow');
3062			self.$input.removeData('selectize');
3063	
3064			$(window).off(eventNS);
3065			$(document).off(eventNS);
3066			$(document.body).off(eventNS);
3067	
3068			delete self.$input[0].selectize;
3069		},
3070	
3071		/**
3072		 * A helper method for rendering "item" and
3073		 * "option" templates, given the data.
3074		 *
3075		 * @param {string} templateName
3076		 * @param {object} data
3077		 * @returns {string}
3078		 */
3079		render: function(templateName, data) {
3080			var value, id, label;
3081			var html = '';
3082			var cache = false;
3083			var self = this;
3084			var regex_tag = /^[\t \r\n]*<([a-z][a-z0-9\-_]*(?:\:[a-z][a-z0-9\-_]*)?)/i;
3085	
3086			if (templateName === 'option' || templateName === 'item') {
3087				value = hash_key(data[self.settings.valueField]);
3088				cache = !!value;
3089			}
3090	
3091			// pull markup from cache if it exists
3092			if (cache) {
3093				if (!isset(self.renderCache[templateName])) {
3094					self.renderCache[templateName] = {};
3095				}
3096				if (self.renderCache[templateName].hasOwnProperty(value)) {
3097					return self.renderCache[templateName][value];
3098				}
3099			}
3100	
3101			// render markup
3102			html = self.settings.render[templateName].apply(this, [data, escape_html]);
3103	
3104			// add mandatory attributes
3105			if (templateName === 'option' || templateName === 'option_create') {
3106				html = html.replace(regex_tag, '<$1 data-selectable');
3107			}
3108			if (templateName === 'optgroup') {
3109				id = data[self.settings.optgroupValueField] || '';
3110				html = html.replace(regex_tag, '<$1 data-group="' + escape_replace(escape_html(id)) + '"');
3111			}
3112			if (templateName === 'option' || templateName === 'item') {
3113				html = html.replace(regex_tag, '<$1 data-value="' + escape_replace(escape_html(value || '')) + '"');
3114			}
3115	
3116			// update cache
3117			if (cache) {
3118				self.renderCache[templateName][value] = html;
3119			}
3120	
3121			return html;
3122		},
3123	
3124		/**
3125		 * Clears the render cache for a template. If
3126		 * no template is given, clears all render
3127		 * caches.
3128		 *
3129		 * @param {string} templateName
3130		 */
3131		clearCache: function(templateName) {
3132			var self = this;
3133			if (typeof templateName === 'undefined') {
3134				self.renderCache = {};
3135			} else {
3136				delete self.renderCache[templateName];
3137			}
3138		},
3139	
3140		/**
3141		 * Determines whether or not to display the
3142		 * create item prompt, given a user input.
3143		 *
3144		 * @param {string} input
3145		 * @return {boolean}
3146		 */
3147		canCreate: function(input) {
3148			var self = this;
3149			if (!self.settings.create) return false;
3150			var filter = self.settings.createFilter;
3151			return input.length
3152				&& (typeof filter !== 'function' || filter.apply(self, [input]))
3153				&& (typeof filter !== 'string' || new RegExp(filter).test(input))
3154				&& (!(filter instanceof RegExp) || filter.test(input));
3155		}
3156	
3157	});
3158	
3159	
3160	Selectize.count = 0;
3161	Selectize.defaults = {
3162		options: [],
3163		optgroups: [],
3164	
3165		plugins: [],
3166		delimiter: ',',
3167		splitOn: null, // regexp or string for splitting up values from a paste command
3168		persist: true,
3169		diacritics: true,
3170		create: false,
3171		createOnBlur: false,
3172		createFilter: null,
3173		highlight: true,
3174		openOnFocus: true,
3175		maxOptions: 1000,
3176		maxItems: null,
3177		hideSelected: null,
3178		addPrecedence: false,
3179		selectOnTab: false,
3180		preload: false,
3181		allowEmptyOption: false,
3182		closeAfterSelect: false,
3183	
3184		scrollDuration: 60,
3185		loadThrottle: 300,
3186		loadingClass: 'loading',
3187	
3188		dataAttr: 'data-data',
3189		optgroupField: 'optgroup',
3190		valueField: 'value',
3191		labelField: 'text',
3192		optgroupLabelField: 'label',
3193		optgroupValueField: 'value',
3194		lockOptgroupOrder: false,
3195	
3196		sortField: '$order',
3197		searchField: ['text'],
3198		searchConjunction: 'and',
3199	
3200		mode: null,
3201		wrapperClass: 'selectize-control',
3202		inputClass: 'selectize-input',
3203		dropdownClass: 'selectize-dropdown',
3204		dropdownContentClass: 'selectize-dropdown-content',
3205	
3206		dropdownParent: null,
3207	
3208		copyClassesToDropdown: true,
3209	
3210		/*
3211		load                 : null, // function(query, callback) { ... }
3212		score                : null, // function(search) { ... }
3213		onInitialize         : null, // function() { ... }
3214		onChange             : null, // function(value) { ... }
3215		onItemAdd            : null, // function(value, $item) { ... }
3216		onItemRemove         : null, // function(value) { ... }
3217		onClear              : null, // function() { ... }
3218		onOptionAdd          : null, // function(value, data) { ... }
3219		onOptionRemove       : null, // function(value) { ... }
3220		onOptionClear        : null, // function() { ... }
3221		onOptionGroupAdd     : null, // function(id, data) { ... }
3222		onOptionGroupRemove  : null, // function(id) { ... }
3223		onOptionGroupClear   : null, // function() { ... }
3224		onDropdownOpen       : null, // function($dropdown) { ... }
3225		onDropdownClose      : null, // function($dropdown) { ... }
3226		onType               : null, // function(str) { ... }
3227		onDelete             : null, // function(values) { ... }
3228		*/
3229	
3230		render: {
3231			/*
3232			item: null,
3233			optgroup: null,
3234			optgroup_header: null,
3235			option: null,
3236			option_create: null
3237			*/
3238		}
3239	};
3240	
3241	
3242	$.fn.selectize = function(settings_user) {
3243		var defaults             = $.fn.selectize.defaults;
3244		var settings             = $.extend({}, defaults, settings_user);
3245		var attr_data            = settings.dataAttr;
3246		var field_label          = settings.labelField;
3247		var field_value          = settings.valueField;
3248		var field_optgroup       = settings.optgroupField;
3249		var field_optgroup_label = settings.optgroupLabelField;
3250		var field_optgroup_value = settings.optgroupValueField;
3251	
3252		/**
3253		 * Initializes selectize from a <input type="text"> element.
3254		 *
3255		 * @param {object} $input
3256		 * @param {object} settings_element
3257		 */
3258		var init_textbox = function($input, settings_element) {
3259			var i, n, values, option;
3260	
3261			var data_raw = $input.attr(attr_data);
3262	
3263			if (!data_raw) {
3264				var value = $.trim($input.val() || '');
3265				if (!settings.allowEmptyOption && !value.length) return;
3266				values = value.split(settings.delimiter);
3267				for (i = 0, n = values.length; i < n; i++) {
3268					option = {};
3269					option[field_label] = values[i];
3270					option[field_value] = values[i];
3271					settings_element.options.push(option);
3272				}
3273				settings_element.items = values;
3274			} else {
3275				settings_element.options = JSON.parse(data_raw);
3276				for (i = 0, n = settings_element.options.length; i < n; i++) {
3277					settings_element.items.push(settings_element.options[i][field_value]);
3278				}
3279			}
3280		};
3281	
3282		/**
3283		 * Initializes selectize from a <select> element.
3284		 *
3285		 * @param {object} $input
3286		 * @param {object} settings_element
3287		 */
3288		var init_select = function($input, settings_element) {
3289			var i, n, tagName, $children, order = 0;
3290			var options = settings_element.options;
3291			var optionsMap = {};
3292	
3293			var readData = function($el) {
3294				var data = attr_data && $el.attr(attr_data);
3295				if (typeof data === 'string' && data.length) {
3296					return JSON.parse(data);
3297				}
3298				return null;
3299			};
3300	
3301			var addOption = function($option, group) {
3302				$option = $($option);
3303	
3304				var value = hash_key($option.attr('value'));
3305				if (!value && !settings.allowEmptyOption) return;
3306	
3307				// if the option already exists, it's probably been
3308				// duplicated in another optgroup. in this case, push
3309				// the current group to the "optgroup" property on the
3310				// existing option so that it's rendered in both places.
3311				if (optionsMap.hasOwnProperty(value)) {
3312					if (group) {
3313						var arr = optionsMap[value][field_optgroup];
3314						if (!arr) {
3315							optionsMap[value][field_optgroup] = group;
3316						} else if (!$.isArray(arr)) {
3317							optionsMap[value][field_optgroup] = [arr, group];
3318						} else {
3319							arr.push(group);
3320						}
3321					}
3322					return;
3323				}
3324	
3325				var option             = readData($option) || {};
3326				option[field_label]    = option[field_label] || $option.text();
3327				option[field_value]    = option[field_value] || value;
3328				option[field_optgroup] = option[field_optgroup] || group;
3329	
3330				optionsMap[value] = option;
3331				options.push(option);
3332	
3333				if ($option.is(':selected')) {
3334					settings_element.items.push(value);
3335				}
3336			};
3337	
3338			var addGroup = function($optgroup) {
3339				var i, n, id, optgroup, $options;
3340	
3341				$optgroup = $($optgroup);
3342				id = $optgroup.attr('label');
3343	
3344				if (id) {
3345					optgroup = readData($optgroup) || {};
3346					optgroup[field_optgroup_label] = id;
3347					optgroup[field_optgroup_value] = id;
3348					settings_element.optgroups.push(optgroup);
3349				}
3350	
3351				$options = $('option', $optgroup);
3352				for (i = 0, n = $options.length; i < n; i++) {
3353					addOption($options[i], id);
3354				}
3355			};
3356	
3357			settings_element.maxItems = $input.attr('multiple') ? null : 1;
3358	
3359			$children = $input.children();
3360			for (i = 0, n = $children.length; i < n; i++) {
3361				tagName = $children[i].tagName.toLowerCase();
3362				if (tagName === 'optgroup') {
3363					addGroup($children[i]);
3364				} else if (tagName === 'option') {
3365					addOption($children[i]);
3366				}
3367			}
3368		};
3369	
3370		return this.each(function() {
3371			if (this.selectize) return;
3372	
3373			var instance;
3374			var $input = $(this);
3375			var tag_name = this.tagName.toLowerCase();
3376			var placeholder = $input.attr('placeholder') || $input.attr('data-placeholder');
3377			if (!placeholder && !settings.allowEmptyOption) {
3378				placeholder = $input.children('option[value=""]').text();
3379			}
3380	
3381			var settings_element = {
3382				'placeholder' : placeholder,
3383				'options'     : [],
3384				'optgroups'   : [],
3385				'items'       : []
3386			};
3387	
3388			if (tag_name === 'select') {
3389				init_select($input, settings_element);
3390			} else {
3391				init_textbox($input, settings_element);
3392			}
3393	
3394			instance = new Selectize($input, $.extend(true, {}, defaults, settings_element, settings_user));
3395		});
3396	};
3397	
3398	$.fn.selectize.defaults = Selectize.defaults;
3399	$.fn.selectize.support = {
3400		validity: SUPPORTS_VALIDITY_API
3401	};
3402	
3403	
3404	Selectize.define('drag_drop', function(options) {
3405		if (!$.fn.sortable) throw new Error('The "drag_drop" plugin requires jQuery UI "sortable".');
3406		if (this.settings.mode !== 'multi') return;
3407		var self = this;
3408	
3409		self.lock = (function() {
3410			var original = self.lock;
3411			return function() {
3412				var sortable = self.$control.data('sortable');
3413				if (sortable) sortable.disable();
3414				return original.apply(self, arguments);
3415			};
3416		})();
3417	
3418		self.unlock = (function() {
3419			var original = self.unlock;
3420			return function() {
3421				var sortable = self.$control.data('sortable');
3422				if (sortable) sortable.enable();
3423				return original.apply(self, arguments);
3424			};
3425		})();
3426	
3427		self.setup = (function() {
3428			var original = self.setup;
3429			return function() {
3430				original.apply(this, arguments);
3431	
3432				var $control = self.$control.sortable({
3433					items: '[data-value]',
3434					forcePlaceholderSize: true,
3435					disabled: self.isLocked,
3436					start: function(e, ui) {
3437						ui.placeholder.css('width', ui.helper.css('width'));
3438						$control.css({overflow: 'visible'});
3439					},
3440					stop: function() {
3441						$control.css({overflow: 'hidden'});
3442						var active = self.$activeItems ? self.$activeItems.slice() : null;
3443						var values = [];
3444						$control.children('[data-value]').each(function() {
3445							values.push($(this).attr('data-value'));
3446						});
3447						self.setValue(values);
3448						self.setActiveItem(active);
3449					}
3450				});
3451			};
3452		})();
3453	
3454	});
3455	
3456	Selectize.define('dropdown_header', function(options) {
3457		var self = this;
3458	
3459		options = $.extend({
3460			title         : 'Untitled',
3461			headerClass   : 'selectize-dropdown-header',
3462			titleRowClass : 'selectize-dropdown-header-title',
3463			labelClass    : 'selectize-dropdown-header-label',
3464			closeClass    : 'selectize-dropdown-header-close',
3465	
3466			html: function(data) {
3467				return (
3468					'<div class="' + data.headerClass + '">' +
3469						'<div class="' + data.titleRowClass + '">' +
3470							'<span class="' + data.labelClass + '">' + data.title + '</span>' +
3471							'<a href="javascript:void(0)" class="' + data.closeClass + '">&times;</a>' +
3472						'</div>' +
3473					'</div>'
3474				);
3475			}
3476		}, options);
3477	
3478		self.setup = (function() {
3479			var original = self.setup;
3480			return function() {
3481				original.apply(self, arguments);
3482				self.$dropdown_header = $(options.html(options));
3483				self.$dropdown.prepend(self.$dropdown_header);
3484			};
3485		})();
3486	
3487	});
3488	
3489	Selectize.define('optgroup_columns', function(options) {
3490		var self = this;
3491	
3492		options = $.extend({
3493			equalizeWidth  : true,
3494			equalizeHeight : true
3495		}, options);
3496	
3497		this.getAdjacentOption = function($option, direction) {
3498			var $options = $option.closest('[data-group]').find('[data-selectable]');
3499			var index    = $options.index($option) + direction;
3500	
3501			return index >= 0 && index < $options.length ? $options.eq(index) : $();
3502		};
3503	
3504		this.onKeyDown = (function() {
3505			var original = self.onKeyDown;
3506			return function(e) {
3507				var index, $option, $options, $optgroup;
3508	
3509				if (this.isOpen && (e.keyCode === KEY_LEFT || e.keyCode === KEY_RIGHT)) {
3510					self.ignoreHover = true;
3511					$optgroup = this.$activeOption.closest('[data-group]');
3512					index = $optgroup.find('[data-selectable]').index(this.$activeOption);
3513	
3514					if(e.keyCode === KEY_LEFT) {
3515						$optgroup = $optgroup.prev('[data-group]');
3516					} else {
3517						$optgroup = $optgroup.next('[data-group]');
3518					}
3519	
3520					$options = $optgroup.find('[data-selectable]');
3521					$option  = $options.eq(Math.min($options.length - 1, index));
3522					if ($option.length) {
3523						this.setActiveOption($option);
3524					}
3525					return;
3526				}
3527	
3528				return original.apply(this, arguments);
3529			};
3530		})();
3531	
3532		var getScrollbarWidth = function() {
3533			var div;
3534			var width = getScrollbarWidth.width;
3535			var doc = document;
3536	
3537			if (typeof width === 'undefined') {
3538				div = doc.createElement('div');
3539				div.innerHTML = '<div style="width:50px;height:50px;position:absolute;left:-50px;top:-50px;overflow:auto;"><div style="width:1px;height:100px;"></div></div>';
3540				div = div.firstChild;
3541				doc.body.appendChild(div);
3542				width = getScrollbarWidth.width = div.offsetWidth - div.clientWidth;
3543				doc.body.removeChild(div);
3544			}
3545			return width;
3546		};
3547	
3548		var equalizeSizes = function() {
3549			var i, n, height_max, width, width_last, width_parent, $optgroups;
3550	
3551			$optgroups = $('[data-group]', self.$dropdown_content);
3552			n = $optgroups.length;
3553			if (!n || !self.$dropdown_content.width()) return;
3554	
3555			if (options.equalizeHeight) {
3556				height_max = 0;
3557				for (i = 0; i < n; i++) {
3558					height_max = Math.max(height_max, $optgroups.eq(i).height());
3559				}
3560				$optgroups.css({height: height_max});
3561			}
3562	
3563			if (options.equalizeWidth) {
3564				width_parent = self.$dropdown_content.innerWidth() - getScrollbarWidth();
3565				width = Math.round(width_parent / n);
3566				$optgroups.css({width: width});
3567				if (n > 1) {
3568					width_last = width_parent - width * (n - 1);
3569					$optgroups.eq(n - 1).css({width: width_last});
3570				}
3571			}
3572		};
3573	
3574		if (options.equalizeHeight || options.equalizeWidth) {
3575			hook.after(this, 'positionDropdown', equalizeSizes);
3576			hook.after(this, 'refreshOptions', equalizeSizes);
3577		}
3578	
3579	
3580	});
3581	
3582	Selectize.define('remove_button', function(options) {
3583		if (this.settings.mode === 'single') return;
3584	
3585		options = $.extend({
3586			label     : '&times;',
3587			title     : 'Remove',
3588			className : 'remove',
3589			append    : true
3590		}, options);
3591	
3592		var self = this;
3593		var html = '<a href="javascript:void(0)" class="' + options.className + '" tabindex="-1" title="' + escape_html(options.title) + '">' + options.label + '</a>';
3594	
3595		/**
3596		 * Appends an element as a child (with raw HTML).
3597		 *
3598		 * @param {string} html_container
3599		 * @param {string} html_element
3600		 * @return {string}
3601		 */
3602		var append = function(html_container, html_element) {
3603			var pos = html_container.search(/(<\/[^>]+>\s*)$/);
3604			return html_container.substring(0, pos) + html_element + html_container.substring(pos);
3605		};
3606	
3607		this.setup = (function() {
3608			var original = self.setup;
3609			return function() {
3610				// override the item rendering method to add the button to each
3611				if (options.append) {
3612					var render_item = self.settings.render.item;
3613					self.settings.render.item = function(data) {
3614						return append(render_item.apply(this, arguments), html);
3615					};
3616				}
3617	
3618				original.apply(this, arguments);
3619	
3620				// add event listener
3621				this.$control.on('click', '.' + options.className, function(e) {
3622					e.preventDefault();
3623					if (self.isLocked) return;
3624	
3625					var $item = $(e.currentTarget).parent();
3626					self.setActiveItem($item);
3627					if (self.deleteSelection()) {
3628						self.setCaret(self.items.length);
3629					}
3630				});
3631	
3632			};
3633		})();
3634	
3635	});
3636	
3637	Selectize.define('restore_on_backspace', function(options) {
3638		var self = this;
3639	
3640		options.text = options.text || function(option) {
3641			return option[this.settings.labelField];
3642		};
3643	
3644		this.onKeyDown = (function() {
3645			var original = self.onKeyDown;
3646			return function(e) {
3647				var index, option;
3648				if (e.keyCode === KEY_BACKSPACE && this.$control_input.val() === '' && !this.$activeItems.length) {
3649					index = this.caretPos - 1;
3650					if (index >= 0 && index < this.items.length) {
3651						option = this.options[this.items[index]];
3652						if (this.deleteSelection(e)) {
3653							this.setTextboxValue(options.text.apply(this, [option]));
3654							this.refreshOptions(true);
3655						}
3656						e.preventDefault();
3657						return;
3658					}
3659				}
3660				return original.apply(this, arguments);
3661			};
3662		})();
3663	});
3664	
3665
3666	return Selectize;
3667}));

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.