.NET

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




    4 Apr 10

    live.pirillo.com – nvidia in the chatroom wants to know what ‘.Net’ is – the framework, not the internet top level domain.


    Filed under: .NET

    Trackback Uri