Skip to content

AJAX Poll

This example demonstrates creating a simple poll where user votes are submitted and results are displayed asynchronously using AJAX (specifically, the modern fetch API) without requiring a full page reload.

Conceptual Poll Interface:

<div id="poll-container">
<h3>Do you enjoy learning modern PHP and AJAX?</h3>
<form id="poll-form">
<label>
<input type="radio" name="vote" value="yes"> Yes
</label>
<br>
<label>
<input type="radio" name="vote" value="no"> No
</label>
<br><br>
<button type="button" id="submit-vote">Submit Vote</button>
</form>
<div id="poll-results" style="margin-top: 15px;"></div>
</div>

Example Explained - The HTML & JavaScript (using fetch)

Section titled “Example Explained - The HTML & JavaScript (using fetch)”

When the user selects an option and clicks the button, JavaScript captures the vote and sends it to a PHP backend script using the fetch API. The response (containing the updated poll results) is then used to update the content of the <div id="poll-results">.

<!DOCTYPE html>
<html>
<head>
<title>AJAX Poll</title>
<style>
#poll-results table { border-collapse: collapse; margin-top: 10px; }
#poll-results td { padding: 5px; border: 1px solid #ccc; }
.poll-bar { background-color: lightblue; height: 20px; display: inline-block; vertical-align: middle; }
.error { color: red; margin-top: 10px; }
</style>
</head>
<body>
<div id="poll-container">
<h3>Do you enjoy learning modern PHP and AJAX?</h3>
<form id="poll-form">
<label>
<input type="radio" name="vote" value="yes" required> Yes
</label>
<br>
<label>
<input type="radio" name="vote" value="no"> No
</label>
<br><br>
<button type="button" id="submit-vote">Submit Vote</button>
</form>
<div id="poll-results" style="margin-top: 15px;"></div>
<div id="poll-error" class="error"></div>
</div>
<script>
document.getElementById('submit-vote').addEventListener('click', function() {
const form = document.getElementById('poll-form');
const resultsDiv = document.getElementById('poll-results');
const errorDiv = document.getElementById('poll-error');
const selectedOption = form.querySelector('input[name="vote"]:checked');
errorDiv.textContent = ''; // Clear previous errors
resultsDiv.innerHTML = 'Voting...'; // Provide feedback
if (!selectedOption) {
errorDiv.textContent = 'Please select an option.';
resultsDiv.innerHTML = '';
return;
}
const voteValue = selectedOption.value;
const formData = new FormData();
formData.append('vote', voteValue);
// Send the vote to the PHP backend using fetch (POST method recommended for state changes)
fetch('poll_vote.php', {
method: 'POST',
body: formData
})
.then(response => {
if (!response.ok) {
// Handle HTTP errors (like 404, 500)
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.text(); // Get the response body as text (HTML)
})
.then(html => {
// Update the results area with the HTML received from PHP
resultsDiv.innerHTML = html;
})
.catch(error => {
// Handle fetch errors (network issues, HTTP errors)
console.error('Fetch Error:', error);
errorDiv.textContent = 'Error submitting vote. Please try again. (' + error.message + ')';
resultsDiv.innerHTML = ''; // Clear 'Voting...' message
});
});
</script>
</body>
</html>

JavaScript Breakdown:

  • An event listener waits for the button click.
  • It finds the selected radio button’s value.
  • A FormData object is created to hold the vote value.
  • fetch() sends a POST request to poll_vote.php with the form data.
  • .then() handles the successful response: checks if the HTTP status is OK, then reads the response body as text (expecting HTML from the PHP script).
  • The received HTML is inserted into the #poll-results div.
  • .catch() handles errors during the fetch process (e.g., network down, server error).
  • Basic validation and user feedback are included.

This PHP script receives the vote, updates the stored results (using a simple text file in this example), and then generates the HTML to display the current results.

Note on File Storage: Using a plain text file for storing results is simple but not suitable for high-traffic sites due to potential race conditions (multiple users writing simultaneously). A database (like MySQL or PostgreSQL) is the standard, more robust solution. This example uses file locking (LOCK_EX) as a basic measure against race conditions.

<?php
declare(strict_types=1);
$filename = "poll_result.txt";
$yes = 0;
$no = 0;
// --- Step 1: Read current results safely ---
$fileHandle = fopen($filename, 'c+'); // Open for read/write, create if not exists
if (!$fileHandle) {
http_response_code(500);
echo "Error: Cannot open data file.";
exit;
}
// Attempt to get an exclusive lock (prevents simultaneous writes)
if (flock($fileHandle, LOCK_EX)) {
$content = fread($fileHandle, (filesize($filename) > 0) ? filesize($filename) : 1); // Read existing content
if ($content) {
$parts = explode("||", $content);
if (count($parts) === 2 && is_numeric($parts[0]) && is_numeric($parts[1])) {
$yes = (int)$parts[0];
$no = (int)$parts[1];
}
}
// --- Step 2: Process the vote (if any) ---
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['vote'])) {
$vote = $_POST['vote']; // Basic: consider more validation
if ($vote === 'yes') {
$yes++;
} elseif ($vote === 'no') {
$no++;
}
// --- Step 3: Write updated results back ---
ftruncate($fileHandle, 0); // Clear the file content
rewind($fileHandle); // Move pointer to the beginning
fwrite($fileHandle, $yes . "||" . $no);
}
flock($fileHandle, LOCK_UN); // Release the lock
} else {
fclose($fileHandle);
http_response_code(503); // Service Unavailable (couldn't get lock)
echo "Error: Could not lock data file. Please try again.";
exit;
}
fclose($fileHandle);
// --- Step 4: Calculate percentages and output HTML results ---
$total = $yes + $no;
$yesPercent = ($total > 0) ? round(($yes / $total) * 100) : 0;
$noPercent = ($total > 0) ? round(($no / $total) * 100) : 0;
// Ensure percentages add up correctly after rounding (adjust largest if needed)
if ($total > 0 && ($yesPercent + $noPercent) !== 100) {
if ($yesPercent > $noPercent) $yesPercent = 100 - $noPercent;
else $noPercent = 100 - $yesPercent;
}
?>
<h2>Result:</h2>
<table>
<tr>
<td>Yes:</td>
<td>
<span class="poll-bar" style="width:<?php echo $yesPercent * 2; // Scale width for display ?>px;"></span>
<?php echo $yesPercent; ?>% (<?php echo $yes; ?> votes)
</td>
</tr>
<tr>
<td>No:</td>
<td>
<span class="poll-bar" style="width:<?php echo $noPercent * 2; ?>px;"></span>
<?php echo $noPercent; ?>% (<?php echo $no; ?> votes)
</td>
</tr>
</table>

PHP Script Logic:

  1. Open/Lock File: Opens poll_result.txt for reading and writing (c+), creating it if necessary. Attempts to acquire an exclusive lock (flock) to prevent race conditions.
  2. Read Data: Reads the current counts from the file (e.g., 10||5). Handles empty or invalid file content.
  3. Process Vote: Checks if the request is POST and if a vote value (‘yes’ or ‘no’) is present. Increments the corresponding counter.
  4. Write Data: Truncates the file, rewinds the pointer, and writes the updated counts back (e.g., 11||5).
  5. Unlock/Close File: Releases the lock (flock) and closes the file handle.
  6. Calculate & Output: Calculates percentages and generates the HTML table containing the results, including simple visual bars. This HTML is sent back as the response to the fetch request.

This simple text file stores the vote counts, separated by ||.

Example content:

25||12

This represents 25 ‘Yes’ votes and 12 ‘No’ votes.

Important: Ensure the web server process (e.g., www-data, apache) has write permissions on this poll_result.txt file and the directory it resides in. Do not grant overly broad permissions (like 777).