diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG
index 05f2328f7da4062b757f09c7470e621f92ef9be6..655f91d1bbb3cf9e2e08e1f392ca584d645c7c36 100644
--- a/actionpack/CHANGELOG
+++ b/actionpack/CHANGELOG
@@ -1,5 +1,9 @@
*SVN*
+* Add support for converting blocks into function arguments to JavaScriptGenerator#call and JavaScriptProxy#call. [Sam Stephenson]
+
+* Add JavaScriptGenerator#literal for wrapping a string in an object whose #to_json is the string itself. [Sam Stephenson]
+
* Add <%= escape_once html %> to escape html while leaving any currently escaped entities alone. Fix button_to double-escaping issue. [Rick]
* Fix double-escaped entities, such as &, {, etc. [Rick]
diff --git a/actionpack/lib/action_view/helpers/prototype_helper.rb b/actionpack/lib/action_view/helpers/prototype_helper.rb
index f1b956cf12e73b213d7bfba3e6b984407b15297b..1b4d2e71d8ef3a0403fbe82c5a709cde0954b6e9 100644
--- a/actionpack/lib/action_view/helpers/prototype_helper.rb
+++ b/actionpack/lib/action_view/helpers/prototype_helper.rb
@@ -458,6 +458,12 @@ def [](id)
JavaScriptElementProxy.new(self, id)
end
+ # Returns an object whose #to_json evaluates to +code+. Use this to pass a literal JavaScript
+ # expression as an argument to another JavaScriptGenerator method.
+ def literal(code)
+ JavaScriptLiteral.new(code)
+ end
+
# Returns a collection reference by finding it through a CSS +pattern+ in the DOM. This collection can then be
# used for further method calls. Examples:
#
@@ -578,16 +584,18 @@ def alert(message)
call 'alert', message
end
- # Redirects the browser to the given +location+, in the same form as
- # +url_for+.
+ # Redirects the browser to the given +location+, in the same form as +url_for+.
def redirect_to(location)
assign 'window.location.href', @context.url_for(location)
end
- # Calls the JavaScript +function+, optionally with the given
- # +arguments+.
- def call(function, *arguments)
- record "#{function}(#{arguments_for_call(arguments)})"
+ # Calls the JavaScript +function+, optionally with the given +arguments+.
+ #
+ # If a block is given, the block will be passed to a new JavaScriptGenerator;
+ # the resulting JavaScript code will then be wrapped inside function() { ... }
+ # and passed as the called function's final argument.
+ def call(function, *arguments, &block)
+ record "#{function}(#{arguments_for_call(arguments, block)})"
end
# Assigns the JavaScript +variable+ the given +value+.
@@ -649,7 +657,7 @@ def page
end
def record(line)
- returning line = "#{line.to_s.chomp.gsub /\;$/, ''};" do
+ returning line = "#{line.to_s.chomp.gsub(/\;\z/, '')};" do
self << line
end
end
@@ -664,10 +672,16 @@ def javascript_object_for(object)
object.respond_to?(:to_json) ? object.to_json : object.inspect
end
- def arguments_for_call(arguments)
+ def arguments_for_call(arguments, block = nil)
+ arguments << block_to_function(block) if block
arguments.map { |argument| javascript_object_for(argument) }.join ', '
end
+ def block_to_function(block)
+ generator = self.class.new(@context, &block)
+ literal("function() { #{generator.to_s} }")
+ end
+
def method_missing(method, *arguments)
JavaScriptProxy.new(self, method.to_s.camelize)
end
@@ -744,6 +758,13 @@ def build_callbacks(options)
end
end
+ # Bypasses string escaping so you can pass around raw JavaScript
+ class JavaScriptLiteral < String #:nodoc:
+ def to_json
+ to_s
+ end
+ end
+
# Converts chained method calls on DOM proxy elements into JavaScript chains
class JavaScriptProxy < Builder::BlankSlate #:nodoc:
def initialize(generator, root = nil)
@@ -752,16 +773,16 @@ def initialize(generator, root = nil)
end
private
- def method_missing(method, *arguments)
+ def method_missing(method, *arguments, &block)
if method.to_s =~ /(.*)=$/
assign($1, arguments.first)
else
- call("#{method.to_s.camelize(:lower)}", *arguments)
+ call("#{method.to_s.camelize(:lower)}", *arguments, &block)
end
end
- def call(function, *arguments)
- append_to_function_chain!("#{function}(#{@generator.send(:arguments_for_call, arguments)})")
+ def call(function, *arguments, &block)
+ append_to_function_chain!("#{function}(#{@generator.send(:arguments_for_call, arguments, block)})")
self
end
@@ -770,7 +791,7 @@ def assign(variable, value)
end
def function_chain
- @function_chain ||= @generator.instance_variable_get("@lines")
+ @function_chain ||= @generator.instance_variable_get(:@lines)
end
def append_to_function_chain!(call)
diff --git a/actionpack/test/template/prototype_helper_test.rb b/actionpack/test/template/prototype_helper_test.rb
index a88d0f7c9f2cbd380285563021a5e64193364870..0364ceea62b06b6ed42e10a0f3185a0d4754935c 100644
--- a/actionpack/test/template/prototype_helper_test.rb
+++ b/actionpack/test/template/prototype_helper_test.rb
@@ -448,10 +448,37 @@ def test_debug_rjs
ensure
ActionView::Base.debug_rjs = false
end
+
+ def test_literal
+ literal = @generator.literal("function() {}")
+ assert_equal "function() {}", literal.to_json
+ assert_equal "", @generator.to_s
+ end
def test_class_proxy
@generator.form.focus('my_field')
assert_equal "Form.focus(\"my_field\");", @generator.to_s
end
+
+ def test_call_with_block
+ @generator.call(:before)
+ @generator.call(:my_method) do |p|
+ p[:one].show
+ p[:two].hide
+ end
+ @generator.call(:in_between)
+ @generator.call(:my_method_with_arguments, true, "hello") do |p|
+ p[:three].visual_effect(:highlight)
+ end
+ assert_equal "before();\nmy_method(function() { $(\"one\").show();\n$(\"two\").hide(); });\nin_between();\nmy_method_with_arguments(true, \"hello\", function() { $(\"three\").visualEffect(\"highlight\"); });", @generator.to_s
+ end
+
+ def test_class_proxy_call_with_block
+ @generator.my_object.my_method do |p|
+ p[:one].show
+ p[:two].hide
+ end
+ assert_equal "MyObject.myMethod(function() { $(\"one\").show();\n$(\"two\").hide(); });", @generator.to_s
+ end
end