A friend recently asked me to review his edit-in-place code which turned out to be a modification of the one found at http://docs.jquery.com/Tutorials:Edit_in_Place_with_Ajax. Reading the tutorial on that page I asked myself how I would do this differently? Defining a global setClickable() function and then calling $('#editInPlace").click() is totally uncool, essentially limiting yourself to one edit-in-place area per page.
Since the concept of edit-in-place is so simple and the implementation should be likewise I want to try and tackle this as a tutorial by going through concepts and teaching to build from scratch.
The concept
There's a piece of text on a page, e.g. a heading or paragraph, looking plain and un-interesting. When you hover over it however a visual highlight, usually pale yellow, indicates that it's something special - an editable region. You then click on the text and it magically transforms into an editable box with save and cancel buttons tagged at the end. On clicking either save or cancel the editable box transforms back into its original form with text updated if saved.
The build
What we want to do is put these concepts into code whilst building something that's re-usable. A plugin will do the trick! Let's call our plugin inlineEdit.
We are only 3 steps away from achieving our goal! Follow along...
Step 1: Getting the basics right
The basic functionality consists of a static text element (span, div etc...) that can transforms into an input element on click. And on hover the text element will highlight to indicate special interactions.
Here's our first pass: Basic Interaction
Code explanation: Our plugin accepts a CSS class name defined in a stylesheet which is used to toggle the text element's classname - a class defining a background-color typically #ffC is sufficient for this. On clicking we replace the entire contents of the original text element with an input element and pass the original content into the input as its value. For now changes are saved when the input loses focus.
Step 2: Save, Cancel & Callbacks
As you may notice above we saved on blur and there is no way of canceling changes. Let's improve the interaction by adding a save button. This is important as you'd want to do something with the changes or allow a user to manually cancel. Every application does something different at the saving stage so we'll simply define a save callback and let the implementer decide what to do on save, be it Ajax post or what not. The save action is also cancelable by returning false inside the callback.
Here is our second pass: Save, cancel and callbacks
Step 3: Finishing Off
Our plugin is nearly there. Just a few nice finishing touches remaining. We want to give the user ability to change the button label and pass an initial value. Also to make the plugin more re-usable we want to move the core plugin code out to a separate file and reference it instead. To set the stage for further improvements in the future we also split the code into two distinct clauses.
Final result: Live Demo and source code is here
Usage
Now to use this brand spanking new plugin we simply apply it to any DOM element like so:
HTML
<span class="editable">Hello World!</span>
JavaScript - Basic
$(function() {
$('.editable').inlineEdit();
});
JavaScript - Customised button label with save callback
$(function() {
$('.editable').inlineEdit({
buttonText: 'Add',
save: function(e, data) {
return confirm('Change name to '+ data.value +'?');
}
});
});
Feedback welcome - if you happen to improve upon this code base please share-a-like!
Enjoy!
Update 26-Aug-09: Added placeholder for cases when text is empty; changed label option to buttonText to minimise confusion.
Update 18-Oct-09 Fixed bug in setting initial value via script.
Update 17-Jan-10 Moved code to http://github.com/caphun/jquery.inlineedit. I will continue to improve this plugin from there. Feel free to fork it!


Hi,
This is a great plugin, but only seems to work with mouse interaction (unless I'm missing something!).
When the enter key is pressed while the input box is enabled, the whole form is submitted; if you tab to focus the "save" button, the edits are discarded because the input field lost focus.
Any ideas for making this keyboard-friendly?
Thanks,
Tim
A little bug in plugin was found.
… self.html('<input type="text" value="'+ self.value() +'…
this code doesn't works if self.value() contains " (double quotation mark).
Can be fixed using attr() method for setting input value:
self.find('input').attr('value', self.value())
@tim: you're completely correct. I'll look at making this keyboard-friendly!
@igor: thanks for the bug report!
Hi Caphun,
First of all, great script. I had a biggie question though. I'm trying to figure out how to save for a table within mySQL database. I noticed you have the php version setup for a session and I guess I'm pretty novice when it comes to combining javascript and php together. Could you please help me out??
I have like 5 different fields I want to change within my "USERS" table.
1. Firstname
2. Lastname
3. Phone
4. Email
5. Website
Regards,
-Shaun
Hi,
Great script – helps a lot. I also needed to have key capture – Enter to save and Esc to quit. I added the following in the mutate function
.find( self.options.control )
.keyup( function( event ) {
if (event.keyCode == '13') {
self.save( self.element, event );
self.change( self.element, event );
} else if (event.keyCode == '27') {
self.change( self.element, event );
}
})
.end()
Hope it helps.
Oh and is there any reason why placeholders would not be set on load?
@mathew: thanks for giving back! I added your change to the codebase, see:
http://bit.ly/9yGqVV
Hi, first great script. I like it and have started to build an grid around it. But i have ran into issues especially IE8.
Even the basic example at the top of the page does not work in IE8.
Mozilla Firefox works nicely. In IE8 using basic example if you click on "Joe Blogg" and start to type before "Joe" works good but if you start to type after "Joe Blogg" it does nothing then when u click to lose focus the text box stays there and cannot be changed. Please tell me there is a fix for this b/c i really like the script.
Thanks in advance
I figured out the issue with IE8. When the field is changed to a textbox the self.bind('click') event still gets called. I wrote a mod that will actually determine if the control contains text or a textbox before continuing on with the rest of the code in the click event. Hopes this helps someone else! thanks for the script
First – love the simplicity of this.
One issue I'm having though in IE7:
When you clicked an editable element in IE7 the cursor goes to the beginning of the text. If I then click the mouse at the end of the text the textbox seems to lose focus so that when I click delete it actually acts as the browsers 'Back' button rather than delete a character. This actually happens if you click anywhere outside of the text box.
I can never seem to get focus back to the text box unless I highlight the text or click save/cancel and start again.
Has anyone else experienced this in IE7 with jQuery 1.4.2?
Hi Caphun,
Thanks for publishing this script. I was looking for a minimal inplace editor, and this one seems pretty powerful. A bit too powerful for my limited javascript skills as luck would have it. I noticed that the only thing being sent to the save() function in data is the value of the input. While, that's great in a very simple case, it's not so helpful for using ajax as your save() function. What makes sense to me in this case, is setting the original id attribute to some property. That way, I can send that to my server-side script and actually know what I will be changing. I have tried getting the id in the init method and sending that from the save method. I always end up with a blank. Could you, perhaps, post a snippet here how to extend your plugin with the original element's id attribute value? Much appreciated.
Here is what I've tried:
// plugin defaults
defaults: {
hover: 'ui-state-hover',
value: '',
control_id: '',
save: '',
buttons: 'save cancel',
placeholder: 'Click to edit',
control: 'input'
},
control_id: function( newValue ) {
if ( arguments.length ) {
this.element.data( 'control_id' + namespace, newValue );
}
return this.element.data( 'control_id' + namespace );
},
save: function( elem, event ) {
var hash = {
value: this.element.find( this.options.control ).val(),
id: this.element.attr('id')
};
if ( ( $.isFunction( this.options.save ) &amp;&amp; this.options.save.call( this, event, hash ) ) !== false || !this.options.save ) {
this.value( hash.value );
this.control_id( hash.id );
}
},
@bretticus: Done some minor refactoring of the code and changed the save callback's <code>this</code> object to refer to the original DOM element. So now you should be able to get the id within the callback function using <code>this.id</code>, example: http://bit.ly/bKjZ4o
All – I'll get round to testing IE soon. I promise!
As promised, here's a commit with a load of IE fixes: http://bit.ly/dz6a6l
Thanks for a great plugin!
Is it possible to use it to edit a row in a table as in multiple elements at once with one save for the whole row?
Thanks
Great plugin!!
i was wondering is it possible to have a "double click " event call the inline editor instead of a single click?
Thanks
This is cool. Does the extensions example with the drop list actually work? I am getting odd behavior:
1. the list won't stay dropped down on selection
2. the value is not sent through on save (maybe because one can't click anything in the drop down?)
Hi,
cool plugin. As Gray asked – how could it be (is there a way) initialized by double click instead of the single click?
thanks,
Jee
@nathan: the plugin doesn't handle this situation as it only acts on a single field.
@gray & @jee: what's the rationale behind double rather than single click?
@jay: the extensions example should work but maybe I've not tested it exhaustively enough. Could you send me a testcase?
Hi Caphun,
My rationale for the double click was that i have another single click event being used to multi-select some other elements within my page.
I did find a way to get it to work. I noticed that you was binding the event with a single click so i changed it to a double click
see jquery bind() http://api.jquery.com/bind
$('.dblclick').inlineEdit();
and added the class name of .dblclick to all the elements I'm calling the function on.
(within jquery.inlineedit.js)
on line 28 i changed:
.live( ['click','mouseenter','mouseleave'].join(namespace+' '), function( event ) {
to
.live( ['dblclick'].join(namespace+' '), function( event ) {
(did not need mouse functions)
on line 36 i changed:
case 'click':
to
case 'dblclick':
and it works nicely.
The only issue i now have is if i click away from the element once the inlineedit has been called, the element freezes and i can not get the focus back on the element. So i have to refresh the page, I'm still working on that issue but will post my results once i find the problem.
found it,(you will have to forgive me I'm still learning jQuery)
in line 83 i changed
cancelOnBlur: false
to
cancelOnBlur: true
@gray: I think a better approach to this on the plugin perspective would be to add a mutateEvent option which defaults to "click".
Good for beginners. I think jquery in its very nature is very easy to understand…
Am using this, but I need one extra thing, I also need to have the old value of the field in some variable, I tried adding putting in code for getting the old value of the text field at the start of mutate but it didn't worked. Am new to jquery, a little help would be appreciated
Pingback: RealTime - Questions: "Help me please about javascript odd or even code please!!!!please!!!!?"
small update – added
if (self.value() == this.options.placeholder)
self.value('');
to the mutate: function so that upon first entering edit mode the edit box is blanked out if no new text was entered.
Also modified the save: function to return more date (event, newValue, oldValue, element)
this.options.save.call(this.element[0], event, hash, this.value(), elem ))
Awesome little plugin, thanks.
Thanks for writing this.
Pingback: Buzz Lightyear is here » Blog Archive » links for 2011-03-04
Bug with Firefox 3.6.15 : jquery…edit.js (ligne 30)
I haven't test on the other browser
this.each(function () {$.inlineEdit.getInstance(this, options).initValue();}).live is not a function
[Stopper sur une erreur] .live( ['click', 'mouseenter',…n(namespace+' '), function( event ) {
Thanks for sharing.
What if I want to revert back to the original value from an async ajax call?
Example:
$('.edit').inlineEdit({
buttons: 'Ok',
cancelOnBlur: true,
save: function(event, data) {
$.post("save.php", {id : $(this).closest('tr').attr('id'),
value : data.value, colonne : $(this).index()},
function(msg){
if(msg !=='OK'){
alert("Error");
–> revert back to initial value.
}
});
}
});
@JFR: return false to revert on the save function.
@jack: thanks reporting that bug with the placedholder value. Fixed in github. btw, you can also get those values without modifying the save method like so:
$('.editable').inlineEdit({
save: function(event, data) {
var widget = $( this ).data('widget.inlineedit');
// original element, old value, new value
console.log( widget.element, widget.value(), data.value );
}
});
Great script. Incredibly easy to use.
I'm noticing what might be a bug with saving via Ajax. When you press the Enter key to save it sends the Ajax request twice. I'm using essentially the same script as your repository demo (https://github.com/caphun/jquery.inlineedit/blob/master/demos/save.html)
@matthias: thanks for bug testing. I just tried this myself but couldn't reproduce the problem. could you upload a test? The demo does an Ajax post on page load to get the latest value then another on save.
@caphun It was only happening when I had the save button and hit enter – like enter was triggering the save button, but also called the keyup event in the code (without preventing default action).
I've since removed the save button and everything works fine. I'll try extracting the inline edit specific code from my source and demo it by itself to see if it still does it.
@caphun, can I do this with inlineEdit:
- instead of placeholder text, have a textbox input (as usual, click to enter text)
- instead of save button, have "enter" to save text
- instead of cancel, have onblur to cancel changes
Can I also pass additional variables other that the one inlineEdit text to be saved together with it?
Thanks caphun!
1) the inlineEdit always starts off as in-page text and mutates into a textbox onclick. To start off as a textbox you'd need to somehow trigger the mutate function onload. You could add a custom event to the live() call say 'init' and then trigger it like so: $('.editable').inlineEdit().trigger('init');
2) Yes, use the buttons option with the following value: '<button class="save">enter</button>'
3) Yes, set cancelOnBlur to true
hi,
it works great! how can i edit a multi-line field?
thanks
jl
@jean-louis: see the textarea example
Is this plugin compatible with Jquery 1.6.1?
@andrew: should be.
Thanks to the author for this slick and easy inline edit script. I'm using it in textarea mode and am running into one trouble. If a user enters line breaks into the textarea, after saving it shows up as an HTML break tag with the brackets escaped. Not the expected result. How might this be fixed.
Is there a way to easily manipulate the style of the textarea or the input, the same way that I can modify css for the buttons?
I thought about adding a class attribute to the input control which I could then manipulate with jquery .css(), but I don't want to mess with the .js file…
Pingback: Inline text change not updating on ajax request (but changes on DB) - Programmers Goodies
Any examples using the plugin with classic asp?
Hi caphun! Very cool plugin, and as a newbie to jQuery, I found it very clean and simple to use
On question:
Is it possible to trigger the function from another node than the one with class="editable". I would like to use a trigger e.g. <a class="triggerEditable">
Something like this:
<span class="editable">The Text</span>
<a class="triggerEditable">Click to edit "The Text"
Anyone?
Nice post. Very very important tutorial.
Thanks for sharing. Mark