Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

Ticket #933 (closed enhancement: fixed)

Opened 3 years ago

Last modified 3 years ago

[PATCH] send_ajax response helper

Reported by: mortonda@dgrmm.net Assigned to: David
Priority: high Milestone:
Component: ActionPack Version: 0.11.0
Severity: normal Keywords: ajax update helper
Cc: mortonda@dgrmm.net

Description

A more flexible ajax method would be for link_to_remote or similar to use

:complete => 'eval(request.responseText)'

and then have the response specify what action is to happen in the client, via javascript. This could be a partial that is simply some javascript code. some response helpers could be made to assist with this code, to automatically escape whatever needs to be escaped. I propose something like this:

<%= ajax_update "target" do %>
html code to go in a div named "target", with single quotes 
and any other dangerous javascript escaped.
<% end %>

This could be accompanied by similar functions for ajax_delete, ajax_insert, ajax_append, etc.

Additionally, multiple statements could be included in the response, so multiple parts of the sceen could be updated at the same time.

Attachments

send_ajax.diff (2.5 kB) - added by mortonda@dgrmm.net on 03/29/05 17:44:29.
apply to: actionpack/lib/action_view/helpers/javascript_helper.rb
send_ajax.2.diff (2.3 kB) - added by mortonda@dgrmm.net on 03/29/05 17:57:25.
typo in docs fixed. Use this patch instead of previous
send_ajax.3.diff (2.8 kB) - added by mortonda@dgrmm.net on 03/30/05 05:18:26.
added formfill action which can fill in all the fields for a given AR object.
send_ajax.4.diff (2.9 kB) - added by mortonda@dgrmm.net on 03/31/05 18:14:54.
fixed typo with :target variables.
upload_progress_send_ajax.rb (2.2 kB) - added by madrobby on 04/05/05 05:31:06.
Slightly expanded version to use it in helpers, adds an optional "text" parameter
ajax_render.rb (2.8 kB) - added by freedumb on 04/05/05 08:28:42.
Helper module for the controller. Provides #render_ajax and #render_ajax_to_string .
send_ajax.5.diff (2.9 kB) - added by mortonda@dgrmm.net on 04/07/05 20:56:54.
added JS check to see if form object exists before accessing the value attribute

Change History

03/26/05 00:14:53 changed by mortonda@dgrmm.net

requires patch in #940, as the html text may contain newlines.

03/26/05 00:35:53 changed by mortonda@dgrmm.net

Here's a first stab at a helper function:

      def ajax_update(target, &block)
                # execute the block=
        buffer = eval("_erbout", block.binding)
        pos = buffer.length
        block.call
        
        # extract the block 
        data = buffer[pos..-1]
        
        # replace it in the original with empty string
        buffer[pos..-1] = ''

        text = escape_javascript(data)
        "$('#{target}').innerHTML = '#{text}'"

      end

To use this, place something like this in a partial or a template (make sure no layout is used):

<% @result = ajax_update("targetdiv") do  %>
"That's all folks!"
I just replaced your <b>targetdiv</b> with this text. How do you like that?
<% end %>
<%= @result %>

and to trigger it, make a page that actually has a div with an id of "targetdiv" and use link_to_remote:

<div id="targetdiv">blah</div>
<div>
<%= link_to_remote "deletefirst",
          :url => {
            :controller =>"message",
            :action=>"deletefirst"
          },
          :complete => 'eval(request.responseText)'
%>
</div>

Again, the beauty of this is that the server sepcifies were the response goes, not the client; if there was an error, the server could update the flash area or something.

Still todo: insert, append, delete... should be similar to code already written.

again: don't forget patch in #940

03/26/05 03:06:21 changed by mortonda@dgrmm.net

This supports more functions, and raises an argument error if called with an invalid argument

      def send_ajax(target, action = :update, &block)
        
        if block then
          # execute the block=  
          # this code stolen from the capture helper
          buffer = eval("_erbout", block.binding)
          pos = buffer.length
          block.call
          
          # extract the block 
          data = buffer[pos..-1]
          
          # replace it in the original with empty string
          buffer[pos..-1] = ''
          text = escape_javascript(data)
        end
          
        # Some discussion has been taking place on 
        # http://wiki.rubyonrails.com/rails/show/Ajax%20Discussion
        # as to whether this is the fastest way to manipulate the DOM
        # At the moment, it appears the safest and most expedient
        case action
          when :replace            
            "$('#{target}').innerHTML = '#{text}'"
          when :insert
            "$('#{target}').innerHTML = '#{text}' + $('#{target}').innerHTML"         
          when :append
            "$('#{target}').innerHTML += '#{text}'"
          when :empty
            "$('#{target}').innerHTML = ''"
          when :delete
            "$('#{target}').parentNode.removeChild($('#{target}'))"
          else
            raise ArgumentError
        end
      
      end

03/26/05 03:12:19 changed by mortonda@dgrmm.net

one could add some more descriptive text to the raise ArgumentError line ;)

03/26/05 03:16:40 changed by leeo

A more verbose argument error:

raise ArgumentError, "second parameter must be one of replace, insert, append, empty or delete"

03/26/05 20:46:28 changed by mortonda@dgrmm.net

Makes use of new position code and better errors

      # helper function to send a javascript command to the client.
      # options hash should have:
      # :action => (:insert, :replace, :delete, :empty)
      # :target => DOM element. (string name)
      # :position => (:before, :top, :bottom, :after) 
      #   postion is only used in the insert routines
      # pass in a block of captured erb html for functions that make sense to do so.
      def send_ajax(options, &block)
        
        if block then
          # execute the block=  
          # this code stolen from the capture helper
          buffer = eval("_erbout", block.binding)
          pos = buffer.length
          block.call
          
          # extract the block 
          data = buffer[pos..-1]
          
          # replace it in the original with empty string
          buffer[pos..-1] = ''
          text = escape_javascript(data)
        end
          
        # Some discussion has been taking place on 
        # http://wiki.rubyonrails.com/rails/show/Ajax%20Discussion
        # as to whether this is the fastest way to manipulate the DOM
        # At the moment, it appears the safest and most expedient
        case options[:action]
          when :insert
            case options[:position]
              when :before
                "Insert.before('#{options[:target]}', '#{text}')"
              when :top
                "Insert.top('#{options[:target]}', '#{text}')"
              when :bottom
                "Insert.bottom('#{options[:target]}', '#{text}')"
              when :after
                "Insert.after('#{options[:target]}', '#{text}')"
              else
                raise ArgumentError, "Invalid position, choose one of :before, :top, :bottom, :after"
            end
          when :replace
            "$('#{target}').innerHTML = '#{text}'"         
          when :empty
            "$('#{target}').innerHTML = ''"
          when :delete
            "$('#{target}').parentNode.removeChild($('#{options[:target]}'))"
          else
            raise ArgumentError, "Invalid action, choos one of :insert, :replace, :delete, :empty"
        end
      
      end

03/27/05 14:17:40 changed by mortonda@dgrmm.net

note: this code work in revision 1011, but the OO'ified Insertion lib broke this code.

03/29/05 17:42:44 changed by mortonda@dgrmm.net

  • summary changed from ajax response helpers to [PATCH] ajax response helpers.

Updated to work with OO'ified Insertion lib.

(I needed to add a "new" to it to create the object)

      # helper function to send a javascript command to the client.
      # options hash should have:
      # :action => (:insert, :replace, :delete, :empty)
      # :target => DOM element. (string name)
      # :position => (:before, :top, :bottom, :after) 
      #   postion is only used in the insert routines
      # pass in a block of captured erb html for functions that make sense to do so.
      def send_ajax(options, &block)
        
        if block then
          # execute the block=  
          # this code stolen from the capture helper
          buffer = eval("_erbout", block.binding)
          pos = buffer.length
          block.call
          
          # extract the block 
          data = buffer[pos..-1]
          
          # replace it in the original with empty string
          buffer[pos..-1] = ''
          text = escape_javascript(data)
        end
          
        case options[:action]
          when :insert
            case options[:position]
              when :before
                "new Insertion.Before('#{options[:target]}', '#{text}')"
              when :top
                "new Insertion.Top('#{options[:target]}', '#{text}')"
              when :bottom
                "new Insertion.Bottom('#{options[:target]}', '#{text}')"
              when :after
                "new Insertion.After('#{options[:target]}', '#{text}')"
              else
                raise ArgumentError, "Invalid position, choose one of :before, :top, :bottom, :after"
            end
          when :replace
            "$('#{target}').innerHTML = '#{text}'"         
          when :empty
            "$('#{target}').innerHTML = ''"
          when :delete
            "$('#{target}').parentNode.removeChild($('#{options[:target]}'))"
          else
            raise ArgumentError, "Invalid action, choos one of :insert, :replace, :delete, :empty"
        end
      
      end

03/29/05 17:44:29 changed by mortonda@dgrmm.net

  • attachment send_ajax.diff added.

apply to: actionpack/lib/action_view/helpers/javascript_helper.rb

03/29/05 17:57:25 changed by mortonda@dgrmm.net

  • attachment send_ajax.2.diff added.

typo in docs fixed. Use this patch instead of previous

03/29/05 18:04:11 changed by anonymous

  • summary changed from [PATCH] ajax response helpers to ajax response helpers - exploit ruby parsetree?.

Something like Ruby ParseTree (rubyforge) might make the following feasible: - write just Ruby code including for the DOM - use some Ruby-browser bridge to see the DOM (e.g. COM/IE6) - debug just Ruby code - use ParseTree to transform into Javascript

This will probably mean - some subset of Ruby (classes, methods, blocks should be fine) that can be mapped reasonably to Javascript - a fair chunk of work up front on the browser bridge

But the result might be worth it.

03/29/05 18:14:30 changed by mortonda@dgrmm.net

  • priority changed from normal to high.
  • summary changed from ajax response helpers - exploit ruby parsetree? to send_ajax response helper.

Possibly interesting idea, but not applicable to this ticket. A better place to discuss it would be http://wiki.rubyonrails.com/rails/show/Ajax%20Discussion

03/29/05 18:16:29 changed by mortonda@dgrmm.net

  • summary changed from send_ajax response helper to [PATCH] send_ajax response helper.

03/30/05 05:18:26 changed by mortonda@dgrmm.net

  • attachment send_ajax.3.diff added.

added formfill action which can fill in all the fields for a given AR object.

03/31/05 18:14:54 changed by mortonda@dgrmm.net

  • attachment send_ajax.4.diff added.

fixed typo with :target variables.

04/05/05 05:31:06 changed by madrobby

  • attachment upload_progress_send_ajax.rb added.

Slightly expanded version to use it in helpers, adds an optional "text" parameter

04/05/05 08:28:42 changed by freedumb

  • attachment ajax_render.rb added.

Helper module for the controller. Provides #render_ajax and #render_ajax_to_string .

04/07/05 20:56:54 changed by mortonda@dgrmm.net

  • attachment send_ajax.5.diff added.

added JS check to see if form object exists before accessing the value attribute

04/08/05 02:50:49 changed by madrobby

Mortonda: please have a look at "upload_progress_send_ajax.rb", and incorporate this change in your code (simple but very useful addition...)! thx

04/21/05 09:57:05 changed by mortonda@dgrmm.net

here's another way to call send_ajax:

<% concat(send_ajax( ......) do %>
html stuff
<% end, binding) %> 

07/03/05 11:01:49 changed by david

  • status changed from new to closed.
  • resolution set to fixed.