Back on day one we looked at using the Prototype libraryto take all the hard work out of making a simple Ajax call. While thatwas fun and all, it didn’t go that far towards implementing somethingreally practical. We dipped our toes in, but haven’t learned to swimyet.
So here is swimming lesson number one. Anyone who’s used Flickrto publish their photos will be familiar with the edit-in-place systemused for quickly amending titles and descriptions on photographs.Hovering over an item turns its background yellow to indicate it iseditable. A simple click loads the text into an edit box, right thereon the page.
Prototypeincludes all sorts of useful methods to help reproduce something likethis for our own projects. As well as the simple Ajax GETs we learnedhow to do last time, we can also do POSTs (which we’ll need here) and awhole bunch of manipulations to the user interface – all through simplelibrary calls. Here’s what we’re building, so let’s do it.
Thereare two major components to this process; the user interfacemanipulation and the Ajax call itself. Our set-up is much the same aslast 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:
onMouseOver
onMouseOut
<textarea>
and buttonsThefirst step is to offer feedback to the user that the item is editable.This is done by shading the background colour when the user mousesover. 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.
:hover
isn’t supported on many elements in Internet Explorer for WindowsWith 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. Inthis 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;
}
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);
}
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 controlover 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 Ihave to limit the scope of this article somewhere. It might be a goodidea 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.
Ifthe Ajax call is successful, the server-side script returns the editedcontent, which we then place back inside the object from editComplete
, and tidy up.
Themissing piece of the puzzle is the server-side script for committingthe changes to your database. Obviously, any solution I provide here isnot going to fit your particular application. For the purposes ofgetting 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 beuseful, however, you’ll need to know exactly which record you should beupdating. 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.
Youshould also check the user’s credentials to make sure they havepermission to edit whatever it is they’re editing. Basically the samerules apply as with any script in your application.
Thereare a few bits and bobs that in an ideal world I would tidy up. Thefirst is the error handling, as I’ve already mentioned. The second isthat from an idealistic standpoint, I’d rather not be using innerHTML
.However, the reality is that it’s presently the most efficient way ofmaking large changes to the document. If you’re serving as XML,remember that you’ll need to replace these with proper DOM nodes.
It’salso important to note that it’s quite difficult to make something likethis universally accessible. Whenever you start updating large chunksof a document based on user interaction, a lot of non-traditionaldevices don’t cope well. The benefit of this technique, though, is thatif JavaScript is unavailable none of the functionality gets implementedat 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.
I’ve put together an example pageusing the inert PHP script above. That is to say, your edits aren’tcommitted to a database, so the example is reset when the page isreloaded.
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.