Sunday, November 16, 2008

perrow.ca 2.0

I've just finished rewriting some parts of my family web site to be more "AJAX-y". There are a bunch of pages where we have pictures from various vacations and baby pictures of my kids and such. With the old format, the page loaded all of the images for a particular page and if you wanted to see a close-up of one of them, you clicked on it and it just displayed the raw image. Then you had to click the back button to get back to the list of images. This was fine, but not very nice on a slow system (like my parents and Gail's parents, who are on dial-up). Some friends of mine use smugmug.com for their family pictures, and I like their setup — it shows some thumbnails in a panel on the left and when you click on one, it loads that image on the right. There are also next and previous links to cycle through pictures one at a time, and there are page forward and page back links for going to the next set of thumbnails. I decided to steal borrow this idea and in so doing, teach myself Javascript and AJAX. AJAX is actually an acronym meaning "Asynchronous Javascript And XML", but my solution doesn't use any XML, so I suppose I should just call it AJ.

I started doing the work myself from scratch. I found an online tutorial that described Javascript (I had done a little bit previously, but had to get much more in-depth) as well as what AJAX was and how to do asynchronous requests. After adding a few fairly simple AJAX things to my lacrosse pool web site, I started work on the image galleries at perrow.ca. I was making progress when I discovered a Javascript library called jquery which made some of the AJAX stuff easier but more importantly, made navigating through the DOM much easier as well. It allows you to iterate through all objects of a particular class and make changes to them, or find a particular element given its ID, and make other changes that would otherwise require lots of HTML changes. Best of all, they've already solved the cross-browser issues for you, so when trying to determine the size of an element, I don't have to worry about the fact that IE does things in the DOM differently than Firefox.

I downloaded it and started adding jquery calls to my Javascript code, and this allowed me to clean up my code significantly. As I mentioned, all of the code I'd written to handle IE and Firefox differences went away (and I hadn't even got to Safari, Opera, or Chrome).

The most complex part of this whole project was keeping all the languages straight. The whole site is written using PHP, so to make changes, I had to change the PHP code to generate different HTML, then update the CSS stylesheet, then add the Javascript code to make the AJAX requests, then update more PHP code to handle those requests. The Firebug plug-in for Firefox was invaluable for debugging Javascript, although there were a number of occasions where it would just lock up. Firefox would continue seemingly normally, but Firebug just stopped responding. I couldn't even click the 'X' or hit F12 to close the Firebug window. Shutting down Firefox completely and then starting it up again cleared out whatever cruft was causing that.

The other difficult part was the fact that the browser's "Display page source" feature is not useful if the contents of the page are mostly generated using Javascript. If the page wasn't doing the right thing, I had to tweak the output so that it would display the raw html so that I could see what was going wrong. This is what we old guys call the time-honoured printf method of debugging, not like these kids today with their C# and their Visual Studio and their fancy-schmancy debugging tools. Back in my day, we used printf and that was it. And we liked it.

The whole page is basically done with Javascript now. The HTML for the main part of the page now consists of:

<div id="pagecount">&nbsp;</div>
<div id="imgcount">&nbsp;</div>
<div style="clear: both"></div>
<div id="thumbnailarea"><span class="spinner">&nbsp;</span></div>
<div id="mainimagearea"><span class="spinner">&nbsp;</span></div>

Everything else is done using CSS and Javascript — CSS to define where things go and what they look like (colours, borders, stuff like that), and Javascript to load the page initially and to handle mouse clicks. The "pagecount" block is populated with the page number, number of pages, and page navigation links. The "imgcount" block contains the image number, image count, and image navigation links. The thumbnail area is populated with the appropriate page of thumbnails, currently four rows of images, where the number of images per row is dependent on the size of the block in the browser. The "mainimagearea" is populated with the first image on the page, and is then updated whenever the user clicks on a thumbnail. When the page is loaded, the "spinner" spans are initialized to contain a little spinner image spinner that indicates that something is happening in the background. Thanks to jquery, this can be done in a single line, regardless of how many spinners I have on the page:

$(".spinner").html( "<img src='/images/ajax-loader.gif' />" );

It's also nice that if I change the spinner image or the location of the file, I just have to change the URL in this one place rather than anywhere I use the spinner image. That can be done with CSS as well:

.spinner { background: url( /images/ajax-loader.gif ); }

If you do it using Javascript, however, the spinners won't show up if you have Javascript disabled. In that case, however, nothing else will load either, so the page will be entirely blank. I had to add <noscript></noscript> tags containing a message saying that you need Javascript enabled to make this work. Since I do have a non-Javascript solution (the old code), I could just put that code inside the <noscript> tag, and I probably will... later.

I used to have a special page for a "slideshow", where it would display a large image and you could click next and previous to go through them, or click "autoplay" and it would automatically go to the next image after five seconds. To implement this in the Javascript world, I just added a checkbox for "Auto-advance", which sets a Javascript timer for five seconds and pretends that "Next" was clicked when the timer fires. I had to make sure I cancel the timer if the user clicks anything else, but apart from that, it was pretty easy.

No comments: