lost password?

home
•  xaraya
•  rails +
•  django
•  webdev
•  xamp
•  musings

rss
Tag this page
   

» Blogs that link here
last modified: Mar 04, 2008
(first posted: Feb 11, 2008)
(3140 Reads)
keywords: in_place_editor REST
Permalink

Restful In Place Editor

Here's a patch to the in_place_editor to work smoother with RESTful resources.

I was bothered that using the standard in_place_editor helpers for Rails required I add new actions to my controllers and corresponding routes in routes.rb.

As of Rails 2.0 it's is now a plug-in rather than part of core. I should probably make my changes a separate plug-in, or better, submit this as a patch. Instead, for now, I suggest you install the plug-in and copy and hack the code.

To install,

$ script/plugin install http://svn.rubyonrails.org/rails/plugins/in_place_editing/ 

Open the file vendor/plugins/in_place_editing/lib/in_place_macros_helper.rb and copy the two methods, in_place_editor and in_place_editor_field to your app/helpers/application_helper.rb

in_place_editor helper

We add a new option to this helper:

:as     Name of the param the value is returned into
e.g. :as => 'foo[name]'

To the in_place_editor method, add the following line after the js_options['callback'] = line:

    js_options['callback']   = "function(form) { return #{options[:with]} }" if options[:with]
# this line is the only change
js_options['callback'] = "function(form,value) { return '#{options[:as]}=' + escape(value) }" if options[:as]

 

in_place_editor_field helper

In this we make just 2 changes. First, the default :url will now be your standard :update action in the controller. And 2nd, the new value will be set using standard REST parameters, eg params[:foo => { attribute_name => value }]


# Renders the value of the specified object and method with in-place editing capabilities.
def in_place_editor_field(object, method, tag_options = {}, in_place_editor_options = {}) tag = ::ActionView::Helpers::InstanceTag.new(object, method, self)
tag_options = {:tag => "span", :id => "#{object}_#{method}_#{tag.object.id}_in_place_editor", :class => "in_place_editor_field"}.merge!(tag_options)
in_place_editor_options[:url] ||= { :action => "update", :id => tag.object.id, :method => :post, :_method => :put }
in_place_editor_options[:as] = "#{object}[#{method}]"
tag.to_content_tag(tag_options.delete(:tag), tag_options) + in_place_editor(tag_options[:id], in_place_editor_options)
end


Your controller

Last but not least you do things differently in your controller than the standard in_place_editor.

You do not need to use "in_place_edit_for" at all! Forgetaboutit!!

Rather, you just change the #update action to respond to ajax calls, as follows:

  # PUT /foo/1
def update
@foo = Foo.find(params[:id])
success = @foo.update_attributes(params[:foo])
respond_to do |format|
format.html do
if success
flash[:notice] = 'Foo was successfully updated.'
redirect_to(@foo)
else
render :action => "edit"
end
format.js do
# assume updating only one attribute
attribute = params[:foo].keys.first.to_s
render :text => self.class.attributes.include? attribute ? @foo[attribute] : '(bad attribute)'
end

end
end

Basically we update the attributes in params, as usual. Except when its an ajax call, we assume there's really only one attribute in the params being updated. And the ajax renders the new value for updating the page.

A bit unconventionally, I do the update_attributes first then respond based on format. That's because, as mentioned in other blogs, there's no easy way to handle validations when doing in_place_editing. But then again it can be done (google it), and change your #update action as needed.

Views

There's nothing to change in the views, they work the same as the standard in_place_editor, for example

<%= in_place_editor_field "foo", :title %>

 

PS Thanks for the leethal comments... :)

 

UPDATE March 4, 2008

I found some views reference attributes better handled from a different controller than the current one, like associations of the current resource. I've modified the in_place_editor_field to use a controller based on the resource object name rather than assume the current controller.

I also added support for a :formatter option, so you can run the content through a filter before updating the view. For example, I use BlueCloth formatting, and specify :formatter => 'markdown' on my text attributes.

Here's the full helper: 

  def in_place_editor_rest(object, method, tag_options = {}, in_place_editor_options = {})
tag = ::ActionView::Helpers::InstanceTag.new(object, method, self)
tag_options = {:tag => "span", :id => "#{object}_#{method}_#{tag.object.id}_in_place_editor", :class => "in_place_editor_field"}.merge!(tag_options)
in_place_editor_options[:url] ||= { :controller => object.pluralize, :action => "update", :id => tag.object.id, :method => :post, :_method => :put }
in_place_editor_options[:as] = "#{object}[#{method}]"
# changed to support inline formatter
if formatter = in_place_editor_options.delete(:formatter)
in_place_editor_options[:load_text_url] ||= { :controller => object.pluralize, :action => 'show', :id => tag.object.id, :attribute => method.to_s }
var = @template.instance_variable_get("@#{object}")
value = var.send(method)
content = content_tag(tag_options.delete(:tag), @template.send( formatter, value), tag_options)
else
content = tag.to_content_tag(tag_options.delete(:tag), tag_options)
end
content + in_place_editor(tag_options[:id], in_place_editor_options)
end

 

Restful In Place Editor

Posted by: Ian on February 23, 2008 05:17 AM
This is good, thank you! I'll leave you a lethal comment next time. :)

#

Restful In Place Editor

Posted by: Sven on February 29, 2008 07:21 AM
thank you for this patches, but I still cannot get it working. I also applied the patches for the protect_from_forgery bug (http://dev.rubyonrails.org/ticket/10055) but still get InvalidAuthenticityToken this is my view: <% for context in @contexts %> <% @context = context %> <%= in_place_editor_field :context , :name %> <% end %>
<%= will_paginate @contexts %>

#

Restful In Place Editor

Posted by: linoj on February 29, 2008 11:43 AM
Sven, i just found an alternative solution to mine, posted within days of this post, which may play better with the 2.0 forgery stuff http://www.bizmeetsdev.com/articles/2008/02/09/editable_content_tag

#

Restful In Place Editor

Posted by: linoj on March 05, 2008 12:11 AM
I'm adding this note (found on the rails wiki) although I havent tried it or integrated it into the helper: "In rails 2.0, if you’re trying to submit some crazy AJAX you’ve coded manually and you’re getting an Invalid Authenticity Token error, be sure to add the following to the query string of parameters being submitted: &authenticity_token=<%= form_authenticity_token %> form_authenticity_token will generate a valid token that rails needs to validate the request.

#

Restful In Place Editor

Posted by: Ken on March 18, 2008 01:23 PM
I just tried adding the authenticity_token to the string as you described above, and it works perfectly now. A heck of a lot easier than the alternatives. Any known security issues? Anybody else wonder why in_place_editor would be considered "crazy AJAX" ? Seems pretty mainstream to me...

#

Restful In Place Editor

Posted by: Brian on April 12, 2008 12:24 AM
Take a look at http://dev.rubyonrails.org/ticket/10055 for an alternative solution for this problem.

#

Restful In Place Editor

Posted by: Hiroshi on June 21, 2008 10:31 PM
Nice patch, but there is a problem with multi-bytes characters in the value. It could be avoided by using encodeURIComponent() instead of escape(). - js_options['callback'] = "function(form,value) { return '#{options[:as]}=' + escape(value) }" if options[:as] + js_options['callback'] = "function(form,value) { return '#{options[:as]}=' + encodeURIComponent(value) }" if options[:as]

#

Restful In Place Editor

Posted by: Nick on June 23, 2008 06:04 PM
Can you help explain the last line in your controller? render :text => self.class.attributes.include? attribute ? @foo[attribute] : '(bad attribute)' This doesn't even allow the page to load for me, and I'm trying to figure out what we're actually doing and what I need to change for my situation. I basically just changed @foo to @contact, but I think I'm missing something.

#

Post a new comment

: This is not spam

Name :