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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 877c716db7b58ef210bf6c74e61a3eb3bbeb487c
4
- data.tar.gz: 723d3273bedb197b867abcddefa2965468dd030a
2
+ SHA256:
3
+ metadata.gz: 4c4e02c23ba5d8d6ba50707be770694a60f4abc20cbff88c2fbe9baa7ed63619
4
+ data.tar.gz: fbcbd8c7ab7ff3910e2161fd980e78eb215bda8942f71d64c2332d874817a622
5
5
  SHA512:
6
- metadata.gz: 4e64e8e34bee05dd83f5c0334078e7930757d1c31f00ca4321f88740ebefcb246e9e84c55eb6b95d9b4d9df6534b2322c2ac9067fda376ccc0b711dd4cc25642
7
- data.tar.gz: f2c06a3bddb62d8fe4f98c0ca43f8422c79b88db92c7a9b9fff54d77e2bcea35b04cb5cfd16d074872cbe32a4aac629352569edbf7b1f6c1805be09d4aae6d9c
6
+ metadata.gz: 5d8afa5bdfdb0b4a50df1dc5fc9154d32f7d2b9823fb63075ce6f9cd26edf11bc274ac73a2f91a75fac64c58dc023dc3ab8c54aa80a38385493ff411d9a0d648
7
+ data.tar.gz: b8d8f1986c48ee1b2c502a16932fcc0809a10abaa8a4bc577423d1931361d55a79e3e1b4484c9a145761fa6aba9accb9a286a86c51c69c6f91d87cfcec09b179
@@ -1,3 +1,3 @@
1
1
  module UberSelectRails
2
- VERSION = "0.1.2"
2
+ VERSION = "0.4.0"
3
3
  end
@@ -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 ));