Hostingheaderbarlogoj
Join InMotion Hosting for $3.49/mo & get a year on Tuts+ FREE (worth $180). Start today.
Advertisement

Create an In-Place Editing System

by

Making users click through multiple pages just to edit a field is so 1999. In this tutorial, you'll learn how to create an in-place editing system as found on popular sites, such as Flickr.


A word from the Author

With all the buzz around Web 2.0, ease of use is now much more important than ever. Being able to edit some content without having to go to another page is something a lot of users really crave. A lot of big names are already using this pattern to great effect. If you've used Flickr, you've probably seen this in action.

I believe a demo is worth a thousand words. Hit the demo and try it out yourselves.

Today, we are going to look at how to implement this with, you guessed it right, our favorite JavaScript library, jQuery. Interested? Let's get started right away!

Design Goals

Before we start looking at how to implement the functionality, here are a few thoughts about the goals and the resulting decisions.

  • We need to let the user edit the content without leaving the page. This is a given.
  • This should either function as a whole or fail as a whole. When JS is disabled, we don't want to run into weird quirks.
  • The user should know the content is editable. A subtle blue background change should draw the user's attention to this.
  • When dealing with how to trigger the edit there are a few options. We can either let the user edit on a normal click or double click. I've chosen double click since random double clicks occur at a smaller rate than random clicks. Switching it is just a matter of changing the parameter in the bind event.
  • A way for the user to save or discard the edits.
  • Save or edit events can be triggered by 2 ways. Keyboard events or mouse events. I chose mouse events since keyboard events lack specificity.
  • With respect to mouse events, you could use either traditional buttons or usual links. I chose links for no particular reason.
  • The user should be able to resume editing even if he clicks outside of the input box or leaves the page and comes back.
  • Additionally, the user should be able to edit as many fields as possible simultaneously.

Now that we've mapped out our needs we can now move on to how we are going to do this.

Plan of Action

We'll now need to map out what needs to be done in a specific order.

Step 1: We'll need to add a class of editable to each elements which need this functionality.

Step 2: We'll next need to add hovers to each editable item to draw attention to the fact that that item's content is editable. We'll add and remove the hovers using JavaScript instead of CSS. This is mainly done for devices or browsers with JavaScript disabled. We don't want to send them wrong visual cues.

Step 3: When an editable item is double clicked, we need to swap out the contents and replace it with a text box with the old text in it.

Step 4a: When the user wants to save the edits, copy the input's value to the parent element and remove the input box.

Step 4b: Or when the user wants to discard the changes, replace the old content and remove the input box.

These are the basic steps in creating this functionality. Of course there are few other small things but I'll explain them as we go along.

Core Markup

The HTML markup of the demo page looks like so.

<!DOCTYPE html>
<html lang="en-GB">
<head>
<title>In-place editing system - by Siddharth for NetTuts</title>
<link type="text/css" href="css/style.css" rel="stylesheet" />
<script type="text/javascript" src="js/jquery.js"></script>
<script type="text/javascript" src="js/mojo.js"></script>
</head>
<body>

<div id="container">

<h1>In-place editing</h1>
<div>by Siddharth for the lovely folks at Net Tuts</div>
<p>Elements with a class of <em>editable</em> are, well, editable. In case you haven't noticed, all 
elements containing the <em>editable</em> class get a blue background on hover to indicate this capability. </p> 

<p>Double click to edit the contents. Use the dynamically created links to save or discard the changes. 
You can open up as many fields to edit as you want without any hiccups.</p>

<div class="block">
<h2>I </h2>
<ul>
<li class="editable">am Siddharth</li>
<li class="editable">love working with the web</li>
<li class="editable">am a freelancer</li>
<li class="editable">write for Net Tuts</li>
<li class="editable">can be found at <a href="http://www.ssiddharth.com">www.ssiddharth.com</a></li>
<li class="editable">will never let you down or give you up :)</li>
</ul>
</div>

<div class="block">
<h2>Things to do this week</h2>
<ul>
<li class="editable">Get design approval from Deacon</li>
<li class="editable">Send an invoice to Albert </li>
<li class="editable">Start work on Dwight's project</li>
<li class="editable">Talk with Sarah about new ideas</li>
<li class="editable">Check Seth's site for rendering bugs</li>
<li class="editable">Meet with Clintson to discuss project</li>
</ul>
</div>

</div>
</body>
</html>

As you see, disregarding the boiler plate, we have two unordered lists. Each li element has a class of editable to denote that its content can be edited.

We've also included the jQuery library and our own script file.

CSS Styling

body{
	font-family: "Lucida Grande", "Verdana", sans-serif;
	font-size: 12px;
}

a{
	color: #000;
}

a:hover{
	text-decoration: none;
}

p{
	margin: 30px 0 10px 0;
}

h1{
	font-size: 30px;
	padding: 0;
	margin: 0;
}

h2{
	font-size: 20px;
}

#container{
	width: 820px;
	margin-left: auto;
	margin-right: auto;
	padding: 50px 0 0 0;

}

.editHover{
	background-color: #E8F3FF;
}

.editBox{
	width: 326px;
	min-height: 20px;
	padding: 10px 15px;
	background-color: #fff;
	border: 2px solid #E8F3FF;
}

ul{
	list-style: none;
}

li{
	width: 330px;
	min-height: 20px;
	padding: 10px 15px;
	margin: 5px;
}

li.noPad{
	padding: 0;
	width: 360px;
}

form{
	width: 100%;
}

.btnSave, .btnCancel{
	padding: 6px 30px 6px 75px;
}

.block{
	float: left;
	margin: 20px 0;
}

Nothing special here. Just a bunch of code for layout and styling purposes.

Take special note of the editHover and noPad classes. We'll be using them in a bit.

JavaScript Implementation

Now that we have a solid framework and some basic styling in place, we can start coding up the required functionality. Do note that we make extensive use of jQuery. Specifically we'll need at least version 1.3 or higher. Anything less and it won't work.

Adding Hovers

Hovers

As noted earlier, we'll need to add a subtle blue background to editable objects to signify they are editable. We've already created the editHover class to take care of this.

$(".editable").hover(
		function()
		{
			$(this).addClass("editHover");
		}, 
		function()
		{
			$(this).removeClass("editHover");
		}
	);

This tiny snippet takes care of that for us. We use jQuery's hover method to add the editHover class when the element is hovered upon and remove it when it is not. We use this to refer to the specific element that is hovered over. If we had used .editable as the selector instead each and every element will get the class added to it. So we use this to target only the element we need.

Switching out the Elements

First up, we need to make sure our code is executed when the target element is double clicked. So we'll first hook up the handler for this event first.

$(".editable").bind("dblclick", replaceHTML);

We attach the replaceHTML function to the double click event relative to the editable element with that one liner. Now we can move on the switching out the elements.

function replaceHTML()
	{
		oldText = $(this).html()
						 .replace(/"/g, """);
		$(this).html("")
			   .html("<form><input type=\"text\" class=\"editBox\" 
			    value=\"" + oldText + "\" /> </form><a href=\"#\" class=\"btnSave\">Save changes</a> 
		        <a href=\"#\" class=\"btnDiscard\">Discard changes</a>");
	}

Let's go over our code bit by tiny bit.

I define the functionality inside a separate named function instead of an anonymous function for a specific reason: I'll be using this function more than once. Next, we save the content of the element for future use using jQuery's html method and replacing all quotes since it messes up our output down the line.

Now that our content is safely stored for later use, we can switch out the elements. First we empty out the li element by sending in an empty string to the html method. Next, we insert some standard HTML for an input box. We add some classes to it for styling purposes. More importantly, we set its value attribute to the original text contained by the element stored in oldText. We also add a couple of links to take care of saving and discarding the edits. We've also added classes to them so they can be targeted easily and for styling.

As always, we use this to target the element which triggered the event.

Keeping the Edits

Edited Text
$(".btnSave").live("click", 
	function()
	{
		newText = $(this).siblings("form")
						 .children(".editBox")
						 .val().replace(/"/g, """);
						 
		$(this).parent()
			   .html(newText);
	}
);

First up, let me introduce jQuery's live method. You probably haven't seen this much before so I'll give a quick introduction.

You can't hook up handlers to events triggered by elements which are not even present in the DOM when the page and the JavaScript was loaded. If you use normal event binding functions, it'll fail due to the above mentioned reason. The live method takes care of that.

It binds handlers to events irrespective of when the element was created. For more about this, you can go through the official docs.

Lets look into our code now. We first bind the code contained within our anonymous function to the click event. Inside the function we first save the text contained in the input box. This can be a little tricky since the input box doesn't have an ID. So we first look for the form element which happens to be its sibling and then traverse through to find the input element. We then copy its value after replacing all the quotes it may contain.

Next, we obtain the links parent element, the li element and replace its HTML content with the text we copied in the previous step.

This block could have easily been created as a one liner but I chose to split it to 2 lines in the interest of readability.

Discarding the Edits

$(".btnDiscard").live("click", 
	function()
	{
		$(this).parent()
			   .html(oldText);
	}
);

This is just as simple as it looks. Since the user doesn't want to keep any of the edits. We just replace the HTML content of parent element with the original text we had copied earlier to the oldText variable.

With this the core of our work is done. We just need to do a couple of edits to make sure things don't break when the user does unexpected things.

Binding and Unbinding

If you've tested out our code at this point you'll probably end up with this functionality breaking bug: When a user double clicks in the resulting input box it is now filled with the HTML content of the editing system. Try it yourself. With each double click, the value of the input box reflects by adding another bunch of text to it. This issue will probably be a lot worse if you've chosen click as the trigger event.

To rectify this, we need to unbind the event handler for that specific element alone and rebind them as soon as the user clicks either save or discard. Let's implement that now.

Our previous blocks of code now need to be edited out to so:

function replaceHTML()
	{
		//Code
		$(this).html("")
		// Earlier form insertion code
                .unbind('dblclick', replaceHTML);
	}

We unhook the handler for the element which triggered the event. The rest of the elements with the editable class still have their handlers intact and will respond to events.

$(".btnSave").live("click", 
	function()
	{
		// Earlier code
						 
		$(this).parent()
			   .html(newText)
                           .bind("dblclick", replaceHTML);         
	}
);
$(".btnDiscard").live("click", 
	function()
	{
		$(this).parent()
			   .html(oldText)
                           .bind("dblclick", replaceHTML);  
	}
);

Next we attach those handlers back in spite of whether the user chooses to edit them or not. If we don't re-attach these, the fields can be edited only once. The second time they are double clicked, the handlers are no longer attached to the events. We rectify this by hooking the handlers back to the events.

A Few Tweaks

This last bit of code is purely to spruce up the appearance of our effect. If you've noticed, the li has a bit of padding in place to make the text within look better. But when the text is stripped out and replaced by a text box we result looks ugly and breaks the effect. We want the text box to take up exactly the same space the original text took. With this in mind, we add a noPad class to the element when it has been double clicked and removed again when the user saves or discards the edit.

function replaceHTML()
	{
		//Code
		$(this).addClass("noPad")
                    		.html("")
		// Earlier code
	}

We unhook the handler for the element which triggered the event. The rest of the elements with the editable class still have their handlers intact and will respond to events.

$(".btnSave").live("click", 
	function()
	{
		// Earlier code
						 
		$(this).parent()
			   .removeClass("noPad")   
                // Earlier code    
	}
);
$(".btnDiscard").live("click", 
	function()
	{
		$(this).parent()
			   .removeClass("noPad")
                           // Earlier code
	}
);

The Complete Code

Here is how the complete code looks like:

$(document).ready(function() 
{
	var oldText, newText;

  	$(".editable").hover(
					function()
					{
						$(this).addClass("editHover");
					}, 
					function()
					{
						$(this).removeClass("editHover");
					}
					);
  
  	$(".editable").bind("dblclick", replaceHTML);
	 
	 
	$(".btnSave").live("click", 
					function()
					{
						newText = $(this).siblings("form")
										 .children(".editBox")
										 .val().replace(/"/g, """);
										 
						$(this).parent()
							   .html(newText)
							   .removeClass("noPad")
							   .bind("dblclick", replaceHTML);
					}
					); 
	
	$(".btnDiscard").live("click", 
					function()
					{
						$(this).parent()
							   .html(oldText)
							   .removeClass("noPad")
							   .bind("dblclick", replaceHTML);
					}
					); 
	
	function replaceHTML()
					{
						oldText = $(this).html()
										 .replace(/"/g, """);

						$(this).addClass("noPad")
							   .html("")
							   .html("<form><input type=\"text\" class=\"editBox\" 
							    value=\"" + oldText + "\" /> </form><a href=\"#\" class=\"btnSave\">Save changes</a> 
							   .unbind('dblclick', replaceHTML);
			
					}
}
);

Not bad. Fifty odd lines to add some spiffy new functionality.

Taking it One Step Further: The Backend

In the interest of not making it too long, I've stuck to creating the client side functionality alone. If you want to implement this functionality within your own projects, its implicitly assumed that you'd need a back-end system in place to save these changes and more importantly, you'd need an AJAX request for making this call asynchronously.

Adding this functionality should be a cinch but do make a note of this. The code above was created just to illustrate this pattern and not for production use. So I've abstained from adding additional IDs attributes to elements and name attributes to text boxes. In your production code, do add all of them so the text box's name attribute can be set meaningfully and in such a way the back end can recognize which piece of data needs to be updated.

To add an AJAX request, our save handler would have to be updated to so:

$(".btnSave").live("click", 
	function()
	{
		newText = $(this).siblings("form")
			 .children(".editBox")
			 .val().replace(/"/g, """);
                                  
                 $.ajax({
			type: "POST",
	 	url: "handler.php",
			data: newText,
			success: function(msg){
			 // Some code here to reflect a successful edit;
			}
			});
						 
		$(this).parent()
			   .html(newText)
			   .removeClass("noPad")
			   .bind("dblclick", replaceHTML);
	}
);

Remember, for the back-end to make any sense of what you are sending to it, you need some additional data along with the updated text so the app knows which data to edit. You could easily send in more than one piece of data to the script if you need to.

Conclusion

And there you have it; how to add a user friendly functionality to your projects. Hopefully you've found this tutorial interesting and this has been useful to you. Feel free to reuse this code elsewhere in your projects and chime in here if you are running into difficulties.

Questions? Nice things to say? Criticisms? Hit the comments section and leave me a comment. Happy coding!


Advertisement