krjs 0.5.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ 0.5.5 - Feb 16, 2010
2
+ * Pushed to gemcutter
3
+ * Fixed tests so they all pass with Rails 2.3.5
4
+
5
+ 0.5.2 - Sep 08, 2008
6
+ * Initial release
7
+ * Gem'ified plugin
@@ -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
+
@@ -0,0 +1,10 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+
5
+ desc 'Test the krjs plugin.'
6
+ Rake::TestTask.new(:test) do |t|
7
+ t.libs << 'lib'
8
+ t.pattern = 'test/**/*_test.rb'
9
+ t.verbose = true
10
+ end
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'krjs'
@@ -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
@@ -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
@@ -0,0 +1,4 @@
1
+ RAILS_ENV = 'test'
2
+ require File.expand_path(File.join(File.dirname(__FILE__), '../../../../config/environment.rb'))
3
+ require 'test/unit'
4
+ require 'krjs'
@@ -0,0 +1,10 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
2
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
3
+ <html>
4
+ <head>
5
+ <%= javascript_include_tag :defaults %>
6
+ </head>
7
+ <body>
8
+ <%= yield %>
9
+ </body>
10
+ </html>
@@ -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 %>
@@ -0,0 +1,2 @@
1
+ # Note, test for this is commented because it doesn't appear external rjs works
2
+ page.insert_html :after, 'form', CGI.escapeHTML(params.inspect)
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