AJAX pagers for Drupal 7

One of the many great things about working at CivicActions is that we get a stipend for work we do for the community. This month I decided to use some of my stipend time to code up a proposed solution for Drupal core for dynamically loading paged content.

Why should paged content be loaded through AJAX? Well, take the example of comments. You’re viewing a page with lots of comments. You click a link to bring up the next 30 comments. You wait while the whole page refreshes.

Fine, but you’ve had to load a whole page when really all you needed was the new batch of comments. This means wasted processing time and bandwidth, both on your end and on the server’s. Hence, a prime use case for AJAX (or, more correctly, AHAH) loading–load just the new comments and replace the old ones with them.

Implementation approaches

How to implement this?

There’s two basic approaches we could take. One – and usually the one that’s tried first – is to create a special callback to be used for the paged comments when loading through AJAX. This is what’s done e.g. in the Upload module for file uploads. Regular file uploads are processed as part of the regular submission handling for forms that include file uploads. For the AJAX submission, we have a special menu item along with a corresponding special handler, upload_js().

This approach gets the job done, but it leads to code duplication. It means we have to code separately every time we want an AJAX behaviour. It also means we need to expose new menu items, meaning that

The other approach says, have just one rendering process and make it work in both regular and AJAX contexts. This second approach has some key advantages: it avoids code duplication and the security issues that can arise when we open up multiple avenues for the same functionality.

Coding a patch

Awhile back I contributed a patch to add AJAX paging to the Views module. To start off, I had another look at that patch. Sure enough, it contained some of what we’d need: a bit of Javascript to recognize pager links and attach a callback to the event of them being clicked. So far, so good.

But we also need some processing on the server side. Critically, we need:

  1. a way to designate the page element to be replaced via AJAX,
  2. a way to recognize that the request is for just a particular set of elements (the new comments) and not the full, rendered page, and
  3. a way to indicate that a value has been returned and so regular full page rendering is not needed.

Which existing page element is to be replaced?

The first of these problems is straightforward. Need a way to recognize a block of elements? Put them in a wrapper element with a recognizable selector. So in this case, pass the contents of a pager section through a theme function to provide a wrapper element:

/**
* Wrap the output of a pager query, including the pager element.
*
* @param $contents
*   The contents of a pager query.
* @param $id
*   The id by which the pager element will be identified. Used in requesting JSON output.
* @return
*   An HTML string.
*
* @ingroup themeable
*/
function theme_pager_wrapper($contents, $id)
?>

What new data are to be returned from the server?

Which brings us to the second problem, how to indicate what data are expected from the AJAX request. This problem really has three parts:

  • First, we need to know just which set of data are being requested. On any given page there might be many elements that could be requested via AJAX, but a given request is only looking for one of those.
  • Second, we need to know any parameters of the request–like, what page of paged content do we need?
  • Finally, we need to know the expected rendering format. In this case, we’re going to want Javascript encoded objects or JSON.

For the first of these problems, the theme function above already gives a good start. We can give a unique ID to each page element that can be refreshed through AJAX. On the Javascript site, we can determine this ID and pass it to the server as part of the request. Then, on the server, we have a precise way of identifying what set of elements are being requested.

For the second of these problems, how to pass parameters, we’re actually fine with no additional work. For pagers, the parameters (what page of results we’re requesting) are already fed in as part of the URL. We’re not making a new request to the server but rather reusing the standard node/x display path. In the Javascript we’re using the same original links as the pager links had, complete with any URL-encoded variables. So all the data we need are already part of the request.

So we’re left with how to indicate that it’s JSON we’re after. Here we can introduce a simple request handler:

/**
* Handle a request for JSON data.
*
* @param $id
*   A unique ID for the request.
* @param $output
*   Output to render.
*/
function drupal_handle_json_request($id, $output)
  return
FALSE;
}
?>

We have an ID that we’re using to identify the element that can be requested via AJAX. We simply test if that element is being requested via a ‘json_request’ parameter. If so, well, let’s render in JSON.

Preventing a full page render

To enable our regular page request handlers to be used as well for specific AJAX requests, our last requirement is to prevent a regular full page render. We want to return only the JSON, not the full HTML page.

My first thought here was to introduce a new constant that could be returned by menu callbacks indicating that the request was already handled. But chx pointed out that we already have this ability without a new constant. Simply don’t return a value (or explicitly return NULL). A null return indicates that rendering has already been handled and so no theme('page') call is needed.

Patch

Put it all together and you get the patch on this issue: http://drupal.org/node/150378. Although it started out with paging and comments as the specific use case, the patch tries to map out general approaches that can be used wherever we need to refresh just part of a page.

A good start, maybe. But as with any significant new feature, an initial patch is only the first step. What’s needed now is reviews as well as testing. Have I piqued your interest? If so, please roll up your sleeves and have a go at critiquing or testing the patch. Thanks!

2017-03-31T06:20:32+00:00 Categories: Drupal|

About the Author: