Official website for Web Designer - defining the internet through beautiful design
FOLLOW US ON:
Author: Steve Jenkins
12th March 2013

Create stunning timelines with TimelineJS

Timelines are complicated but effective tools for showing large amounts of data. TimelineJS removes the pain

Create stunning timelines with TimelineJS

TimelineJS (timeline.verite.co) is an easy-to-use JavaScript library, both for user and developer. It can be as easy as embedding an iframe onto your page using the embed generator with a Google Doc spreadsheet, or as complicated as dynamically adding in your own events based on external sources; we’re going to opt for the latter.

TimelineJS currently has native support for Twitter, Flickr, Google Maps, YouTube, Vimeo, Dailymotion, Wikipedia and SoundCloud. We’ll be using Twitter, Flickr and YouTube to show favourites over time, to give an overview of when and what you like, although it wouldn’t be hard to adapt this to only show the last seven days. Don’t let this limit you, though: it’s perfectly suited to showing real-time events and has been used on news sites (such as the venerated French newspaper Le Monde – bit.ly/I3q2f5) to show how political events unfolded.

If you’re looking for an easy way to include a timeline in your site, this may well be perfect. It even includes multilingual support for 31 languages. To top it all off, it’s undeniably good-looking and includes options to use 14 different fonts if the stock one doesn’t take your fancy.

DOWNLOAD TUTORIAL FILES

Create stunning timelines with TimelineJS

Git It

To get started, use Git to clone TimelineJS into your project. Or, if you’re not a Git aficionado, download a compressed copy from GitHub (github.com/VeriteCo/TimelineJS). It includes all of the CSS, images, and JavaScript that we’ll need to get started.

001 $ git clone git://github.com/VeriteCo/
002 TimelineJS.git

Directory structure

Now that we’ve got the files, we’re going to go ahead and set up a basic timeline. The directory structure is important because TimelineJS looks in specific places for files. You can configure the locations yourself, if you don’t have a say over directory structure, in the constructor with {css: ‘/path/to/css’, js: ‘path/to/js’}.

001 index.html
002 |– js
003     +– timeline.json
004     +– app.js
005 |– timeline
006     |– css
007     |– js
008     |– lib

Link to scripts

TimelineJS’s only dependency is jQuery, so
grab the latest version from the jQuery site or use the version included with TimelineJS. Also, link to the file ‘storyjs-embed.js‘ — and that’s it! Despite the plethora
of files and complexities within TimelineJS, that’s the
only one you have to explicitly include – it loads the others itself.

001 <script src=”timeline/lib/jquery-min.
001 js”></script>
001 <script src=”timeline/js/storyjs-embed.
001 js”></script>

Create a story

To create a new timeline, we call the createStoryJS constructor and pass it an object with some configuration details. These are the only required field, but there’s potential for many more. We’ll write the source JSON file that it points to in a moment, but make sure that it points to the right place on your own setup.

001 $(document).ready(function() { 
002     createStoryJS({
003         width: ‘800’, 
004         height: ‘600’, 
005         source: ‘js/timeline.json’, 
006         embed_id: ‘my-timeline’ 
007     }); 
008 }); 

Make a container

The embed_id property in the previous step is the ID of the node you want to attach it to. It can belong to any type of block-level element (divs, sections etc) so long as the IDs match up. Inside this element is where TimelineJS will append all of its own content.

001 <section id=”my-timeline”></section>

The timeline object

All we need now is a smattering of JSON to populate our timeline. If you’re familiar with the
syntax then you’ll be just fine. If not, have a look at developer.mozilla.org/en/docs/JSON to get acquainted. The empty date array will hold all the events in our timeline. If you get the text ‘Loading timeline’ for a long time, make sure your JSON
validates at jsonlint.com.

001 {“timeline”:{ 
002     “headline”:”My Week”,
003     “type”:”default”,
004     “text”:”This is a collection of my     favourite things over the last week”,
005     “startDate”:”2012,12,19”,  
006     “date”: [{“startDate”:”2013,04,10”,
007         “endDate”:”2013,04,11”,
008         “headline”:”Tweet, tweet!”,
009         “text”:”<p>I like this
010  tweet</p>”,
011         “asset”: {
012             “media”:”https://twitter.com/    webdesignermag/status/279240825651752961”,
013             “credit”:”Web Designer”,
014             “caption”:””
015 }}]}}

Fullscreen

Let’s reconfigure TimelineJS and make it take up the full height and width of the browser. Simply replace the width and height properties with innerWidth and innerHeight, or $(‘body’).width() and $(‘body’).height() if you prefer to use jQuery. For extra brownie points you can add a resize listener so that it always adapts to the browser’s window.

001 width:  window.innerWidth,
002 height: window.innerHeight,

App structure

We’re going to structure our app so all the requests are self-contained in one function, and we’ll access each service as we need it, like populateTimeline.twitter() using Deferred objects (for an look at Deferred objects, see issue 203). As we add more services, remember to return them so that we can access them.

001 var populateTimeline = (function() { 
002     function twitter() {}
003     return { 
004         twitter: twitter 
005     }; 
006 })(); 

Get Twitter data

We could enter the data in manually, but that would be tedious and prone to error. Also, VéritéCo called JSON users nerds and we’d hardly be nerds if we did that. Instead, we’ll write a script to get our last 20 tweets and put them on a timeline using jQuery. Start with the basic JSON call within twitter().

001 return $.Deferred(function() {
002     var deferred = this;
003     $.getJSON(“https://api.twitter.com/1/
statuses/user_timeline/USERNAME.json”, 
function(data){
004     });
005 };

Dynamic timeline object

Outside of populateTimeline(), set up a basic timeline object that looks like this. It’s as minimal as we can be and currently won’t work, as we need to add events to the date array. The headline and text are the first pieces of text that the user will see, a bit like a slideshow’s title slide.

001 var timeline = {
002     “timeline”: {
003         “headline”:”My Fav Things”,
004         “type”:”default”,
005         “text”:”This is a collection of my     favourite things about me”,
006         “date”: [],
007     }
008 };

Tweet loop

Within the callback of the JSON call, we’ll loop through the response data and construct our own date event. The headline is just the first word in the tweet and the addition of 1 to the month is because it’s 0 based (0–11 instead of 1–12 as we humans are used to).

001 var tweets = data.length; 
002    for (var i = 0; i < tweets; i++) {
003     var timestamp = Date.parse(data[i].    created_at),
004         date = new Date(timestamp),
005         month = date.getMonth() + 1;
006     var tweet = {
007         headline: data[i].text.split(‘ ‘)[0],
008         text: ‘’,
009     };
010 }

Flesh out asset

Even though we’ve just made a call to Twitter, TimelineJS takes very specific data so we’ll build up what we need with the data we’ve just received. The startDate and endDate look like ‘2013,1,14’ – the American date format – but we get a human-readable one so we make a timestamp out of it and call the relevant methods.

001 asset: {
002     media: ‘https://twitter.com/’ + data[i].
user.screen_name + ‘/status/’ + data[i].id_str,
003     credit: ‘’,
004     caption: ‘’
005 },
006 startDate: date.getFullYear() + ‘,’ + month + ‘,’ + date.
007 getDate(),
008 endDate: this.startDate

Resolve it

With our tweet complete, we can then push it to the date array which TimelineJS reads to create the timeline. Despite being JSON, we can do this the same way we do with any old array. We then close the for loop and, once everything has been completed, resolve the deferred object, basically saying ‘I’m done now’.

001         timeline.timeline.date.push(tweet); 
002     } 
003 deferred.resolve();

Then create

We’ve now got our latest tweets and updated the timeline object, now all we need to do is make sure the timeline initiates only after we’ve got those tweets. We do this with the jQuery when() and then() methods. It’s quite an expressive way of saying ‘when I’ve got tweets, then do this’.

001  $.when(populateTimeline.twitter()).    then(function() { 
002     createStoryJS({.
003         width: window.innerWidth,
004         height: window.innerHeight,
005         source: timeline,
006         embed_id: ‘my-timeline’
007     });
008 });

Tweets over time

We’ve got the tweets and initiated the timeline, all that’s left is to refresh the browser and you should see a bevy of tweets appear! TimelineJS automatically sets the start date and end dates, even when they’re not in order in the JSON file, which saves us a lot of hassle; it also chooses whether to show hours or days.

Request YouTube

Now that we’ve got Twitter support down, we’re going to use the same approach to get a user’s favourite videos from YouTube. You’ll have to replace USERNAME with your own username. By default, YouTube serves XML so we have to specify that we want to receive JSON with the alt GET variable.

001 function youtube() {
002     $.get(‘https://gdata.youtube.com/
feeds/api/users/USERNAME/favorites?alt=json’, 
function(data) {
003     });
004 }

Prep data

For YouTube, we have to do a bit of prepping of the data so that TimelineJS can consume it correctly. Timeline is expecting a URL that looks like:
‘http://youtu.be/ID’ but the API doesn’t give us this, so we have to deconstruct the URL to gain the video ID and then append it to the media property.

001 for (var i=0; i < data.feed.entry.length;     i++) {
002     var url = data.feed.entry[i].media$group.media$player[0].url,
003     taintedId = url.substr(url.        indexOf(‘?v=’), 14),
004     id = taintedId.replace(‘?v=’, ‘’),
005     timestamp = Date.parse(data.feed.    entry[i].published.$t),
006     date = new Date(timestamp),
007     month = date.getMonth() + 1;
008 }

Push the video

Unlike Twitter, we can add extra information like credit and caption which gets added to each video. We’ve set the credit to be the uploader’s username (this appears directly below the video) and the caption to the description given to the video. It’s these extra details that TimelineJS displays very nicely and can give the user extra information.

001 var video = { 
002     headline: data.feed.entry[i].title.$t,
003     text: ‘’, 
004     asset: {
005         media: ‘http://youtu.be/’ + id,
006         credit: data.feed.entry[i].author[0].    name.$t,
007         caption: data.feed.entry[i].content.$t
008     },
009     startDate: date.getFullYear() + ‘,’ +     month + ‘,’ + date.getDate(), 
010     endDate: this.startDate
011 };
012 timeline.timeline.date.push(video);

Flickr support

We’ve got tweets and we’ve got video content, what could be better? Still images you say? Go on then, let’s add in our Flickr favourites too. This is a bit more complicated because it requires a function called jsonFlickrApi to exist, so we need to cater for this. We’ll call a function within populateTimeline() to push photos to the timeline.

001 function jsonFlickrApi(response) { 
002     populateTimeline.
003 flickrCallback(response); 
004 }

Flickr JSONP call

As a result of using deferred objects, we can’t make a normal AJAX request with jQuery because that would cause the function to resolve before we want it to. Instead, we’ll make a script tag with the src attribute set to the Flickr API call we wish to make. Once the callback function has finished, we set flickrReturned to true and resolve flickr().

001 function flickr() {
002     return $.Deferred(function() {
003         var deferred = this;
004         var script = document         createElement(‘script’);
005         script.src = ‘http://api.flickr.    com/services/rest/?method=flickr.favorites.    getPublicList&format=json&api_key=API_KEY&user_    id=USER_ID’; 
006         document.body.
007 appendChild(script); 
008         setInterval(function() {if         (flickrReturned) deferred.resolve();}, 200); 
009     });
010 }

Callback function

The flickrCallback function will add our Flickr favourites to the timeline. We multiply the date by 1,000 to convert it from seconds to milliseconds (this is what JavaScript expects a Date object to be in). This should all look familiar: we’re using the same approach but accessing different properties for the different services.

001 function flickrCallback(response) {
002 for (var i=0; i < response.photos.photo.    length; i++) {                        003         var photo = response.photos.photo[i],
004             date = new Date(photo.date_faved *     1000),
005         var flickrPhoto = {
006             headline: photo.title,
007             asset: {
008                 media:’ HYPERLINK “http://www.    flickr.com/photos/’” http://www.flickr.com/    photos/’+photo.owner+ ‘/’+photo.id,
009             }
010         };
011         timeline.timeline.date.        push(flickrPhoto);
012     }
013     flickrReturned = true;
014 }

Populating the timeline

Finally, make all the different calls at the same time; the then() callback won’t fire until all three requests have been made and added their assets to the timeline JSON. When you refresh the page you’ll see all your past favourites in a timeline, each displayed in a unique way.

001 $.when(populateTimeline.twitter(),     populateTimeline.flickr(), populateTimeline.    youtube()).then(function() {
002        createStoryJS({
003            width: window.innerWidth,
004         height: window.innerHeight,
005            source: timeline,
006         embed_id: ‘my-timeline’
007     });
008 });

  • Tell a Friend
  • Follow our Twitter to find out about all the latest web development, news, reviews, previews, interviews, features and a whole more.
    • john_fischer

      Nice Article, wondering if there is a way we can only keep the navigation bar at the bottom and hide the sliders.