Rails源码研究之ActionView:五,prototype_helper

Rails的Ajax是依赖于prototype库的,我们来看看ActionView对prototype的一些方法调用的封装
源文件为prototype_helper.rb,其中包括RJS的update_page调用的JavaScriptGenerator类的定义:
1,link_to_remote是简单的异步url请求
[code]
def link_to_remote(name, options = {}, html_options = {})
link_to_function(name, remote_function(options), html_options)
end

def remote_function(options)
javascript_options = options_for_ajax(options)
update = ''
if options[:update] && options[:update].is_a?(Hash)
update = []
update << "success:'#{options[:update][:success]}'" if options[:update][:success]
update << "failure:'#{options[:update][:failure]}'" if options[:update][:failure]
update = '{' + update.join(',') + '}'
elsif options[:update]
update << "'#{options[:update]}'"
end
function = update.empty? ?
"new Ajax.Request(" :
"new Ajax.Updater(#{update}, "
url_options = options[:url]
url_options = url_options.merge(:escape => false) if url_options.is_a?(Hash)
function << "'#{url_for(url_options)}'"
function << ", #{javascript_options})"
function = "#{options[:before]}; #{function}" if options[:before]
function = "#{function}; #{options[:after]}" if options[:after]
function = "if (#{options[:condition]}) { #{function}; }" if options[:condition]
function = "if (confirm('#{escape_javascript(options[:confirm])}')) { #{function}; }" if options[:confirm]
return function
end
[/code]
link_to_remote调用remote_function组织Prototype方法调用,然后调用link_to_function生成html标签和绑定onclick事件
看看例子:
[code]
<%= link_to_remote "Delete this post", :update => "posts",
:url => { :action => "destroy", :id => post.id } %>

<%= link_to_remote "Undo", :url => { :action => "undo", :n => word_counter },
:complete => "undoRequestCompleted(request)" %>
[/code]
其中:update参数用来在Ajax异步调用完成后更新页面DOM元素,通常controller返回的结果为一个partial

2,periodically_call_remote用来做周期性url请求
[code]
def periodically_call_remote(options = {})
frequency = options[:frequency] || 10
code = "new PeriodicalExecuter(function() {#{remote_function(options)}}, #{frequency})"
javascript_tag(code)
end
[/code]
它new一个PeriodicalExecuter对象来完成周期性的工作,而callbacks等参数和link_to_remote一样,其中:frequency默认是10秒,看看例子:
[code]
<%= periodically_call_remote :update => 'time_div',
:url => {:action => "time"},
:frequency => 1.0 %>
[/code]

3,observe_form和observe_field用来监听form和field,如果内容改变,则触发function调用
[code]
def observe_form(form_id, options = {})
if options[:frequency]
build_observer('Form.Observer', form_id, options)
else
build_observer('Form.EventObserver', form_id, options)
end
end

def observe_field(field_id, options = {})
if options[:frequency] && options[:frequency] > 0
build_observer('Form.Element.Observer', field_id, options)
else
build_observer('Form.Element.EventObserver', field_id, options)
end
end

protected
def build_observer(klass, name, options = {})
if options[:with] && !options[:with].include?("=")
options[:with] = "'#{options[:with]}=' + value"
else
options[:with] ||= 'value' if options[:update]
end
callback = options[:function] || remote_function(options)
javascript = "new #{klass}('#{name}', "
javascript << "#{options[:frequency]}, " if options[:frequency]
javascript << "function(element, value) {"
javascript << "#{callback}}"
javascript << ", '#{options[: on]}'" if options[: on]
javascript << ")"
javascript_tag(javascript)
end
[/code]
build_observer方法给出了生成JavaScript的实现(这段代码看着有点reflection的意思),然后使用javascript_tag来将JavaScript写入模板
看看例子:
[code]
<%= observe_form "entry-form", :frequency => 1,
:update => "live-preview",
:complete => "Element.show('live-preview')",
:url => {:action => "preview"} %>

<%= observe_field 'search', :frequency => 0.5,
:update => 'target_id',
:url => { :controller => '<controller>', :action=> 'list' },
:with => "'search=' + escape(value)" %>
[/code]

4,JavaScriptGenerator
[code]
def update_page(&block)
JavaScriptGenerator.new(@template, &block).to_s
end

class JavaScriptGenerator

def initialize(context, &block) #:nodoc:
@context, @lines = context, []
include_helpers_from_context
@context.instance_exec(self, &block)
end

private
def include_helpers_from_context
@context.extended_by.each do |mod|
extend mod unless mod.name =~ /^ActionView::Helpers/
end
extend GeneratorMethods
end

module GeneratorMethods

def to_s
returning javascript = @lines * $/ do
if ActionView::Base.debug_rjs
source = javascript.dup
javascript.replace "try {\n#{source}\n} catch (e) "
javascript << "{ alert('RJS error:\\n\\n' + e.toString()); alert('#{source.gsub('\\','\0\0').gsub(/\r\n|\n|\r/, "\\n").gsub(/["']/) { |m| "\\#{m}" }}'); throw e }"
end
end
end

def [](id)
JavaScriptElementProxy.new(self, id)
end

def select(pattern)
JavaScriptElementCollectionProxy.new(self, pattern)
end

def insert_html(position, id, *options_for_render)
insertion = position.to_s.camelize
call "new Insertion.#{insertion}", id, render(*options_for_render)
end

def replace_html(id, *options_for_render)
call 'Element.update', id, render(*options_for_render)
end

def replace(id, *options_for_render)
call 'Element.replace', id, render(*options_for_render)
end

def remove(*ids)
loop_on_multiple_args 'Element.remove', ids
end

def show(*ids)
loop_on_multiple_args 'Element.show', ids
end

def hide(*ids)
loop_on_multiple_args 'Element.hide', ids
end

def toggle(*ids)
loop_on_multiple_args 'Element.toggle', ids
end

def alert(message)
call 'alert', message
end

def redirect_to(location)
assign 'window.location.href', @context.url_for(location)
end

def call(function, *arguments, &block)
record "#{function}(#{arguments_for_call(arguments, block)})"
end

def delay(seconds = 1)
record "setTimeout(function() {\n\n"
yield
record "}, #{(seconds * 1000).to_i})"
end

def visual_effect(name, id = nil, options = {})
record @context.send(:visual_effect, name, id, options)
end

def sortable(id, options = {})
record @context.send(:sortable_element_js, id, options)
end

def draggable(id, options = {})
record @context.send(:draggable_element_js, id, options)
end

def drop_receiving(id, options = {})
record @context.send(:drop_receiving_element_js, id, options)
end

def call(function, *arguments, &block)
record "#{function}(#{arguments_for_call(arguments, block)})"
end

private

def page
self
end

def record(line)
returning line = "#{line.to_s.chomp.gsub(/\;\z/, '')};" do
self << line
end
end

def method_missing(method, *arguments)
JavaScriptProxy.new(self, method.to_s.camelize)
end

end
end
[/code]
我们终于看到了update_page这个helper方法了,它初始化JavaScriptGenerator对象,然后调用to_s方法返回生成的JavaScript代码
而update_page后的block中page方法返回self,即返回JavaScriptGenerator对象
JavaScriptGenerator类中定义了select,replace,hide,delay,visual_effect等我们熟悉的helper方法
而其中visual_effect,sortable,draggable和drop_receiving实际上会调用scriptaculous_helper中相应的方法
看看RJS的例子:
[code]
update_page do |page|
page.insert_html :bottom, 'list', "<li>#{@item.name}</li>"
page.visual_effect :highlight, 'list'
page.hide 'status-indicator', 'cancel-link'
end
[/code]
而[]方法和select方法分别使用JavaScriptElementProxy和JavaScriptElementCollectionProxy代理类

如果大家想了解和熟悉RJS语法,多看看javascript_helper.rb,prototype_helper.rb,scriptaculous_helper.rb
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值