aboutsummaryrefslogblamecommitdiffstats
path: root/actionpack/lib/action_view/helpers/javascript_helper.rb
blob: 619ce94341c70e259feb05e9236d5160aa2ec21d (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11

                                              



                                                                                                      




                                                                                                                         





                                                                                                   
 







                                                                                                                                       
       

                                                                                                                                             



                                                                



                                                                 







                                                                                                                                      

                                                                      
         
 

                                       
 

                                                                                
 


                                         











                                                                                                                                   


                                     












                                                                             
                                                                                                    



                    
                                
 
                                     


                                       
                                    
                                    

                  






                                                     


                                                           
     
 



                                    
                                    
                                                   
      

                                      
















                                                                 
                                                             
      
                                         
     
 










                                                       
 


                                                       
 

                 
 
 
                                                       
 


                                                                          
 





                                                                                                



                                         










                                                                                      














                                                                           
    



                                                 
    


                                               
    




                                                              
  


                                                  
    


                                                     
      






                                                                         
    

                                         
  








                                                               
     




             













                                                                            
                                    

                                              
                                        




                                                                


                                                                      
                         
 

                                                                           
                                                                                          
      

                         
       
     
   
require File.dirname(__FILE__) + '/tag_helper'

module ActionView
  module Helpers
    # You must call <%= define_javascript_functions %> in your application before using these helpers.
    module JavascriptHelper
      # Returns a link that'll trigger a javascript +function+ using the onclick handler and return false after the fact.
      #
      # Examples:
      #   link_to_function "Greeting", "alert('Hello world!')"
      #   link_to_function(image_tag("delete"), "if confirm('Really?'){ do_delete(); }")
      def link_to_function(name, function, html_options = {})
        content_tag(
          "a", name, 
          html_options.symbolize_keys.merge(:href => "#", :onclick => "#{function}; return false;")
        )
      end

      # Returns a link to a remote action defined by <tt>options[:url]</tt> (using the url_for format) that's called in the background 
      # using XMLHttpRequest. The result of that request can then be inserted into a DOM object who's id can be specified
      # with <tt>options[:update]</tt>. Usually, the result would be a partial prepared by the controller with either render_partial
      # or render_partial_collection. 
      #
      # Examples:
      #  link_to_remote "Delete this post", :update => "posts", :url => { :action => "destroy", :id => post.id }
      #  link_to_remote(image_tag("refresh"), :update => "emails", :url => { :action => "list_emails" })
      #
      # By default, these remote requests are processed asynchronous during which various callbacks can be triggered (for progress indicators
      # and the likes).
      #
      # Example:
      #   link_to_remote word,
      #       :url => { :action => "undo", :n => word_counter },
      #       :complete => "undoRequestCompleted(request)"
      #
      # The complete list of callbacks that may be specified are:
      #
      # * <tt>:uninitialized</tt> -- EXPLAIN ME!
      # * <tt>:loading</tt> --  EXPLAIN ME!
      # * <tt>:loaded</tt> --  EXPLAIN ME!
      # * <tt>:interactive</tt> --  EXPLAIN ME!
      # * <tt>:complete</tt> --  EXPLAIN ME!
      #
      # If you for some reason or another needs synchronous processing (that'll block the browser while the request is happening), you
      # can specify <tt>options[:type] = :sync</tt>.
      def link_to_remote(name, options = {}, html_options = {})  
        link_to_function(name, remote_function(options), html_options)
      end

      def form_remote_tag(options = {})
        options[:form] = true

        options[:html] ||= { }
        options[:html][:onsubmit] = "#{remote_function(options)}; return false;"

        tag("form", options[:html], true)
      end

      def remote_function(options)
        function = options[:update] ? 
          "update_with_response('#{options[:update]}', '#{url_for(options[:url])}'#{', Form.serialize(this)' if options[:form]})" :
          "xml_request('#{url_for(options[:url])}'#{', Form.serialize(this)' if options[:form]})"

        function = "#{options[:before]}; #{function}" if options[:before]
        function = "#{function}; #{options[:after]}"  if options[:after]
        function = "if (#{options[:condition]}) { #{function}; }" if options[:condition]

        return function
      end

      def define_javascript_functions
    <<-EOF
    <script language="JavaScript">
    /* Convenience form methods */
    Field = {
      clear: function() {
        for(i = 0; i < arguments.length; i++) { o(arguments[i]).value = ''; }
        return true;
      },

      focus: function(id) {
        o(id).focus();
        return true;
      },
      
      present: function() {
        for(i = 0; i < arguments.length; i++) { if (o(arguments[i]).value == '') { return false; } }
        return true;
      }
    }
    
    /* XMLHttpRequest Methods */

    function update_with_response() {
      var container  = o(arguments[0]);
      var url        = arguments[1];
      var parameters = arguments[2];
      var async      = arguments[3];
      var callbacks  = arguments[4];

      if (async) {
        if(!callbacks) callbacks = {}
        complete = callbacks['complete']
        callbacks['complete'] = function(request) {
          container.innerHTML = request.responseText
          if(complete) complete(request)
        }
        xml_request(url, parameters, true, callbacks)
      } else {
        container.innerHTML = xml_request(url, parameters);
      }
    }

    function xml_request() {
      var url        = arguments[0];
      var parameters = arguments[1];
      var async      = arguments[2];
      var callbacks  = arguments[3];
      var type       = parameters ? "POST" : "GET";
      
      req = xml_http_request_object();
      req.open(type, url, async);

      if (async) {
        invoke_callback = function(which) {
          if(callbacks && callbacks[which]) callbacks[which](req)
        }

        req.onreadystatechange = function() {
          switch(req.readyState) {
            case 0: invoke_callback('uninitialized'); break
            case 1: invoke_callback('loading'); break
            case 2: invoke_callback('loaded'); break
            case 3: invoke_callback('interactive'); break
            case 4: invoke_callback('complete'); break
          }
        }
      }

      req.send(parameters ? parameters + "&_=" : parameters);
      
      if(!async) return req.responseText;
    }

    function xml_http_request_object() {
      var req = false;
      try {
        req = new ActiveXObject("Msxml2.XMLHTTP");
      } catch (e) {
        try {
          req = new ActiveXObject("Microsoft.XMLHTTP");
        } catch (E) {
          req = false;
        }
      }

      if (!req && typeof XMLHttpRequest!='undefined') {
        req = new XMLHttpRequest();
      }

      return req;
    }


    /* Common methods ------------------------------ */

    function toggle_display_by_id(id) {
      o(id).style.display = (o(id).style.display == "none") ? "" : "none";
    }

    function toggle_display() {
      for(i = 0; i < arguments.length; i++) {
        o(arguments[i]).style.display = (o(arguments[i]).style.display == "none") ? "" : "none";
      }
    }

    function o(id) {
      return document.getElementById(id);
    }

    function get_elements_by_class(tag_name, class_name) {
      var all = document.all ? document.all : document.getElementsByTagName(tag_name);
      var elements = new Array();

      for (var e = 0; e < all.length; e++)
        if (all[e].className == class_name)
          elements[elements.length] = all[e];

      return elements;
    }


    /* Serialize a form by Sam Stephenson ------------------------------ */

    Form = {
      Serializers: {
        input: function(element) {
          switch (element.type.toLowerCase()) {
            case 'hidden':
            case 'text':
              return Form.Serializers.textarea(element);
            case 'checkbox':  
            case 'radio':
              return Form.Serializers.inputSelector(element);
          }
        },
    
        inputSelector: function(element) {
          if (element.checked)
            return [element.name, element.value];
        },
    
        textarea: function(element) {
          return [element.name, element.value];
        },
    
        select: function(element) {
          var index = element.selectedIndex;
          return [element.name, element.options[index].value];
        }
      },
  
      serialize: function(form) {
        var elements = Form.getFormElements(form);
        var queryComponents = new Array();
    
        for (var i = 0; i < elements.length; i++) {
          var element = elements[i];
          var method = element.tagName.toLowerCase();
      
          var parameter = Form.Serializers[method](element);
          if (parameter) {
            var queryComponent = encodeURIComponent(parameter[0]) + '=' +
                                 encodeURIComponent(parameter[1]);
            queryComponents.push(queryComponent);
          }
        }
    
        return queryComponents.join('&');
      },
  
      getFormElements: function(form) {
        var elements = new Array();
        for (tagName in Form.Serializers) {
          var tagElements = form.getElementsByTagName(tagName);
          for (var j = 0; j < tagElements.length; j++)
            elements.push(tagElements[j]);
        }
        return elements;
      }
    }
    </script>
    EOF
      end

      private
        def build_callbacks(options)
          callbacks = nil
          %w{uninitialized loading loaded interactive complete}.each do |cb|
            cb = cb.to_sym
            if options[cb]
              callbacks ? callbacks << "," : callbacks = "{"
              callbacks <<
                "#{cb}:function(request){#{options[cb].gsub(/"/){'\"'}}}"
            end
          end
          callbacks << "}" if callbacks
          callbacks
        end

        def remote_function(options)
          callbacks = build_callbacks(options)

          function = options[:update] ? 
            "update_with_response('#{options[:update]}', " :
            "xml_request("

          function << "'#{url_for(options[:url])}'"
          function << ', Form.serialize(this)' if options[:form]
          function << ', null' if !options[:form] && callbacks
          function << ", true" if callbacks || options[:type] != :sync
          function << ", #{callbacks}" if callbacks
          function << ')'

          function = "#{options[:before]}; #{function}" if options[:before]
          function = "#{function}; #{options[:after]}"  if options[:after]
          function = "if (#{options[:condition]}) { #{function}; }" if options[:condition]
      
          return function
        end
    end
  end
end