MySql

27 Aug 10

Listening to what your visitors have to say, is always beneficial when planning new features or changes in your website. For a long time we’ve been limited to just setting up a contact form and hoping that quality feedback will follow, which unfortunately is not always the case.

Today we are taking things up a notch – we are applying the same social principles that have brought success to sharing sites such as Digg and delicious, and encourage visitors to suggest and vote on features that they want implemented on your website.

The XHTML

Starting with the new HTML5 doctype, we define the opening and closing head and title tags, and include the main stylesheet of the app – styles.css, in the document.

suggestions.php










Feature Suggestfor Tutorialzine.com

suggestionText" class="rounded" />

After this comes the body tag and the #page div, which is the main container element. It holds the heading, the unordered list with all the suggestions (which is generated by PHP, as you will see in a moment), and the submit form.

Lastly we include the jQuery library from Google’s AJAX Library CDN, and our own script.js file, which is discussed in detail in the last section of this tutorial.

Feature Suggest App w/ jQuery, PHP, MySQL

Feature Suggest App w/ jQuery, PHP, MySQL

The Table Schema

The app uses two MySQL tables to store data. Suggestions and Suggestions_votes. The first table contains the text of the suggestion and data such as rating and the number of votes the item has received. The second table keeps record of the IPs of the voters and prevents more than one vote to be cast in a single day per IP.

Suggestion Table Schema

Suggestion Table Schema

To speed up the selection queries, an index is defined on the rating field. This helps when showing the suggestions ordered by popularity.

The suggestion votes table has a primary key consisting of three fields – the suggestion_id, the IP of the voter, and the date of the vote. And because primary keys do not allow for duplicate rows, we can be sure that users can vote only once per day by just checking the value of the affected_rows variable after the insert.

Suggestions_Votes Schema

Suggestions_Votes Schema

The PHP

Before delving into the generation of the suggestion items and the AJAX interactions, first we have to take a look at the suggestion PHP class. It uses two PHP magic methods (apart from the constructor) to provide rich functionality to our code. When generating the front page, PHP runs a MySQL select query against the database, and creates an object of this class for every table row. The columns of the row are added as properties to the object.

suggestion.class.php

class Suggestion
{
	private $data = array();

	public function __construct($arr = array())
	{
		if(!empty($arr)){

			// The $arr array is passed only when we manually
			// create an object of this class in ajax.php

			$this->data = $arr;
		}
	}

	public function __get($property){

		// This is a magic method that is called if we
		// access a property that does not exist.

		if(array_key_exists($property,$this->data)){
			return $this->data[$property];
		}

		return NULL;
	}

	public function __toString()
	{
		// This is a magic method which is called when
		// converting the object to string:

		return '
  • '.$this->suggestion.'
    '.(int)$this->rating.'
  • '; } }

    The __toString() method is used to create a string representation of the object. With its help we can build the HTML markup, complete with the suggestion title and number of votes.

    The __get() method is used to route the access to undefined properties of the class to the $data array. This means that if we access $obj->suggestion, and this property is undefined, it is going to be fetched from the $data array, and returned to us as if it existed. This way we can just pass an array to the constructor, instead of setting up all the properties. We are using this when creating an object in ajax.php.

    Now lets proceed with the generation of the unordered list on the front page.

    suggestions.php

    require "connect.php";
    require "suggestion.class.php";
    
    // Converting the IP to a number. This is a more effective way
    // to store it in the database:
    
    $ip	= sprintf('%u',ip2long($_SERVER['REMOTE_ADDR']));
    
    // The following query uses a left join to select
    // all the suggestions and in the same time determine
    // whether the user has voted on them.
    
    $result = $mysqli->query("
    	SELECT s.*, if (v.ip IS NULL,0,1) AS have_voted
    	FROM suggestions AS s
    	LEFT JOIN suggestions_votes AS v
    	ON(
    		s.id = v.suggestion_id
    		AND v.day = CURRENT_DATE
    		AND v.ip = $ip
    	)
    	ORDER BY s.rating DESC, s.id DESC
    ");
    
    $str = '';
    
    if(!$mysqli->error)
    {
    	// Generating the UL
    
    	$str = '
    
      '; // Using MySQLi's fetch_object method to create a new // object and populate it with the columns of the result query: while($suggestion = $result->fetch_object('Suggestion')){ $str.= $suggestion; // Uses the __toString() magic method. } $str .='
    '; }

    After running the query, we use the fetch_object() method of the $result object. This method creates an object of the given class for every row in the result, and assigns the columns of that row to the object as public properties.

    PHP also manages the AJAX requests sent by jQuery. This is done in ajax.php. To distinguish one AJAX action from another, the script takes a $_GET['action'] parameter, which can have one of two values – ‘vote‘ or ‘submit‘.

    ajax.php

    require "connect.php";
    require "suggestion.class.php";
    
    // If the request did not come from AJAX, exit:
    if($_SERVER['HTTP_X_REQUESTED_WITH'] !='XMLHttpRequest'){
    	exit;
    }
    
    // Converting the IP to a number. This is a more effective way
    // to store it in the database:
    
    $ip	= sprintf('%u',ip2long($_SERVER['REMOTE_ADDR']));
    
    if($_GET['action'] == 'vote'){
    
    	$v = (int)$_GET['vote'];
    	$id = (int)$_GET['id'];
    
    	if($v != -1 && $v != 1){
    		exit;
    	}
    
    	// Checking to see whether such a suggest item id exists:
    	if(!$mysqli->query("SELECT 1 FROM suggestions WHERE id = $id")->num_rows){
    		exit;
    	}
    
    	// The id, ip and day fields are set as a primary key.
    	// The query will fail if we try to insert a duplicate key,
    	// which means that a visitor can vote only once per day.
    
    	$mysqli->query("
    		INSERT INTO suggestions_votes (suggestion_id,ip,day,vote)
    		VALUES (
    			$id,
    			$ip,
    			CURRENT_DATE,
    			$v
    		)
    	");
    
    	if($mysqli->affected_rows == 1)
    	{
    		$mysqli->query("
    			UPDATE suggestions SET
    				".($v == 1 ? 'votes_up = votes_up + 1' : 'votes_down = votes_down + 1').",
    				rating = rating + $v
    			WHERE id = $id
    		");
    	}
    
    }
    else if($_GET['action'] == 'submit'){
    
    	// Stripping the content
    	$_GET['content'] = htmlspecialchars(strip_tags($_GET['content']));
    
    	if(mb_strlen($_GET['content'],'utf-8')<3){
    		exit;
    	}
    
    	$mysqli->query("INSERT INTO suggestions SET suggestion = '".$mysqli->real_escape_string($_GET['content'])."'");
    
    	// Outputting the HTML of the newly created suggestion in a JSON format.
    	// We are using (string) to trigger the magic __toString() method.
    
    	echo json_encode(array(
    		'html'	=> (string)(new Suggestion(array(
    			'id'			=> $mysqli->insert_id,
    			'suggestion'	=> $_GET['content']
    		)))
    	));
    }
    

    When jQuery fires the ‘vote‘ request, it does not expect any return values, so the script does not output any. In the ‘submit‘ action, however, jQuery expects a JSON object to be returned, containing the HTML markup of the suggestion that was just inserted. This is where we create a new Suggestion object for the sole purpose of using its __toString() magic method and converting it with the inbuilt json_encode() function.

    Suggest & Vote on Features

    Suggest & Vote on Features

    The jQuery

    All of the jQuery code resides in script.js. It listens for click events on the green and red arrows. But as suggestions can be inserted at any point, we are using the live() jQuery method, so we can listen for the event even on elements that are not yet created.

    script.js

    $(document).ready(function(){
    
    	var ul = $('ul.suggestions');
    
    	// Listening of a click on a UP or DOWN arrow:
    
    	$('div.vote span').live('click',function(){
    
    		var elem		= $(this),
    			parent		= elem.parent(),
    			li			= elem.closest('li'),
    			ratingDiv	= li.find('.rating'),
    			id			= li.attr('id').replace('s',''),
    			v			= 1;
    
    		// If the user's already voted:
    
    		if(parent.hasClass('inactive')){
    			return false;
    		}
    
    		parent.removeClass('active').addClass('inactive');
    
    		if(elem.hasClass('down')){
    			v = -1;
    		}
    
    		// Incrementing the counter on the right:
    		ratingDiv.text(v + +ratingDiv.text());
    
    		// Turning all the LI elements into an array
    		// and sorting it on the number of votes:
    
    		var arr = $.makeArray(ul.find('li')).sort(function(l,r){
    			return +$('.rating',r).text() - +$('.rating',l).text();
    		});
    
    		// Adding the sorted LIs to the UL
    		ul.html(arr);
    
    		// Sending an AJAX request
    		$.get('ajax.php',{action:'vote',vote:v,'id':id});
    	});
    
    	$('#suggest').submit(function(){
    
    		var form		= $(this),
    			textField	= $('#suggestionText');
    
    		// Preventing double submits:
    		if(form.hasClass('working') || textField.val().length<3){
    			return false;
    		}
    
    		form.addClass('working');
    
    		$.getJSON('ajax.php',{action:'submit',content:textField.val()},function(msg){
    			textField.val('');
    			form.removeClass('working');
    
    			if(msg.html){
    				// Appending the markup of the newly created LI to the page:
    				$(msg.html).hide().appendTo(ul).slideDown();
    			}
    		});
    
    		return false;
    	});
    });

    When a click on one of those arrows occurs, jQuery determines whether the ‘inactive’ class is present on the LI element. This class is only assigned to the suggestion, if the user has voted during the last day, and, if present, the script will ignore any click events.

    Notice how $.makeArray is used to turn the jQuery objects, containing the LI elements, into a true array. This is done, so we can use the array.sort() method and pass it a custom sort function, which takes two LIs at the same time and outputs a negative integer, zero or a positive integer depending on which of the two elements has a grater rating. This array is later inserted into the unordered list.

    The CSS

    Now that we have all the markup generated, we can move on with the styling. As the styling is pretty much trivial, I only want to show you the class that rounds the top-left and bottom-right corners of the elements that it is applied to. You can see the rest of the CSS rules in styles.css.

    styles.css

    .rounded,
    #suggest,
    .suggestions li{
    	-moz-border-radius-topleft:12px;
    	-moz-border-radius-bottomright:12px;
    
    	-webkit-border-top-left-radius:12px;
    	-webkit-border-bottom-right-radius:12px;
    
    	border-top-left-radius:12px;
    	border-bottom-right-radius:12px;
    }

    Notice that the Mozilla syntax differs from the standard in the way it targets the different corners of the element. Keeping that in mind, we can apply this class to pretty much every element, as you can see from the demonstration.

    With this our Feature Suggest App is complete!

    Conclusion

    If you plan to set up this script on your own server, you would need to create the two suggestion tables by running the code found in tables.sql in the SQL tab of phpMyAdmin. Also remember to fill in your database connection details in connect.php.

    You can use this script to gather precious feedback from your visitors. You can also disable the option for users to add new suggestions, and use it as a kind of an advanced poll system.

    Be sure to share your thoughts in your comment section below.

    Tutorialzine/tutorials/~4/lo__4Ubbc1M" height="1" width="1" />


    Filed under: .NET,CSS,Comment,MySql,PHP,Ping,Poll,Script,Tutorial,Upload,jQuery

    Trackback Uri




    27 Aug 10

    When you publish something online, there are not that many ways to determine whether people like what you have to say. Comments, the cornerstone of blogging, are too demanding, and users often prefer not to post one. If you’ve dropped by Behance, you’ve probably noticed their appreciate badge, which is a neat solution to this exact problem. With it people share their appreciation for somebody’s work.

    Today we are implementing such a badge, which you can include in every page of your website with a bit of jQuery magic. So go ahead and download the zip from the button above (PSD included!) and continue with the tutorial.

    The Database Schema

    The script we are doing today uses two tables. The first holds one record for each of the pages which have the appreciate button enabled. The second one stores the IP of the person that voted along the unique ID of the page. This way we can easily determine whether the person has previously voted for the page and display the appropriate version of the button (active or disabled).

    Table Schema appreciate_pages

    Table Schema appreciate_pages

    The hash field holds an MD5 sum of the URL of the page. This way we add an UNIQUE index which will speed up the selects we run on the records, as well ensure there are no duplicate records in the table. The appreciated column holds the number of appreciations of the pages.

    Table Schema appreciate_votes

    Table Schema appreciate_votes

    The appreciate_votes table contains the IP of the person that has voted (in the form of an integer), and the id of the page from the appreciate_pages table. The timestamp is automatically updated to the current time when an insert occurs.

    You can create these two tables by running the code from tables.sql in the SQL section of phpMyAdmin from the downloadable archive, part of this tutorial.

    Step 1 – XHTML

    Lets start with the XHTML part of the tutorial. The markup of the page is extremely simple. To have the appreciate button functioning, you just need to provide a container in which the button is inserted, and an optional element, which holds the total number of clicks on the button. You can safely omit the latter one, leaving you with only one div to code.

    page.html

    
    
    
    
    
    
    
    
    
    

    In the page above, you can see that I am including two stylesheet files. The first is styles.css, which is used to style the page, and appreciate.css, which is located in the plugin directory, and is responsible for the styling of the appreciate button.

    Before the closing body tag, you can see that I also include the jQuery library from Google’s CDN repository, the plugin.js file and script.js, which uses the plugin to create the button on the page. You will only need to change the contents of script.js to make the script working on your pages.

    Click To Appreciate Button

    Click To Appreciate – looks good on both dark and light backgrounds

    Step 2 – PHP

    PHP handles the database interactions and is on the backend of the AJAX requests. Most of the script logic is located in c script.php which you can see below. But first lets take a look at connect.php, which handles the database connection.

    appreciateMe/connect.php

    $db_host = 'localhost';
    $db_user = 'YourUsername';
    $db_pass = 'YouPassword';
    $db_name = 'NameOfDB';
    
    @$mysqli = new mysqli($db_host, $db_user, $db_pass, $db_name);
    
    if (mysqli_connect_errno()) {
    	die('
    

    Could not connect to the database

    '); } $mysqli->set_charset("utf8");

    Up until now, we’ve always used the old mysql extension for database connections under PHP, as it is a bit easier to use and I wanted to keep the code compatible with PHP 4. However, with the recent announcement that WordPress (our favorite blogging engine) will be dropping support for that version of PHP, I decided that it is time to also make the switch to the new version – MySQLi (MySQL improved).

    As you can see from the code above, the only major difference with the old way we connected to a database, is that here we create a MySQLi object instead of using the mysql_ functions. Also, as you will see in a moment, when we query the database a MySQL resource object is returned, which in turn has its own set of methods. This might sound intimidating, but it will become perfectly clear once you see it in action.

    appreciateMe/script.php

    /* Setting the error reporting level */
    error_reporting(E_ALL ^ E_NOTICE);
    include 'connect.php';
    
    if(!$_GET['url'] || !filter_input(INPUT_GET,'url',FILTER_VALIDATE_URL)){
    	exit;
    }
    
    $pageID			= 0;
    $appreciated	= 0;
    $jsonArray		= array();
    $hash			= md5($_GET['url']);
    $ip				= sprintf('%u',ip2long($_SERVER['REMOTE_ADDR']));
    
    // $result is an object:
    $result = $mysqli->query("SELECT id,appreciated FROM appreciate_pages WHERE hash='".$hash."'");
    
    if($result)
    {
    	list($pageID,$appreciated) = $result->fetch_row();
    	// fetch_row() is a method of result
    }
    
    // The submit parameter denotes that we need to write to the database
    
    if($_GET['submit'])
    {
    	if(!$pageID)
    	{
    		// If the page has not been appreciated yet, insert a new
    		// record to the database.
    
    		$mysqli->query("
    			INSERT INTO appreciate_pages
    			SET
    				hash='".$hash."',
    				url='".$mysqli->real_escape_string($_GET['url'])."'"
    		);
    
    		if($mysqli->affected_rows){
    
    			// The insert_id property contains the value of
    			// the primary key. In our case this is also the pageID.
    
    			$pageID = $mysqli->insert_id;
    		}
    	}
    
    	// Write the vote to the DB, so the user can vote only once
    
    	$mysqli->query("
    		INSERT INTO appreciate_votes
    		SET
    			ip = ".$ip.",
    			pageid = ".$pageID
    	);
    
    	if($mysqli->affected_rows){
    		$mysqli->query("
    			UPDATE appreciate_pages
    			SET appreciated=appreciated+1 WHERE id=".$pageID
    		);
    
    		// Increment the appreciated field
    	}
    
    	$jsonArray = array('status'=>1);
    }
    else
    {
    	// Only print the stats
    
    	$voted = 0;
    
    	// Has the user voted?
    	$res = $mysqli->query("
    		SELECT 1 FROM appreciate_votes
    		WHERE ip=".$ip." AND pageid=".$pageID
    	);
    
    	if($res->num_rows){
    		$voted = 1;
    	}
    
    	$jsonArray = array('status'=>1,'voted'=>$voted,'appreciated'=>$appreciated);
    }
    
    // Telling the browser to interpret the response as JSON:
    header('Content-type: application/json');
    
    echo json_encode($jsonArray);
    

    The script handles two different types of AJAX requests – read only request (which returns a JSON object with information about the number of appreciations of the page, and whether the current user has clicked the button), and write requests (which save the visitor’s vote to the database, and if necessary, save the page URL and hash as well).

    As you an see in the code snippet above, one of the first things that the script does is to calulate the MD5 hash of the page. This is used as a unique key in the database, as URLs have unlimited length which is incompatible with MySQL’s UNIQUE keys. As an MD5 hash is unique for most practical purposes, we can safely use it in our selects and inserts, instead of the long URL addresses.

    In the last line of the code, we convert the $jsonArray array into a valid JSON object with the inbuilt json_encode PHP function, and output it with a applicatoin/json content type.

    Click To Appreciate - Inactive

    Click To Appreciate – Inactive

    Step 3 – jQuery

    Inside the appreciateMe directory you can find the plugin.js file. You must include it in the page you wish to show the Appreciate button on. It uses AJAX to request data from the PHP backend and uses the response it receives to create the markup of the button.

    appreciateMe/plugin.js

    function(){
    
    	$.appreciateButton = function(options){
    
    		// The options object must contain a URL and a Holder property
    		// These are the URL of the Appreciate php script, and the
    		// div in which the badge is inserted
    
    		if(!'url' in options || !'holder' in options){
    			return false;
    		}
    
    		var element = $(options.holder);
    
    		// Forming the url of the current page:
    
    		var currentURL = 	window.location.protocol+'//'+
    					window.location.host+window.location.pathname;
    
    		// Issuing a GET request. A rand parameter is passed
    		// to prevent the request from being cached in IE
    
    		$.get(options.url,{url:currentURL,rand:Math.random()},function(response){
    
    			// Creating the appreciate button:
    
    			var button = $('',{
    				href:'',className:'appreciateBadge',
    				html:'Appreciate Me'
    			});
    
    			if(!response.voted){
    				// If the user has not voted previously,
    				// make the button active / clickable.
    				button.addClass('active');
    			}
    			else button.addClass('inactive');
    
    			button.click(function(){
    				if(button.hasClass('active')){
    
    					button.removeClass('active').addClass('inactive');
    
    					if(options.count){
    						// Incremented the total count
    						$(options.count).html(1 + parseInt(response.appreciated));
    					}
    
    					// Sending a GET request with a submit parameter.
    					// This will save the appreciation to the MySQL DB.
    
    					$.getJSON(options.url,{url:currentURL,submit:1});
    				}
    
    				return false;
    			});
    
    			element.append(button);
    
    			if(options.count){
    				$(options.count).html(response.appreciated);
    			}
    		},'json');
    
    		return element;
    	}
    
    })(jQuery);
    

    The script basically creates a new method in the main jQuery object. This differs from the plugins that we usually do, in that this type of plugins are not called on a set of elements (no need to select elements). You can just call $.appreciateButton() while passing a configuration object as a parameter. This is exactly what we’ve done in script.js add a button to the page:

    script.js

    $(document).ready(function(){
    
    	// Creating an appreciate button.
    
    	$.appreciateButton({
    		url		: 'appreciateMe/script.php',	// URL to the PHP script.
    		holder	: '#main',				// The button will be inserted here.
    		count	: '#countDiv'			// Optional. Will show the total count.
    	});
    
    });
    

    The configuration object, which is passed as a parameter, has to contain a url and a holder properties, whereas count is optional. Notice that I’ve specified the path to script.php relatively, as appreciateMe is a child directory of the one the page is currently in.

    However, if you plan to add the script to a site with a variable path structure, you should probably specify an absolute path. Either add a leading slash, or provide a complete URL with http://.

    Step 4 – CSS

    Now that we have all the markup and code in place, it is time to turn to the styling. The CSS rules that style the appreciate badge are located in appreciate.css. You could optionally copy these rules to your main stylesheet file, if you’d like to avoid the extra request, but beware that you will need to change the paths to the background images.

    appreciateMe/appreciate.css

    .appreciateBadge{
    	width:129px;
    	height:129px;
    	display:block;
    	text-indent:-9999px;
    	overflow:hidden;
    	background:url('sprite.png') no-repeat;
    	text-decoration:none;
    	border:none;
    }
    
    .appreciateBadge.active{
    	background-position:left top;
    }
    
    .appreciateBadge.active:hover{
    	background-position:0 -129px;
    }
    
    .appreciateBadge.inactive{
    	background-position:left bottom;
    	cursor:default;
    }
    

    There are three versions of the appreciate badge image. A default one, a hover one, and an inactive one. All three of these reside in the same file – sprite.png, one below the other. With this technique you can switch between the versions instantaneously by offsetting the background image of the hyperlink.

    styles.css

    #main{
    	margin:80px auto;
    	width:130px;
    }
    
    #countDiv{
    	color:#eee;
    	font-size:35px;
    	margin-right:120px;
    	position:absolute;
    	right:50%;
    	top:265px;
    }
    

    You can find the rest of the styles, which refine the looks of page.html, in styles.css. Only two sets of styles affect the appreciate button directly. The #main div, which contains the button and centers it on the page, and #countDiv in which the total number of appreciations is inserted.

    With this our Click to Appreciate Badge is complete!

    Conclusion

    Before being able to run this script on your server, you first have to replace the MySQL credentials in connect.php with your own. Also, you will need to run the contents of tables.sql in the SQL tab of phpMyAdmin, so the two tables are created. Lastly, depending on your URL paths, you may have to change the URL property of appreciateMe/script.php in the script.js JavaScript File.

    Tutorialzine/tutorials/~4/H5YGHZkAUEU" height="1" width="1" />


    Filed under: .NET,Announcement,CSS,Comment,Information,JavaScript,MySql,PHP,Ping,Script,Stats,Tech,Tutorial,Upload,Wordpress,jQuery

    Trackback Uri




    27 Aug 10

    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

    ajax-commenting-system%2Fimg%2Fdefault_avatar.gif">
    30 Jun 2010
    Comment Body

    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

    Add a Comment

    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

    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 = '';
    			$link_close =  '';
    		}
    
    		// 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 '
    
    '.$link_open.' '.$link_close.'
    '.$link_open.$d['name'].$link_close.'
    '. date('d M Y',$d['dt']).'
    '.$d['body'].'
    '; }

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

    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(''+
    						v+'');
    				});
    			}
    		},'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?

    Tutorialzine/tutorials/~4/fzYKTDwT3GY" height="1" width="1" />


    Filed under: CSS,Comment,MySql,PHP,Script,Tutorial,Upload,jQuery

    Trackback Uri




    5 Mar 10

    TutorialZine has written a tutorial and shown us how to create a “Who is online” widget with PHP, MySQL and jQuery. It will display the number of visitors, currently viewing your site, and it will even be able to detect the country your visitors are from and display it in a slide out panel.

    Having access to real time data on your site userbase is a dream to any webmaster. Tools like Google Analytics give a great perspective on your site


    Filed under: CSS,Forms,Information,MySql,PHP,jQuery

    Trackback Uri