The default “in_place_editing” plugin for Rails is disappointing. This is my
solution.
- Support for nested resources
- RESTful. Just add a line to your
respond_to
block to render as JSON. - Easy to use outside of Rails. Just add a class name and the path as the
rel
attribute to an element and you’re done. - Logic lives in the JavaScript (You may consider this a feature or a flaw. I
consider it a feature.)
- Support for text input, text area and select fields
- Support for foreign key parameters for select fields
- Customizable. One can set:
- class for edit element
- class submit and cancel buttons
- custom functions to be called on Ajax.Request onLoading and onComplete events
i.e. to set up a spinner.
The better-edit-in-place script makes it easy to create AJAX edit-in-place
fields in a RESTful web app. Note that if your approach isn’t RESTful,
then this script won’t do much for you.
If you have not installed this plugin using script/plugin install
script,
then copy src/editable.js file to your public/javascripts folder.
To load this file with your Rails app simply add the following line to your layout:
<%= javascript_include_tag 'editable' %>
If you’re using the classic way of handling events:
document.observe('dom:loaded', function(event) { Editable.setupAll(); });
Another solution is to use event delegation:
document.delegators('click', { '.editable': function(element) { element.editable(); } });
To make an element editable, give it the class name editable
. You can use different
class name, but in this case you need to pass it to Editable.setupAll method
or change CSS selector if you’re using event delegation.
Specify the element’s resource url as the element’s rel
attribute, then give it
an id
attribute that contains the name of the record’s model, as well as
the attribute to be edited. You can also add the record’s id as well, so
as not to have multiple elements with the same id in the same page. All in,
an editable element’s attributes look like this:
<span class="editable" id="list_1_name" rel="/lists/1">…value…</span>
When the element is clicked, it will be hidden, and an input will appear
that will allow the user to change the field. You must have an appropriate
respond_to set in your controller that renders the record as a JSON response.
A Rails example:
respond_to do |format| format.html # Whatever format.json { render :json => @list } end
If you’re using this plugin in a Rails app, you can use the following view
helper to simplify the markup for your editable element:
edit_in_place(@list, :name)
By default, the edit-in-place tag will be a <span>
element. You can override
this using the :tag
option:
edit_in_place(@list, :name, :tag => :h1)
If you only want to allow editing when certain conditions are met, passing :allow
with either true or false will
allow (or not) in place editing.
edit_in_place(@list, :name, :allow => @list.finalized? )
If the non-editable field has an empty or nil value it will render the attribute name:
<i title="This information is missing.">[name]</i>
NOTE
This will only happen if :allow
is passed false and the attribute returns true when blank?
is called
on it.
When editable attributes have no value the in place editor will have a default :empty_message
:
If name is blank?
:
edit_in_place(@list, :name)
Will produce:
<span class="editable" id="list_1_name" rel="/lists/1">[name]</span>
If name is blank?
and an :empty_message
is set:
edit_in_place(@list, :name, :empty_message => 'enter name')
Will produce:
<span class="editable" id="list_1_name" rel="/lists/1">enter name</span>
Setting :edit_blank
to false will render an empty span with an attached in place edit form if the value is blank?
.
Better Edit in Place also works seamlessly with nested resources. Just pass
in the resources as an array. This will update an item’s name
attribute:
edit_in_place([@list, @item], :name)
The list_item_path
will be determined automatically. If you wish to
pass a different URL in, you can do that as well:
edit_in_place([@list, @item], :name, :url => weird_item_path(@item))
You might need to tell ActiveRecord not to include the root while serializing
to JSON. To do so, put this line in an initializer, or environment.rb:
ActiveRecord::Base.include_root_in_json = false
:id
:tag
:url
:rel
:edit_blank
:empty_message
:allow
By default input text element is used. To change it to text area, check box, or select, you need to set
editField type:
document.delegators('click', { '.editable': function(element) { element.editable({ editField: {'type': 'textarea'} // or 'select', 'checkbox' }); } });
The checkbox
behaves the same as a select
in that when it is changed the value is updated without the need to submit.
Edit element class name can also be set:
element.editable({ editField: { 'type': 'textarea', 'class': 'editable-input' } });
Class name can also be set for submit and cancel buttons:
element.editable({ submitButtonClass: 'editable-submit', cancelButtonClass: 'editable-cancel' });
For select elements you need to specify possible options:
element.editable({ editField: { 'type': 'select', 'options': [["black", "1"], ["gray", "2"], ["white", "3"]] } });
To load data directly from Rails, you can use to_json method:
element.editable({ editField: { 'type': 'select', 'options': <%= Colors.all.map{|c| [c.name, c.id.to_s]}.to_json %> } });
If edited attribute is a foreign key, you need to explicitly specify it:
element.editable({ editField: { 'type': 'select', 'options': <%= Author.all.map{|c| [c.name, c.id.to_s]}.to_json %>, 'foreignKey': true } });
When using Rails helper, you need to refer to the actual object, instead of the foreign key:
edit_in_place(@post, :author)
You also need to set #to_s method on the associated model i.e.
class Author < ActiveRecord::Base def to_s name end end
You can set custom callbacks for Ajax.Request onLoading and onComplete events:
element.editable({ onLoading: function() {alert("Loading...")}; onComplete: function() {alert("Request completed")}; });
Custom functions can be fired after onComplete
finishes, allowing for further page manipulation.
element.editable({ editField: { type: 'checkbox' }, afterSave : function (edited) { if (edited.element) edited.element.toggleClassName('true').toggleClassName('false'); } });
Configurable options:
option | default |
---|---|
editField.type | ‘input’ |
fieldSize | ‘30×5’ (only used with text_field and text_area input types) |
submitButtonClass | inplace-submit |
cancelButtonClass | inplace-cancel |
saveText | Save |
cancelText | Cancel |
onLoading | Prototype.emptyFunction |
onComplete | Prototype.emptyFunction |
afterSave | false (can be passed a function to act on updated element or page ) |
- Escape key should cancel (simple event handler)
- Maybe some more options (but not too many)
- Instead of adding .json to URL, set proper content-type
- Clean up fieldSize setting handling
- Figure out how to have inline elements not collapse and reflow page when edit
form is absolute positioned and/or is larger than containing element.
Copyright © 2008 Pat Nakajima