blazer 2.2.6

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of blazer might be problematic. Click here for more details.

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