This time, we are making a Simple AJAX Commenting System. It will feature a gravatar integration and demonstrate how to achieve effective communication between jQuery and PHP/MySQL with the help of JSON.
Step 1 – XHTML
First, lets take a look at the markup of the comments. This code is generated by PHP in the Comment class, which we are going to take a look at in a moment.
demo.php
<div class="comment">
<div class="avatar">
<a href="http://tutorialzine.com/">
<img src="http://www.gravatar.com/avatar/112fdf7a8fe3609e7af2cd3873b5c6bd?size=50&default=http%3A%2F%2Fdemo.tutorialzine.com%2F2010%2F06%2Fsimple-ajax-commenting-system%2Fimg%2Fdefault_avatar.gif">
</a>
</div>
<div class="name"><a href="http://tutorialzine.com/">Person's Name</a></div>
<div title="Added at 06:40 on 30 Jun 2010" class="date">30 Jun 2010</div>
<p>Comment Body</p>
</div>
The avatar div contains a hyperlink (if the user entered a valid URL when submitting the comment) and an avatar image, which is fetched from gravatar.com. We will return to this in the PHP step of the tut. Lastly we have the name and time divs, and the comment body.
The other important element in the XHTML part is the comment form. It is sent via POST. All fields except for the URL field are required.
demo.php
<div id="addCommentContainer">
<p>Add a Comment</p>
<form id="addCommentForm" method="post" action="">
<div>
<label for="name">Your Name</label>
<input type="text" name="name" id="name" />
<label for="email">Your Email</label>
<input type="text" name="email" id="email" />
<label for="url">Website (not required)</label>
<input type="text" name="url" id="url" />
<label for="body">Comment Body</label>
<textarea name="body" id="body" cols="20" rows="5"></textarea>
<input type="submit" id="submit" value="Submit" />
</div>
</form>
</div>
The form is submitted via AJAX. The validation is performed entirely in the backend by submit.php, as you will see in the jQuery step of the tutorial. Every field has a corresponding label element, with an appropriate for attribute.
Simple AJAX Commenting System
Step 2 – PHP
PHP handles the communication with the MySQL database and generates the markup of the comments. It is also on the receiving end of the AJAX requests and inserts the comment data to the comments table. You can see the code that prints the comments to the page below.
demo.php
/*
/ Select all the comments and populate the $comments array with objects
*/
$comments = array();
$result = mysql_query("SELECT * FROM comments ORDER BY id ASC");
while($row = mysql_fetch_assoc($result))
{
$comments[] = new Comment($row);
}
The MySQL query selects all the entries from the database and fills the $comments array with objects of the comment class, which you will see below. This array is outputted later in the execution of the script.
demo.php
/*
/ Output the comments one by one:
*/
foreach($comments as $c){
echo $c->markup();
}
Each comment has a markup() method, which generates valid HTML code ready to be printed to the page. You can see the definition of this method and the class below.
The class takes a row from the database (fetched with mysql_fetch_assoc() ) and stores it in the private variable $data. It is available only to the methods of the class and cannot be accessed from outside.
comment.class.php – Part 1
class Comment
{
private $data = array();
public function __construct($row)
{
/*
/ The constructor
*/
$this->data = $row;
}
public function markup()
{
/*
/ This method outputs the XHTML markup of the comment
*/
// Setting up an alias, so we don't have to write $this->data every time:
$d = &$this->data;
$link_open = '';
$link_close = '';
if($d['url']){
// If the person has entered a URL when adding a comment,
// define opening and closing hyperlink tags
$link_open = '<a href="'.$d['url'].'">';
$link_close = '</a>';
}
// Converting the time to a UNIX timestamp:
$d['dt'] = strtotime($d['dt']);
// Needed for the default gravatar image:
$url = 'http://'.dirname($_SERVER['SERVER_NAME'].$_SERVER["REQUEST_URI"]).
'/img/default_avatar.gif';
return '
<div class="comment">
<div class="avatar">
'.$link_open.'
<img src="http://www.gravatar.com/avatar/'.
md5($d['email']).'?size=50&default='.
urlencode($url).'" />
'.$link_close.'
</div>
<div class="name">'.$link_open.$d['name'].$link_close.'</div>
<div class="date" title="Added at '.
date('H:i \o\n d M Y',$d['dt']).'">'.
date('d M Y',$d['dt']).'</div>
<p>'.$d['body'].'</p>
</div>
';
}
This script uses gravatar to present avatars in the comments. For those of you, who have not used gravatar, this is a really useful service, which lets you associate an avatar with your email address. The avatar image can easily be fetched by passing an md5() encoded hash of your email address to gravatar.com. This is exactly what we do on line 48.
Notice line 39 above it – the script tries to figure out the URL at which it is located, and determines the exact address of the default_avatar.gif image. This gif is passed to gravatar along the md5 hash, so if no avatar was found for this particular email, the fallback image is displayed instead.
comment.class.php – Part 2
public static function validate(&$arr)
{
/*
/ This method is used to validate the data sent via AJAX.
/
/ It return true/false depending on whether the data is valid, and populates
/ the $arr array passed as a paremter (notice the ampersand above) with
/ either the valid input data, or the error messages.
*/
$errors = array();
$data = array();
// Using the filter_input function introduced in PHP 5.2.0
if(!($data['email'] = filter_input(INPUT_POST,'email',FILTER_VALIDATE_EMAIL)))
{
$errors['email'] = 'Please enter a valid Email.';
}
if(!($data['url'] = filter_input(INPUT_POST,'url',FILTER_VALIDATE_URL)))
{
// If the URL field was not populated with a valid URL,
// act as if no URL was entered at all:
$url = '';
}
// Using the filter with a custom callback function:
if(!($data['body'] = filter_input(INPUT_POST,'body',FILTER_CALLBACK,
array('options'=>'Comment::validate_text'))))
{
$errors['body'] = 'Please enter a comment body.';
}
if(!($data['name'] = filter_input(INPUT_POST,'name',FILTER_CALLBACK,
array('options'=>'Comment::validate_text'))))
{
$errors['name'] = 'Please enter a name.';
}
if(!empty($errors)){
// If there are errors, copy the $errors array to $arr:
$arr = $errors;
return false;
}
// If the data is valid, sanitize all the data and copy it to $arr:
foreach($data as $k=>$v){
$arr[$k] = mysql_real_escape_string($v);
}
// Ensure that the email is in lower case (for a correct gravatar hash):
$arr['email'] = strtolower(trim($arr['email']));
return true;
}
The validate() method above (also part of the class) is defined as static. This means that it can be evoked directly like Comment::validate(), without the need of creating an object of the class. What this method does, is validate the input data which is submitted via AJAX.
This method uses the new filter functions, which are available as of PHP 5.2.0. These allow us to easily validate and filter any input data that is passed to the script. For example filter_input(INPUT_POST,’url’,FILTER_VALIDATE_URL) means that we are checking whether $_POST['url'] is a valid URL address. If it is, the function returns the value of the variable, otherwise it return false.
This is really useful, as up until now, we had to use custom regular expressions to validate data (and have series of if statements). Also, another advantage is that this data is fetched before any configuration-specific transformations (like magic quotes) are applied.
We also have the option of specifying a custom function which is going to apply some more advanced modifications of the data, as you can see from lines 31 and 37.
comment.class.php – Part 3
private static function validate_text($str)
{
/*
/ This method is used internally as a FILTER_CALLBACK
*/
if(mb_strlen($str,'utf8')<1)
return false;
// Encode all html special characters (<, >, ", & .. etc) and convert
// the new line characters to <br> tags:
$str = nl2br(htmlspecialchars($str));
// Remove the new line characters that are left
$str = str_replace(array(chr(10),chr(13)),'',$str);
return $str;
}
}
The last method is validate_text, which we are passing as a callback function in the two filter_input calls above. It encodes all special HTML characters, effectively blocking XSS attacks. It also replaces the newline characters with <br /> line breaks.
submit.php
/*
/ This array is going to be populated with either
/ the data that was sent to the script, or the
/ error messages:
/*/
$arr = array();
$validates = Comment::validate($arr);
if($validates)
{
/* Everything is OK, insert to database: */
mysql_query(" INSERT INTO comments(name,url,email,body)
VALUES (
'".$arr['name']."',
'".$arr['url']."',
'".$arr['email']."',
'".$arr['body']."'
)");
$arr['dt'] = date('r',time());
$arr['id'] = mysql_insert_id();
/*
/ The data in $arr is escaped for the mysql insert query,
/ but we need the unescaped text, so we apply,
/ stripslashes to all the elements in the array:
/*/
$arr = array_map('stripslashes',$arr);
$insertedComment = new Comment($arr);
/* Outputting the markup of the just-inserted comment: */
echo json_encode(array('status'=>1,'html'=>$insertedComment->markup()));
}
else
{
/* Outputting the error messages */
echo '{"status":0,"errors":'.json_encode($arr).'}';
}
submit.php receives the comment form data via an AJAX request. It validates it and outputs a JSON object with either the XHTML markup of the successfully inserted comment, or a list of error messages. jQuery uses the status property to determine whether to display the error messages or add the comment markup to the page.
You can see two example responses below.
Successful response
{
"status": 1,
"html": "Html Code Of The Comment Comes Here..."
}
The html property contains the code of the comment, similar to markup in step one.
Failure response
{
"status": 0,
"errors": {
"email": "Please enter a valid Email.",
"body": "Please enter a comment body.",
"name": "Please enter a name."
}
}
On failure, jQuery loops through the errors object, and outputs the errors next to the fields that caused them.
Fancy CSS3 & jQuery Submit Form
Step 3 – CSS
Now that we have all the markup properly generated and displayed on the page, we can move on to styling it.
styles.css – Part 1
.comment,
#addCommentContainer{
/* Syling the comments and the comment form container */
padding:12px;
width:400px;
position:relative;
background-color:#fcfcfc;
border:1px solid white;
color:#888;
margin-bottom:25px;
/* CSS3 rounded corners and drop shadows */
-moz-border-radius:10px;
-webkit-border-radius:10px;
border-radius:10px;
-moz-box-shadow:2px 2px 0 #c2c2c2;
-webkit-box-shadow:2px 2px 0 #c2c2c2;
box-shadow:2px 2px 0 #c2c2c2;
}
.comment .avatar{
/*
/ The avatar is positioned absolutely,
/ and offset outside the comment div
/*/
height:50px;
left:-70px;
position:absolute;
width:50px;
background:url('img/default_avatar.gif') no-repeat #fcfcfc;
/* Centering it vertically: */
margin-top:-25px;
top:50%;
-moz-box-shadow:1px 1px 0 #c2c2c2;
-webkit-box-shadow:1px 1px 0 #c2c2c2;
box-shadow:1px 1px 0 #c2c2c2;
}
The .comment divs and the #addCommentContainer are styled at once because they share most of the styling. A number of CSS3 rules are applied, including rounded corners and a box-shadow. Needless to say, these do not work in older browsers, but as they are purely presentational, the script will still work without them.
styles.css – Part 2
.comment .avatar img{
display:block;
}
.comment .name{
font-size:20px;
padding-bottom:10px;
color:#ccc;
}
.comment .date{
font-size:10px;
padding:6px 0;
position:absolute;
right:15px;
top:10px;
color:#bbb;
}
.comment p,
#addCommentContainer p{
font-size:18px;
line-height:1.5;
}
#addCommentContainer input[type=text],
#addCommentContainer textarea{
/* Styling the inputs */
display:block;
border:1px solid #ccc;
margin:5px 0 5px;
padding:3px;
font-size:12px;
color:#555;
font-family:Arial, Helvetica, sans-serif;
}
#addCommentContainer textarea{
width:300px;
}
label{
font-size:10px;
}
label span.error{
color:red;
position:relative;
right:-10px;
}
#submit{
/* The submit button */
background-color:#58B9EB;
border:1px solid #40A2D4;
color:#FFFFFF;
cursor:pointer;
font-family:'Myriad Pro',Arial,Helvetica,sans-serif;
font-size:14px;
font-weight:bold;
padding:4px;
margin-top:5px;
-moz-border-radius:4px;
-webkit-border-radius:4px;
border-radius:4px;
}
#submit:hover{
background-color:#80cdf5;
border-color:#52b1e2;
}
In the second part of the stylesheet, we style the comment and form elements. Notice the input[type=text] selector, which selects elements depending on the type attribute.
Step 4 – jQuery
Now lets continue with jQuery, which is the last step of this tutorial. After including the library to the bottom of the page (best for the perceived performance of the page) we can start coding the script file.
script.js
$(document).ready(function(){
/* The following code is executed once the DOM is loaded */
/* This flag will prevent multiple comment submits: */
var working = false;
/* Listening for the submit event of the form: */
$('#addCommentForm').submit(function(e){
e.preventDefault();
if(working) return false;
working = true;
$('#submit').val('Working..');
$('span.error').remove();
/* Sending the form fileds to submit.php: */
$.post('submit.php',$(this).serialize(),function(msg){
working = false;
$('#submit').val('Submit');
if(msg.status){
/*
/ If the insert was successful, add the comment
/ below the last one on the page with a slideDown effect
/*/
$(msg.html).hide().insertBefore('#addCommentContainer').slideDown();
$('#body').val('');
}
else {
/*
/ If there were errors, loop through the
/ msg.errors object and display them on the page
/*/
$.each(msg.errors,function(k,v){
$('label[for='+k+']').append('<span class="error">'+
v+'</span>');
});
}
},'json');
});
});
Starting from the top, we have the $(document).ready() call, which binds a function to the DOM content loaded event. The working variable acts as a flag, which tells the script if an AJAX request is in progress (thus preventing double posting).
In the callback function for the POST AJAX request, we check the status property to determine whether the comment was successfully inserted. If it was, we add the received markup to the page after the last comment with a slideDown animation.
If there were problems, we display the error messages by appending an error span to the appropriate label element (the for attribute of the label contains the id of the input that caused the error).
With this our Simple AJAX Commenting System is complete!
Conclusion
To be able to run this script on your server, you need to create the comments table in your MySQL database. You can do this by executing the SQL code found in table.sql from the SQL tab of phpMyAdmin. After this you need to enter your MySQL connection details in connect.php.
You are free to modify and use this code any way you see fit.
What do you think? How would you improve this script?
It’s been summer for about a week now. Whether you’re on vacation or burning the midnight oil, attending a local/nearby WordCamp is a great way to spend a weekend. Meet other WordPress users, developers, designers & consultants, learn a little something, maybe share a little of your own experience and knowledge, and break bread (or raise a toast) with new friends and collaborators. Here are the WordCamps scheduled for this summer, along with what I know about them.
July 3: WordCamp Germany – Berlin, Germany. I love it that they’re using BuddyPress for their event site. They have multiple tracks, and what looks to be a nice variety of sessions. It’s only a few days away, so if you’re thinking of going, get your tickets now!
July 10: WordCamp Boulder – Boulder, Colorado, USA. This was WordCamp Denver last year, but the organizers have decided to mix it up and go back and forth between Denver and Boulder, which also has a thriving tech community. This year the venue is the Boulder Theater (so pretty!), and there will sessions for bloggers and devs alike, plus a Genius Bar to help people get their WordPress sites all fixed up. The speaker lineup looks good, and I hear they’re pumping up the wifi this year. I’ll be there, likely hunched over a notebook with Lisa Sabin-Wilson (author of WordPress for Dummies and BuddyPress for Dummies) to talk about the WordPress User Handbook project, and/or hunched over a sketchbook with Kevin Conboy (designed the new lighter “on” state for admin menus in WordPress 3.0) to work out a new default WordCamp.org theme (using BuddyPress). You can still get tickets!
July 17–18: WordCamp UK- Manchester, England, UK. The roving WordCamp UK will be in Manchester this year, and is probably the closest to BarCamp style of all the WordCamps, using a wiki to plan some speakers/sessions and organizing the rest ad-hoc on the first day of the event. I’ll be attending this one as well, and am looking forward to seeing WordPress lead developer Peter Westwood again. I’m also looking forward to meeting some core contributors for the first time in person, like Simon Wheatley and John O’Nolan. Mike Little, co-founder of WordPress, is on the organizing team of WordCamp UK. Tickets on sale now!
July 24: WordCamp Nigeria – Lagos, Nigeria. Their site seems to have a virus, so no link from here, but if you’re in Nigeria and interested in attending/getting involved, a quick Google search will get you to the organizers.
August 7: WordCamp Houston – Houston, TX, USA. Houston, Texas, birthplace of WordPress! Fittingly, Matt Mullenweg will be there to give the keynote. WordCamp Houston is running three tracks — Business, Blogger and Developer — in recognition of the fact that people who are interested in using WordPress for their business may not actually be bloggers or developers themselves. This used to get labeled as a “CMS” track at previous WordCamps (including NYC 2009), but with WordPress 3.0 supporting CMS functionality out of the box, “Business” is a much more appropriate label. Who wants to bet on if there will be BBQ for lunch?
August 7 : WordCamp Iowa – Des Moines, Iowa, USA. Another placeholder page. Happening, not happening? I’ve emailed the organizer and will update this post once I know more.
August 7–8: WordCamp New Zealand – Auckland, New Zealand. They haven’t announced this year’s speakers or topics, but they’ve been running polls to get community input into the program. Of note: in 2011 WordCamp New Zealand will be shifting seasons and will be in February instead, when the weather is nicer.
August 20–22: WordCamp Savannah – Savannah, Georgia, USA. Disclaimer: I am completely biased about Savannah, since I’m one of the organizers. This will be the first WordCamp in Savannah, and it’s being held at the Savannah College of Art and Design River Club, an awesome venue that used to be a cotton warehouse or something like that. Since Savannah doesn’t really have a cohesive WordPress community yet (though a fair number of people from Savannah attended WordCamp Atlanta earlier this year), this WordCamp is aimed squarely at building a local community. We’ll have a local meet-and-greet, regular sessions with visiting speakers (lots of core contributors coming to this one, plus Matt), and on Sunday it will be combination unconference/genius bar/collaborative workspace. Oh, and a potluck! We’ll also be running a pre-WordCamp workshop for people who have never used WordPress but want to get started, so that they’ll be able to follow the presentations and conversations littered with WordPress-specific vocabulary over the weekend. Ticket sales just opened, so get your tickets now.
For a schedule of all upcoming WordCamps, visit wordcamp.org. The autumn schedule is already packed! If you don’t see WordCamp in your area and are interested in organizing one, get more information and let us know.