rails
(9,666 views)

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

 

Comments

by Ian on Feb 23, 2008

>>

This is good, thank you! I'll leave you a lethal comment next time. :)

by admin on Feb 29, 2008

>>

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

by admin on Mar 05, 2008

>>

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.

by Ken on Mar 18, 2008

>>

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...

by Hiroshi on Jun 22, 2008

>>

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]

by Nick on Jun 23, 2008

>>

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.

by Biggles on Dec 11, 2008

Re: Restful In Place Editor

Handling the data with RJS still raises the problem that after updating the value, the javascript of the RJS is displayed instead of the updated value. I tried to outline the solution to that at http://schotte.twoday.net/stories/5381459/

by Mohamed Aslam on Apr 06, 2009

>>

SVN url is not working.

New Comment

markdown formatting permitted