assoc_whisperer 1.0.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 +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: []
|