Ajax Spy

Posted on 30th January 2009 — Last month I showed you how to create a spy effect as seen on the first version of QuickSnapper (though they’ve since changed their site design with to release the release of Little Snapper).

However I did promise I would demonstrate how to ajaxify the spy.


Watch the jQuery Ajax spy screencast (Alternative flash version)

QuickTime version is approximately 45Mb, flash version is streaming.

View the demo used in the screencast (view the source for the Ajax response)

Breaking down the problem

My key priority for me, when I’m writing code that will make Ajax hits, is that where possible, I’m throttling the number of hits. By this I mean, I’m only performing Ajax requests when I absolutely have to.

To achieve this, I’ll show you in the code where I have created a hidden store of the Ajax response that I use to populate the spy at the preset interval. This gives the impression of real-time data. It’s almost real-time, and since we’re not working with finance systems or data that absolutely has to be real-time, it’s acceptable.

The way this is achieve is that we create a hidden store, in our case a ul element, and collecting the Ajax results in the ul. At the same time, the spy is requesting the latest (i.e. the top) li element in our store. When the store gets down to the last item, it refills itself.

It might sound a bit fiddly, but it’s a very straight forward change to the previous spy.

Here’s a visualisation of, roughly, what’s going on:


As per the original spy, we simply prepare a contain (prefill for those without JS) of latest products:

<ul class="spy">
    <!-- contents of list item -->

Server Side

I’ve created a dummy server spy which you can view the source for. It’s nothing special, and for the demo, doesn’t use a database. It creates an array of data, randomises it and then loops through 5 of the results returning HTML.

In a production environment, I would expect this to make use of the database, and grab perhaps slightly more items to show in the spy.

jQuery (changes)

Since our starting point is the simple spy, I wanted to show where you need to make adjustments to ajaxify the script.

You may want to download the original simple spy.

Creating the Store

The first job is create the ‘caching’ store that will hold the Ajax results, and will refill itself when it’s almost empty.

With in the plugin code (but before return this.each()) we create a new function.

It needs to achieve the following tasks:

  1. Return the lastest/top item from the store.
  2. Remove that top item from the store.
  3. If we’re returning the last item from the store, refill via an Ajax hit
function getSpyItem($source) {
  // find all the li direct descendants in the store ($source is a ul)
  var $items = $source.find('> li');
  // if we only have 1 left, we need to do an ajax hit to get more
  if ($items.length == 1) {
    // do an hit to get some more
    // see playschool: ajax load for a screencast on .load
  } else if ($items.length == 0) {
    // if there are none left, then return false and handle it properly.
    return false;
  // grab the first item, and remove it from the $source
  return $items.filter(':first').remove();

Changing the Spy Function

Originally the spy cached a copy of all the li elements and looped round them. We had a bit of logic to handle tracking whether we reached the end or not. With the Ajax version we don’t need any of this code – since our store should be infinite (since we’re getting it live from the server).

Where we had this code:

var $insert = $(items[currentItem]).css({ // ...

Which reads the items array, we’ll change this to use the getSpyItem function:

var $item = getSpyItem($source); // $source is declared early (see below)
if ($item != false) { // i.e. there's a result to work with
  var $insert = $item.css({ // ...

We will also remove the currentItem logic within the spy function (and remove the declarations too – i.e. var currentItem since we don’t need it anymore).

Initialising the Spy

Now that our code is nearly ready, we need to make sure we initialise properly.

The tasks we have left are:

  1. Create the store, and save it in the $source variable (as used above)
  2. Only show li according to the limit, and move the remainder in to the $source ul element.

Within the this.each() initialisation block, we’ll create our store, at the same time we hide it and append it to the DOM:

var $source = $('<ul />').hide().appendTo('body');

Then change:

$list.find('> li').filter(':gt(' + (limit - 1) + ')').remove();

To the following, so that the ‘left over’ elements are in the store, ready to be grabbed in getSpyItem:

$list.find('> li').filter(':gt(' + (limit - 1) + ')').appendTo($source);

And that’s it!

And Finally…

We can add a mouse over to pause the spy. It’s very simple.

In the spy function, we wrap the logic that grabs new items with:

if (running) { // rest of spy code

However, make sure the setTimeout recursive call is outside this if statement so that it keeps checking.

Then we bind two custom events to the spy in the initialisation phase of the plugin:

$list.bind('stop', function () {
    running = false;
}).bind('start', function () {
    running = true;

And finally, when we attach our plugin, we can trigger the stop and start events when the mouse enters and leaves (note I’m using enter/leave rather than in/out):

$('ul.spy').simpleSpy().bind('mouseenter', function () {
}).bind('mouseleave', function () {