uber_select_rails 0.6.0 → 1.0.0
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 +4 -4
- data/lib/uber_select_rails/version.rb +1 -1
- data/vendor/assets/javascript/uber_select/README.md +28 -7
- data/vendor/assets/javascript/uber_select/javascript/jquery.uber-select.js +12 -6
- data/vendor/assets/javascript/uber_select/javascript/uber_search/list.js +160 -0
- data/vendor/assets/javascript/uber_select/javascript/uber_search/output_container.js +29 -0
- data/vendor/assets/javascript/uber_select/javascript/uber_search/pane.js +93 -0
- data/vendor/assets/javascript/uber_select/javascript/uber_search/search.js +77 -0
- data/vendor/assets/javascript/uber_select/javascript/uber_search/search_field.js +96 -0
- data/vendor/assets/javascript/uber_select/javascript/{search.js → uber_search/search_model.js} +11 -69
- data/vendor/assets/javascript/uber_select/javascript/uber_search.js +381 -296
- data/vendor/assets/javascript/uber_select/test.css +1 -0
- data/vendor/assets/javascript/uber_select/test.html +26 -8
- data/vendor/assets/javascript/uber_select.js +1 -1
- metadata +9 -13
- data/vendor/assets/javascript/uber_select/javascript/list.js +0 -122
- data/vendor/assets/javascript/uber_select/javascript/output_container.js +0 -25
- data/vendor/assets/javascript/uber_select/javascript/pane.js +0 -100
- data/vendor/assets/javascript/uber_select/javascript/search_field.js +0 -84
- data/vendor/assets/javascript/uber_select/javascript/string_extensions.js +0 -8
@@ -1,371 +1,456 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
value: null, // Initialize with this selectedValue
|
12
|
-
disabled: false, // Initialize with this disabled value
|
13
|
-
search: true, // Show the search input
|
14
|
-
clearSearchButton:'✕', // Text content of clear search button
|
15
|
-
selectCaret: '⌄', // Text content of select caret
|
16
|
-
hideBlankOption: false, // Should blank options be hidden automatically?
|
17
|
-
treatBlankOptionAsPlaceholder: false, // Should blank options use the placeholder as text?
|
18
|
-
highlightByDefault: true, // Should the first result be auto-highlighted?
|
19
|
-
minQueryLength: 0, // Number of characters to type before results are displayed
|
20
|
-
minQueryMessage: true, // Message to show when the query doesn't exceed the minimum length. True for default, false for none, or custom message.
|
21
|
-
placeholder: null, // Placeholder to show in the selected text area
|
22
|
-
searchPlaceholder: 'Type to search', // Placeholder to show in the search input
|
23
|
-
noResultsText: 'No Matches Found', // The message shown when there are no results
|
24
|
-
resultPostprocessor: function(result, datum){}, // A function that is run after a result is built and can be used to decorate it
|
25
|
-
buildResult: null, // A function that is used to build result elements
|
26
|
-
outputContainer: null, // An object that receives the output once a result is selected. Must respond to setValue(value), and view()
|
27
|
-
onRender: function(resultsContainer, result) {}, // A function to run when the results container is rendered. If the result returns false, the default select handler is not run and the event is cancelled
|
28
|
-
onSelect: function(datum, result, clickEvent) {}, // A function to run when a result is selected. If the result returns false, the default select handler is not run and the event is cancelled
|
29
|
-
onNoHighlightSubmit: function(value) {}, // A function to run when a user presses enter without selecting a result.
|
30
|
-
noDataText: 'No options' // Text to show in there is nothing in the set of data to pick from
|
31
|
-
}, options)
|
32
|
-
|
33
|
-
var context = this
|
34
|
-
var view = $('<span class="uber_select" role="listbox"></span>')
|
35
|
-
var selectedValue = options.value // Internally selected value
|
36
|
-
var outputContainer = options.outputContainer || new OutputContainer({selectCaret: options.selectCaret})
|
37
|
-
var resultsContainer = $('<div class="results_container"></div>')
|
38
|
-
var messages = $('<div class="messages"></div>')
|
39
|
-
var pane = new Pane()
|
40
|
-
|
41
|
-
if (options.ariaLabel) { view.attr("aria-label", options.ariaLabel) }
|
42
|
-
|
43
|
-
var searchField = new SearchField({
|
44
|
-
placeholder: options.searchPlaceholder,
|
45
|
-
clearButton: options.clearSearchButton,
|
46
|
-
searchInputAttributes: options.searchInputAttributes
|
47
|
-
})
|
48
|
-
|
49
|
-
var search = new Search(searchField.input, resultsContainer, {
|
50
|
-
model: {
|
51
|
-
dataForMatching: dataForMatching,
|
52
|
-
minQueryLength: options.minQueryLength,
|
53
|
-
queryPreprocessor: options.queryPreprocessor || Search.prototype.queryPreprocessor,
|
54
|
-
datumPreprocessor: options.datumPreprocessor || datumPreprocessor,
|
55
|
-
patternForMatching: options.patternForMatching || Search.prototype.patternForMatching
|
56
|
-
},
|
57
|
-
view: {
|
58
|
-
renderResults: renderResults,
|
59
|
-
buildResult: options.buildResult || buildResult,
|
60
|
-
keypressInput: options.search ? searchField.input : null
|
1
|
+
//= require_self
|
2
|
+
//= require_tree ./uber_search
|
3
|
+
|
4
|
+
(function($) {
|
5
|
+
window.UberSearch = function(data, options){
|
6
|
+
var eventsTriggered = {
|
7
|
+
shown: 'shown',
|
8
|
+
renderedResults: 'renderedResults',
|
9
|
+
clear: 'clear',
|
10
|
+
select: 'select'
|
61
11
|
}
|
62
|
-
})
|
63
12
|
|
13
|
+
options = $.extend({
|
14
|
+
uberSelectId: generateUUID(), // A unique identifier for select
|
15
|
+
ariaLabel: null, // Label of the select for screen readers
|
16
|
+
value: null, // Initialize with this selectedValue
|
17
|
+
disabled: false, // Initialize with this disabled value
|
18
|
+
search: true, // Show the search input
|
19
|
+
clearSearchButton:'✕', // Text content of clear search button
|
20
|
+
selectCaret: '⌄', // Text content of select caret
|
21
|
+
hideBlankOption: false, // Should blank options be hidden automatically?
|
22
|
+
treatBlankOptionAsPlaceholder: false, // Should blank options use the placeholder as text?
|
23
|
+
highlightByDefault: true, // Should the first result be auto-highlighted?
|
24
|
+
minQueryLength: 0, // Number of characters to type before results are displayed
|
25
|
+
minQueryMessage: true, // Message to show when the query doesn't exceed the minimum length. True for default, false for none, or custom message.
|
26
|
+
placeholder: null, // Placeholder to show in the selected text area
|
27
|
+
searchPlaceholder: 'Type to search', // Placeholder to show in the search input
|
28
|
+
noResultsText: 'No Matches Found', // The message shown when there are no results
|
29
|
+
resultPostprocessor: function(result, datum){}, // A function that is run after a result is built and can be used to decorate it
|
30
|
+
buildResult: null, // A function that is used to build result elements
|
31
|
+
outputContainer: null, // An object that receives the output once a result is selected. Must respond to setValue(value), and view()
|
32
|
+
onRender: function(resultsContainer, result) {}, // A function to run when the results container is rendered. If the result returns false, the default render handler is not run and the event is cancelled
|
33
|
+
onSelect: function(datum, result, clickEvent) {}, // A function to run when a result is selected. If the result returns false, the default select handler is not run and the event is cancelled
|
34
|
+
onNoHighlightSubmit: function(value) {}, // A function to run when a user presses enter without selecting a result.
|
35
|
+
noDataText: 'No options', // Text to show in there is nothing in the set of data to pick from
|
36
|
+
matchGroupNames: false, // Show results for searches that match the result's group name
|
37
|
+
alwaysOpen: false // Should the options list always appear open?
|
38
|
+
}, options)
|
39
|
+
|
40
|
+
var context = this
|
41
|
+
var view = $('<span>', { class: "uber_select", id: options.uberSelectId })
|
42
|
+
var selectedValue = options.value // Internally selected value
|
43
|
+
var outputContainer = options.outputContainer || new UberSearch.OutputContainer({selectCaret: options.selectCaret, ariaLabel: options.ariaLabel})
|
44
|
+
var resultsContainer = $('<div class="results_container"></div>')
|
45
|
+
var messages = $('<div class="messages"></div>')
|
46
|
+
var pane = new UberSearch.Pane()
|
47
|
+
|
48
|
+
var searchField = new UberSearch.SearchField({
|
49
|
+
placeholder: options.searchPlaceholder,
|
50
|
+
clearButton: options.clearSearchButton,
|
51
|
+
searchInputAttributes: options.searchInputAttributes
|
52
|
+
})
|
64
53
|
|
65
|
-
|
54
|
+
var search = new UberSearch.Search(searchField.input, resultsContainer, {
|
55
|
+
model: {
|
56
|
+
dataForMatching: dataForMatching,
|
57
|
+
minQueryLength: options.minQueryLength,
|
58
|
+
queryPreprocessor: options.queryPreprocessor || UberSearch.Search.prototype.queryPreprocessor,
|
59
|
+
datumPreprocessor: options.datumPreprocessor || datumPreprocessor,
|
60
|
+
patternForMatching: options.patternForMatching || UberSearch.Search.prototype.patternForMatching
|
61
|
+
},
|
62
|
+
view: {
|
63
|
+
renderResults: renderResults,
|
64
|
+
buildResult: options.buildResult || buildResult,
|
65
|
+
}
|
66
|
+
})
|
66
67
|
|
67
|
-
// Show the pane when the select element is clicked
|
68
|
-
$(outputContainer.view).on('click', function(event){
|
69
|
-
if (outputContainer.view.hasClass('disabled')) { return }
|
70
68
|
|
71
|
-
|
72
|
-
})
|
69
|
+
// BEHAVIOUR
|
73
70
|
|
74
|
-
|
75
|
-
|
76
|
-
|
71
|
+
// Hide the pane when clicked out or another pane is opened
|
72
|
+
$(document).on('click shown', function(event){
|
73
|
+
if (!options.alwaysOpen && pane.isOpen() && isEventOutside(event)){
|
74
|
+
pane.hide()
|
75
|
+
}
|
76
|
+
})
|
77
77
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
return false
|
85
|
-
}
|
86
|
-
})
|
78
|
+
// Hide the pane when tabbing away from view
|
79
|
+
$(view).on('keydown', function(event){
|
80
|
+
if (!options.alwaysOpen && pane.isOpen() && event.which === 9) {
|
81
|
+
pane.hide()
|
82
|
+
}
|
83
|
+
})
|
87
84
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
85
|
+
$(view).on('setHighlight', function(event, result, index) {
|
86
|
+
if (index < 0 && options.search) {
|
87
|
+
setOutputContainerAria("aria-activedescendant", "")
|
88
|
+
$(searchField.input).focus()
|
89
|
+
} else if (index < 0) {
|
90
|
+
setOutputContainerAria("aria-activedescendant", "")
|
91
|
+
$(outputContainer.view).focus()
|
92
|
+
} else {
|
93
|
+
setOutputContainerAria("aria-activedescendant", result.id)
|
94
|
+
}
|
95
|
+
})
|
93
96
|
|
94
|
-
|
95
|
-
|
96
|
-
}
|
97
|
-
pane.view.find("ul.results li:first").focus()
|
98
|
-
}
|
97
|
+
$(view).on('inputDownArrow', function(event) {
|
98
|
+
search.stepHighlight(1)
|
99
|
+
})
|
99
100
|
|
100
|
-
|
101
|
-
|
101
|
+
$(view).on('inputUpArrow', function(event) {
|
102
|
+
outputContainer.view.focus()
|
103
|
+
})
|
102
104
|
|
105
|
+
// Show the pane if the user was tabbed onto the trigger and pressed enter, space, or down arrow
|
106
|
+
$(outputContainer.view).on('keydown', function(event){
|
107
|
+
if (outputContainer.view.hasClass('disabled')) { return }
|
108
|
+
|
109
|
+
if (event.which === 40) { // open and focus pane when down key is pressed
|
110
|
+
if (pane.isClosed()) {
|
111
|
+
pane.show()
|
112
|
+
} else if (options.search) {
|
113
|
+
$(searchField.input).focus()
|
114
|
+
} else {
|
115
|
+
search.stepHighlight(1)
|
116
|
+
}
|
117
|
+
return false
|
118
|
+
}
|
103
119
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
120
|
+
if (event.which === 32 || event.which === 13){ // toggle pane when space or enter is pressed
|
121
|
+
pane.toggle()
|
122
|
+
return false
|
123
|
+
}
|
124
|
+
})
|
109
125
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
})
|
126
|
+
// Show the pane when the select element is clicked
|
127
|
+
$(outputContainer.view).on('click', function(event){
|
128
|
+
if (outputContainer.view.hasClass('disabled')) { return }
|
114
129
|
|
115
|
-
|
116
|
-
|
117
|
-
if (options.onRender(resultsContainer, getSelection()) === false) {
|
118
|
-
event.stopPropagation()
|
119
|
-
return
|
120
|
-
}
|
130
|
+
pane.show()
|
131
|
+
})
|
121
132
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
133
|
+
// When the pane is opened
|
134
|
+
$(pane).on('shown', function(){
|
135
|
+
setOutputContainerAria('aria-expanded', true)
|
136
|
+
search.clear()
|
137
|
+
markSelected(true)
|
138
|
+
view.addClass('open')
|
126
139
|
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
})
|
140
|
+
if (options.search) {
|
141
|
+
$(searchField.input).focus()
|
142
|
+
}
|
131
143
|
|
132
|
-
|
133
|
-
|
134
|
-
var datum = $(this).data()
|
144
|
+
triggerEvent(eventsTriggered.shown)
|
145
|
+
})
|
135
146
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
147
|
+
// When the pane is hidden
|
148
|
+
$(pane).on('hidden', function(){
|
149
|
+
setOutputContainerAria('aria-expanded', false)
|
150
|
+
view.removeClass('open')
|
151
|
+
view.focus()
|
152
|
+
})
|
140
153
|
|
141
|
-
|
154
|
+
// When the query is changed
|
155
|
+
$(search).on('queryChanged', function(){
|
156
|
+
updateMessages()
|
157
|
+
})
|
142
158
|
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
159
|
+
// When the search results are rendered
|
160
|
+
$(search).on('renderedResults', function(event){
|
161
|
+
if (options.onRender(resultsContainer, getSelection()) === false) {
|
162
|
+
event.stopPropagation()
|
163
|
+
return
|
164
|
+
}
|
147
165
|
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
166
|
+
markSelected()
|
167
|
+
updateMessages()
|
168
|
+
triggerEvent(eventsTriggered.renderedResults)
|
169
|
+
})
|
152
170
|
|
171
|
+
// When the search field is cleared
|
172
|
+
$(searchField).on('clear', function(){
|
173
|
+
triggerEvent(eventsTriggered.clear)
|
174
|
+
})
|
153
175
|
|
154
|
-
|
176
|
+
// When a search result is chosen
|
177
|
+
resultsContainer.on('click', '.result:not(.disabled)', function(event){
|
178
|
+
var datum = $(this).data()
|
155
179
|
|
156
|
-
|
157
|
-
|
180
|
+
if (options.onSelect(datum, this, event) === false) {
|
181
|
+
event.stopPropagation()
|
182
|
+
return
|
183
|
+
}
|
158
184
|
|
159
|
-
|
160
|
-
pane.addContent('messages', messages)
|
161
|
-
pane.addContent('results', resultsContainer)
|
185
|
+
event.stopPropagation();
|
162
186
|
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
187
|
+
setValue(valueFromResult(this))
|
188
|
+
if (!options.alwaysOpen) {
|
189
|
+
pane.hide()
|
190
|
+
}
|
191
|
+
triggerEvent(eventsTriggered.select, [datum, this, event])
|
192
|
+
})
|
167
193
|
|
168
|
-
|
194
|
+
// When query is submitted
|
195
|
+
$(searchField.input).on('noHighlightSubmit', function(event) {
|
196
|
+
options.onNoHighlightSubmit($(this).val())
|
197
|
+
})
|
169
198
|
|
170
|
-
updateMessages()
|
171
|
-
updateSelectedText()
|
172
|
-
markSelected()
|
173
199
|
|
200
|
+
// INITIALIZATION
|
174
201
|
|
175
|
-
|
202
|
+
setDisabled(options.disabled)
|
203
|
+
setData(data)
|
176
204
|
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
updateSelectedText()
|
181
|
-
markSelected()
|
182
|
-
}
|
205
|
+
if (options.search) { pane.addContent('search', searchField.view) }
|
206
|
+
pane.addContent('messages', messages)
|
207
|
+
pane.addContent('results', resultsContainer)
|
183
208
|
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
209
|
+
// If the output container isn't in the DOM yet, add it
|
210
|
+
if (!$(outputContainer.view).closest('body').length){
|
211
|
+
$(outputContainer.view).appendTo(view)
|
212
|
+
}
|
213
|
+
|
214
|
+
$(view).append(pane.view)
|
215
|
+
|
216
|
+
updateMessages()
|
188
217
|
updateSelectedText()
|
189
218
|
markSelected()
|
190
|
-
}
|
191
219
|
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
}
|
220
|
+
if (options.alwaysOpen) {
|
221
|
+
pane.show()
|
222
|
+
}
|
196
223
|
|
197
|
-
// Enables or disables UberSearch
|
198
|
-
function setDisabled(boolean){
|
199
|
-
outputContainer.setDisabled(boolean)
|
200
|
-
}
|
201
224
|
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
225
|
+
// HELPER FUNCTIONS
|
226
|
+
function generateUUID() {
|
227
|
+
// https://www.w3resource.com/javascript-exercises/javascript-math-exercise-23.php
|
228
|
+
var dt = new Date().getTime();
|
229
|
+
var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
230
|
+
var r = (dt + Math.random()*16)%16 | 0;
|
231
|
+
dt = Math.floor(dt/16);
|
232
|
+
return (c=='x' ? r :(r&0x3|0x8)).toString(16);
|
233
|
+
});
|
234
|
+
return uuid;
|
208
235
|
}
|
209
|
-
}
|
210
236
|
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
237
|
+
function setData(newData){
|
238
|
+
data = setDataDefaults(newData)
|
239
|
+
search.setData(data)
|
240
|
+
updateSelectedText()
|
241
|
+
markSelected()
|
242
|
+
}
|
217
243
|
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
} else {
|
225
|
-
return $.map(data, function(datum){ if (datum.visibility != 'query' || datum.value == selectedValue) return datum })
|
244
|
+
// Selects the result corresponding to the given value
|
245
|
+
function setValue(value){
|
246
|
+
if (selectedValue == value) { return }
|
247
|
+
selectedValue = value
|
248
|
+
updateSelectedText()
|
249
|
+
markSelected()
|
226
250
|
}
|
227
|
-
}
|
228
251
|
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
252
|
+
// Returns the selected value
|
253
|
+
function getValue(){
|
254
|
+
return selectedValue
|
255
|
+
}
|
256
|
+
|
257
|
+
// Enables or disables UberSearch
|
258
|
+
function setDisabled(boolean){
|
259
|
+
outputContainer.setDisabled(boolean)
|
260
|
+
}
|
233
261
|
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
262
|
+
// Updates the enhanced select with the text of the selected result
|
263
|
+
function setSelectedText(text){
|
264
|
+
if (text) {
|
265
|
+
outputContainer.setValue(text)
|
266
|
+
} else {
|
267
|
+
outputContainer.setValue(options.placeholder)
|
268
|
+
}
|
269
|
+
}
|
238
270
|
|
239
|
-
|
240
|
-
|
241
|
-
|
271
|
+
// Inherit values for matchValue and value from text
|
272
|
+
function setDataDefaults(data){
|
273
|
+
return $.map(data, function(datum) {
|
274
|
+
return $.extend({ value: datum.text, matchValue: datum.text }, datum)
|
275
|
+
})
|
276
|
+
}
|
242
277
|
|
243
|
-
|
244
|
-
|
245
|
-
|
278
|
+
// Converts the dataFromSelect into a datum list for matching
|
279
|
+
function dataForMatching(processedQuery, data){
|
280
|
+
// If a query is present, include only select options that should be used when searching
|
281
|
+
// Else, include only options that should be visible when not searching
|
282
|
+
if (processedQuery) {
|
283
|
+
return $.map(data, function(datum){ if (datum.visibility != 'no-query' || datum.value == selectedValue) return datum })
|
284
|
+
} else {
|
285
|
+
return $.map(data, function(datum){ if (datum.visibility != 'query' || datum.value == selectedValue) return datum })
|
246
286
|
}
|
247
|
-
}
|
287
|
+
}
|
288
|
+
|
289
|
+
// Match against the datum.group and datum.matchValue
|
290
|
+
function datumPreprocessor(datum){
|
291
|
+
if (options.matchGroupNames && datum.group) {
|
292
|
+
return datum.group + " " + datum.matchValue
|
293
|
+
} else {
|
294
|
+
return datum.matchValue
|
295
|
+
}
|
296
|
+
}
|
248
297
|
|
249
|
-
//
|
250
|
-
|
298
|
+
// Adds group support and blank option hiding
|
299
|
+
function renderResults(data){
|
300
|
+
var context = this
|
301
|
+
var sourceArray = []
|
251
302
|
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
var groupNodes = reject(sourceArray, 'li[data-group="' + group + '"]')
|
256
|
-
var sublist = $('<ul class="sublist"></ul>').attr('data-group', group)
|
257
|
-
var sublistNode = $('<li></li>').append('<span class="sublist_name">' + group + '</span>')
|
303
|
+
$.each(data, function(index, datum){
|
304
|
+
// Add the group name so we can group items
|
305
|
+
var result = context.buildResult(index, datum).attr('data-group', datum.group)
|
258
306
|
|
259
|
-
|
260
|
-
|
307
|
+
// Omit blank option from results
|
308
|
+
if (!options.hideBlankOption || datum.value){
|
309
|
+
sourceArray.push(result)
|
310
|
+
}
|
311
|
+
})
|
261
312
|
|
262
|
-
|
263
|
-
|
313
|
+
// Arrange ungrouped list items
|
314
|
+
var destArray = reject(sourceArray, 'li:not([data-group])')
|
264
315
|
|
265
|
-
|
266
|
-
|
267
|
-
|
316
|
+
// Arrange list items into sub lists
|
317
|
+
while (sourceArray.length) {
|
318
|
+
var group = $(sourceArray[0]).attr('data-group')
|
319
|
+
var groupNodes = reject(sourceArray, 'li[data-group="' + group + '"]')
|
320
|
+
var sublist = $('<ul>', { class: "sublist", 'data-group': group })
|
321
|
+
var sublistNode = $('<li>', { role: "listitem", 'aria-label': group }).append($('<span>', { class: "sublist_name" }).html(group))
|
268
322
|
|
269
|
-
|
270
|
-
|
271
|
-
function reject(sourceArray, selector){
|
272
|
-
var dest = filter(sourceArray, selector)
|
273
|
-
var source = filter(sourceArray, selector, true)
|
274
|
-
sourceArray.splice(0, sourceArray.length)
|
275
|
-
sourceArray.push.apply(sourceArray, source)
|
276
|
-
return dest
|
277
|
-
}
|
323
|
+
sublist.append(groupNodes)
|
324
|
+
sublistNode.append(sublist)
|
278
325
|
|
279
|
-
|
280
|
-
|
281
|
-
}
|
326
|
+
destArray.push(sublistNode)
|
327
|
+
}
|
282
328
|
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
.data(datum) // Store the datum so we can get know what the value of the selected item is
|
329
|
+
this.view.toggleClass('empty', !destArray.length)
|
330
|
+
this.view.html(destArray)
|
331
|
+
}
|
287
332
|
|
288
|
-
|
289
|
-
|
333
|
+
// Removes elements from the sourcArray that match the selector
|
334
|
+
// Returns an array of removed elements
|
335
|
+
function reject(sourceArray, selector){
|
336
|
+
var dest = filter(sourceArray, selector)
|
337
|
+
var source = filter(sourceArray, selector, true)
|
338
|
+
sourceArray.splice(0, sourceArray.length)
|
339
|
+
sourceArray.push.apply(sourceArray, source)
|
340
|
+
return dest
|
341
|
+
}
|
290
342
|
|
291
|
-
|
343
|
+
function filter(sourceArray, selector, invert){
|
344
|
+
return $.grep(sourceArray, function(node){ return node.is(selector) }, invert)
|
345
|
+
}
|
292
346
|
|
293
|
-
|
294
|
-
|
347
|
+
function buildResult(index, datum){
|
348
|
+
var text = (options.treatBlankOptionAsPlaceholder ? datum.text || options.placeholder : datum.text);
|
295
349
|
|
296
|
-
|
297
|
-
|
298
|
-
|
350
|
+
var result = $('<li class="result" role="listitem" tabindex="-1"></li>') // Use -1 tabindex so that the result can be focusable but not tabbable.
|
351
|
+
.attr('id', (options.uberSelectId + "-" + index))
|
352
|
+
.text(text || String.fromCharCode(160)) // Insert text or
|
353
|
+
.data(datum) // Store the datum so we can get know what the value of the selected item is
|
299
354
|
|
300
|
-
|
355
|
+
if (datum.title) { result.attr('title', datum.title) }
|
356
|
+
if (datum.disabled) { result.addClass('disabled') }
|
301
357
|
|
302
|
-
|
303
|
-
$(selected).addClass('selected').removeClass('hidden')
|
358
|
+
options.resultPostprocessor(result, datum)
|
304
359
|
|
305
|
-
|
306
|
-
search.highlightResult(selected)
|
307
|
-
} else if (options.highlightByDefault) {
|
308
|
-
search.highlightResult(results.not('.hidden').not('.disabled').first())
|
360
|
+
return result
|
309
361
|
}
|
310
|
-
}
|
311
362
|
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
363
|
+
function markSelected(focus) {
|
364
|
+
focus = focus || false
|
365
|
+
var selected = getSelection()
|
366
|
+
var results = search.getResults()
|
367
|
+
|
368
|
+
$(results).filter('.selected').not(selected).removeClass('selected').attr('aria-selected', false)
|
369
|
+
|
370
|
+
// Ensure the selected result is unhidden
|
371
|
+
$(selected).addClass('selected').removeClass('hidden')
|
372
|
+
$(selected).attr('aria-selected', true)
|
373
|
+
|
374
|
+
if (selected) {
|
375
|
+
search.highlightResult(selected, { focus: focus })
|
376
|
+
} else if (options.highlightByDefault) {
|
377
|
+
search.highlightResult(results.not('.hidden').not('.disabled').first(), { focus: focus })
|
320
378
|
}
|
321
|
-
}
|
322
|
-
return selected
|
323
|
-
}
|
379
|
+
}
|
324
380
|
|
325
|
-
|
326
|
-
|
327
|
-
|
381
|
+
// Returns the selected element and its index
|
382
|
+
function getSelection(){
|
383
|
+
var results = search.getResults()
|
384
|
+
var selected
|
385
|
+
$.each(results, function(i, result){
|
386
|
+
if (selectedValue == valueFromResult(result)){
|
387
|
+
selected = result
|
388
|
+
return false
|
389
|
+
}
|
390
|
+
})
|
391
|
+
return selected
|
392
|
+
}
|
328
393
|
|
329
|
-
|
330
|
-
|
331
|
-
|
394
|
+
function valueFromResult(result){
|
395
|
+
return $(result).data('value')
|
396
|
+
}
|
397
|
+
|
398
|
+
function updateSelectedText(){
|
399
|
+
setSelectedText(textFromValue(selectedValue))
|
400
|
+
}
|
332
401
|
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
402
|
+
function textFromValue(value){
|
403
|
+
return $.map(data, function(datum) {
|
404
|
+
if (datum.value == value) {
|
405
|
+
return datum.selectedText || datum.text
|
406
|
+
}
|
407
|
+
})[0]
|
408
|
+
}
|
409
|
+
|
410
|
+
function updateMessages(){
|
411
|
+
messages.show()
|
412
|
+
if (!queryLength() && !resultsCount()){
|
413
|
+
messages.html(options.noDataText)
|
414
|
+
} else if (options.minQueryLength && options.minQueryMessage && queryLength() < options.minQueryLength){
|
415
|
+
messages.html(options.minQueryMessage === true ? 'Type at least ' + options.minQueryLength + (options.minQueryLength == 1 ? ' character' : ' characters') + ' to search' : options.minQueryMessage)
|
416
|
+
} else if (options.noResultsText && !resultsCount()){
|
417
|
+
messages.html(options.noResultsText)
|
418
|
+
} else {
|
419
|
+
messages.empty().hide()
|
337
420
|
}
|
338
|
-
}
|
339
|
-
}
|
421
|
+
}
|
340
422
|
|
341
|
-
|
342
|
-
|
343
|
-
if (!queryLength() && !resultsCount()){
|
344
|
-
messages.html(options.noDataText)
|
345
|
-
} else if (options.minQueryLength && options.minQueryMessage && queryLength() < options.minQueryLength){
|
346
|
-
messages.html(options.minQueryMessage === true ? 'Type at least ' + options.minQueryLength + (options.minQueryLength == 1 ? ' character' : ' characters') + ' to search' : options.minQueryMessage)
|
347
|
-
} else if (options.noResultsText && !resultsCount()){
|
348
|
-
messages.html(options.noResultsText)
|
349
|
-
} else {
|
350
|
-
messages.empty().hide()
|
423
|
+
function queryLength(){
|
424
|
+
return search.getQuery().length
|
351
425
|
}
|
352
|
-
}
|
353
426
|
|
354
|
-
|
355
|
-
|
356
|
-
|
427
|
+
function resultsCount(){
|
428
|
+
return search.getResults().length
|
429
|
+
}
|
357
430
|
|
358
|
-
|
359
|
-
|
360
|
-
|
431
|
+
function setOutputContainerAria() {
|
432
|
+
outputContainer.view.attr.apply(outputContainer.view, arguments)
|
433
|
+
}
|
361
434
|
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
$(context).trigger(eventType, callbackArgs)
|
366
|
-
}
|
435
|
+
// returns true if the event originated outside this component
|
436
|
+
function isEventOutside(event){
|
437
|
+
event = event.originalEvent || event // Handle both jQuery events and standard JS events
|
367
438
|
|
368
|
-
|
439
|
+
if (event.composed) { // Support UberSelect when used in the Shadow DOM
|
440
|
+
return !event.composedPath().includes(view[0])
|
441
|
+
} else {
|
442
|
+
return !$(event.target).closest(view).length
|
443
|
+
}
|
444
|
+
}
|
445
|
+
|
446
|
+
// Allow observer to be attached to the UberSearch itself
|
447
|
+
function triggerEvent(eventType, callbackArgs){
|
448
|
+
view.trigger(eventType, callbackArgs)
|
449
|
+
$(context).triggerHandler(eventType, callbackArgs)
|
450
|
+
}
|
451
|
+
|
452
|
+
// PUBLIC INTERFACE
|
369
453
|
|
370
|
-
|
371
|
-
}
|
454
|
+
$.extend(this, {view:view, searchField:searchField, setValue:setValue, getValue: getValue, setData:setData, setDisabled:setDisabled, getSelection:getSelection, options:options})
|
455
|
+
}
|
456
|
+
})(jQuery)
|