AJAX pagers for Drupal 7
on
October 26, 2008
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.
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.
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:- a way to designate the page element to be replaced via AJAX,
- 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
- 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:<?php
/**
* 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) {
drupal_add_js('misc/pager.js');
return '<div id="' . $id . '" class="pager-contents">' . $contents . '</div>';
}
?>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.
<?php
/**
* 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) {
if (isset($_REQUEST['json_request']) && $_REQUEST['json_request'] == $id) {
$response = array(
'status' => TRUE,
'content' => $output,
);
drupal_json($response);
return TRUE;
}
return FALSE;
}
?>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 returnNULL). A null return indicates that rendering has already been handled and so no theme('page') call is needed.












