Toggling a form with AJAX and PHP
Tags: HTML JavaScript PHP
Recently, I discovered the joy of AJAX. My task was to add a widget to a client's webpage that enables users to subscribe and unsubscribe to new comments on a blog post without having to reload the whole page (Figure 1). The solution involved a combination of HTML, PHP and jQuery. In case I should need to do something like this again, here's a recipe of how I did it.
Data structure
In MySQL, I have a database table containing blog posts, and another table containing users. This recipe involves setting up a new table, blog_views
to link them. This intermediate table contains a blog post ID, a user ID and a field, is_subscribed
which indicates whether a user wants to receive emails when someone comments to the blog post. A record is added to the table when a user first views a blog post, and the subscription status defaults to not subscribed. At the end of the page, our widget enables users to subscribe.
Basic HTML
The subscription form appears at the bottom of a blog post, below the comments section. The form is very sparse; it consists of a label and button. That's all I need. Since I won't be triggering the submit
action, which would reload the entire page, I could probably use a div
as the container. However, the form
element is more semantic, and I'll just leave out the action
and method
attributes. Here's the markup for the initial unsubscribed state:
<form class="subscribeForm">
<label for="subscribeBtn">Would you like to receive an email when
someone comments on this blog post?</label>
<br>
<button
type="submit"
id="subscribeBtn"
class="btn btn-success">Email me</button>
</form>
And the markup for the subscribed state:
<form class="subscribeForm">
<label for="subscribeBtn">You are currently receiving an email when
someone comments on this blog post. Would you like to stop receiving
emails?</label>
<br>
<button
type="submit"
id="subscribeBtn"
class="btn btn-danger">Stop emailing me</button>
</form>
They’re basically the same form, just with different text. What I’d like to do is to add this text in dynamically, depending on the user’s current subscribed state.
Adding form text with a PHP function
As well as populating the form when the page loads, I’ll need to update it after my AJAX response. Rather than hard-code the text in the form, I’ll pull it in dynamically. I’ll write a PHP function which accepts the subscription status (true or false) as a parameter, and returns an associative array containing the text for the form. I’m going to say that the isSubscribed
function is in a class called User
, and returns whether the currently logged-in user is subscribed, as recorded in the blog_views
table. Here’s the abstracted HTML:
/* blog.php */
<?php
$isSubscribed = isSubscribed( $user->id );
$formText = getSubscribeFormText( $isSubscribed );
?>
<!-- blog post and comments appear here -->
<form class="subscribeForm">
<label
id="subscribeLabel"
for="subscribeBtn"><?php $formText['label'] ?>
</label>
<br>
<button
type="submit"
id="subscribeBtn"
class="btn <?php $formText['btnClass'] ?>">
<?php $formText['btnText'] ?></button>
<input
type="hidden"
id="isSubscribed"
value="<?php echo $isSubscribed; ?>">
<input
type="hidden"
id="userId"
value="<?php echo $user->id; ?>">
</form>
And the function which provides the form text:
/* functions.php */
function getSubscribeFormText( isSubscribed = false ){
$formText = array();
$btnClasses = array( 'btn-success', 'btn-danger' );
if( true == isSubscribed ){
$formText['label'] = 'You are currently receiving an email when ' .
'someone comments on this blog post. Would you like to stop ' .
'receiving emails?';
$formText['btnClass'] = $btnClasses[1];
$formText['btnClassOld'] = $btnClasses[0];
$formText['btnText'] = 'Stop emailing me';
} else {
$formText['label'] = 'Would you like to receive an email when ' .
'someone comments on this blog post?';
$formText['btnClass'] = $btnClasses[0];
$formText['btnClassOld'] = $btnClasses[1];
$formText['btnText'] = 'Email me';
}
return $formText;
}
Later, I’ll not only want to add a class to the form, I’ll want to remove the old one as well. That’s why this function also returns the btnClassOld
value. It’ll be redundant when the form first loads.
The form text above is hard-coded into PHP, but in the future, I might want to give admin-level users control of the text that appears on the form. I could do this by adding an option in my CMS database which contains the text to display, and a page in the dashboard which allows an admin to edit it. That’s a level of complexity that I don’t want to worry about here, however.
Calling AJAX
What happens when the user clicks the button? It will call a jQuery function:
/* script.js */
$(function(){
var btn = $('subscribeBtn');
[...]
btn.click(function(){
[...]
});
} // end jQuery
The function called when the button is clicked contains an AJAX request. I want it to send the subscription status in a GET
request to another PHP file, ajax_subscribe.php
. GET
is the default action for an AJAX request, so there’s no need to add a type
setting. There are two hidden inputs in my form, #userId
and #isSubscribed
. I need to extract the values of both of these from the form. The user ID stays the same, but the subscription status can change, so after the button is clicked, I get the subscription status from the form and then append it and the user ID to the URL for the GET
request.
/* script.js */
$(function(){
var btn = $('#subscribeBtn');
var isSubscribedInput = $('#isSubscribed');
var userIdInput = $('userId');
var userId = userIdInput.attr('value');
[...]
btn.click(function(){
var isSubscribed = isSubscribedInput.attr('value');
$.ajax({
url: 'ajax_subscribe.php?isSubscribed=' + isSubscribed +
'&user_id=' + userId,
[...]
});
});
} // end jQuery
There’s more jQuery to come, but first, let’s see what’s happening in ajax_subscribe.php
.
Update the database and return JSON
The code in ajax_subscribe.php
has two jobs. First, it has to update the database with the user’s new subscription status. Here’s the code for that:
/* ajax_subscribe.php */
if( isset( $_GET['is_subscribed'] ) && isset( $_GET['user_id'] ) ){
$is_subscribed = $_GET['is_subscribed'];
$user_id = $_GET['user_id'];
$sql = 'UPDATE user ' .
'SET is_subscribed = ' . (1 - $is_subscribed) . ' ' .
'WHERE id_user = ' . $user_id;
$result = $conn->query($sql);
[...]
Then, if the query was successful, I want to return the updated information for the subscription form in blog.php
. This is as simple as echo
ing out the result to screen (or, rather, to our jQuery AJAX object). To make it easy to process, let’s encode it as JSON.
[...]
$result = $conn->query($sql);
if( $result ){
$output = getSubscribeFormText( 1 - $is_subscribed );
echo json_encode( $output );
} else {
throw new Exception;
};
} // end if isset
And back to jQuery we go
My AJAX request wasn’t quite finished. First, I needed to tell it what to expect back from ajax_subscribe.php
. I decided to return an array in JSON code, so we use the dataType
setting to communicate this:
/* script.js */
$(function(){
[...]
btn.click(function(){
[...]
$.ajax({
url: 'ajax_subscribe.php?is_subscribed=' + isSubscribed +
'&user_id=' + userId,
dataType: 'json',
[...]
}); // end ajax
}); // end click
} // end jQuery
If ajax_subscribe.php
gets called successfully, I want jQuery to update the subscription form with the values returned by ajax_subscribe.php
. First, I’ll need to add a variable for the <label>
element. Second, I need a success
function to handle the form updates. The response
parameter passed into this success function is whatever gets output from ajax_subscribe.php
, in other words, the JSON. I should add an error
function, too, which displays an error message near the form, but I’ll just hand-wave that for now.
/* script.js */
[...]
btn.click(function(){
[...]
var label = $('#subscribeLabel');
$.ajax({
url: 'ajax_subscribe.php?is_subscribed=' + isSubscribed +
'&user_id=' + userId,
dataType: 'json',
success: function( response ) {
label.text( response.label );
btn.removeClass( response.btnClassOld )
.addClass( response.btnClass )
.text( response.btnText );
isSubscribedInput.attr( 'value',
1 - parseInt( isSubscribed ) );
} // end success
}); // end ajax
[...]
Conclusion
The story isn’t quite over. I still need to plumb in the code that sends subscribed users an email. In my case, a lot of that functionality was already set up; I just needed to add a line that triggers an existing function when a blog post is published.
I’ve also been quite lax with my testing and error-checking. Some situations which I might want to test for include:
- What if jQuery doesn’t load?
- What if
script.js
doesn’t load? - What if my CSS (containing my button styles) doesn’t load?
- What if
ajax_subscribe.php
fails to load? - What if there’s a database error, and
ajax_subscribe.php
returns an error, or nothing?
These are all eventualities that I should account for in my code.
All this seems like a lot of effort for one simple toggle. I mean, why not just reload the page? I suppose if the page contains lots of assets, such as a blog post, images, comments and so on, all coming from different servers, you don’t want to burden your users’ mobile data with a simple update to a database. That’s why using an AJAX request might be a good idea here.
Although I haven’t quite got my head around MVC structure for scripting web applications, I suspect that the separation of concerns here (view - handler - data, Figure 2) has the beginnings of this approach. Learning MVC is definitely in my sights.
So, what do you think of this recipe? How would you have done it differently? Comment below!
Skip to navigation
Comments
comments powered by Disqus