Fork me on GitHub

THE SITE HAS MOVED TO Sammyjs.org. YOU ARE BEING REDIRECTED

Sammy

Tutorials

The JSON Store: A Sammy.js Tutorial, Part I

As promised I’m starting a series of tutorials for getting to know Sammy.js. Instead of starting huge I’m going to start pretty small and slowly build up. However, instead of starting at Hello World (which the documentation pretty well covers), lets jump into a somewhat more realistic use case. Lets party like its 1999 and build ourselves an E-commerce front end! (Don’t you love the word E-commerce? Its just like E-mail, but with more $$$.) We’ll call our shop The JSON Store (just to confuse people).

The goal of this little walkthrough is to build a very basic e-commerce front end. It wont do everything yet. We’ll get to everything later. What it will do:

  • load a selection of items from JSON
  • Display the items in a grid, with images
  • Allow you to click on the product to view more details about them

OK. So its not much. Its going to be a pretty basic run through, but hey! you might learn something.

All the code is on github (of course). You can follow along with the commits and see the app get built bit by bit.

First things first, lets build a simple directory layout. Right now we’re not going to be talking to any services or a backend, just flat files. Heres the structure I created:

$ tree the_json_store
|-data
|-javascripts
|---sammy
|-stylesheets
|-templates

The sammy directory is just a clone of the lib/ directory in the sammy repository.

$ git clone git://github.com/quirkey/sammy.git
$ cp -r sammy/lib the_json_store/javascripts/sammy

It’s a bunch of extraneous files, but thats OK, we’re not including all of them at one time. We also need jQuery:

cd the_json_store/javascripts
wget http://jqueryjs.googlecode.com/files/jquery-1.4.2.js

OK. Those are all the dependencies. Lets also create an empty JS file to store our app code. We could just include the code inside of script tags in our HTML, but I find its cleaner and easier to manage as an external file.

cd the_json_store/javascripts
touch json_store.js

Now lets get started by making a basic HTML thats empty except for our script includes:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>

  <title>The JSON Store</title>
</head>
<body>

<script src="javascripts/jquery-1.4.2.min.js" type="text/javascript" charset="utf-8"></script>
<script src="javascripts/sammy/sammy.js" type="text/javascript" charset="utf-8"></script>
<script src="javascripts/json_store.js" type="text/javascript" charset="utf-8"></script>
</body>
</html>

The main thing worth noting is the order that which we include the files. First jQuery, then sammy.js, then our app js. One last thing to note before we start coding – since we’re not using any sort of backend, I’ve created a simple JSON file that contains the info about the items in our store. For now, we’re selling CD’s and an item looks like:

{
  "title": "The Door",
  "artist": "Religious Knives",
  "image": "http://ecx.images-amazon.com/images/I/51og8BkN8jL._SS250_.jpg",
  "large_image": "http://ecx.images-amazon.com/images/I/51og8BkN8jL._SS500_.jpg",
  "price": 9.98,
  "url": "http://www.amazon.com/Door-Religious-Knives/dp/B001FGW0UQ/?tag=quirkey-20"
}

data/items.json is an array of 4 items with an identical structure.

Lets dig in and start actually coding our Sammy app. In our app file (javascripts/json_store.js) lets encapsulate our code in the module pattern so that we can prevent any scope leak and make use of local functions.

(function($) {

  $.sammy(function() {

  });

})(jQuery);

The first thing we want to do is handle an ‘index’. This should be the page thats loaded if a user first comes to our lovely store. Since we have a good idea at this point that the we’re at least going to maintain a header throughout the site and only the content area will change, lets add the header to our html:

<div id="container">
  <div id="header">
    <h1>The JSON Store</h1>
  </div>
  <div id="main">
  </div>
</div>

Now we have to tell our Sammy app to only worry about the ‘main’ area. You
do this by setting the app’s element_selector.

$.sammy(function() {
  this.element_selector = '#main';
});

That’s pretty explicit; It’s also possible to use this shorthand:

$.sammy('#main', function() {

});

For our ‘index’ we want to load all the items, then display each of them within the grid. Plan of attack:

  • create a route to handle the index
  • ajax fetch the item data
  • render each item using client-side templating

First step: create a route.

this.get('#/', function(context) {

});

This is the most basic route. When the browser hits ‘#/’ the function that we’ve defined will be executed and a Sammy.EventContext with additional info will be passed to context (the callback is also evaluated within the context of Sammy.EventContext, read more about routes in the Sammy docs). Lets just add a simple call to log to make sure our route is firing.

this.get('#/', function(context) {
  context.log('Yo yo yo');
});

log just logs to our firebug or web inspector console by default. So lets crack open our browser to index.html with firebug open and see if it works.

Oy, It doesn’t. Even if we change the URL to ‘index.html#/’ it still doesn’t send the log message. Quit banging your head. Theres a very good reason. Our Sammy app isn’t running! Sammy doesn’t run until you tell it to, and usually you want to tell it to run after the DOM has loaded. Lets add a call to run within a jQuery.ready callback. We’ll also pass it a ‘start_url’ which is the route we want it to load if no other route was specified. Our code should look like this:

(function($) {

  var app = $.sammy('#main', function() {

    this.get('#/', function(context) {
      context.log('Yo yo yo');
    });

  });

  $(function() {
    app.run('#/');
  });

})(jQuery);

Now, lets try again. Open up our browser and it should go directly to ‘#/’ and we should see a nice little timestamped message in our log:

Huzzah! Lets proceed to making our route actually useful. As we said, the first thing we want to do is load our item data via AJAX. We’ll get the data using $.ajax() and then pass it to another callback. In this callback well be iterating over the items. And for now, lets just log their names.

this.get('#/', function(context) {
  $.ajax({
    url: 'data/items.json',
    dataType: 'json',
    success: function(items) {
      $.each(items, function(i, item) {
        context.log(item.title, '-', item.artist);
      });
    }
  });
});

If all goes as plan, we should see our items print in the console:

OKAY! Now that we have our data, we want to actual render it so that our thousands of potential shoppers can see it.

Note about using Google Chrome (or Chromium) and AJAX

If you’re using a recent version of chrome and have followed the instructions up until this point, you might be scratching your head wondering why nothing seems to work, or at least the items dont seem to be fetched from the JSON. Since version 5, Chrome has blocked AJAX resquests to any protocol other than http://. Of course, this includes file://, so if you’re just running this code on your local machine the AJAX requests will seem to work, but just wont.

There’s an easy solution, however, just run a local web server. If you’re coding in a Django, Sinatra, or Rails app, this shouldn’t be a problem. If you just want to serve files from a directory, there are a couple options. If you’re on OS X, you have Apache built in. Start Web Sharing in your Sharing Preferences and put you’re files under the Apache www directory. To make it even easier, I’ve included a simple ruby script called ‘servefiles’ that will start a ruby web server in the directory.

As of Sammy 0.6 there’s a whole new way to load data, interpolate it, and add it to the screen – the RenderContext. The RenderContext tries to abstract away the problems of complex asynchronous templating, by creating a simple way to chain commands. The chaining looks a lot like jQuery’s own syntax, but RenderContext actually is doing a lot behind the scenes to make sure that each command happens in order whether its asynchronous or not. This means that you can fetch the data, interpolate it, and render it to the screen without ever making a callback. Sounds pretty nifty, eh?

Lets start by changing the $.ajax() call into a call to load(). load will create a RenderContext then load the data at the URL given and pass it to the next command in the chain. In our case we can just make this a call to then().

this.get('#/', function(context) {
  this.load('data/items.json')
      .then(function(items) {
          $.each(items, function(i, item) {
            context.log(item.title, '-', item.artist);
          });
      });
});

then() is one of the building blocks of RenderContext that all the other commands use. It tells the RenderContext to execute the callback as soon as the previous operation is done, passing the results as an argument. For example, we could chain as many then()s as we want, returning something different each time:

this.load('data/items.json')
    .then(function(items) {
      this.log(items); //=> [{title: ...}]
      return items[0];
    })
    .then(function(item) {
      this.log(item); //=> {title: ...}
      return item.title;
    })
    then(function(title) {
      this.log(title); //=> "The Door"
    });

Obviously the above example is useless, but you can see how we can refine or change content and then pass it down the chain. To learn more about RenderContext, check out the API Documentation

By changing our original AJAX call to load() we’ve also gained parsing of the JSON, simply by using the .json extension. That tells Sammy to use the JSON ‘engine’ to parse the file, which in this case turns it from a JSON string, to a JS object.

Now that we have the data and a RenderContext to work with, lets make a template and interpolate the data. To do this we have to pick a templating engine and install the Sammy plugin for it. There are a lot of great options for client side templating these days – Mustache, EJS, HAML, Pure, Meld – I suggest when writing an app, you check them all out and evaluate which of them fits your needs. For the purposes of this tutorial, we’re going to pick an old and trusted friend: Sammy.Template. The Sammy.Template plugin provides us with a simple method based on John Resig’s templating code and Greg Borenstein’s $.srender that allows us to render HTML with embedded javascript and variables.

In order to use Sammy.Template we need to include the plugin in our HTML and then ‘use’ it in our app.

In index.html:

<script src="javascripts/sammy/plugins/sammy.template.js" type="text/javascript" charset="utf-8"></script>

In our app:

this.use('Template');

Using 'Template' as a string above is a shortcut for saying include the Sammy.Template object.

Two lines later, we got templates! Lets create a simple template for an item.
In templates/item.template:

<div class="item">
  <div class="item-image"><img src="<%= item.image %>" alt="<%= item.title %>" /></div>
  <div class="item-artist"><%= item.artist %></div>
  <div class="item-title"><%= item.title %></div>
  <div class="item-price">$<%= item.price %></div>
</div>

Here we’re going to interpolate a single item and print out some of its data. Now that we have our template, lets render it. The method for rendering remote templates in Sammy is render. Instead of just logging, lets render the items with our new template:

$.each(items, function(i, item) {
  context.render('templates/item.template', {item: item})
         .appendTo(context.$element());
});

Here we’re telling the context to fetch the template at the path we’ve defined. By using the extension ‘template’ we’re informing Sammy to use the template() method we’ve defined. Next, we’re passing local variables to the template. Here its our our item. Because we’re not swapping out the entire content area, we use .appendTo() to append the rendered content to our app’s $element. With our new partial we should see our items displaying! (NOTE: I took a brief but inconsequential interlude to add just a teeny bit of stylesheet so that our page isn’t so ugly).

Theres just one more missing element for our first phase. Clicking on an item to view its detail. We know how to start though right? Lets define a route for an item detail.

this.get('#/item/:id', function(context) {

});

Here we’re using the special :id interpolation in our route path. This allows us to pull the id for the item into the params object. For the sake of simplicity, were going to say that an item id is its index in the items array. This means that before we can load the details, we need to load the items data again. Instead of a dreaded copy and paste, lets use the opportunity to refactor a bit. Since we know were going to need to load the items before each route, we could use the before() method to run something before each route runs. However, this won’t work because we’re not just doing anything before, we’re making an asynchronous request to the server/filesystem and then when its complete, we want to move on to the route. For this set of problems, we have around(). around() works similar to before, but instead of just proceeding to the route, we have to let Sammy know to move on to the next operation. In our case, this would look something like:

this.around(function(callback) {
  var context = this;
  this.load('data/items.json')
      .then(function(items) {
        context.items = items;
      })
      .then(callback);
});

First, we load the items.json as we did before, then we assign the items into our EventContext which is a shared context between the filters and the route. The first argument to the around callback is the next callback in the chain, or in this case the route. Once we’ve assigned the items, we can queue the callback, making sure that the items are always loaded before we run our route code.

Now that we have the item data loaded in our context, lets pull out the item we want and render a detail view.

this.get('#/item/:id', function(context) {
  this.item = this.items[this.params['id']];
  if (!this.item) { return this.notFound(); }
  this.partial('templates/item_detail.template');
});

Pretty simple, ay? For demonstration sake, here I’m using this instead of context to show that in a route callback, they’re equivalent. Its important to mention though, if your nesting callbacks, this will most likely change, which is why using the context from the arguments is rather helpful.

We pull item item out of the array, assign it to item and then render a new item_detail template. This time, instead of using render(), we use partial() which internally calls render and then swaps our the content of the entire app element. The template code looks roughly the same as the index item template, except we’re going to use the large image and add a link to Amazon and a link back to the index:

<div class="item-detail">
  <div class="item-image"><img src="<%= item.large_image %>" alt="<%= item.title %>" /></div>
  <div class="item-info">
    <div class="item-artist"><%= item.artist %></div>
    <div class="item-title"><%= item.title %></div>
    <div class="item-price">$<%= item.price %></div>
    <div class="item-link"><a href="<%= item.url %>">Buy this item on Amazon</a></div>
    <div class="back-link"><a href="#/">&laquo; Back to Items</a></div>
  </div>
</div>

We’re missing one more thing here: A way to access the detail! Thats not entirely true actually, if we type ‘#/item/0’ in our Location bar, we should be greeted with a rather large contemporary psychedelic rock album cover:

Let’s just link up the image to our detail page:

<div class="item-image">
  <a href="#/item/<%= id %>"><img src="<%= item.image %>" alt="<%= item.title %>" /></a>
</div>

And then make sure we interpolate the id into our template:

this.get('#/', function(context) {
  $.each(this.items, function(i, item) {
    context.render('templates/item.template', {id: i, item: item})
           .appendTo(context.$element());
  });
});

Now when we load our index page up, we should be able able to click to an item and even go back to the index. Our back and forward buttons should work great, too! One last bug to fix: When we go back to the index after loading a detail, the detail is still there! This is because of how we’re loading the item partials, instead of using the app’s swap() method, we’ve been appending each partial to the content area. The fix is a single line. We just need to clear the content area before loading the partials:

this.get('#/', function(context) {
  context.app.swap('');
  // ...
});

So after writing more HTML than Sammy code, we have a very simple single page app that loads JSON data, renders HTML using client side templating, and lets us link between multiple different routes dynamically. Not too shabby. Next time we’ll learn how to use post() routes to handle form submissions and more!

Go on to Part II Now