Building the carousel

chaostangent.com footer carousel

The newest addition to chaostangent.com is the carousel nestling comfortably at the foot of every page. Sporting a variety of "social media" feeds as well as other morsels, it showcases a number of interesting technologies and techniques including: a fully looping carousel (JavaScript and CSS), integration with numerous external APIs (PHP, Zend Framework), screen-scraping and local caching of results to name but a few. It successfully fulfils the primary goal I had for it: cramming as much functionality into a contained a space as reasonably possible.

I can just boot up Zend_Service_Delicious and be done with it right? If only things were that simple.

JavaScript

The carousel interface is design du-jour at the moment - sported by sites such as Apple, BBC iPlayer and Gametrailers - they manage selective display of information while still providing a high degree of interactivity. In short: they're swish and solve the problem of too much to feature in too little space. The carousel library I am using is a simplified, stripped-down version of one I developed for a large work project - for this reason I'm unable to release it under any kind of license. The original has a number of features that I wouldn't be using including automated construction of a "jump to" control and being able to navigate over a number of entries at once. My library is the only one I know of which successfully loops, providing an "infinite" carousel of sorts; other publicly available libraries cease at either end of the carousel which in some situations is more intuitive but the challenge of making one not do this was posed to me, and I couldn't very well pass it up.

The library works as such:

  • If moving right on the carousel (usually seen as "advancing" or moving forwards in countries with left-to-right written language) the first element within the carousel has its margin tweened from zero to negative its width e.g. if an element is 100 pixels wide the margin would tween from 0 to -100 pixels. This tween uses the Scriptaculous effects library and "pulls" the item out of sight.
  • Once this is complete the now hidden element is removed from the start of the carousel items and appended to the end.
  • With this done the now appended element's margin is set back to zero.
  • If moving left on the carousel (usually seen as "receding" or moving backwards) the last element of the carousel is modified with a negative margin equal to its width.
  • That element is then appended to the beginning of the carousel items.
  • Once done, the negative margin is then tweened to zero, effectively "pushing" the carousel item into view.

Both effects can be done in a few lines of JavaScript and the act of moving an element from one end of the carousel to another can be done in a single line, e.g. for the "move right" action:

this.elem.insert(this.elem.childElements().first().remove().setStyle({"marginLeft": 0}));

The majority of the class (thank you Prototype) concerns itself with ensuring that there are no glitches during the transition: it locks itself to prevent repeated clicks which could cause the carousel items to resequence themselves and the visual smoothness to be lost. The transition itself is non-standard and was originally pioneered by Robert Penner for Flash and is commonly termed the "Easing equations" - these provide smooth, life like movement for animations. The equation used for this is the "EaseFromTo" equation adapted by Ken Snyder for Scriptaculous.

Markup and styling

The carousel would not operate as it does without some fairly dense and brutal markup and styling. It is comprised of an outside container which acts as a window onto the contents of the unordered list below it: each item within the list is treated as a single carousel item. The unordered list must be in one line otherwise there is a jarring "drop in" effect for items; for this reason the container has its overflow set to hidden, the unordered list has its white-space set to "nowrap" and the list items are set to display as inline-blocks. That's the crux of the styling, obviously with IE6 and 7 not supporting inline-block displays, this won't work but thankfully they abuse the "inline" property enough for them to play nicely:

#external.scripted { overflow: hidden; }
#external.scripted > ul { white-space: nowrap; overflow: hidden; }
#external > ul > li { display: inline-block; width: 277px; }

/* In style-ie6.css */
#external ul li { display: inline; }

/* In style-ie7.css */
#external > ul > li { display: inline; }

The benefit to this markup and styling is that there are numerous possibilities for accessibility improvements when JavaScript is disabled: if the information is unimportant (e.g. not the primary navigation on the page which is a bad idea anyway) then the default visible carousel items can be left as-is; for a quick and dirty accessible solution, removing the hidden overflow and white-space declarations from the carousel means all of the items will follow the normal page flow - this however can cause a conspicuous "snap-in" effect when the script is loaded. The most effective method however is to change the overflow-x property to "auto" to allow for horizontal scrolling - a native and lo-fi way of providing access to the full carousel; this has its own foibles though including the necessity of setting a width on the default, no-script carousel, this is offset by loading of the scripted controls being more subtle than the other methods. I opted not to implement this as the contents are superfluous enough to be ignored, especially so close to the foot of the page.

Probably the most taxing part of the markup was working out the width a margins for each carousel item so that it aligned with the columns / grid I had set up for the site - this involved a bit of number crunching and a selection of scribbled diagrams.

PHP

Before chaostangent.com was a blog it was a splash page that served much the same purpose as the carousel: it aggregated a lot of social media into a small space (before it did that it was a simple splash page, and before that it was a blog again but I digress). For this reason a lot of the work involving PHP had already been done - or so I initially thought. There a subtle tribulations associated with the back end code that didn't become apparent until I began implementing it.

The Zend Framework supplies a lot of Zend_Service_* classes which - in theory - take a lot of the effort out of interacting with external APIs. I can for instance just boot up Zend_Service_Delicious and be done with it right? If only things were that simple. I needed to cache the results of the queries - using Zend_Cache, natch - so that the queries wouldn't be done on a page request and bog down the server and the target API - however passing some Zend_Service_* objects into Zend_Cache proved problematic as they weren't set up for serialization and thus not correctly stored. For this reason I use the Zend_Feed component to read in my delicious RSS feed which provides all that I need and was able to be successfully serialized and cached; Zend_Feed was likewise used for the two other blogs I post to. The YouTube listing of my favourite videos is available as an RSS feed, however the feed doesn't contain interesting video information such as rating, length and so forth, so using the Zend_Gdata component seemed like a sure fit. Unfortunately querying something relatively simple like a favourite video feed for a user causes a lot of data to be generated, so much so that even with only four results the cache file for YouTube comes out at over half a megabyte and obviously spikes the memory usage when loading this in. There is also the bizarre deficiency that while the RSS feed contained the date and time of when I had made a video a favourite, the Zend_Gdata classes did not. So I either compromised on my desires for the YouTube element of the carousel, rolled my own storage for the feed or just suck it up. I opted for the latter and vowed to optimise it for memory usage at a later time.

Last.fm

My last.fm feed was to be one of the highlights of the carousel so I put the most effort into it. Lamentably, the Zend_Service component was for the ageing AudioScrobbler service which while  functionality compatible with the updated last.fm API but omitted several necessary features. This included the guarantee that results for my most recent tracks would include images and links. I pontificated writing a whole new Zend_Service component but put that on the back burner and dove into using the API directly. To cut a long story short I shied away from using Zend_Rest as PHP's function naming didn't allow for periods within function names which more or less forced me to use the XML-RPC component: Zend_XmlRpc. Working around its refusal to return objects - instead defaulting to quote-escaped XML strings which I then converted to SimpleXML elements - I conditionally stacked up a number of queries to ensure that the information I needed was, within reasonable parameters, always available. A glitch did occur with using SimpleXML and the xpath function whereby it tended to cache the xpath result if it was used on a separate child node from the same parent. For example: the main XML RPC call to get my recent tracks

$result = $xmlrpc->call("user.getRecentTracks", array($p));
$sx = new SimpleXMLElement(stripslashes($result));
foreach($sx->recenttracks->track AS $track)

Now if I iterate over each track and do

$image = (string)reset($track->xpath("//image[@size='small']"));

$image should contain the individual image for each track. Not so, $image will always retain the first image that xpath() returned as the results are silently cached by SimpleXML / PHP. To get around this you have to splinter the SimpleXMLElement from its parent with an otherwise redundant

$track = new SimpleXMLElement($track->asXML());

A simple fix but a pain to debug.

LOVEFiLM

The last element I wanted to include on my carousel was my LOVEFiLM (similar to Netflix in the US) recent DVD rental list. LOVEFiLM is apparently trialling a fully fledged API developed by in conjunction with an external company; however I didn't need a full API, only a list of my most recently rented and rated. The page for this was uniform enough so I set about trying to find a way to pull in that page and working on it from there. The most likely candidate was a cookie left by the website called "lovefilm_session" which has a lifetime of two weeks and the value looks like a standard MD5 hash. PHP doesn't place any IP restrictions on sessions so I was hoping that the Perl implementation the site was using was similar and simply sending the cookie along with a request for the page. This worked a treat in local testing and thankfully carried across to production without incident. The next task was getting the desired information out - being an in-production site meant that simply loading the HTML (despite being under the XHTML 1.0 transitional DTD) into an XML interpreter threw up numerous errors and was unworkable which meant regular expressions were my only recourse.

Using a suitably generic regex to get the title, rating and the URL for the DVD in question required some fine tuning - it still needs to match on certain patterns which makes it inherently fragile. Hopefully by the time LOVEFiLM update their site their API will have been released for consumption.

Conclusion

Overall the carousel took a full weekend of planning, design, build and testing as well as some last minute tweaks such as digging into the WordPress date functions for internationalising dates and times to complete but the result is better than I could have hoped for. There are always more social media sites out there so the possibilities for additions and enhancements are great - I can only see the carousel getting better as time goes on.

Respond to “Building the carousel”

Community rules:

  1. Keep it civil: no personal attacks, slurs, harassment, hate speech, or threats
  2. No spam: includes marketing, pyramid schemes, scams etc.
  3. Notify of any spoilers: even if it's for something the post isn't about
  4. Your response may be edited or removed: if your response was in good faith, you may be contacted via email explaining why

Your address will never be shared

The following HTML tags are allowed: <b> <strong> <i> <em> <a href>