
The most noteworthy of my clientelle is the Andre Sobel River of Life Foundation. They’ve been a steady source of work for the last year, and every day in which I do something to improve their web presence is, to me, a day well spent.
When I was asked how difficult it would be to create a live counter on the site that showed the number of minutes that the organization has given to the families it has helped, my first thought was something along the lines of “not very difficult at all.”
I mean, really. How hard could it possibly be? I knew of some simple Javascript codelets that could immediately provide a solution for a ticking clock. But, once I wrote out the actual feature list for the thing I was creating, I noticed some pretty big hurdles.
Here’s what the counter needed to produce:
- The number of minutes that have passed since January 1st of 2000, along with a ticking seconds counter.
- This number must be live, updating on the page constantly.
- Each individual number must be wrapped in its own container, with pictures of specific children behind them.
- Clicking on a number/child will bring up that child’s story.
- Supply an embeddable widget that other people can put on their blogs/MySpace/Facebook pages.
Getting the number of minutes
This was the easiest part of this entire equation: display the number of minutes that have elapsed between now and January 1st, 2000. Since the number of minutes needed to be the same regardless of timezone (this was actually a big deal), I couldn’t use Javascript. So, I wrote this extremely simple PHP script:
date_default_timezone_set('America/Los_Angeles');
$time = strtotime('January 2000');
$now = time();
$diff = $now - $time;
echo $diff;
The time() command will always return time in Unix Epoch format, which is the number of seconds that have elapsed since January 1, 1970. The number of seconds between January of 2000 and now is simply the difference between the two variables (on line 6).
Everytime this script loads, it prints a number that looks something like this: 321178160. Originally, I had the script divide by 60 (giving me the number of minutes) and kick that out, but I realized that since I needed the seconds to tick on the page, it would have to load this server-side script every second. This would not have been ideal (actually, this is how the embeddable widget works, but it's only running the script once; it doesn't implement ticking seconds or keep the clock live).
Minutes, not seconds
For performance's sake, I had to keep as much stuff as possible on the client side. The minutes are calculated from the PHP clock script, and then Javascript adds time to it. The seconds are done entirely with Javascript, since the timezone for the seconds is irrelevant.
Parsing the Number
First thing I needed was an AJAX call to grab the number from the clock.php script. That much was easy:
$.ajax({
url: '/clock/counter.php',
success: function(data) {
c=parseInt(data);
maketime();
}
});
Then, I needed to turn that number into minutes and wrap each digit in its own container. But, this number also had to be updated every second, which was accomplished by adding 1 to the number from the PHP script every second and then dividing by 60. Like so:
function maketime(){
var t;
c=c+1;
epic = c;
t=setTimeout("maketime(); wrapem()",1000);
epic = addCommas(Math.round(epic / 60));
var timeoffset = (+29800);
var now = new Date();
now.setTime(now.getTime() + timeoffset);
var seconds = now.getSeconds();
var s = ((seconds < 10) ? ":0" : ":") + seconds;
nowtime = epic + s;
arr = nowtime.split("");
clock = arr.join("");
clock = ""+clock+"";
$(function(){
$("#asrl-clock").html(clock);
$(".number:nth-child(2), .number:nth-child(6)").addClass('comma').removeClass('number');
$(".number:nth-child(10)").addClass('colon').removeClass('number');
$(".number:last").addClass("last");
wrapem();
});
}
The wrapem() function is actually a little bit misleading in its name. Really, the wraps are applied to each character when they are split into an array and rejoined in their own spans. What wrapem() does is attach appropriate numbers to their corresponding background image and link.
function wrapem(){
$(".number:nth-child(1)").attr("id","one");
$(".number:nth-child(3)").attr("id","three").wrap('<a href="/2010/02/27/meet-alia" class="clink" />');
$(".number:nth-child(4)").attr("id","four").wrap('<a href="/2010/02/27/meet-anthony-2" class="clink" />');
$(".number:nth-child(5)").attr("id","five").wrap('<a href="/2010/02/27/meet-daniela" class="clink" />');
$(".number:nth-child(7)").attr("id","seven").wrap('<a href="/2010/03/04/meet-noah/" class="clink" />');
$(".number:nth-child(8)").attr("id","eight").wrap('<a href="2010/03/04/meet-robbie/" class="clink" />');
$(".number:nth-child(9)").attr("id","nine").wrap('<a href="/2010/03/04/meet-augustin/" class="clink" />');
$(".number:nth-child(11)").attr("id","eleven");
$(".number:nth-child(12)").attr("id","twelve");
$("#one").css({background:"url(/clock/images/alex.jpg) no-repeat center center"}).wrap('<a href="/2010/02/27/meet-alex-2" class="clink" />');
$("#three").css({background:"url(/clock/images/alia.jpg) no-repeat center center"});
$("#four").css({background:"url(/clock/images/anthony.jpg) no-repeat center center"});
$("#five").css({background:"url(/clock/images/daniela.jpg) no-repeat center center"});
$("#seven").css({background:"url(/clock/images/noah.jpg) no-repeat center center"});
$("#eight").css({background:"url(/clock/images/robbie.jpg) no-repeat center center"});
$("#nine").css({background:"url(/clock/images/augustin.jpg) no-repeat center center"});
}
The Live Link Problem
Originally, I was going to use the Thickbox functionality that I had already loaded for a different section of the page to open the story boxes for the kids. Unfortunately, it didn't work, and it took me a while to figure out why. Essentially, the links weren't part of the DOM when the page loaded; they were created after the clock script had time to run and generate the numbers. In fact, as far as the DOM was concerned, these numbers were completely new every time the maketime() function ran; every single second. After banging my head against the desk for about three hours, I realized that it would be easy to write my own simple lightbox script and use jQuery's live() method to activate them.
$(function(){
$(".clink").live('click', function(){
hrefval = $(this).attr('href');
$.ajax({ url: hrefval, context: $(".entry"), success: function(data){
$("#story").fadeOut('fast');
$("#story").html(data).fadeIn('fast');
}});
return false;
});
$(".close-story").live('click', function(){
$("#story").fadeOut('fast');
return false;
});
});
By using the href attribute, I didn't even have to change the links that the AJAX was grabbing the data from.
Styling the Counter
Using the split() and join() Javascript functions let me wrap each character in a span, but the problem is that I had two commas and a colon (great name for a TV show, BTW) that, in fact, couldn't be set in their own curved box. Luckily, this number will be seven digits long for the next 10 years and change, so I was able to use the nth selector to remove the "number" class from the characters.
I set the background images for the digits using the same method. I'm sure there's a more elegant way to solve this problem, but I had to weigh how much time I had left to spend on it vs. how much the end user would benefit from some slightly improved code.
Final Thoughts
I wound up learning a lot about JSON and XSS (cross-server scripting) which wound up being irrelevant to the big counter on the home page, but vital to the embeddable one.
Also, I found out that you can embed stylesheets in Javascripts. Like so:
function loadJSON(url) {
var headID = document.getElementsByTagName("head")[0];
var newScript = document.createElement('script');
newScript.type = 'text/javascript';
newScript.src = url;
headID.appendChild(newScript);
stylesheet = document.createElement("link");
stylesheet.rel = "stylesheet";
stylesheet.type = "text/css";
stylesheet.href = "http://andreriveroflife.org/clock/clockstyle.css";
stylesheet.media = "all";
document.lastChild.firstChild.appendChild(stylesheet);
}
function processJSON(feed){
document.writeln(feed.returned);
}
loadJSON('http://andreriveroflife.org/clock/counter.php?json=?');
