Tags:

Count post views by AJAX in WordPress without any plugin

Published
207

In the previous post, we finished counting post views for WordPress by PHP and checking the view state by using a PHP session.

For low-traffic websites, this approach works well. But PHP session memory grows as the volume of traffic does. Remember that web hosting often has quite limited memory. Therefore, this technique is unsafe for websites with heavy traffic because it can lead to memory overflow and a website crash.

Then we come to the solution: count WordPress post views by using AJAX and save the viewed article in the local storage of the browser.

What is AJAX?

AJAX stands for Asynchronous JavaScript And XML.

What can we do with AJAX?

  • Update a web page without reloading the page
  • Request data from a server – after the page has loaded
  • Receive data from a server – after the page has loaded
  • Send data to a server in the background

In this post, we will use AJAX to send the update view action to the wordpress server after the page is loaded.

Use local storage instead of session storage

As I mentioned at the beginning, if we check if the user has read the post or not by storing data in a PHP session on the server side, it might cause memory overflow and crash the website.

Then we can store this data in the browser instead. We have 2 choices: session storage and local storage. The difference between them is browser will clear session storage data when you close the browsing tab when local storage data is not.

Then I choose local storage to save the post view state, and because local storage will not be cleared automatically, so we will have to implement a technique to set the expiration time.

Starting to code

WordPress custom REST API

WordPress supports us to create custom REST endpoints. Through this, we can create a new web API to receive the “update post view” from AJAX.

Add two new functions, updatePostViewApi to handle the request from users and then increase the view count. When they submit any not-existing post id, the API will return a 404 error code.

/** Update post view by ID
 *
 */
function updatePostViewApi(WP_REST_Request $request)
{
    $postID = $request['id'];
    $post = get_post($postID);

    if (is_null($post)) {
        return new WP_REST_Response(null, 404);
    }
    setPostView($postID);
    return new WP_REST_Response(null, 200);
}

The next function’s purpose is to register a route for our new endpoint. In addition, I added a middleware for checking the post id parameter (must be a numeric value).

<span role="button" tabindex="0" data-code="add_action('rest_api_init', function () { register_rest_route('catalog', '/post_view/(?P
add_action('rest_api_init', function () {
    register_rest_route('catalog', '/post_view/(?P<id>\d+)', array(
        'methods' => 'GET',
        'callback' => 'updatePostViewApi',
        'args' => array(
            'id' => array(
                'validate_callback' => function ($param, $request, $key) {
                    return is_numeric($param);
                },
            ),
        ),
    ));
});

Don’t forget to comment out the update view line, it is no need anymore.

function displayPostView()
{
    $postID = get_the_ID();
    // setPostView($postID);
    echo getPostView($postID);
}

Frontend code

On the frontend side, create a new js file inside your theme, then create two functions to work with local storage. These two’s missions are saving and getting data with a key and a specified expiration time.

I left the expiration period by 1 day, which means if users read the article today, the post view will not be increased until they come back tomorrow (or by manually deleting the browsing data).

const postViewKey = "catalog_view_";
/**
 * 
 * @param {*string} {*number} key 
 * @param {*any} value 
 * @param {*number} ttl number of hours that item valid
 */
function setDataWithExpiry(key, value, ttl = 1) {
	const now = new Date();
	// add hours to now
	now.setTime(now.getTime() + (ttl * 60 * 60 * 1000))
	const item = {
		value: value,
		expiry: now,
	}
	localStorage.setItem(key, JSON.stringify(item));
}

/**
 * Get local storage data if ttl is valid
 * @param {*string} key 
 * @returns null if not exist or expired | object
 */
function getDataWithExpiry(key) {
	const itemStr = localStorage.getItem(key);
	// if the item doesn't exist, return null
	if (!itemStr) {
		return null;
	}
	const item = JSON.parse(itemStr);
	const now = new Date();
	// compare the expiry time of the item with the current time
	if (now.getTime() > item.expiry) {
		// If the item is expired, delete the item from storage
		// and return null
		localStorage.removeItem(key);
		return null;
	}
	return item.value;
}

After that, we need the next 3 functions to update the view, updatePostView will check whether the article is read or not, if not, send a request to update the view by using fetch API.

function updatePostView(postId){
    if(!postId || (typeof postId != 'number' && typeof postId != 'string')){
        return;
    }
    // check the post is already viewed
    const view = getDataWithExpiry(postViewKey + postId);
    if(!view){
        // call api to update view
        sendUpdateViewRequest(postId, () => setDataWithExpiry(postViewKey + postId, true));
        return;
    }
}

function sendUpdateViewRequest(postId, callback){
	fetch(`/wp-json/catalog/post_view/${postId}`)
	.then(function (response) {
		if(response.ok){
			callback();
		}
	}).catch(function (err) {
		console.warn('Something went wrong.', err);
	});
}

Next, add a listener for page loaded event, this means the action will occur when the web page’s UI appeared on the screen successfully.

document.addEventListener('DOMContentLoaded', function () {
	if(catalogPostId){
		updatePostView(catalogPostId);
	}
}, false);

In the last step, modify header.php in your theme, and add this code inside of <head> block. This will print out a variable for the js listener to detect post ID. If the current page is not a single post, it will be ignored by the function updatePostView

<span role="button" tabindex="0" data-code="<?php echo '<script>'; if(get_post_type() == 'post'){ echo 'var catalogPostId = '.get_the_ID() .';'; } else { echo 'var catalogPostId = 0;'; } echo '
<?php 
echo '<script>';
if(get_post_type() == 'post'){
    echo 'var catalogPostId = '.get_the_ID() .';';
} else {
    echo 'var catalogPostId = 0;';
}
echo '</script>';
?>

Conclusion

Hope that the content I bring about counting WordPress post views by using AJAX can help you to reduce the time developing your WordPress website.

Happy coding!