assoc_whisperer 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f848a3a5d1cb39a1d9d6e9c484e4172f37e359fb
4
+ data.tar.gz: bb2e200d77eeef53c57261290b41f038aa6019ca
5
+ SHA512:
6
+ metadata.gz: 73df66f03af82aff21ff584edd67092a1879925c60d94654d563218b94c6f6e40e0f797b15313b9db264f527f19db14db2a7a48d96d6ed9b42ed8c1025851534
7
+ data.tar.gz: 9803f688879fe6037989afebec937d80d6f7f22069b9c0949cd2b94e40c7ad5548ffb1ef5bdea214f6e5bba119914b701b2109a281b5ee954aed1425284c4fc4
data/.gitignore ADDED
@@ -0,0 +1,23 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
23
+ .idea
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in assoc_whisperer.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Ondřej Želazko
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # AssocWhisperer
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'assoc_whisperer'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install assoc_whisperer
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it ( https://github.com/[my-github-username]/assoc_whisperer/fork )
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1 @@
1
+ (function(){var e,t=function(e,t){return function(){return e.apply(t,arguments)}};document.AssocWhispering={all:[],working:false,find:function(e){var t,n,r,i;i=document.AssocWhispering.all;for(n=0,r=i.length;n<r;n++){t=i[n];if(t._action===e){return t}}return null},selectTextFor:function(e,t){var n;n=document.AssocWhispering.find(t);n._last_input="§";return n.getList("",false,function(){return n.selectText(e)})},selectValueFor:function(e,t){var n;n=document.AssocWhispering.find(t);n._last_input="§";return n.getList("",false,function(){switch(e){case"#1":return n.select(n._list_tag.find("span:first"));default:return n.selectValue(e)}})}};$(document).ready(function(){return $(".assoc_whisperer").each(function(t,n){var r;r=new e(n);return document.AssocWhispering.all.push(r)})});e=function(){function e(e){this.setUnfilled=t(this.setUnfilled,this);this.selectText=t(this.selectText,this);this.selectValue=t(this.selectValue,this);this.select=t(this.select,this);this.removeList=t(this.removeList,this);this.deactivateList=t(this.deactivateList,this);this.showList=t(this.showList,this);this.getList=t(this.getList,this);this.controls_blur=t(this.controls_blur,this);this.dropdown_button_click=t(this.dropdown_button_click,this);this.text_field_keyup=t(this.text_field_keyup,this);this._tag=$(e);this._action=this._tag.attr("data-action");this._url=this._tag.attr("data-url");this._text_field=this._tag.children(".text_field");this._value_field=this._tag.children(".value_field");this._text_field.keyup(this.text_field_keyup);this._text_field.click(function(e){return function(){return e._text_field.select()}}(this));this._text_field.focus(function(e){return function(){return e._focus="text_field"}}(this));this._text_field.blur(this.controls_blur);this._tag.children(".dropdown_button").click(this.dropdown_button_click);this._is_filled=this._value_field.val()!==""}e.prototype._tag=null;e.prototype._text_field=null;e.prototype._value_field=null;e.prototype._list_tag=null;e.prototype._action=null;e.prototype._url=null;e.prototype._timeout=null;e.prototype._last_input="§";e.prototype._is_filled=false;e.prototype._focus=null;e.prototype.text_field_keyup=function(e){var t,n;if(e.keyCode===40){return}else if(e.keyCode===27){this.removeList();return}n=this._text_field.val();if(this._timeout){clearTimeout(this._timeout)}if(n!==this._last_input&&this._is_filled){this.setUnfilled()}if(n===""){this._last_input=n;return this.removeList()}else{t=function(e){return function(){return e.getList(n)}}(this);return this._timeout=setTimeout(t,700)}};e.prototype.dropdown_button_click=function(){this._last_input="§";if(this._is_filled){return this.getList("",true)}else{return this.getList(this._text_field.val(),true)}};e.prototype.controls_blur=function(){this._focus=null;return setTimeout(function(e){return function(){if(!e._focus){return e.removeList()}}}(this),100)};e.prototype.getList=function(e,t,n){if(t==null){t=false}if(n==null){n=null}if(e===this._last_input){return}this._last_input=e;this.deactivateList();document.AssocWhispering.working=true;return $.ajax(this._tag.attr("data-url"),{type:"GET",dataType:"html",data:{data_action:this._tag.attr("data-action"),input:e},error:this.removeList,success:function(e){return function(r){e.showList(r);if(t){e._list_tag.focus()}if(n){n()}return document.AssocWhispering.working=false}}(this)})};e.prototype.showList=function(e){var t;this.removeList();this._list_tag=$(e);this._list_tag.css("min-width",this._tag.width());this._list_tag.css("left",this._tag.offset().left);this._list_tag.css("top",this._tag.position().top+this._tag.outerHeight());this._tag.append(this._list_tag);this._list_tag.focus(function(e){return function(){return e._focus="list"}}(this));this._list_tag.blur(this.controls_blur);t=this._list_tag.find("span");return t.click(function(e){return function(t){return e.select($(t.currentTarget))}}(this))};e.prototype.deactivateList=function(){if(!this._list_tag){return}return this._list_tag.addClass("inactive")};e.prototype.removeList=function(){if(!this._list_tag){return}this._list_tag.detach();return this._list_tag=null};e.prototype.select=function(e){var t;if(e.length!==1){return}t=e.text();this._text_field.val(t);this._value_field.val(e.attr("data-value"));this._last_input=t;this._is_filled=true;this._text_field.removeClass("unfilled");return this.removeList()};e.prototype.selectValue=function(e){return this.select(this._list_tag.find("span[data-value="+e+"]"))};e.prototype.selectText=function(e){return this.select(this._list_tag.find('span:contains("'+e+'")'))};e.prototype.setUnfilled=function(){this._is_filled=false;this._value_field.val("");return this._text_field.addClass("unfilled")};return e}()}).call(this)
@@ -0,0 +1,177 @@
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('span: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
+ @_last_input = '§'
92
+ if @_is_filled
93
+ @getList('', true)
94
+ else
95
+ @getList(@_text_field.val(), true)
96
+
97
+ # Called by both text_field and list, to ensure that if focused anything else, the list hides itself.
98
+ controls_blur: =>
99
+ @_focus = null
100
+ setTimeout( =>
101
+ @removeList() unless @_focus
102
+ ,100)
103
+
104
+ ########################## L I S T ################################################
105
+
106
+ # An Ajax request sent to a server for given url with given action and input as params.
107
+ # Fires only if input changed. Before the request it visualy deactivates the list, hides it on error.
108
+ # If success, attaches the list and fires onShow callback, if defined.
109
+ getList: (input, focus=false, onShown=null) =>
110
+ return if input==@_last_input
111
+ @_last_input = input
112
+ @deactivateList()
113
+ document.AssocWhispering.working = true;
114
+ $.ajax(@_tag.attr('data-url'),
115
+ type: 'GET'
116
+ dataType: 'html'
117
+ data: {data_action: @_tag.attr('data-action'), input: input}
118
+ error: @removeList
119
+ success: (data) =>
120
+ @showList(data)
121
+ @_list_tag.focus() if focus
122
+ onShown() if onShown
123
+ document.AssocWhispering.working = false;
124
+ )
125
+
126
+ # Attaches a list to html document, within the Whisperer's tag and sets its position to be under the input text field.
127
+ showList: (data) =>
128
+ @removeList()
129
+ @_list_tag = $(data)
130
+ @_list_tag.css('min-width', @_tag.width())
131
+ @_list_tag.css('left', @_tag.offset().left)
132
+ @_list_tag.css('top', @_tag.position().top + @_tag.outerHeight())
133
+
134
+ @_tag.append(@_list_tag)
135
+ @_list_tag.focus => @_focus = 'list'
136
+ @_list_tag.blur @controls_blur
137
+
138
+ rows = @_list_tag.find('span')
139
+ rows.click (el) => @select($(el.currentTarget))
140
+
141
+ # Adds a class 'inactive' to list to make it visualy distinguishable.
142
+ deactivateList: =>
143
+ return unless @_list_tag
144
+ @_list_tag.addClass('inactive')
145
+
146
+ # Detaches list from html, ie. hides it.
147
+ removeList: =>
148
+ return unless @_list_tag
149
+ @_list_tag.detach()
150
+ @_list_tag = null
151
+
152
+ ########################## S E L E C T I O N ############################################
153
+
154
+ # Selects given row and sets its value to the hidden value field and removes the list.
155
+ select: (row) =>
156
+ return unless row.length==1
157
+ text = row.text()
158
+ @_text_field.val(text)
159
+ @_value_field.val(row.attr('data-value'))
160
+ @_last_input = text
161
+ @_is_filled = true
162
+ @_text_field.removeClass('unfilled')
163
+ @removeList()
164
+
165
+ # Selects row by given value.
166
+ selectValue: (value) =>
167
+ @select(@_list_tag.find('span[data-value='+value+']'))
168
+
169
+ # Selects row by given text label.
170
+ selectText: (text) =>
171
+ @select(@_list_tag.find('span:contains("'+text+'")'))
172
+
173
+ # Sets Whisperer to 'unfilled' state - no value has been selected (applies css class 'unfilled').
174
+ setUnfilled: =>
175
+ @_is_filled = false
176
+ @_value_field.val('')
177
+ @_text_field.addClass('unfilled')
@@ -0,0 +1,48 @@
1
+ .assoc_whisperer {
2
+ border: inset 2px lightgrey;
3
+ background-color: #e8d7d2;
4
+ }
5
+ .assoc_whisperer .text_field {
6
+ border: none;
7
+ background-color: transparent;
8
+ padding: 1px;
9
+ }
10
+ .assoc_whisperer .unfilled {
11
+ color: darkred;
12
+ }
13
+ .assoc_whisperer .dropdown_button {
14
+ padding-left: 2px;
15
+ padding-right: 2px;
16
+ border: 2px lightgrey outset;
17
+ background-color: gainsboro;
18
+ font-size: 0.9em;
19
+ -webkit-touch-callout: none;
20
+ -webkit-user-select: none;
21
+ -khtml-user-select: none;
22
+ -moz-user-select: none;
23
+ -ms-user-select: none;
24
+ user-select: none;
25
+ cursor: default;
26
+ }
27
+ .assoc_whisperer .list {
28
+ max-height: 300px;
29
+ overflow-y: scroll;
30
+ border: 1px solid gray;
31
+ position: absolute;
32
+ background-color: #e8d7d2;
33
+ font-weight: normal;
34
+ }
35
+ .assoc_whisperer .list span {
36
+ display: table-row;
37
+ text-align: center;
38
+ -webkit-touch-callout: none;
39
+ -webkit-user-select: none;
40
+ -khtml-user-select: none;
41
+ -moz-user-select: none;
42
+ -ms-user-select: none;
43
+ user-select: none;
44
+ cursor: default;
45
+ }
46
+ .assoc_whisperer .list span:hover {
47
+ background-color: #fffbb5;
48
+ }
@@ -0,0 +1,79 @@
1
+ module ActionView
2
+ module Helpers
3
+
4
+ module AssocWhispererHelper
5
+
6
+ # Createa an association whisperer tag, that will attach javascript object to self
7
+ # allowing user to select a text but stores something differend (mostly respective id).
8
+ #
9
+ # ==== Examples
10
+ # assoc_whisperer :task, :worker, :workers, size: 25, url: '/whisp'
11
+ # # => <span class="assoc_whisperer" data-action="workers" data-url="/whisp">
12
+ # <input class="value_field" id="task_worker_id" name="task[worker_id]" type="hidden" value="">
13
+ # <input autocomplete="off" class="text_field unfilled" id="task_worker_txt" name="task[worker_txt]" size="25" type="text" value="">
14
+ # <span class="dropdown_button">▾</span></span>
15
+ #
16
+ # Let's say your association looks like this:
17
+ #
18
+ # class Task
19
+ # belongs_to :worker, class_name: 'Worker', foreign_key: :manager
20
+ # end
21
+ # class Worker
22
+ # def full_name; end
23
+ # def manager_id; end
24
+ # end
25
+ #
26
+ # assoc_whisperer :task, :worker, :workers, name: :manager, value_method: :manager_id, text_method: :full_name
27
+ # # => <span class="assoc_whisperer" data-action="workers" data-url="">
28
+ # <input class="value_field" id="task_manager" name="task[manager]" type="hidden" value="">
29
+ # <input autocomplete="off" class="text_field unfilled" id="task_manager_txt" name="task[manager_txt]" size="12" type="text" value="">
30
+ # <span class="dropdown_button">▾</span></span>
31
+ #
32
+ # And the 'value_field' would be filled by +manager_id+ of +worker+ associated to +task+, 'text_field' by his +full_name+
33
+ #
34
+ def assoc_whisperer(object_name, method, data_action, options={})
35
+ wrapper_assoc_whisperer_assets
36
+
37
+ options[:url] = AssocWhisperer.def_url unless options.has_key? :url
38
+ Tags::AssocWhispererField.new(object_name, method, self, data_action, options).render
39
+ end
40
+
41
+ def assoc_whisperer_tag(name, data_action, options = {})
42
+ wrapper_assoc_whisperer_assets
43
+
44
+ sanitized_id = name.to_s.delete(']').gsub(/[^-a-zA-Z0-9:.]/, "_")
45
+ text_tag_name = name.to_s
46
+ text_tag_name.insert (text_tag_name[-1]==']' ? -2 : -1), '_txt'
47
+
48
+ content = %Q(<input class="value_field" id="#{sanitized_id}" name="#{name}" type="hidden")
49
+ content << %Q( value="#{options[:value]}">)
50
+ content << %Q(<input autocomplete="off" class="text_field#{' unfilled' if options[:value].blank?}")
51
+ content << %Q( id="#{sanitized_id}_txt" name="#{text_tag_name}" size="#{options[:size]||12}")
52
+ content << %Q( type="text" value="#{options[:text]}">)
53
+ content << %Q(<span class="dropdown_button">\u25BE</span>)
54
+ content_tag :span, content.html_safe, 'data-url' => (options[:url]||AssocWhisperer.def_url),
55
+ 'data-action' => data_action, 'class' => 'assoc_whisperer'
56
+ end
57
+
58
+ private
59
+
60
+ def wrapper_assoc_whisperer_assets
61
+ unless @assoc_whisp_attached
62
+ attach_method = AssocWhisperer.attach_assets_method
63
+ self.send attach_method if attach_method && self.respond_to?(attach_method)
64
+ @assoc_whisp_attached = true
65
+ end
66
+ end
67
+
68
+ end
69
+
70
+ class FormBuilder
71
+ def assoc_whisperer(method, data_action, options = {})
72
+ @template.assoc_whisperer @object_name, method, data_action, objectify_options(options)
73
+ end
74
+ end
75
+
76
+ autoload :AssocWhispererHelper
77
+ include AssocWhispererHelper
78
+ end
79
+ end
@@ -0,0 +1,88 @@
1
+ module ActionView
2
+ module Helpers
3
+ module Tags # :nodoc:
4
+ class AssocWhispererField < Base # :nodoc:
5
+
6
+ def initialize(object_name, method_name, template_object, action, options={})
7
+ @action = action
8
+ options[:value_method] = :id unless options.has_key? :value_method
9
+ options[:text_method] = :to_s unless options.has_key? :text_method
10
+ super object_name, method_name, template_object, options
11
+ end
12
+
13
+ def render
14
+ content = %Q(<input class="value_field" id="#{value_tag_id}" name="#{value_tag_name}" type="hidden")
15
+ content << %Q( value="#{value_tag_value}">)
16
+ content << %Q(<input autocomplete="off" class="text_field#{' unfilled' unless whispered_object}")
17
+ content << %Q( id="#{text_tag_id}" name="#{text_tag_name}" size="#{@options[:size]||12}")
18
+ content << %Q( type="text" value="#{text_tag_value}">)
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'
22
+ end
23
+
24
+ private
25
+
26
+ def whispered_object
27
+ @whispered_object ||= value @object
28
+ end
29
+
30
+ def whispered_object_attribute(method)
31
+ wo = whispered_object
32
+ wo.send method if wo && wo.respond_to?(method)
33
+ end
34
+
35
+ def text_tag_name
36
+ "#{@object_name}[#{text_sanitized_method_name}]"
37
+ end
38
+
39
+ def text_tag_id
40
+ "#{sanitized_object_name}_#{text_sanitized_method_name}"
41
+ end
42
+
43
+ def text_tag_value
44
+ text = @options[:text_method]
45
+ text = if text.respond_to? :call
46
+ text.call whispered_object
47
+ else
48
+ whispered_object_attribute(text)
49
+ end
50
+ ERB::Util.html_escape text
51
+ end
52
+
53
+ def text_sanitized_method_name
54
+ return @text_sanitized_method_name if defined? @text_sanitized_method_name
55
+ wsmn = if @options[:name]
56
+ "#{@options[:name]}_txt"
57
+ else
58
+ "#{@method_name}_txt"
59
+ end
60
+ @text_sanitized_method_name = wsmn.sub(/\?$/,"")
61
+ end
62
+
63
+ def value_tag_name
64
+ "#{@object_name}[#{value_sanitized_method_name}]"
65
+ end
66
+
67
+ def value_tag_id
68
+ "#{sanitized_object_name}_#{value_sanitized_method_name}"
69
+ end
70
+
71
+ def value_tag_value
72
+ whispered_object_attribute @options[:value_method]
73
+ end
74
+
75
+ def value_sanitized_method_name
76
+ return @value_sanitized_method_name if defined? @value_sanitized_method_name
77
+ wsmn = if @options[:name]
78
+ @options[:name].to_s
79
+ else
80
+ "#{@method_name}_#{@options[:value_method]}"
81
+ end
82
+ @value_sanitized_method_name = wsmn.sub(/\?$/,"")
83
+ end
84
+
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,9 @@
1
+ <%
2
+ text_method = local_assigns[:text] || :to_s
3
+ val_method = local_assigns[:val] || :id
4
+ %>
5
+ <div tabindex=0 class="list">
6
+ <% @objects.each do |o| %>
7
+ <span data-value="<%= o.send val_method %>"><%= o.send text_method %></span>
8
+ <% end %>
9
+ </div>
@@ -0,0 +1,5 @@
1
+ <div tabindex=0 class="list">
2
+ <% @rows.each do |r| %>
3
+ <span data-value="<%= r.last %>"><%= r.first %></span>
4
+ <% end %>
5
+ </div>
@@ -0,0 +1,23 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'assoc_whisperer/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "assoc_whisperer"
8
+ spec.version = AssocWhisperer::VERSION
9
+ spec.authors = ["Ondřej Želazko"]
10
+ spec.email = ["zelazk.o@email.cz"]
11
+ spec.summary = %q{Rails tag assoc_whisperer for forms}
12
+ spec.description = %q{You can associate two models together, while user inputs e.g. name and server recieves id}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler"
22
+ spec.add_development_dependency "rake"
23
+ end
@@ -0,0 +1,4 @@
1
+ # encoding: utf-8
2
+ module AssocWhisperer
3
+ VERSION = '1.0.0'
4
+ end
@@ -0,0 +1,18 @@
1
+ # encoding: utf-8
2
+ require "assoc_whisperer/version"
3
+
4
+ module AssocWhisperer
5
+
6
+ class << self
7
+ attr_accessor :def_url, :attach_assets_method
8
+ end
9
+
10
+ module Rails
11
+ class Engine < ::Rails::Engine
12
+ end
13
+ end
14
+
15
+ end
16
+
17
+ AssocWhisperer.def_url = '/whisp'
18
+ AssocWhisperer.attach_assets_method = :attach_assoc_whisperer_assets
metadata ADDED
@@ -0,0 +1,88 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: assoc_whisperer
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Ondřej Želazko
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-06-25 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: You can associate two models together, while user inputs e.g. name and
42
+ server recieves id
43
+ email:
44
+ - zelazk.o@email.cz
45
+ executables: []
46
+ extensions: []
47
+ extra_rdoc_files: []
48
+ files:
49
+ - ".gitignore"
50
+ - Gemfile
51
+ - LICENSE.txt
52
+ - README.md
53
+ - Rakefile
54
+ - app/assets/javascript/assoc_whisp.min.js
55
+ - app/assets/javascript/assoc_whispering.coffee
56
+ - app/assets/stylesheets/assoc_whisp_example.css
57
+ - app/helpers/action_view/helpers/assoc_whisperer_helper.rb
58
+ - app/helpers/action_view/helpers/tags/assoc_whisperer_field.rb
59
+ - app/views/assoc_whisperer/list.html.erb
60
+ - app/views/assoc_whisperer/list_array.html.erb
61
+ - assoc_whisperer.gemspec
62
+ - lib/assoc_whisperer.rb
63
+ - lib/assoc_whisperer/version.rb
64
+ homepage: ''
65
+ licenses:
66
+ - MIT
67
+ metadata: {}
68
+ post_install_message:
69
+ rdoc_options: []
70
+ require_paths:
71
+ - lib
72
+ required_ruby_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ required_rubygems_version: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ requirements: []
83
+ rubyforge_project:
84
+ rubygems_version: 2.2.2
85
+ signing_key:
86
+ specification_version: 4
87
+ summary: Rails tag assoc_whisperer for forms
88
+ test_files: []