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