Tuesday 29 July 2008

icon_button_to_remote

Following from my earlier post on making icon buttons, I needed to add AJAXy goodness to the edit actions on one of our index pages.... but I still wanted cute little icons to make them look pretty.

So I've updated the code to incorporate an icon_button_to_remote method. Note that icon_button_to has also been modified, but it still depends upon the get_icon_path and fixed_button_to methods from the previous post.

  # generates appropriate image-buttons, given an "icon type" and the usual
  # button options.
  def icon_button_to(icon_type, icon_label, form_opts = nil, button_opts = {})
    unless form_opts.blank?
      button_opts.merge!(:type => 'image', :src => get_icon_path(icon_type), 
                       :alt => icon_label, :title => icon_label)
      the_icon = fixed_button_to(icon_label, form_opts, button_opts)
    else
      # no form was passed in - we want a non-linked icon.
      the_icon = image_tag(get_icon_path(icon_type), :width => 16, :height => '16', 
                       :alt => icon_label, :title => icon_label)
    end
    content_tag 'div', the_icon, :class => 'icon_button'
  end
  # add a remote AJAX-linked icon button
  def icon_button_to_remote(icon_type, icon_label, remote_options = {}, button_options = {})
    link_to_remote(icon_button_to(icon_type, icon_label, nil, button_options), remote_options)
  end

The example would be for an index page with a list of Widgets. Each one has an "edit" button using the code below. The page also has a section for entering a new widget - which would be replaced by the "edit this widget" template when you click on the icon.

<!-- In the template at the top of the list -->
<%= w_path = new_widget_path() # url_for is slow
    icon_button_to_remote(
         :new, 'Add new widget', 
         :update => 'new_widget_div',
         :url => w_path,
         :method => :get,
         :html => {:href => w_path},
         :complete => "Element.hide('edit_widget_div'); 
                          Element.show('new_widget_div'); 
                          new Effect.Highlight('new_widget_div');"
        ) -%>

<!-- and next to each widget -->
<%= ew_path = edit_widget_path(@widget) # url_for is slow
    icon_button_to_remote(
         :edit, 'Edit this widget', 
         :update => 'edit_widget_div',
         :url => ew_path,
         :method => :get,
         :html => {:href => ew_path},
         :complete => "Element.hide('new_widget_div'); 
                          Element.show('edit_widget_div'); 
                          new Effect.Highlight('edit_widget_div');"
        ) -%>

<!-- Later in the template... -->
<div id="new_widget_div">New widget form would go here</div>

<div id="edit_widget_div" style="display:none;">Edit widget form will be populated here</div>

You also have to update your Controller code for the new and edit actions to accept js and respond with no layout - otherwise your div gets populated with the entire edit/new page :P

The best way is to move the meat of the page into a partial (which can also be loaded in the edit.rhtml template) callable from the action thus:

  def edit
    respond_to do |format|
      format.js { return render(:partial => 'edit', :layout => false) }
      format.html # edit.rhtml
    end
  end

No comments: