krjs 0.5.5
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +7 -0
- data/MIT-LICENSE +21 -0
- data/README +76 -0
- data/Rakefile +10 -0
- data/init.rb +1 -0
- data/lib/krjs.rb +165 -0
- data/lib/krjs_with_debug_info.rb +242 -0
- data/test/krjs_test.rb +139 -0
- data/test/test_helper.rb +4 -0
- data/test/views/layouts/sample.rhtml +10 -0
- data/test/views/sample/form_test.rhtml +14 -0
- data/test/views/sample/index.rhtml +15 -0
- data/test/views/sample/on_remember_change.rjs +2 -0
- metadata +74 -0
data/CHANGELOG
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
Copyright (c) 2006 Chew Choon Keat
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
|
+
|
data/README
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
= KRJS - Keat's RJS
|
2
|
+
|
3
|
+
RJS is a great Ruby DSL to write javascript. However, its so tempting to write
|
4
|
+
RJS directly in the views, and soon the views would contain substantial controller knowledge (e.g. link_to_remote, link_to, etc)
|
5
|
+
|
6
|
+
KRJS attempts to solve that problem by allowing dynamic inclusion of AJAX calls on HTML elements. When a controller defines a method (based on naming convention) that handles a client-side event, the rendering engine will do the wiring automatically - when the event happens, an AJAX call will be made to the controller's method which would ideally reply with RJS and update portions of the document.
|
7
|
+
|
8
|
+
KRJS could potentially use behavior/selector style javascript instead of modifying elements itself - See http://www.lukeredpath.co.uk/index.php/2006/06/06/introducing-unobtrusive-javascript-for-rails and http://www.vivabit.com/bollocks/2006/02/09/rails-is-the-devil-in-your-client-side-shoulder.
|
9
|
+
|
10
|
+
== Examples
|
11
|
+
|
12
|
+
To see it in action, create a blank controller and a index.rhtml file:
|
13
|
+
./script/generate controller Sample index
|
14
|
+
|
15
|
+
In your view (app/views/sample/index.rhtml) write:
|
16
|
+
<%= form_tag({:action => 'submit'}, {:id => 'form'}) %>
|
17
|
+
<%= text_field 'account', 'login', :id => 'account-new-login' %><br />
|
18
|
+
<%= submit_tag 'Login' %>
|
19
|
+
<%= end_form_tag %>
|
20
|
+
|
21
|
+
In your controller (app/controllers/sample_controller.rb), write:
|
22
|
+
class SampleController < ApplicationController
|
23
|
+
def on_form_submit
|
24
|
+
render :update do |page|
|
25
|
+
page.insert_html :after, params[:dom_id], CGI.escapeHTML(params.inspect)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
Go to your browser, load the page and click on the submit button. The form
|
31
|
+
should be submitted via ajax (not a fullpage refresh) and rjs code should
|
32
|
+
update the page, right after the form (submit button)
|
33
|
+
|
34
|
+
You can also try adding this method to the controller:
|
35
|
+
def on_account_login_blur
|
36
|
+
render :update do |page|
|
37
|
+
page.insert_html :after, params[:dom_id], CGI.escapeHTML(params.inspect)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
View the page again, type in something in the textfield, press the TAB key (to lose focus on the input field). An AJAX call should be made to your controller, and the respective RJS will update the page.
|
42
|
+
|
43
|
+
== Explanation
|
44
|
+
|
45
|
+
Controller#on_XX_YY means the controller receives ajax requests when event "onYY" of element XX occurs. e.g. if YY is "focus", then the ajax request is sent during "onfocus" even of XX field.
|
46
|
+
Controller#on_XX_YY_ZZ means the controller receives ajax requests when field XX is modified - changes are polled every ZZ seconds instead of waiting for event to occur.
|
47
|
+
Note: if YY is 'form' or 'submit', XX is considered as a DOM ID for a form instead of a field. This impacts the :with parameter. i.e. for a field, the value is submitted; for a form, the whole form is submitted
|
48
|
+
|
49
|
+
== Installation
|
50
|
+
|
51
|
+
Go to your RAILS_ROOT directory and execute:
|
52
|
+
./script/plugin install http://choonkeat.svnrepository.com/svn/rails-plugins/krjs
|
53
|
+
|
54
|
+
== Testing
|
55
|
+
|
56
|
+
To test, go to your RAILS_ROOT directory and execute (1 line):
|
57
|
+
PLUGIN=krjs rake test:plugins
|
58
|
+
|
59
|
+
To be sure you test only KRJS, do it on a clean RAILS directory:
|
60
|
+
$ rails test_directory
|
61
|
+
$ cd test_directory
|
62
|
+
$ script/plugin install \
|
63
|
+
http://choonkeat.svnrepository.com/svn/rails-plugins/krjs
|
64
|
+
$ PLUGIN=krjs rake test:plugins
|
65
|
+
|
66
|
+
== License
|
67
|
+
|
68
|
+
KRJS is released under the MIT license.
|
69
|
+
|
70
|
+
== Author
|
71
|
+
|
72
|
+
Chew Choon Keat <choonkeat at gmail>
|
73
|
+
http://blog.yanime.org/
|
74
|
+
|
75
|
+
12 June 2006
|
76
|
+
|
data/Rakefile
ADDED
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'krjs'
|
data/lib/krjs.rb
ADDED
@@ -0,0 +1,165 @@
|
|
1
|
+
# KRJS (keat's rails java script)
|
2
|
+
#
|
3
|
+
# MIT license
|
4
|
+
|
5
|
+
module Krjs
|
6
|
+
# brought over from tag_helper (exist after 2.0.1)
|
7
|
+
BOOLEAN_ATTRIBUTES = Set.new(%w(disabled readonly multiple))
|
8
|
+
|
9
|
+
def self.included(base)
|
10
|
+
base.class_eval do
|
11
|
+
|
12
|
+
# just a public copy of the original tag options
|
13
|
+
# we need to overwrite this because InstanceTag and TagHelper
|
14
|
+
# are already bound to its private version.
|
15
|
+
# without this the alias_method_chain seems not to work well
|
16
|
+
def tag_options(options, escape = true)
|
17
|
+
unless options.blank?
|
18
|
+
attrs = []
|
19
|
+
if escape
|
20
|
+
options.each do |key, value|
|
21
|
+
next unless value
|
22
|
+
key = key.to_s
|
23
|
+
value = ::Krjs::BOOLEAN_ATTRIBUTES.include?(key) ? key : escape_once(value)
|
24
|
+
attrs << %(#{key}="#{value}")
|
25
|
+
end
|
26
|
+
else
|
27
|
+
attrs = options.map { |key, value| %(#{key}="#{value}") }
|
28
|
+
end
|
29
|
+
" #{attrs.sort * ' '}" unless attrs.empty?
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# adds an observer to the original tag method if needed
|
34
|
+
def tag_with_observer(*attrs) #name, options = nil, open = false
|
35
|
+
appended = observer(attrs[1])
|
36
|
+
return tag_without_observer(*attrs) << appended.to_s
|
37
|
+
end
|
38
|
+
|
39
|
+
# adds an observer to the original content_tag method if needed
|
40
|
+
def content_tag_with_observer(name, content, options = {})
|
41
|
+
appended = observer(options)
|
42
|
+
return content_tag_without_observer(name, content, options) + appended.to_s
|
43
|
+
end
|
44
|
+
|
45
|
+
# adds a remote function to the options if needed
|
46
|
+
def tag_options_with_remote_function(options, escape = true)
|
47
|
+
viewer, method_name, event_attr = viewer_method_eventattr(options) #unless options.include?
|
48
|
+
if method_name && event_attr && options[event_attr].nil?
|
49
|
+
options[event_attr] = remote_function(
|
50
|
+
:url => HashWithIndifferentAccess.new(options).merge({
|
51
|
+
:action => method_name,
|
52
|
+
:dom_id => options['id'],
|
53
|
+
:dom_index => split_dom_id(options['id'])[1],
|
54
|
+
}),
|
55
|
+
:with => (event_attr =~ /submit/ || method_name =~ /form/ ?
|
56
|
+
'(window.Form ? Form.serialize(this) : jQuery(this).serialize())' : "'dom_value=' + encodeURIComponent(this.value)")
|
57
|
+
) + "; return false;"
|
58
|
+
end
|
59
|
+
return tag_options_without_remote_function(options, escape = true)
|
60
|
+
end
|
61
|
+
|
62
|
+
# chain the new methods with the old ones
|
63
|
+
|
64
|
+
# tag => tag_with_observer
|
65
|
+
# original tag => tag_without_observer
|
66
|
+
# alias_method_chain :tag, :observer
|
67
|
+
|
68
|
+
# content_tag => content_tag_with_observer
|
69
|
+
# original content_tag => content_tag_without_observer
|
70
|
+
alias_method_chain :content_tag, :observer
|
71
|
+
|
72
|
+
# tag_options => tag_options_with_remote_function
|
73
|
+
# original tag_options => tag_option_without_remote_function
|
74
|
+
alias_method_chain :tag_options, :remote_function
|
75
|
+
|
76
|
+
protected
|
77
|
+
|
78
|
+
# creates the javascript needed to observe a form/field
|
79
|
+
# if necessary, otherwise returns nil
|
80
|
+
def observer(options)
|
81
|
+
viewer, method_name, event_attr = viewer_method_eventattr(options)
|
82
|
+
appended = nil
|
83
|
+
if event_attr =~ /^on(\w+)_(\d+)$/
|
84
|
+
on_evt = $1
|
85
|
+
freq = $2
|
86
|
+
observe_options = HashWithIndifferentAccess.new({
|
87
|
+
:url => HashWithIndifferentAccess.new(options).merge({
|
88
|
+
:action => method_name,
|
89
|
+
:dom_id => options['id'],
|
90
|
+
:dom_index => split_dom_id(options['id'])[1],
|
91
|
+
}),
|
92
|
+
:with => "'dom_value=' + Form.serialize($('#{options['id']}'))",
|
93
|
+
:frequency => freq.to_i,
|
94
|
+
})
|
95
|
+
if on_evt =~ /(form|submit)/
|
96
|
+
appended = observe_form(options['id'], observe_options)
|
97
|
+
else
|
98
|
+
appended = observe_field(options['id'], observe_options.merge({
|
99
|
+
:with => "'dom_value=' + encodeURIComponent($('#{options['id']}').value)",
|
100
|
+
:on => on_evt,
|
101
|
+
})
|
102
|
+
)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
appended
|
106
|
+
end
|
107
|
+
|
108
|
+
# there might be a better splitting policy yet?
|
109
|
+
def split_dom_id(dom_id)
|
110
|
+
dom_id = dom_id.to_s.tr('[]', '')
|
111
|
+
dom_id.split(/-/) # based on convention of dashed_dom_id plugin
|
112
|
+
end
|
113
|
+
|
114
|
+
# given a dom_id, retrieve the defined controller method (if any)
|
115
|
+
# e.g. on_student_submit, on_student_name_change, on_student_grade_focus
|
116
|
+
# if the controller has no methods of such naming conventions, we'll look
|
117
|
+
# to see if there are view templates of such filenames
|
118
|
+
def controller_method(ctrler, dom_id)
|
119
|
+
return nil if dom_id.nil?
|
120
|
+
array = split_dom_id(dom_id)
|
121
|
+
method_match = "on_#{Regexp.escape(array.first)}_"
|
122
|
+
method_match += "(#{Regexp.escape(array[2])}|field|submit)_" if not array[2].nil?
|
123
|
+
regexp = Regexp.new("^#{method_match}(.+)$")
|
124
|
+
ret = ctrler.methods.find{|x| x =~ regexp }
|
125
|
+
|
126
|
+
if ret.nil? && self.respond_to?(:base_path)
|
127
|
+
view_path = File.join(self.base_path, ctrler.controller_name) unless [self.base_path, ctrler.controller_name].include?(nil)
|
128
|
+
Dir.open(view_path) do |dir|
|
129
|
+
ret = dir.find{|x| x =~ regexp }.to_s.gsub(/\.[^\.]+$/, '')
|
130
|
+
end unless view_path.blank? || !(File.exist? view_path)
|
131
|
+
end
|
132
|
+
ret
|
133
|
+
end
|
134
|
+
|
135
|
+
# convenience method to obtain all 3 information
|
136
|
+
def viewer_method_eventattr(options)
|
137
|
+
viewer = self.respond_to?(:controller) ? self : @template_object
|
138
|
+
return [] unless viewer
|
139
|
+
method_name ||= controller_method(viewer.controller, options['id'])
|
140
|
+
event_attr ||= "on#{$1}" if method_name =~ /_([^_]+(|_\d+))$/
|
141
|
+
[viewer, method_name, event_attr]
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
module ActionView
|
148
|
+
module Helpers
|
149
|
+
module TagHelper
|
150
|
+
include Krjs
|
151
|
+
end
|
152
|
+
class InstanceTag
|
153
|
+
def method_missing(method, *args)
|
154
|
+
if @template_object && @template_object.respond_to?(method)
|
155
|
+
@template_object.send method, *args
|
156
|
+
else
|
157
|
+
super
|
158
|
+
end
|
159
|
+
end
|
160
|
+
include JavaScriptHelper
|
161
|
+
include PrototypeHelper
|
162
|
+
include Krjs
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
@@ -0,0 +1,242 @@
|
|
1
|
+
# KRJS (keat's rails java script)
|
2
|
+
#
|
3
|
+
# MIT license
|
4
|
+
|
5
|
+
$indent = 0
|
6
|
+
def log message
|
7
|
+
puts ' ' * $indent << message
|
8
|
+
end
|
9
|
+
|
10
|
+
def inc
|
11
|
+
$indent = $indent + 1
|
12
|
+
end
|
13
|
+
|
14
|
+
def dec
|
15
|
+
$indent = $indent - 1
|
16
|
+
end
|
17
|
+
|
18
|
+
#module ActionView
|
19
|
+
# module Helpers
|
20
|
+
#class InstanceTag
|
21
|
+
# include JavascriptHelper
|
22
|
+
#
|
23
|
+
# # reader method used by TagHelper below..
|
24
|
+
# def template_object
|
25
|
+
# @template_object
|
26
|
+
# end
|
27
|
+
##
|
28
|
+
# # required by JavascriptHelper
|
29
|
+
# def url_for(options)
|
30
|
+
# template_object.url_for(options)
|
31
|
+
# end
|
32
|
+
#end
|
33
|
+
|
34
|
+
#[TagHelper].each do |klass|
|
35
|
+
# klass.class_eval do
|
36
|
+
# include PrototypeHelper # this isn't so good?
|
37
|
+
|
38
|
+
module Krjs
|
39
|
+
def self.included(base)
|
40
|
+
base.class_eval do
|
41
|
+
def tag_options(options)
|
42
|
+
cleaned_options = options.reject { |key, value| value.nil? }
|
43
|
+
unless cleaned_options.empty?
|
44
|
+
" " + cleaned_options.symbolize_keys.map { |key, value|
|
45
|
+
%(#{key}="#{html_escape(value.to_s)}")
|
46
|
+
}.sort.join(" ")
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# adds observer to the original tag method
|
51
|
+
def tag_with_observer(*attrs) #name, options = nil, open = false
|
52
|
+
#debugger
|
53
|
+
puts
|
54
|
+
log '#tag_with_observer ' << attrs.inspect
|
55
|
+
inc
|
56
|
+
appended = observer(attrs[1])
|
57
|
+
res = tag_without_observer(*attrs) << appended.to_s
|
58
|
+
dec
|
59
|
+
log '#end_tag_with_observer'
|
60
|
+
return res
|
61
|
+
end
|
62
|
+
|
63
|
+
# adds observer to the original content_tag method
|
64
|
+
def content_tag_with_observer(name, content, options = {})
|
65
|
+
log '#content_tag_with_observer'
|
66
|
+
inc
|
67
|
+
appended = observer(options)
|
68
|
+
res = content_tag_without_observer(name, content, options) + appended.to_s
|
69
|
+
dec
|
70
|
+
log '#end_content_tag_with_observer'
|
71
|
+
return res
|
72
|
+
end
|
73
|
+
|
74
|
+
# adds a remote function to the options if needed
|
75
|
+
def tag_options_with_remote_function(options)
|
76
|
+
#debugger
|
77
|
+
log '#tag_options_with_remotefunction' << options.inspect
|
78
|
+
inc
|
79
|
+
viewer, method_name, event_attr = viewer_method_eventattr(options) #unless options.include?
|
80
|
+
if method_name && event_attr && options[event_attr].nil?
|
81
|
+
# viewer.controller.logger.debug "options before: #{options.inspect}"
|
82
|
+
options[event_attr] = remote_function(
|
83
|
+
:url => HashWithIndifferentAccess.new(options).merge({
|
84
|
+
:action => method_name,
|
85
|
+
:dom_id => options['id'],
|
86
|
+
:dom_index => split_dom_id(options['id'])[1],
|
87
|
+
}),
|
88
|
+
:with => (event_attr =~ /submit/ || method_name =~ /form/ ?
|
89
|
+
'Form.serialize(this)' : "'dom_value=' + encodeURIComponent(this.value)")
|
90
|
+
) + "; return false;"
|
91
|
+
# viewer.controller.logger.debug "options after: #{options.inspect}"
|
92
|
+
# return false is important to neuter the browser event
|
93
|
+
end
|
94
|
+
result = tag_options_without_remote_function(options)
|
95
|
+
dec
|
96
|
+
log '#end_tag_options_with_remotefunction'
|
97
|
+
|
98
|
+
return result
|
99
|
+
end
|
100
|
+
|
101
|
+
# just a copy
|
102
|
+
def tag_options(options)
|
103
|
+
cleaned_options = convert_booleans(options.stringify_keys.reject {|key, value| value.nil?})
|
104
|
+
' ' + cleaned_options.map {|key, value| %(#{key}="#{escape_once(value)}")}.sort * ' ' unless cleaned_options.empty?
|
105
|
+
end
|
106
|
+
|
107
|
+
# chain the new methods with the old ones
|
108
|
+
|
109
|
+
# tag => tag_with_observer
|
110
|
+
# original tag => tag_without_observer
|
111
|
+
alias_method_chain :tag, :observer
|
112
|
+
|
113
|
+
# content_tag => content_tag_with_observer
|
114
|
+
# original content_tag => content_tag_without_observer
|
115
|
+
alias_method_chain :content_tag, :observer
|
116
|
+
|
117
|
+
|
118
|
+
# tag_options => tag_options_with_remote_function
|
119
|
+
# original tag_options => tag_option_without_remote_function
|
120
|
+
alias_method_chain :tag_options, :remote_function
|
121
|
+
|
122
|
+
protected
|
123
|
+
|
124
|
+
# creates the javascript needed to observe a form/field
|
125
|
+
# if necessary, otherwise returns nil
|
126
|
+
def observer(options)
|
127
|
+
log '#observer'
|
128
|
+
inc
|
129
|
+
viewer, method_name, event_attr = viewer_method_eventattr(options)
|
130
|
+
appended = nil
|
131
|
+
if event_attr =~ /^on(\w+)_(\d+)$/
|
132
|
+
on_evt = $1
|
133
|
+
freq = $2
|
134
|
+
observe_options = HashWithIndifferentAccess.new({
|
135
|
+
:url => HashWithIndifferentAccess.new(options).merge({
|
136
|
+
:action => method_name,
|
137
|
+
:dom_id => options['id'],
|
138
|
+
:dom_index => split_dom_id(options['id'])[1],
|
139
|
+
}),
|
140
|
+
:with => "'dom_value=' + Form.serialize($('#{options['id']}'))",
|
141
|
+
:frequency => freq.to_i,
|
142
|
+
})
|
143
|
+
if on_evt =~ /(form|submit)/
|
144
|
+
appended = observe_form(options['id'], observe_options)
|
145
|
+
else
|
146
|
+
appended = observe_field(options['id'], observe_options.merge({
|
147
|
+
:with => "'dom_value=' + encodeURIComponent($('#{options['id']}').value)",
|
148
|
+
:on => on_evt,
|
149
|
+
})
|
150
|
+
)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
dec
|
154
|
+
log '#end_observer'
|
155
|
+
appended
|
156
|
+
end
|
157
|
+
|
158
|
+
# there might be a better splitting policy yet?
|
159
|
+
def split_dom_id(dom_id)
|
160
|
+
dom_id = dom_id.tr('[]', '')
|
161
|
+
dom_id.to_s.split(/-/) # based on convention of dashed_dom_id plugin
|
162
|
+
end
|
163
|
+
|
164
|
+
# given a dom_id, retrieve the defined controller method (if any)
|
165
|
+
# e.g. on_student_submit, on_student_name_change, on_student_grade_focus
|
166
|
+
# if the controller has no methods of such naming conventions, we'll look
|
167
|
+
# to see if there are view templates of such filenames
|
168
|
+
def controller_method(ctrler, dom_id)
|
169
|
+
log '#controller_method'
|
170
|
+
inc
|
171
|
+
if dom_id.nil?
|
172
|
+
dec
|
173
|
+
log '#end_controoller_method'
|
174
|
+
return nil
|
175
|
+
end
|
176
|
+
array = split_dom_id(dom_id)
|
177
|
+
method_match = "on_#{Regexp.escape(array.first)}_"
|
178
|
+
method_match += "(#{Regexp.escape(array[2])}|field|submit)_" if not array[2].nil?
|
179
|
+
regexp = Regexp.new("^#{method_match}(.+)$")
|
180
|
+
ret = ctrler.methods.find{|x| x =~ regexp }
|
181
|
+
# ctrler.logger.debug "match '#{method_match}' finds '#{ret}'"
|
182
|
+
if ret.nil? && self.respond_to?(:base_path)
|
183
|
+
view_path = File.join(self.base_path, ctrler.controller_name)
|
184
|
+
# ctrler.logger.debug "looking to match within #{view_path}"
|
185
|
+
Dir.open(view_path) do |dir|
|
186
|
+
ret = dir.find{|x| x =~ regexp }.to_s.gsub(/\.[^\.]+$/, '')
|
187
|
+
end unless not File.exist? view_path
|
188
|
+
end
|
189
|
+
dec
|
190
|
+
log '#end_controoller_method'
|
191
|
+
ret
|
192
|
+
end
|
193
|
+
|
194
|
+
# convenience method to obtain all 3 information
|
195
|
+
def viewer_method_eventattr(options)
|
196
|
+
log '#viewer_method_eventattr'
|
197
|
+
inc
|
198
|
+
#viewer = nil
|
199
|
+
viewer = self.respond_to?(:controller) ? self : self.template_object
|
200
|
+
|
201
|
+
#controller = eval("controller")
|
202
|
+
#controller ||= eval("template_object.controller")
|
203
|
+
method_name ||= controller_method(viewer.controller, options['id'])
|
204
|
+
# viewer.controller.logger.debug "using method_name #{method_name.inspect}" if method_name
|
205
|
+
event_attr ||= "on#{$1}" if method_name =~ /_([^_]+(|_\d+))$/
|
206
|
+
# viewer.controller.logger.debug "using event_attr #{event_attr.inspect}" if event_attr
|
207
|
+
#puts '#end viewer_method_eventattr'
|
208
|
+
dec
|
209
|
+
log "#end_viewer_method_eventattr #{method_name} #{event_attr}"
|
210
|
+
[viewer, method_name, event_attr]
|
211
|
+
end
|
212
|
+
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
# end
|
217
|
+
# end
|
218
|
+
#end
|
219
|
+
#
|
220
|
+
#
|
221
|
+
module ActionView
|
222
|
+
module Helpers
|
223
|
+
module TagHelper
|
224
|
+
public :tag_options
|
225
|
+
include Krjs
|
226
|
+
end
|
227
|
+
class InstanceTag
|
228
|
+
def template_object; @template_object; end
|
229
|
+
def url_for(options); template_object.url_for(options); end
|
230
|
+
include JavaScriptHelper
|
231
|
+
include PrototypeHelper
|
232
|
+
public :tag_options
|
233
|
+
include Krjs
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
#module ActionView
|
239
|
+
# class Base
|
240
|
+
# include Krjs
|
241
|
+
# end
|
242
|
+
#end
|
data/test/krjs_test.rb
ADDED
@@ -0,0 +1,139 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper'
|
2
|
+
|
3
|
+
class FakeView < ActionView::Base;end
|
4
|
+
|
5
|
+
class SampleController < ActionController::Base
|
6
|
+
def index;
|
7
|
+
end
|
8
|
+
|
9
|
+
def alternate_index;
|
10
|
+
render :action => 'index';
|
11
|
+
end
|
12
|
+
|
13
|
+
def on_test_krjs_form_change; # whole form will be submitted here;
|
14
|
+
end
|
15
|
+
|
16
|
+
def on_form_submit
|
17
|
+
render :update do |page|
|
18
|
+
page.insert_html :after, params[:dom_id], CGI.escapeHTML(params.inspect)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def on_account_login_blur
|
23
|
+
render :update do |page|
|
24
|
+
page.insert_html :after, params[:dom_id], CGI.escapeHTML(params.inspect)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def on_account_password_change_3
|
29
|
+
render :update do |page|
|
30
|
+
page.insert_html :after, params[:dom_id], CGI.escapeHTML(params.inspect)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def on_account_comments_change
|
35
|
+
render :update do |page|
|
36
|
+
page.insert_html :after, params[:dom_id], CGI.escapeHTML(params.inspect)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def on_account_country_change_9
|
41
|
+
render :update do |page|
|
42
|
+
page.insert_html :after, params[:dom_id], CGI.escapeHTML(params.inspect)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
SampleController.view_paths=(File.join(File.dirname(__FILE__), 'views'))
|
48
|
+
|
49
|
+
ActionController::Routing::Routes.draw do |map|
|
50
|
+
map.connect ':controller/:action/:id'
|
51
|
+
end
|
52
|
+
|
53
|
+
class SampleControllerTest < ActionController::TestCase
|
54
|
+
|
55
|
+
def setup
|
56
|
+
@fakeview = FakeView.new # a view to test the TagHelper methods have been chained
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_presence_of_instance_methods
|
60
|
+
# removed tag_without_observer since Choon commented # alias_method_chain :tag, :observer
|
61
|
+
%w{tag_options tag_options_with_remote_function tag_options_without_remote_function tag tag_with_observer
|
62
|
+
content_tag content_tag_with_observer content_tag_without_observer}.each do |instance_method|
|
63
|
+
assert_respond_to @fakeview, instance_method
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def test_basic
|
68
|
+
get :index
|
69
|
+
assert_not_ajaxified 'form', 'change', 'form submit'
|
70
|
+
assert_ajaxified 'form', 'submit', 'form submit'
|
71
|
+
|
72
|
+
assert_not_ajaxified 'account-new-login', 'focus', 'login onblur'
|
73
|
+
assert_ajaxified 'account-new-login', 'blur', 'login onblur'
|
74
|
+
|
75
|
+
assert_not_ajaxified 'account-new-password', 'change', 'password onchange'
|
76
|
+
# assert_ajaxified 'account-new-password', 'observe', 'password onchange'
|
77
|
+
|
78
|
+
assert_not_ajaxified 'remember', 'blur', 'remember onblur'
|
79
|
+
|
80
|
+
assert_not_ajaxified 'account_comments', 'blur', 'account_comments onblur'
|
81
|
+
assert_ajaxified 'account_comments', 'change', 'account_comments onchange'
|
82
|
+
|
83
|
+
assert_not_ajaxified 'account_country', 'change', 'account_country onblur'
|
84
|
+
assert_ajaxified 'account_country', 'observe', 'account_country observe'
|
85
|
+
|
86
|
+
# external .rjs file - NOTE: I don't know if external rjs ever worked, but it doesn't work now
|
87
|
+
# assert_ajaxified 'remember', 'change', 'remember onchange'
|
88
|
+
end
|
89
|
+
|
90
|
+
def test_form
|
91
|
+
get :form_test
|
92
|
+
assert @response.body =~ /Form.serialize/, "Ajaxified form must submit as whole, not merely dom_value"
|
93
|
+
assert_ajaxified 'test_krjs_form', 'change', 'on_test_krjs_form_change'
|
94
|
+
end
|
95
|
+
|
96
|
+
def test_optional_action
|
97
|
+
|
98
|
+
end
|
99
|
+
|
100
|
+
def test_optional_callback
|
101
|
+
|
102
|
+
end
|
103
|
+
|
104
|
+
protected
|
105
|
+
|
106
|
+
def assert_ajaxified(dom_id, event, assert_comments=nil)
|
107
|
+
ttag, observer = rendered_html(dom_id, event)
|
108
|
+
case event
|
109
|
+
when 'observe'
|
110
|
+
assert(!observer.blank?, "#{assert_comments}\n#{ttag} #{observer}\n\n#{@response.body}")
|
111
|
+
else
|
112
|
+
assert((ttag =~ / on#{event}\=/), "#{assert_comments}\n#{ttag} #{observer}\n\n#{@response.body}")
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def assert_not_ajaxified(dom_id, event, assert_comments=nil)
|
117
|
+
tag, observer = rendered_html(dom_id, event)
|
118
|
+
case event
|
119
|
+
when 'observe'
|
120
|
+
assert(observer.blank?, "#{assert_comments}\n#{tag} #{observer}\n\n#{@response.body}")
|
121
|
+
else
|
122
|
+
assert_nil((tag =~ / on#{event}\=/), "#{assert_comments}\n#{tag} #{observer}\n\n#{@response.body}")
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# returns an array,
|
127
|
+
# first element is the tag of the dom_id: e.g. "<form id='thisform'.... >" if dom_id is "thisform"
|
128
|
+
# second element (nillable) is the '<script ... </script>' appended to the tag by rjs if its an observed field
|
129
|
+
def rendered_html(dom_id, event)
|
130
|
+
if @response.body =~ /(\<([^\>]+) id="#{Regexp.escape(dom_id.to_s)}".*?\>(.+\2>\s*<script))/m
|
131
|
+
# matches <select>... </select><script
|
132
|
+
tag = $2
|
133
|
+
return tag, $1 if $3 =~ /\/#{tag}>(.+)/
|
134
|
+
end
|
135
|
+
assert @response.body =~ /(\<[^\>]+ id="#{Regexp.escape(dom_id.to_s)}".*?\>(<script |))/m, "Cannot find #{Regexp.escape(dom_id.to_s)} in #{$1}\n\n#{@response.body}"
|
136
|
+
return $1, $2
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
<html><head>
|
2
|
+
<%= javascript_include_tag :defaults %>
|
3
|
+
</head><body>
|
4
|
+
|
5
|
+
<% form_tag({:action => 'submit'}, {:id => 'test_krjs_form'}) do %>
|
6
|
+
<label for="object_value">Value : </label>
|
7
|
+
<%= text_field 'object', 'value' %><br/>
|
8
|
+
<label for="state">State : </label>
|
9
|
+
<%= text_field_tag 'state'%><br/>
|
10
|
+
|
11
|
+
<%= submit_tag "Save" %>
|
12
|
+
<% end %>
|
13
|
+
|
14
|
+
</body></html>
|
@@ -0,0 +1,15 @@
|
|
1
|
+
<% form_tag({:action => 'submit'}, {:id => 'form'}) do %>
|
2
|
+
Login: <%= text_field 'account', 'login', :id => 'account-new-login' %><br />
|
3
|
+
Password: <%= password_field_tag 'account[password]', nil,
|
4
|
+
:id => 'account-new-password' %><br />
|
5
|
+
<%= check_box_tag 'remember' %> Remember login?<br />
|
6
|
+
|
7
|
+
Comments?
|
8
|
+
<%= text_area 'account', 'comments' %><br />
|
9
|
+
|
10
|
+
Country:
|
11
|
+
<%= select 'account', 'country', ['US', 'Canada', 'Others'] %><br />
|
12
|
+
|
13
|
+
<p />
|
14
|
+
<%= submit_tag 'Login' %>
|
15
|
+
<% end %>
|
metadata
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: krjs
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.5.5
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Choon Keat
|
8
|
+
- Wes Hays
|
9
|
+
- John Dell
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
|
14
|
+
date: 2010-02-16 00:00:00 -08:00
|
15
|
+
default_executable:
|
16
|
+
dependencies: []
|
17
|
+
|
18
|
+
description: RJS is a great Ruby DSL to write javascript. However, it's so tempting to write RJS directly in the views, and soon the views contain substantial controller knowledge (e.g. link_to_remote, link_to, etc) KRJS attempts to solve that problem by allowing dynamic inclusion of AJAX calls on HTML elements. When a controller defines a method (based on naming convention) that handles a client-side event, the rendering engine will do the wiring automatically - when the event happens, an AJAX call will be made to the controller's method which would ideally reply with RJS and update portions of the document.
|
19
|
+
email: gems@gbdev.com
|
20
|
+
executables: []
|
21
|
+
|
22
|
+
extensions: []
|
23
|
+
|
24
|
+
extra_rdoc_files: []
|
25
|
+
|
26
|
+
files:
|
27
|
+
- CHANGELOG
|
28
|
+
- MIT-LICENSE
|
29
|
+
- README
|
30
|
+
- Rakefile
|
31
|
+
- init.rb
|
32
|
+
- lib/krjs.rb
|
33
|
+
- lib/krjs_with_debug_info.rb
|
34
|
+
- test/krjs_test.rb
|
35
|
+
- test/test_helper.rb
|
36
|
+
- test/views/layouts/sample.rhtml
|
37
|
+
- test/views/sample/form_test.rhtml
|
38
|
+
- test/views/sample/index.rhtml
|
39
|
+
- test/views/sample/on_remember_change.rjs
|
40
|
+
has_rdoc: true
|
41
|
+
homepage: http://github.com/gbdev/krjs
|
42
|
+
licenses: []
|
43
|
+
|
44
|
+
post_install_message:
|
45
|
+
rdoc_options: []
|
46
|
+
|
47
|
+
require_paths:
|
48
|
+
- lib
|
49
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: "0"
|
54
|
+
version:
|
55
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: "0"
|
60
|
+
version:
|
61
|
+
requirements: []
|
62
|
+
|
63
|
+
rubyforge_project:
|
64
|
+
rubygems_version: 1.3.5
|
65
|
+
signing_key:
|
66
|
+
specification_version: 3
|
67
|
+
summary: Keat's RJS - using RJS without messing with your Views
|
68
|
+
test_files:
|
69
|
+
- test/krjs_test.rb
|
70
|
+
- test/test_helper.rb
|
71
|
+
- test/views/layouts/sample.rhtml
|
72
|
+
- test/views/sample/form_test.rhtml
|
73
|
+
- test/views/sample/index.rhtml
|
74
|
+
- test/views/sample/on_remember_change.rjs
|