uber_select_rails 0.1.2 → 0.1.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+ }