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

Groniningen

Very nice high quality tutorial, every aspact is in it. From server to client side

Cool idea. I could see the advantages of using something like this. Thank you.

amazing tuts, thanks for sharing dude ^^

Is this site quite new? I only found it recently. The quality of the tutorials is great and so is the design of the site itself. Any plans for a few screencast tutorials?

webchester

Very nice lesson :)

Thank you for posting the lesson!

Great great great :)
Please I want to use only scroll.
But i have some list inside list which is hidden, and on click is expanded.
So, how to recalculate #quotes-slide height when its expanded?

Thanks

Martin Angelov

@ Kenan

To make the script rescan the height of the div you are scrolling, once your list is expanded you can do something like this:

Replace line 11 from part one of script.js:

if(!this.hideDiv)

With something like this:

if(!this.hideDiv || rescan)

And you'll need to initialize the variable somewhere outside of $document.ready() in the script.js file. This will enable you to control when all the heights are recalculated by just setting rescan to true when you expand a list on click.

Also don't forget to set rescan=false; inside the if construct above, otherwise all the heights will be recalculated every time you move the mouse (which you certainly do not want).

Hope that helps.

This is simply beautiful Martin - I wanna ' quote ' my entire page now ! :D awesome tut ( once again ! ) always great to see all these new tuts u post here . love ur site :)

M.

Yes Yes Yes :)

Thanks man ;)

This is the best! This site has amazing tutorials!

It's really smooth.....and nice....thanks......

I made a one page web site using the script. Worked wonderfully. If you are interested you can click on my name to check it out.

Thanks again. Put a credit link on there for ya too.

What would you have to change to allow people to vote using the right list as well as the qtip?

Any one? I need to know the same thing. i am kinda new to jquery and all I have managed to do so far is break it lol. The people viewing the site can't figure out to vote and it would be easier if they could vote on the right.

Liminal Graphic Design Cornwall

This is really really cool - love the effect man! Thanks for posting, need to give it a go.

Its great work... looks treemanddousss

Any way to allow this to pull from say an XML file? Rather than a database?

I'm interested in removing the rating part of it and using it for a 'Testimonials' page.

i want to create my own website.
can i use this tutorials examples in my website for my website designing purpose by changing source code. or any liscence required for that.

I used this tutorial as a base for my site and the author didn't say anything. I will probably be putting a credit link back on there soon. I have made a few changes though. Added Fatfree Cart, a jump menu etc. Either way it's a sweet tutorial.

leonidas

thx for this nice brilliant article

i'm wondering if there is away to add a live dynamic "top ten" voted articles according to users votes.

thx in advance for help.

Digital Scene

Great post, thanks for sharing your time and efforts here. Reckon I will be able to use this for a little project I have in mind for a accommodation site I run...

hey bro what if i want the quotes to rotate or switch to the next in say 5 secs automatically ??