assoc_whisperer 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +23 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +2 -0
- data/app/assets/javascript/assoc_whisp.min.js +1 -0
- data/app/assets/javascript/assoc_whispering.coffee +177 -0
- data/app/assets/stylesheets/assoc_whisp_example.css +48 -0
- data/app/helpers/action_view/helpers/assoc_whisperer_helper.rb +79 -0
- data/app/helpers/action_view/helpers/tags/assoc_whisperer_field.rb +88 -0
- data/app/views/assoc_whisperer/list.html.erb +9 -0
- data/app/views/assoc_whisperer/list_array.html.erb +5 -0
- data/assoc_whisperer.gemspec +23 -0
- data/lib/assoc_whisperer/version.rb +4 -0
- data/lib/assoc_whisperer.rb +18 -0
- metadata +88 -0
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
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 @@
|
|
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,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,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: []
|