PageSourceSearch

https://app.simplelogin.io/static/assets/js/vendors/selectize.min.js

js simplelogin.io collected 2026-09-24 08:43:17 UTC 103,580 bytes, 3,829 lines download raw bytes

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

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.