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.
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,
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
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:
* 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.
* 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?
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)
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.
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!