aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Heinemeier Hansson <david@loudthinking.com>2006-03-03 19:34:23 +0000
committerDavid Heinemeier Hansson <david@loudthinking.com>2006-03-03 19:34:23 +0000
commit2e67f1adc8e52121270d1139665a965e3f0792d8 (patch)
treeccaf6b866c3895fede3fd00bf849a1f542394cf9
parent116658e69b9c4a722e6ae8717629b8cd0057db89 (diff)
downloadrails-2e67f1adc8e52121270d1139665a965e3f0792d8.tar.gz
rails-2e67f1adc8e52121270d1139665a965e3f0792d8.tar.bz2
rails-2e67f1adc8e52121270d1139665a965e3f0792d8.zip
RJS now does enumerations, baby! (closes #3876) [Rick Olson]
git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@3754 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
-rw-r--r--actionpack/lib/action_view/helpers/prototype_helper.rb108
-rw-r--r--actionpack/test/template/prototype_helper_test.rb104
-rw-r--r--activesupport/lib/active_support/json.rb10
-rw-r--r--activesupport/lib/active_support/json/encoders/core.rb4
-rw-r--r--activesupport/test/json.rb34
5 files changed, 233 insertions, 27 deletions
diff --git a/actionpack/lib/action_view/helpers/prototype_helper.rb b/actionpack/lib/action_view/helpers/prototype_helper.rb
index 4c6c020ebe..db36f3de5f 100644
--- a/actionpack/lib/action_view/helpers/prototype_helper.rb
+++ b/actionpack/lib/action_view/helpers/prototype_helper.rb
@@ -434,7 +434,7 @@ module ActionView
# page.select('p.welcome b').first # => $$('p.welcome b').first();
# page.select('p.welcome b').first.hide # => $$('p.welcome b').first().hide();
def select(pattern)
- JavaScriptCollectionProxy.new(self, pattern)
+ JavaScriptElementCollectionProxy.new(self, pattern)
end
# Inserts HTML at the specified +position+ relative to the DOM element
@@ -687,9 +687,9 @@ module ActionView
# Converts chained method calls on DOM proxy elements into JavaScript chains
class JavaScriptProxy < Builder::BlankSlate #:nodoc:
- def initialize(generator, root)
+ def initialize(generator, root = nil)
@generator = generator
- @generator << root
+ @generator << root if root
end
private
@@ -697,7 +697,7 @@ module ActionView
if method.to_s =~ /(.*)=$/
assign($1, arguments.first)
else
- call(method, *arguments)
+ call("#{method.to_s.first}#{method.to_s.classify[1..-1]}", *arguments)
end
end
@@ -707,7 +707,7 @@ module ActionView
end
def assign(variable, value)
- append_to_function_chain! "#{variable} = #{@generator.send(:javascript_object_for, value)}"
+ append_to_function_chain!("#{variable} = #{@generator.send(:javascript_object_for, value)}")
end
def function_chain
@@ -715,7 +715,7 @@ module ActionView
end
def append_to_function_chain!(call)
- function_chain[-1] = function_chain[-1][0..-2] if function_chain[-1][-1..-1] == ";" # strip last ;
+ function_chain[-1].chomp!(';')
function_chain[-1] += ".#{call};"
end
end
@@ -737,15 +737,105 @@ module ActionView
def reload
replace :partial => @id.to_s
end
+
+ end
+
+ class JavaScriptVariableProxy < JavaScriptProxy #:nodoc:
+ def initialize(generator, variable)
+ @variable = variable
+ @empty = true # only record lines if we have to. gets rid of unnecessary linebreaks
+ super(generator)
+ end
+
+ # The JSON Encoder calls this to check for the #to_json method
+ # Since it's a blank slate object, I suppose it responds to anything.
+ def respond_to?(method)
+ true
+ end
+
+ def to_json
+ @variable
+ end
+
+ private
+ def append_to_function_chain!(call)
+ @generator << @variable if @empty
+ @empty = false
+ super
+ end
end
class JavaScriptCollectionProxy < JavaScriptProxy #:nodoc:
+ ENUMERABLE_METHODS_WITH_RETURN = [:all, :any, :collect, :map, :detect, :find, :findAll, :select, :max, :min, :partition, :reject, :sortBy]
+ ENUMERABLE_METHODS = ENUMERABLE_METHODS_WITH_RETURN + [:each]
+
def initialize(generator, pattern)
- @pattern = pattern
- super(generator, "$$('#{pattern}')")
+ super(generator, @pattern = pattern)
+ end
+
+ def grep(variable, pattern, &block)
+ enumerable_method("grep(#{pattern.to_json}, function(value, index) {", variable, %w(value index), &block)
+ end
+
+ def inject(variable, memo, &block)
+ enumerable_method("inject(#{memo.to_json}, function(memo, value, index) {", variable, %w(memo value index), &block)
+ end
+
+ def pluck(variable, property)
+ add_variable_assignment!(variable)
+ append_enumerable_function!("pluck(#{property.to_json});")
+ end
+
+ def zip(variable, *arguments, &block)
+ add_variable_assignment!(variable)
+ append_enumerable_function!("zip(#{arguments.collect { |a| a.to_json } * ', '}")
+ if block
+ function_chain[-1] += ", function(array) {"
+ yield @generator, ActiveSupport::JSON::Variable.new('array')
+ add_return_statement!
+ @generator << '});'
+ else
+ function_chain[-1] += ');'
+ end
end
- # TODO: Implement funky stuff like .each
+ private
+ def method_missing(method, *arguments, &block)
+ ENUMERABLE_METHODS.include?(method) ? enumerate(method, ENUMERABLE_METHODS_WITH_RETURN.include?(method), &block) : super
+ end
+
+ def enumerate(enumerable, variable = nil, &block)
+ enumerable_method("#{enumerable}(function(value, index) {", variable, %w(value index), &block)
+ end
+
+ def enumerable_method(enumerable, variable, yield_params, &block)
+ add_variable_assignment!(variable) if variable
+ append_enumerable_function!(enumerable)
+ yield *([@generator] + yield_params.collect { |p| JavaScriptVariableProxy.new(@generator, p) })
+ add_return_statement! if variable
+ @generator << '});'
+ end
+
+ def add_variable_assignment!(variable)
+ function_chain.push("#{variable} = #{function_chain.pop}")
+ end
+
+ def add_return_statement!
+ unless function_chain.last =~ /return/
+ function_chain.push("return #{function_chain.pop.chomp(';')};")
+ end
+ end
+
+ def append_enumerable_function!(call)
+ function_chain[-1].chomp!(';')
+ function_chain[-1] += ".#{call}"
+ end
+ end
+
+ class JavaScriptElementCollectionProxy < JavaScriptCollectionProxy #:nodoc:\
+ def initialize(generator, pattern)
+ super(generator, "$$('#{pattern}')")
+ end
end
end
end
diff --git a/actionpack/test/template/prototype_helper_test.rb b/actionpack/test/template/prototype_helper_test.rb
index 5a4ec61b39..8ae7a98108 100644
--- a/actionpack/test/template/prototype_helper_test.rb
+++ b/actionpack/test/template/prototype_helper_test.rb
@@ -148,6 +148,8 @@ class PrototypeHelperTest < Test::Unit::TestCase
end
end
+ActionView::Helpers::JavaScriptCollectionProxy.send :public, :enumerate
+
class JavaScriptGeneratorTest < Test::Unit::TestCase
include BaseTest
@@ -244,8 +246,8 @@ Element.update("baz", "<p>This is a test</p>");
end
def test_element_proxy_two_deep
- @generator['hello'].hide("first").display
- assert_equal %($('hello').hide("first").display();), @generator.to_s
+ @generator['hello'].hide("first").clean_whitespace
+ assert_equal %($('hello').hide("first").cleanWhitespace();), @generator.to_s
end
def test_select_access
@@ -281,4 +283,102 @@ Element.update("baz", "<p>This is a test</p>");
assert_equal %(Droppables.add('blah', {onDrop:function(element){new Ajax.Request('http://www.example.com/order', {asynchronous:true, evalScripts:true, parameters:'id=' + encodeURIComponent(element.id)})}});),
@generator.drop_receiving('blah', :url => { :action => "order" })
end
+
+ def test_collection_proxy_with_each
+ @generator.select('p.welcome b').each do |page, value|
+ value.remove_class_name 'selected'
+ end
+ @generator.select('p.welcome b').each do |page, value, index|
+ page.call 'alert', index
+ page.call 'alert', value, 'selected'
+ end
+ assert_equal <<-EOS.strip, @generator.to_s
+$$('p.welcome b').each(function(value, index) {
+value.removeClassName("selected");
+});
+$$('p.welcome b').each(function(value, index) {
+alert(index);
+alert(value, "selected");
+});
+ EOS
+ end
+
+ def test_collection_proxy_on_enumerables_with_return_and_index
+ iterator = Proc.new { |page, value| page << '(value.className == "welcome")' }
+ iterator_with_index = Proc.new { |page, value, index| page.call 'alert', index ; page << '(value.className == "welcome")' }
+ ActionView::Helpers::JavaScriptCollectionProxy::ENUMERABLE_METHODS_WITH_RETURN.each do |enum|
+ @generator.select('p').enumerate(enum, 'a', &iterator)
+ @generator.select('p').enumerate(enum, 'b', &iterator_with_index)
+
+ assert_equal <<-EOS.strip, @generator.to_s
+a = $$('p').#{enum}(function(value, index) {
+return (value.className == "welcome");
+});
+b = $$('p').#{enum}(function(value, index) {
+alert(index);
+return (value.className == "welcome");
+});
+ EOS
+ @generator = create_generator
+ end
+ end
+
+ def test_collection_proxy_with_grep
+ @generator.select('p').grep 'a', /^a/ do |page, value|
+ page << '(value.className == "welcome")'
+ end
+ @generator.select('p').grep 'b', /b$/ do |page, value, index|
+ page.call 'alert', value
+ page << '(value.className == "welcome")'
+ end
+
+ assert_equal <<-EOS.strip, @generator.to_s
+a = $$('p').grep(/^a/, function(value, index) {
+return (value.className == "welcome");
+});
+b = $$('p').grep(/b$/, function(value, index) {
+alert(value);
+return (value.className == "welcome");
+});
+ EOS
+ end
+
+ def test_collection_proxy_with_inject
+ @generator.select('p').inject 'a', [] do |page, memo, value|
+ page << '(value.className == "welcome")'
+ end
+ @generator.select('p').inject 'b', nil do |page, memo, value, index|
+ page.call 'alert', memo
+ page << '(value.className == "welcome")'
+ end
+
+ assert_equal <<-EOS.strip, @generator.to_s
+a = $$('p').inject([], function(memo, value, index) {
+return (value.className == "welcome");
+});
+b = $$('p').inject(null, function(memo, value, index) {
+alert(memo);
+return (value.className == "welcome");
+});
+ EOS
+ end
+
+ def test_collection_proxy_with_pluck
+ @generator.select('p').pluck('a', 'className')
+ assert_equal %(a = $$('p').pluck("className");), @generator.to_s
+ end
+
+ def test_collection_proxy_with_zip
+ ActionView::Helpers::JavaScriptCollectionProxy.new(@generator, '[1, 2, 3]').zip('a', [4, 5, 6], [7, 8, 9])
+ ActionView::Helpers::JavaScriptCollectionProxy.new(@generator, '[1, 2, 3]').zip('b', [4, 5, 6], [7, 8, 9]) do |page, array|
+ page.call 'array.reverse'
+ end
+
+ assert_equal <<-EOS.strip, @generator.to_s
+a = [1, 2, 3].zip([4, 5, 6], [7, 8, 9]);
+b = [1, 2, 3].zip([4, 5, 6], [7, 8, 9], function(array) {
+return array.reverse();
+});
+ EOS
+ end
end \ No newline at end of file
diff --git a/activesupport/lib/active_support/json.rb b/activesupport/lib/active_support/json.rb
index 77c7225c66..943adebd35 100644
--- a/activesupport/lib/active_support/json.rb
+++ b/activesupport/lib/active_support/json.rb
@@ -3,7 +3,15 @@ require 'active_support/json/encoders'
module ActiveSupport
module JSON #:nodoc:
class CircularReferenceError < StandardError; end
-
+ # returns the literal string as its JSON encoded form. Useful for passing javascript variables into functions.
+ #
+ # page.call 'Element.show', ActiveSupport::JSON::Variable.new("$$(#items li)")
+ class Variable < String
+ def to_json
+ self
+ end
+ end
+
class << self
REFERENCE_STACK_VARIABLE = :json_reference_stack
diff --git a/activesupport/lib/active_support/json/encoders/core.rb b/activesupport/lib/active_support/json/encoders/core.rb
index 003c938be4..d3193af555 100644
--- a/activesupport/lib/active_support/json/encoders/core.rb
+++ b/activesupport/lib/active_support/json/encoders/core.rb
@@ -56,6 +56,10 @@ module ActiveSupport
result << '}'
end
end
+
+ define_encoder Regexp do |regexp|
+ regexp.inspect
+ end
end
end
end
diff --git a/activesupport/test/json.rb b/activesupport/test/json.rb
index c5b597abd7..fc4d7705b1 100644
--- a/activesupport/test/json.rb
+++ b/activesupport/test/json.rb
@@ -9,26 +9,30 @@ class Foo
end
class TestJSONEmitters < Test::Unit::TestCase
- TrueTests = [[ true, %(true) ]]
- FalseTests = [[ false, %(false) ]]
- NilTests = [[ nil, %(null) ]]
- NumericTests = [[ 1, %(1) ],
- [ 2.5, %(2.5) ]]
+ TrueTests = [[ true, %(true) ]]
+ FalseTests = [[ false, %(false) ]]
+ NilTests = [[ nil, %(null) ]]
+ NumericTests = [[ 1, %(1) ],
+ [ 2.5, %(2.5) ]]
- StringTests = [[ 'this is the string', %("this is the string") ],
- [ 'a "string" with quotes', %("a \\"string\\" with quotes") ]]
+ StringTests = [[ 'this is the string', %("this is the string") ],
+ [ 'a "string" with quotes', %("a \\"string\\" with quotes") ]]
- ArrayTests = [[ ['a', 'b', 'c'], %([\"a\", \"b\", \"c\"]) ],
- [ [1, 'a', :b, nil, false], %([1, \"a\", \"b\", null, false]) ]]
+ ArrayTests = [[ ['a', 'b', 'c'], %([\"a\", \"b\", \"c\"]) ],
+ [ [1, 'a', :b, nil, false], %([1, \"a\", \"b\", null, false]) ]]
- HashTests = [[ {:a => :b, :c => :d}, %({\"c\": \"d\", \"a\": \"b\"}) ]]
+ HashTests = [[ {:a => :b, :c => :d}, %({\"c\": \"d\", \"a\": \"b\"}) ]]
- SymbolTests = [[ :a, %("a") ],
- [ :this, %("this") ],
- [ :"a b", %("a b") ]]
+ SymbolTests = [[ :a, %("a") ],
+ [ :this, %("this") ],
+ [ :"a b", %("a b") ]]
+
+ ObjectTests = [[ Foo.new(1, 2), %({\"a\": 1, \"b\": 2}) ]]
+
+ VariableTests = [[ ActiveSupport::JSON::Variable.new('foo'), 'foo'],
+ [ ActiveSupport::JSON::Variable.new('alert("foo")'), 'alert("foo")']]
+ RegexpTests = [[ /^a/, '/^a/' ], /^\w{1,2}[a-z]+/ix, '/^\\w{1,2}[a-z]+/ix']
- ObjectTests = [[ Foo.new(1, 2), %({\"a\": 1, \"b\": 2}) ]]
-
constants.grep(/Tests$/).each do |class_tests|
define_method("test_#{class_tests[0..-6].downcase}") do
self.class.const_get(class_tests).each do |pair|