Build an awesome status board

GFX9.COM share Build an awesome status board, you can download now.

When Panic unveiled their status panel to the world, I was both impressed and inspired. In today's tutorial and screencast, I'll show you how to built a similar status board!


Here's what our status board will have: at the top, there will a pageviews graph; it would take another tutorial to get that data from Google Analytics, so for today, we'll just use some random data. Then, we'll pull in our upcoming events from Google Calendar. We'll have a project status table, pulling data from a database. Finally, we'll show tweets from our "company" and other people mentioning our company. Let's go!

Before we get started, I should mention that the icons I use in this project come from Smashing Magazine's free set entitled On Stage; unfortunately, I can't include them in the demo files, but here's a list of the file names I renamed them to for the project (most of the project depends on the file names); I've resized the icons to 64x64px; it should be obvious as you go through the tutorial which icons belong where.

  • bazquirk.png
  • behind.png
  • complete.png
  • foobar.png
  • gadget.png
  • gizmo.png
  • jane.png (editted buddy.psd)
  • joe.png (editted buddy.psd)
  • john.png (editted buddy.psd)
  • ontime.png
  • sally.png (editted buddy.psd)
  • waiting.png
  • widget.png

Step 1 Start it with the Markup

While our finished page will have dynamic content, we'll build the interface first by using static content to get everything set up, and then we'll work on the server-side code later.

Here's our preliminary shell:

	    Status Board 

Not hard at all: three divs in a wrapping div. Let's look at div#data right now. Inside it, we'll put two divs:



  • 3/22Meet with Chris details go here
  • 3/26Holiday
  • 4/15Foobar Deadline
  • 5/24Office Party

We'll fill in div#visits with JavaScript in a while. As for div#schedule, we've put in a list. Later, we'll pull this data from Google Calendar, but this will give us something to build the interface on. The important part is that we've wrapped the event date in em tags and the event details in small tags.

The next main section is div#projects. This will show us the latest information about the projects our little imaginary company is working on. Just like the schedule, we'll be pulling this in from a data source (a database, in this case); for now, it's just a static table:

Gizmo On Time Joe Sally
Widget Behind John Joe Jane
Gadget Complete Sally Jane
Foobar Waiting John Joe
Bazquirk On Time Sally Jane Joe

The div#twitter will show us two groups of tweets: those made by our company, and those mentioning our company. We'll use Remy Sharp's twitterlib to do this. Here's the markup we'll need:


Finally, we'll import the scripts we're using:

  • jQuery
  • twitterlib
  • jQuery Flot Plugin
    • ExplorerCanvas for IE (needed for Flot)
  • jQuery Pause / Resume Animation Plugin

Of course, in production, you would put all these into one file.


The last file here, statBoard.js will be our own creation.

Full Screencast

Step 2 Prettify it with the CSS

Before we start the CSS, here's what our finished product will look like; this should make the CSS a bit clearer.

First, we'll set up the body and h1, as well as set all our main content divs to contain their children.

	body { 
	    background: #f3f3f3; 
	    font:bold 25px/1.5 'Helvetica Neue', Arial, 'Liberation Sans', FreeSans, sans-serif; 
	h1 { margin:0; padding:0; font-size:40px; } 
	#wrap > div { overflow:hidden; }

Next, let's consider that first content div, which has an id of data.

	#data { 
	#visits { 
	#tooltip { 
	    position: absolute; 
	    padding: 2px 5px; 

We'll give that div a bottom margin of 40px, just for breathing. Then we'll turn to its first child, div#visits: float it to the left, set its width and right margin, and reduce the font size.

Where's this tooltip div coming from? It will be inserted when we create the analytics graph using a jQuery plugin. Here, we're setting the visual styles, as well as positioning it absolutely and hiding it. When we hover over points on our chart, it will be positioned and then fade in.

Let's look at div#schedule next:

	#schedule  { 
	#schedule ul { 
	#schedule li { 
	#schedule em { 
	    padding:0 10px; 
	#schedule small { 

This shouldn't be too hard to figure out; we want div#schedule to be to the right of div#visits, so we float it to the left and make sure the width + padding will equal 50%. Then add some colour and shave off the corners. The overflow:hidden is important. Remember that we'll be pulling in the event details; we don't want the details to wrap to more than one line, so we'll let them overflow and then hide the overflow.

The styling for the ul and li are pretty simple; we've given the li a width of 1000px so that those details won't wrap.

Next up we have the ems inside div#schedule. This is where our dates will go. We remove the usual italic-ness by setting the font-style to normal. By setting the display to inline-block we can set a width on 50px; since we're showing the date in the format m/d, this should look good in all cases. And of course, round the corners.

Finally, for the details, we're using the small tag; we'll set font-size, font-style, and colour.

The next main section of our status board is div#projects.

	#projects table { 
	#projects td { 
	#projects td:not(:last-child) { 
	#projects img { 

We'll make sure our table takes up the entire width of the page; we'll also give the cells some margin with border-spacing. We'll give each cell some padding and slightly round the corners. Then, let's use some CSS3 selectors to center the text in every cell expect the last one; if the browser doesn't support :not() or :last-child, this rule will be ignored, and all the cells will stay left-aligned: not a big deal. Finally, we'll vertically center the images.

We only have the twitter div left:

	#twitter > div {  
	#twitter ul { 
	#twitter li { 
	#twitter img { 

Currently, our div#twitter has only two empty divs within. We'll load the tweets with JavaScript soon, but here's what we want to happen: each div within div#twitter will be a marquee, scrolling tweets across the screen. Therefore, we'll set those divs to hide their overflow, and also set their font to 5px less than the rest of the page. For the lists, we'll remove the bullets, position it relatively (so we can animate the right positioning to do the marquee) and set the width to 5000px; I'll explain the width once we get to the JavaScript. Each list item (each tweet) will be floated left and given a bit of right margin. Finally, the images (we'll be displaying twitter profile images) are styled appropriately.

Step 3 Interact with it with the JavaScript

On to the JavaScript! So we don't clutter up the global namespace, we'll create a single function that will return the methods of our status board. Here's the shell we'll start with:

	var statBoard = function () { 
	    return { 
	        graphVisits    : function (selector) {}, 
	        twitterize     : function (selector, fn, subject) {}, 
	        iconizeWorkers : function (selector) {} 

Let's build out graphVisits first; you'll probably want to pull in analytics from Google Analytics or your choice of stats app. However, we're just going to generate some random data for our example.

First, we'll set up our variables:

	var el = $(selector), 
	    data = [], prev = null, 
	    showTooltip = function (x, y, contents) { 
', { id : 'tooltip', text : contents, css : { top: y + 5, left: x + 5 } }).appendTo('body').fadeIn(200); };

Respectively, we've got the element that we'll insert the graph into, the array of data we'll pass to the Flot jQuery plugin, prev, which you'll see in use, and a showTooltip function. This creates that div#tooltip that we styled earlier, appends it to the body, and fades it in.

	for (i = 0; i < 32; i++) { 
	    data.push([new Date(2010, 2, i).getTime(), Math.floor(Math.random() * 6000)]); 

Next, we set the height of the element that will contain the graph; the flot plugin requires this. We set it to the same height of div#schedule; jQuery's outerHeight method returns the height of the object including its padding and border. Then, we'll fill up our array data; the flot plugin accepts an array of arrays, each inside array having an x and y coordinate. For our x-coordinates, we're going to use a JavaScript date; the values we pass into the Date constructor are the year, month, and day (there are more, to set the time). We're setting the year to 2010, and the month to March (it's zero based, so Jan = 0). This will allow us to have dates in the bottom. We'll set the y-coordinate to a random number.

(Note: In the screencast, I mistakenly said that the year parameter was based on the Unix epoch, so that putting 10 would result in 1980; this is incorrect; what I've explained above it right.)

	    $.plot(el, [{ data: data, color:'#494949', lines : { show: true }, points : { show : true} }], 
	        { xaxis : { mode : 'time', timeformat : '%b %d' }, grid : { hoverable : true, clickable : true } });

Now we create the graph; we'll call the plot method and pass in the element that will hold the graph as the first parameter. The second parameter is an array, with only one object: the data will be our data array, the color will be the color of our graph line; then, we set the lines and points objects to show : true; lines will "connect the dots" on our graph, and points will make each datapoint more prominent. We could put miltiple objects like this one in the second-parameter array to chart multiple sets of data.

The last parameter is another object; it sets some properties on the graph. We'll set the x-axis to be in time mode; the timeformat is how we want the date to be formatted. According to the Flot documentation, "%b %d" will give use the month and day. Then, we set the grid enable hoverable and clickable.

	el.bind('plothover', function (event, pos, item) { 
	    if (item) { 
	        if (prev != item.datapoint) { 
	            prev = item.datapoint; 
	            showTooltip(item.pageX, item.pageY, item.datapoint[1]); 
	    else { 
	        prev = null; 

The next step is to build the tooltip functionality. The plothover event is fired whenever you hover over the graph, and it passes three parameters to event handlers: the event, the position, and the datapoint you're hovering over. First, we'll check to see if we're on an item. If we are, we'll check to see if prev does not equal the position of the point. If it doesn't that means we were not on this point at the last plothover (we could move around on one item, remember). Therefore, we'll record which point we're on, make sure the tooltip isn't showing somewhere else, and show the tooltip in the new position. If prev does equal the current datapoint, we don't need to do anything, because the tooltip is already showing. Finally, if we're not on a point, we'll make sure the tooltip isn't showing and set prev back to null.

That's the end of our graphVisits function; let's move on to our twitterize function:

	twitterize : function (selector, fn, subject) { 
	    var container = $(selector);

Twitterize takes three parameters: the selector of the element to load the tweets into, the name of the function we want to call in the twitterlib library, and the subject of our twitter call; this could be a username if we're using the timeline function, or it could be a search string if we're using the search function.

Once we're in the function, we'll get the tweet container.

	twitterlib[fn](subject, { limit : 10 }, function (tweets) { 
	    var list = $('
    '), i, len = tweets.length, totalWidth = 0;

Next, we call twitterlib. We use the function name passed in, and set the subject as the first parameter. Then, we use the options object to tell the library to only get us the ten latest tweets to match our request. Finally, we add a callback function, which takes the tweets we get from twitterlib. First things first, we set our variables. You'll see them all in use.

	for (i = 0; i < len; i++ ) { 
  • ') .find('a') .attr('href', '' + tweets[i].user.screen_name + '/status/' + tweets[i].id) .end() .find('img') .attr('src', tweets[i].user.profile_image_url) .end() .append(this.ify.clean(tweets[i].text)) .appendTo(list); } container.append(list);

    Now we loop over each tweet in the group. We'll make an HTML fragment of a list item and an anchor-wrapped image inside. We find the anchor, set the href, and use end() to go back to the li. Then we find the image, set the source, and go back to the list item. Finally, we append the text of the tweet to the list item, which will put it in after the anchor. We run this text through twitterlib.ify.clean; this will link up the links, mentions, and hashtags. Then we'll append the list item to the unordered list we created.

    When we've worked through all our tweets, we'll append the list to the container.

    Flashback: remember how we set the width of #twitter ul to 5000px? We did this so that each tweet would be on a single line, and wouldn't wrap. This is because we're now going to get the width of each list item.

    	$('li', list).each(function (i, el) { 
    	    totalWidth += $(el).outerWidth(true); 

    We'll loop through each list item (we can use the list as a context parameter) and use the outerWidth function to get the width. Just like outerHeight, outerWidth includes the border and padding, and (since we've passed in true) the margins. We add all those widths to our totalWidth variable. Now totalWidth is the right width for our list, so we'll set that.

    	function scrollTweets() { 
    	    var rand = totalWidth * Math.floor(Math.random() * 10 + 15); 
    	        right: totalWidth 
    	    }, rand, 'linear', function () { 
    	        list.css('right', - container.width()); 

    Next order of business: get that list scrolling. We'll calculate the scrolling speed by multiplying the totalWidth by a random whole number between ten and fifteen. Then, we do the animation. Normally, we'd use the animate function, but we're using the pauseanimate plugin, so we use the function startAnimation. We want to animate the right value to totalWidth. The duration parameter gets the random number. We'll set the easing to 'linear'; by default, it is 'swing,' which will ease in at the beginning and out at the end. Lastly, a callback function: we'll set the right position of our list to negative the container width. This will push it to the right of the screen. Then, we'll call our scrollTweets function so the cycle restarts.

    Outside our ScrollTweets function, we'll call it to get things started.

    The last part of twitterize is the hover events:

    	    list.hover(function () { 
    	    }, function () { 
    	}); // end of twitterlib call

    When we hover over our tweets list, we'll use the pauseanimate plugin to pause the animation temporarily. We'll use the resumeAnimation function in the mouseout function. That's the end of twitterize!

    Our last function will swap out the workers' names for their images.

    	iconizeWorkers : function (selector) { 
    	    $(selector).each(function (i, el) { 
    	        var el = $(el),  
    	            workers = el.text().split(' '), 
    	            imgs = ''; 
    	        $.each(workers, function (i, val) { 
    	            imgs += ''+ val + ''; 

    It isn't too complicated; we'll get the elements matching the selecor that's passed in and iterate over each of them. We get their content, spliting it at the spaces, and store that in an array called workers. Then, for each worker, we append an image to the imgs string, which uses the names to get the images. Then, we replace the names with the imgs string, using jQuery's html method.

    Of course, we have to call our methods from within our HTML:


    Step 4 Power it with PHP

    The final stretch: getting the data in with PHP. First let's get the project data from a database. This requires that's we've made a database. You're probably pretty familiar with the process: I fired up PhpMyAdmin and made a database called 'projects.' Then, I created a table called 'project_status.' This has four fields: id (the auto-incrementing primary key), name, status, and workers. Here's the data I put in:

    The function that gets this data isn't hard. Create a file called statboard.php and let's get into it!

    	function getProjects() { 
    	    $sql = new mysqli('localhost', 'root', '', 'projects')  or die('could not connect'); 
    	    $result = $sql->query("SELECT * FROM project_status"); 
    	    $html = ""; 
    	    while ($row = $result->fetch_object()) { 
    	        $name   = ucwords($row->name); 
    	        $status = ucwords($row->status); 
    	        $img    = str_replace(" ", "", $row->status); 
    	        $html .= "$name"; 
    	        $html .= "$name$status $status"; 
    	        $html .= "$row->workers"; 
    	    return $html; 

    First we create an instance of the mysqli class and connect it to our databases. Next, query the database for all the rows. We'll create a string called $html, which we'll return at the end. Then we'll use a while loop to go over each row that we recieved. In our loop, we set three variables. We get the name and status, and use the PHP function ucwords to capatlized the first letters of each word. Then, we get the status. We want to use this status as the name of the status icons, so we'll use str_replace to remove any spaces in the status label. Finally, we concatenate the table row together. These three lines will produce the same HTML we used when prototyping.

    Once we've looped through each row, we'll return the HTML string. To use this on our page, we'll first have to get statBoard.php:


    Then, we'll remove the table we hard-coded earlier and add this in its place:


    Our last job is to pull the schedule in from Google Calendar; first, you'll need to set your calendar to public; then, you'll need to get the XML feed for the calendar.

    We'll do this in two functions; let's begin the first:

    	function parseCalendarFeed($feed_url, $count = 4) { 
    	    $content = file_get_contents($feed_url); 
    	    $x = new SimpleXmlElement($content); 
    	    $entries = $x->entry; 
    	    $arr = array();

    Meet the parseCalendarFeed function; it takes two parameters: the feed URL, and the number of items we want to get. We start by getting the content from the URL and create a SimppleXmlElement with it. If you inspect that object, you'll see that the events on the caledar are within the entry element; we'll store that for later use. Finally, we'll created the array that we'll return at the end.

    	for ($i = 0; $i < count($entries); $i++) { 
    	    $item = explode("
    ", $entries[$i]->content); array_unshift($item, (string)$entries[$i]->title);

    Next, we'll loop over each item in $entries; we want to get the content element and the title element. There's a problem with the content element, however; it's formatted like this:

    	When : [date here] 

    Event Status: confirmed
    Event Description: [description here]

    So, we'll use the php explode method to break the it into an array, called $item, with each line as a member of the array. Then, we'll get the title of the entry and use array_unshift to add it to the front of $item.

    When that's done, we'll have an array that looks like this:

    	array ( 
    	    [0] => [event title] 
    	    [1] => When: [the date] 
    	    [2] => 
    	    [3] => Event Status: [the status] 
    	    [4] => Event Description: [the description] 
    	foreach($item as $k => $v) { 
    	    if ($k === 2 || $k === 3) { 
    	    } else { 
    	        $temp = explode(":", $v); 
    	        $item[$k] = (isset($temp[1])) ? trim($temp[1]) : $temp[0]; 

    Now we loop over each member of the $item array; we don't need members with index 2 or 3, so we'll unset those. For the rest of the items, we'll split them at the comma. We have to use a ternary expression to resset the value, though, because the first item (the title) doesn't have a comma. If there's a second item in the temporary array, we'll set the value in the $item array to the second part, which holds the info we want. If it doesn't have a second piece (which will be the case for the title), we'll use the first. Now our array looks like this:

    	array ( 
    	    [0] => [event title] 
    	    [1] => [the date] 
    	    [4] => [the description] 

    Perfect . . . except that the indices are incorrect.

    	        $item = array_values($item); 
    	        $item[1] = explode(" ", substr($item[1], 4, 6)); 
    	        $months = array('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'); 
    	        $item[1][0] = array_search($item[1][0], $months) + 1; 
    	        $item[1] = implode($item[1], '/'); 
    	        array_unshift($arr, $item); 
    	    } // end for 
    	    $arr = array_slice($arr, 0, $count); 
    	    return $arr; 
    	} // end parseCalendarFeed

    To take care of the indices, we use the array_values function we reset them. Now the only thing left is for format the date. We'd like the date to be in the format 'm/d.' The date has an index of 1 in our item array, so we'll set explode that member; but we don't explode the whole member. We get a substring of it, starting at character index 4, and going for 6 characters. This will give us the month name and the day number.

    We'll need to compare the month name against another array, so we create the $months array. Then, we set $item[1][0] (the month name) to the index of the month name in $months plus one. We use the function array_search to get the right index, and add one because the array is zero-based. Finally, we'll implode the array, which joins the members, passing in '/' as the separator. Now our date is correctly formatted. The last thing to do is put it into $arr; we use arrayunshift to put it onto the beginning of the array. Once we finish working with all the entries, they will be in the reverse order, which is what we want.

    Finally, we'll slice the array so it only containers the number of elements we want. Then we can return it!

    We use the parseCalendarFeed function in our getSchedule function:

    	function getSchedule() { 
    	    $feed = ""; 
    	    $events = parseCalendarFeed($feed); 
    	    $html = ""; 
    	    foreach($events as $event) { 
    	        $html .= "
  • $event[1] $event[0]"; if(isset($event[2])) { $html .= " $event[2]
  • "; } else { $html .= ""; } } return $html; }

    Inside this function, we store the feed URL and pass it to parseCalendarFeed; we'll start our $html string and then loop through each event. This will generate the same HTML structure that we used when prototyping; we have to check for the existence of details ($event[2]); if they are there, we add them; if not, we close the list item. Once we've looped through every one, we return the string $html.

    To call the calendar into the page, use this in place of the list items we hard-coded:

    	&ly;?php echo getSchedule(); ?>

    Step 5: Admire it with Pride!

    And that's it; that's our entire, working, data-pulling, twitter-scrolling status board! Hope you had fun, and let me know what you think.

    submit to reddit

    Similar content