This is a sort of continuation of my post on progressive enhancement.
Problem definition
Given a long list of data, display it to the user in pages to avoid scrolling. Typically you'd have a bunch of navigation links at the end with First, Last, Next, Previous links, or links to specific pages.Six steps from vanilla HTML pages to AJAX pages
It's important to note that the problem definition does not mention AJAX, but people always like to make their apps buzzword compliant. So, forget about AJAX for the moment and concentrate on solving the problem — retrieve a database resultset in limited sized pages. Once we've done that, it's five more steps to accessible AJAXified pages:- Build the page as you would for html only pagination
- When your pagination links load, attach
onclick
handlers to them. - The onclick handler makes an
asyncRequest
tothis.href + '&js=1'
(or something similar) - Modify your backend code to check for
js=1
in the query string.
If not found, then send the entire page with header and footer as before
If found, then send one of the following:- The html for the paged data
- XML for the paged data
- A JSON object representing the paged data
- In your callback for the
asyncRequest
, do one of the following:- Put the html into the innerHTML of your page's container object
- Parse the XML and translate it to the DOM objects for your paged data
eval()
the JSON and redraw the DOM for the paged data
- Rewrite the hrefs in your paging links to point to new pages.
The Code
Let's look at some of the code. I'll use the yui utilities for connection and event management since I've been playing with that.For simplicity, I'll assume that we're representing our data as a <LI>st. A table is similar, except that you need to redraw the entire table since it's read-only in IE.
Step 1: Build HTML (PHP with MySQL)
<div id="page"> <ul> <?php // Fetch all results and print them while($o = mysql_fetch_array($result, MYSQL_ASSOC)) { ?> <li><?php print $o['name'] ?></li> <?php } ?> </ul> <?php // Verify next/last page links $prev_page = ($pg<=0?0:$pg-1); $next_page = ($pg>=$num_pages?$num_pages:$pg+1); // Display navigation links, disable (via css) links that cannot be selected ?> <p class="navbar"> <a id="first-link" href="foo.php?pg=0" class="<?php if($pg == 0) echo 'disabled' ?>">First</a> <a id="prev-link" href="foo.php?pg=<?php print $prev_page ?>" class="<?php if($pg == 0) echo 'disabled' ?>">Prev</a> <a id="last-link" href="foo.php?pg=<?php print $num_pages ?>" class="<?php if($pg == $num_pages) echo 'disabled' ?>">Last</a> <a id="next-link" href="foo.php?pg=<?php print $next_page ?>" class="<?php if($pg == $num_pages) echo 'disabled' ?>">Next</a> </p> </div>
Step 2: Attach onclick handlers
var nav_links = ['first-link', 'prev-link', 'next-link', 'last-link']; YAHOO.util.Event.addListener(nav_links, 'click', navigationHandler);
Step 3: Make async call:
var callback = { success: gotResponse, failure: failedResponse } var navigationHandler = function(e) { var url = this.href + '&js=1'; YAHOO.util.Connect.asyncRequest('GET', url, callback, null); YAHOO.util.Event.preventDefault(e); return false; }
Step 4: Modify back end to check for
js=1
:<?php$js = $_GET['js']; if($js) { header('Content-type: text/json'); } else {?> <div id="page"> <ul> <?php} $json = array('n'=>$num_pages, 'p'=>$pg, 'l' => array());// Fetch all results and print them while($o = mysql_fetch_array($result, MYSQL_ASSOC)) {if($js) { $json['l'][] = $o['name']; } else {?> <li><?php print $o['name'] ?></li> <?php}}if($js) { print json_encode($json); // nothing more to output, so quit exit(); }?> </ul>
I've hilighted the code that changed, it's just a bunch of if conditions. Yeah, it's ugly, but cleaning it up is not the purpose of this article.
Step 5: Make your
asyncRequest
handler:var gotResponse = function(o) { var json = eval("(" + o.responseText + ")") ; var list = json['l']; var num_pages = json['n']; var page = json['p']; var prev_page = (page<=0?0:page-1); var next_page = (page>=num_pages?num_pages:page+1); var lis=""; for(var i=0; i<list.length; i++) { lis += "<li>" + list[i] + "</li>\n"; } var ul = document.getElementById('page').getElementsByTagName('ul')[0]; ul.innerHTML = lis;
Step 6: Rewrite paging urls:
var fl = document.getElementById('first-link'); var pl = document.getElementById('prev-link'); var nl = document.getElementById('next-link'); var ll = document.getElementById('last-link'); var url = fl.href.substr(0, fl.href.indexOf('pg=')+3); pl.href = url + prev_page; nl.href = url + next_page; ll.href = url + num_pages; fl.className = pl.className = (page<=0?'disabled':''); nl.className = ll.className = (page>=num_pages?'disabled':''); }
Steps 5 and 6 are the same function of course, so don't split them up.
A brief explanation
Well, there you have it. If javascript is disabled, the default<A>
behaviour is to make a GET
request to foo.php
with default values for pg
. On every page call, the back end changes the value of pg
in the Next and Previous links, and possibly in the Last link if records in the database have changed.If javascript is enabled, we prevent the default href from being called with our
return false;
, and instead make the same call using asyncRequest
, but with an additional query parameter saying that we want a javascript (json) object back.The back end php script still hits the database as usual, and gets back a result set, which it now builds into a PHP hash, and then converts to a JSON object. The JSON object is sent back to the client where it is converted into HTML to push into the
<UL>
.The
page
and num_pages
variables allow us to rewrite the hrefs so that they point to up to date paging links, that you can, in fact, bookmark.Improvements
To make the code cleaner, you may want to build your PHP hash at the start, and then based on the value of$js
, either convert it to HTML or to JSON. This of course has the disadvantage of having to iterate through the array twice. If you're just looking at 20 records, I'd say it was worth it, and a better approach if you start off that way.This is quite a simple implementation. You could get really creative with javascript, showing funky page transitions that keep the user busy while your
asyncRequest
returns.Update: json_encode is available from the PHP-JSON extension available under the LGPL.
Update 2: The cleaner way to write the PHP code that I mentioned in Improvements above is something like this:
<?php $js = $_GET['js']; if($js) header('Content-type: text/json'); $list = array(); while($o = mysql_fetch_array($result, MYSQL_ASSOC)) $list[] = $o['name']; if($js) { $json = array('n'=>$num_pages, 'p'=>$pg, 'l' => $list); print json_encode($json); // nothing more to output, so quit exit(); } else { ?> <div id="page"> <ul> <?php foreach($list as $name) { ?> <li><?php print $name ?></li> <?php } ?> </ul> <?php // Verify next/last page links $prev_page = ($pg<=0?0:$pg-1); $next_page = ($pg>=$num_pages?$num_pages:$pg+1); // Display navigation links, disable (via css) links that cannot be selected ?> <p class="navbar"> <a id="first-link" href="foo.php?pg=0" class="<?php if($pg == 0) echo 'disabled' ?>">First</a> <a id="prev-link" href="foo.php?pg=<?php print $prev_page ?>" class="<?php if($pg == 0) echo 'disabled' ?>">Prev</a> <a id="last-link" href="foo.php?pg=<?php print $num_pages ?>" class="<?php if($pg == $num_pages) echo 'disabled' ?>">Last</a> <a id="next-link" href="foo.php?pg=<?php print $next_page ?>" class="<?php if($pg == $num_pages) echo 'disabled' ?>">Next</a> </p> </div> <?php } ?>
Update: I've put up a working example on sourceforge.
16 comments :
Thanks, it is really useful information. I am sure, I am going to use it in one of my projects...
-abdul
ask me for better code if you want.
json_encode($json); - Guess you should add where to get this function for PHP
json_encode is from the PHP extension for JSON available here: http://www.aurore.net/projects/php-json/
In case you are unable to install the php-json extension(read shared host) take a look at http://mike.teczno.com/json.html and http://www.reallyshiny.com/scripts/php-json-class/
they work just as well (we used them before going to the C based extension), but are far slower than the extension.
Like tarique says, use only if you can't install an extension, though it may be worth it to ask your hosting provider to install it.
Can someone post a demo of this code?
If user clicks somewhere else while we're prefetching next page, all prefetched data will be sent to the user anyway.. is there any method to handle this, by aborting AJAX call or something?
it is possible to cancel an asyncRequest before it completes, but chances are that the back end script may not terminate.
With the yui libraries, call the abort() method of the connect object.
Paul, I've put up a working demo of this code.
Thx for the info.
I have only one question left:
You use "next", "last", "preview", "first". Is it possible to make it like this?
1 2 3...8 9 10 next | last
Thx in advance.
@Anonymous: yes, it's quite easy. Just add the links inside the navbar p as regular links.
You'll then have to change Step 6 to something like this:
[pseudocode]
1. Get all P with className == 'navbar'
2. Get all A tags within this P
3. Rewrite urls for them
[/pseudocode]
That's it.
Hi while searching for far side of the moon i came across this blog site but when i saw it was completely different any way have a look at my post on far side of the moon :)
http://probedeep.blogspot.com/2008/03/other-side-of-moon-ufos-or-nazis.html
Can you please put this on mediafire or anywhere, i really like it.
I'm not sure what mediafire is, but there's a live version on sourceforge - the link is at the bottom of the page
Post a Comment