selectr-rails 2.4.1

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: ad91379746104f2d422d58a35d1c25602defab25
4
+ data.tar.gz: 83a5a3a11c62803254270fa56b94d667ec77a47e
5
+ SHA512:
6
+ metadata.gz: 4b6780c44a204c486df0de925884371e361c1eca9f86faa5fac5fce82a5d30c6d2cdff5cbef85cd2826130303f1c71605c46674aa9cd6fe73d3e1f2ba0c84e79
7
+ data.tar.gz: f33899acf509a75a3f0fcbe205c7fd9934deadd601d8c84c8addae239d869bad7d0e9f24315806091d709402834ca63474d7b7cafe73b0d2708fa48ff20f2d33
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.3.7
5
+ before_install: gem install bundler -v 1.16.1
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in selectr-rails.gemspec
6
+ gemspec
@@ -0,0 +1,35 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ selectr-rails (0.1.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ diff-lcs (1.3)
10
+ rake (10.5.0)
11
+ rspec (3.7.0)
12
+ rspec-core (~> 3.7.0)
13
+ rspec-expectations (~> 3.7.0)
14
+ rspec-mocks (~> 3.7.0)
15
+ rspec-core (3.7.1)
16
+ rspec-support (~> 3.7.0)
17
+ rspec-expectations (3.7.0)
18
+ diff-lcs (>= 1.2.0, < 2.0)
19
+ rspec-support (~> 3.7.0)
20
+ rspec-mocks (3.7.0)
21
+ diff-lcs (>= 1.2.0, < 2.0)
22
+ rspec-support (~> 3.7.0)
23
+ rspec-support (3.7.1)
24
+
25
+ PLATFORMS
26
+ ruby
27
+
28
+ DEPENDENCIES
29
+ bundler (~> 1.16)
30
+ rake (~> 10.0)
31
+ rspec (~> 3.0)
32
+ selectr-rails!
33
+
34
+ BUNDLED WITH
35
+ 1.16.1
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2018 Jesse Chavez
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,50 @@
1
+ # Selectr::Rails
2
+
3
+ This gem adds [Selectr](https://github.com/Mobius1/Selectr) to a rails project.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'selectr-rails'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install selectr-rails
20
+
21
+ ## Usage
22
+
23
+ Add the following to your `application.js`:
24
+
25
+ ```
26
+ //= require selectr
27
+ ```
28
+
29
+ Add to your `application.css` or `application.scss`:
30
+
31
+ ```
32
+ *= require selectr
33
+ ```
34
+
35
+ Then initialize a select tag with:
36
+
37
+ ```javascript
38
+ new Selectr('#mySelect')
39
+ ```
40
+
41
+ For more information check out the [Selectr Documentation](https://github.com/Mobius1/Selectr/wiki)
42
+
43
+
44
+ ## Contributing
45
+
46
+ Bug reports and pull requests are welcome on GitHub at https://github.com/JesseChavez/selectr-rails.
47
+
48
+ ## License
49
+
50
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "selectr/rails"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,6 @@
1
+ require 'selectr/version'
2
+
3
+ module Selectr
4
+ class Engine < ::Rails::Engine
5
+ end
6
+ end
@@ -0,0 +1,3 @@
1
+ module Selectr
2
+ VERSION = '2.4.1'
3
+ end
@@ -0,0 +1,27 @@
1
+
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'selectr/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'selectr-rails'
8
+ spec.version = Selectr::VERSION
9
+ spec.authors = ['Jesse Chavez']
10
+ spec.email = ['jesse.chavez.r@gmail.com']
11
+
12
+ spec.summary = 'Selectr Rails'
13
+ spec.description = 'This gem adds Selectr to a rails project'
14
+ spec.homepage = 'https://github.com/JesseChavez/selectr-rails'
15
+ spec.license = 'MIT'
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
18
+ f.match(%r{^(test|spec|features)/})
19
+ end
20
+ spec.bindir = 'exe'
21
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
+ spec.require_paths = ['lib']
23
+
24
+ spec.add_development_dependency 'bundler', '~> 1.16'
25
+ spec.add_development_dependency 'rake', '~> 10.0'
26
+ spec.add_development_dependency 'rspec', '~> 3.0'
27
+ end
@@ -0,0 +1,2284 @@
1
+ /*!
2
+ * Selectr 2.4.0
3
+ * http://mobius.ovh/docs/selectr
4
+ *
5
+ * Released under the MIT license
6
+ */
7
+ (function(root, factory) {
8
+ var plugin = "Selectr";
9
+
10
+ if (typeof define === "function" && define.amd) {
11
+ define([], factory);
12
+ } else if (typeof exports === "object") {
13
+ module.exports = factory(plugin);
14
+ } else {
15
+ root[plugin] = factory(plugin);
16
+ }
17
+ }(this, function(plugin) {
18
+ 'use strict';
19
+
20
+ /**
21
+ * Default configuration options
22
+ * @type {Object}
23
+ */
24
+ var defaultConfig = {
25
+ /**
26
+ * Emulates browser behaviour by selecting the first option by default
27
+ * @type {Boolean}
28
+ */
29
+ defaultSelected: true,
30
+
31
+ /**
32
+ * Sets the width of the container
33
+ * @type {String}
34
+ */
35
+ width: "auto",
36
+
37
+ /**
38
+ * Enables/ disables the container
39
+ * @type {Boolean}
40
+ */
41
+ disabled: false,
42
+
43
+ /**
44
+ * Enables / disables the search function
45
+ * @type {Boolean}
46
+ */
47
+ searchable: true,
48
+
49
+ /**
50
+ * Enable disable the clear button
51
+ * @type {Boolean}
52
+ */
53
+ clearable: false,
54
+
55
+ /**
56
+ * Sort the tags / multiselect options
57
+ * @type {Boolean}
58
+ */
59
+ sortSelected: false,
60
+
61
+ /**
62
+ * Allow deselecting of select-one options
63
+ * @type {Boolean}
64
+ */
65
+ allowDeselect: false,
66
+
67
+ /**
68
+ * Close the dropdown when scrolling (@AlexanderReiswich, #11)
69
+ * @type {Boolean}
70
+ */
71
+ closeOnScroll: false,
72
+
73
+ /**
74
+ * Allow the use of the native dropdown (@jonnyscholes, #14)
75
+ * @type {Boolean}
76
+ */
77
+ nativeDropdown: false,
78
+
79
+ /**
80
+ * Allow the use of native typing behavior for toggling, searching, selecting
81
+ * @type {boolean}
82
+ */
83
+ nativeKeyboard: false,
84
+
85
+ /**
86
+ * Set the main placeholder
87
+ * @type {String}
88
+ */
89
+ placeholder: "Select an option...",
90
+
91
+ /**
92
+ * Allow the tagging feature
93
+ * @type {Boolean}
94
+ */
95
+ taggable: false,
96
+
97
+ /**
98
+ * Set the tag input placeholder (@labikmartin, #21, #22)
99
+ * @type {String}
100
+ */
101
+ tagPlaceholder: "Enter a tag..."
102
+ };
103
+
104
+ /**
105
+ * Event Emitter
106
+ */
107
+ var Events = function() {};
108
+
109
+ /**
110
+ * Event Prototype
111
+ * @type {Object}
112
+ */
113
+ Events.prototype = {
114
+ /**
115
+ * Add custom event listener
116
+ * @param {String} event Event type
117
+ * @param {Function} func Callback
118
+ * @return {Void}
119
+ */
120
+ on: function(event, func) {
121
+ this._events = this._events || {};
122
+ this._events[event] = this._events[event] || [];
123
+ this._events[event].push(func);
124
+ },
125
+
126
+ /**
127
+ * Remove custom event listener
128
+ * @param {String} event Event type
129
+ * @param {Function} func Callback
130
+ * @return {Void}
131
+ */
132
+ off: function(event, func) {
133
+ this._events = this._events || {};
134
+ if (event in this._events === false) return;
135
+ this._events[event].splice(this._events[event].indexOf(func), 1);
136
+ },
137
+
138
+ /**
139
+ * Fire a custom event
140
+ * @param {String} event Event type
141
+ * @return {Void}
142
+ */
143
+ emit: function(event /* , args... */ ) {
144
+ this._events = this._events || {};
145
+ if (event in this._events === false) return;
146
+ for (var i = 0; i < this._events[event].length; i++) {
147
+ this._events[event][i].apply(this, Array.prototype.slice.call(arguments, 1));
148
+ }
149
+ }
150
+ };
151
+
152
+ /**
153
+ * Event mixin
154
+ * @param {Object} obj
155
+ * @return {Object}
156
+ */
157
+ Events.mixin = function(obj) {
158
+ var props = ['on', 'off', 'emit'];
159
+ for (var i = 0; i < props.length; i++) {
160
+ if (typeof obj === 'function') {
161
+ obj.prototype[props[i]] = Events.prototype[props[i]];
162
+ } else {
163
+ obj[props[i]] = Events.prototype[props[i]];
164
+ }
165
+ }
166
+ return obj;
167
+ };
168
+
169
+ /**
170
+ * Helpers
171
+ * @type {Object}
172
+ */
173
+ var util = {
174
+ extend: function(src, props) {
175
+ props = props || {};
176
+ var p;
177
+ for (p in src) {
178
+ if (src.hasOwnProperty(p)) {
179
+ if (!props.hasOwnProperty(p)) {
180
+ props[p] = src[p];
181
+ }
182
+ }
183
+ }
184
+ return props;
185
+ },
186
+ each: function(a, b, c) {
187
+ if ("[object Object]" === Object.prototype.toString.call(a)) {
188
+ for (var d in a) {
189
+ if (Object.prototype.hasOwnProperty.call(a, d)) {
190
+ b.call(c, d, a[d], a);
191
+ }
192
+ }
193
+ } else {
194
+ for (var e = 0, f = a.length; e < f; e++) {
195
+ b.call(c, e, a[e], a);
196
+ }
197
+ }
198
+ },
199
+ createElement: function(e, a) {
200
+ var d = document,
201
+ el = d.createElement(e);
202
+ if (a && "[object Object]" === Object.prototype.toString.call(a)) {
203
+ var i;
204
+ for (i in a)
205
+ if (i in el) el[i] = a[i];
206
+ else if ("html" === i) el.innerHTML = a[i];
207
+ else if ("text" === i) {
208
+ var t = d.createTextNode(a[i]);
209
+ el.appendChild(t);
210
+ } else el.setAttribute(i, a[i]);
211
+ }
212
+ return el;
213
+ },
214
+ hasClass: function(a, b) {
215
+ if (a)
216
+ return a.classList ? a.classList.contains(b) : !!a.className && !!a.className.match(new RegExp("(\\s|^)" + b + "(\\s|$)"));
217
+ },
218
+ addClass: function(a, b) {
219
+ if (!util.hasClass(a, b)) {
220
+ if (a.classList) {
221
+ a.classList.add(b);
222
+ } else {
223
+ a.className = a.className.trim() + " " + b;
224
+ }
225
+ }
226
+ },
227
+ removeClass: function(a, b) {
228
+ if (util.hasClass(a, b)) {
229
+ if (a.classList) {
230
+ a.classList.remove(b);
231
+ } else {
232
+ a.className = a.className.replace(new RegExp("(^|\\s)" + b.split(" ").join("|") + "(\\s|$)", "gi"), " ");
233
+ }
234
+ }
235
+ },
236
+ closest: function(el, fn) {
237
+ return el && el !== document.body && (fn(el) ? el : util.closest(el.parentNode, fn));
238
+ },
239
+ isInt: function(val) {
240
+ return typeof val === 'number' && isFinite(val) && Math.floor(val) === val;
241
+ },
242
+ debounce: function(a, b, c) {
243
+ var d;
244
+ return function() {
245
+ var e = this,
246
+ f = arguments,
247
+ g = function() {
248
+ d = null;
249
+ if (!c) a.apply(e, f);
250
+ },
251
+ h = c && !d;
252
+ clearTimeout(d);
253
+ d = setTimeout(g, b);
254
+ if (h) {
255
+ a.apply(e, f);
256
+ }
257
+ };
258
+ },
259
+ rect: function(el, abs) {
260
+ var w = window;
261
+ var r = el.getBoundingClientRect();
262
+ var x = abs ? w.pageXOffset : 0;
263
+ var y = abs ? w.pageYOffset : 0;
264
+
265
+ return {
266
+ bottom: r.bottom + y,
267
+ height: r.height,
268
+ left: r.left + x,
269
+ right: r.right + x,
270
+ top: r.top + y,
271
+ width: r.width
272
+ };
273
+ },
274
+ includes: function(a, b) {
275
+ return a.indexOf(b) > -1;
276
+ },
277
+ startsWith: function(a, b) {
278
+ return a.substr( 0, b.length ) === b;
279
+ },
280
+ truncate: function(el) {
281
+ while (el.firstChild) {
282
+ el.removeChild(el.firstChild);
283
+ }
284
+ }
285
+ };
286
+
287
+
288
+ function isset(obj, prop) {
289
+ return obj.hasOwnProperty(prop) && (obj[prop] === true || obj[prop].length);
290
+ }
291
+
292
+ /**
293
+ * Append an item to the list
294
+ * @param {Object} item
295
+ * @param {Object} custom
296
+ * @return {Void}
297
+ */
298
+ function appendItem(item, parent, custom) {
299
+ if (item.parentNode) {
300
+ if (!item.parentNode.parentNode) {
301
+ parent.appendChild(item.parentNode);
302
+ }
303
+ } else {
304
+ parent.appendChild(item);
305
+ }
306
+
307
+ util.removeClass(item, "excluded");
308
+ if (!custom) {
309
+ item.innerHTML = item.textContent;
310
+ }
311
+ }
312
+
313
+ /**
314
+ * Render the item list
315
+ * @return {Void}
316
+ */
317
+ var render = function() {
318
+ if (this.items.length) {
319
+ var f = document.createDocumentFragment();
320
+
321
+ if (this.config.pagination) {
322
+ var pages = this.pages.slice(0, this.pageIndex);
323
+
324
+ util.each(pages, function(i, items) {
325
+ util.each(items, function(j, item) {
326
+ appendItem(item, f, this.customOption);
327
+ }, this);
328
+ }, this);
329
+ } else {
330
+ util.each(this.items, function(i, item) {
331
+ appendItem(item, f, this.customOption);
332
+ }, this);
333
+ }
334
+
335
+ // highlight first selected option if any; first option otherwise
336
+ if (f.childElementCount) {
337
+ util.removeClass(this.items[this.navIndex], "active");
338
+ this.navIndex = (
339
+ f.querySelector(".selectr-option.selected") ||
340
+ f.querySelector(".selectr-option")
341
+ ).idx;
342
+ util.addClass(this.items[this.navIndex], "active");
343
+ }
344
+
345
+ this.tree.appendChild(f);
346
+ }
347
+ };
348
+
349
+ /**
350
+ * Dismiss / close the dropdown
351
+ * @param {obj} e
352
+ * @return {void}
353
+ */
354
+ var dismiss = function(e) {
355
+ var target = e.target;
356
+ if (!this.container.contains(target) && (this.opened || util.hasClass(this.container, "notice"))) {
357
+ this.close();
358
+ }
359
+ };
360
+
361
+ /**
362
+ * Build a list item from the HTMLOptionElement
363
+ * @param {int} i HTMLOptionElement index
364
+ * @param {HTMLOptionElement} option
365
+ * @param {bool} group Has parent optgroup
366
+ * @return {void}
367
+ */
368
+ var createItem = function(option, data) {
369
+ data = data || option;
370
+ var content = this.customOption ? this.config.renderOption(data) : option.textContent;
371
+ var opt = util.createElement("li", {
372
+ class: "selectr-option",
373
+ html: content,
374
+ role: "treeitem",
375
+ "aria-selected": false
376
+ });
377
+
378
+ opt.idx = option.idx;
379
+
380
+ this.items.push(opt);
381
+
382
+ if (option.defaultSelected) {
383
+ this.defaultSelected.push(option.idx);
384
+ }
385
+
386
+ if (option.disabled) {
387
+ opt.disabled = true;
388
+ util.addClass(opt, "disabled");
389
+ }
390
+
391
+ return opt;
392
+ };
393
+
394
+ /**
395
+ * Build the container
396
+ * @return {Void}
397
+ */
398
+ var build = function() {
399
+
400
+ this.requiresPagination = this.config.pagination && this.config.pagination > 0;
401
+
402
+ // Set width
403
+ if (isset(this.config, "width")) {
404
+ if (util.isInt(this.config.width)) {
405
+ this.width = this.config.width + "px";
406
+ } else {
407
+ if (this.config.width === "auto") {
408
+ this.width = "100%";
409
+ } else if (util.includes(this.config.width, "%")) {
410
+ this.width = this.config.width;
411
+ }
412
+ }
413
+ }
414
+
415
+ this.container = util.createElement("div", {
416
+ class: "selectr-container"
417
+ });
418
+
419
+ // Custom className
420
+ if (this.config.customClass) {
421
+ util.addClass(this.container, this.config.customClass);
422
+ }
423
+
424
+ // Mobile device
425
+ if (this.mobileDevice) {
426
+ util.addClass(this.container, "selectr-mobile");
427
+ } else {
428
+ util.addClass(this.container, "selectr-desktop");
429
+ }
430
+
431
+ // Hide the HTMLSelectElement and prevent focus
432
+ this.el.tabIndex = -1;
433
+
434
+ // Native dropdown
435
+ if (this.config.nativeDropdown || this.mobileDevice) {
436
+ util.addClass(this.el, "selectr-visible");
437
+ } else {
438
+ util.addClass(this.el, "selectr-hidden");
439
+ }
440
+
441
+ this.selected = util.createElement("div", {
442
+ class: "selectr-selected",
443
+ disabled: this.disabled,
444
+ tabIndex: 1, // enable tabIndex (#9)
445
+ "aria-expanded": false
446
+ });
447
+
448
+ this.label = util.createElement(this.el.multiple ? "ul" : "span", {
449
+ class: "selectr-label"
450
+ });
451
+
452
+ var dropdown = util.createElement("div", {
453
+ class: "selectr-options-container"
454
+ });
455
+
456
+ this.tree = util.createElement("ul", {
457
+ class: "selectr-options",
458
+ role: "tree",
459
+ "aria-hidden": true,
460
+ "aria-expanded": false
461
+ });
462
+
463
+ this.notice = util.createElement("div", {
464
+ class: "selectr-notice"
465
+ });
466
+
467
+ this.el.setAttribute("aria-hidden", true);
468
+
469
+ if (this.disabled) {
470
+ this.el.disabled = true;
471
+ }
472
+
473
+ if (this.el.multiple) {
474
+ util.addClass(this.label, "selectr-tags");
475
+ util.addClass(this.container, "multiple");
476
+
477
+ // Collection of tags
478
+ this.tags = [];
479
+
480
+ // Collection of selected values
481
+ this.selectedValues = this.getSelectedProperties('value');
482
+
483
+ // Collection of selected indexes
484
+ this.selectedIndexes = this.getSelectedProperties('idx');
485
+ }
486
+
487
+ this.selected.appendChild(this.label);
488
+
489
+ if (this.config.clearable) {
490
+ this.selectClear = util.createElement("button", {
491
+ class: "selectr-clear",
492
+ type: "button"
493
+ });
494
+
495
+ this.container.appendChild(this.selectClear);
496
+
497
+ util.addClass(this.container, "clearable");
498
+ }
499
+
500
+ if (this.config.taggable) {
501
+ var li = util.createElement('li', {
502
+ class: 'input-tag'
503
+ });
504
+ this.input = util.createElement("input", {
505
+ class: "selectr-tag-input",
506
+ placeholder: this.config.tagPlaceholder,
507
+ tagIndex: 0,
508
+ autocomplete: "off",
509
+ autocorrect: "off",
510
+ autocapitalize: "off",
511
+ spellcheck: "false",
512
+ role: "textbox",
513
+ type: "search"
514
+ });
515
+
516
+ li.appendChild(this.input);
517
+ this.label.appendChild(li);
518
+ util.addClass(this.container, "taggable");
519
+
520
+ this.tagSeperators = [","];
521
+ if (this.config.tagSeperators) {
522
+ this.tagSeperators = this.tagSeperators.concat(this.config.tagSeperators);
523
+ }
524
+ }
525
+
526
+ if (this.config.searchable) {
527
+ this.input = util.createElement("input", {
528
+ class: "selectr-input",
529
+ tagIndex: -1,
530
+ autocomplete: "off",
531
+ autocorrect: "off",
532
+ autocapitalize: "off",
533
+ spellcheck: "false",
534
+ role: "textbox",
535
+ type: "search"
536
+ });
537
+ this.inputClear = util.createElement("button", {
538
+ class: "selectr-input-clear",
539
+ type: "button"
540
+ });
541
+ this.inputContainer = util.createElement("div", {
542
+ class: "selectr-input-container"
543
+ });
544
+
545
+ this.inputContainer.appendChild(this.input);
546
+ this.inputContainer.appendChild(this.inputClear);
547
+ dropdown.appendChild(this.inputContainer);
548
+ }
549
+
550
+ dropdown.appendChild(this.notice);
551
+ dropdown.appendChild(this.tree);
552
+
553
+ // List of items for the dropdown
554
+ this.items = [];
555
+
556
+ // Establish options
557
+ this.options = [];
558
+
559
+ // Check for options in the element
560
+ if (this.el.options.length) {
561
+ this.options = [].slice.call(this.el.options);
562
+ }
563
+
564
+ // Element may have optgroups so
565
+ // iterate element.children instead of element.options
566
+ var group = false,
567
+ j = 0;
568
+ if (this.el.children.length) {
569
+ util.each(this.el.children, function(i, element) {
570
+ if (element.nodeName === "OPTGROUP") {
571
+
572
+ group = util.createElement("ul", {
573
+ class: "selectr-optgroup",
574
+ role: "group",
575
+ html: "<li class='selectr-optgroup--label'>" + element.label + "</li>"
576
+ });
577
+
578
+ util.each(element.children, function(x, el) {
579
+ el.idx = j;
580
+ group.appendChild(createItem.call(this, el, group));
581
+ j++;
582
+ }, this);
583
+ } else {
584
+ element.idx = j;
585
+ createItem.call(this, element);
586
+ j++;
587
+ }
588
+ }, this);
589
+ }
590
+
591
+ // Options defined by the data option
592
+ if (this.config.data && Array.isArray(this.config.data)) {
593
+ this.data = [];
594
+ var optgroup = false,
595
+ option;
596
+
597
+ group = false;
598
+ j = 0;
599
+
600
+ util.each(this.config.data, function(i, opt) {
601
+ // Check for group options
602
+ if (isset(opt, "children")) {
603
+ optgroup = util.createElement("optgroup", {
604
+ label: opt.text
605
+ });
606
+
607
+ group = util.createElement("ul", {
608
+ class: "selectr-optgroup",
609
+ role: "group",
610
+ html: "<li class='selectr-optgroup--label'>" + opt.text + "</li>"
611
+ });
612
+
613
+ util.each(opt.children, function(x, data) {
614
+ option = new Option(data.text, data.value, false, data.hasOwnProperty("selected") && data.selected === true);
615
+
616
+ option.disabled = isset(data, "disabled");
617
+
618
+ this.options.push(option);
619
+
620
+ optgroup.appendChild(option);
621
+
622
+ option.idx = j;
623
+
624
+ group.appendChild(createItem.call(this, option, data));
625
+
626
+ this.data[j] = data;
627
+
628
+ j++;
629
+ }, this);
630
+ } else {
631
+ option = new Option(opt.text, opt.value, false, opt.hasOwnProperty("selected") && opt.selected === true);
632
+
633
+ option.disabled = isset(opt, "disabled");
634
+
635
+ this.options.push(option);
636
+
637
+ option.idx = j;
638
+
639
+ createItem.call(this, option, opt);
640
+
641
+ this.data[j] = opt;
642
+
643
+ j++;
644
+ }
645
+ }, this);
646
+ }
647
+
648
+ this.setSelected(true);
649
+
650
+ var first;
651
+ this.navIndex = 0;
652
+ for (var i = 0; i < this.items.length; i++) {
653
+ first = this.items[i];
654
+
655
+ if (!util.hasClass(first, "disabled")) {
656
+
657
+ util.addClass(first, "active");
658
+ this.navIndex = i;
659
+ break;
660
+ }
661
+ }
662
+
663
+ // Check for pagination / infinite scroll
664
+ if (this.requiresPagination) {
665
+ this.pageIndex = 1;
666
+
667
+ // Create the pages
668
+ this.paginate();
669
+ }
670
+
671
+ this.container.appendChild(this.selected);
672
+ this.container.appendChild(dropdown);
673
+
674
+ this.placeEl = util.createElement("div", {
675
+ class: "selectr-placeholder"
676
+ });
677
+
678
+ // Set the placeholder
679
+ this.setPlaceholder();
680
+
681
+ this.selected.appendChild(this.placeEl);
682
+
683
+ // Disable if required
684
+ if (this.disabled) {
685
+ this.disable();
686
+ }
687
+
688
+ this.el.parentNode.insertBefore(this.container, this.el);
689
+ this.container.appendChild(this.el);
690
+ };
691
+
692
+ /**
693
+ * Navigate through the dropdown
694
+ * @param {obj} e
695
+ * @return {void}
696
+ */
697
+ var navigate = function(e) {
698
+ e = e || window.event;
699
+
700
+ // Filter out the keys we don"t want
701
+ if (!this.items.length || !this.opened || !util.includes([13, 38, 40], e.which)) {
702
+ this.navigating = false;
703
+ return;
704
+ }
705
+
706
+ e.preventDefault();
707
+
708
+ if (e.which === 13) {
709
+
710
+ if (this.config.taggable && this.input.value.length > 0) {
711
+ return false;
712
+ }
713
+
714
+ return this.change(this.navIndex);
715
+ }
716
+
717
+ var direction, prevEl = this.items[this.navIndex];
718
+ var lastIndex = this.navIndex;
719
+
720
+ switch (e.which) {
721
+ case 38:
722
+ direction = 0;
723
+ if (this.navIndex > 0) {
724
+ this.navIndex--;
725
+ }
726
+ break;
727
+ case 40:
728
+ direction = 1;
729
+ if (this.navIndex < this.items.length - 1) {
730
+ this.navIndex++;
731
+ }
732
+ }
733
+
734
+ this.navigating = true;
735
+
736
+
737
+ // Instead of wasting memory holding a copy of this.items
738
+ // with disabled / excluded options omitted, skip them instead
739
+ while (util.hasClass(this.items[this.navIndex], "disabled") || util.hasClass(this.items[this.navIndex], "excluded")) {
740
+ if (this.navIndex > 0 && this.navIndex < this.items.length -1) {
741
+ if (direction) {
742
+ this.navIndex++;
743
+ } else {
744
+ this.navIndex--;
745
+ }
746
+ } else {
747
+ this.navIndex = lastIndex;
748
+ break;
749
+ }
750
+
751
+ if (this.searching) {
752
+ if (this.navIndex > this.tree.lastElementChild.idx) {
753
+ this.navIndex = this.tree.lastElementChild.idx;
754
+ break;
755
+ } else if (this.navIndex < this.tree.firstElementChild.idx) {
756
+ this.navIndex = this.tree.firstElementChild.idx;
757
+ break;
758
+ }
759
+ }
760
+ }
761
+
762
+ // Autoscroll the dropdown during navigation
763
+ var r = util.rect(this.items[this.navIndex]);
764
+
765
+ if (!direction) {
766
+ if (this.navIndex === 0) {
767
+ this.tree.scrollTop = 0;
768
+ } else if (r.top - this.optsRect.top < 0) {
769
+ this.tree.scrollTop = this.tree.scrollTop + (r.top - this.optsRect.top);
770
+ }
771
+ } else {
772
+ if (this.navIndex === 0) {
773
+ this.tree.scrollTop = 0;
774
+ } else if ((r.top + r.height) > (this.optsRect.top + this.optsRect.height)) {
775
+ this.tree.scrollTop = this.tree.scrollTop + ((r.top + r.height) - (this.optsRect.top + this.optsRect.height));
776
+ }
777
+
778
+ // Load another page if needed
779
+ if (this.navIndex === this.tree.childElementCount - 1 && this.requiresPagination) {
780
+ load.call(this);
781
+ }
782
+ }
783
+
784
+ if (prevEl) {
785
+ util.removeClass(prevEl, "active");
786
+ }
787
+
788
+ util.addClass(this.items[this.navIndex], "active");
789
+ };
790
+
791
+ /**
792
+ * Add a tag
793
+ * @param {HTMLElement} item
794
+ */
795
+ var addTag = function(item) {
796
+ var that = this,
797
+ r;
798
+
799
+ var docFrag = document.createDocumentFragment();
800
+ var option = this.options[item.idx];
801
+ var data = this.data ? this.data[item.idx] : option;
802
+ var content = this.customSelected ? this.config.renderSelection(data) : option.textContent;
803
+
804
+ var tag = util.createElement("li", {
805
+ class: "selectr-tag",
806
+ html: content
807
+ });
808
+ var btn = util.createElement("button", {
809
+ class: "selectr-tag-remove",
810
+ type: "button"
811
+ });
812
+
813
+ tag.appendChild(btn);
814
+
815
+ // Set property to check against later
816
+ tag.idx = item.idx;
817
+ tag.tag = option.value;
818
+
819
+ this.tags.push(tag);
820
+
821
+ if (this.config.sortSelected) {
822
+
823
+ var tags = this.tags.slice();
824
+
825
+ // Deal with values that contain numbers
826
+ r = function(val, arr) {
827
+ val.replace(/(\d+)|(\D+)/g, function(that, $1, $2) {
828
+ arr.push([$1 || Infinity, $2 || ""]);
829
+ });
830
+ };
831
+
832
+ tags.sort(function(a, b) {
833
+ var x = [],
834
+ y = [],
835
+ ac, bc;
836
+ if (that.config.sortSelected === true) {
837
+ ac = a.tag;
838
+ bc = b.tag;
839
+ } else if (that.config.sortSelected === 'text') {
840
+ ac = a.textContent;
841
+ bc = b.textContent;
842
+ }
843
+
844
+ r(ac, x);
845
+ r(bc, y);
846
+
847
+ while (x.length && y.length) {
848
+ var ax = x.shift();
849
+ var by = y.shift();
850
+ var nn = (ax[0] - by[0]) || ax[1].localeCompare(by[1]);
851
+ if (nn) return nn;
852
+ }
853
+
854
+ return x.length - y.length;
855
+ });
856
+
857
+ util.each(tags, function(i, tg) {
858
+ docFrag.appendChild(tg);
859
+ });
860
+
861
+ this.label.innerHTML = "";
862
+
863
+ } else {
864
+ docFrag.appendChild(tag);
865
+ }
866
+
867
+ if (this.config.taggable) {
868
+ this.label.insertBefore(docFrag, this.input.parentNode);
869
+ } else {
870
+ this.label.appendChild(docFrag);
871
+ }
872
+ };
873
+
874
+ /**
875
+ * Remove a tag
876
+ * @param {HTMLElement} item
877
+ * @return {void}
878
+ */
879
+ var removeTag = function(item) {
880
+ var tag = false;
881
+
882
+ util.each(this.tags, function(i, t) {
883
+ if (t.idx === item.idx) {
884
+ tag = t;
885
+ }
886
+ }, this);
887
+
888
+ if (tag) {
889
+ this.label.removeChild(tag);
890
+ this.tags.splice(this.tags.indexOf(tag), 1);
891
+ }
892
+ };
893
+
894
+ /**
895
+ * Load the next page of items
896
+ * @return {void}
897
+ */
898
+ var load = function() {
899
+ var tree = this.tree;
900
+ var scrollTop = tree.scrollTop;
901
+ var scrollHeight = tree.scrollHeight;
902
+ var offsetHeight = tree.offsetHeight;
903
+ var atBottom = scrollTop >= (scrollHeight - offsetHeight);
904
+
905
+ if ((atBottom && this.pageIndex < this.pages.length)) {
906
+ var f = document.createDocumentFragment();
907
+
908
+ util.each(this.pages[this.pageIndex], function(i, item) {
909
+ appendItem(item, f, this.customOption);
910
+ }, this);
911
+
912
+ tree.appendChild(f);
913
+
914
+ this.pageIndex++;
915
+
916
+ this.emit("selectr.paginate", {
917
+ items: this.items.length,
918
+ total: this.data.length,
919
+ page: this.pageIndex,
920
+ pages: this.pages.length
921
+ });
922
+ }
923
+ };
924
+
925
+ /**
926
+ * Clear a search
927
+ * @return {void}
928
+ */
929
+ var clearSearch = function() {
930
+ if (this.config.searchable || this.config.taggable) {
931
+ this.input.value = null;
932
+ this.searching = false;
933
+ if (this.config.searchable) {
934
+ util.removeClass(this.inputContainer, "active");
935
+ }
936
+
937
+ if (util.hasClass(this.container, "notice")) {
938
+ util.removeClass(this.container, "notice");
939
+ util.addClass(this.container, "open");
940
+ this.input.focus();
941
+ }
942
+
943
+ util.each(this.items, function(i, item) {
944
+ // Items that didn't match need the class
945
+ // removing to make them visible again
946
+ util.removeClass(item, "excluded");
947
+ // Remove the span element for underlining matched items
948
+ if (!this.customOption) {
949
+ item.innerHTML = item.textContent;
950
+ }
951
+ }, this);
952
+ }
953
+ };
954
+
955
+ /**
956
+ * Query matching for searches
957
+ * @param {string} query
958
+ * @param {HTMLOptionElement} option
959
+ * @return {bool}
960
+ */
961
+ var match = function(query, option) {
962
+ var result = new RegExp(query, "i").exec(option.textContent);
963
+ if (result) {
964
+ return option.textContent.replace(result[0], "<span class='selectr-match'>" + result[0] + "</span>");
965
+ }
966
+ return false;
967
+ };
968
+
969
+ // Main Lib
970
+ var Selectr = function(el, config) {
971
+
972
+ config = config || {};
973
+
974
+ if (!el) {
975
+ throw new Error("You must supply either a HTMLSelectElement or a CSS3 selector string.");
976
+ }
977
+
978
+ this.el = el;
979
+
980
+ // CSS3 selector string
981
+ if (typeof el === "string") {
982
+ this.el = document.querySelector(el);
983
+ }
984
+
985
+ if (this.el === null) {
986
+ throw new Error("The element you passed to Selectr can not be found.");
987
+ }
988
+
989
+ if (this.el.nodeName.toLowerCase() !== "select") {
990
+ throw new Error("The element you passed to Selectr is not a HTMLSelectElement.");
991
+ }
992
+
993
+ this.render(config);
994
+ };
995
+
996
+ /**
997
+ * Render the instance
998
+ * @param {object} config
999
+ * @return {void}
1000
+ */
1001
+ Selectr.prototype.render = function(config) {
1002
+
1003
+ if (this.rendered) return;
1004
+
1005
+ // Merge defaults with user set config
1006
+ this.config = util.extend(defaultConfig, config);
1007
+
1008
+ // Store type
1009
+ this.originalType = this.el.type;
1010
+
1011
+ // Store tabIndex
1012
+ this.originalIndex = this.el.tabIndex;
1013
+
1014
+ // Store defaultSelected options for form reset
1015
+ this.defaultSelected = [];
1016
+
1017
+ // Store the original option count
1018
+ this.originalOptionCount = this.el.options.length;
1019
+
1020
+ if (this.config.multiple || this.config.taggable) {
1021
+ this.el.multiple = true;
1022
+ }
1023
+
1024
+ // Disabled?
1025
+ this.disabled = isset(this.config, "disabled");
1026
+
1027
+ this.opened = false;
1028
+
1029
+ if (this.config.taggable) {
1030
+ this.config.searchable = false;
1031
+ }
1032
+
1033
+ this.navigating = false;
1034
+
1035
+ this.mobileDevice = false;
1036
+ if (/Android|webOS|iPhone|iPad|BlackBerry|Windows Phone|Opera Mini|IEMobile|Mobile/i.test(navigator.userAgent)) {
1037
+ this.mobileDevice = true;
1038
+ }
1039
+
1040
+ this.customOption = this.config.hasOwnProperty("renderOption") && typeof this.config.renderOption === "function";
1041
+ this.customSelected = this.config.hasOwnProperty("renderSelection") && typeof this.config.renderSelection === "function";
1042
+
1043
+ // Enable event emitter
1044
+ Events.mixin(this);
1045
+
1046
+ build.call(this);
1047
+
1048
+ this.bindEvents();
1049
+
1050
+ this.update();
1051
+
1052
+ this.optsRect = util.rect(this.tree);
1053
+
1054
+ this.rendered = true;
1055
+
1056
+ // Fixes macOS Safari bug #28
1057
+ if (!this.el.multiple) {
1058
+ this.el.selectedIndex = this.selectedIndex;
1059
+ }
1060
+
1061
+ var that = this;
1062
+ setTimeout(function() {
1063
+ that.emit("selectr.init");
1064
+ }, 20);
1065
+ };
1066
+
1067
+ Selectr.prototype.getSelected = function () {
1068
+ var selected = this.el.querySelectorAll('option:checked');
1069
+ return selected;
1070
+ };
1071
+
1072
+ Selectr.prototype.getSelectedProperties = function (prop) {
1073
+ var selected = this.getSelected();
1074
+ var values = [].slice.call(selected)
1075
+ .map(function(option) { return option[prop]; })
1076
+ .filter(function(i) { return i!==null && i!==undefined; });
1077
+ return values;
1078
+ };
1079
+
1080
+ /**
1081
+ * Attach the required event listeners
1082
+ */
1083
+ Selectr.prototype.bindEvents = function() {
1084
+
1085
+ var that = this;
1086
+
1087
+ this.events = {};
1088
+
1089
+ this.events.dismiss = dismiss.bind(this);
1090
+ this.events.navigate = navigate.bind(this);
1091
+ this.events.reset = this.reset.bind(this);
1092
+
1093
+ if (this.config.nativeDropdown || this.mobileDevice) {
1094
+
1095
+ this.container.addEventListener("touchstart", function(e) {
1096
+ if (e.changedTouches[0].target === that.el) {
1097
+ that.toggle();
1098
+ }
1099
+ });
1100
+
1101
+ this.container.addEventListener("click", function(e) {
1102
+ if (e.target === that.el) {
1103
+ that.toggle();
1104
+ }
1105
+ });
1106
+
1107
+ var getChangedOptions = function(last, current) {
1108
+ var added=[], removed=last.slice(0);
1109
+ var idx;
1110
+ for (var i=0; i<current.length; i++) {
1111
+ idx = removed.indexOf(current[i]);
1112
+ if (idx > -1)
1113
+ removed.splice(idx, 1);
1114
+ else
1115
+ added.push(current[i]);
1116
+ }
1117
+ return [added, removed];
1118
+ };
1119
+
1120
+ // Listen for the change on the native select
1121
+ // and update accordingly
1122
+ this.el.addEventListener("change", function(e) {
1123
+ if (that.el.multiple) {
1124
+ var indexes = that.getSelectedProperties('idx');
1125
+ var changes = getChangedOptions(that.selectedIndexes, indexes);
1126
+
1127
+ util.each(changes[0], function(i, idx) {
1128
+ that.select(idx);
1129
+ }, that);
1130
+
1131
+ util.each(changes[1], function(i, idx) {
1132
+ that.deselect(idx);
1133
+ }, that);
1134
+
1135
+ } else {
1136
+ if (that.el.selectedIndex > -1) {
1137
+ that.select(that.el.selectedIndex);
1138
+ }
1139
+ }
1140
+ });
1141
+
1142
+ }
1143
+
1144
+ // Open the dropdown with Enter key if focused
1145
+ if (this.config.nativeDropdown) {
1146
+ this.container.addEventListener("keydown", function(e) {
1147
+ if (e.key === "Enter" && that.selected === document.activeElement) {
1148
+ // Show the native
1149
+ that.toggle();
1150
+
1151
+ // Focus on the native multiselect
1152
+ setTimeout(function() {
1153
+ that.el.focus();
1154
+ }, 200);
1155
+ }
1156
+ });
1157
+ }
1158
+
1159
+ // Non-native dropdown
1160
+ this.selected.addEventListener("click", function(e) {
1161
+
1162
+ if (!that.disabled) {
1163
+ that.toggle();
1164
+ }
1165
+
1166
+ e.stopPropagation();
1167
+ e.preventDefault();
1168
+ });
1169
+
1170
+ if ( this.config.nativeKeyboard ) {
1171
+ var typing = '';
1172
+ var typingTimeout = null;
1173
+
1174
+ this.selected.addEventListener("keydown", function (e) {
1175
+ // Do nothing if disabled, not focused, or modifier keys are pressed
1176
+ if (
1177
+ that.disabled ||
1178
+ that.selected !== document.activeElement ||
1179
+ (e.altKey || e.ctrlKey || e.metaKey)
1180
+ ) {
1181
+ return;
1182
+ }
1183
+
1184
+ // Open the dropdown on [enter], [ ], [↓], and [↑] keys
1185
+ if (
1186
+ e.key === " " ||
1187
+ (! that.opened && ["Enter", "ArrowUp", "ArrowDown"].indexOf(e.key) > -1)
1188
+ ) {
1189
+ that.toggle();
1190
+ e.preventDefault();
1191
+ e.stopPropagation();
1192
+ return;
1193
+ }
1194
+
1195
+ // Type to search if multiple; type to select otherwise
1196
+ // make sure e.key is a single, printable character
1197
+ // .length check is a short-circut to skip checking keys like "ArrowDown", etc.
1198
+ // prefer "codePoint" methods; they work with the full range of unicode
1199
+ if (
1200
+ e.key.length <= 2 &&
1201
+ String[String.fromCodePoint ? "fromCodePoint" : "fromCharCode"](
1202
+ e.key[String.codePointAt ? "codePointAt" : "charCodeAt"]( 0 )
1203
+ ) === e.key
1204
+ ) {
1205
+ if ( that.config.multiple ) {
1206
+ that.open();
1207
+ if ( that.config.searchable ) {
1208
+ that.input.value = e.key;
1209
+ that.input.focus();
1210
+ that.search( null, true );
1211
+ }
1212
+ } else {
1213
+ if ( typingTimeout ) {
1214
+ clearTimeout( typingTimeout );
1215
+ }
1216
+ typing += e.key;
1217
+ var found = that.search( typing, true );
1218
+ if ( found && found.length ) {
1219
+ that.clear();
1220
+ that.setValue( found[0].value );
1221
+ }
1222
+ setTimeout(function () { typing = ''; }, 1000);
1223
+ }
1224
+ e.preventDefault();
1225
+ e.stopPropagation();
1226
+ return;
1227
+ }
1228
+ });
1229
+
1230
+ // Close the dropdown on [esc] key
1231
+ this.container.addEventListener("keyup", function (e) {
1232
+ if ( that.opened && e.key === "Escape" ) {
1233
+ that.close();
1234
+ e.stopPropagation();
1235
+
1236
+ // keep focus so we can re-open easily if desired
1237
+ that.selected.focus();
1238
+ }
1239
+ });
1240
+ }
1241
+
1242
+ // Remove tag
1243
+ this.label.addEventListener("click", function(e) {
1244
+ if (util.hasClass(e.target, "selectr-tag-remove")) {
1245
+ that.deselect(e.target.parentNode.idx);
1246
+ }
1247
+ });
1248
+
1249
+ // Clear input
1250
+ if (this.selectClear) {
1251
+ this.selectClear.addEventListener("click", this.clear.bind(this));
1252
+ }
1253
+
1254
+ // Prevent text selection
1255
+ this.tree.addEventListener("mousedown", function(e) {
1256
+ e.preventDefault();
1257
+ });
1258
+
1259
+ // Select / deselect items
1260
+ this.tree.addEventListener("click", function(e) {
1261
+ var item = util.closest(e.target, function(el) {
1262
+ return el && util.hasClass(el, "selectr-option");
1263
+ });
1264
+
1265
+ if (item) {
1266
+ if (!util.hasClass(item, "disabled")) {
1267
+ if (util.hasClass(item, "selected")) {
1268
+ if (that.el.multiple || !that.el.multiple && that.config.allowDeselect) {
1269
+ that.deselect(item.idx);
1270
+ }
1271
+ } else {
1272
+ that.select(item.idx);
1273
+ }
1274
+
1275
+ if (that.opened && !that.el.multiple) {
1276
+ that.close();
1277
+ }
1278
+ }
1279
+ }
1280
+
1281
+ e.preventDefault();
1282
+ e.stopPropagation();
1283
+ });
1284
+
1285
+ // Mouseover list items
1286
+ this.tree.addEventListener("mouseover", function(e) {
1287
+ if (util.hasClass(e.target, "selectr-option")) {
1288
+ if (!util.hasClass(e.target, "disabled")) {
1289
+ util.removeClass(that.items[that.navIndex], "active");
1290
+
1291
+ util.addClass(e.target, "active");
1292
+
1293
+ that.navIndex = [].slice.call(that.items).indexOf(e.target);
1294
+ }
1295
+ }
1296
+ });
1297
+
1298
+ // Searchable
1299
+ if (this.config.searchable) {
1300
+ // Show / hide the search input clear button
1301
+
1302
+ this.input.addEventListener("focus", function(e) {
1303
+ that.searching = true;
1304
+ });
1305
+
1306
+ this.input.addEventListener("blur", function(e) {
1307
+ that.searching = false;
1308
+ });
1309
+
1310
+ this.input.addEventListener("keyup", function(e) {
1311
+ that.search();
1312
+
1313
+ if (!that.config.taggable) {
1314
+ // Show / hide the search input clear button
1315
+ if (this.value.length) {
1316
+ util.addClass(this.parentNode, "active");
1317
+ } else {
1318
+ util.removeClass(this.parentNode, "active");
1319
+ }
1320
+ }
1321
+ });
1322
+
1323
+ // Clear the search input
1324
+ this.inputClear.addEventListener("click", function(e) {
1325
+ that.input.value = null;
1326
+ clearSearch.call(that);
1327
+
1328
+ if (!that.tree.childElementCount) {
1329
+ render.call(that);
1330
+ }
1331
+ });
1332
+ }
1333
+
1334
+ if (this.config.taggable) {
1335
+ this.input.addEventListener("keyup", function(e) {
1336
+
1337
+ that.search();
1338
+
1339
+ if (that.config.taggable && this.value.length) {
1340
+ var val = this.value.trim();
1341
+
1342
+ if (e.which === 13 || util.includes(that.tagSeperators, e.key)) {
1343
+
1344
+ util.each(that.tagSeperators, function(i, k) {
1345
+ val = val.replace(k, '');
1346
+ });
1347
+
1348
+ var option = that.add({
1349
+ value: val,
1350
+ text: val,
1351
+ selected: true
1352
+ }, true);
1353
+
1354
+ if (!option) {
1355
+ this.value = '';
1356
+ that.setMessage('That tag is already in use.');
1357
+ } else {
1358
+ that.close();
1359
+ clearSearch.call(that);
1360
+ }
1361
+ }
1362
+ }
1363
+ });
1364
+ }
1365
+
1366
+ this.update = util.debounce(function() {
1367
+ // Optionally close dropdown on scroll / resize (#11)
1368
+ if (that.opened && that.config.closeOnScroll) {
1369
+ that.close();
1370
+ }
1371
+ if (that.width) {
1372
+ that.container.style.width = that.width;
1373
+ }
1374
+ that.invert();
1375
+ }, 50);
1376
+
1377
+ if (this.requiresPagination) {
1378
+ this.paginateItems = util.debounce(function() {
1379
+ load.call(this);
1380
+ }, 50);
1381
+
1382
+ this.tree.addEventListener("scroll", this.paginateItems.bind(this));
1383
+ }
1384
+
1385
+ // Dismiss when clicking outside the container
1386
+ document.addEventListener("click", this.events.dismiss);
1387
+ window.addEventListener("keydown", this.events.navigate);
1388
+
1389
+ window.addEventListener("resize", this.update);
1390
+ window.addEventListener("scroll", this.update);
1391
+
1392
+ // remove event listeners on destroy()
1393
+ this.on('selectr.destroy', function () {
1394
+ document.removeEventListener("click", this.events.dismiss);
1395
+ window.removeEventListener("keydown", this.events.navigate);
1396
+ window.removeEventListener("resize", this.update);
1397
+ window.removeEventListener("scroll", this.update);
1398
+ });
1399
+
1400
+ // Listen for form.reset() (@ambrooks, #13)
1401
+ if (this.el.form) {
1402
+ this.el.form.addEventListener("reset", this.events.reset);
1403
+
1404
+ // remove listener on destroy()
1405
+ this.on('selectr.destroy', function () {
1406
+ this.el.form.removeEventListener("reset", this.events.reset);
1407
+ });
1408
+ }
1409
+ };
1410
+
1411
+ /**
1412
+ * Check for selected options
1413
+ * @param {bool} reset
1414
+ */
1415
+ Selectr.prototype.setSelected = function(reset) {
1416
+
1417
+ // Select first option as with a native select-one element - #21, #24
1418
+ if (!this.config.data && !this.el.multiple && this.el.options.length) {
1419
+ // Browser has selected the first option by default
1420
+ if (this.el.selectedIndex === 0) {
1421
+ if (!this.el.options[0].defaultSelected && !this.config.defaultSelected) {
1422
+ this.el.selectedIndex = -1;
1423
+ }
1424
+ }
1425
+
1426
+ this.selectedIndex = this.el.selectedIndex;
1427
+
1428
+ if (this.selectedIndex > -1) {
1429
+ this.select(this.selectedIndex);
1430
+ }
1431
+ }
1432
+
1433
+ // If we're changing a select-one to select-multiple via the config
1434
+ // and there are no selected options, the first option will be selected by the browser
1435
+ // Let's prevent that here.
1436
+ if (this.config.multiple && this.originalType === "select-one" && !this.config.data) {
1437
+ if (this.el.options[0].selected && !this.el.options[0].defaultSelected) {
1438
+ this.el.options[0].selected = false;
1439
+ }
1440
+ }
1441
+
1442
+ util.each(this.options, function(i, option) {
1443
+ if (option.selected && option.defaultSelected) {
1444
+ this.select(option.idx);
1445
+ }
1446
+ }, this);
1447
+
1448
+ if (this.config.selectedValue) {
1449
+ this.setValue(this.config.selectedValue);
1450
+ }
1451
+
1452
+ if (this.config.data) {
1453
+
1454
+
1455
+ if (!this.el.multiple && this.config.defaultSelected && this.el.selectedIndex < 0) {
1456
+ this.select(0);
1457
+ }
1458
+
1459
+ var j = 0;
1460
+ util.each(this.config.data, function(i, opt) {
1461
+ // Check for group options
1462
+ if (isset(opt, "children")) {
1463
+ util.each(opt.children, function(x, item) {
1464
+ if (item.hasOwnProperty("selected") && item.selected === true) {
1465
+ this.select(j);
1466
+ }
1467
+ j++;
1468
+ }, this);
1469
+ } else {
1470
+ if (opt.hasOwnProperty("selected") && opt.selected === true) {
1471
+ this.select(j);
1472
+ }
1473
+ j++;
1474
+ }
1475
+ }, this);
1476
+ }
1477
+ };
1478
+
1479
+ /**
1480
+ * Destroy the instance
1481
+ * @return {void}
1482
+ */
1483
+ Selectr.prototype.destroy = function() {
1484
+
1485
+ if (!this.rendered) return;
1486
+
1487
+ this.emit("selectr.destroy");
1488
+
1489
+ // Revert to select-single if programtically set to multiple
1490
+ if (this.originalType === 'select-one') {
1491
+ this.el.multiple = false;
1492
+ }
1493
+
1494
+ if (this.config.data) {
1495
+ this.el.innerHTML = "";
1496
+ }
1497
+
1498
+ // Remove the className from select element
1499
+ util.removeClass(this.el, 'selectr-hidden');
1500
+
1501
+ // Replace the container with the original select element
1502
+ this.container.parentNode.replaceChild(this.el, this.container);
1503
+
1504
+ this.rendered = false;
1505
+ };
1506
+
1507
+ /**
1508
+ * Change an options state
1509
+ * @param {Number} index
1510
+ * @return {void}
1511
+ */
1512
+ Selectr.prototype.change = function(index) {
1513
+ var item = this.items[index],
1514
+ option = this.options[index];
1515
+
1516
+ if (option.disabled) {
1517
+ return;
1518
+ }
1519
+
1520
+ if (option.selected && util.hasClass(item, "selected")) {
1521
+ this.deselect(index);
1522
+ } else {
1523
+ this.select(index);
1524
+ }
1525
+
1526
+ if (this.opened && !this.el.multiple) {
1527
+ this.close();
1528
+ }
1529
+ };
1530
+
1531
+ /**
1532
+ * Select an option
1533
+ * @param {Number} index
1534
+ * @return {void}
1535
+ */
1536
+ Selectr.prototype.select = function(index) {
1537
+
1538
+ var item = this.items[index],
1539
+ options = [].slice.call(this.el.options),
1540
+ option = this.options[index];
1541
+
1542
+ if (this.el.multiple) {
1543
+ if (util.includes(this.selectedIndexes, index)) {
1544
+ return false;
1545
+ }
1546
+
1547
+ if (this.config.maxSelections && this.tags.length === this.config.maxSelections) {
1548
+ this.setMessage("A maximum of " + this.config.maxSelections + " items can be selected.", true);
1549
+ return false;
1550
+ }
1551
+
1552
+ this.selectedValues.push(option.value);
1553
+ this.selectedIndexes.push(index);
1554
+
1555
+ addTag.call(this, item);
1556
+ } else {
1557
+ var data = this.data ? this.data[index] : option;
1558
+ this.label.innerHTML = this.customSelected ? this.config.renderSelection(data) : option.textContent;
1559
+
1560
+ this.selectedValue = option.value;
1561
+ this.selectedIndex = index;
1562
+
1563
+ util.each(this.options, function(i, o) {
1564
+ var opt = this.items[i];
1565
+
1566
+ if (i !== index) {
1567
+ if (opt) {
1568
+ util.removeClass(opt, "selected");
1569
+ }
1570
+ o.selected = false;
1571
+ o.removeAttribute("selected");
1572
+ }
1573
+ }, this);
1574
+ }
1575
+
1576
+ if (!util.includes(options, option)) {
1577
+ this.el.add(option);
1578
+ }
1579
+
1580
+ item.setAttribute("aria-selected", true);
1581
+
1582
+ util.addClass(item, "selected");
1583
+ util.addClass(this.container, "has-selected");
1584
+
1585
+ option.selected = true;
1586
+ option.setAttribute("selected", "");
1587
+
1588
+ this.emit("selectr.change", option);
1589
+
1590
+ this.emit("selectr.select", option);
1591
+ };
1592
+
1593
+ /**
1594
+ * Deselect an option
1595
+ * @param {Number} index
1596
+ * @return {void}
1597
+ */
1598
+ Selectr.prototype.deselect = function(index, force) {
1599
+ var item = this.items[index],
1600
+ option = this.options[index];
1601
+
1602
+ if (this.el.multiple) {
1603
+ var selIndex = this.selectedIndexes.indexOf(index);
1604
+ this.selectedIndexes.splice(selIndex, 1);
1605
+
1606
+ var valIndex = this.selectedValues.indexOf(option.value);
1607
+ this.selectedValues.splice(valIndex, 1);
1608
+
1609
+ removeTag.call(this, item);
1610
+
1611
+ if (!this.tags.length) {
1612
+ util.removeClass(this.container, "has-selected");
1613
+ }
1614
+ } else {
1615
+
1616
+ if (!force && !this.config.clearable && !this.config.allowDeselect) {
1617
+ return false;
1618
+ }
1619
+
1620
+ this.label.innerHTML = "";
1621
+ this.selectedValue = null;
1622
+
1623
+ this.el.selectedIndex = this.selectedIndex = -1;
1624
+
1625
+ util.removeClass(this.container, "has-selected");
1626
+ }
1627
+
1628
+
1629
+ this.items[index].setAttribute("aria-selected", false);
1630
+
1631
+ util.removeClass(this.items[index], "selected");
1632
+
1633
+ option.selected = false;
1634
+
1635
+ option.removeAttribute("selected");
1636
+
1637
+ this.emit("selectr.change", null);
1638
+
1639
+ this.emit("selectr.deselect", option);
1640
+ };
1641
+
1642
+ /**
1643
+ * Programmatically set selected values
1644
+ * @param {String|Array} value - A string or an array of strings
1645
+ */
1646
+ Selectr.prototype.setValue = function(value) {
1647
+ var isArray = Array.isArray(value);
1648
+
1649
+ if (!isArray) {
1650
+ value = value.toString().trim();
1651
+ }
1652
+
1653
+ // Can't pass array to select-one
1654
+ if (!this.el.multiple && isArray) {
1655
+ return false;
1656
+ }
1657
+
1658
+ util.each(this.options, function(i, option) {
1659
+ if (isArray && util.includes(value.toString(), option.value) || option.value === value) {
1660
+ this.change(option.idx);
1661
+ }
1662
+ }, this);
1663
+ };
1664
+
1665
+ /**
1666
+ * Set the selected value(s)
1667
+ * @param {bool} toObject Return only the raw values or an object
1668
+ * @param {bool} toJson Return the object as a JSON string
1669
+ * @return {mixed} Array or String
1670
+ */
1671
+ Selectr.prototype.getValue = function(toObject, toJson) {
1672
+ var value;
1673
+
1674
+ if (this.el.multiple) {
1675
+ if (toObject) {
1676
+ if (this.selectedIndexes.length) {
1677
+ value = {};
1678
+ value.values = [];
1679
+ util.each(this.selectedIndexes, function(i, index) {
1680
+ var option = this.options[index];
1681
+ value.values[i] = {
1682
+ value: option.value,
1683
+ text: option.textContent
1684
+ };
1685
+ }, this);
1686
+ }
1687
+ } else {
1688
+ value = this.selectedValues.slice();
1689
+ }
1690
+ } else {
1691
+ if (toObject) {
1692
+ var option = this.options[this.selectedIndex];
1693
+ value = {
1694
+ value: option.value,
1695
+ text: option.textContent
1696
+ };
1697
+ } else {
1698
+ value = this.selectedValue;
1699
+ }
1700
+ }
1701
+
1702
+ if (toObject && toJson) {
1703
+ value = JSON.stringify(value);
1704
+ }
1705
+
1706
+ return value;
1707
+ };
1708
+
1709
+ /**
1710
+ * Add a new option or options
1711
+ * @param {object} data
1712
+ */
1713
+ Selectr.prototype.add = function(data, checkDuplicate) {
1714
+ if (data) {
1715
+
1716
+ this.data = this.data || [];
1717
+ this.items = this.items || [];
1718
+ this.options = this.options || [];
1719
+
1720
+ if (Array.isArray(data)) {
1721
+ // We have an array on items
1722
+ util.each(data, function(i, obj) {
1723
+ this.add(obj, checkDuplicate);
1724
+ }, this);
1725
+ }
1726
+ // User passed a single object to the method
1727
+ // or Selectr passed an object from an array
1728
+ else if ("[object Object]" === Object.prototype.toString.call(data)) {
1729
+
1730
+ if (checkDuplicate) {
1731
+ var dupe = false;
1732
+
1733
+ util.each(this.options, function(i, option) {
1734
+ if (option.value.toLowerCase() === data.value.toLowerCase()) {
1735
+ dupe = true;
1736
+ }
1737
+ });
1738
+
1739
+ if (dupe) {
1740
+ return false;
1741
+ }
1742
+ }
1743
+
1744
+ var option = util.createElement('option', data);
1745
+
1746
+ this.data.push(data);
1747
+
1748
+ // Add the new option to the list
1749
+ this.options.push(option);
1750
+
1751
+ // Add the index for later use
1752
+ option.idx = this.options.length > 0 ? this.options.length - 1 : 0;
1753
+
1754
+ // Create a new item
1755
+ createItem.call(this, option);
1756
+
1757
+ // Select the item if required
1758
+ if (data.selected) {
1759
+ this.select(option.idx);
1760
+ }
1761
+
1762
+ return option;
1763
+ }
1764
+
1765
+ // We may have had an empty select so update
1766
+ // the placeholder to reflect the changes.
1767
+ this.setPlaceholder();
1768
+
1769
+ // Recount the pages
1770
+ if (this.config.pagination) {
1771
+ this.paginate();
1772
+ }
1773
+
1774
+ return true;
1775
+ }
1776
+ };
1777
+
1778
+ /**
1779
+ * Remove an option or options
1780
+ * @param {Mixed} o Array, integer (index) or string (value)
1781
+ * @return {Void}
1782
+ */
1783
+ Selectr.prototype.remove = function(o) {
1784
+ var options = [];
1785
+ if (Array.isArray(o)) {
1786
+ util.each(o, function(i, opt) {
1787
+ if (util.isInt(opt)) {
1788
+ options.push(this.getOptionByIndex(opt));
1789
+ } else if (typeof o === "string") {
1790
+ options.push(this.getOptionByValue(opt));
1791
+ }
1792
+ }, this);
1793
+
1794
+ } else if (util.isInt(o)) {
1795
+ options.push(this.getOptionByIndex(o));
1796
+ } else if (typeof o === "string") {
1797
+ options.push(this.getOptionByValue(o));
1798
+ }
1799
+
1800
+ if (options.length) {
1801
+ var index;
1802
+ util.each(options, function(i, option) {
1803
+ index = option.idx;
1804
+
1805
+ // Remove the HTMLOptionElement
1806
+ this.el.remove(option);
1807
+
1808
+ // Remove the reference from the option array
1809
+ this.options.splice(index, 1);
1810
+
1811
+ // If the item has a parentNode (group element) it needs to be removed
1812
+ // otherwise the render function will still append it to the dropdown
1813
+ var parentNode = this.items[index].parentNode;
1814
+
1815
+ if (parentNode) {
1816
+ parentNode.removeChild(this.items[index]);
1817
+ }
1818
+
1819
+ // Remove reference from the items array
1820
+ this.items.splice(index, 1);
1821
+
1822
+ // Reset the indexes
1823
+ util.each(this.options, function(i, opt) {
1824
+ opt.idx = i;
1825
+ this.items[i].idx = i;
1826
+ }, this);
1827
+ }, this);
1828
+
1829
+ // We may have had an empty select now so update
1830
+ // the placeholder to reflect the changes.
1831
+ this.setPlaceholder();
1832
+
1833
+ // Recount the pages
1834
+ if (this.config.pagination) {
1835
+ this.paginate();
1836
+ }
1837
+ }
1838
+ };
1839
+
1840
+ /**
1841
+ * Remove all options
1842
+ */
1843
+ Selectr.prototype.removeAll = function() {
1844
+
1845
+ // Clear any selected options
1846
+ this.clear(true);
1847
+
1848
+ // Remove the HTMLOptionElements
1849
+ util.each(this.el.options, function(i, option) {
1850
+ this.el.remove(option);
1851
+ }, this);
1852
+
1853
+ // Empty the dropdown
1854
+ util.truncate(this.tree);
1855
+
1856
+ // Reset variables
1857
+ this.items = [];
1858
+ this.options = [];
1859
+ this.data = [];
1860
+
1861
+ this.navIndex = 0;
1862
+
1863
+ if (this.requiresPagination) {
1864
+ this.requiresPagination = false;
1865
+
1866
+ this.pageIndex = 1;
1867
+ this.pages = [];
1868
+ }
1869
+
1870
+ // Update the placeholder
1871
+ this.setPlaceholder();
1872
+ };
1873
+
1874
+ /**
1875
+ * Perform a search
1876
+ * @param {string}|{null} query The query string (taken from user input if null)
1877
+ * @param {boolean} anchor Anchor search to beginning of strings (defaults to false)?
1878
+ * @return {Array} Search results, as an array of {text, value} objects
1879
+ */
1880
+ Selectr.prototype.search = function( string, anchor ) {
1881
+ if ( this.navigating ) {
1882
+ return;
1883
+ }
1884
+
1885
+ // we're only going to alter the DOM for "live" searches
1886
+ var live = false;
1887
+ if ( ! string ) {
1888
+ string = this.input.value;
1889
+ live = true;
1890
+
1891
+ // Remove message and clear dropdown
1892
+ this.removeMessage();
1893
+ util.truncate(this.tree);
1894
+ }
1895
+ var results = [];
1896
+ var f = document.createDocumentFragment();
1897
+
1898
+ string = string.trim().toLowerCase();
1899
+
1900
+ if ( string.length > 0 ) {
1901
+ var compare = anchor ? util.startsWith : util.includes;
1902
+
1903
+ util.each( this.options, function ( i, option ) {
1904
+ var item = this.items[option.idx];
1905
+ var matches = compare( option.textContent.trim().toLowerCase(), string );
1906
+
1907
+ if ( matches && !option.disabled ) {
1908
+ results.push( { text: option.textContent, value: option.value } );
1909
+ if ( live ) {
1910
+ appendItem( item, f, this.customOption );
1911
+ util.removeClass( item, "excluded" );
1912
+
1913
+ // Underline the matching results
1914
+ if ( !this.customOption ) {
1915
+ item.innerHTML = match( string, option );
1916
+ }
1917
+ }
1918
+ } else if ( live ) {
1919
+ util.addClass( item, "excluded" );
1920
+ }
1921
+ }, this);
1922
+
1923
+ if ( live ) {
1924
+ // Append results
1925
+ if ( !f.childElementCount ) {
1926
+ if ( !this.config.taggable ) {
1927
+ this.setMessage( "no results." );
1928
+ }
1929
+ } else {
1930
+ // Highlight top result (@binary-koan #26)
1931
+ var prevEl = this.items[this.navIndex];
1932
+ var firstEl = f.querySelector(".selectr-option:not(.excluded)");
1933
+
1934
+ util.removeClass( prevEl, "active" );
1935
+ this.navIndex = firstEl.idx;
1936
+ util.addClass( firstEl, "active" );
1937
+ }
1938
+
1939
+ this.tree.appendChild( f );
1940
+ }
1941
+ } else {
1942
+ render.call(this);
1943
+ }
1944
+
1945
+ return results;
1946
+ };
1947
+
1948
+ /**
1949
+ * Toggle the dropdown
1950
+ * @return {void}
1951
+ */
1952
+ Selectr.prototype.toggle = function() {
1953
+ if (!this.disabled) {
1954
+ if (this.opened) {
1955
+ this.close();
1956
+ } else {
1957
+ this.open();
1958
+ }
1959
+ }
1960
+ };
1961
+
1962
+ /**
1963
+ * Open the dropdown
1964
+ * @return {void}
1965
+ */
1966
+ Selectr.prototype.open = function() {
1967
+
1968
+ var that = this;
1969
+
1970
+ if (!this.options.length) {
1971
+ return false;
1972
+ }
1973
+
1974
+ if (!this.opened) {
1975
+ this.emit("selectr.open");
1976
+ }
1977
+
1978
+ this.opened = true;
1979
+
1980
+ if (this.mobileDevice || this.config.nativeDropdown) {
1981
+ util.addClass(this.container, "native-open");
1982
+
1983
+ if (this.config.data) {
1984
+ // Dump the options into the select
1985
+ // otherwise the native dropdown will be empty
1986
+ util.each(this.options, function(i, option) {
1987
+ this.el.add(option);
1988
+ }, this);
1989
+ }
1990
+
1991
+ return;
1992
+ }
1993
+
1994
+ util.addClass(this.container, "open");
1995
+
1996
+ render.call(this);
1997
+
1998
+ this.invert();
1999
+
2000
+ this.tree.scrollTop = 0;
2001
+
2002
+ util.removeClass(this.container, "notice");
2003
+
2004
+ this.selected.setAttribute("aria-expanded", true);
2005
+
2006
+ this.tree.setAttribute("aria-hidden", false);
2007
+ this.tree.setAttribute("aria-expanded", true);
2008
+
2009
+ if (this.config.searchable && !this.config.taggable) {
2010
+ setTimeout(function() {
2011
+ that.input.focus();
2012
+ // Allow tab focus
2013
+ that.input.tabIndex = 0;
2014
+ }, 10);
2015
+ }
2016
+ };
2017
+
2018
+ /**
2019
+ * Close the dropdown
2020
+ * @return {void}
2021
+ */
2022
+ Selectr.prototype.close = function() {
2023
+
2024
+ if (this.opened) {
2025
+ this.emit("selectr.close");
2026
+ }
2027
+
2028
+ this.opened = false;
2029
+ this.navigating = false;
2030
+
2031
+ if (this.mobileDevice || this.config.nativeDropdown) {
2032
+ util.removeClass(this.container, "native-open");
2033
+ return;
2034
+ }
2035
+
2036
+ var notice = util.hasClass(this.container, "notice");
2037
+
2038
+ if (this.config.searchable && !notice) {
2039
+ this.input.blur();
2040
+ // Disable tab focus
2041
+ this.input.tabIndex = -1;
2042
+ this.searching = false;
2043
+ }
2044
+
2045
+ if (notice) {
2046
+ util.removeClass(this.container, "notice");
2047
+ this.notice.textContent = "";
2048
+ }
2049
+
2050
+ util.removeClass(this.container, "open");
2051
+ util.removeClass(this.container, "native-open");
2052
+
2053
+ this.selected.setAttribute("aria-expanded", false);
2054
+
2055
+ this.tree.setAttribute("aria-hidden", true);
2056
+ this.tree.setAttribute("aria-expanded", false);
2057
+
2058
+ util.truncate(this.tree);
2059
+ clearSearch.call(this);
2060
+ };
2061
+
2062
+
2063
+ /**
2064
+ * Enable the element
2065
+ * @return {void}
2066
+ */
2067
+ Selectr.prototype.enable = function() {
2068
+ this.disabled = false;
2069
+ this.el.disabled = false;
2070
+
2071
+ this.selected.tabIndex = this.originalIndex;
2072
+
2073
+ if (this.el.multiple) {
2074
+ util.each(this.tags, function(i, t) {
2075
+ t.lastElementChild.tabIndex = 0;
2076
+ });
2077
+ }
2078
+
2079
+ util.removeClass(this.container, "selectr-disabled");
2080
+ };
2081
+
2082
+ /**
2083
+ * Disable the element
2084
+ * @param {boolean} container Disable the container only (allow value submit with form)
2085
+ * @return {void}
2086
+ */
2087
+ Selectr.prototype.disable = function(container) {
2088
+ if (!container) {
2089
+ this.el.disabled = true;
2090
+ }
2091
+
2092
+ this.selected.tabIndex = -1;
2093
+
2094
+ if (this.el.multiple) {
2095
+ util.each(this.tags, function(i, t) {
2096
+ t.lastElementChild.tabIndex = -1;
2097
+ });
2098
+ }
2099
+
2100
+ this.disabled = true;
2101
+ util.addClass(this.container, "selectr-disabled");
2102
+ };
2103
+
2104
+
2105
+ /**
2106
+ * Reset to initial state
2107
+ * @return {void}
2108
+ */
2109
+ Selectr.prototype.reset = function() {
2110
+ if (!this.disabled) {
2111
+ this.clear();
2112
+
2113
+ this.setSelected(true);
2114
+
2115
+ util.each(this.defaultSelected, function(i, idx) {
2116
+ this.select(idx);
2117
+ }, this);
2118
+
2119
+ this.emit("selectr.reset");
2120
+ }
2121
+ };
2122
+
2123
+ /**
2124
+ * Clear all selections
2125
+ * @return {void}
2126
+ */
2127
+ Selectr.prototype.clear = function(force) {
2128
+
2129
+ if (this.el.multiple) {
2130
+ // Loop over the selectedIndexes so we don't have to loop over all the options
2131
+ // which can be costly if there are a lot of them
2132
+
2133
+ if (this.selectedIndexes.length) {
2134
+ // Copy the array or we'll get an error
2135
+ var indexes = this.selectedIndexes.slice();
2136
+
2137
+ util.each(indexes, function(i, idx) {
2138
+ this.deselect(idx);
2139
+ }, this);
2140
+ }
2141
+ } else {
2142
+ if (this.selectedIndex > -1) {
2143
+ this.deselect(this.selectedIndex, force);
2144
+ }
2145
+ }
2146
+
2147
+ this.emit("selectr.clear");
2148
+ };
2149
+
2150
+ /**
2151
+ * Return serialised data
2152
+ * @param {boolean} toJson
2153
+ * @return {mixed} Returns either an object or JSON string
2154
+ */
2155
+ Selectr.prototype.serialise = function(toJson) {
2156
+ var data = [];
2157
+ util.each(this.options, function(i, option) {
2158
+ var obj = {
2159
+ value: option.value,
2160
+ text: option.textContent
2161
+ };
2162
+
2163
+ if (option.selected) {
2164
+ obj.selected = true;
2165
+ }
2166
+ if (option.disabled) {
2167
+ obj.disabled = true;
2168
+ }
2169
+ data[i] = obj;
2170
+ });
2171
+
2172
+ return toJson ? JSON.stringify(data) : data;
2173
+ };
2174
+
2175
+ /**
2176
+ * Localised version of serialise() method
2177
+ */
2178
+ Selectr.prototype.serialize = function(toJson) {
2179
+ return this.serialise(toJson);
2180
+ };
2181
+
2182
+ /**
2183
+ * Sets the placeholder
2184
+ * @param {String} placeholder
2185
+ */
2186
+ Selectr.prototype.setPlaceholder = function(placeholder) {
2187
+ // Set the placeholder
2188
+ placeholder = placeholder || this.config.placeholder || this.el.getAttribute("placeholder");
2189
+
2190
+ if (!this.options.length) {
2191
+ placeholder = "No options available";
2192
+ }
2193
+
2194
+ this.placeEl.innerHTML = placeholder;
2195
+ };
2196
+
2197
+ /**
2198
+ * Paginate the option list
2199
+ * @return {Array}
2200
+ */
2201
+ Selectr.prototype.paginate = function() {
2202
+ if (this.items.length) {
2203
+ var that = this;
2204
+
2205
+ this.pages = this.items.map(function(v, i) {
2206
+ return i % that.config.pagination === 0 ? that.items.slice(i, i + that.config.pagination) : null;
2207
+ }).filter(function(pages) {
2208
+ return pages;
2209
+ });
2210
+
2211
+ return this.pages;
2212
+ }
2213
+ };
2214
+
2215
+ /**
2216
+ * Display a message
2217
+ * @param {String} message The message
2218
+ */
2219
+ Selectr.prototype.setMessage = function(message, close) {
2220
+ if (close) {
2221
+ this.close();
2222
+ }
2223
+ util.addClass(this.container, "notice");
2224
+ this.notice.textContent = message;
2225
+ };
2226
+
2227
+ /**
2228
+ * Dismiss the current message
2229
+ */
2230
+ Selectr.prototype.removeMessage = function() {
2231
+ util.removeClass(this.container, "notice");
2232
+ this.notice.innerHTML = "";
2233
+ };
2234
+
2235
+ /**
2236
+ * Keep the dropdown within the window
2237
+ * @return {void}
2238
+ */
2239
+ Selectr.prototype.invert = function() {
2240
+ var rt = util.rect(this.selected),
2241
+ oh = this.tree.parentNode.offsetHeight,
2242
+ wh = window.innerHeight,
2243
+ doInvert = rt.top + rt.height + oh > wh;
2244
+
2245
+ if (doInvert) {
2246
+ util.addClass(this.container, "inverted");
2247
+ this.isInverted = true;
2248
+ } else {
2249
+ util.removeClass(this.container, "inverted");
2250
+ this.isInverted = false;
2251
+ }
2252
+
2253
+ this.optsRect = util.rect(this.tree);
2254
+ };
2255
+
2256
+ /**
2257
+ * Get an option via it's index
2258
+ * @param {Integer} index The index of the HTMLOptionElement required
2259
+ * @return {HTMLOptionElement}
2260
+ */
2261
+ Selectr.prototype.getOptionByIndex = function(index) {
2262
+ return this.options[index];
2263
+ };
2264
+
2265
+ /**
2266
+ * Get an option via it's value
2267
+ * @param {String} value The value of the HTMLOptionElement required
2268
+ * @return {HTMLOptionElement}
2269
+ */
2270
+ Selectr.prototype.getOptionByValue = function(value) {
2271
+ var option = false;
2272
+
2273
+ for (var i = 0, l = this.options.length; i < l; i++) {
2274
+ if (this.options[i].value.trim() === value.toString().trim()) {
2275
+ option = this.options[i];
2276
+ break;
2277
+ }
2278
+ }
2279
+
2280
+ return option;
2281
+ };
2282
+
2283
+ return Selectr;
2284
+ }));