PHP: Hot Pages: A Poll: Extra credit

  1. Visitor Sessions
  2. A Poll

Templating and programming

This is how PHP programming tends to work best: create functionality in a separate file, and then use that functionality to insert dynamic data into your pages, and to display or not display some HTML. In general, you’ll want to keep the number of lines of code on your pages down to one at a time. If you’re doing more than that, you’re doing programming, and it should—usually—go in the include file.

Usually; you’ll need to play it by ear, but you’ll rarely go wrong if you follow that guideline. This is sometimes called “templating”; your HTML is a “template” upon which you stamp out pages using PHP code. One HTML file can produce multiple pages.

Multiple questions

We can put two questions on the page, and treat them separately. How about asking for the best ever president of the United States of America? We can get a list of the presidents easily enough from several places on the net. I’ve provided presidents.txt in the resources file. We can convert that list into an array with PHP’s file function, the same one we used to read the vote tally.

$fictions = new VoteCounter('character', $imaginaries);

$fictions->save();

$presidents = file('/home/USERNAME/includes/presidents.txt', FILE_IGNORE_NEW_LINES);

$election = new VoteCounter('president', $presidents);

$election->save();

$ballots = array($fictions, $election);

That renames the fictional character poll to be “$fictions” instead of “$vote”. It sets up the presidential election poll. And it creates a list of the polls in an array called $ballots.

We can now use the same code that we used to display the imaginary character poll to show both polls:

<title>Latest Polls</title>

<h1>Current Polls</h1>

<?php FOREACH ($ballots as $vote):?>

<hr />

<h2>Poll for <?php echo $vote->fieldName; ?></h2>

<?php IF ($vote->answered()):?>

<p>Thank you for voting for <?php echo $vote->name(); ?>!</p>

<p>The current results are:</p>

<table>

<?php FOREACH ($vote->counts() as $choice=>$count):?>

<tr>

<th><?php echo $vote->choiceName($choice); ?></th>

<td><?php echo $count; ?></td>

</tr>

<?php ENDFOREACH;?>

</table>

<?php ELSE:?>

<form method="post" action="poll.php">

<p>

Please choose a <?php echo $vote->fieldName; ?>:

<?php echo $vote->formSelect(); ?>

<input type="submit" value="Submit your answer" />

</p>

</form>

<?php ENDIF;?>

<?php ENDFOREACH; ?>

We only change a little bit of code in the page’s HTML. Basically, we change the title and the level 1 headline from being about our imaginary character poll to be a more generic “latest polls” and “current polls”. Then we put a foreach loop around the rest of the code, adding a level 2 headline for each poll, and changing the poll-specific text to reference the field name.

When you load the page now, you should see the results for the “imaginary character” poll, and the opportunity to vote in the “president” poll.

image 17

Make your vote, and the page will display the results for both polls. You can put as many polls on the page as you wish, and also remove them when you’re tired of seeing them.

image 18

One thing you might notice is that the choice of field name isn’t always going to be a good choice for the title of the poll. Delete your server’s cookie so that you can see the polls again. Let’s add an option for titling a poll.

In fields.phpi, add a new property for the poll title, and two methods:

protected $title;

public function setTitle($title) {

$this->title = $title;

}

public function title() {

if ($this->title) {

return $this->title;

} else {

return ucfirst($this->fieldName);

}

}

One method sets the title, the other returns the title; if no custom title was created, it will return the field name, but capitalized.

And then set the title for the imaginary character poll:

$fictions = new VoteCounter('character', $imaginaries);

$fictions->setTitle('Your favorite imaginary imaginary character');

$fictions->save();

Replace the level 2 headline:

<h2><?php echo $vote->title(); ?></h2>

The character poll will get a detailed title; but the presidential poll will get the field name, capitalized.

image 19

That’s it! I’m sure you can imagine a lot more options that you might set for each poll. In this case we made the property protected so that people wouldn’t accidentally just display the title property, not knowing that some polls won’t have custom titles. But if you know that a property will only be displayed when it exists, you can leave out both the method to set and the method to display, and access the property directly. Make the property “public” instead of “protected”, and then you can set and display it directly.

$ballot->state = 'Michigan';

echo $ballot->state;

Which method you use, accessing the property directly or accessing it through a method, is up to you and can depend on the needs of the moment. I tend to err toward using methods, because it makes it easier to “intercept” a request to display a property later and provide better options.

Graphical results

We can combine PHP with inline styles to display the results graphically. The $count variable that we display during the foreach loop can just as well go inside of a style attribute.

<table class="graph">

<?php FOREACH ($vote->counts() as $choice=>$count):?>

<tr>

<th><?php echo $vote->choiceName($choice); ?></th>

<td>

<p style="width: <?php echo $count*6; ?>em;">

<?php echo $count; ?>

</p>

</td>

</tr>

<?php ENDFOREACH;?>

</table>

Add some extra styling to the <style> tag in the page’s <head>:

table.graph p {

text-align: right;

margin: 0;

padding: .1em;

background-color: red;

}

It will now display the results as red bars, with longer bars for bigger vote tallies:

image 20

But watch what happens when we display a poll with a lot of results:

image 21

The imaginary character poll has received a lot more total votes than the presidential poll. But if we reduce the multiplier to match the number of votes in the character poll, the presidential poll’s graphs will be too small.

We need a means to calculate a percentage based on the highest vote-getter in that poll as the baseline largest bar. What we need is a smart enough set of results to know the maximum and how much to offset each individual tally to adjust the size of each bar based on the maximum. When we start talking about needing “smarter” variables, we usually need a new class. Let’s create a Results class.

class Results {

public $votes;

public $maximum;

public $multiplier;

public function __construct($votefile) {

$votes = file($votefile, FILE_IGNORE_NEW_LINES);

$votecounts = array_count_values($votes);

arsort($votecounts);

$this->votes = $votecounts;

$this->maximum = max($votecounts);

$this->multiplier = 100/$this->maximum;

}

}

Copy some of the code from VoteCounter’s counts method. The counts method will now return a Results object:

public function counts() {

return new Results($this->filePath);

}

And, now we can slightly modify our graph to have normalized widths:

<table class="graph">

<?php $results = $vote->counts(); ?>

<?php FOREACH ($results->votes as $choice=>$count):?>

<tr>

<th><?php echo $vote->choiceName($choice); ?></th>

<td>

<p style="width: <?php echo $count*$results->multiplier; ?>%;">

<?php echo $count; ?>

</p>

</td>

</tr>

<?php ENDFOREACH;?>

</table>

Because we’re now using percentage widths, we need to make sure the table itself has a width to be a percentage of. Add this to the <style> tag:

table.graph {

width: 90%;

}

table.graph td {

width: 70%;

}

image 22

Notice that the longest bar in both cases is the same size. Each is at 100% of the width of the table cell.

Poll colors

Currently, both polls are the same color. Why not make them a different color to help differentiate them? For that matter, solid red is pretty bright. Let’s lighten them up at the same time. Add a default color to the property list for VoteCounter:

public $color = 'tomato';

And now change the color of the imaginary character poll:

$fictions = new VoteCounter('character', $imaginaries);

$fictions->setTitle('Your favorite imaginary imaginary character');

$fictions->color = 'lightblue';

$fictions->save();

Remove the hard-coded background color from the <style> tag and put it into the paragraph tag:

<p style="

background-color: <?php echo $vote->color; ?>;

width: <?php echo $count*$results->multiplier; ?>%;

">

image 23

You now have a very simple means of accepting, storing, and displaying poll results on a web page.

  1. Visitor Sessions
  2. A Poll