require 'ruby2js' # # Jquery functions are either invoked using jQuery() or, more commonly, $(). # The former presents no problem, the latter is not legal Ruby. # # Accordingly, the first accomodation this filter provides is to map $$ to # $. This works find for $$.ajax and the like, but less so for direct calls # as $$(this) is also a syntax error. $$.(this) and $$[this] will work # but are a bit clumsy. # # So as a second accomodation, the rarely used binary one's complement unary # operator (namely, ~) is usurped, and the AST is rewritten to provide the # effect of this operator being of a higher precedence than method calls. # # As a part of this rewriting, calls to getters and setters are rewritten # to match jQuery's convention for getters and setters: # http://learn.jquery.com/using-jquery-core/working-with-selections/ # # Selected DOM properties (namely checked, disabled, readOnly, and required) # can also use getter and setter syntax. Additionally, readOnly may be # spelled 'readonly'. # # Of course, using jQuery's style of getter and setter calls is supported, # and indeed is convenient when using method chaining. # # Finally, the tilde AST rewriting can be avoided by using consecutive tildes # (~~ is a common Ruby idiom for Math.floor, ~~~ will return the binary # one's complement.); and the getter and setter AST rewriting can be avoided # by the use of parenthesis, e.g. (~this).text. # # Some examples of before/after conversions: # # ~this.val # $(this).val() # # ~"button.continue".html = "Next Step..." # $("button.continue").html("Next Step...") # # ~"button".readonly = false # $("button").prop("readOnly", false) # # $$.ajax( # url: "/api/getWeather", # data: {zipcode: 97201}, # success: proc do |data| # `"#weather-temp".html = "<strong>#{data}</strong> degrees" # end # ) # # $.ajax({ # url: "/api/getWeather", # data: {zipcode: 97201}, # success: function(data) { # $("#weather-temp").html("<strong>" + data + "</strong> degrees"); # } # }) module Ruby2JS module Filter module JQuery include SEXP def initialize @each = true # disable each mapping, see functions filter end # map $$ to $ def on_gvar(node) if node.children[0] == :$$ node.updated nil, ['$'] else super end end def on_send(node) if [:call, :[]].include? node.children[1] # map $$.call(..), $$.(..), and $$[...] to $(...) target = process(node.children.first) if target.type == :gvar and target.children == ['$'] s(:send, nil, '$', *process_all(node.children[2..-1])) else super end elsif node.children[1] == :~ # map ~expression.method to $(expression).method if node.children[0] and node.children[0].type == :op_asgn asgn = node.children[0] if asgn.children[0] and asgn.children[0].type == :send inner = asgn.children[0] return on_send s(:send, s(:send, inner.children[0], (inner.children[1].to_s+'=').to_sym, s(:send, s(:send, s(:send, inner.children[0], :~), *inner.children[1..-1]), *asgn.children[1..-1])), :~) else return on_send asgn.updated nil, [s(:send, asgn.children[0], :~), *asgn.children[1..-1]] end end # See http://api.jquery.com/category/properties/ props = :context, :jquery, :browser, :fx, :support, :length, :selector domprops = %w(checked disabled readonly readOnly required) rewrite_tilda = proc do |node| # Example conversion: # before: # (send (send (send (send nil :a) :b) :c) :~) # after: # (send (send (send nil "$" (send nil :a)) :b) :c) if node.type == :send and node.children[0] if node.children[1] == :~ and node.children[0].children[1] == :~ # consecutive tildes if node.children[0].children[0].children[1] == :~ result = process(node.children[0].children[0].children[0]) else result = s(:send, process(node.children[0].children[0]), :~) end s(:send, s(:send, result, :~), :~) else # possible getter/setter method = node.children[1] method = method.to_s.chomp('=') if method =~ /=$/ rewrite = [rewrite_tilda[node.children[0]], method, *process_all(node.children[2..-1])] if props.include? node.children[1] node.updated nil, rewrite elsif domprops.include? method.to_s method = :readOnly if method.to_s == 'readonly' s(:send, rewrite[0], :prop, s(:sym, method), *rewrite[2..-1]) else s(:send, *rewrite) end end elsif node.type == :block # method call with a block parameter node.updated nil, [rewrite_tilda[node.children[0]], *process_all(node.children[1..-1])] else # innermost expression s(:send, nil, '$', process(node)) end end rewrite_tilda[node].children[0] else super end end end DEFAULTS.push JQuery end end