Print this Article (NS4)
Netscape Navigator
Internet Explorer
Opera
Neoplanet

Forums
HTML
General
Site Dev
Programming
Flash
Grafix (Art)

Laboratory
Smart HTML
Color Lab
Generators

Contents
Simple CGI
  1. Hello World
  2. Print function
  3. Quoting, EOF
  4. Metacharacters
  5. Special Characters
Perl Basics 1
  1. Variables
  2. Arrays
  3. Hashes
  4. Split function
  5. Subroutines
  6. Defaults
Form CGI
  1. Loops
  2. Conditions
  3. Boolean Statements
  4. Pattern Matching
Time CGI
  1. Local Time
  2. GM Time
  3. time function
Perl Basics 2
  1. Reading Files
  2. Writing Files
  3. Including Files
  4. chop function
  5. chomp function
Guestbook CGI

Redirect CGI

Poll CGI

  1. Giving Commands
  2. Voting
  3. Results Display
  4. Adding Your Vote
Password CGI
  1. Authentification
  2. Multiple Users
  3. Encryption
Mailing List CGI
  1. Sendmail
  2. Multiple Recipients
Unlimited Subdomains CGI

News Grabber CGI

  1. LWP::Simple
Message Board CGI (Part 1)

Back to the Top


Perl Case Study - Poll CGI
By Lisa Hui

Polls are a cool application that's both interactive and probably informative. I've seen and created some, ranging from simple to difficult, but first thing's first: you have to be able to understand how they work and create the basic before moving on to the difficult applications. We're going to start with a simple application and build up from there. Error checking is minimal until we get into the advanced parts.

Planning the Script:

Purpose: To add real-time interactivity to a website and provide interesting statistics for visitors to view.

Interaction Features: The user should be able to see the poll and be given a chance to add a vote to the choice that best fits. Once the vote is submitted, he or she should not be able to vote again and be shown the current results. (Optionally, the voter can be given the privilege of viewing the current results without voting).

  • Displaying the poll question and choices to vote on
  • Displaying the current poll results
  • Receiving the data (user requesting a certain action - such as displaying data and voting - to be performed)
  • Interpreting the data/commands

Structure:

How the script will do what it does:

A command will be used to tell the script what the user wants to do or see. We will have the following command options: vote and results. If there is a need to

Receive command and data
Interpret command and parse data
Modify the poll's data files as necessary
Display a HTML page created on-the-fly based on command

Extra Script Features:

  • Web based modification of poll topic and questions
  • Choice user-submitted comments and reports
  • Results with graphical bar display


Giving Commands

The easiest way for a person to access a poll is probably to go to an HTML page given by a certain URL, probably something like http://www.yoursite.com/poll/vote.html and http://www.yoursite.com/poll/results.html.

But we can get our output directly (and in real-time) from the CGI script itself. The URL would probably something look like: http://www.yoursite.com/poll.cgi. You already know that you can tell a CGI script what to do via a form, but there's yet another alternative to pass this "command" information straight to the script via its URL (more specifically, though its query string). If you wanted to directly access the voting page (the HTML equivalent would be vote.html in our example) we might want to just type in something like http://www.yoursite.com/poll.cgi?show=vote. That tells the script that you want to see the voting page. Likewise, http://www.yoursite.com/poll.cgi?show=results would tell the script that you would want to see the results.

This isn't a built in Perl structure! But it does take advantage of the parse/get data routine that was detailed in our Form CGI Case Study. How does the structure? Everything after the question mark (?) is given to the CGI script in the environment variable $ENV{'QUERY_STRING'}. By default, anything passed through the URL to give the CGI script information uses the GET method (discussed in our HTML forms compendium). The form parsing routine takes a pair, which in our example is "show=vote." The first, "show," becomes a key in the hash %FORM (see Perl Basics: Becoming a Perl Newbie Extraordinaire to find out more about hashes). The second, "vote," is the value stored in $FORM{'show'}. In other words, that entire query string to poll.cgi resulted in the creation of a key/value pair creation: the equivalent of saying $FORM{'show'} = "vote";

Thus, it is possible to specify any key/value pair you wish. Instead of show, we could have said http://www.yoursite.com/poll.cgi?page=vote. The only difference in this case is that the key is different. It is the equivalent of declaring $FORM{'page'} = "vote"; (Similarly, the "vote" value can be whatever you want it to be - just keep track of what it is and what you want the script to do if the "page" value is "vote" if you get my drift.)

If you don't have a clue as to how we are going to make the script perform actions based on the value of $FORM{'show'} (we will be using "show" as the key value in the hash) check out the Mailing List CGI Case Study.

Okay, now that we're set with how URLs can pass data to a CGI script and you're already aquainted with how forms pass data to a CGI script, we're going to take the two cases: vote and results. We start out with the basics of the script:

#!/usr/local/bin/perl
######################
# File: poll.cgi

print "Content-type: text/html\n\n";

###### Get and Parse Data ######
if ($ENV{"REQUEST_METHOD"} eq 'GET') { $buffer = $ENV{'QUERY_STRING'}; }
else { read(STDIN, $buffer, $ENV{'CONTENT_LENGTH'}); }

@pairs = split(/&/, $buffer);

foreach $pair (@pairs) {
   ($name, $value) = split(/=/, $pair);
   $value =~ tr/+/ /;
   $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
   $FORM{$name} = $value;
}

#Decide what to do based on the value of the $FORM{'show'} variable
#Three functions:
#1st to show the voting page
#2nd to show the results
#3rd to calculate and add the new vote to the list
if ($FORM{'show'} eq "vote") { &vote; }
elsif ($FORM{'show'} eq "results") { &results; }
elsif($FORM{'show'} eq "addvote") { &addvote; }
else {

print "Error: Action Undefined!";

}

In the code above, you see that I used an if/else statement that executes a function (subroutine) based on the value of $FORM{'show'} (which we learned how to define). Simple enough: we call the appropriately named corresponding subroutine, and that's what we'll work on next.

Voting

What information do we want to display on the "vote" page? It's best to plan out an HTML page and transfer the code over to the CGI script subroutine than to "hard-code" it cold (unless of course, you know your HTML pretty well).

Let's begin by figuring out what we want on the voting page - the poll question and topics. You may be familiar with a format like:

What is the most annoying aspect of a website/page?
__ Long Loading Times
__ Error Messages
__ Pop-up Advertisements
__ Too Many Advertisements in General
__ Lack of Content - Under Construction
__ Broken Links

which can appear in a paper or email survey, but with a web page, you can make it a little more interactive:

What is the most annoying aspect of a website?
Long Loading Times
Error Messages
Pop-up Advertisements
Too Many Advertisements in General
Lack of Content - Under Construction
Broken Links

Notice that you can only select one choice (inherent property of these radio buttons). Also check out the source code:

<FORM Action="poll.cgi" Method=post>
<B>What is the most annoying aspect of a website?</B><BR>
<INPUT type=radio name=choice value=1> Long Loading Times<BR>
<INPUT type=radio name=choice value=2> Error Messages<BR>
<INPUT type=radio name=choice value=3> Pop-up Advertisements<BR>
<INPUT type=radio name=choice value=4> Too Many Advertisements in General<BR>
<INPUT type=radio name=choice value=5> Lack of Content - Under Construction<BR>
<INPUT type=radio name=choice value=6> Broken Links<BR>
<INPUT type=submit value="Vote">
</FORM>

We will use choice numbers to identify which choice is which (easier to match up than the entire choice phrase - "Error Messages," "Broken Links" etc - itself). To make it easier to edit, we will want to store the poll's information in separate files. To make it easiest to work with, we should require 1 file for the poll topic, another file for the choices and the third(last) for saving IP addresses (making sure that the voter only votes once).

In our example, we will only be using very basic formatting so as to show you how the poll script interacts with files and user input.

sub vote {

#We want this subroutine to print out an HTML page with
#the poll question, and all its choices. The poll data is stored
#in 3 different files:
#(1) poll.txt - stores the survey/poll question
#(2) choices.txt - stores the choices and current number of votes
#(3) ip.txt - stores the ip addresses of those who have already voted
#Routine: Get the data from the file, put the data into variables
#In this example, each piece of data is separated by a ( | ) line

#Since there is only one line of text, it should be fairly safe to assume
#that the line can be copied over to a scalar variable ($polldata).
open(POLL, "poll.txt");
   $polldata = <POLL>;
close(POLL);

($question,$totalvotes) = split(/\|/,$polldata);

#Each line in the choice file will contain 3 pieces of information:
#1. the choice
#2. the number of the choice (1,2,3, etc for first, second, third etc)
#3. the number of current votes for each choice
open(POLL,"choices.txt");
   @polldata = <POLL>;
close(POLL);

print "<form action=\"poll.cgi\" method=post>\n";

#The following will print out each poll choice.
foreach $choice(@polldata) {
   chomp($choice);
   ($pollchoice,$number,$votes) = split(/\|/,$choice);
   print "<input type=radio name=\"choice\" value=\"$number\"> $pollchoice<br>\n";
}

print "<input type=submit value=\"Vote\">\n";
print "</form>";

}

Results Display

What should the results page show? The current poll voting statistics. You don't have to make your voters wait to see the results (that may or may not be a total turn-off). Every time someone visits http://www.yoursite.com/poll.cgi?show=results, you simply tell the script to check up on the current statistics and print those numbers (and all related information) on a "dynamically generated" page through the CGI script.

What do you need to do? The general idea is to get the data (as we did in the vote subroutine) and to print it out with the current number of votes (the one thing we didn't do in the vote subroutine). A very simple version might look like:

sub results {

open(POLL, "poll.txt");
  $question = <POLL>;
close(POLL);

print "$question<p>\n";

open(POLL,"choices.txt");
  @polldata = <POLL>;
close(POLL);

foreach $line(@polldata) {
  chomp($line);
  ($choice, $number, $votes) = split(/\|/, $line);
  print "$choice ($votes)<br>\n";
}

}

Click here to see the results: poll.cgi?show=results

Notice that it simply prints out the poll/survey question, its choices and the number of votes for each choice. What about those cool bars that you see at many professional polling sites? Well, now that we've gone through (and hopefully mastered) the basic output format for the results, we'll be add this feature in.

What we do know: the number of votes for each choice. This is what we'll use to convert to bar widths (which depend on the number of votes there are to make them relative to each other). What we want to do is convert it to a percentage scale of up to 100.

That would mean finding the total number of votes (first adding all the votes for each choice together) so that:

$percentwidth = 100 x    $votes  
$totalvotes

But remembering math, we have to make sure that $totalvotes does NOT equal zero - which would make the fraction undefined, resulting in an script error. (Hint: using an if/else loop works)

if ($totalvotes) { $percentwidth = 100 * int($votes/$totalvotes); }
else { $percentwidth = 0; }

Now we have our width percentage (an integer between 0 and 100). You can adjust that as much as you need; we will be doubling the amount of that width percentage in our example just so that it will fill up enough of the page: $width = $percentwidth * 2;

We can now use this with an image to create the bars. Each bar would be an image with a tag

Image:
This is by far, the less complicated way to create a bar :) For this you should probably choose an absolute width - something simple like: $width = $percentwidth * 2, however, you should force this to be an integer value by casting: $width = int($percentwidth * 2);

Then when outputting (in the foreach loop) we would add an image and specify the $width to be the image's width:

print "$choice <img src=\"bar.gif\" width=\"$width\"> ($votes votes)";

To make these bars align together, we can print them out in a table; back to the loop:

print "<table width=80%>\n";
foreach $line(@polldata) {
  chomp($line);
  ($choice, $number, $votes) = split(/\|/, $line);
  print "<tr><td>$choice</td><td><img src=\"bar.gif\" width=\"$width\"> ($votes votes)</td></tr>";
}

print "</table>\n";

Table:
To do this, you must first specify the total width of the table in the <TABLE> tag (see the Tables HTML tutorial for further information). The table should have at least 2 columns for:

Poll/Survey Choice Vote Percentage Bar

Out of the 100% of this table with a total width of 250 (just a width I chose - you can choose any that fits your site best), let's say that the number of votes for this choice turns out to be 70%. The percentage width would be 70.

-----100% = WIDTH of Table-----
70% -add bar color N/A

You would want to make a mini table like the second row of the table shown above so that we have a nested table for each Poll/Survey choice:

Poll/Survey Choice
70% -add bar color N/A

Back to the foreach loop:

print "<table width=80%>\n";

foreach $line(@polldata) {
  chomp($line);
  ($choice, $number, $votes) = split(/\|/, $line);
  print "<tr><td>$choice</td><td> <!-- create mini table -->";
  print "<table width=150 cellspacing=0 cellpadding=0>\n";
  print "<tr><td width=\"$width\"><img src=\"/1x1.gif\"></td></tr></table> <!-- end mini table -->";

  print "<tr></td></tr>";
}

print "</table>\n";

Adding Your Vote

What happens when someone submits their choice to your poll? When he/she hits the 'Vote' button, the form (whose ACTION attribute is "poll.cgi") calls the script (poll.cgi) and passes the form's information - the number of the choice you chose, and a hidden value <INPUT TYPE=hidden NAME=show VALUE=addvote> - to it. As long as this information is given, you're all set.

Once the script gets the information, you just have to update one file: choices.txt, which holds the number of votes for each choice. Let's give it a run through:

sub addvote {

#An arbitrary checking feature that makes sure that
#a person doesn't spam the poll by voting more than once:
#Open up your IP address log file ip.txt and save that information
#in an array. Then go through each array entry to make sure
#that the current voter's IP address does not appear in this list.
open(IP,"ip.txt");
  @ips = <IP>;
close(IP);

foreach $ipaddress(@ips) {
  chomp($ipaddress);
  if ($ipaddress eq $ENV{'REMOTE_ADDR'}) { print "You have already voted! Please vote only once.\"; exit; }
}

#If the ip address $ipaddress had matched the IP address
#stored in the environment variable $ENV{'REMOTE_ADDR'}
#then the program would have exited (stopped execution).
#Otherwise, it will finish the loop and continue with the following:

#Get the original information
open(POLL,"choices.txt");
  @polldata = <POLL>;
close(POLL);

#Then rewrite the file with the poll data
#making sure to increment the vote count at the right choice

$totalvotes = 0;
open(POLL,">choices.txt");
  foreach $choice(@polldata) {
    chomp($choice);
    ($thechoice, $number,$votes) = split(/\|/, $choice);
    if($number eq $FORM{'choice'}) {
       $votes++;
    }
    print POLL "$thechoice|$number|$votes\n";
  }

#Once you are done with that, you'll want to show the voter
#the current results - so just run the result subroutine now:
&results;

}

Vote on This Poll
See the Current Results

If you got through this case study fine, you might want to venture off into some more advanced features. Nevertheless, stay tuned for another installment when we talk about adding a web-based editing interface, allowing user input and voters to add their own choices to the poll!

Last Updated August 3, 1999


Perl Case Study - Password CGI

©1999 Team 26297 "Ad Infinitum Web." All rights reserved. Any reproduction of this document for commercial or redistribution purposes without the permission of the author is forbidden.