Edit-in-Place with Ajax

Edit-in-Place with Ajax

Back on day one we looked at using the Prototype library
to take all the hard work out of making a simple Ajax call. While that
was fun and all, it didn’t go that far towards implementing
something really practical. We dipped our toes in, but haven’t
learned to swim yet.

So here is swimming lesson number one. Anyone who’s used Flickr
to publish their photos will be familiar with the edit-in-place system
used for quickly amending titles and descriptions on photographs.
Hovering over an item turns its background yellow to indicate it is
editable. A simple click loads the text into an edit box, right there
on the page.

Prototype
includes all sorts of useful methods to help reproduce something like
this for our own projects. As well as the simple Ajax GETs we learned
how to do last time, we can also do POSTs (which we’ll need here)
and a whole bunch of manipulations to the user interface – all
through simple library calls. Here’s what we’re building, so let’s do it.

Getting Started

There
are two major components to this process; the user interface
manipulation and the Ajax call itself. Our set-up is much the same as
last time (you may wish to read the first article if you’ve not already done so). We have a basic HTML page which links in the prototype.js file and our own editinplace.js. Here’s what Santa dropped down my chimney:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>Edit-in-Place with Ajax</title>
<link href="editinplace.css" rel="Stylesheet" type="text/css" />
<script src="prototype.js" type="text/javascript"></script>
<script src="editinplace.js" type="text/javascript"></script>
</head>
<body>
<h1>Edit-in-place</h1>
<p id="desc">Dashing through the snow on a one horse open sleigh.</p>
</body>
</html>

So that’s our page. The editable item is going to be the <p> called desc. The process goes something like this:

  1. Highlight the area onMouseOver
  2. Clear the highlight onMouseOut
  3. If the user clicks, hide the area and replace with a <textarea> and buttons
  4. Remove all of the above if the user cancels the operation
  5. When the Save button is clicked, make an Ajax POST and show that something’s happening
  6. When the Ajax call comes back, update the page with the new content

Events and Highlighting

The
first step is to offer feedback to the user that the item is editable.
This is done by shading the background colour when the user mouses
over. Of course, the CSS :hover pseudo class is a straightforward way to do this, but for three reasons, I’m using JavaScript to switch class names.

  1. :hover isn’t supported on many elements in Internet Explorer for Windows
  2. I want to keep control over when the highlight switches off after an update, regardless of mouse position
  3. If JavaScript isn’t available we don’t want to end up with the CSS suggesting it might be

With this in mind, here’s how editinplace.js starts:

Event.observe(window, 'load', init, false);

function init(){
makeEditable('desc');
}

function makeEditable(id){
Event.observe(id, 'click', function(){edit($(id))}, false);
Event.observe(id, 'mouseover', function(){showAsEditable($(id))}, false);
Event.observe(id, 'mouseout', function(){showAsEditable($(id), true)}, false);
}

function showAsEditable(obj, clear){
if (!clear){
Element.addClassName(obj, 'editable');
}else{
Element.removeClassName(obj, 'editable');
}
}

The first line attaches an onLoad event to the window, so that the function init() gets called once the page has loaded. In turn, init()
sets up all the items on the page that we want to make editable. In
this example I’ve just got one, but you can add as many as you
like.

The function madeEditable() attaches the mouseover, mouseout and click events to the item we’re making editable. All showAsEditable does is add and remove the class name editable from the object. This uses the particularly cunning methods Element.addClassName() and Element.removeClassName() which enable you to cleanly add and remove effects without affecting any styling the object may otherwise have.

Oh, remember to add a rule for .editable to your style sheet:

.editable{
color: #000;
background-color: #ffffd3;
}

The Switch

As you can see above, when the user clicks on an editable item, a call is made to the function edit(). This is where we switch out the static item for a nice editable textarea. Here’s how that function looks.

function edit(obj){
Element.hide(obj);

var textarea ='<div id="' + obj.id + '_editor">
<textarea id="' + obj.id + '_edit" name="' + obj.id + '" rows="4" cols="60">'
+ obj.innerHTML + '</textarea>';

var button = '<input id="' + obj.id + '_save" type="button" value="SAVE" /> OR
<input id="' + obj.id + '_cancel" type="button" value="CANCEL" /></div>';

new Insertion.After(obj, textarea+button);

Event.observe(obj.id+'_save', 'click', function(){saveChanges(obj)}, false);
Event.observe(obj.id+'_cancel', 'click', function(){cleanUp(obj)}, false);

}

The first thing to do is to hide the object. Prototype comes to the rescue with Element.hide() (and of course, Element.show() too). Following that, we build up the textarea and buttons as a string, and then use Insertion.After() to place our new editor underneath the (now hidden) editable object.

The last thing to do before we leave the user to edit is it attach listeners to the Save and Cancel buttons to call either the saveChanges() function, or to cleanUp() after a cancel.

In the event of a cancel, we can clean up behind ourselves like so:

function cleanUp(obj, keepEditable){
Element.remove(obj.id+'_editor');
Element.show(obj);
if (!keepEditable) showAsEditable(obj, true);
}

Saving the Changes

This is where all the Ajax fun occurs. Whilst the previous article introduced Ajax.Updater()
for simple Ajax calls, in this case we need a little bit more control
over what happens once the response is received. For this purpose, Ajax.Request() is perfect. We can use the onSuccess and onFailure parameters to register functions to handle the response.

function saveChanges(obj){
var new_content = escape($F(obj.id+'_edit'));

obj.innerHTML = "Saving...";
cleanUp(obj, true);

var success = function(t){editComplete(t, obj);}
var failure = function(t){editFailed(t, obj);}

var url = 'edit.php';
var pars = 'id=' + obj.id + '&content=' + new_content;
var myAjax = new Ajax.Request(url, {method:'post',
postBody:pars, onSuccess:success, onFailure:failure});
}

function editComplete(t, obj){
obj.innerHTML = t.responseText;
showAsEditable(obj, true);
}

function editFailed(t, obj){
obj.innerHTML = 'Sorry, the update failed.';
cleanUp(obj);
}

As you can see, we first grab in the contents of the textarea into the variable new_content.
We then remove the editor, set the content of the original object to
“Saving…? to show that an update is occurring, and
make the Ajax POST.

If the Ajax fails, editFailed()
sets the contents of the object to “Sorry, the update
failed.? Admittedly, that’s not a very helpful way to
handle the error but I have to limit the scope of this article
somewhere. It might be a good idea to stow away the original contents
of the object (obj.preUpdate = obj.innerHTML) for later retrieval before setting the content to “Saving…?. No one likes a failure – especially a messy one.

If
the Ajax call is successful, the server-side script returns the edited
content, which we then place back inside the object from editComplete, and tidy up.

Meanwhile, back at the server

The
missing piece of the puzzle is the server-side script for committing
the changes to your database. Obviously, any solution I provide here is
not going to fit your particular application. For the purposes of
getting a functional demo going, here’s what I have in PHP.

<?php
$id = $_POST['id'];
$content = $_POST['content'];
echo htmlspecialchars($content);
?>

Not exactly rocket science is it? I’m just catching the content
item from the POST and echoing it back. For your application to be
useful, however, you’ll need to know exactly which record you
should be updating. I’m passing in the ID of my <div>, which is not a fat lot of use. You can modify saveChanges() to post back whatever information your app needs to know in order to process the update.

You
should also check the user’s credentials to make sure they have
permission to edit whatever it is they’re editing. Basically the
same rules apply as with any script in your application.

Limitations

There
are a few bits and bobs that in an ideal world I would tidy up. The
first is the error handling, as I’ve already mentioned. The
second is that from an idealistic standpoint, I’d rather not be
using innerHTML. However, the reality is that it’s
presently the most efficient way of making large changes to the
document. If you’re serving as XML, remember that you’ll
need to replace these with proper DOM nodes.

It’s also
important to note that it’s quite difficult to make something
like this universally accessible. Whenever you start updating large
chunks of a document based on user interaction, a lot of
non-traditional devices don’t cope well. The benefit of this
technique, though, is that if JavaScript is unavailable none of the
functionality gets implemented at all – it fails silently. It is
for this reason that this shouldn’t be used as a complete replacement for a traditional, universally accessible edit form. It’s a great time-saver for those with the ability to use it, but it’s no replacement.

See it in action

I’ve put together an example page
using the inert PHP script above. That is to say, your edits
aren’t committed to a database, so the example is reset when the
page is reloaded.

About the author

Drew McLellan is a web developer, author and no-good swindler from just outside London, England. At the Web Standards Project he works on press, strategy and tools. Drew keeps a personal weblog covering web development issues and themes.

Your comments

  1. CM Harrington:

    Thanks so much for this!

    I needed it for a project, and I was trying to figure out the most efficient way to go about solving the problem.

    Cheers!

    23 December 2005, 01:17

  2. John:

    Incredible example. Thank you so much for this.

    23 December 2005, 01:35

  3. Ballwalkin‘ Mike:

    Great Tutorial. This is JUST what I was looking for in my next project!

    23 December 2005, 05:09

  4. Dustin Diaz:

    Cool stuff Drew. This reminds of PPK’s edit in place idea
    for a CMS…although, he never actually did anything with it
    except save the changes into the DOM memory. This…actually does
    something! Great work man.

    23 December 2005, 06:19

  5. chandru:

    Too good. Very nice article and neatly explained. thats really of u.

    23 December 2005, 06:55

  6. Jonatan Olofsson:

    Very good article, easily explained and a good example

    Thanks!

    23 December 2005, 08:00

  7. archmond:

    wow.. great idea.

    23 December 2005, 08:13

  8. JérÔÔme:

    cÔÔl !

    I think you need to CSS-style the
    TextArea though, so that in place editing isn’t too confusing
    (i.e. clicking a word and then needing to go up or down a line,
    depending on the new layout to change it).

    You are mentionning
    finding a way to Highlight the fact you did change something, but the
    font switch is too much a change, I think.(as seen with FireFox 1.5 at
    least)

    I definitely think we should find an idea to pursue this ‘Impress your friends’ serie beyond the 24th.

    23 December 2005, 09:11

  9. xvrd:

    found some very cool tutorials on this site. thanks for all the work
    you put into this! the question is though… what happens on the
    25th? is all that wonderful content just going to disappear? or are you
    maybe planning on zipping all the tutorials and sample files up into
    one handy package for people to download? enquiering minds want to
    know…

    23 December 2005, 09:43

  10. Vesa Virlander:

    Now this was interesting article. Good work! I’ll have to start playing with it.

    23 December 2005, 10:07

  11. Will Kelly:

    Good tutorial. I recently got inspired by Flickr too, to do a similar thing in a CMS.

    One
    thing that could be done to improve it is to hook it into a form. For
    instance if it was a text input field not a textarea (i.e. for a title)
    pressing return would automatically submit without having to do extra
    coding. It could also be a fall-back if the ajax call failed or
    wasn’t supported in a particular browser.

    23 December 2005, 10:25

  12. trovster:

    Nice little explaination. Prototype is pretty massive and daunting task if you don’t know JavaScript very well.

    When I first looked into copying the Flickr experience I found this edit in place function, but it didn’t have the AJAX element.

    In the end I made it up myself, it works, but probably not as well as this.

    As for XML and innerHTML. FF1.5 now supports it when sending pages as XML. Good or bad, I haven’t yet decided.

    23 December 2005, 12:04

  13. Tim:

    Thx Drew – this is probably the best christmas gift for all of us🙂

    Merry Christmas

    23 December 2005, 13:34

  14. marco:

    ooohh this is sweeet! thank you!

    ..and yes, Merry Christmas!

    23 December 2005, 15:44

  15. Blake Scarbrough:

    Very slick. One issue I ran into was when I placed and named html
    entity like & or …, it didn’t save it as I typed it
    in. Is there a way to escape the ampersand?

    23 December 2005, 16:26

  16. db:

    pretty cool! i was wondering, say i wanted to run the cleanup
    function just by clicking outside of the textarea, how would i go about
    that. i tried attaching to the body tag, but i couldn’t get it to
    work… thx!

    23 December 2005, 17:39

  17. Drew McLellan:

    db: You may wish to rethink your goal. What would happen if a user
    clicked outside of the edit box part way through making an edit? (e.g.
    they may be switching between windows, copying and pasting from
    elsewhere).

    23 December 2005, 18:19

  18. db:

    that’s true, but i wouldn’t use it for large amounts of
    text. more like titles or tags, as in flickr or del.icio.us… but
    that’s a good point.

    23 December 2005, 18:55

  19. Chuck Reynolds:

    I’d like to see “nl2br? integrated and html as well.

    You know what would really be cool, integrate TinyMCE or FCKeditor into the edit screen – that would rock.

    Good job though – nice exp.

    23 December 2005, 19:12

  20. Drew McLellan:

    xvrd: I have no plans to remove any content
    published here this month. All the URLs can be considered permanent.
    (If I do reshuffle for whatever reason, I’ll make sure the links
    keep working).

    23 December 2005, 21:44

  21. dave:

    You can also use the Ajax.InPlaceEditor that is from http://script.aculo.us that simplifies some of this…

    23 December 2005, 22:26

  22. Pete Freitag:

    Once again thanks for the great article!

    24 December 2005, 02:03

  23. Joe Winterhalter:

    Thanks for the great tut, cant seem to get the function to saving the text at all, where does the php go?

    24 December 2005, 12:42

  24. eli:

    is there anyone with a working version of this that has their source
    code that i could some how download? i have done this tutorial about 3
    times now and NOTHING on it is working. i’m an experienced
    mysql/php web designer but new to this whole ajax thing and just trying
    out some demos here and there. i got the other ajax demo to work just
    fine, but all this one does is display a title and a paragraph and
    nothing is editable or changes colors on mouse over or anything. any
    help? thanks.

    25 December 2005, 02:52

  25. Ben:

    Interesting tutorial – I’ll have to give it a try.

    26 December 2005, 04:12

Impress us

(Not shown)

Textile Help

Schreibe einen Kommentar

Bitte logge dich mit einer dieser Methoden ein, um deinen Kommentar zu veröffentlichen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s