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.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.travis.yml +5 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +35 -0
- data/LICENSE.txt +21 -0
- data/README.md +50 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/selectr-rails.rb +6 -0
- data/lib/selectr/version.rb +3 -0
- data/selectr-rails.gemspec +27 -0
- data/vendor/assets/javascripts/selectr.js +2284 -0
- data/vendor/assets/stylesheets/selectr.css +472 -0
- metadata +101 -0
checksums.yaml
ADDED
|
@@ -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
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
|
@@ -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
|
data/LICENSE.txt
ADDED
|
@@ -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.
|
data/README.md
ADDED
|
@@ -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).
|
data/Rakefile
ADDED
data/bin/console
ADDED
|
@@ -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__)
|
data/bin/setup
ADDED
|
@@ -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
|
+
}));
|