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.
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!
I already had a database named inplace with a table called data on my development server. For our use we'll add another table.
I typically prefer using phpMyAdmin for running my SQL queries. Click on the SQL tab and paste in the following query:
If everything worked out as it should you should get the following screen:
A closer look at the table:
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.
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 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:
is replaced by:
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.
After making the edits, remember to save the file with a .php extension.
Create a file named handler.php and paste in the following:
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.
I'm assuming you have the old JavaScript code nearby to compare with and edit.
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:
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.
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:
If you are wondering why I used the live function here, please refer to the earlier article.
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.
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.
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.
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.
Make note of the id attribute. We'll use it later.
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.
The earlier block which succeeded the ajax call can now be replaced by:
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.
In order to rectify this, we'll need to change the selector variable in case we encounter an error.
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.
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!
Please note that actual compensation will be dependent upon the quality of the final tutorial and screencast.
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.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
| 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' ); |
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.
01
02
03
04
05
06
07
08
09
10
11
| <?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' ); ?> |
The Editor
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:
1
2
3
4
5
6
| < 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 > |
1
2
3
4
5
6
| < 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 > |
1
2
3
4
5
6
7
| <?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>' ; } ?> |
1
2
3
4
5
6
7
| <?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>' ; } ?> |
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.
1
| <?php require ( "db.php" ); ?> |
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:
01
02
03
04
05
06
07
08
09
10
11
12
| <?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;} } ?> |
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:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
| 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); } |
Creating a Unified Handler
The earlier iteration binded similar but separate functions for each of the functional links. We'll unify them here.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
| 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 ; } |
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:
1
| $( ".btnSave, .btnDiscard" ).live( "click" , handler); |
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
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.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
| 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 AJAX Implementation
On to the AJAX implementation then. We are going to use jQuery's standard ajax function here.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| 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 } |
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
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.
1
| < div id = "status" ></ div > |
The Helper Function
In the interest of code reusability, we'll create a helper function which updates the status bar as needed.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
| 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)}); } |
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:
1
2
3
4
5
6
7
8
| if (code == 1) { UI( "Success" ); } else { UI( "Failure" ); } |
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.
01
02
03
04
05
06
07
08
09
10
| if (code == 1) { UI( "Success" ); selector = "editBox" ; } else { UI( "Failure" ); selector = "buffer" ; } |
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!
- Follow us on Twitter, or subscribe to the Nettuts+ RSS Feed for the best web development tutorials on the web. Ready
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.
No comments:
Post a Comment