Advertisement
JavaScript & AJAX

Create an In-Place Editing System: One Step Further

by

A few months ago, you learned how to create an in-place editing system. Today, we'll take things a step further as we create a simple backend, which will allow our website to remember the changes that we've made.

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.

Today, we are going to improve on the earlier version: weeding out some bugs, adding some features, and, more importantly, saving all the data to an actual database for retention. Interested? Let's get started right away!

Prepping the Database

First up, we need a database to pull in the information from and then, when required, update the data it holds. For the sake of this exercise, let us setup a table with some random data.

I already had a database named inplace with a table called data on my development server. For our use we'll add another table.

Tutorial Image

I typically prefer using phpMyAdmin for running my SQL queries. Click on the SQL tab and paste in the following query:

CREATE TABLE IF NOT EXISTS `inplace` (  
  `field` varchar(120) NOT NULL,  
  `value` text NOT NULL,  
   PRIMARY KEY (`field`)  
 ) ENGINE=MyISAM;  
   
   INSERT INTO `inplace` (`field`, `value`) VALUES  
   ('name', 'am Siddharth'),  
   ('passion', 'love working with the web'),
   ('profession', 'am a freelancer'),
   ('work', 'write for Net Tuts'),
   ('url', 'can be found at www.ssiddharth.com'),
   ('punch', 'will never let you down or give you up :)'),
   ('design', 'Get design approval from Yusuf'),  
   ('invoice', 'Send an invoice to Drew'),
   ('research', 'Start research on Pallav\'s project'),
   ('discuss', 'Speak with Harnish about new ideas'),
   ('debug', 'Check Aditya\'s site for rendering bugs'),
   ('meet', 'Meet with Clintson to discuss new project');
Tutorial Image

If everything worked out as it should you should get the following screen:

Tutorial Image

A closer look at the table:

Tutorial Image

Since I explicitly wanted to keep the simplicity of the demo and just add the back end people requested, I am keeping the table structure very simple. Feel free to modify and extend it in your projects.

Now that the sample table has been created and pre populated with some test data, we can move on to the actual back end.

Setting up a Database Config File

Since we'll be accessing the database often either to read data or to update the data it contains, it's prudent to create a config file which holds the relevant data. Create a file called db.php and paste the following in it.

<?php 
DEFINE ('DB_USER', 'sid');  
DEFINE ('DB_PASSWORD', 'somerandompassword');  
DEFINE ('DB_HOST', 'localhost');  
DEFINE ('DB_NAME', inplace); 

$connection = mysql_connect(DB_HOST, DB_USER, DB_PASSWORD) or 
die('Connection to the specified database couldn\'t be established');  
mysql_select_db(DB_NAME)  or 
die ('Specified database couldn\'t be selected');   
?>

Nothing special here. We define all the relevant details, connect to the host using the given username/password combination and then select the relevant database for manipulation down the road.

The Editor

Tutorial Image

The editor takes care of reading from the database and outputting the data in a specific format so it is easy for us to send relevant details back to the server informing which record to update. We'll talk about it more in a second.

The code doesn't change significantly from the static HTML only code from the earlier version. We do, however, need to make the data dynamic. So in the original HTML code, this:

<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>
<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>

is replaced by:

<?php  
$query = "SELECT * FROM inplace LIMIT 0, 6";    
$result = mysql_query($query) or die ('Query couldn\'t be executed');  
while ($row = mysql_fetch_assoc($result)) {
echo '<li class="editable" id="'.$row['field'].'">'.$row['value'].'</li>'; 
}
?>
<?php  
$query = "SELECT * FROM inplace LIMIT 6, 6";    
$result = mysql_query($query) or die ('Query couldn\'t be executed');  
while ($row = mysql_fetch_assoc($result)) {
echo '<li class="editable" id="'.$row['field'].'">'.$row['value'].'</li>'; 
}
?>

Since the table is small, we'll just select everything from the table but ask it to return only the first 6 elements. Next, I just iterate through and print out the li elements. Take special note of the fact that each li elements gets its id attribute set to the name of field it obtains its value from. This will be used later in the data sent back to the server to denote which record needs to be updated.

I am aware exposing the name of the field like this may pose a security threat but on a properly secured environment, I don't think this will instigate any troubles. Else you could just use aliases here and do a reverse lookup on the server side. Let your creative juices flow there. For a very straight forward demo, it seemed rather overkill.

Also, don't forget to include the db.php file we created earlier to the editor. This line will take care of that.

<?php require("db.php"); ?>

After making the edits, remember to save the file with a .php extension.

The Handler

The handler is where the page posts the details to. This takes care of checking whether data was actually sent to the page, and if so, sanitizes the sent data and then updates the relevant values.

Create a file named handler.php and paste in the following:

<?php
require("db.php");

if (isset($_POST['field']) && isset($_POST['value'])) {
	$value = mysql_real_escape_string($_POST['value']);
	$field = mysql_real_escape_string($_POST['field']);
	
	$query = "UPDATE inplace SET value ='$value' WHERE field='$field'";   
	$result = mysql_query($query) or die ('Query couldn\'t be executed');
	if ($result) {echo 1;}
} 
?>

A pretty straightforward affair. Let me explain each step in detail.

Since we'll need to manipulate the database, we first include the db.php file we created earlier.

Next, we check whether both our required variables, field- value which tells us which field to update and value - the value to update to, are sent as POST variables to the handler. If so, we can proceed to the actual work. If not, nothing happens.

Once, we've verified that the variables were sent, we can go on sanitizing the data for insertion into the database. To keep it as simple as possible, we'll use the mysql_real_escape_string function to sanitize our data. This function escapes the special characters present in the passed string. If passed in un sanitized, our code is subject to SQL injection attacks.

Now that we've made sure the data is safe, we can update the relevant record. I am assuming this part needs no explanation since it is very simple SQL. In layman's terms, in the inplace table, change field's corresponding value to value.

If everything goes on according to plan, return a value of 1 which'll be captured by our script to determine the outcome of the transaction so it can proceed accordingly. I'll elaborate more later below. Please do note that in this case, I merely report whether the attempt succeeded or failed. In your project, you may want to return much more detailed info in case any error occurs. You are not limited to my extremely simple implementation.

The JavaScript

Now that the back end has been constructed, it's time to edit the front end part of the project to let it communicate with the server. We'll also look at implementing a new feature along the way.

Cleaning up the Old Code

One of the complaints of the old version was data corruption when certain actions were performed in a specific order. This was due to my extreme need for simplicity and conciseness which ultimately led me to overlook that specific scenario. Never the less, we'll rectify that today.

I'm assuming you have the old JavaScript code nearby to compare with and edit.

Getting Rid of Global Variables

The first version used global variables to hold the original data which led to unexpected results in certain case. We'll rectify this first.

The easiest way to rectify this would be to just add a hidden input next to the original input and use it as a buffer. Since it is created and destroyed on the fly and is specific to that element alone, we can edit/save/discard as many elements as possible as many time as possible without any hiccups.

The old replaceHTML function gets updated to:

function replaceHTML()
     {
	var buffer = $(this).html()
			    .replace(/"/g, """);
	$(this).addClass("noPad")
	       .html("")
	       .html("<form class=\"editor\">
			<input type=\"text\" name=\"value\" class=\"editBox\" value=\"" + buffer + "\" />
			<input type=\"hidden\" name=\"buffer\" class=\"buffer\" value=\"" + buffer + "\" /> 
		     </form>
		     <a href=\"#\" class=\"btnSave\">Save changes</a> 
		     <a href=\"#\" class=\"btnDiscard\">Discard changes</a>")
	       .unbind('dblclick', replaceHTML);		
     }

Not a big edit here. First we create an internal variable called buffer to hold the original value. We then purge the HTML content of the parent element and inject our own. In addition to the original snippet, we add a hidden text box which retains the original value. Nothing else is changed here.

Creating a Unified Handler

The earlier iteration binded similar but separate functions for each of the functional links. We'll unify them here.

function handler()
     {
	var selector;
	if ($(this).hasClass("btnSave"))
	{
	     selector = "editBox"
	}
	else 
        {
            selector = "buffer"
        }
		
	$(this).parent()
	       .html($(this).siblings("form")
			    .children("."+selector)
			    .val())
	       .removeClass("noPad editHover")					
	       .bind("dblclick", replaceHTML);				   
		
	return false;
     }

Instead of using anonymous functions like last time, we are going to use a normal function. We are only going to edit small parts of the function to make it handle both save and discard requests.

We first declare a variable named selector which holds the selector to use whilst updating the li elements. editBox is the class assigned the visible text box and buffer is the class assigned to the hidden text box which holds the original value.

Since we are unifying the event handlers, we need to check which link was clicked. We first see whether the clicked link has a class of btnSave. If so, then the user wants to save the edits and so we assign the value of editBox to the selector variable. If not, buffer is assigned.

The rest of the handler remains the same as the old version except that the selector is injected dynamically based on the action instead of it being hard coded into the function. If you seem lost here, look at the first part of the series to understand what the last block does. Essentially, we inject the selected text box's value into the parent li element and rebind the original event handler.

Don't forget to update the event handlers for each link. The following one liner takes care of that:

$(".btnSave, .btnDiscard").live("click", handler);

If you are wondering why I used the live function here, please refer to the earlier article.

Adding AJAX Capabilities

With all the bugs squashed out and the code generally tightened up a little, we can start working on implementing the actual functionality.

Prepping the HTML

Before we can send the data to the server, we need to find a way to send relevant details back to the server. In this case, we need 2 details to make a successful edit.

  • The value itself
  • The name of the field to be updated

The first part is rather straightforward since we have an actual text box holding the values to be sent to the server. The second part needs a little work.

Whilst creating the editor, remember that we used the primary ID of the table as id attributes to each li element? We are going to make use of it here. We'll just create another hidden text box which'll hold the value which can be then posted back to the server.

function replaceHTML()
	{
	     var buffer = $(this).html()
				 .replace(/"/g, """);
	     $(this).addClass("noPad")
		    .html("")
	   	    .html("<form class=\"editor\">
			     <input type=\"text\" name=\"value\" class=\"editBox\" value=\"" + buffer + "\" />
	  		     <input type=\"hidden\" name=\"buffer\" class=\"buffer\" value=\"" + buffer + "\" /> 
                             <input type=\"hidden\" name=\"field\" class=\"record\" value=\"" + $(this).attr("id") + "\" /> 
			   </form>
 		          <a href=\"#\" class=\"btnSave\">Save changes</a> 
			  <a href=\"#\" class=\"btnDiscard\">Discard changes</a>")
		    .unbind('dblclick', replaceHTML);		
	}

The replaceHTML function has to be updated like so. The only difference is the addition of hidden text box with the name field. We use jQuery's attr function to access the li element's ID attribute and use it as the text box's value.

The AJAX Implementation

On to the AJAX implementation then. We are going to use jQuery's standard ajax function here.

function handler()
     {
	// Previous code
	if ($(this).hasClass("btnSave"))
	{
        	var selector = "editBox";
		var str = $(this).siblings("form").serialize();
		$.ajax({
   			type: "POST",
                   	async: false,
			timeout: 100,
   			url: "handler.php",
   			data: str,
   			success: function(msg){code = msg;},					 	});	
		if(code == 1)
		{
	   	    alert ("Success");
		}
		else
		{
		    alert ("Failure");
		}
	}
	// Rest of the code
     }

Since we only need to send the data to the server when the user has clicked the relevant link, we encapsulate all the code within the if block we created earlier to check which link was clicked.

I make use of the ajax function since I find it to be the most robust. First up, I serialize the data the parent form holds so it can be posted to the server. Next, I call the ajax function setting all relevant details as necessary which includes the type of request to make - POST and the URL to post to. We also specify that the data we serialized earlier should be sent to the server.

Usually, you'd use the inbuilt success and error callbacks to make further changes but I've chosen not to do so here. Instead, I am just capturing the text sent back by the server. If it returns 1, a value we configured our handler to return if everything happened correctly, we alert the user to let him know.

Implementing a Status Bar

Tutorial Image

Alerts are a pretty rudimentary way to update the user with the status of the action. With that in mind, we are going to scrap the alert system and instead implement a simple status bar at the bottom which reflect these changes.

The Markup

We don't need anything special here. We just need a simple div element which we can manipulate. We are just going to have to add that directly in the editor.

<div id="status"></div>

Make note of the id attribute. We'll use it later.

The Helper Function

In the interest of code reusability, we'll create a helper function which updates the status bar as needed.

function UI(state)
    {
	var status = {};
	status.Ready = "Ready";
	status.Post = "Saving your data. Please wait...";
	status.Success = "Success! Your edits have been saved.";
	status.Failure = "Attempts to save data failed. Please retry.";
		
	var background = {};
	background.Ready = "#E8F3FF";
	background.Post = "#FAD054";
	background.Success = "#B6FF6C";
	background.Failure = "#FF5353";

	$("#status").animate({opacity: 0}, 200, function (){$("#status").html(status[state]).css({background: background[state]}).animate({opacity: 1}, 200)});
     }

The function, which we've named, UI, takes the state of the status bar as its parameter. Inside the function, we create two objects: status holds the relevant text and background holds the background colors of the status bar.

We could just directly update the status bar's text and background color but here at Net Tuts, that's not how we roll. :)

We are going to make use of jQuery's animate function to gracefully animate the status bar. First, we animate its opacity to zero. Next, we update its text and background color and then animate it back to full visibility.

Take special note of the fact that the logic used to update the values are enclosed within an anonymous function and passed as the callback to the original animation. This way the bar will animate to zero opacity and then everything is updated. If the animations are chained, the text and background colors will be updated just after the initial animation starts which leads to a very jarring effect.

Adding it to UI

Adding it to the UI and updating the status bar now is a piece of cake. Instead of the alerts we used earlier, we need to use the UI function.

The earlier block which succeeded the ajax call can now be replaced by:

if(code == 1)
{
	UI("Success");
}
else
{
	UI("Failure");
}

Also, don't forget to add UI("Ready"); when the page loads so the user knows the system is ready for manipulation and UI("Post"); when the data is being posted to the server.

When you are adding your own states to the task bar, make special note of the fact that the string we send as the parameter to the function maps directly to the property of the object.

Proper Data Persistence

The last thing we need to look at is the fact that if the attempt to save the data failed, the updated text is still retained. This seems rather counter intuitive. If the attempt to save the data fails, we need to make sure the original text is placed back so the user knows the data hasn't been saved.

In order to rectify this, we'll need to change the selector variable in case we encounter an error.

if(code == 1)
{
    UI("Success");
    selector = "editBox";
}
else
{
    UI("Failure");
    selector = "buffer";
}

If the value was edited successfully, we change the the relevant variable's value to editBox. But if the attempt ended in failure, we need to swap out the new value with the old value. So we assign buffer to the variable so that value will revert back to its original value.

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.

Please do keep in mind that this system was designed with the primary intention of teaching the techniques associated with this, not as a production system designed to drop in into existing systems. This is more of a foundation that I encourage people to build upon and improve.

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

Write a Plus Tutorial

Did you know that you can earn up to $600 for writing a PLUS tutorial and/or screencast for us? We're looking for in depth and well-written tutorials on HTML, CSS, PHP, and JavaScript. If you're of the ability, please contact Jeffrey at nettuts@tutsplus.com.

Please note that actual compensation will be dependent upon the quality of the final tutorial and screencast.

Write a PLUS tutorial

Related Posts
  • Code
    HTML5
    HTML5: Battery Status APIPdl54 preview image@2x
    The number of people browsing the web using mobile devices grows every day. It's therefore important to optimize websites and web applications to accommodate mobile visitors. The W3C (World Wide Web Consortium) is well aware of this trend and has introduced a number of APIs that help with this challenge. In this article, I will introduce you to one of these APIs, the Battery Status API.Read More…
  • Code
    PHP
    Building a Customer Management App Using AngularJS and LaravelLaravel 4 auth retina preview
    When creating a single-page app we should use some kind of framework to do some of the job for us, so we can focus on the actual functionality. AngularJS fits here perfectly, because features like dynamic dependency injection and bi-directional data binding are just great. Sometimes we also require some kind of server. If you've chosen PHP then Laravel may be your best option, as it's easy to work with and pretty powerful.Read More…
  • Code
    PHP
    Creating a Photo Tag Wall With Twilio Picture Messaging & PHPProcedural to oop php retina preview
    Twilio's recently announced Picture Messaging has vastly opened up what we can do with text messaging, now we can attach photos to our text messages and have them get used in different ways. In our case, we are going to build a Photo Tag Wall, which will contain photos linked to tags that will be displayed on a website.Read More…
  • Code
    JavaScript & AJAX
    Working With IndexedDB - Part 3Indexeddb retina preview
    Welcome to the final part of my IndexedDB series. When I began this series my intent was to explain a technology that is not always the most... friendly one to work with. In fact, when I first tried working with IndexedDB, last year, my initial reaction was somewhat negative ("Somewhat negative" much like the Universe is "somewhat old."). It's been a long journey, but I finally feel somewhat comfortable working with IndexedDB and I respect what it allows. It is still a technology that can't be used everywhere (it sadly missed being added to iOS7), but I truly believe it is a technology folks can learn and make use of today. In this final article, we're going to demonstrate some additional concepts that build upon the "full" demo we built in the last article. To be clear, you must be caught up on the series or this entry will be difficult to follow, so you may also want to check out part one.Read More…
  • Code
    JavaScript & AJAX
    Working With IndexedDB - Part 2Indexeddb retina preview
    Welcome to the second part of my IndexedDB article. I strongly recommend reading the first article in this series, as I'll be assuming you are familiar with all the concepts covered so far. In this article, we're going to wrap up the CRUD aspects we didn't finish before (specifically updating and deleting content), and then demonstrate a real world application that we will use to demonstrate other concepts in the final article.Read More…
  • Code
    Tools & Tips
    Tips to Avoid Brittle UI TestsUi test retina preview
    In the last article I talked about a few ideas and patterns, like the Page Object pattern, that help write maintainable UI tests. In this article we are going to discuss a few advanced topics that could help you write more robust tests, and troubleshoot them when they fail:Read More…