Creating a Dynamic Poll with jQuery and PHP

When you combine some neat functionality courtesy of PHP with the cleverness of jQuery you can produce some pretty cool results. In this tutorial we’ll create a poll using PHP and XHTML, then make use of some jQuery Ajax effects to eliminate the need for a page refresh, and to give it a nice little bit of animation.

  1. HTML
  2. PHP
    1. Introduction
    2. poll_default()
    3. poll_submit()
    4. poll_return_results()
    5. poll_ajax()
  3. CSS
  4. Javascript
    1. Introduction
    2. formProcess()
    3. loadResults()
    4. animateResults()

HTML

Let’s get our <head> set up:

<link href="style.css" rel="stylesheet" type="text/css" />
<script src="jquery.js" type="text/javascript" charset="utf-8"></script>
<script src="jquery.cookie.js" type="text/javascript" charset="utf-8"></script>
<script src="poll.js" type="text/javascript" charset="utf-8"></script>
  • style.css will hold the CSS markup.
  • jquery.js is the base jQuery library.
  • jquery.cookie.js is a plugin by Klaus Hartl to add cookie manipulation to jQuery.
  • poll.js will have the Javascript that makes the poll dynamic.

Next, we’ll create a simple poll form:

Poll
<div id="poll-container">
    <h3>Poll</h3>
    <form id='poll' action="poll.php" method="post" accept-charset="utf-8">
        <p>Pick your favorite Javascript framework:</p>
        <p><input type="radio" name="poll" value="opt1" id="opt1" /><label for='opt1'>&nbsp;jQuery</label><br />
        <input type="radio" name="poll" value="opt2" id="opt2" /><label for='opt2'>&nbsp;Ext JS</label><br />
        <input type="radio" name="poll" value="opt3" id="opt3" /><label for='opt3'>&nbsp;Dojo</label><br />
        <input type="radio" name="poll" value="opt4" id="opt4" /><label for='opt4'>&nbsp;Prototype</label><br />
        <input type="radio" name="poll" value="opt5" id="opt5" /><label for='opt5'>&nbsp;YUI</label><br />
        <input type="radio" name="poll" value="opt6" id="opt6" /><label for='opt6'>&nbsp;mootools</label><br /><br />
        <input type="submit" value="Vote &rarr;" /></p>
    </form>
</div>

This form will be processed by the PHP for now, and when we get the Javascript running, by jQuery. The PHP and Javascript are designed to pull the option ID from the value tag. &nbsp; is just a HTML entity encoded space, and &rarr; is an arrow: →.

PHP

Introduction

If Javascript is disabled, the PHP will:

  1. Take GET/POST requests from the form
  2. Set/check a cookie
  3. Make sure the request is from a unique IP
  4. Store the vote in a flat file DB
  5. Return the results included with a HTML file

If Javascript is enabled, the PHP will:

  1. Take GET/POST requests from the Javascript
  2. Make sure the request is from a unique IP
  3. Store the vote in a flat file DB
  4. Return the results as JSON

For the flat file DB we will be using a package written by Luke Plant.

First, we need an array with the names and IDs of the poll options:

<?php
$options[1] = 'jQuery';
$options[2] = 'Ext JS';
$options[3] = 'Dojo';
$options[4] = 'Prototype';
$options[5] = 'YUI';
$options[6] = 'mootools';

The flatfile package uses numbers for the column identifiers, so lets set some constants to convert those to names:

define('OPT_ID', 0);
define('OPT_TITLE', 1);
define('OPT_VOTES', 2);

When the form is submitted, PHP needs to know what file to insert the results into and return, so we set another constant:

define('HTML_FILE', 'index.html');

We need to include flatfile.php and initialize a database object:

require_once('flatfile.php');
$db = new Flatfile();

The flat files are just text files stored in the data directory:

$db->datadir = 'data/';
define('VOTE_DB', 'votes.txt');
define('IP_DB', 'ips.txt');

If we get a request with the poll parameter, it’s the static form, so we process it. If the request has a vote parameter in it, it’s a Ajax request. Otherwise, we just return the HTML_FILE.

if ($_GET['poll'] || $_POST['poll']) {
  poll_submit();
}
else if ($_GET['vote'] || $_POST['vote']) {
  poll_ajax();
}
else {
  poll_default();
}

poll_default()

function poll_default() {
  global $db;

  $ip_result = $db->selectUnique(IP_DB, 0, $_SERVER['REMOTE_ADDR']);

  if (!isset($_COOKIE['vote_id']) && empty($ip_result)) {
    print file_get_contents(HTML_FILE);
  }
  else {
    poll_return_results($_COOKIE['vote_id']);
  }
}

poll_default() processes requests directly to the script with no valid GET/POST requests.

The global line makes the $db object available in the function’s scope.

The script tracks unique IPs to make sure you can only vote once, so we do a query to check whether it is in the DB:

$ip_result = $db->selectUnique(IP_DB, 0, $_SERVER['REMOTE_ADDR']);

If we don’t have a cookie and the IP query comes up empty, the client hasn’t voted yet, so we can just send the HTML file which contains the form. Otherwise, we just send the results:

if (!isset($_COOKIE['vote_id']) && empty($ip_result)) {
  print file_get_contents(HTML_FILE);
}
else {
  poll_return_results($_COOKIE['vote_id']);
}

poll_submit()

function poll_submit() {
  global $db;
  global $options;

  $id = $_GET['poll'] || $_POST['poll'];
  $id = str_replace("opt", '', $id);

  $ip_result = $db->selectUnique(IP_DB, 0, $_SERVER['REMOTE_ADDR']);

  if (!isset($_COOKIE['vote_id']) && empty($ip_result)) {
    $row = $db->selectUnique(VOTE_DB, OPT_ID, $id);
    if (!empty($row)) {
      $ip[0] = $_SERVER['REMOTE_ADDR'];
      $db->insert(IP_DB, $ip);

      setcookie("vote_id", $id, time()+31556926);

      $new_votes = $row[OPT_VOTES]+1;
      $db->updateSetWhere(VOTE_DB, array(OPT_VOTES => $new_votes), new SimpleWhereClause(OPT_ID, '=', $id));

      poll_return_results($id);
    }
    else if ($options[$id]) {
      $ip[0] = $_SERVER['REMOTE_ADDR'];
      $db->insert(IP_DB, $ip);

      setcookie("vote_id", $id, time()+31556926);

      $new_row[OPT_ID] = $id;
      $new_row[OPT_TITLE] = $options[$id];
      $new_row[OPT_VOTES] = 1;
      $db->insert(VOTE_DB, $new_row);

      poll_return_results($id);
    }
  }
  else {
    poll_return_results($id);
  }
}

poll_submit() takes the form submission, checks if the client has already voted, and then updates the DB with the vote.

These lines get the selected option’s ID, and set $id to it:

$id = $_GET['poll'] || $_POST['poll'];
$id = str_replace("opt", '', $id);

We need to check whether the option is in the DB yet:

$row = $db->selectUnique(VOTE_DB, OPT_ID, $id);

If it is in the DB (result not empty), we need to run an updateSetWhere(). If it isn’t we need to do an insert():

if (!empty($row)) {
  $new_votes = $row[OPT_VOTES]+1;
  $db->updateSetWhere(VOTE_DB, array(OPT_VOTES => $new_votes), new SimpleWhereClause(OPT_ID, '=', $id));

  poll_return_results($id);
}
else if ($options[$id]) {
  $new_row[OPT_ID] = $id;
  $new_row[OPT_TITLE] = $options[$id];
  $new_row[OPT_VOTES] = 1;
  $db->insert(VOTE_DB, $new_row);

  poll_return_results($id);
}

Either way, we need to insert the IP into the DB, and set a cookie (expires in one year):

$ip[0] = $_SERVER['REMOTE_ADDR'];
$db->insert(IP_DB, $ip);

setcookie("vote_id", $id, time()+31556926);

poll_return_results()

function poll_return_results($id = NULL) {
    global $db;

    $html = file_get_contents(HTML_FILE);
    $results_html = "<div id='poll-container'><div id='poll-results'><h3>Poll Results</h3>\n<dl class='graph'>\n";

    $rows = $db->selectWhere(VOTE_DB,
      new SimpleWhereClause(OPT_ID, "!=", 0), -1,
      new OrderBy(OPT_VOTES, DESCENDING, INTEGER_COMPARISON));

    foreach ($rows as $row) {
      $total_votes = $row[OPT_VOTES]+$total_votes;
    }

    foreach ($rows as $row) {
      $percent = round(($row[OPT_VOTES]/$total_votes)*100);
      if (!$row[OPT_ID] == $id) {
        $results_html .= "<dt class='bar-title'>". $row[OPT_TITLE] ."</dt><dd class='bar-container'><div id='bar". $row[OPT_ID] ."'style='width:$percent%;'>&nbsp;</div><strong>$percent%</strong></dd>\n";
      }
      else {
        $results_html .= "<dt class='bar-title'>". $row[OPT_TITLE] ."</dt><dd class='bar-container'><div id='bar". $row[OPT_ID] ."' style='width:$percent%;background-color:#0066cc;'>&nbsp;</div><strong>$percent%</strong></dd>\n";
      }
    }

    $results_html .= "</dl><p>Total Votes: ". $total_votes ."</p></div></div>\n";

    $results_regex = '/<div id="poll-container">(.*?)<\/div>/s';
    $return_html = preg_replace($results_regex, $results_html, $html);
    print $return_html;
}

poll_return_results() generates the poll results, takes the HTML file, replaces the form with the results, and returns the file to the client.

First, lets grab the HTML file and set $html to it:

$html = file_get_contents(HTML_FILE);

Next, we start the results HTML structure:

$results_html = "<div id='poll-container'><div id='poll-results'><h3>Poll Results</h3>\n<dl class='graph'>\n";

To create the results HTML we need to get all the rows (options) from the DB sorted by number of votes:

$rows = $db->selectWhere(VOTE_DB,
  new SimpleWhereClause(OPT_ID, "!=", 0), -1,
  new OrderBy(OPT_VOTES, DESCENDING, INTEGER_COMPARISON));

We also need the total votes to calculate percentages:

foreach ($rows as $row) {
  $total_votes = $row[OPT_VOTES]+$total_votes;
}

Next, we calculate the percentage of votes the current option has:

foreach ($rows as $row) {
  $percent = round(($row[OPT_VOTES]/$total_votes)*100);

The HTML for the results will be a definition list (<dl>) styled with CSS to create bar graphs:

$results_html .= "<dt class='bar-title'>". $row[OPT_TITLE] ."</dt><dd class='bar-container'><div id='bar". $row[OPT_ID] ."'style='width:$percent%;'>&nbsp;</div><strong>$percent%</strong></dd>\n";

Also, we should check if the current option is the one the client voted for, and change the color:

if (!$row[OPT_ID] == $id) {

}
else {
  $results_html .= "<dt class='bar-title'>". $row[OPT_TITLE] ."</dt><dd class='bar-container'><div id='bar". $row[OPT_ID] ."' style='width:$percent%;background-color:#0066cc;'>&nbsp;</div><strong>$percent%</strong></dd>\n";
}

Here, we add a total vote count and close the html tags:

$results_html .= "</dl><p>Total Votes: ". $total_votes ."</p></div></div>\n";

This is a regex that finds the poll-container <div>:

$results_regex = '/<div id="poll-container">(.*?)<\/div>/s';

The last step in this function is to replace the poll form with the results using the regex, and return the result:

$return_html = preg_replace($results_regex, $results_html, $html);
print $return_html;

poll_ajax()

function poll_ajax() {
  global $db;
  global $options;

  $id = $_GET['vote'] || $_POST['vote'];

  $ip_result = $db->selectUnique(IP_DB, 0, $_SERVER['REMOTE_ADDR']);

  if (empty($ip_result)) {
    $ip[0] = $_SERVER['REMOTE_ADDR'];
    $db->insert(IP_DB, $ip);

    if ($id != 'none') {
      $row = $db->selectUnique(VOTE_DB, OPT_ID, $id);
      if (!empty($row)) {
        $new_votes = $row[OPT_VOTES]+1;

        $db->updateSetWhere(VOTE_DB, array(OPT_VOTES => $new_votes), new SimpleWhereClause(OPT_ID, '=', $id));
      }
      else if ($options[$id]) {
        $new_row[OPT_ID] = $id;
        $new_row[OPT_TITLE] = $options[$id];
        $new_row[OPT_VOTES] = 1;
        $db->insert(VOTE_DB, $new_row);
      }
    }
  }

  $rows = $db->selectWhere(VOTE_DB, new SimpleWhereClause(OPT_ID, "!=", 0), -1, new OrderBy(OPT_VOTES, DESCENDING, INTEGER_COMPARISON));
  print json_encode($rows);
}

poll_ajax() takes a request from the Javascript, adds the vote to the DB, and returns the results as JSON.

There are a few lines of code that are different from poll_submit(). The first checks if the Javascript just wants the results, and no vote should be counted:

if ($id != 'none')

The other two lines select the whole DB and return it as JSON:

$rows = $db->selectWhere(VOTE_DB, new SimpleWhereClause(OPT_ID, "!=", 0), -1, new OrderBy(OPT_VOTES, DESCENDING, INTEGER_COMPARISON));
print json_encode($rows);

CSS

.graph {
  width: 250px;
  position: relative;
  right: 30px;
}
.bar-title {
  position: relative;
  float: left;
  width: 104px;
  line-height: 20px;
  margin-right: 17px;
  font-weight: bold;
  text-align: right;
}
.bar-container {
  position: relative;
  float: left;
  width: 110px;
  height: 10px;
  margin: 0px 0px 15px;
}

.bar-container div {
  background-color:#cc4400;
  height: 20px;
}
.bar-container strong {
  position: absolute;
  right: -32px;
  top: 0px;
  overflow: hidden;
}
#poll-results p {
  text-align: center;
}
Poll Results

This CSS styles the results returned by the PHP or Javascript.

  • .graph styles the container for the bars, titles and percentages. The width will be different for each site.
  • .bar-title styles the titles for the bar graphs.
  • .bar-container styles the individual bar and percentage containers
  • .bar-container div styles the div that the bar is applied to. To create the bars, a percentage width is set with PHP or Javascript.
  • .bar-container strong styles the percentage.
  • #poll-results p styles the total votes.

Javascript

Introduction

The Javascript will intercept the submit button, send the vote with Ajax, and animate the results.

First, some global variables. You should recognize the first three from the PHP. votedID stores the ID of the option the client voted for.

var OPT_ID = 0;
var OPT_TITLE = 1;
var OPT_VOTES = 2;

var votedID;

Now we need a jQuery ready function which runs when the page loads:

$(document).ready(function(){

Inside that function we register the handler for the vote button which will run formProcess when it is triggered:

$("#poll").submit(formProcess);

We also need to check if the results <div> exists, and animate the results if it does:

if ($("#poll-results").length > 0 ) {
    animateResults();
}

If we have a cookie we should jump straight to generating the results because the user has already voted. To do that we need to get rid of the poll form, get the id from the cookie, grab the results from the PHP and pass them to loadResults().

if ($.cookie('vote_id'))
    $("#poll-container").empty();
    votedID = $.cookie('vote_id');
    $.getJSON("poll.php?vote=none",loadResults);
}

formProcess()

function formProcess(event){
  event.preventDefault();

  var id = $("input[@name='poll']:checked").attr("value");
  id = id.replace("opt",'');

  $("#poll-container").fadeOut("slow",function(){
    $(this).empty();

    votedID = id;
    $.getJSON("poll.php?vote="+id,loadResults);

    $.cookie('vote_id', id, {expires: 365});
    });
}

formProcess() is called by the submit event which passes it an event object. It prevents the form from doing a normal submit, checks/sets the cookies, runs an Ajax submit instead, then calls loadResults() to convert the results to HTML.

First, we need to prevent the default action (submitting the form):

event.preventDefault();

Next, we get the ID from the currently selected option:

var id = $("input[@name='poll']:checked").attr("value");
id = id.replace("opt",'');

input[@name='poll']:checked is a jQuery selector that selects a <input> with an attribute of name='poll' that is checked. attr("value") gets the value of the object which in our case is optn where n is the ID of the option.

Now that we have the ID, we can process it. To start, we fade out the poll form, and setup an anonymous function as a callback that is run when the fade is complete. Animations don’t pause the script, so weird things happen if you don’t do it this way.

$("#poll-container").fadeOut("slow",function(){

After it has faded out we can delete the form from the DOM using empty():

$(this).empty();

In this case, $(this) is jQuery shorthand for the DOM element that the fade was applied to.

jQuery has some other shortcut functions, including $.getJSON() which does GET request for a JSON object. When we have the object, we call loadResults() with it:

$.getJSON("poll.php?vote="+id,loadResults);

The last thing to do is set the cookie:

$.cookie('vote_id', id, {expires: 365});

loadResults()

function loadResults(data) {
  var total_votes = 0;
  var percent;

  for (id in data) {
    total_votes = total_votes+parseInt(data[id][OPT_VOTES]);
  }

  var results_html = "<div id='poll-results'><h3>Poll Results</h3>\n<dl class='graph'>\n";
  for (id in data) {
    percent = Math.round((parseInt(data[id][OPT_VOTES])/parseInt(total_votes))*100);
    if (data[id][OPT_ID] !== votedID) {
      results_html = results_html+"<dt class='bar-title'>"+data[id][OPT_TITLE]+"</dt><dd class='bar-container'><div id='bar"+data[id][OPT_ID]+"'style='width:0%;'>&nbsp;</div><strong>"+percent+"%</strong></dd>\n";
    } else {
      results_html = results_html+"<dt class='bar-title'>"+data[id][OPT_TITLE]+"</dt><dd class='bar-container'><div id='bar"+data[id][OPT_ID]+"'style='width:0%;background-color:#0066cc;'>&nbsp;</div><strong>"+percent+"%</strong></dd>\n";
    }
  }

  results_html = results_html+"</dl><p>Total Votes: "+total_votes+"</p></div>\n";

  $("#poll-container").append(results_html).fadeIn("slow",function(){
    animateResults();});
}

loadResults() is called by $.getJSON() and is passed a JSON object containing the results DB. It is pretty much the same as it’s PHP counterpart poll_return_results() with a few exceptions. The first difference is that we set the width on all the bars to 0% because we will be animating them. The other difference is that we are using a jQuery append() instead of regex to show the results. After the results fade in, the function calls animateResults().

animateResults()

function animateResults(){
  $("#poll-results div").each(function(){
      var percentage = $(this).next().text();
      $(this).css({width: "0%"}).animate({
                width: percentage}, 'slow');
  });
}

animateResults() iterates through each of the bars and animates the width property based on the percentage.

each() is a jQuery function that iterates through each element that is selected:

$("#poll-results div").each(function(){

First, we set the percentage to the text of the element next to the bar which is the <strong> containing the percentage.

var percentage = $(this).next().text();

Then we make sure the width is set to 0%, and animate it:

$(this).css({width: "0%"}).animate({
  width: percentage}, 'slow');

Related Posts

Add Comment

Discussion 200 Comments

Comment Page 6 of 6 1 ... 4 5 6
  1. Lise says:

    I am also getting nothing after fade out despite making all the changes above. I have a question out to my web host about PHP version. Has anyone else identified any other issues that need fixing? And are there any changes we can make to the PHP to make it run on PHP4?

  2. Lise says:

    It turns out I am running PHP 5.2 so the version is not my issue :(

  3. Jordan says:

    I guess the versions are not the issues..
    I tried with different versions, the results still fade away..
    Please, help..

  4. Plugo.cz says:

    Wonderfull, thanks for this tutorial.

  5. Martin says:

    After editing the 2 lines in the poll.php file, remember:

    1) Delete the cookie vote_id
    2) Delete your IP in ips.txt

    I’ve tested it in localhost (xampp) and it’s works fine.

  6. Jesse Smith says:

    I’m having the most common problem – ips.txt does update, but votes.txt does not update.

    I have made all the suggested changes. And I have php version 5.2.9

    Can someone with a working version send me their poll.php source?

    Thanks a lot. jessesmth23@gmail.com

  7. Peter Yee says:

    Tried Robbie’s fix. Still not working.

    Same problem with Jesse Smith. ips.txt update, but votes.txt not. my php is 5.2.6

    HEELLPP!!! please…..

  8. Colton says:

    Finally working, MAKE SURE that you delete your ip from the ips.txt at first I was just clearing my cookies in my browser and not paying attention to that. Make sure you have ?> at the end of the poll.php document and change the necessary lines in poll.php as stated above.

  9. Jody says:

    Hello,

    I would like to show the number of votes for each answer, not only the percentage. It is possible ? if yes how? thanks

  10. Lorne Pike says:

    I tried all of the above changes but still had the problem with it fading out rather than displaying any results. I then removed the ?> at the end, that had been suggested in some of the comments, but kept the changes that shannon mentions above, and voila! For me it’s working fine.

    Thanks shannon and everyone!

  11. Eldian says:

    Following Robbie’s instruction above I got this to work on PHP v5.2.5 but has not work on PHP v5.2.10 for me.

    For PHP v5.2.10 it will not show the results. After submitting your vote it will simply fade away and then show a blank screen.

    If anyone has gotten this to work on PHP v5.2.10 please post your source code.

  12. John says:

    Anyone adapted this great poll for store data in a mysql database?

  13. saulius says:

    try to double click vote in demo :)

    you should unbind click event from the button after doing preventDefault().

  14. jp says:

    I just installed the script to my local host. everything is working fine. but the result is not visible. noting is visible in he output. its blank.

  15. me123 says:

    Anyone know how I could use this poll with Blogger? thx

  16. David Moreen says:

    This looks like a really nice poll, I mean it. The problem that I would have is that you are using javascript to determine wether the user has voted yet…

  17. faaliyet says:

    Hi
    I’ve rewrited the script to work with mysql. Works perfect without one problem. The problem is cookies of firefox. When I create a new poll, that’s returning with the same cookie. I can’t rate the different poll in FF. That is OK in IE. I think, I should use with sessions and without jquery cookie plugin.

    Do you have any idea ?

    Thanks.
    faaliyet

    • Mark says:

      Hi faaliyet,

      Do you have the code for the MySQL conversion? I was trying it myself, but i got stuck.

      Greetings,

      Mark

      • faaliyet says:

        Hi Mark,
        That’s not ready yet! When I finish , I will share that here as link. Break time for ajax poller. Now I’m working on another ajax- php module. Hold on please.

        Thanks.
        Seyhun Cavus.

  18. frz says:

    I hope that MySQL conversion will show soon :)

  19. Michael says:

    Is there any way to have multiple polls on one page with this script?

  20. yazer says:

    For those getting blank pages, edit the HTML file and put the folder path for the JS files… for example:
    “poll/jquery.js”

  21. simmo says:

    Great code and robbie’s fix worked for me but i was wondering if anybody knew how to reset the poll.

    for example, if you wanted to update the poll each week with a different question…i guess u can delete the votes file and clear all the IP’s but if the actual users do not clear there cookies and history on thier own web browser this will not allow thejm to vote…is there a work around for this? cheerz in advance!

    • Elijah says:

      I’m sure there’s another way to do that, however, I’d just rename the cookie name to something other than “vote_id” and reference the new cookie name in poll.php and poll.js. That should work just fine.

      • simmo says:

        That seemed to work, thanks. I have also changed the exipration date of the cookie to a day, instead of a year. I wonder if this will allow users to add a vote after a day of making a previous vote???

        Would that work?

  22. lucky says:

    If you have java version of the demo is like a

  23. Aimee says:

    I just read all the above comments. This is absolutely ridiculous. What a terrible tutorial.

    • joe says:

      I agree. so much potential, but what a waste of time in the end. he author could have done a just a litttle bit more explaining and this would have been the BEST script on the web :(

  24. joe says:

    this is all fine and dandy, and quite nice, but there are no instructions for setting this up properly on the server, unless im missing something?

    nowhere does it say or show how to set what permissions for which files.

  25. keyur patel says:

    awesome tutorial, thanks

  26. I spent 30 minutes with the tutorial before deciding on a simple and effective Ajax poll tutorial from W3Schools.

    The W3Schools tutorial had my poll up and running in less than 5 minutes.

    http://www.w3schools.com/php/php_ajax_poll.asp

  27. Encounter says:

    I have uploaded the poll, but first time viewing I don’t see the form second time refreshed, then only the results come up because my IP was logged. Any ideas?

  28. marat says:

    What is “rightright” property?

Comment Page 4 of 4 1 2 3 4

Add a Comment