Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

Changeset 3754

Show
Ignore:
Timestamp:
03/03/06 19:34:23 (3 years ago)
Author:
david
Message:

RJS now does enumerations, baby! (closes #3876) [Rick Olson]

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/actionpack/lib/action_view/helpers/prototype_helper.rb

    r3667 r3754  
    435435        #   page.select('p.welcome b').first.hide # => $$('p.welcome b').first().hide(); 
    436436        def select(pattern) 
    437           JavaScriptCollectionProxy.new(self, pattern) 
     437          JavaScriptElementCollectionProxy.new(self, pattern) 
    438438        end 
    439439   
     
    688688    # Converts chained method calls on DOM proxy elements into JavaScript chains  
    689689    class JavaScriptProxy < Builder::BlankSlate #:nodoc: 
    690       def initialize(generator, root
     690      def initialize(generator, root = nil
    691691        @generator = generator 
    692         @generator << root 
     692        @generator << root if root 
    693693      end 
    694694 
     
    698698            assign($1, arguments.first) 
    699699          else 
    700             call(method, *arguments) 
     700            call("#{method.to_s.first}#{method.to_s.classify[1..-1]}", *arguments) 
    701701          end 
    702702        end 
     
    708708 
    709709        def assign(variable, value) 
    710           append_to_function_chain! "#{variable} = #{@generator.send(:javascript_object_for, value)}" 
     710          append_to_function_chain!("#{variable} = #{@generator.send(:javascript_object_for, value)}") 
    711711        end 
    712712         
     
    716716         
    717717        def append_to_function_chain!(call) 
    718           function_chain[-1] = function_chain[-1][0..-2] if function_chain[-1][-1..-1] == ";" # strip last ; 
     718          function_chain[-1].chomp!(';') 
    719719          function_chain[-1] += ".#{call};" 
    720720        end 
     
    738738        replace :partial => @id.to_s 
    739739      end 
     740       
    740741    end 
    741742 
     743    class JavaScriptVariableProxy < JavaScriptProxy #:nodoc: 
     744      def initialize(generator, variable) 
     745        @variable = variable 
     746        @empty    = true # only record lines if we have to.  gets rid of unnecessary linebreaks 
     747        super(generator) 
     748      end 
     749 
     750      # The JSON Encoder calls this to check for the #to_json method 
     751      # Since it's a blank slate object, I suppose it responds to anything. 
     752      def respond_to?(method) 
     753        true 
     754      end 
     755 
     756      def to_json 
     757        @variable 
     758      end 
     759       
     760      private 
     761        def append_to_function_chain!(call) 
     762          @generator << @variable if @empty 
     763          @empty = false 
     764          super 
     765        end 
     766    end 
     767 
    742768    class JavaScriptCollectionProxy < JavaScriptProxy #:nodoc: 
     769      ENUMERABLE_METHODS_WITH_RETURN = [:all, :any, :collect, :map, :detect, :find, :findAll, :select, :max, :min, :partition, :reject, :sortBy] 
     770      ENUMERABLE_METHODS = ENUMERABLE_METHODS_WITH_RETURN + [:each] 
     771 
    743772      def initialize(generator, pattern) 
    744         @pattern = pattern 
     773        super(generator, @pattern = pattern) 
     774      end 
     775 
     776      def grep(variable, pattern, &block) 
     777        enumerable_method("grep(#{pattern.to_json}, function(value, index) {", variable, %w(value index), &block) 
     778      end 
     779 
     780      def inject(variable, memo, &block) 
     781        enumerable_method("inject(#{memo.to_json}, function(memo, value, index) {", variable, %w(memo value index), &block) 
     782      end 
     783 
     784      def pluck(variable, property) 
     785        add_variable_assignment!(variable) 
     786        append_enumerable_function!("pluck(#{property.to_json});") 
     787      end 
     788 
     789      def zip(variable, *arguments, &block) 
     790        add_variable_assignment!(variable) 
     791        append_enumerable_function!("zip(#{arguments.collect { |a| a.to_json } * ', '}") 
     792        if block 
     793          function_chain[-1] += ", function(array) {" 
     794          yield @generator, ActiveSupport::JSON::Variable.new('array') 
     795          add_return_statement! 
     796          @generator << '});' 
     797        else 
     798          function_chain[-1] += ');' 
     799        end 
     800      end 
     801 
     802      private 
     803        def method_missing(method, *arguments, &block) 
     804          ENUMERABLE_METHODS.include?(method) ? enumerate(method, ENUMERABLE_METHODS_WITH_RETURN.include?(method), &block) : super 
     805        end 
     806 
     807        def enumerate(enumerable, variable = nil, &block) 
     808          enumerable_method("#{enumerable}(function(value, index) {", variable, %w(value index), &block) 
     809        end 
     810 
     811        def enumerable_method(enumerable, variable, yield_params, &block) 
     812          add_variable_assignment!(variable) if variable 
     813          append_enumerable_function!(enumerable) 
     814          yield *([@generator] + yield_params.collect { |p| JavaScriptVariableProxy.new(@generator, p) }) 
     815          add_return_statement! if variable 
     816          @generator << '});' 
     817        end 
     818 
     819        def add_variable_assignment!(variable) 
     820          function_chain.push("#{variable} = #{function_chain.pop}") 
     821        end 
     822 
     823        def add_return_statement! 
     824          unless function_chain.last =~ /return/ 
     825            function_chain.push("return #{function_chain.pop.chomp(';')};") 
     826          end 
     827        end 
     828         
     829        def append_enumerable_function!(call) 
     830          function_chain[-1].chomp!(';') 
     831          function_chain[-1] += ".#{call}" 
     832        end 
     833    end 
     834     
     835    class JavaScriptElementCollectionProxy < JavaScriptCollectionProxy #:nodoc:\ 
     836      def initialize(generator, pattern) 
    745837        super(generator, "$$('#{pattern}')") 
    746838      end 
    747  
    748       # TODO: Implement funky stuff like .each 
    749839    end 
    750840  end 
  • trunk/actionpack/test/template/prototype_helper_test.rb

    r3667 r3754  
    149149end 
    150150 
     151ActionView::Helpers::JavaScriptCollectionProxy.send :public, :enumerate 
     152 
    151153class JavaScriptGeneratorTest < Test::Unit::TestCase 
    152154  include BaseTest 
     
    245247 
    246248  def test_element_proxy_two_deep 
    247     @generator['hello'].hide("first").display 
    248     assert_equal %($('hello').hide("first").display();), @generator.to_s 
     249    @generator['hello'].hide("first").clean_whitespace 
     250    assert_equal %($('hello').hide("first").cleanWhitespace();), @generator.to_s 
    249251  end 
    250252   
     
    282284      @generator.drop_receiving('blah', :url => { :action => "order" }) 
    283285  end 
     286 
     287  def test_collection_proxy_with_each 
     288    @generator.select('p.welcome b').each do |page, value| 
     289      value.remove_class_name 'selected' 
     290    end 
     291    @generator.select('p.welcome b').each do |page, value, index| 
     292      page.call 'alert', index 
     293      page.call 'alert', value, 'selected' 
     294    end 
     295      assert_equal <<-EOS.strip, @generator.to_s 
     296$$('p.welcome b').each(function(value, index) { 
     297value.removeClassName("selected"); 
     298}); 
     299$$('p.welcome b').each(function(value, index) { 
     300alert(index); 
     301alert(value, "selected"); 
     302}); 
     303      EOS 
     304  end 
     305 
     306  def test_collection_proxy_on_enumerables_with_return_and_index 
     307    iterator            = Proc.new { |page, value|        page << '(value.className == "welcome")' } 
     308    iterator_with_index = Proc.new { |page, value, index| page.call 'alert', index ; page << '(value.className == "welcome")' } 
     309    ActionView::Helpers::JavaScriptCollectionProxy::ENUMERABLE_METHODS_WITH_RETURN.each do |enum| 
     310      @generator.select('p').enumerate(enum, 'a', &iterator) 
     311      @generator.select('p').enumerate(enum, 'b', &iterator_with_index) 
     312       
     313      assert_equal <<-EOS.strip, @generator.to_s 
     314a = $$('p').#{enum}(function(value, index) { 
     315return (value.className == "welcome"); 
     316}); 
     317b = $$('p').#{enum}(function(value, index) { 
     318alert(index); 
     319return (value.className == "welcome"); 
     320}); 
     321      EOS 
     322      @generator = create_generator 
     323    end 
     324  end 
     325 
     326  def test_collection_proxy_with_grep 
     327    @generator.select('p').grep 'a', /^a/ do |page, value| 
     328      page << '(value.className == "welcome")' 
     329    end 
     330    @generator.select('p').grep 'b', /b$/ do |page, value, index| 
     331      page.call 'alert', value 
     332      page << '(value.className == "welcome")' 
     333    end 
     334 
     335    assert_equal <<-EOS.strip, @generator.to_s 
     336a = $$('p').grep(/^a/, function(value, index) { 
     337return (value.className == "welcome"); 
     338}); 
     339b = $$('p').grep(/b$/, function(value, index) { 
     340alert(value); 
     341return (value.className == "welcome"); 
     342}); 
     343    EOS 
     344  end 
     345 
     346  def test_collection_proxy_with_inject 
     347    @generator.select('p').inject 'a', [] do |page, memo, value| 
     348      page << '(value.className == "welcome")' 
     349    end 
     350    @generator.select('p').inject 'b', nil do |page, memo, value, index| 
     351      page.call 'alert', memo 
     352      page << '(value.className == "welcome")' 
     353    end 
     354 
     355    assert_equal <<-EOS.strip, @generator.to_s 
     356a = $$('p').inject([], function(memo, value, index) { 
     357return (value.className == "welcome"); 
     358}); 
     359b = $$('p').inject(null, function(memo, value, index) { 
     360alert(memo); 
     361return (value.className == "welcome"); 
     362}); 
     363    EOS 
     364  end 
     365 
     366  def test_collection_proxy_with_pluck 
     367    @generator.select('p').pluck('a', 'className') 
     368    assert_equal %(a = $$('p').pluck("className");), @generator.to_s 
     369  end 
     370 
     371  def test_collection_proxy_with_zip 
     372    ActionView::Helpers::JavaScriptCollectionProxy.new(@generator, '[1, 2, 3]').zip('a', [4, 5, 6], [7, 8, 9]) 
     373    ActionView::Helpers::JavaScriptCollectionProxy.new(@generator, '[1, 2, 3]').zip('b', [4, 5, 6], [7, 8, 9]) do |page, array| 
     374      page.call 'array.reverse' 
     375    end 
     376 
     377    assert_equal <<-EOS.strip, @generator.to_s 
     378a = [1, 2, 3].zip([4, 5, 6], [7, 8, 9]); 
     379b = [1, 2, 3].zip([4, 5, 6], [7, 8, 9], function(array) { 
     380return array.reverse(); 
     381}); 
     382    EOS 
     383  end 
    284384end 
  • trunk/activesupport/lib/active_support/json.rb

    r3356 r3754  
    44  module JSON #:nodoc: 
    55    class CircularReferenceError < StandardError; end 
    6        
     6    # returns the literal string as its JSON encoded form.  Useful for passing javascript variables into functions. 
     7    # 
     8    # page.call 'Element.show', ActiveSupport::JSON::Variable.new("$$(#items li)") 
     9    class Variable < String 
     10      def to_json 
     11        self 
     12      end 
     13    end 
     14 
    715    class << self 
    816      REFERENCE_STACK_VARIABLE = :json_reference_stack 
  • trunk/activesupport/lib/active_support/json/encoders/core.rb

    r3356 r3754  
    5757        end 
    5858      end 
     59 
     60      define_encoder Regexp do |regexp| 
     61        regexp.inspect 
     62      end 
    5963    end 
    6064  end 
  • trunk/activesupport/test/json.rb

    r3356 r3754  
    1010 
    1111class TestJSONEmitters < Test::Unit::TestCase 
    12   TrueTests    = [[ true,  %(true)  ]] 
    13   FalseTests   = [[ false, %(false) ]] 
    14   NilTests     = [[ nil,   %(null)  ]] 
    15   NumericTests = [[ 1,     %(1)     ], 
    16                   [ 2.5,   %(2.5)   ]] 
     12  TrueTests    = [[ true,  %(true)  ]] 
     13  FalseTests    = [[ false, %(false) ]] 
     14  NilTests      = [[ nil,   %(null)  ]] 
     15  NumericTests = [[ 1,     %(1)     ], 
     16                  [ 2.5,   %(2.5)   ]] 
    1717                     
    18   StringTests  = [[ 'this is the string',     %("this is the string")         ], 
    19                   [ 'a "string" with quotes', %("a \\"string\\" with quotes") ]] 
     18  StringTests  = [[ 'this is the string',     %("this is the string")         ], 
     19                  [ 'a "string" with quotes', %("a \\"string\\" with quotes") ]] 
    2020                  
    21   ArrayTests   = [[ ['a', 'b', 'c'],          %([\"a\", \"b\", \"c\"])          ], 
    22                   [ [1, 'a', :b, nil, false], %([1, \"a\", \"b\", null, false]) ]] 
     21  ArrayTests    = [[ ['a', 'b', 'c'],          %([\"a\", \"b\", \"c\"])          ], 
     22                  [ [1, 'a', :b, nil, false], %([1, \"a\", \"b\", null, false]) ]] 
    2323   
    24   HashTests    = [[ {:a => :b, :c => :d}, %({\"c\": \"d\", \"a\": \"b\"}) ]] 
     24  HashTests    = [[ {:a => :b, :c => :d}, %({\"c\": \"d\", \"a\": \"b\"}) ]] 
    2525                   
    26   SymbolTests  = [[ :a,     %("a")    ], 
    27                   [ :this,  %("this") ], 
    28                   [ :"a b", %("a b")  ]] 
     26  SymbolTests  = [[ :a,     %("a")    ], 
     27                  [ :this,  %("this") ], 
     28                  [ :"a b", %("a b")  ]] 
    2929 
    30   ObjectTests  = [[ Foo.new(1, 2), %({\"a\": 1, \"b\": 2}) ]] 
    31    
     30  ObjectTests   = [[ Foo.new(1, 2), %({\"a\": 1, \"b\": 2}) ]] 
     31 
     32  VariableTests = [[ ActiveSupport::JSON::Variable.new('foo'), 'foo'], 
     33                   [ ActiveSupport::JSON::Variable.new('alert("foo")'), 'alert("foo")']] 
     34  RegexpTests   = [[ /^a/, '/^a/' ], /^\w{1,2}[a-z]+/ix, '/^\\w{1,2}[a-z]+/ix'] 
     35 
    3236  constants.grep(/Tests$/).each do |class_tests| 
    3337    define_method("test_#{class_tests[0..-6].downcase}") do