In place editing with Javascript, jQuery and Rails 3
Introduction
To do that, I wanted to think of a general solution, one that could be applied to my other projects. Even a framework agnostic solution. I decided I would write it completely in Javascript and communicate with the server in a conventional RESTful way. Doing a bit of research before starting out, I ran across the project of Jan Varwig called Rest In Place which did precisely the same thing. So I examined carefully his code, fixed some stuff and extended it to support all usage cases I wanted to cover, this is how BestInPlace is born.
Basically, BestInPlace makes possible to tag (via HTML classes and HTML5 data* attributes) any field that is going to be user-editable so that the script automatically converts it to an form input when the user clicks on it, with no further muss or fuss for the developer. Of course, this field can take the form of a one-line text input, a textarea for longer texts, a select dropdown that will populate with your custom collection of options or boolean sort of data that works the same way a checkbox would, and allows value customization as well. Additionally, the script will trim and sanitize all user input, display server errors in case the format is not proper, it will also allow you to provide an external handler to activate the input.
Before getting into details.
Installation
Simply copy and load the files from the folder the following JS files in your application (in the same order):
Add the following line to your onLoad block:
$(document).ready(function() {
/* Activating Best In Place */
jQuery(".best_in_place").best_in_place()
});
Installation of the Rails 3 Gem
Only add the folloging line to your application’s Gemfile and run bundle install:
gem "best_in_place"
You still need to load the onLoad block, jQuery.js and jquery.purr.js in your application, but you can use the generator to copy (and update when necessary) the best_in_place.js script:
rails g best_in_place:setup
Usage
First of all, you should write your controllers in a RESTful way and make sure they respond properly to a json request. Here you can see an example of how a standard update action should look like in a Rails app:
def update
@user = User.find(params[:id])
respond_to do |format|
if @user.update_attributes(params[:user])
format.html { redirect_to(@user, :notice => 'User was successfully updated.') }
format.json { head :ok }
else
format.html { render :action => "edit" }
format.json { render :json => @user.errors, :status => :unprocessable_entity }
end
end
end
At the same time, if you want to perform server-side validations so that errors are displayed when users introduce invalid data, then the models should look something like this:
class User < ActiveRecord::Base
validates :name,
:length => { :minimum => 2, :maximum => 24, :message => "has invalid length"},
:presence => {:message => "can't be blank"}
validates :last_name,
:length => { :minimum => 2, :maximum => 24, :message => "has invalid length"},
:presence => {:message => "can't be blank"}
validates :address,
:length => { :minimum => 5, :message => "too short length"},
:presence => {:message => "can't be blank"}
validates :email,
:presence => {:message => "can't be blank"},
:format => {:with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i,
:message => "has wrong email format"}
validates :zip, :numericality => true, :length => { :minimum => 5 }
end
Now all defined messages will be shown to the user if he introduces invalid data and the field will remain unchanged. Notice that messages will be displayed in a jquery.purr pop up. Take time to style it the way you prefer.
Options / Parameters
Best in Place will be wrapped into an html object (div/span preferred) with the following attributes:
- class = “best_in_place”
- data-url = Object Path
- data-object = Object name
- data-attribute = Object field
- data-type = “input”, “textarea”, “select”, “checkbox” (defaults to input)
- data-collection = “[[key, 'value'], [key, 'value'],…]” (a structured JSON containing all possible values in the collection in case of the select or a couple of values in case of a checkbox ["falsevalue", "truevalue"].
- data-activator = “#DOM-OBJECT”.Dom object that will convert the text into an input. In case it is not specified it will work by clicking on the text.
Beware: All attributes are assigned underscore strings.
If you are using the Rails 3 Gem, you only need to tag user in-place editable fields like this:
best_in_place object, field, OPTIONS
Params:
- object (Mandatory): The Object parameter represents the object itself you are about to modify
- field (Mandatory): The field (passed as symbol) is the attribute of the Object you are going to display/edit.
Options:
- :type It can be only [:input, :textarea, :select, :checkbox] or if undefined it defaults to :input.
- :collection: In case you are using the :select type then you must specify the collection of values it takes. In case you are using the :checkbox type you can specify the two values it can take, or otherwise they will default to Yes and No.
- :path: URL to which the updating action will be sent. If not defined it defaults to the :object path.
- :nil: The nil param defines the content displayed in case no value is defined for that field. It can be something like “click me to edit”. If not defined it will show “-”.
- :activator: Is the DOM object that can activate the field. If not defined the user will making editable by clicking on it.
- :sanitize: True by default. If set to false it will allow users to introduce html tags to the input/textarea.
Let’s see now how to create user-editable fields by some examples. We’ll use a generic User show action, as I did in the Test Application.
Inputs
In regular HTML:
<span class='best_in_place' id='best_in_place_user_name' data-url='/users/4' data-object='user'
data-attribute='name' data-type='input'>Dominic</span>
Using the Rails 3 gem:
<%= best_in_place @user, :name, :type => :input %>
<%= best_in_place @user, :name, :type => :input, :nil => "Click me to add content!" %>
<%= best_in_place @user, :name, :activator => "#activator" %>
Textarea
In regular HTML:
<span class='best_in_place' id='best_in_place_user_description' data-url='/users/4'
data-object='user'data-attribute='description' data-type='textarea'>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus a lectus et lacus
ultrices auctor. Morbi aliquet convallis tincidunt. Praesent enim libero, iaculis at
commodo nec, fermentum a dolor. Quisque eget eros id felis lacinia faucibus feugiat et ante.
</span>
Using the Rails 3 gem:
<%= best_in_place @user, :description, :type => :textarea %>
Allowing html tags:
<%= best_in_place @user, :description, :type => :textarea, :sanitize => false %>
Select dropdowns
In regular HTML:
<span class='best_in_place' id='best_in_place_user_country' data-url='/users/4'
data-object='user' data-collection='[[1,"Spain"],[2,"Italy"],[3,"Germany"],[4,"France"]]'
data-attribute='country' data-type='select'>Germany</span>
Using the Rails 3 gem:
<%= best_in_place @user, :country, :type => :select, :collection => [[1, "Spain"], [2, "Italy"],
[3, "Germany"], [4, "France"]] %>
Checkbox values
In regular HTML:
<span class='best_in_place' id='best_in_place_user_receive_email' data-url='/users/4'
data-object='user' data-collection='["No thanks","Yes of course"]'
data-attribute='receive_email' data-type='checkbox'
>Yes of course</span>
Using the Rails 3 gem:
<%= best_in_place @user, :receive_emails, :type => :checkbox, :collection => ["No, thanks", "Yes, of course!"] %>
Wrapping up
Best In Place will make easier your application interfaces by letting users modify in-line every content. At the same time, it will control user input and display errors. Let me know if there’s something you would do different or don’t hesitate to give me a pull request if you contribute anyhow.
Tags: in-place-editjavascriptjqueryrails 3UX
28 Comments »






28 Comments on “In place editing with Javascript, jQuery and Rails 3”
Follow comments feed
Hi!
I really like your work here. I was using Jan’s solution but yours is a very great improvement.
A feature I would love to see is adding relations to the :collection. Let’s say a @user can have a language. Then it would be great to just do something like:
:select, :collection => Language.all %>
It could show the name of the language but save the id.
Any thoughts on that?
Nevertheless, a great gem!
Best
Jay
Thanks, but this feature is there already. The only thing is you should pass the collection doing [[id, value], [id, value], [id, value],…]
Have you tried running this on Rails 2? Should I just use REST in Place or do you think it would be easy enough for me to port your library to Rails 2?
Thanks
It should work in Rails 2 as well. The gem is just a helper though, you could create it yourself if you needed. The important thing is the script, and it isn’t framework-specific, all it needs is for you to have RESTful controllers, the way I explain in this tutorial.
Thank you so much for posting this! This is exactly what I was looking for. Nice work.
Buscant informació de Rails em trobo amb aquesta notícia i al cap d’una estona, coi si aquest de la foto de la dreta em sembla que l’he vist a algun lloc… ah vale és de la FIB. Felicitats pel blog, he trobat coses molt útils!
Hi Bernat,
This sounds like an nice implementation of inline editing. However, still being fairly new to rails, I’m getting the following error when I try and run my server after installing your gem. I can uninstall it and everything goes back to working fine. Its essentially a photo gallery with lots of javascript and other packages. I’m running Rails 3.0.5 on Ruby 1.9.2, with jquery 1.5.1. I’ll keep digging to see if I can figure it out, but thought I’d ping you to see if you had any ideas. Thanks for your contribution and efforts on this.
Here’s the error:
/opt/dev/rails/3/pappy/config/initializers/setup_jquery.rb:1:in `’: You have a nil object when you didn’t expect it! (NoMethodError)
My setup_jquery.rb has:
Pappy::Application.config.action_view.javascript_expansions[:defaults] = ['jquery.min', 'rails']
which works fine normally. I suspect there is a require missing somewhere, but I’m not sure where. Any ideas?
Thanks,
Steve
A quick update that I got things working by removing my jquery initializer. I thought I needed it to replace prototype as the default javascript lib. I found and installed the ‘jquery-rails’ gem and that seems to have installed jquery the correct way. Thanks again for creating and sharing your code!
- Steve
Can I have the id value from the “select” to replace a div?
if one wanted to override one of the callbacks, what is the “right” way? Presumably, i should be able to get a handle to the BestInPlaceEditorObject, and set the loadErrorCallback function appropriately, but what scope does that editor object exist?
Man, this was a great tutorial. You rock!
Is there any way to implement this in a “real” checkbox, rather than using “Yes, No” collection?
Being new at this stuff, I apologize, but can you clarify how to pass in relations to the :collection part. For example, I have a table of clients with name as an attribute. How would I pass in the list of client names that have been entered into the system so far? Great gem by the way!
I am trying to add the jquery datepicker to the input field and combine that with your awesome best in place. Any suggestions on how to do this would be appreciated.
thanks man, awesome plugin.
for those who want to use this on some other page like the INDEX page just add
or whatever your model name to the partial that populates it.
hello Bernat,
thanks a lot for posting these kind of tutorials ,using these idea in the project has helped me lot and any newbie can also learn easily from u r tutorials
you rock dude!!!!!!!!!!
This is awesome man. But, is there a way I can define an “oncomplete” callback? I need to do some post processing to the updated html…
I appologize for not keeping the project updated, i’ve been craizingly busy lately. I recommend you to look at the Github README instead of the instructions above, I’ll keep there the latest version of the specifications. And also, I’ll devote some time eventually to look at the pull requests and issues people opened, thanks everyone for posting!
I can get it working by embedding the html. But I cannot get the best_in_place method working. I keep getting a MoMethod error. Any suggestions? Also I want to trigger another javascript event after the value is entered and accepted. Any suggestions.
dude you done a great job. its so simple and having magical effects…
I see the message when there is an error in the data due to validations but no message if the data is saved correctly
I see these codes where I need to add the message any example or can i extend and put something in application.js exampple will be great if possible.
loadSuccessCallback : function(data) {
this.element.html(data[this.objectName]);
// Binding back after being clicked
$(this.activator).bind(‘click’, {editor: this}, this.clickHandler);
},
thank you
-imohan
The edit in place demo doesn’t really work in Chrome for the multiline “User Description” text area. Before the first time you edit-in-place the label moves up and down as you move your mouse over the text, and then, after you leave the field and let it revert to simple text, and you move your mouse over the field, the content itself jumps up and down maddeningly, seeming to squirm away from all attempts to click it, like fish through castaways’ fingers.
To add to that, it is only on one of the examples that this is happening, it is an example with a … tag in it. This creates a break and it is when the mouse moves over this break that the squirming starts.
Amazingly simple and elegant! Thank you for sharing it with the public
Is there a plan to add advanced input types such as tinymce or markdown or textile?
Please, say how to scroll red pen over editable field?
Felicitats pel railcast nano! quin crack estas fet :)
really thanx but look at this ……. irtci.com
Felicidades Bernat. Muy útil. Tienes alguna idea de cómo poder implementar el uso de tabs para moverse entre campos a editar? He tratado usando :html_attrs => {:tabindex => 1} sin éxito. Ojalá puedas comentar algo al respecto.
Gracias.