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.
# Passing multiple parameters can be accomplished by using array index
# syntax (e.g., ~['a', self])
#
# 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.
#
# Additionally, 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.
#
# Finally, the fourth parameter of $.post defaults to :json, allowing Ruby
# block syntax to be used for the success function.
#
# 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 = "#{data} degrees"
# end
# )
#
# $.ajax({
# url: "/api/getWeather",
# data: {zipcode: 97201},
# success: function(data) {
# $("#weather-temp").html("" + data + " degrees");
# }
# })
module Ruby2JS
module Filter
module JQuery
include SEXP
# 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] == :~ and not @react
# 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)
stopProps = false
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]
stopProps = true if node.children[1] == :[]
if node.children[1] == :~ and node.children[0].children[1] == :~
# consecutive tildes
if node.children[0].children[0].children[1] == :~
result = node.children[0].children[0].children[0]
else
result = s(:attr, node.children[0].children[0], :~)
end
s(:attr, s(:attr, process(result), :~), :~)
else
# possible getter/setter
method = node.children[1]
method = method.to_s.chomp('=') if method =~ /=$/
method = :each! if method == :each
rewrite = [rewrite_tilda[node.children[0]],
method, *node.children[2..-1]]
if stopProps or props.include? node.children[1]
rewrite[1] = 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]],
*node.children[1..-1]]
elsif node.type == :array
# innermost expression is an array
s(:send, nil, '$', *node)
else
# innermost expression is a scalar
s(:send, nil, '$', node)
end
end
process rewrite_tilda[node].children[0]
else
super
end
end
# Example conversion:
# before:
# $$.post ... do ... end
# (block (send (gvar :$$) :post ...) (args) (...))
# after:
# $$.post ..., proc { ... }, :json
# (send (gvar :$$) :post ...
# (block (send nil :proc) (args) (...)) (:sym :json))
def on_block(node)
call = node.children.first
return super unless call.children.first == s(:gvar, :$$)
return super unless call.children[1] == :post
return super unless call.children.length <= 4
children = call.children.dup
children << s(:str, '') if children.length <= 2
children << s(:hash) if children.length <= 3
children << s(:block, s(:send, nil, :proc), *node.children[1..-1])
children << s(:sym, :json)
process call.updated nil, children
end
end
DEFAULTS.push JQuery
end
end