uber_select_rails 0.1.2 → 0.4.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 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 ));