Toggling a form with AJAX and PHP

My selfie

James Turner,

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.

A rough sketch of the subscription widget
Figure 1: The subscription widget has two states, subscribed and unsubscribed, for each logged-in user.

Skip to navigation

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.

Skip to navigation

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.

Skip to navigation

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.

Skip to navigation

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.

Skip to navigation

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 echoing 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

Skip to navigation

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
	[...]

Skip to navigation

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:

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.

A diagram of the data flow between PHP and JavaScript files.
Figure 2: The flow of data from the form view, to AJAX, to the data handler, and back again.

So, what do you think of this recipe? How would you have done it differently? Comment below!

Skip to navigation