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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0dad48d52dd5e14c133c8d6c01861f5ae3e9069110df7b8b5faa7840703342f7
4
- data.tar.gz: 72be4762c753d35290e7f9d63d6b9575f0967d44d9a9b382d417accb6324fd6b
3
+ metadata.gz: 4e848d17e956bbe5c52f9f7e299e47cc2cb296755ae8ce7cbf94877b5a4857ca
4
+ data.tar.gz: 87fdd8c564c2960b8d20015245aeb48b28b8c2ee7b7e12e42fd393eb49ebaeec
5
5
  SHA512:
6
- metadata.gz: a39831aaeb705925e5bf27053e4445232096eb8ba8f4e572ce2809a21b1cd4a9f42af62189614089fa7dba4fa9b5f046988fcfea92ce47ee872e76f24692b797
7
- data.tar.gz: 98cdf58391fea57de5ac5ea903a72e2d6fb15b67956ec64d0ffcdf73aa3231349f38b1555a31f2422653b2ed6acb37b63882918cbf16856368cb54af42c1924f
6
+ metadata.gz: 4500a0a600213b393da36f7676deee006ddd301f4f258d336017e2d0260182615da1a75ccda716f231049e357049a23a25b1b26a88597d844f39423babb202b7
7
+ data.tar.gz: 38d0c07a95368fcdaaa6d3338f21e572a846cf70142353e2a1ec365e6f4a9222c3ac060cd1cca6a4321b4aa1866b62421c3b1ab887e148a89801497975cda09b
@@ -1,3 +1,3 @@
1
1
  module UberSelectRails
2
- VERSION = "0.6.0"
2
+ VERSION = "1.0.0"
3
3
  end
@@ -24,15 +24,15 @@ you can use UberSelect to gussy up forms, without changing any of the underlying
24
24
  $('.my_selects').uberSelect(options);
25
25
  ```
26
26
 
27
- #### Attributes <a name="UberSearch attributes"></a>
27
+ #### Attributes <a name="uber-search-attributes"></a>
28
28
 
29
- Attribtes on the outermost element can be specified by setting the `data-uber-attributes` attribute on the `<select>` element. Values should be passed
29
+ Attributes on the outermost element can be specified by setting the `data-uber-attributes` attribute on the `<select>` element. Values should be passed
30
30
  as a JSON string of key/value pairs where the key is the attribute name and the value is the attribute value.
31
31
 
32
32
  #### Options
33
33
 
34
34
  Options can be specified by setting the `data-uber-options` attribute on the `<select>` element. Values should be passed
35
- as a JSON string. All options on the are passed to the underlying UberSearch, see [UberSearch options](#UberSearchOptions).
35
+ as a JSON string. All options on the are passed to the underlying UberSearch, see [UberSearch options](#uber-search-options).
36
36
 
37
37
  - ##### prepopulateSearchOnOpen
38
38
 
@@ -76,7 +76,7 @@ as a JSON string. All options on the are passed to the underlying UberSearch, se
76
76
 
77
77
  - ##### optionFromDatum
78
78
 
79
- 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.
79
+ A function that is used to customize the option's value and text built from a JSON response. `datum` is a single result returned from the JSON response.
80
80
 
81
81
  The function signature is as follows:
82
82
 
@@ -98,6 +98,13 @@ as a JSON string. All options on the are passed to the underlying UberSearch, se
98
98
 
99
99
  Add an aria-label attribute with this value to the uber_select element.
100
100
 
101
+ #### Option Data Attributes
102
+ `<option>` elements can each use data-attributes to control datum properties. See [UberSearch data](#uber-search-data).
103
+
104
+ - `data-match-value`
105
+ - `data-visibility`
106
+ - `data-selected-text`
107
+
101
108
  #### Events Triggered
102
109
 
103
110
  - ##### uber-select:ready
@@ -129,7 +136,7 @@ being used in purely in JS, and not being linked to a `<select>` element in a fo
129
136
  new UberSearch(data, options);
130
137
  ```
131
138
 
132
- #### Data
139
+ #### Data <a name="uber-search-data"></a>
133
140
 
134
141
  Data is an array of objects. Each object may have the following properties:
135
142
 
@@ -180,7 +187,7 @@ Data is an array of objects. Each object may have the following properties:
180
187
  Returns the currently selected element from the search results.
181
188
 
182
189
 
183
- #### Options <a name="UberSearch options"></a>
190
+ #### Options <a name="uber-search-options"></a>
184
191
 
185
192
  Options can be specified by setting the `data-uber-options` attribute on the `<select>` element. Values should be passed
186
193
  as a JSON string.
@@ -297,7 +304,7 @@ as a JSON string.
297
304
 
298
305
  - ##### onRender
299
306
 
300
- A function to run when the results container is rendered. If the result returns false, the default `select` event
307
+ A function to run when the results container is rendered. If the result returns false, the default `render` event
301
308
  handler is not run and the event is cancelled.
302
309
 
303
310
  The function signature is as follows:
@@ -328,6 +335,20 @@ as a JSON string.
328
335
  function(value) { }
329
336
  ```
330
337
 
338
+ - ##### matchGroupNames
339
+
340
+ Toggles whether or not to match results using their `datum.group` in addition to their `datum.matchValue`. When `true`
341
+ the searches that match the group name will cause options within that group to appear.
342
+
343
+ Default: `false`
344
+
345
+ - ##### alwaysOpen
346
+
347
+ Toggles whether options page is always shown open. When `true` the pane will start open and will not close when a option is selected
348
+ nor via any clicks on the select itself.
349
+
350
+ Default: `false`
351
+
331
352
  - ##### outputContainer (Deprecated)
332
353
 
333
354
  An object that receives the output once a result is selected. Must respond to `setValue(value)` and `view()`. This object serves to
@@ -66,10 +66,10 @@
66
66
  $(select).append(optionsFromData(data))
67
67
  updateSelectValue(options.value)
68
68
  uberSearch.setData(dataFromSelect(select))
69
- $(select).trigger(eventsTriggered.ready)
69
+ triggerEvent(eventsTriggered.ready)
70
70
  })
71
71
  } else {
72
- $(select).trigger(eventsTriggered.ready)
72
+ triggerEvent(eventsTriggered.ready)
73
73
  }
74
74
 
75
75
 
@@ -158,7 +158,7 @@
158
158
  var before = $(select).val()
159
159
  $(select).val(value)
160
160
  var after = $(select).val() // Read value the same way instead of comparing to `value` so the same coercion is applied
161
- if (before != after) { $(select).trigger('change') } // Only trigger a change if the value has actually changed
161
+ if (before != after) { triggerEvent('change') } // Only trigger a change if the value has actually changed
162
162
  }
163
163
 
164
164
  // Selects the option with an emptystring value, or the first option if there is no blank option
@@ -169,7 +169,8 @@
169
169
  if (!selectValue) { return }
170
170
 
171
171
  // Clear the value
172
- $(select).val('').trigger('change')
172
+ $(select).val('')
173
+ triggerEvent('change')
173
174
 
174
175
  // If that cleared it then we're done, otherwise, select the first option
175
176
  if ($(select).find('option:selected').length){ return }
@@ -180,13 +181,18 @@
180
181
  if (firstOptionValue == selectValue) { return }
181
182
 
182
183
  // Select the first option
183
- $(select).val(firstOptionValue).trigger('change')
184
+ $(select).val(firstOptionValue)
185
+ triggerEvent('change')
184
186
  }
185
187
 
186
188
  // Hide the select, but keep its width to allow it to set the min width of the uber select
187
189
  // NOTE: IE doesn't like 0 height, so give it 1px height and then offset
188
190
  function hideSelect(){
189
- $(select).wrap($('<div>').css({visibility: 'hidden', height: '1px', marginTop: '-1px', pointerEvents: 'none'}).addClass('select_width_spacer'))
191
+ $(select).wrap($('<div>').css({visibility: 'hidden', height: '1px', marginTop: '-1px', pointerEvents: 'none'}).addClass('select_width_spacer').attr('aria-hidden', true))
192
+ }
193
+
194
+ function triggerEvent(eventType) {
195
+ return select.dispatchEvent(new CustomEvent(eventType, { bubbles: true }));
190
196
  }
191
197
  })
192
198
 
@@ -0,0 +1,160 @@
1
+ (function($) {
2
+ UberSearch.List = function(options) {
3
+ var context = this
4
+
5
+ var view = this.view = $('<ul class="results"></ul>')
6
+
7
+
8
+ // BEHAVIOUR
9
+
10
+ // Handle up and down arrow key presses
11
+ $(view).on('keydown', function(event){
12
+ switch (event.which) {
13
+ case 38: // Up Arrow
14
+ stepHighlight(-1, true)
15
+ return false
16
+ case 40: // Down Arrow
17
+ stepHighlight(1)
18
+ return false
19
+ case 32: // Space
20
+ if (highlightedResult().length) {
21
+ highlightedResult().click()
22
+ }
23
+ return false
24
+ case 13: // Enter
25
+ if (highlightedResult().length) {
26
+ highlightedResult().click()
27
+ } else {
28
+ $(this).trigger('noHighlightSubmit')
29
+ }
30
+ return false
31
+ }
32
+ })
33
+
34
+ // When a list item is hovered
35
+ $(view).on('mouseenter focus', '.result:not(.disabled)', function(){
36
+ if ($(this).hasClass('highlighted')) { return }
37
+ unhighlightResults()
38
+ highlightResult(this, {scroll: false})
39
+ })
40
+
41
+
42
+ // HELPER FUNCTIONS
43
+
44
+ this.getResults = function(){
45
+ return $(view).find('.result')
46
+ }
47
+
48
+ this.renderResults = function(data){
49
+ var results = $.map(data, function(datum){
50
+ return context.buildResult(datum)
51
+ })
52
+
53
+ view.toggleClass('empty', !data.length)
54
+
55
+ view.html(results)
56
+ }
57
+
58
+ this.unhighlightResults = unhighlightResults
59
+ this.highlightResult = highlightResult
60
+ this.stepHighlight = stepHighlight
61
+ this.setHighlight = setHighlight
62
+
63
+ function stepHighlight(amount, allowUnhighlight){
64
+ var index = selectableResults().index(highlightedResult())
65
+ setHighlight(index + amount, { allowUnhighlight: allowUnhighlight })
66
+ }
67
+
68
+ function setHighlight(index, options) {
69
+ options = $.extend({}, options)
70
+
71
+ var result = selectableResults()[index]
72
+
73
+ if (result){
74
+ unhighlightResults({ blur: !options.focus })
75
+ highlightResult(result, { focus: options.focus })
76
+ } else if (options.allowUnhighlight) {
77
+ unhighlightResults({ blur: !options.focus })
78
+ }
79
+ triggerEvent('setHighlight', [result, index])
80
+ }
81
+
82
+ function highlightResult(result, options) {
83
+ result = $(result)
84
+ options = $.extend({
85
+ scroll: true,
86
+ focus: true
87
+ }, options)
88
+
89
+ if (!result.length) {
90
+ return
91
+ }
92
+
93
+ result.addClass('highlighted')
94
+
95
+ if (options.focus) {
96
+ result.focus()
97
+ }
98
+
99
+ if (options.scroll){
100
+ scrollResultIntoView(result)
101
+ }
102
+ }
103
+
104
+ function unhighlightResults(options){
105
+ options = $.extend({
106
+ blur: true
107
+ }, options)
108
+
109
+ var result = highlightedResult()
110
+ result.removeClass('highlighted')
111
+
112
+ if (options.blur) {
113
+ result.blur()
114
+ }
115
+ }
116
+
117
+ function highlightedResult(){
118
+ return results().filter('.highlighted')
119
+ }
120
+
121
+ function selectableResults(){
122
+ return visibleResults().not('.disabled')
123
+ }
124
+
125
+ function visibleResults(){
126
+ return results().not('.hidden')
127
+ }
128
+
129
+ function results(){
130
+ return view.find('.result')
131
+ }
132
+
133
+ function scrollResultIntoView(result){
134
+ result = $(result)
135
+ var container = result.closest('.results').css('position', 'relative') // Ensure the results container is positioned so offset is calculated correctly
136
+ var containerHeight = container.outerHeight()
137
+ var containerTop = container.get(0).scrollTop
138
+ var containerBottom = containerTop + containerHeight
139
+ var resultHeight = result.outerHeight()
140
+ var resultTop = result.get(0).offsetTop
141
+ var resultBottom = resultTop + resultHeight
142
+
143
+ if (containerBottom < resultBottom){
144
+ container.scrollTop(resultBottom - containerHeight)
145
+ } else if (containerTop > resultTop){
146
+ container.scrollTop(resultTop)
147
+ }
148
+ }
149
+
150
+
151
+ // Allow observer to be attached to the UberSearch itself
152
+ function triggerEvent(eventType, callbackArgs){
153
+ view.trigger(eventType, callbackArgs)
154
+ $(context).triggerHandler(eventType, callbackArgs)
155
+ }
156
+
157
+ // INITIALIZATION
158
+ $.extend(this, options) // Allow overriding of functions
159
+ }
160
+ })(jQuery)
@@ -0,0 +1,29 @@
1
+ (function($) {
2
+ UberSearch.OutputContainer = function(options){
3
+ options = $.extend({}, options)
4
+ var view = $('<span class="selected_text_container" aria-expanded="false" aria-activedescendant aria-haspopup="listbox" role="combobox" tabindex="0"></span>')
5
+ var selectedText = $('<span class="selected_text"></span>').appendTo(view)
6
+ var selectCaret = $('<span class="select_caret"></span>').appendTo(view).html(options.selectCaret)
7
+
8
+ // INITIALIZATION
9
+
10
+ if (options.ariaLabel) { view.attr("aria-label", options.ariaLabel) }
11
+ setValue()
12
+
13
+ // HELPER FUNCTIONS
14
+
15
+ function setValue(value){
16
+ selectedText.text(value || String.fromCharCode(160)); // Insert value or &nbsp;
17
+
18
+ view.toggleClass('empty', !value)
19
+ }
20
+
21
+ function setDisabled(boolean) {
22
+ view.toggleClass('disabled', boolean)
23
+ }
24
+
25
+ // PUBLIC INTERFACE
26
+
27
+ $.extend(this, {view: view, setValue: setValue, setDisabled: setDisabled})
28
+ }
29
+ })(jQuery)
@@ -0,0 +1,93 @@
1
+ (function($) {
2
+ UberSearch.Pane = function() {
3
+ var eventsTriggered = {
4
+ shown: 'shown',
5
+ hidden: 'hidden'
6
+ }
7
+
8
+ var context = this
9
+ var model = {}
10
+ var isOpen = false
11
+ var view = $('<div class="pane" role="listbox"></div>').toggle(isOpen)
12
+ var innerPane = $('<div class="pane_inner"></div>').appendTo(view)
13
+
14
+
15
+ // PUBLIC INTERFACE
16
+
17
+ $.extend(this, {
18
+ model: model,
19
+ view: view,
20
+ addContent: addContent,
21
+ removeContent: removeContent,
22
+ show: show,
23
+ hide: hide,
24
+ toggle: toggle,
25
+ isOpen: paneIsOpen,
26
+ isClosed: paneIsClosed
27
+ })
28
+
29
+
30
+ // BEHAVIOUR
31
+
32
+ // Make it possible to have elements in the pane that close it
33
+ view.on('click', '[data-behaviour~=close-pane]', function(event){
34
+ context.hide()
35
+ })
36
+
37
+ // Close the pane when the user presses escape
38
+ $(document).on('keyup', function(event){
39
+ if (event.which == 27 && isOpen){
40
+ context.hide()
41
+ return false
42
+ }
43
+ })
44
+
45
+
46
+ // HELPER FUNCTIONS
47
+
48
+ function paneIsOpen(){
49
+ return isOpen
50
+ }
51
+
52
+ function paneIsClosed(){
53
+ return !isOpen
54
+ }
55
+
56
+ function addContent(name, content){
57
+ model[name] = content
58
+ innerPane.append(content)
59
+ }
60
+
61
+ function removeContent(name){
62
+ $(model[name]).remove()
63
+ delete model['name']
64
+ }
65
+
66
+ function show(){
67
+ if (isOpen) { return }
68
+ isOpen = true
69
+ view.show()
70
+ view.addClass('showing')
71
+ triggerEvent(eventsTriggered.shown)
72
+ }
73
+ function hide(){
74
+ if (!isOpen) { return }
75
+ isOpen = false
76
+ view.hide()
77
+ view.removeClass('showing')
78
+ triggerEvent(eventsTriggered.hidden)
79
+ }
80
+ function toggle(){
81
+ if (isOpen) {
82
+ context.hide()
83
+ } else {
84
+ context.show()
85
+ }
86
+ }
87
+
88
+ function triggerEvent(eventType, callbackArgs){
89
+ view.trigger(eventType, callbackArgs)
90
+ $(context).triggerHandler(eventType, callbackArgs)
91
+ }
92
+ }
93
+ })(jQuery)
@@ -0,0 +1,77 @@
1
+ (function($) {
2
+ UberSearch.Search = function(queryInput, resultsContainer, options){
3
+ var context = this
4
+ var model = new UberSearch.SearchModel(options.model)
5
+ var list = new UberSearch.List(options.view)
6
+ var resultsRendered = false
7
+
8
+ // HELPER FUNCTIONS
9
+
10
+ this.setData = function(data){
11
+ model.setData(data)
12
+ }
13
+
14
+ this.renderResults = function(){
15
+ list.renderResults(model.getResults())
16
+ $(this).trigger('renderedResults')
17
+ resultsRendered = true
18
+ }
19
+
20
+ this.getQuery = function(){
21
+ return model.getQuery()
22
+ }
23
+
24
+ this.getResults = function(){
25
+ return list.getResults()
26
+ }
27
+
28
+ this.clear = function(){
29
+ if (!resultsRendered){
30
+ this.renderResults()
31
+ }
32
+
33
+ if (queryInput.val() === '') {
34
+ list.unhighlightResults()
35
+ } else {
36
+ queryInput.val('').change()
37
+ }
38
+ }
39
+
40
+ this.highlightResult = function(element, options) {
41
+ list.unhighlightResults()
42
+ list.highlightResult(element, options)
43
+ }
44
+
45
+ this.stepHighlight = list.stepHighlight
46
+ this.setHighlight = list.setHighlight
47
+
48
+
49
+ // BEHAVIOUR
50
+
51
+ $(queryInput).on('searchInput', function(){
52
+ model.setQuery(this.value)
53
+ })
54
+
55
+ // Forward navigating away from queryInput
56
+ $(queryInput).on('inputDownArrow', function() {
57
+ $(context).trigger('inputDownArrow')
58
+ })
59
+
60
+ $(model).on('resultsUpdated', function(){
61
+ context.renderResults()
62
+ })
63
+
64
+ // Forward query change
65
+ $(model).on('queryChanged', function(){
66
+ $(context).trigger('queryChanged')
67
+ })
68
+
69
+
70
+ // INITIALIZATION
71
+
72
+ resultsContainer.html(list.view)
73
+
74
+
75
+ // PROTOTYPES
76
+ }
77
+ })(jQuery)
@@ -0,0 +1,96 @@
1
+ (function($) {
2
+ UberSearch.SearchField = function(options){
3
+ options = $.extend({
4
+ placeholder: 'Type to Search',
5
+ searchInputAttributes: { 'aria-label': "Type to Search" },
6
+ clearButton:'&#x2715;' // Text content of clear search button
7
+ }, options)
8
+
9
+ var inputAttrs = {}
10
+ $.extend(inputAttrs, { placeholder: options.placeholder }, options.searchInputAttributes)
11
+
12
+ var context = this
13
+ var input = this.input = $('<input type="search" class="search_input">').attr(inputAttrs)
14
+ var value = input.val()
15
+ var clearButton = this.clearButton = $('<span class="clear_search_button"></span>').html(options.clearButton)
16
+ var view = this.view = $('<span class="search_field_container"></span>').append(input).append(clearButton)
17
+ var eventNames = isOnInputSupported() ? 'input change' : 'keyup change'
18
+
19
+
20
+ // PUBLIC INTERFACE
21
+
22
+ $.extend(this, {refresh: refresh})
23
+
24
+
25
+ // BEHAVIOUR
26
+
27
+ // When a change is detected
28
+ input.on(eventNames, function() {
29
+ refresh() // Always refresh on input in case something has altered the state without informing us
30
+
31
+ if (input.val() == value) { return }
32
+
33
+ triggerEvent('searchInput')
34
+ value = input.val()
35
+ })
36
+
37
+ // When the clear button is pressed
38
+ clearButton.on('click', function(){
39
+ input.val('')
40
+ refresh()
41
+ input.focus()
42
+ triggerEvent('searchInput')
43
+ triggerEvent('clear')
44
+ })
45
+
46
+ // When the enter button is pressed
47
+ input.on('keydown', function(event){
48
+ if (event.which == 13){
49
+ triggerEvent('querySubmit')
50
+ }
51
+
52
+ if (event.which == 38) { // Up Arrow
53
+ triggerEvent('inputUpArrow')
54
+ return false
55
+ }
56
+
57
+ if (event.which == 40){ // Down Arrow
58
+ triggerEvent('inputDownArrow')
59
+ return false
60
+ }
61
+ })
62
+
63
+
64
+ // HELPER FUNCTIONS
65
+
66
+ function refresh(){
67
+ updateClearButtonVisiblity()
68
+ updateSearchInputClass()
69
+ }
70
+
71
+ function updateSearchInputClass(){
72
+ input.toggleClass('empty', !input.val())
73
+ }
74
+
75
+ function isOnInputSupported(){
76
+ // IE 8 and 9 are the only common browsers that don't completely support oninput
77
+ // IE 10 and 11 fire the input event when the element is focussed, treat them as unsupported as well
78
+ return !(navigator.userAgent.indexOf('MSIE')!==-1 || navigator.appVersion.indexOf('Trident/') > 0) // SOURCE: http://stackoverflow.com/questions/21825157/internet-explorer-11-detection
79
+ }
80
+
81
+ function updateClearButtonVisiblity(){
82
+ clearButton.toggle(!!input.val().length)
83
+ }
84
+
85
+ // Allow observer to be attached to the SearchField itself
86
+ function triggerEvent(eventType, callbackArgs){
87
+ input.trigger(eventType, callbackArgs)
88
+ $(context).triggerHandler(eventType, callbackArgs)
89
+ }
90
+
91
+
92
+ // INITIALIZATION
93
+
94
+ refresh()
95
+ }
96
+ })(jQuery)