Fancy Quotes With jQuery, AJAX & CSS

Download

Today we are making a fancy quote rating system that will display a number of famous quotes and will enable our site visitors to rate their favorites.

We are using PHP and MySQL on the back-end and jQuery and jQuery UI on the front.

As an addition, we are using two jQuery plugins - qTip for fancy tips and the Star Rating plugin for great looking star-based ratings for each quote.

In the process we are also implementing a dynamic font replacement with Cufon.

So go ahead, grab the download archive and continue with step one.

Note: This tutorial is quite old and doesn't work in PHP7 and above. We are keeping it online only as a reference.

Step 1 - XHTML

As usual, we start with the XHTML part of the tut. The quote system is divided in two - the left part, which is a sort of a "full view" mode for the quote, featuring a beautiful font replacement with Cufon, and the right part, which shows the quotes in a browse or "thumbnail" mode.

You can see the code below.

demo.php

<div id="main">
<div id="box">    <!-- Holds the full view of the quote -->

<div id="topPart">    <!-- The top part of the view -->

<div id="quote-content">  <!-- The quote text and author -->
<h1 class="quote"></h1>
<span id="author"></span>
</div>

</div>

<div id="botPart"></div>
<div class="clear"></div>
</div>

<div id="quotes-list">    <!-- The quote list on the right, scrollable on mouse move -->
<div id="quotes-hide">
<div id="quotes-slide">

<?php
// Outputting the list
echo $str;
?>

</div>
</div>
</div>

<div class="clear"></div>
</div>

The markup is pretty straightforward. We define the main structure of the rating system.

On lines 21-24 we echo the quotes, contained in the $str PHP variable. We will come back to this in a few moments.

Now lets take a closer look at the styling.

i4.png
A Fancy Quote System

Step 2 - CSS

Because the code is too long for a single take, I've divided it into two parts. I've also removed some of the styles that are not directly used by the quote system. You can view those in the demo files.

demo.css - Part 1

#box{
    /* This holds the full view of the quote */
    background:url(img/mid_part.png) repeat-y;
    width:574px;
    margin:10px auto;
    float:left;
}

#topPart{
    /* Positioned inside the #box div, holds the top part of the view */
    background:url(img/top_part.png) no-repeat;

    min-height:195px;

    /* Providing height for browsers that do not support min-height (e.g. IE6) */
    height:195px;

    float:left;
    margin-top:-10px;
    width:100%;
    padding-top:20px;
}

div > #topPart{
    /* Setting the height to auto for non-IE6 browsers */
    height:auto;
}

#botPart{
    /* Bottom part of the full view */
    background:url(img/bot_part.png) no-repeat;
    height:24px;
    float:left;
    width:100%;
    margin-bottom:-10px;
}

h1.quote{
    /* This is the text of the quote in the full view. Replaced by Cufon */
    font-size:45px;
    color:#616161;
    text-align:right;
    padding:15px 40px 0 40px;
    line-height:60px;
}

#author{
    /* The span that holds the name of the author */
    color:#9C9C9C;
    float:right;
    font-size:14px;
    font-style:italic;
    font-weight:bold;
    letter-spacing:1px;
    margin-right:40px;
    text-transform:uppercase;
}

Although I recently stopped supporting IE6 in my works, I decided to make an exception this time, mainly because the only thing needed for it to work in that version are two lines of code.

As you may have heard, IE6 does not support the min-height property. It also incorrectly interprets height as specifying minimum height.

This is why I provide both in the #topPart rules and later set height to auto for browsers that support the > CSS selector. As IE6 does not understand this operator, it is left with a height behaving like min-height and ignoring the original one.

demo.css - Part 2

#quotes-list{
    /* The list on the right */
    background:#444444 none repeat scroll 0 0;
    border:1px solid #333333;
    float:left;
    margin-left:50px;
    padding-top:10px;
    width:230px;
    height:350px;
    overflow:hidden;
}

#quotes-hide{
    /* Positioned inside #quotes-list, but a 15px smaller */
    height:335px;
    overflow:hidden;
}

.star-rating{
    margin-top:2px;
}

.thumb{
    /* Each quote that is positioned in the list on the right of the page */
    background:url(img/top_part.png) no-repeat 50% 70%;
    color:#777777;
    font-size:16px;
    font-weight:bold;
    height:40px;
    margin:0 10px 15px;
    overflow:hidden;
    padding:15px;
    width:180px;
    cursor:pointer;
    text-align:left;
}

.thumb:hover{
    background:white;
}

#quotes-list,.thumb,p.tut{
    /* Using CSS3 rounded corners */
    -moz-border-radius:6px;
    -webkit-border-radius:6px;
    border-radius:6px;
}

.points{
    /* The points next to the quote title */
    color:#999999;
    font-size:12px;
    font-weight:normal;
}

The second part of the code styles the sliding list on the right side. All the quotes are positioned one after another and they overflow their parent container. Then, setting overflow to hidden hides those that do not fit and we are ready to use jQuery to code that fancy mousemove interactivity. We will come back to this in the jQuery part.

Now we can continue with the next step.

i12.png
The quote rater explained

Step 3 - PHP

PHP generates the quote list by running an SQL query against the database, and selecting all the quotes. This data is also passed to the JavaScript front end with the help of the fillData() JS function.

demo.php

// Error reporting
error_reporting(E_ALL^E_NOTICE);

// Including file for the DB connection:
define("INCLUDE_CHECK",1);
require 'connect.php';
require 'functions.php';

// Converting the visitor's IP to a long int:
$ip = ip2long($_SERVER['REMOTE_ADDR']);

$today = date("Y-m-d");

// Selecting the quotes and LEFT JOIN-ing them to the votes:

$result = mysql_query("
SELECT q.*, IF(v.id,1,0) AS voted
FROM quotes AS q
LEFT JOIN quotes_votes AS v
    ON  q.id = v.qid
    AND v.ip =".$ip."
    AND v.date_submit = '".$today."'
");

$i=1;
$str='';
$script='';

while($row=mysql_fetch_assoc($result))
{
    // Looping through all the quotes and generating the list on the right of the page:
    $str.= '<div class="thumb" id="q-'.$i.'">'.substr($row['txt'],0,20).
    '<span class="points">...</span><div class="star-rating" id="rating-'.$i.'">';

    $row['rating'] = round($row['rating']);
    for($z=0;$z<5;$z++)
    {
        $str.='<input type="radio" name="rate-'.$i.'" value="'.($z+1).'" '.($z+1==$row['rating']?'checked="checked"':'').' disabled="disabled" />';
    }

    $str.='</div></div>';

    // Each quote calls the fillData JS function
    $script.="fillData(".formatJSON($row).");".PHP_EOL;

    $i++;
}

The $str variable is then outputted to the page, as you saw in the first step.

PHP also handles the voting for the quotes. You can view the code from vote.php below.

vote.php

// Error reporting
error_reporting(E_ALL^E_NOTICE);

define("INCLUDE_CHECK",1);
require "connect.php";

if(!$_POST['qid'] || !$_POST['vote']) die('0');

/* Converting the visitor's IP into a long int: */
$ip = ip2long($_SERVER['REMOTE_ADDR']);

/* Converting the parameters to int will prevent any malpractices */
$qid = (int)$_POST['qid'];
$v = (int)$_POST['vote'];

/* Inserting the vote in the votes DB */
mysql_query('   INSERT INTO quotes_votes (qid,ip,vote,date_submit)
                VALUES ('.$qid.','.$ip.','.$v.',CURDATE())');

/* There is an unique index set up that prevents duplicate votes */
if(mysql_affected_rows($link)==1)
{
    mysql_query("UPDATE quotes SET votes=votes+1, vsum=vsum+".$v.", rating=vsum/votes WHERE id=".$qid);
    echo'1';
}
else echo '0';

The script outputs 1 on success and 0 on failure. This is later used by jQuery to decide whether voting was successful. This brings us to the next step.

i21.png
Replacing fonts with Cufon

Step 4 - jQuery

jQuery handles all the user interactions with the quote rating system. It shows the quote in full view, issues the replacement of the fonts with Cufon and handles the voting process.

Before being able to use the jQuery library, we have to include a few files in the page.

<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.7.2/jquery-ui.min.js"></script>
<script type="text/javascript" src="star-rating/ui.stars.min.js"></script>
<script type="text/javascript" src="q-tip/jquery.qtip-1.0.0-rc3.min.js"></script>
<script src="cufon/cufon-yui.js" type="text/javascript"></script>
<script src="cufon/ChunkFive_400.font.js" type="text/javascript"></script>

<script type="text/javascript">
Cufon.replace('h1.quote');
</script>

<script type="text/javascript" src="script.js"></script>

<script type="text/javascript">
<!-- Outputting the JS generated on the PHP-side -->
<?php echo $script ?>
</script>

The first two lines include the jQuery library and jQuery UI (an addition to jQuery for interface components) in the page. After this we include the Star Rating plugin, qTip, and the Cufon text replacement along with the ChunkFive converted font.

Now lets take a look at script.js, which holds all of our JavaScript.

Because of the length of the code, I've divided it into three parts.

script.js - Part 1

$(document).ready(function(){
    /* Executed on DOM load */

    $(".star-rating").stars();
    /* Converts all the radio groups into star ratings */

    $("#quotes-hide").mousemove(function(e){

        /* The scrollable quote container */

        if(!this.hideDiv)
        {
            /* These variables are initialized only the first time the function is run: */
            this.hideDiv = $(this);
            this.scrollDiv = $('#quotes-slide');
            this.pos = this.hideDiv.offset();

            this.pos.top+=20;
            /* Adding a 20px offset, so that the scrolling begins 20px from the top */

            this.slideHeight = this.scrollDiv.height();
            this.height = this.hideDiv.height();
            this.height-=20;
            /* Adding a bottom offset */

            this.totScroll = this.slideHeight-this.height;
        }

        this.scrollDiv.css({

            /* Remember that this.scrollDiv is a jQuery object, as initilised above */
            marginTop:'-'+this.totScroll*(Math.max(e.pageY-this.pos.top,0)/this.height)+'px'

            /* Assigning a negative top margin according to the position of the mouse cursor, passed
            with e.pageY; It is relative to the page, so we substract the position of the scroll container */
        });
    });

    $('.thumb').click(function(e){
        /* Executed once a quote from the list on the right is clicked */

        var obj = quotes[parseInt(e.target.id.replace('q-',''))-1];
        /* The e.target.id of the quote corresponds to its position in the quotes array */

        $('#quote-content').fadeOut('fast',function(){
            /* This is a callback function, run once the quote container on the left has faded out */

            if(!obj) return false;

            /* Replacing the contents of the quote text and author */
            $(this).find('h1').html(obj.txt);
            $(this).find('span').html(obj.author);

            /* Changing the background color: */
            if(obj.bgc) $('body').stop().animate({backgroundColor:obj.bgc},'slow');

            /* Generating the radio boxes for the tool-tip star rating */
            var tmpstr='';
            for(var z=0;z<5;z++)
            {
                tmpstr+='<input type="radio" name="voteQ" value="'+(z+1)+'" ';
                if(z+1==obj.rating) tmpstr+='checked=checked ';
                if(parseInt(obj.voted)) tmpstr+='disabled="disabled" ';
                tmpstr+='/>';
            }

            tmpstr='<div id="voteRating">'+tmpstr+'</div>';

            /* Updating the content of the tool-tip and converting it to a star rating */
            $('#box').qtip("api").updateContent(tmpstr);

            $('#voteRating').stars({
                cancelShow: false,
                oneVoteOnly: true,
                callback:function(ui, type, value){vote(obj,value);}
            });

            /* Regenerating the cufon text replacement for the new quote text on the left */
            Cufon.refresh();

            /* Show the new quote with a fade-in effect */

            $(this).fadeIn('fast');
        });
    });

First we bind a mousemove event with the DIV with a quotes-hide id. Inside the callback function, the container is scrolled with the help of a negative top margin. This way we save a lot of real estate on the page and provide a some nice interactivity.

Next we set up a click event handler on the thumbs, which shows the appropriate quote in the view.

script.js - Part 2

  /* Creating and configuring the tool-tip with the help
    of the qtip plugin: */
    $('#box').qtip({
        content: {
            text:'This is an active list element',
            title: { text: 'Vote for this quote'},
            prerender:true
        },
        show: 'mouseover',
        hide: {
            delay:1000,
            fixed:true,
            when:'mouseout'
        },
        position: {
            corner: {
                target: 'bottomMiddle',
                tooltip: 'topMiddle'
            },
            adjust:{
                y:20
            }
        },
        style: {
            border: {
                width: 2,
                radius: 6
            },
            name:'light',
            tip: 'topMiddle'
        }
    });

    /* After the page has loaded, click the first quote on the right */
    setTimeout(function(){$('#q-1').click();},250);
});

In the second part of the code, we are configuring the qTip plugin. It provides countless options and customizations, so it would be a good idea to have the documentation ready if you plan to modify the code. You can consult with the plugin's homepage for more info.

Also we issue a click on the first quote, so that the view is not empty on page load.

script.js - Part 3

/* The global array holding all the data about the quotes.
Filled in on page load. */
var quotes = new Array();
function fillData(obj)
{
    quotes.push(obj);
}

function vote(ob,value)
{
    /* Sending the votes to vote.php */
    $.post('vote.php',{qid:ob.id,vote:value},function(data){

        if(data=="1")
        {
            /* If the vote was saved successfully, update the quote data.. */
            ob.voted=1;
            ob.votes=ob.votes+1;
            ob.vsum=ob.vsum+parseInt(value);
            ob.rating = Math.round(ob.vsum/ob.votes);

            /* ..and update the star rating */
            $('#rating-'+ob.id).stars("select", ob.rating);
            quotes[ob.id-1]=ob;
        }
    });
}

The first function - fillData() takes a JavaScript object as a parameter, and adds it to the quotes array for later use.

As you remember, in the PHP step we converted the $row arrays, returned by mysql_fetch_array, to JSON objects with our custom formatJSON() function and enclosed them in a fillData function call.

This is later outputted as JS code which is executed before document.ready(), and as a result we have all the data about the quotes available to jQuery in the quotes array.

The second function handles the AJAX requests to vote.php and updates the interface if necessary.

i31.png
The mouse slider

Step 5 - MySQL

If you plan to run this demo on your own, you'll need to create the tables by executing the SQL code in tables.sql on your server (you can do this via the phpMyAdmin interface).

Also you'll need to fill in your MySQL credentials in connect.php.

With this our fancy quote manager is complete!

Conclusion

Today we created a fancy quote managing system and learned a thing or two about tooltips, font replacements, AJAX, PHP and many more.

You are free to download and use the demo in your own sites, modifying it any way you see fit.

Also if you liked this tutorial be sure to subscribe to our RSS feed or follow us on twitter.

Bootstrap Studio

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

Learn more

Related Articles