Fork me on GitHub

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.3.2.min.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.3.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.js 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.

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

OK. 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(function() {
    this.element_selector = '#main';    

    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.js', 
    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.

Lets talk about client side templating. As of Sammy 0.3, templating is not built in, but its very easy to include. 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. There are a number of Javascript/Client side templating options out there, and I hope to have plugins to support them as soon as possible. Please fork and contribute!

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(Sammy.Template);

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 partial. Instead of just logging, lets render the items with our new template:

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

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 need to define a callback that just appends the rendered item to our $element (which we defined about as #main). 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, lets use Sammy’s before() method. We can pretty much move the code we have in our index route with a couple of small changes:

this.before(function() {
  // load the items
  var context = this;
  $.ajax({
    url: 'data/items.js', 
    dataType: 'json',
    async: false,
    success: function(items) {
      context.items = items;
    }
  });
});

The first difference is instead of rendering partials in the AJAX callback, we’re going to just assign the items to items in our context. This will allow us to retrieve them easily in the route callbacks. The second difference is a little more subtle. By setting the async option to false, we’re making sure this request executes synchronously and therefore blocks execution of the routes until its finished. This is definitely the easiest way to make sure that routes don’t get execute before data is loaded, but its not necessarily the best. We’ll go over better ways to do this in a later tutorial.

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. We dont have to supply and extra arguments to partial() because we can replace the entire content area and also use the item we’ve assigned to the context. 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(context.items, function(i, item) {
    context.partial('templates/item.template', {id: i, item: item}, function(rendered) {
      context.$element().append(rendered);
    });
  });
});

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!

Feel free to email me at aaron at quirkey dot com.

Please check out my blog.

If you like or use this library – I don’t want donations – but you can recommend me on workingwithrails.com or hire me to work on your next project.