An AJAX Click to Appreciate Badge

Download

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

i11.png

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.

i21.png

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

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>An AJAX Click To Appreciate Badge</title>

<link rel="stylesheet" type="text/css" href="styles.css" />
<link rel="stylesheet" type="text/css" href="appreciateMe/appreciate.css"/>
</head>

<body>

<div id="countDiv"></div>
<div id="main"></div>

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js" type="text/javascript"></script>
<script src="appreciateMe/plugin.js" type="text/javascript"></script>
<script src="script.js" type="text/javascript"></script>

</body>
</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.

i3.png

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('<h1>Could not connect to the database</h1>');
}

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

i4.png

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 = $('<a>',{
                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.

Bootstrap Studio

The revolutionary web design tool for creating responsive websites and apps.

Learn more

Related Articles

pelumini

Nice one again!

SportTips World

excellent

Shawn Ramsey

Nice job Martin! A great idea, design and code. I may have to incorporate this one into my blog. Thanks!

Nelson Pacheco

You explained that really well. Because of it I added your RSS =D

Thanks!

e11world

Hi, this seems really nice and useful but I couldn't get it to work on my site. I don't think it's a path issue but maybe something to do with mysqli instead of mysql. Any ideas or should I show sample or something?? Any help is really appreciated.

Very good script, but it does not work for me.

I'm in php5 and MySQL4.0

bye

Eliseo Beltran

Great! As always!

really nice, cool featured from Behance... also, something like facebook's "I Like" button jejee

Perhaps my questions is, why do you use MySQLi instead of SQLite in this case?, perhaps SQLite is more easy for newbie users, it's portable and without installation. Well, i'm not a database guru, so this is just a question to learn, not to object your article :)

thanks as always for share
it's great for the comunity !

Great tutorial as always :)

I was always curious with PHP what the @ symbol before a given variable name (is it only for variables?) signifies and never bothered to ask!

Any help would be great! Keep up the awesome work, thanks.

Martin Angelov

Thank you for the comments folks!

@djb

You should upgrade to the latest version of MySQL if you can. There are many benefits in both performance and available functionality. You can read more here.

@ dlv

I've also wanted to experiment with SQLite for some time, but the only thing stopping me from using it in a tutorial, is that SQLite admin managers are not readily available on most hosting providers, whereas phpMyAdmin is ubiquitous.

Apart from this, SQLite would be a great choice for this kind of application.

@ Jenn

The @ symbol suppresses the error messages that might arise on the line, and keeps them from being printed to the user. Otherwise, in connect.php, if MySQLi fails to connect (because the MySQL server is not responding or other reasons) it might print your username to the visitor's browser.

would anyone like to make this into a wordpress plugin?

Sat Chen

I am kind of curious on how you would make this into a wordpress plugin. Any help?

The very first opening brace is missing in the plugin.js code...

Good idea. Wonderful tut >.<
wonna try in my blog...

Great tutorial! Thanks!

i noticed that the Ajax Badge only allows one click from a particular IP Address...i work in an organisation where we all share one IP address(eventually). That may mean only one of us in the org could 'like' s'thing. perhaps you could consider using session variables instead?...

leaflette

Yup, it is awesome, but really hope that, it has in form of wordpress plugin, Want to install it for the blog. Sighh*

Rey Bango

Nice work although the term is actually spelled "Ajax".

http://en.wikipedia.org/wiki/Ajax_(programming)

techyogi

Thanks Martin :).This is very descriptive tutorial you have written.Going to write abut it on my blog.I hope you are OK with it.

Garrett Sullivan

Just wanted to let you know that I implemented the code and it worked without a hitch. I've been waiting for something like this for a while.

Thanks a lot man!

Garrett Sullivan

Quick question though. I wanted to put this on each post of my blog. The blog scrolls downwards and displays many posts on one page and its screwing up the button because its meant for a 1 per page sort of thing. Is there any way I could make it work where you could put many buttons on one page or no?

Thanks!

Great tutorial. In case anyone else has any issues with it "not working," it could be a jquery noconflict thing.

http://api.jquery.com/jQuery.noConflict/

e11world

Hi all,
I tried using this a while back and now I'm trying again with some progress. Before I was getting an error message of some sort but now no error or anything but the badge and counter are not showing up! SQL tables and credentials are correct and no erros but still not working. Any ideas?? http://eddiepotros.com/appreciate/

iLimitless

Exactly the same trouble. I've solved it! There was an error in my password to DB. So check your settings in connect.php and your DB password.

Christian

I have tested it and it works!, but when I try to use it in wordpress it just doesn't work, I add de div tag for it to appear and it just doesn't appear, any idea to fix it?

e11world

Just want to let whoever is having a problem in on an update after I'VE FIXED MINE. iLimitless was right, I had the wrong db user and now it works http://eddiepotros.com/appreciate/

My new question now is how can I do this for multiple elements on one page (what's the best approach too)? If I wanted to add it similar to the Like or Rate per song like on this site http://georgeelias.net/music.html

I appreciate any answers! Merry Christmas everyone :)

e11world

I hope that this is still visited by people because I would love an answer to this question as well as another I asked a while ago.
I'm using the script on http://e11world.com/party2012 (in the middle of the page) and when I click the image, I get a NaN but when I refresh, it counted the vote. I wonder why I'm getting that?

Did you evenr find a solution to the NaN displaying?? Thank you

Alistair

Hi Guys,

Has anyone transformed this into a wordpress plugin yet? I would be willing to pay and Im sure others would too if this was possible.

Please let me know.

Alistair

Hi Martin. Thanks for your awesome tutorial. Some people were asking about a NaN error and I've overcome this by doing the following:

Find this line in the plugin.js file:

if(options.count){
    // Incremented the total count

Paste the following code directly underneath that line:

var default_counter;
default_counter = parseInt(response.appreciated);
if (isNaN(default_counter)) {
    default_counter = 0;
}

That will fix the NaN error.

Cheers,

@BHRIV

benzenski

Anyone had a problem with the wrong numbers showing?
I can see that clicking on it with a different ip adds correctly an entry in the db, but sometimes there's a display bug that shows 1 to the counter. Any idea where it could come from?

Thanks