selectize-rails_kwyoung11 0.12.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 77e43f9e7a54e631565cb59353afb32f5a950795
4
+ data.tar.gz: 6ec95f736c59417c64381b7f05da987ad8245ded
5
+ SHA512:
6
+ metadata.gz: 56f7c154f551a28088f432079c85bffd57a8c2e031efbd91a2b6e2eb3e45c24b5ecc3f38def12e8c4e804ffcf018033929e368e7cd2c0965b8d51d34aec0f289
7
+ data.tar.gz: 8c8964b9026c1a601b3195532525bad67229d389d0a57efe5df94d1c5497f7b9044393d06743505253b4f874c7fa6ea4d49e036c4b2641c878af61ad5b537d13
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ _source
@@ -0,0 +1,33 @@
1
+ # Changelog
2
+
3
+ | Version | Notes |
4
+ | ----------:| ----------------------------------------------------------- |
5
+ | 0.12.7 | Adds ability to input duplicate values in select input |
6
+ | 0.12.6 | Update to v0.12.6 of selectize.js |
7
+ | 0.12.5 | Update to v0.12.5 of selectize.js |
8
+ | 0.12.4.1 | Moved css files to scss to be able to use `@import` |
9
+ | 0.12.4 | Update to v0.12.4 of selectize.js |
10
+ | 0.12.3 | Update to v0.12.3 of selectize.js |
11
+ | 0.12.2 | Update to v0.12.2 of selectize.js |
12
+ | 0.12.1 | Update to v0.12.1 of selectize.js |
13
+ | 0.12.0 | Update to v0.12.0 of selectize.js |
14
+ | 0.11.2 | Update to v0.11.2 of selectize.js |
15
+ | 0.11.0 | Update to v0.11.0 of selectize.js |
16
+ | 0.9.1 | Update to v0.9.1 of selectize.js |
17
+ | 0.9.0 | Update to v0.9.0 of selectize.js |
18
+ | 0.8.5 | Update to v0.8.5 of selectize.js |
19
+ | 0.8.4 | Update to v0.8.4 of selectize.js |
20
+ | 0.8.3 | Update to v0.8.3 of selectize.js |
21
+ | 0.8.1 | Update to v0.8.1 of selectize.js |
22
+ | 0.8.0 | Update to v0.8.0 of selectize.js |
23
+ | 0.7.7 | Update to v0.7.7 of selectize.js |
24
+ | 0.7.6 | Update to v0.7.6 of selectize.js |
25
+ | 0.7.5 | Update to v0.7.5 of selectize.js |
26
+ | 0.7.4 | Update to v0.7.4 of selectize.js |
27
+ | 0.7.3 | Update to v0.7.3 of selectize.js |
28
+ | 0.7.2 | Update to v0.7.2 of selectize.js |
29
+ | 0.7.0 | Update to v0.7.0 of selectize.js |
30
+ | 0.6.14 | Update to v0.6.14 of selectize.js |
31
+ | 0.6.4 | Update to v0.6.4 of selectize.js |
32
+ | 0.6.1 | Update and set gem version equal to selectize.js version |
33
+ | 0.1.0 | Initial release |
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in selectize-rails.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Manuel van Rijn
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,72 @@
1
+ # selectize-rails [![Gem Version](https://badge.fury.io/rb/selectize-rails.png)](http://badge.fury.io/rb/selectize-rails)
2
+
3
+ selectize-rails provides the [selectize.js](https://selectize.github.io/selectize.js/)
4
+ plugin as a Rails engine to use it within the asset pipeline.
5
+
6
+ ## Installation
7
+
8
+ Add this to your Gemfile:
9
+
10
+ ```ruby
11
+ gem "selectize-rails"
12
+ ```
13
+
14
+ and run `bundle install`.
15
+
16
+ ## Usage
17
+
18
+ In your `application.js`, include the following:
19
+
20
+ ```js
21
+ //= require selectize
22
+ ```
23
+
24
+ In your `application.css`, include the following:
25
+
26
+ ```css
27
+ *= require selectize
28
+ *= require selectize.default
29
+ ```
30
+
31
+ Or if you like, you could use import instead
32
+
33
+ ```sass
34
+ @import 'selectize'
35
+ @import 'selectize.bootstrap3'
36
+ ```
37
+
38
+ ### Themes
39
+
40
+ To include additional theme's you can replace the `selectize.default` for one of the [theme files](https://github.com/selectize/selectize.js/tree/master/dist/css)
41
+
42
+
43
+ ## Examples
44
+
45
+ See the [demo page](http://selectize.github.io/selectize.js/) for examples how to use the plugin
46
+
47
+ ## Changes
48
+
49
+ | Version | Notes |
50
+ | ----------:| ----------------------------------------------------------- |
51
+ | 0.12.6 | Update to v0.12.6 of selectize.js |
52
+ | 0.12.5 | Update to v0.12.5 of selectize.js |
53
+ | 0.12.4.1 | Moved css files to scss to be able to use `@import` |
54
+ | 0.12.4 | Update to v0.12.4 of selectize.js |
55
+ | 0.12.3 | Update to v0.12.3 of selectize.js |
56
+
57
+ [older](CHANGELOG.md)
58
+
59
+ ## License
60
+
61
+ * The [selectize.js](http://selectize.github.io/selectize.js/) plugin is licensed under the
62
+ [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0)
63
+ * The [selectize-rails](https://github.com/manuelvanrijn/selectize-rails) project is
64
+ licensed under the [MIT License](http://opensource.org/licenses/mit-license.html)
65
+
66
+ ## Contributing
67
+
68
+ 1. Fork it
69
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
70
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
71
+ 4. Push to the branch (`git push origin my-new-feature`)
72
+ 5. Create new Pull Request
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,12 @@
1
+ require 'rails'
2
+ require 'selectize-rails/version'
3
+
4
+ module Selectize
5
+ module Rails
6
+ if ::Rails.version < '3.1'
7
+ require 'selectize-rails/railtie'
8
+ else
9
+ require 'selectize-rails/engine'
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,5 @@
1
+ module Selectize
2
+ module Rails
3
+ class Engine < ::Rails::Engine; end
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Selectize
2
+ module Rails
3
+ class Railtie < ::Rails::Railtie; end
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Selectize
2
+ module Rails
3
+ VERSION = '0.12.8'.freeze
4
+ end
5
+ end
@@ -0,0 +1,23 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'selectize-rails/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "selectize-rails_kwyoung11"
8
+ spec.version = Selectize::Rails::VERSION
9
+ spec.authors = ["Manuel van Rijn", "Kevin Young"]
10
+ spec.email = ["manuel@manuelvanrijn.nl"]
11
+ spec.description = %q{A small gem for putting selectize.js into the Rails asset pipeline}
12
+ spec.summary = %q{an asset gemification of the selectize.js plugin}
13
+ spec.homepage = "https://github.com/kwyoung11/selectize-rails"
14
+ spec.license = "MIT, Apache License v2.0"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "rake"
23
+ end
@@ -0,0 +1,21 @@
1
+ #!/bin/bash
2
+
3
+ # Checkout vendor repo
4
+ echo "Cloning selectize/selectize.js github repo into tmp_vendor"
5
+ git clone https://github.com/selectize/selectize.js.git tmp_vendor
6
+
7
+ # Copy files
8
+ echo "Copying selectize.js"
9
+ cp tmp_vendor/dist/js/standalone/selectize.js vendor/assets/javascripts/selectize.js
10
+ echo "Copying css files"
11
+ cp tmp_vendor/dist/css/*.css vendor/assets/stylesheets/
12
+ echo "Moving css to scss"
13
+ cd vendor/assets/stylesheets/
14
+ find ./ -name "*.css" | xargs -I '{}' basename '{}' | sed 's/\.css//' | xargs -I '{}' mv '{}.css' '{}.scss'
15
+ cd ../../../
16
+
17
+ # Delete vendor repo
18
+ echo "Removing cloned vendor repo"
19
+ rm -rf tmp_vendor
20
+
21
+ echo "Finished... You'll need to commit the changes. You should consider updating the changelog and gem version number"
@@ -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.settings.allowDuplicates && 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
+ }));