assoc_whisperer 1.0.4 → 1.1.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,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')