selectr-rails 2.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
+ }));