Development and Managed Hosting
ANEXIA
AUG
23
2017

Improve website performance with PJAX

Written on August 23, 2017 by Manuel Wutte

Many of you will probably be asking the same question: PJAX? Shouldn’t it be called AJAX?

The answer to this is a resounding “no”; PJAX is a more specific type of data organization that is based on AJAX.
I would like to show you here what it really has going for it, and how you can benefit from this technology in practical terms. To do this, you need at least an average knowledge of JavaScript or jQuery. Even if you don’t have much experience with jQuery, it’s not a problem: there’s a complete example further below, which you can use as a starting point for your own project.

 

PJAX in everyday life

When you use Facebook, Twitter or LinkedIn, you are sure to have noticed that you can click on as many links and buttons as you like, but the page never completely reloads. If you are aware of this, then you already understand what PJAX technology is all about – exchanging and reloading content within certain defined areas of a website. This means that the website is loaded only once when you access it for the first time. If you click somewhere on the page, only the content is exchanged with the content of the actual target page, and thus the URL in the browser line changes as a matter of course.

The advantage in this lies in the fact that individual pages of a website can be loaded much faster, since you don’t have to constantly load all of the (in part, external) resources every time. The experience is much quieter and more fluid for the user him/herself, not least because there is no need to reload, and therefore the flickering associated with reloading a new page is no longer an issue.

 

Integrating PJAX into a website

Integrating PJAX technology is relatively easy. Even older websites can be upgraded with it. Everything that is needed can be solved with just a few Javascript or jQuery functions.

 

Step 1: Events delegation

Javascript-based event listeners normally function in such a way that they define a function or a behavior pattern, which determines what is supposed to happen with the element in question (e.g., a button) when a specific trigger occurs. This function is normally bound directly to the respective element. This occurs during a page view, when the browser renders all of the elements in sequence, and having completed this, binds the necessary event listeners to these elements.

In this case, since elements are reloaded in PJAX (or in part, in AJAX as well), and therefore are overwritten, all event listeners (associated with the old element) are likewise overwritten. The browser itself does not re-initialize these new elements. As a result, the old event listeners are inoperative when they are loaded.

Therefore we must ensure that the elements in question know that there are indeed one or more event listeners. To this end, the event listener should not be attached directly to the element, but instead should be located outside of said element. This process is known as “delegating”.

In order to demonstrate this in practice, here is an example of a regular jQuery event listener (triggered by a click):

$("#my_button").click(function(){
	alert('Click event triggered!');
});

As you can see here, the first element in question (“#my_button“) is selected and an event listener is added to it.

In the case of a delegated listener, on the other hand, this looks very different:

$("body").delegate("#my_button", "click", function(){
	alert('Click event triggered!');
});

In this case, instead of selecting the target element (“#my_button“), we first select another element that is guaranteed not to change (here, “body“). Here, we would say that this other element is “holding” the event for another element.

Now we indicate within the delegate() function which target element we would like to activate (“#my_button”), and to which trigger it should respond (here: “click“).

In practice, the advantage in this is that we can exchange and replace elements, and the event listeners will nonetheless continue to function since they are located elsewhere.

 

Step 2: Redirecting links via Javascript

If you were to click on a link on the website, the browser would load this link normally by performing a complete reload of the website (if it is an internal link).

But the purpose of PJAX is precisely to prevent this behavior and to dynamically exchange content or reload it as needed.

To do this, we add a click event listener to the link, which blocks the default behavior so that we can now define our own link handler:

 

$('body').delegate('a', 'click', function (e) {
	// prevent default behaviour of the link
	e.preventDefault();

	// TODO: handle custom link behaviour
});

The problem with this event listener is that this would block ALL links, however our own link handler should handle only the internal links.

I have therefore gone ahead and prepared the following event handler for you:

 

$('body').delegate('a', 'click', function (e) {
    var link = e.currentTarget;
    var isError = false;

    // Middle click, cmd click, and ctrl click should open links in a new tab as normal.
    if (e.which > 1 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) {
        isError = true;
    }

    // Ignore cross origin links
    if (location.protocol !== link.protocol || location.hostname !== link.hostname) {
        isError = true;
    }

    // Ignore case when a hash is being tacked on the current URL
    if (link.href.indexOf('#') > -1 && stripHash(link) == stripHash(location)) {
        isError = true;
    }

    // Ignore event with default prevented
    if (e.isDefaultPrevented()) {
        isError = true;
    }

    // Check if link target is "_blank"
    if ($(this).attr('target') == '_blank') {
        isError = true;
    }


    // Perform link click
    if (!$(this).hasClass('dropdown-toggle') &&
        !$(this).data('toggle') &&
        !isError) {

        // We define the DOM element "main" as primary container for our replaced content
        var targetSelector = 'main';
        var $target = $(targetSelector);
        var href = $(this).attr('href');

        e.stopPropagation();
        e.preventDefault();

        if (!$target.length) {
            // target element does not exist
            return;
        }

        $.get(href, function (data, status) {
            if (status === 'success') {
                var parser = new DOMParser();
                var $data = $(parser.parseFromString(data, 'text/html'));
                var $newTarget = $data.find(targetSelector);
                var newTargetHtml = $newTarget.html();

                // Set HTML content from response
                $target.html( newTargetHtml );
            }
            else {
                alert("Unexpected response code: " + status);
            }
        });
    }
});

To start with, we check step by step check to see whether the link in question should be handled by our click handler or not.

If this is indeed the case, we must now determine which area should be exchanged with new content. In this case, the area in question is the <main/> block within the HTML body (defined in the variable, targetSelector).

The actual URL of the link can subsequently be loaded via AJAX (by means of $.get()).

If we were now able to load the URL via AJAX, we tell jQuery explicitly that the response should be interpreted as HTML using the DOMParser().

We now do a search for our desired block (in this case, <main/> using the targetSelector variable from above) in this parsed HTML, and replace this block in the old HTML with the new block by simply overwriting its content with the content from our parsed and filtered HTML using the html() function.

 

Step 3: Transferring forms in the background

The same behavior as seen in the links is now transferred to the forms.

The only difference in this case is that, in principle, we only need to replace a small subarea when sending the form.

The easiest way to do this is to place a container around the actual form.

<div id="my-form-container">
    <form action="" method="POST" data-refresh-target="#my-form-container">
            <input type="text" name="demo">
            
            <button type="submit">Send message</button>
    </form>
</div>

In the case of the form itself, we now use a data attribute (referred to here as a “data-refresh-target”) to specify in which container we would like to load the response after sending.

But now to our submit handler:

$('body').delegate('form', 'submit', function (e) {
    var $this = $(this);
    var targetSelector = $this.data('refresh-target');
    var $target = $(targetSelector);

    if (!$target.length) {
        // target element does not exist
        return;
    }

    e.stopPropagation();
    e.preventDefault();


    var rqMethod = 'GET';
    var attrRqMethod = $(this).attr('method');
    var url = $this.attr('action');

    if (attrRqMethod) {
        rqMethod = attrRqMethod;
    }

    $.ajax({
        //mimeType: 'text/html; charset=utf-8', // ! Need set mimeType only when run from local file
        url: url,
        data: $(this).serialize(),
        type: rqMethod,
        cache: false,
        success: function (data, status, XHR) {

            // Set HTML content from response
            $target.html( data );

        },
        dataType: 'html'
    });
});

Within this handler, we now use the data() function to read the target container (via the data attribute, “refresh-target“, which we have already previously defined in the form element), in which we would later like to load the response.

Next we check whether the form is to be sent via GET or via POST. We get this information directly from the form element by means of the “method” attribute.
If this is not defined, for example, a form is always sent via GET.

In order to also transfer the content of the form fields, we serialize these fields using the serialize() function and transfer this as a “data” parameter of the ajax() function.

Instead of the ajax()function, one might just as easily work with $.get() or $.post(). The advantage in the former case is that we can control the request type (GET or POST) directly using a single attribute (“type“).

Just like the link handler above, we simply replace the contents of the old container with the HTML that is returned.
In this example, we assume that the only HTML returned, is the HTML that is inserted 1:1 into the container, thus without the remaining layout.

 

Complete example (demo)

We have now gotten through all of the measures needed for asynchronous data handling.

Nevertheless, since the whole thing is initially somewhat complicated, I have put together a small example for you (based on the CodeIgniter framework):

Download the demo

Copy the entire demo to a webserver (e.g. XAMPP, LAMP, etc.) and you will be able to click through the dummy.

The scripts in this example are already somewhat extended; for example, there is a fading effect when replacing the content, so that the whole thing makes an even quieter impression.

Don’t worry, there are comments throughout the entire demo. You should be able to find your way through it without significant problems.

 

Conclusion

PJAX is used first and foremost to make the user’s experience more pleasant by not distracting the user with constant page reloads.

Because external resources only have to be loaded once, this loading is unnecessary for further interactions, which in turn has a positive effect on the loading time and thus on the performance in general.

It is possible to further exhaust PJAX technology by specifying whether or not one wants the layout around the actual HTML by sending this information as a header variable or additional GET/POST variable, for example.
You could then dynamically decide on the server side whether to rename the entire markup, or just a subarea of it.

In the example above, I have already shown to you in a much simplified way that normal links each load the entire markup, including layout, while in the case of forms, only the relevant HTML is actually loaded.

There are numerous ways to improve the performance of a website, and this method is just one of many.