uber_select_rails 0.1.2 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/lib/uber_select_rails/version.rb +1 -1
- data/uber_select.gemspec +1 -1
- data/vendor/assets/javascript/uber_select/.gitignore +18 -0
- data/vendor/assets/javascript/uber_select/README.md +264 -0
- data/vendor/assets/javascript/uber_select/javascript/jquery.uber-select.js +182 -0
- data/vendor/assets/javascript/uber_select/javascript/list.js +121 -0
- data/vendor/assets/javascript/uber_select/javascript/output_container.js +25 -0
- data/vendor/assets/javascript/uber_select/javascript/pane.js +94 -0
- data/vendor/assets/javascript/uber_select/javascript/search.js +151 -0
- data/vendor/assets/javascript/uber_select/javascript/search_field.js +80 -0
- data/vendor/assets/javascript/uber_select/javascript/string_extensions.js +8 -0
- data/vendor/assets/javascript/uber_select/javascript/uber_search.js +329 -0
- data/vendor/assets/javascript/uber_select/test.css +158 -0
- data/vendor/assets/javascript/uber_select/test.html +248 -0
- metadata +15 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 4c4e02c23ba5d8d6ba50707be770694a60f4abc20cbff88c2fbe9baa7ed63619
|
4
|
+
data.tar.gz: fbcbd8c7ab7ff3910e2161fd980e78eb215bda8942f71d64c2332d874817a622
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5d8afa5bdfdb0b4a50df1dc5fc9154d32f7d2b9823fb63075ce6f9cd26edf11bc274ac73a2f91a75fac64c58dc023dc3ab8c54aa80a38385493ff411d9a0d648
|
7
|
+
data.tar.gz: b8d8f1986c48ee1b2c502a16932fcc0809a10abaa8a4bc577423d1931361d55a79e3e1b4484c9a145761fa6aba9accb9a286a86c51c69c6f91d87cfcec09b179
|
data/uber_select.gemspec
CHANGED
@@ -12,7 +12,7 @@ Gem::Specification.new do |spec|
|
|
12
12
|
spec.summary = %q{A Rails gem containing a javascript plugin that adds a layer of UI goodness overtop of basic HTML select elements}
|
13
13
|
spec.homepage = "https://github.com/culturecode/uber_select_rails"
|
14
14
|
|
15
|
-
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
15
|
+
spec.files = `git ls-files -z --recurse-submodules`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
16
16
|
spec.bindir = "exe"
|
17
17
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
18
18
|
spec.require_paths = ["lib"]
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# See https://help.github.com/articles/ignoring-files for more about ignoring files.
|
2
|
+
#
|
3
|
+
# If you find yourself ignoring temporary files generated by your text editor
|
4
|
+
# or operating system, you probably want to add a global ignore instead:
|
5
|
+
# git config --global core.excludesfile '~/.gitignore_global'
|
6
|
+
|
7
|
+
# Ignore bundler config.
|
8
|
+
/.bundle
|
9
|
+
|
10
|
+
# Ignore the default SQLite database.
|
11
|
+
/db/*.sqlite3
|
12
|
+
/db/*.sqlite3-journal
|
13
|
+
|
14
|
+
# Ignore all logfiles and tempfiles.
|
15
|
+
/log/*
|
16
|
+
!/log/.keep
|
17
|
+
/tmp
|
18
|
+
.DS_Store
|
@@ -0,0 +1,264 @@
|
|
1
|
+
# UberSelect
|
2
|
+
|
3
|
+
UberSelect is a fancy UI on top of a regular `<select>` element.
|
4
|
+
|
5
|
+
## Requirements
|
6
|
+
Tested on jQuery 1.11.x to 3.3.x
|
7
|
+
|
8
|
+
## Parts
|
9
|
+
UberSelect is composed of two main parts, an UberSearch that does all the of searching and UI, and an UberSelect which
|
10
|
+
is used to connect an UberSearch to a `<select>` element.
|
11
|
+
|
12
|
+
### UberSelect
|
13
|
+
This is the object that allows an UberSearch and a `<select>` element to work together. The select element can be used
|
14
|
+
to control the state of the UberSearch, and vice-versa. This means you can programmatically change the state of the
|
15
|
+
select, and UberSearch will update. Users can interact with the UberSearch, and the select will update. This also means
|
16
|
+
you can use UberSelect to gussy up forms, without changing any of the underlying inputs.
|
17
|
+
|
18
|
+
#### Usage
|
19
|
+
|
20
|
+
```JS
|
21
|
+
$('.my_selects').uberSelect(options);
|
22
|
+
```
|
23
|
+
|
24
|
+
#### Options
|
25
|
+
Options can be specified by setting the `data-uber-options` attribute on the `<select>` element. Values should be passed
|
26
|
+
as a JSON string. All options on the are passed to the underlying UberSearch, see [UberSearch options](#UberSearchOptions).
|
27
|
+
|
28
|
+
- ##### prepopulateSearchOnOpen
|
29
|
+
Determines whether the search input starts with the selected value in it when the pane is opened.
|
30
|
+
|
31
|
+
Default: `false`
|
32
|
+
|
33
|
+
- ##### clearSearchClearsSelect
|
34
|
+
Determines whether the select value should be cleared when the search is cleared.
|
35
|
+
|
36
|
+
Default: `false`
|
37
|
+
|
38
|
+
- ##### placeholder
|
39
|
+
Placeholder to show in the selected text area.
|
40
|
+
|
41
|
+
Default: `<select>` element attributes `<select placeholder="my placeholder" data-placeholder="my placeholder">`
|
42
|
+
|
43
|
+
- ##### dataUrl
|
44
|
+
A url to pre-fetch select options from. JSON response should be of the form
|
45
|
+
`[{text:'option with explicit value', value: 'some value'}, {text:'option with implicit value'}]`. For a custom JSON response, use in conjunction with optionFromDatum.
|
46
|
+
|
47
|
+
Default: `null`
|
48
|
+
|
49
|
+
- ##### optionFromDatum
|
50
|
+
A function that is used to customize the options value and text built from a JSON response. `datum` is a single result returned from the JSON response.
|
51
|
+
|
52
|
+
The function signature is as follows:
|
53
|
+
```js
|
54
|
+
function(datum) {
|
55
|
+
return // a <option> element to represent the select
|
56
|
+
}
|
57
|
+
```
|
58
|
+
|
59
|
+
Default: `datum.value` populates the `<option>` value, `datum.text` populates the `<option>` text.
|
60
|
+
|
61
|
+
- ##### value
|
62
|
+
Initialize the UberSearch with this selected value
|
63
|
+
|
64
|
+
Default: `<select>` element `value` property
|
65
|
+
|
66
|
+
#### Events Triggered
|
67
|
+
- ##### uber-select:ready
|
68
|
+
This fires when the UberSelect has initialized and is ready for user interaction
|
69
|
+
|
70
|
+
#### Events Observed
|
71
|
+
The `<select>` element observes the following events:
|
72
|
+
|
73
|
+
- ##### uber-select:refreshOptions
|
74
|
+
The UberSearch options list will be updated to match the `<select>` element's `<option>` list.
|
75
|
+
|
76
|
+
- ##### uber-select:refresh
|
77
|
+
The UberSearch will update its selected value to match the `<select>` element's. This handler also runs when the
|
78
|
+
`<select>` element triggers a `change` event.
|
79
|
+
|
80
|
+
### UberSearch
|
81
|
+
The UberSearch performs all of the heavy lifting. It creates the UI views, maintains state, and performs the searching.
|
82
|
+
It can be instantiated without the use of an UberSelect, which can be useful for situations where the selected value is
|
83
|
+
being used in purely in JS, and not being linked to a `<select>` element in a form.
|
84
|
+
|
85
|
+
#### Usage
|
86
|
+
|
87
|
+
```JS
|
88
|
+
new UberSearch(data, options);
|
89
|
+
```
|
90
|
+
|
91
|
+
#### Data
|
92
|
+
Data is an array of objects. Each object may have the following properties:
|
93
|
+
|
94
|
+
- ##### text
|
95
|
+
String shown to the user in the list of results. This value is required if *value* is not provided.
|
96
|
+
|
97
|
+
- ##### selectedText
|
98
|
+
String shown to the user in the output container when this option is selected. *optional*
|
99
|
+
|
100
|
+
- ##### title
|
101
|
+
Title text shown to the user when hovering over the result. *optional*
|
102
|
+
|
103
|
+
- ##### value
|
104
|
+
This is matched against the *value* option passed UberSearch and will appear selected if it matches. It is also used to match against search input text when the user searches. This value is required if *text* is not provided.
|
105
|
+
|
106
|
+
- ##### matchValue
|
107
|
+
This overrides the value used to match against search input text when the user searches. *optional*
|
108
|
+
|
109
|
+
- ##### visibility
|
110
|
+
This is used to determine whether the option appears only when searching or only when not searching. Values accepted: `query`, `no-query`. *optional*
|
111
|
+
|
112
|
+
- ##### disabled
|
113
|
+
This is used to determine whether the option appears disabled. *optional*
|
114
|
+
|
115
|
+
- ##### group
|
116
|
+
This is used to visually group options. All options with the same group will appear together. *optional*
|
117
|
+
|
118
|
+
#### Methods
|
119
|
+
|
120
|
+
- ##### setData(data)
|
121
|
+
Sets the data. `data` should be an Array conforming to the specifications described in <a href="#data">Data</a>
|
122
|
+
|
123
|
+
|
124
|
+
#### Options <a name="UberSearch options"></a>
|
125
|
+
Options can be specified by setting the `data-uber-options` attribute on the `<select>` element. Values should be passed
|
126
|
+
as a JSON string.
|
127
|
+
|
128
|
+
- ##### value
|
129
|
+
Sets the initially selected value of the UberSearch. This value should match the `value` property of the desired
|
130
|
+
option data.
|
131
|
+
|
132
|
+
Default: `null`
|
133
|
+
|
134
|
+
- ##### search
|
135
|
+
Determines whether the search input be shown.
|
136
|
+
|
137
|
+
Default: `true`
|
138
|
+
|
139
|
+
- ##### clearSearchButton
|
140
|
+
Sets the text content of clear search button.
|
141
|
+
|
142
|
+
Default: `✕`
|
143
|
+
|
144
|
+
- ##### selectCaret
|
145
|
+
Sets the text content of clear select caret.
|
146
|
+
|
147
|
+
Default: `⌄`
|
148
|
+
|
149
|
+
- ##### hideBlankOption
|
150
|
+
Sets whether blank options should be hidden automatically.
|
151
|
+
|
152
|
+
Default: `false`
|
153
|
+
|
154
|
+
- ##### treatBlankOptionAsPlaceholder
|
155
|
+
Determines whether the `text` property of an option with a blank `value` property should be used as the placeholder
|
156
|
+
text if no placeholder is specified.
|
157
|
+
|
158
|
+
Default: `false`
|
159
|
+
|
160
|
+
- ##### highlightByDefault
|
161
|
+
Determines whether the first search result be auto-highlighted.
|
162
|
+
|
163
|
+
Default: `true`
|
164
|
+
|
165
|
+
- ##### minQueryLength
|
166
|
+
Sets minimum number of characters the user must type before a search will be performed.
|
167
|
+
|
168
|
+
Default: `0`
|
169
|
+
|
170
|
+
- ##### minQueryMessage
|
171
|
+
Sets the message shown to the user when the query doesn't exceed the minimum length. `true` for a default message,
|
172
|
+
`false` for none, or provide a string to set a custom message.
|
173
|
+
|
174
|
+
Default: `true`
|
175
|
+
|
176
|
+
- ##### placeholder
|
177
|
+
Sets the placeholder shown in the selected text area.
|
178
|
+
|
179
|
+
Default: `null`
|
180
|
+
|
181
|
+
- ##### searchPlaceholder
|
182
|
+
Sets the placeholder shown in the search input.
|
183
|
+
|
184
|
+
Default: `'Type to search'`
|
185
|
+
|
186
|
+
- ##### noResultsText
|
187
|
+
Sets the message shown when there are no results.
|
188
|
+
|
189
|
+
Default: `'No Matches Found'`
|
190
|
+
|
191
|
+
- ##### noDataText
|
192
|
+
Sets the text to show when the results list is empty and no search is in progress
|
193
|
+
|
194
|
+
Default: `'No options'`
|
195
|
+
|
196
|
+
- ##### buildResult
|
197
|
+
A function that is used to build result elements.
|
198
|
+
|
199
|
+
The function signature is as follows:
|
200
|
+
```js
|
201
|
+
function(listOptionData) {
|
202
|
+
return // HTML/element to insert into the the results list
|
203
|
+
}
|
204
|
+
```
|
205
|
+
|
206
|
+
- ##### resultPostprocessor
|
207
|
+
A function that is run after a result is built and can be used to decorate it. This can be useful when extending the
|
208
|
+
functionality of an existing UberSearch implementation.
|
209
|
+
|
210
|
+
The function signature is as follows:
|
211
|
+
```js
|
212
|
+
function(resultsListElement, listOptionData) { }
|
213
|
+
```
|
214
|
+
|
215
|
+
Default: No-op
|
216
|
+
|
217
|
+
- ##### onRender
|
218
|
+
A function to run when the results container is rendered. If the result returns false, the default `select` event
|
219
|
+
handler is not run and the event is cancelled.
|
220
|
+
|
221
|
+
The function signature is as follows:
|
222
|
+
```js
|
223
|
+
function(resultsContainer, searchResultsHTML) { }
|
224
|
+
```
|
225
|
+
|
226
|
+
- ##### onSelect
|
227
|
+
A function to run when a result is selected. If the result returns false, the default `select` event handler is not
|
228
|
+
run and the event is cancelled.
|
229
|
+
|
230
|
+
The function signature is as follows:
|
231
|
+
```js
|
232
|
+
function(listOptionData, resultsListElement, clickEvent) { }
|
233
|
+
```
|
234
|
+
|
235
|
+
- ##### onNoHighlightSubmit
|
236
|
+
A function to run when a user presses enter without selecting a result.
|
237
|
+
Should be used in combination with `highlightByDefault: false`.
|
238
|
+
|
239
|
+
The function signature is as follows:
|
240
|
+
```js
|
241
|
+
function(value) { }
|
242
|
+
```
|
243
|
+
|
244
|
+
- ##### outputContainer (Deprecated)
|
245
|
+
An object that receives the output once a result is selected. Must respond to `setValue(value)` and `view()`. This object serves to
|
246
|
+
attach the result list to the DOM at the desired location.
|
247
|
+
|
248
|
+
#### Events Triggered
|
249
|
+
- ##### shown
|
250
|
+
This fires when the UberSearch pane is opened.
|
251
|
+
|
252
|
+
- ##### renderedResults
|
253
|
+
This fires each time the list of results is updated.
|
254
|
+
|
255
|
+
- ##### clear
|
256
|
+
This fires when the user clicks the clear search button.
|
257
|
+
|
258
|
+
- ##### select
|
259
|
+
This fires when the user selects a result.
|
260
|
+
|
261
|
+
The handler function signature is as follows:
|
262
|
+
```js
|
263
|
+
function(event, [listOptionData, resultsContainer, originalEvent]) { }
|
264
|
+
```
|
@@ -0,0 +1,182 @@
|
|
1
|
+
(function( $ ) {
|
2
|
+
var eventsTriggered = {
|
3
|
+
ready: 'uber-select:ready'
|
4
|
+
}
|
5
|
+
var eventsObserved = {
|
6
|
+
refreshOptions: 'uber-select:refreshOptions',
|
7
|
+
refresh: 'uber-select:refresh change',
|
8
|
+
}
|
9
|
+
|
10
|
+
$.fn.uberSelect = function(opts) {
|
11
|
+
this.each(function(){
|
12
|
+
if (this.uberSearch) { return } // Prevent multiple initializations on the same element
|
13
|
+
var select = this
|
14
|
+
var options = $.extend({
|
15
|
+
prepopulateSearchOnOpen: false, // Should the search input start with the selected value in it when the pane is opened?
|
16
|
+
clearSearchClearsSelect: false, // Should the select value be cleared When the search is cleared?
|
17
|
+
disabled: $(select).is(':disabled'), // Whether the select is currently disabled
|
18
|
+
placeholder: $(select).attr('placeholder') || $(select).attr('data-placeholder'), // Placeholder to show in the selected text area
|
19
|
+
dataUrl: null, // A url to pre-fetch select options from, see optionsFromData for data format
|
20
|
+
optionFromDatum: optionFromDatum, // A function to create select options
|
21
|
+
value: $(select).val() // Initialize the UberSearch with this selected value
|
22
|
+
}, opts, $(select).data('uber-options'))
|
23
|
+
|
24
|
+
var uberSearch = this.uberSearch = new UberSearch(dataFromSelect(select), options)
|
25
|
+
|
26
|
+
|
27
|
+
// BEHAVIOUR
|
28
|
+
|
29
|
+
// When the UberSearch pane is opened
|
30
|
+
$(uberSearch).on('shown', function(){
|
31
|
+
if (options.prepopulateSearchOnOpen){
|
32
|
+
updateSearchValueFromSelect()
|
33
|
+
}
|
34
|
+
})
|
35
|
+
|
36
|
+
// When the clear search button is clicked
|
37
|
+
$(uberSearch).on('clear', function(){
|
38
|
+
if (options.clearSearchClearsSelect){
|
39
|
+
clearSelect()
|
40
|
+
}
|
41
|
+
})
|
42
|
+
|
43
|
+
// When the list values change
|
44
|
+
$(select).on(eventsObserved.refreshOptions, refreshOptionsList)
|
45
|
+
|
46
|
+
// When the select value changes
|
47
|
+
$(select).on(eventsObserved.refresh, updateSelectedValue)
|
48
|
+
|
49
|
+
// When a result is selected
|
50
|
+
$(uberSearch).on('select', function(_, datum){
|
51
|
+
updateSelectValue(datum.value)
|
52
|
+
})
|
53
|
+
|
54
|
+
// INITIALIZATION
|
55
|
+
|
56
|
+
uberSearch.view.insertBefore(select).append(select)
|
57
|
+
hideSelect()
|
58
|
+
if (options.dataUrl) {
|
59
|
+
$.getJSON(options.dataUrl).done(function(data){
|
60
|
+
$(select).append(optionsFromData(data))
|
61
|
+
uberSearch.setData(dataFromSelect(select))
|
62
|
+
$(select).trigger(eventsTriggered.ready)
|
63
|
+
})
|
64
|
+
} else {
|
65
|
+
$(select).trigger(eventsTriggered.ready)
|
66
|
+
}
|
67
|
+
|
68
|
+
|
69
|
+
// HELPER FUNCTIONS
|
70
|
+
|
71
|
+
// Given a select element
|
72
|
+
// Returns an array of data to match against
|
73
|
+
function dataFromSelect(select){
|
74
|
+
var opts = $(select).find('option')
|
75
|
+
var datum;
|
76
|
+
var parent;
|
77
|
+
|
78
|
+
return $.map(opts, function(option){
|
79
|
+
// This is optimized for performance and does not use jQuery convenience methods. Seems to be about 30% faster loading during non-scientific tests.
|
80
|
+
datum = {
|
81
|
+
text: option.textContent,
|
82
|
+
selectedText: getAttribute(option, 'data-selected-text'),
|
83
|
+
value: getAttribute(option, 'value'),
|
84
|
+
title: getAttribute(option, 'title'),
|
85
|
+
disabled: getAttribute(option, 'disabled'),
|
86
|
+
matchValue: getAttribute(option, 'data-match-value'),
|
87
|
+
visibility: getAttribute(option, 'data-visibility'),
|
88
|
+
element: option
|
89
|
+
}
|
90
|
+
|
91
|
+
parent = option.parentElement
|
92
|
+
if (parent.nodeName == 'OPTGROUP') {
|
93
|
+
datum.group = getAttribute(parent, 'label')
|
94
|
+
datum.visibility = datum.visibility || getAttribute(parent, 'data-visibility')
|
95
|
+
}
|
96
|
+
|
97
|
+
return datum
|
98
|
+
})
|
99
|
+
}
|
100
|
+
|
101
|
+
// Generates select options from data
|
102
|
+
function optionsFromData(data){
|
103
|
+
var elements = []
|
104
|
+
var groups = {}
|
105
|
+
$.each(data, function(_, datum){
|
106
|
+
if (datum.group) {
|
107
|
+
groups[datum.group] || elements.push(groups[datum.group] = groupFromDatum(datum))
|
108
|
+
groups[datum.group].append(options.optionFromDatum(datum))
|
109
|
+
} else {
|
110
|
+
elements.push(options.optionFromDatum(datum))
|
111
|
+
}
|
112
|
+
})
|
113
|
+
|
114
|
+
return elements
|
115
|
+
}
|
116
|
+
|
117
|
+
function getAttribute(element, attribute) {
|
118
|
+
var value = element.getAttribute(attribute)
|
119
|
+
return value === null ? undefined : value // Allow $.extend to overwrite missing attributes by setting them to undefined
|
120
|
+
}
|
121
|
+
|
122
|
+
function groupFromDatum(datum){
|
123
|
+
return $('<optgroup>').attr('label', datum.group)
|
124
|
+
}
|
125
|
+
|
126
|
+
function optionFromDatum(datum){
|
127
|
+
return $('<option>').attr('value', datum.value || datum.text).text(datum.text || datum.value)
|
128
|
+
}
|
129
|
+
|
130
|
+
// Copies the value of the select into the search input
|
131
|
+
function updateSearchValueFromSelect(){
|
132
|
+
uberSearch.searchField.input.val($(select).find('option:selected').text())
|
133
|
+
uberSearch.searchField.refresh()
|
134
|
+
}
|
135
|
+
|
136
|
+
function refreshOptionsList(){
|
137
|
+
uberSearch.setDisabled($(select).is(':disabled'))
|
138
|
+
uberSearch.setData(dataFromSelect(select))
|
139
|
+
updateSelectedValue()
|
140
|
+
}
|
141
|
+
|
142
|
+
// Updates the UberSearch's selected value from the select element's value
|
143
|
+
function updateSelectedValue(){
|
144
|
+
uberSearch.setValue($(select).val())
|
145
|
+
}
|
146
|
+
|
147
|
+
function updateSelectValue(value){
|
148
|
+
$(select).val(value).trigger('change')
|
149
|
+
}
|
150
|
+
|
151
|
+
// Selects the option with an emptystring value, or the first option if there is no blank option
|
152
|
+
function clearSelect(){
|
153
|
+
var selectValue = $(select).val()
|
154
|
+
|
155
|
+
// If the select is already cleared, avoid firing a change event
|
156
|
+
if (!selectValue) { return }
|
157
|
+
|
158
|
+
// Clear the value
|
159
|
+
$(select).val('').trigger('change')
|
160
|
+
|
161
|
+
// If that cleared it then we're done, otherwise, select the first option
|
162
|
+
if ($(select).find('option:selected').length){ return }
|
163
|
+
|
164
|
+
var firstOptionValue = $(select).find('option').prop('value')
|
165
|
+
|
166
|
+
// If the first option is already set then we're done, otherwise, select the first option
|
167
|
+
if (firstOptionValue == selectValue) { return }
|
168
|
+
|
169
|
+
// Select the first option
|
170
|
+
$(select).val(firstOptionValue).trigger('change')
|
171
|
+
}
|
172
|
+
|
173
|
+
// Hide the select, but keep its width to allow it to set the min width of the uber select
|
174
|
+
// NOTE: IE doesn't like 0 height, so give it 1px height and then offset
|
175
|
+
function hideSelect(){
|
176
|
+
$(select).wrap($('<div>').css({visibility: 'hidden', height: '1px', marginTop: '-1px', pointerEvents: 'none'}).addClass('select_width_spacer'))
|
177
|
+
}
|
178
|
+
})
|
179
|
+
|
180
|
+
return this
|
181
|
+
}
|
182
|
+
}( jQuery ));
|