uber_select_rails 0.1.2 → 0.1.8

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.
@@ -0,0 +1,120 @@
1
+ function List(options) {
2
+ var context = this
3
+
4
+ var view = this.view = $('<ul class="results"></ul>')
5
+
6
+
7
+ // BEHAVIOUR
8
+
9
+ // Handle up and down arrow key presses
10
+ $(options.keypressInput).on('keydown', function(event){
11
+ switch (event.which) {
12
+ case 38: // Up Arrow
13
+ stepHighlight(-1, true)
14
+ return false
15
+ case 40: // Down Arrow
16
+ stepHighlight(1)
17
+ return false
18
+ case 13: // Enter
19
+ if (highlightedResult().length) {
20
+ highlightedResult().click()
21
+ } else {
22
+ $(this).trigger('noHighlightSubmit')
23
+ }
24
+ return false
25
+ }
26
+ })
27
+
28
+ // When a list item is hovered
29
+ $(view).on('mouseenter', '.result', function(){
30
+ unhighlightResults()
31
+ highlightResult(this, {scroll: false})
32
+ })
33
+
34
+
35
+ // HELPER FUNCTIONS
36
+
37
+ this.getResults = function(){
38
+ return $(view).find('.result')
39
+ }
40
+
41
+ this.renderResults = function(data){
42
+ var results = $.map(data, function(datum){
43
+ return context.buildResult(datum)
44
+ })
45
+
46
+ view.toggleClass('empty', !data.length)
47
+
48
+ view.html(results)
49
+ }
50
+
51
+ // Can be overridden to format how results are built
52
+ this.buildResult = function(datum){
53
+ return $('<li></li>').html(datum).addClass('result')
54
+ }
55
+
56
+ this.unhighlightResults = unhighlightResults
57
+ this.highlightResult = highlightResult
58
+
59
+ function stepHighlight(amount, allowUnhighlight){
60
+ var index = visibleResults().index(highlightedResult())
61
+ var result = visibleResults()[index + amount]
62
+
63
+ if (result || allowUnhighlight){
64
+ unhighlightResults()
65
+ highlightResult(result)
66
+ }
67
+ }
68
+
69
+ function highlightResult(result, options){
70
+ result = $(result)
71
+ options = $.extend({scroll: true}, options)
72
+
73
+ if (!result.length) { return }
74
+
75
+ var visibleResult = visibleResults().filter(result)
76
+ if (visibleResult.length) {
77
+ visibleResult.addClass('highlighted')
78
+
79
+ if (options.scroll){
80
+ scrollResultIntoView(visibleResult)
81
+ }
82
+ }
83
+ }
84
+
85
+ function unhighlightResults(){
86
+ highlightedResult().removeClass('highlighted')
87
+ }
88
+
89
+ function highlightedResult(){
90
+ return results().filter('.highlighted')
91
+ }
92
+
93
+ function visibleResults(){
94
+ return results().not('.hidden')
95
+ }
96
+
97
+ function results(){
98
+ return view.find('.result')
99
+ }
100
+
101
+ function scrollResultIntoView(result){
102
+ result = $(result)
103
+ var container = result.closest('.results').css('position', 'relative') // Ensure the results container is positioned so offset is calculated correctly
104
+ var containerHeight = container.outerHeight()
105
+ var containerTop = container.get(0).scrollTop
106
+ var containerBottom = containerTop + containerHeight
107
+ var resultHeight = result.outerHeight()
108
+ var resultTop = result.get(0).offsetTop
109
+ var resultBottom = resultTop + resultHeight
110
+
111
+ if (containerBottom < resultBottom){
112
+ container.scrollTop(resultBottom - containerHeight)
113
+ } else if (containerTop > resultTop){
114
+ container.scrollTop(resultTop)
115
+ }
116
+ }
117
+
118
+ // INITIALIZATION
119
+ $.extend(this, options) // Allow overriding of functions
120
+ }
@@ -0,0 +1,21 @@
1
+ var OutputContainer = function(options){
2
+ options = $.extend({}, options)
3
+ var view = $('<span class="selected_text_container" tabindex=0 role="button"></span>')
4
+ var selectedText = $('<span class="selected_text"></span>').appendTo(view)
5
+ var selectCaret = $('<span class="select_caret"></span>').appendTo(view).html(options.selectCaret)
6
+
7
+ // INITIALIZATION
8
+
9
+ setValue()
10
+
11
+ // HELPER FUNCTIONS
12
+
13
+ function setValue(value){
14
+ selectedText.html(value || '&nbsp;')
15
+ view.toggleClass('empty', !value)
16
+ }
17
+
18
+ // PUBLIC INTERFACE
19
+
20
+ $.extend(this, {view: view, setValue: setValue})
21
+ }
@@ -0,0 +1,90 @@
1
+ function Pane(options){
2
+ options = $.extend({
3
+ trigger: null
4
+ }, options)
5
+
6
+ var context = this
7
+ var model = this.model = {}
8
+ var isOpen = false
9
+ var view = this.view = $('<div class="pane"></div>').toggle(isOpen)
10
+ var innerPane = $('<div class="pane_inner"></div>').appendTo(view)
11
+
12
+
13
+ // PUBLIC INTERFACE
14
+
15
+ $.extend(this, {view: view, addContent: addContent, removeContent: removeContent, show: show, hide: hide})
16
+
17
+
18
+ // BEHAVIOUR
19
+
20
+ if (options.trigger){
21
+ // Show the pane when the select element is clicked
22
+ $(options.trigger).on('click', function(event){
23
+ context.show()
24
+ })
25
+
26
+ // Show the pane if the user was tabbed onto the trigger and pressed enter or space
27
+ $(options.trigger).on('keyup', function(event){
28
+ if (event.which == 13 || event.which == 32){
29
+ context.show()
30
+ return false
31
+ }
32
+ })
33
+ }
34
+
35
+ // Hide the pane when clicked out
36
+ $(document).on('mousedown', function(event){
37
+ if (isEventOutsidePane(event) && isEventOutsideTrigger(event)){
38
+ context.hide()
39
+ }
40
+ })
41
+
42
+ // Make it possible to have elements in the pane that close it
43
+ view.on('click', '[data-behaviour~=close-pane]', function(event){
44
+ context.hide()
45
+ })
46
+
47
+ // Close the pane when the user presses escape
48
+ $(document).on('keyup', function(event){
49
+ if (event.which == 27 && isOpen){
50
+ context.hide()
51
+ options.trigger.focus()
52
+ }
53
+ })
54
+
55
+
56
+ // HELPER FUNCTIONS
57
+
58
+ function addContent(name, content){
59
+ model[name] = content
60
+ innerPane.append(content)
61
+ }
62
+
63
+ function removeContent(name){
64
+ $(model[name]).remove()
65
+ delete model['name']
66
+ }
67
+
68
+ function show(){
69
+ if (isOpen) { return }
70
+ isOpen = true
71
+ view.show()
72
+ $(context).trigger('shown')
73
+ }
74
+ function hide(){
75
+ if (!isOpen) { return }
76
+ isOpen = false
77
+ view.hide()
78
+ $(context).trigger('hidden')
79
+ }
80
+
81
+ // returns true if the event originated outside the pane
82
+ function isEventOutsidePane(event){
83
+ return !$(event.target).closest(view).length
84
+ }
85
+
86
+ function isEventOutsideTrigger(event){
87
+ return !$(event.target).closest(options.trigger).length
88
+ }
89
+
90
+ }
@@ -0,0 +1,151 @@
1
+ function Search(queryInput, resultsContainer, options){
2
+ var context = this
3
+ var model = new SearchModel(options.model)
4
+ var list = new List(options.view)
5
+ var resultsRendered = false
6
+
7
+ // HELPER FUNCTIONS
8
+
9
+ this.setData = function(data){
10
+ model.setData(data)
11
+ }
12
+
13
+ this.renderResults = function(){
14
+ list.renderResults(model.getResults())
15
+ $(this).trigger('renderedResults')
16
+ resultsRendered = true
17
+ }
18
+
19
+ this.getQuery = function(){
20
+ return model.getQuery()
21
+ }
22
+
23
+ this.getResults = function(){
24
+ return list.getResults()
25
+ }
26
+
27
+ this.clear = function(){
28
+ if (!resultsRendered){
29
+ this.renderResults()
30
+ }
31
+
32
+ if (queryInput.val() === '') {
33
+ list.unhighlightResults()
34
+ } else {
35
+ queryInput.val('').change()
36
+ }
37
+ }
38
+
39
+ this.highlightResult = function(element) {
40
+ list.unhighlightResults()
41
+ list.highlightResult(element)
42
+ }
43
+
44
+ // BEHAVIOUR
45
+
46
+ $(queryInput).on('searchInput', function(){
47
+ model.setQuery(this.value)
48
+ })
49
+
50
+ $(model).on('resultsUpdated', function(){
51
+ context.renderResults()
52
+ })
53
+
54
+ // Forward query change
55
+ $(model).on('queryChanged', function(){
56
+ $(context).trigger('queryChanged')
57
+ })
58
+
59
+
60
+ // INITIALIZATION
61
+
62
+ resultsContainer.html(list.view)
63
+
64
+
65
+ // PROTOTYPES
66
+
67
+ function SearchModel(options){
68
+ var data, results
69
+ var processedQuery = ''
70
+ var context = this
71
+ options = $.extend({minQueryLength: 0}, options)
72
+
73
+ this.setQuery = function(value){
74
+ value = context.queryPreprocessor(value)
75
+
76
+ if (processedQuery == value) { return }
77
+ processedQuery = value || ''
78
+ this.updateResults()
79
+ $(this).trigger('queryChanged')
80
+ }
81
+
82
+ this.getQuery = function(){
83
+ return processedQuery || ''
84
+ }
85
+
86
+ this.setData = function(value){
87
+ data = value || []
88
+ this.updateResults()
89
+ }
90
+
91
+ this.getResults = function(){
92
+ return results
93
+ }
94
+
95
+ this.updateResults = function(){
96
+ if (options.minQueryLength > processedQuery.length) {
97
+ results = []
98
+ } else if (this.isBlankQuery()){
99
+ results = $.each(this.dataForMatching(processedQuery, data), function(){ return this })
100
+ } else {
101
+ results = []
102
+ var pattern = this.patternForMatching(processedQuery)
103
+ $.each(this.dataForMatching(processedQuery, data), function(index, datum){
104
+ if (context.match(pattern, context.datumPreprocessor(datum), processedQuery)){
105
+ results.push(datum)
106
+ }
107
+ })
108
+ }
109
+ $(this).trigger('resultsUpdated')
110
+ }
111
+
112
+ this.isBlankQuery = function(){
113
+ return processedQuery === ''
114
+ }
115
+
116
+ // Can be overridden to select a subset of data for matching
117
+ // Defaults to the identity function
118
+ this.dataForMatching = function(processedQuery, data){
119
+ return data
120
+ }
121
+
122
+ // Provides a regexp for matching the processedDatum from the processedQuery
123
+ // Can be overridden to provide more sophisticated matching behaviour
124
+ this.patternForMatching = function(processedQuery){
125
+ return new RegExp(processedQuery.escapeForRegExp(), 'i')
126
+ }
127
+
128
+ // Can be overridden to provide more sophisticated matching behaviour
129
+ this.match = function(pattern, processedDatum, processedQuery){
130
+ return pattern.test(processedDatum)
131
+ }
132
+
133
+ // Can be overridden to mutate the query being used to match before matching
134
+ // Defaults to whitespace trim
135
+ this.queryPreprocessor = function(query){
136
+ return $.trim(query)
137
+ }
138
+
139
+ // Can be overridden to mutate the data the moment before it is matched
140
+ // Useful extract string from JSON datum
141
+ // Defaults to the identity function
142
+ this.datumPreprocessor = function(datum){
143
+ return datum
144
+ }
145
+
146
+ // INITIALIZATION
147
+ $.extend(this, options) // Allow overriding of functions
148
+ delete this.data // Data isn't an attribute we want to expose
149
+ this.setData(options.data)
150
+ }
151
+ }
@@ -0,0 +1,80 @@
1
+ function SearchField(options){
2
+ options = $.extend({
3
+ placeholder: 'Type to Search',
4
+ clearButton:'&#x2715;' // Text content of clear search button
5
+ }, options)
6
+
7
+ var context = this
8
+ var input = this.input = $('<input type="search" class="search_input">').attr('placeholder', options.placeholder)
9
+ var value = input.val()
10
+ var clearButton = this.clearButton = $('<span class="clear_search_button"></span>').html(options.clearButton)
11
+ var view = this.view = $('<span class="search_field_container"></span>').append(input).append(clearButton)
12
+ var eventNames = isOnInputSupported() ? 'input change' : 'keyup change'
13
+
14
+
15
+ // PUBLIC INTERFACE
16
+
17
+ $.extend(this, {refresh: refresh})
18
+
19
+
20
+ // BEHAVIOUR
21
+
22
+ // When a change is detected
23
+ input.on(eventNames, function() {
24
+ refresh() // Always refresh on input in case something has altered the state without informing us
25
+
26
+ if (input.val() == value) { return }
27
+
28
+ triggerEvent('searchInput')
29
+ value = input.val()
30
+ })
31
+
32
+ // When the clear button is pressed
33
+ clearButton.on('click', function(){
34
+ input.val('')
35
+ refresh()
36
+ input.focus()
37
+ triggerEvent('searchInput')
38
+ triggerEvent('clear')
39
+ })
40
+
41
+ // When the enter button is pressed
42
+ input.on('keydown', function(event){
43
+ if (event.which == 13){
44
+ triggerEvent('querySubmit')
45
+ }
46
+ })
47
+
48
+
49
+ // HELPER FUNCTIONS
50
+
51
+ function refresh(){
52
+ updateClearButtonVisiblity()
53
+ updateSearchInputClass()
54
+ }
55
+
56
+ function updateSearchInputClass(){
57
+ input.toggleClass('empty', !input.val())
58
+ }
59
+
60
+ function isOnInputSupported(){
61
+ // IE 8 and 9 are the only common browsers that don't completely support oninput
62
+ // IE 10 and 11 fire the input event when the element is focussed, treat them as unsupported as well
63
+ return !(navigator.userAgent.indexOf('MSIE')!==-1 || navigator.appVersion.indexOf('Trident/') > 0) // SOURCE: http://stackoverflow.com/questions/21825157/internet-explorer-11-detection
64
+ }
65
+
66
+ function updateClearButtonVisiblity(){
67
+ clearButton.toggle(!!input.val().length)
68
+ }
69
+
70
+ // Allow observer to be attached to the SearchField itself
71
+ function triggerEvent(eventType, callbackArgs){
72
+ input.trigger(eventType, callbackArgs)
73
+ $(context).trigger(eventType, callbackArgs)
74
+ }
75
+
76
+
77
+ // INITIALIZATION
78
+
79
+ refresh()
80
+ }