assoc_whisperer 1.0.4 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,7 @@
1
1
  ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- NDIyMjM5N2NmMmQ5ZWQ1YTQ1NDY0OWVjYmI3MzFlNDE2NzM4MzllZA==
5
- data.tar.gz: !binary |-
6
- MjlhMDE5NmE1YzlhM2QzYzA2NjA5NjczYTY2OWQyOWJiNjYwMTk0Mg==
2
+ SHA1:
3
+ metadata.gz: 560ba60c23716d32676b7fad089a78bb3564acce
4
+ data.tar.gz: 56bf2e3a493c9e96bca65ea408dc5f515d97a590
7
5
  SHA512:
8
- metadata.gz: !binary |-
9
- ZDQzOGQ1MmYxYjY5MjNhZjM0MjM1NTFkNWZiZWRkNjQ5ZDQzMGJkMWNiZjFi
10
- NmE1ZWViZTBjNGExMDYxMjgyNzU2ZmMyNWE4ZmU0MTZlNWJjNTQwYWMxNDUz
11
- NGYyMGRlZWRmZmJlOTk0NDhhNzVlMDJlMDFlNWM1MGNlM2ZhNGY=
12
- data.tar.gz: !binary |-
13
- OWVlMGQ0MGU0MjQ2M2M3YmNhZjVlYjVlOWYwZWVmZmQzMjk2MjRiYzQyYzI1
14
- YzZiMjBkZDYyZjQ3YTdhNTY5NmY4YzJkNWJmODA5YzQ3NmZjODA0OTIzMTFk
15
- NGI1YWYxMWRlY2E4NWI1Y2UyNmMzYmIwMDgyMDU3YmFiOTE3NzQ=
6
+ metadata.gz: e79e15a365f8db2031de3ed1d3daf7b8a68477be0e5daa039739be214b16172e8a90f0de9356255ff5dc9b416ab5f09d7a8b7f85a7968da911d93756da46c2a5
7
+ data.tar.gz: 8a89817479453e08c83062b5506199fbaf160317fcab33f22b0d1cf68e319a3191ce7c6369e46d24e4f2d69f4e0713588bc07b23e05f4854b059b15dbb6eeefe
@@ -0,0 +1,248 @@
1
+ /**
2
+ * Created by doooby on 30.9.14.
3
+ */
4
+
5
+ document.AssocWhisperer = (function () {
6
+ var proto, klass, all_array = [];
7
+
8
+ $(document).ready(function(){
9
+ var w;
10
+ $('.assoc_whisperer').each(function (i, el) {
11
+ w = createWhisperer(el);
12
+ all_array.push(w);
13
+ });
14
+ });
15
+
16
+ // This is where Whisperer Object is created - with all hidden inner methods.
17
+ function createWhisperer(dom_node) {
18
+ var el, w, _nodes, _timer;
19
+
20
+ el = $(dom_node);
21
+ _nodes = {
22
+ base: el,
23
+ text_field: el.children('.text_field'),
24
+ value_field: el.children('.value_field'),
25
+ list: null
26
+ };
27
+
28
+ w = Object.create(proto, {
29
+ nodes: {value: _nodes},
30
+ action: {value: el.attr('data-action')},
31
+ url: {value: el.attr('data-url')},
32
+ focused: {value: false, writable: true},
33
+ filled: {value: _nodes['value_field'].val()!=='', writable: true},
34
+ client_side: {value: el.attr('data-client-side')==='true'},
35
+ full_data: {value: null, writable: true}
36
+ });
37
+
38
+ _nodes.text_field.on('keyup', function(e){
39
+ var input_text;
40
+ if (e.keyCode===27) { // escape key
41
+ w.removeList();
42
+ return;
43
+ }
44
+ // if (e.keyCode!==37 && e.keyCode!==39) {} // left & right
45
+
46
+ if (_timer) clearTimeout(_timer);
47
+ if (w.filled) {
48
+ w.filled = false;
49
+ _nodes['value_field'].val('');
50
+ _nodes['text_field'].addClass('unfilled');
51
+ }
52
+
53
+ input_text = _nodes['text_field'].val();
54
+ if (input_text==='') {
55
+ w.removeList();
56
+ }
57
+ else {
58
+ if (_nodes['list']) {
59
+ _nodes['list'].addClass('invalid');
60
+ }
61
+ _timer = setTimeout(function () {
62
+ if (w.client_side) {
63
+ if (!w.full_data) querry.call(w, null, function (html_text) {
64
+ w.full_data = $(html_text);
65
+ digest(input_text);
66
+ });
67
+ else digest(input_text);
68
+ }
69
+ else querry.call(w, input_text, function (html_text) {
70
+ showList($(html_text));
71
+ });
72
+ }, 700);
73
+ }
74
+ });
75
+ _nodes.text_field.on('click', function(){ _nodes['text_field'].select(); });
76
+ _nodes.text_field.focus(function(){ w.focused = true; });
77
+ _nodes.text_field.blur(function(){ onBlur(); });
78
+ el.children('.dropdown_button').on('click', function(){
79
+ if (_timer) clearTimeout(_timer);
80
+ if (w.client_side) {
81
+ function f () {
82
+ var input_text;
83
+ if (w.filled) showList(w.full_data);
84
+ else {
85
+ input_text = _nodes['text_field'].val();
86
+ digest(input_text==='' ? null : input_text);
87
+ }
88
+ _nodes['list'].focus();
89
+ }
90
+ if (!w.full_data) querry.call(w, null, function (html_text) {
91
+ w.full_data = $(html_text);
92
+ f();
93
+ });
94
+ else f();
95
+ }
96
+ else {
97
+ if (w.filled) querry.call(w, null, function (html_text) {
98
+ showList($(html_text));
99
+ });
100
+ else querry.call(w, _nodes['text_field'].val(), function (html_text) {
101
+ showList($(html_text));
102
+ });
103
+ _nodes['list'].focus();
104
+ }
105
+ });
106
+
107
+ // For local full_data finds matching rows and shows them.
108
+ function digest(input_text) {
109
+ var narrowed_list = w.full_data;
110
+ if (input_text && input_text!=='') {
111
+ input_text = input_text.toLowerCase();
112
+ narrowed_list = $(narrowed_list[0].cloneNode());
113
+ w.full_data.children().each(function () {
114
+ if (this.innerText.toLowerCase().indexOf(input_text)!==-1)
115
+ narrowed_list.append($(this).clone());
116
+ });
117
+ }
118
+ showList(narrowed_list);
119
+ }
120
+
121
+ // Attaches a List sent by html string within Whisperer's tag. Positions it underneath the text field.
122
+ function showList (list) {
123
+ var base_dom;
124
+ w.removeList();
125
+
126
+ _nodes['list'] = list;
127
+
128
+ base_dom = _nodes['base'];
129
+ list.css('min-width', base_dom.width());
130
+ list.css('left', base_dom.offset().left);
131
+ list.css('top', base_dom.position().top + base_dom.outerHeight());
132
+ list.focus(function(){ w.focused = true; });
133
+ list.blur(function(){ onBlur(); });
134
+ base_dom.append(list);
135
+
136
+ list.find('div').on('click', function(el){ w.select($(el.currentTarget)); });
137
+ }
138
+
139
+ // Hides the List if either of the controls were unfocused.
140
+ function onBlur () {
141
+ w.focused = false;
142
+ setTimeout(function(){ if (!w.focused) w.removeList(); }, 100);
143
+ }
144
+
145
+ return w;
146
+ }
147
+
148
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
149
+ // Whisperer prototype //
150
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
151
+
152
+ proto = Object.create(Object);
153
+
154
+ // Removes the List should there be any.
155
+ Object.defineProperty(proto, 'removeList', {
156
+ value: function () {
157
+ if (this.nodes['list']) {
158
+ this.nodes['list'].detach();
159
+ this.nodes['list'] = null
160
+ }
161
+ }
162
+ });
163
+
164
+ // Select given row (must be jQuery object) and hides the List.
165
+ Object.defineProperty(proto, 'select', {
166
+ value: function (row) {
167
+ var text;
168
+ if (row.length!==1) return;
169
+ text = row.text();
170
+ this.nodes['text_field'].val(text);
171
+ this.nodes['text_field'].removeClass('unfilled');
172
+ this.nodes['value_field'].val(row.attr('data-value'));
173
+ this.filled = true;
174
+ this.removeList();
175
+ }
176
+ });
177
+
178
+ // Actual ajax request for given input
179
+ function querry (input, on_success) {
180
+ var w = this;
181
+ klass.querrying = true;
182
+ $.ajax(w.url, {
183
+ type: 'GET',
184
+ dataType: 'html',
185
+ data: {data_action: w.action, input: (input||'')},
186
+ error: function () { w.removeList(); },
187
+ success: on_success,
188
+ complete: function () { klass.querrying = false; }
189
+ }
190
+ );
191
+ }
192
+
193
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
194
+ // public interface //
195
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
196
+
197
+
198
+ klass = Object.create(Object, {querrying: {value: false, writable: true}});
199
+
200
+ // Return a Whisperer instance by its data-action attribute.
201
+ function findWhisperer(action) {
202
+ var i, w;
203
+ for (i = 0; i < all_array.length; i += 1) {
204
+ w = all_array[i];
205
+ if (w.action===action) return w;
206
+ }
207
+ return null;
208
+ }
209
+
210
+ // Sets a value to Whisperer
211
+ Object.defineProperty(klass, 'setValueFor', {
212
+ value: function (value, whisp_action) {
213
+ findWhisperer(whisp_action).nodes['value_field'].val(value);
214
+ }
215
+ });
216
+
217
+ // Querries for selected text and seletcs the option if found
218
+ Object.defineProperty(klass, 'selectTextFor', {
219
+ value: function (text, whisp_action) {
220
+ var w;
221
+ w = findWhisperer(whisp_action);
222
+ querry.call(w, text, function (html_text) {
223
+ w.select($(html_text).find('div:contains("'+text+'")'));
224
+ });
225
+ }
226
+ });
227
+
228
+ // Querries and seletcs the option by value if found
229
+ // Selects the first option if the given value is '#1'.
230
+ Object.defineProperty(klass, 'selectValueFor', {
231
+ value: function (value, whisp_action) {
232
+ var w;
233
+ w = findWhisperer(whisp_action);
234
+ querry.call(w, null, function (html_text) {
235
+ switch (value) {
236
+ case '#1':
237
+ w.select($(html_text).find('div:first'));
238
+ break;
239
+ default:
240
+ w.select($(html_text).find('div[data-value="'+value+'"]'));
241
+ break;
242
+ }
243
+ });
244
+ }
245
+ });
246
+
247
+ return Object.freeze(klass);
248
+ })();
@@ -52,7 +52,8 @@ module ActionView
52
52
  content << %Q( type="text" value="#{options[:text]}">)
53
53
  content << %Q(<span class="dropdown_button">\u25BE</span>)
54
54
  content_tag :span, content.html_safe, 'data-url' => (options[:url]||AssocWhisperer.def_url),
55
- 'data-action' => data_action, 'class' => 'assoc_whisperer'
55
+ 'data-action' => data_action, 'data-client-side' => (options[:client_side] && 'true'),
56
+ 'class' => 'assoc_whisperer'
56
57
  end
57
58
 
58
59
  private
@@ -17,8 +17,8 @@ module ActionView
17
17
  content << %Q( id="#{text_tag_id}" name="#{text_tag_name}" size="#{@options[:size]||12}")
18
18
  content << %Q( type="text" value="#{text_tag_value}">)
19
19
  content << %Q(<span class="dropdown_button">\u25BE</span>)
20
- content_tag :span, content.html_safe, 'data-url' => @options[:url],
21
- 'data-action' => @action, 'class' => 'assoc_whisperer'
20
+ content_tag :span, content.html_safe, 'data-url' => @options[:url], 'data-action' => @action,
21
+ 'data-client-side' => (@options[:client_side] && 'true'), 'class' => 'assoc_whisperer'
22
22
  end
23
23
 
24
24
  private
@@ -1,5 +1,5 @@
1
1
  # encoding: utf-8
2
2
  module AssocWhisperer
3
- VERSION = "1.0.4"
3
+ VERSION = "1.1.0"
4
4
 
5
5
  end
metadata CHANGED
@@ -1,41 +1,41 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: assoc_whisperer
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.4
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ondřej Želazko
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-06-27 00:00:00.000000000 Z
11
+ date: 2014-10-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ! '>='
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: '0'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ! '>='
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ! '>='
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
33
  version: '0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ! '>='
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
41
  description: You can associate two models together, while user inputs e.g. name and
@@ -46,12 +46,12 @@ executables: []
46
46
  extensions: []
47
47
  extra_rdoc_files: []
48
48
  files:
49
- - .gitignore
49
+ - ".gitignore"
50
50
  - Gemfile
51
51
  - LICENSE.txt
52
52
  - README.md
53
53
  - Rakefile
54
- - app/assets/javascripts/assoc_whisp.coffee
54
+ - app/assets/javascripts/assoc_whisp.js
55
55
  - app/assets/stylesheets/assoc_whisp_example.css
56
56
  - app/helpers/action_view/helpers/assoc_whisperer_helper.rb
57
57
  - app/helpers/action_view/helpers/tags/assoc_whisperer_field.rb
@@ -70,17 +70,17 @@ require_paths:
70
70
  - lib
71
71
  required_ruby_version: !ruby/object:Gem::Requirement
72
72
  requirements:
73
- - - ! '>='
73
+ - - ">="
74
74
  - !ruby/object:Gem::Version
75
75
  version: '0'
76
76
  required_rubygems_version: !ruby/object:Gem::Requirement
77
77
  requirements:
78
- - - ! '>='
78
+ - - ">="
79
79
  - !ruby/object:Gem::Version
80
80
  version: '0'
81
81
  requirements: []
82
82
  rubyforge_project:
83
- rubygems_version: 2.1.11
83
+ rubygems_version: 2.2.2
84
84
  signing_key:
85
85
  specification_version: 4
86
86
  summary: Rails tag assoc_whisperer for forms
@@ -1,179 +0,0 @@
1
- document.AssocWhispering = {
2
- # Stores all instantiated Whisperer objects.
3
- all: []
4
- # Any active Whisperer#getList method holds this variable in true state.
5
- working: false
6
- # Return a Whisperer instance by its data-action attribute.
7
- find: (action) ->
8
- for w in document.AssocWhispering.all
9
- return w if w._action==action
10
- return null
11
- # Makes a whisperer (found by its data-action) to select the given text, if present.
12
- selectTextFor: (text, whisp_action) ->
13
- w = document.AssocWhispering.find(whisp_action)
14
- w._last_input = '§'
15
- w.getList('', false, ->
16
- w.selectText(text)
17
- )
18
- # Makes a whisperer (found by its data-action) to select the given value, if present.
19
- # Selects the first value if the given value is '#1'.
20
- selectValueFor: (value, whisp_action) ->
21
- w = document.AssocWhispering.find(whisp_action)
22
- w._last_input = '§'
23
- w.getList('', false, ->
24
- switch value
25
- when '#1' then w.select(w._list_tag.find('div:first'))
26
- else w.selectValue(value)
27
- )
28
- }
29
-
30
- $(document).ready ->
31
- # attach all whisperers to after document id loaded
32
- $('.assoc_whisperer').each (i, el) ->
33
- w = new Whisperer(el)
34
- document.AssocWhispering.all.push(w)
35
-
36
- # This is the class that is instantiated and hold behind a tag with 'assoc_whisperer' css class.
37
- # It holds an action and url that is supposed to call when user changes the input text field.
38
- # Request is sent 700ms after last input key hit.
39
- # On success response attaches the list and shows it.
40
- # One can click on the drop down button to have the list generated for empty input (ie. '' string).
41
- class Whisperer
42
- _tag: null
43
- _text_field: null
44
- _value_field: null
45
- _list_tag: null
46
- _action: null
47
- _url: null
48
-
49
- _timeout: null
50
- _last_input: '§'
51
- _is_filled: false
52
- _focus: null
53
-
54
- # Constructor takes the element tag, that it shoud hang to (and which holds all the settings).
55
- constructor: (element) ->
56
- @_tag = $(element)
57
- @_action = @_tag.attr('data-action')
58
- @_url = @_tag.attr('data-url')
59
- @_text_field = @_tag.children('.text_field')
60
- @_value_field = @_tag.children('.value_field')
61
-
62
- @_text_field.keyup @text_field_keyup
63
- @_text_field.click => @_text_field.select()
64
- @_text_field.focus => @_focus = 'text_field'
65
- @_text_field.blur @controls_blur
66
- @_tag.children('.dropdown_button').click @dropdown_button_click
67
-
68
- @_is_filled = @_value_field.val()!=''
69
-
70
- ########################## E V E N T S ##############################################
71
-
72
- # Catchs ever key pressed to send a request only after the last one (applying 700ms timeout).
73
- text_field_keyup: (e) =>
74
- if e.keyCode==40 #down
75
- return
76
- else if e.keyCode==27 #escape
77
- @removeList()
78
- return
79
- input = @_text_field.val()
80
- clearTimeout(@_timeout) if @_timeout
81
- @setUnfilled() if input!=@_last_input && @_is_filled
82
- if input==''
83
- @_last_input = input
84
- @removeList()
85
- else
86
- fnc = => @getList(input)
87
- @_timeout = setTimeout(fnc, 700)
88
-
89
- # A 'button' to show the whole menu list (like entering an empty input).
90
- dropdown_button_click: =>
91
- hold_last = @_last_input
92
- @_last_input = '§'
93
- if @_is_filled
94
- @getList('', true)
95
- @_last_input = hold_last
96
- else
97
- @getList(@_text_field.val(), true)
98
-
99
- # Called by both text_field and list, to ensure that if focused anything else, the list hides itself.
100
- controls_blur: =>
101
- @_focus = null
102
- setTimeout( =>
103
- @removeList() unless @_focus
104
- ,100)
105
-
106
- ########################## L I S T ################################################
107
-
108
- # An Ajax request sent to a server for given url with given action and input as params.
109
- # Fires only if input changed. Before the request it visualy deactivates the list, hides it on error.
110
- # If success, attaches the list and fires onShow callback, if defined.
111
- getList: (input, focus=false, onShown=null) =>
112
- return if input==@_last_input
113
- @_last_input = input
114
- @deactivateList()
115
- document.AssocWhispering.working = true;
116
- $.ajax(@_tag.attr('data-url'),
117
- type: 'GET'
118
- dataType: 'html'
119
- data: {data_action: @_tag.attr('data-action'), input: input}
120
- error: @removeList
121
- success: (data) =>
122
- @showList(data)
123
- @_list_tag.focus() if focus
124
- onShown() if onShown
125
- document.AssocWhispering.working = false;
126
- )
127
-
128
- # Attaches a list to html document, within the Whisperer's tag and sets its position to be under the input text field.
129
- showList: (data) =>
130
- @removeList()
131
- @_list_tag = $(data)
132
- @_list_tag.css('min-width', @_tag.width())
133
- @_list_tag.css('left', @_tag.offset().left)
134
- @_list_tag.css('top', @_tag.position().top + @_tag.outerHeight())
135
-
136
- @_tag.append(@_list_tag)
137
- @_list_tag.focus => @_focus = 'list'
138
- @_list_tag.blur @controls_blur
139
-
140
- rows = @_list_tag.find('div')
141
- rows.click (el) => @select($(el.currentTarget))
142
-
143
- # Adds a class 'inactive' to list to make it visualy distinguishable.
144
- deactivateList: =>
145
- return unless @_list_tag
146
- @_list_tag.addClass('inactive')
147
-
148
- # Detaches list from html, ie. hides it.
149
- removeList: =>
150
- return unless @_list_tag
151
- @_list_tag.detach()
152
- @_list_tag = null
153
-
154
- ########################## S E L E C T I O N ############################################
155
-
156
- # Selects given row and sets its value to the hidden value field and removes the list.
157
- select: (row) =>
158
- return unless row.length==1
159
- text = row.text()
160
- @_text_field.val(text)
161
- @_value_field.val(row.attr('data-value'))
162
- @_last_input = text
163
- @_is_filled = true
164
- @_text_field.removeClass('unfilled')
165
- @removeList()
166
-
167
- # Selects row by given value.
168
- selectValue: (value) =>
169
- @select(@_list_tag.find('div[data-value='+value+']'))
170
-
171
- # Selects row by given text label.
172
- selectText: (text) =>
173
- @select(@_list_tag.find('div:contains("'+text+'")'))
174
-
175
- # Sets Whisperer to 'unfilled' state - no value has been selected (applies css class 'unfilled').
176
- setUnfilled: =>
177
- @_is_filled = false
178
- @_value_field.val('')
179
- @_text_field.addClass('unfilled')