mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
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
This commit is contained in:
parent
116658e69b
commit
2e67f1adc8
5 changed files with 233 additions and 27 deletions
|
@ -434,7 +434,7 @@ module ActionView
|
||||||
# page.select('p.welcome b').first # => $$('p.welcome b').first();
|
# page.select('p.welcome b').first # => $$('p.welcome b').first();
|
||||||
# page.select('p.welcome b').first.hide # => $$('p.welcome b').first().hide();
|
# page.select('p.welcome b').first.hide # => $$('p.welcome b').first().hide();
|
||||||
def select(pattern)
|
def select(pattern)
|
||||||
JavaScriptCollectionProxy.new(self, pattern)
|
JavaScriptElementCollectionProxy.new(self, pattern)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Inserts HTML at the specified +position+ relative to the DOM element
|
# 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
|
# Converts chained method calls on DOM proxy elements into JavaScript chains
|
||||||
class JavaScriptProxy < Builder::BlankSlate #:nodoc:
|
class JavaScriptProxy < Builder::BlankSlate #:nodoc:
|
||||||
def initialize(generator, root)
|
def initialize(generator, root = nil)
|
||||||
@generator = generator
|
@generator = generator
|
||||||
@generator << root
|
@generator << root if root
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
@ -697,7 +697,7 @@ module ActionView
|
||||||
if method.to_s =~ /(.*)=$/
|
if method.to_s =~ /(.*)=$/
|
||||||
assign($1, arguments.first)
|
assign($1, arguments.first)
|
||||||
else
|
else
|
||||||
call(method, *arguments)
|
call("#{method.to_s.first}#{method.to_s.classify[1..-1]}", *arguments)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -707,7 +707,7 @@ module ActionView
|
||||||
end
|
end
|
||||||
|
|
||||||
def assign(variable, value)
|
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
|
end
|
||||||
|
|
||||||
def function_chain
|
def function_chain
|
||||||
|
@ -715,7 +715,7 @@ module ActionView
|
||||||
end
|
end
|
||||||
|
|
||||||
def append_to_function_chain!(call)
|
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};"
|
function_chain[-1] += ".#{call};"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -737,15 +737,105 @@ module ActionView
|
||||||
def reload
|
def reload
|
||||||
replace :partial => @id.to_s
|
replace :partial => @id.to_s
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
class JavaScriptCollectionProxy < JavaScriptProxy #:nodoc:
|
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)
|
def initialize(generator, pattern)
|
||||||
@pattern = pattern
|
super(generator, @pattern = pattern)
|
||||||
super(generator, "$$('#{pattern}')")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# TODO: Implement funky stuff like .each
|
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
|
||||||
|
|
||||||
|
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
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -148,6 +148,8 @@ class PrototypeHelperTest < Test::Unit::TestCase
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
ActionView::Helpers::JavaScriptCollectionProxy.send :public, :enumerate
|
||||||
|
|
||||||
class JavaScriptGeneratorTest < Test::Unit::TestCase
|
class JavaScriptGeneratorTest < Test::Unit::TestCase
|
||||||
include BaseTest
|
include BaseTest
|
||||||
|
|
||||||
|
@ -244,8 +246,8 @@ Element.update("baz", "<p>This is a test</p>");
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_element_proxy_two_deep
|
def test_element_proxy_two_deep
|
||||||
@generator['hello'].hide("first").display
|
@generator['hello'].hide("first").clean_whitespace
|
||||||
assert_equal %($('hello').hide("first").display();), @generator.to_s
|
assert_equal %($('hello').hide("first").cleanWhitespace();), @generator.to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_select_access
|
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)})}});),
|
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" })
|
@generator.drop_receiving('blah', :url => { :action => "order" })
|
||||||
end
|
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
|
end
|
|
@ -3,6 +3,14 @@ require 'active_support/json/encoders'
|
||||||
module ActiveSupport
|
module ActiveSupport
|
||||||
module JSON #:nodoc:
|
module JSON #:nodoc:
|
||||||
class CircularReferenceError < StandardError; end
|
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
|
class << self
|
||||||
REFERENCE_STACK_VARIABLE = :json_reference_stack
|
REFERENCE_STACK_VARIABLE = :json_reference_stack
|
||||||
|
|
|
@ -56,6 +56,10 @@ module ActiveSupport
|
||||||
result << '}'
|
result << '}'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
define_encoder Regexp do |regexp|
|
||||||
|
regexp.inspect
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -9,25 +9,29 @@ class Foo
|
||||||
end
|
end
|
||||||
|
|
||||||
class TestJSONEmitters < Test::Unit::TestCase
|
class TestJSONEmitters < Test::Unit::TestCase
|
||||||
TrueTests = [[ true, %(true) ]]
|
TrueTests = [[ true, %(true) ]]
|
||||||
FalseTests = [[ false, %(false) ]]
|
FalseTests = [[ false, %(false) ]]
|
||||||
NilTests = [[ nil, %(null) ]]
|
NilTests = [[ nil, %(null) ]]
|
||||||
NumericTests = [[ 1, %(1) ],
|
NumericTests = [[ 1, %(1) ],
|
||||||
[ 2.5, %(2.5) ]]
|
[ 2.5, %(2.5) ]]
|
||||||
|
|
||||||
StringTests = [[ 'this is the string', %("this is the string") ],
|
StringTests = [[ 'this is the string', %("this is the string") ],
|
||||||
[ 'a "string" with quotes', %("a \\"string\\" with quotes") ]]
|
[ 'a "string" with quotes', %("a \\"string\\" with quotes") ]]
|
||||||
|
|
||||||
ArrayTests = [[ ['a', 'b', 'c'], %([\"a\", \"b\", \"c\"]) ],
|
ArrayTests = [[ ['a', 'b', 'c'], %([\"a\", \"b\", \"c\"]) ],
|
||||||
[ [1, 'a', :b, nil, false], %([1, \"a\", \"b\", null, false]) ]]
|
[ [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") ],
|
SymbolTests = [[ :a, %("a") ],
|
||||||
[ :this, %("this") ],
|
[ :this, %("this") ],
|
||||||
[ :"a b", %("a b") ]]
|
[ :"a b", %("a b") ]]
|
||||||
|
|
||||||
ObjectTests = [[ Foo.new(1, 2), %({\"a\": 1, \"b\": 2}) ]]
|
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']
|
||||||
|
|
||||||
constants.grep(/Tests$/).each do |class_tests|
|
constants.grep(/Tests$/).each do |class_tests|
|
||||||
define_method("test_#{class_tests[0..-6].downcase}") do
|
define_method("test_#{class_tests[0..-6].downcase}") do
|
||||||
|
|
Loading…
Reference in a new issue