Fun with in_place_editor
July 12th, 2006
You may or may not be aware of a Rails helper function called in_place_editor. This function generates a handy AJAXified form that displays text from a field in a normal, non-editable block – until the user clicks on the text. Magically, the uneditable text block becomes editable! (it’s hidden via javascript and a previously hidden form is displayed)
The in_place_editor is a quick fix for AJAXifying a field on a form and can be quite handy once it’s set up. After experimenting with it for a while, one major feature that I found lacking from this mini-helper was that data validation errors are not displayed.
In most cases, this probably wouldn’t be a problem, because in many text-areas, the sky is pretty much the limit. In the particular app I was working on at the time however, field size was important. For example, I had a few fields that were limited to 300 characters. In normal ActiveRecord model validation, if someone enters 301 characters and submits a form, the form will come back to them with the error message and the 301 characters still in their box, waiting to be dealt with.
Now try that with the in_place_editor, and instead, you’ll notice that if the record fails to save, you won’t get the customized error messages you get with non-ajax forms, and even worse, those 301 characters that your user poured over will have evaporated into the ether.
My solution was to use a session variable to store any unsaved field data. Since I had to do this for a number of fields in a table, I tried to DRY things up a little by creating a wrapper helper function for in_place_editor.
In these examples, our model is named Thing.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
# ****** thing_controller ******* # # get the actual text from the database def unformatted_text_from_field @thing = Thing.find params[:id] # we check here for the unsaved data in the session in case the # user's data was rejected by validation because we want # to give them a chance to redeem themselves and fix the data if session[:unsaved] and session[:unsaved][params[:field]] render :layout => false, :inline => session[:unsaved][params[:field]] else render :layout => false, :inline => "<%= @thing[\"#{params[:field]}\"] %>" end end # our ajax receiving action def update_thing_in_place @thing = Thing.find params[:id] @thing[params[:field]] = params[:value] if @thing.save if session[:unsaved] session[:unsaved][params[:field]] = nil end else # since the save failed, we need to store the unsaved field # data into our session variable - notice we are using # a hash within the session - just in case they start to # edit two different fields at once without saving, this # hash will keep track of what data goes with what field if session[:unsaved].nil? session[:unsaved] = {} session[:unsaved][params[:field]] = params[:value] end # falls through to RJS template end |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# ***** RJS template (update_thing_in_place.rjs) ***** # # show errors from validation if error_messages_for('thing') page["errors_for_#{params[:field]}"].replace_html error_messages_for 'thing' page["errors_for_#{params[:field]}"].show else page["errors_for_#{params[:field]}"].hide end # put back original text so it's not lost after getting rejected by validator if session[:unsaved] and session[:unsaved][params[:field]] page["#{params[:field]}"].replace_html textilize_without_paragraph(session[:unsaved][params[:field]]) else page["#{params[:field]}"].replace_html textilize_without_paragraph(@thing["#{params[:field]}"]) end |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
# ***** in a helper somewhere ****** # (like helpers/application_helper.rb) # # # creates an in_place_editor for given fieldname # options are rows, cols, save_text (for save button), edit_text (for text in place of empty data) def in_place_field(object, fieldname, label_text = nil, editor_options = {} ) # default options editor_options[:rows] ||= 3 editor_options[:cols] ||= 80 editor_options[:save_text] ||= "save" editor_options[:edit_text] ||= "click here to edit" editor_options[:show_char_count] ||= false output = "" output << "<div id=\"errors_for_#{fieldname}\"></div>" if label_text output << "#{label_text}: " end output << "<div class=\"in_place_edit_box\" id=\"#{fieldname}\">" if object[fieldname] and object[fieldname].size > 0 output << textilize_without_paragraph(object[fieldname]) else output << "<em>#{editor_options[:edit_text]}</em>" end output << "</div>" output << in_place_editor( fieldname, { :load_text_url => { :action => :unformatted_text_from_field, :field => fieldname, :id => object }, :url => { :action => "update_thing_in_place", :field => fieldname, :id => object }, :rows => editor_options[:rows], :cols=> editor_options[:cols], :save_text => editor_options[:save_text], :script => true } ) output end |
1 2 3 |
<div> <%= in_place_field(@thing, "highlights", "Notes/Highlights") %> </div> |
:rows => 1 in the options of in_place_field.
Next steps for this helper function? The most obvious to me would be to extend it to accept fields from any model, instead of being designed just for Thing. Shouldn't be that far off, though, eh?


February 8th, 2008 at 12:01 PM
hi i have a doubt regarding in place editors; when the value of the field is null i am not able to access the editor field. How can i avoid this? Is this a bug or there is something which i am missing here?