`
hideto
  • 浏览: 2683558 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

Rails源码研究之ActionView:五,prototype_helper

    博客分类:
  • Ruby
阅读更多
Rails的Ajax是依赖于prototype库的,我们来看看ActionView对prototype的一些方法调用的封装
源文件为prototype_helper.rb,其中包括RJS的update_page调用的JavaScriptGenerator类的定义:
1,link_to_remote是简单的异步url请求
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

link_to_remote调用remote_function组织Prototype方法调用,然后调用link_to_function生成html标签和绑定onclick事件
看看例子:
<%= 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)" %>

其中:update参数用来在Ajax异步调用完成后更新页面DOM元素,通常controller返回的结果为一个partial

2,periodically_call_remote用来做周期性url请求
def periodically_call_remote(options = {})
   frequency = options[:frequency] || 10
   code = "new PeriodicalExecuter(function() {#{remote_function(options)}}, #{frequency})"
   javascript_tag(code)
end

它new一个PeriodicalExecuter对象来完成周期性的工作,而callbacks等参数和link_to_remote一样,其中:frequency默认是10秒,看看例子:
<%= periodically_call_remote :update => 'time_div', 
                             :url => {:action => "time"},
                             :frequency => 1.0 %>


3,observe_form和observe_field用来监听form和field,如果内容改变,则触发function调用
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

build_observer方法给出了生成JavaScript的实现(这段代码看着有点reflection的意思),然后使用javascript_tag来将JavaScript写入模板
看看例子:
<%= 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)" %>


4,JavaScriptGenerator
      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

我们终于看到了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的例子:
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

而[]方法和select方法分别使用JavaScriptElementProxy和JavaScriptElementCollectionProxy代理类

如果大家想了解和熟悉RJS语法,多看看javascript_helper.rb,prototype_helper.rb,scriptaculous_helper.rb
分享到:
评论
1 楼 weskycn 2007-07-03  
太棒了,你真是个rails's hero.

相关推荐

Global site tag (gtag.js) - Google Analytics