In this tutorial you will learn how Sammy renders a Mustache template and then load and interpolate the template. In addition, you will use Sammy and templates as Asynchronous Module Definition (AMD) modules.
The tutorial builds on the previous postings Getting Started with SammyJS – Routes, where you learned you can use Sammy to provide client side routing, and Loading JSON Using Sammy where you learned how to load JSON data using sammy.load().
This tutorial goes beyond the getting started with Sammy tutorial, JSON Store, provided in Sammy’s documentation. In this tutorial you will learn what happens behind the scenes with each of the important calls. The idea is to help you choose the right Sammy calls as your application gets more complex.
Getting Started
You will need jQuery, Mustache, Sammy, Sammy.Mustache from the Sammy Plugins. The links take you to where get the libraries. Also jQuery, Mustache, and Sammy.js are available on NuGet.
Place the libraries in a folder named Scripts.
Sample Data
Here is some sample data that goes in a file named Products.txt in a Data folder.
Data/Products.txt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"products": [ | |
{ | |
"id": 1, | |
"name": "Toy1", | |
"category": "Toy", | |
"price": 45.05, | |
"description": "<p>This is <strong>strong</strong> description Toy1</p>" | |
}, | |
{ | |
"id": 2, | |
"name": "Toy2", | |
"category": "Toy", | |
"price": 35.20, | |
"description": "<p>This is an <em>emphasis</em> description of Toy2</p>" | |
} | |
] | |
} |
Sample Templates
Let’s start with the template. Create the file named productsList.html in Templates folder.
Templates/productList.html
https://gist.github.com/devdays/418696e90decdb0ad701
and another for a single product productDetail.html in Templates folder.
Templates/productDetail.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!– product {{id}} –> | |
<h3>{{name}}</h3> | |
<div>Category: {{category}}</div> | |
<div>Price: ${{price}}</div> | |
<div>Description: {{&description}}</div> | |
<div>Description: {{{description}}}</div> |
Note that for the demo we are using Mustache triple brackets to render unescaped HTML, use the triple mustache: {{{name}}}
. You can also use &
to unescape a variable.
The template will display as:
Getting Started HTML
Begin with the following starting point for our single page app. It uses needs Mustache and Sammy.
https://gist.github.com/devdays/b98b817de4b8fe7f4f7
In this tutorial, you are using sammy.app.get(), which is an alias for route(‘get’, … For more information on routes, see Sammy Routes.
Display Products
Replace this.get(‘#/products’, route with the following code:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
this.get('#/products', function (context) { | |
var products = context.items; | |
context.app.swap(''); // clear out the $element in this case '#content' | |
context.render('templates/productsList.html', products) | |
.appendTo(context.$element()); | |
}); |
The products data was loaded with sammy’s around() method that you learned about in a previous post. The data has been cached, so the data is retrieved once. In our implementation of loading the data, our JSON data has been put into the context.items property for use in the routes.
sammy.app.use
sammy.use() tells the application the entry point for Sammy plugins. The first argument to use should be a function() that is evaluated in the context of the current application.
For example:
this.use('Mustache', 'html');
If plugin name is passed as a string Sammy assumes you are trying to load the function names Sammy.Mustache.
In sammy.mustache.js, the plugin code makes the Sammy.Mustache method public and available to the application when it is time to use a template.
The second parameter tells Sammy to accept the .html extension as the one to use with the Mustache template.
$element, sammy.app.swap
Note the sammy.app.swap() method takes the text of the first parameter and makes it the new html in the $element. In this example, you clear the content of $element.
context.app.swap('');
$element represents the HTML element that we assigned when we created the sammy app. In this case you used the jQuery selector #content when you called $.Sammy('#content'
.
You can also set a new $element
by assigning a new selector to sammy.app.element_selector.
Overriding Swap
Optionally you could provide your own swap.
For example, you could provide a function to fade out and fade in new content.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// implements a 'fade out'/'fade in' | |
this.swap = function(content, callback) { | |
var context = this; | |
context.$element().fadeOut('slow', function() { | |
context.$element().html(content); | |
context.$element().fadeIn('slow', function() { | |
if (callback) { | |
callback.apply(); | |
} | |
}); | |
}); | |
}; |
context.render
Sammy assumes you will want to render data using a template in just a few lines of code. It will automatically loads the template from a location that you specify, apply the data, call the template engine (in our case, the Mustache plugin) and returns you the HTML result.
The solution provides for great flexibility, if you understand the default behavior.
sammy.render() is a shortcut that you can use to can be called with multiple different signatures:
- this.render(callback);
- this.render(‘/location’);
- this.render(‘/location’, {some: data});
- this.render(‘/location’, callback);
- this.render(‘/location’, {some: data}, callback);
- this.render(‘/location’, {some: data}, {my: partials});
- this.render(‘/location’, callback, {my: partials});
- this.render(‘/location’, {some: data}, callback, {my: partials});
It takes the parameters and then, in order:
- .loadPartials(partials)
- .load(location)
- .interpolate(data, location)
- .then(callback);
If you need finer control, you can use the sammy functions yourself to replace render. Here’s a summary of what each function does.
Note: as an alternative to calling swap and then calling render to put the content into $element, you could also call partial() which combines render and swap into the $element as a single method.
context.loadPartials()
This takes the path to template that can be called from within the template. An example is shown in the Sammy documentation here.
context.load()
sammy.load() is most often used to load templates into the context. Load has three parameters:
- The name of the remote file, jQuery object, or DOM element
- The options. You can specify that you are retrieving JSON or you can also specify to not cache the data loaded.
- The callback that is executed after the template load.
If a jQuery or DOM object is passed the innerHTML of the node is used. This is useful for nesting templates as part of the initial page load wrapped in invisible elements or <script> templates.
You can specify options.
{ json: true, cache: false }
JSON data is automatically cached in the templateCache unless you specify cache as false. The default is to cache the incoming data. The data is stored in the sammy.app.templateCache().
The callback is the function you execute after the load. Use .then to get the data returned from the load.
context.interpolate()
context.interpolate() takes the template, looks up the templating engine that you want, applies the data to the template using that engine, and returns the HTML.
context.then()
context.then() puts functions into a stack that are called once a long-running operation is completed. So when you use Sammy’s load function, you use then to respond once the load has completed.
Note that sammy is not really executing a promise. In version 0.7.4 there is not a fail method.
If then() is passed a string instead of a function, Sammy will looked for a helper method on the event context.
context.appendTo(), prependTo(), replace()
context.appendTo() inserts the set of matched elements to the end of the target.in a way that insures the right order. It uses jQuery.appendTo.
In our sample, it takes the result from render and then appends it to the context.$element.
prependTo() inserts every element in the set of matched elements to the beginning of the target. It uses jQuery.prependTo.
replace() to replace the content of the selector using jQuery.html().
Display Product
Replace this.get(‘#/product/:id’, route with the following code:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
this.get('#/products/:id', function (context) { | |
var param = context.params['id']; | |
var products = context.items.products; | |
if (!products[param]) { | |
return context.notFound(); | |
} | |
context.partial('Templates/productDetail.html', products[param]); | |
}); |
Note for the use in this tutorial, you are using the array offset, and not the product id value to find the product. (See the next tutorial on how to find a product using your own matching function using LoDash.)
You can retrieve the products from the context.items property that you defined in your implementation of this.around().
context.params[]
Returns the value of the URL parameters.
context.partial()
context.partial() first calls sammy.context.render(), which is explained in a previous section, and then calls swap(), also explained in a previous section.
It assumes you want to replace the content of $element.
HTML Tutorial Example
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html xmlns="http://www.w3.org/1999/xhtml"> | |
<head> | |
<title></title> | |
</head> | |
<body> | |
<nav> | |
<ul> | |
<li><a href="#/">Home</a></li> | |
<li><a href="#/products">Products</a></li> | |
<li><a href="#/products/1">Product 1</a></li> | |
<li><a href="#/data">Data</a></li> | |
</ul> | |
</nav> | |
<div id='content'></div> | |
<script src="Scripts/jquery-1.9.1.js"></script> | |
<script src="Scripts/mustache.js"></script> | |
<script src="Scripts/sammy-0.7.4.js"></script> | |
<script src="Scripts/sammy.mustache.js"></script> | |
<script> | |
// ====== set up require.js here in a later step ====== | |
(function () { | |
"use strict"; | |
// this line changes to | |
// var app = sammy('#content', function () { | |
// without the reference to jQuery when we use requireJS | |
var app = $.sammy('#content', function () { | |
// first param is a function name | |
this.use('Mustache', 'html'); | |
// the callback is the entire route wrapped in a closure | |
this.around(function (callback) { | |
var context = this; | |
this.load('data/products.txt', { json: true }) | |
.then(function (items) { | |
context.items = items; | |
}) | |
.then(callback); | |
}); | |
this.get('#/', function (context) { | |
context.app.swap(''); // clear the content area before loading the partials | |
context.$element().append('<h1>Main page</h1>'); | |
}); | |
this.get('#/data', function (context) { | |
context.app.swap(''); // clear the content area before loading the partials | |
context.$element().append(JSON.stringify(context.items)); | |
}); | |
this.get('#/products/:id', function (context) { | |
// render product here | |
}); | |
this.get('#/products', function (context) { | |
// render products here | |
}); | |
}); | |
$(function () { | |
app.run('#/'); | |
}); | |
})(); | |
</script> | |
</body> | |
</html> |
AMD Using RequireJS With Sammy
You may want to use Sammy use as a module Asynchronous Module Definition (AMD) for JavaScript modules.
You will need to make just a couple changes.
First, you will need require.js which you can download RequireJS on its Website or get RequireJS on NuGet. And you will need to load require. You will use RequireJS to load jQuery, Sammy, Mustache, and the Sammy.Mustache plugin. The Sammy.Mustache plugin is already a AMD module that requires Mustache.
Also, when you start Sammy, you no longer need $.sammy
. Use sammy()
instead.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<title>Sammy+Mustache+RequireJS</title> | |
</head> | |
<body> | |
<nav> | |
<ul> | |
<li><a href="#/">Home</a></li> | |
<li><a href="#/products">Products</a></li> | |
<li><a href="#/products/1">Product 1</a></li> | |
<li><a href="#/data">Data</a></li> | |
</ul> | |
</nav> | |
<div id='content'></div> | |
<script src="Scripts/require.js"></script> | |
<script> | |
// ====== set up require.js ================ | |
(function () { | |
"use strict"; | |
require.config({ | |
baseUrl: 'Scripts', | |
paths: { | |
//"underscore": "lodash", | |
"jquery": "jquery-1.9.1", | |
//"q": "q", | |
"sammy": "sammy-0.7.4", | |
"mustache": "mustache", | |
"mustachePlugin" : "sammy.mustache" | |
}, | |
shim: { | |
// we get an error that "jQuery is not defined" error without this | |
// shim for sammy | |
"sammy": { | |
deps: ["jquery"], | |
exports: "sammy" | |
} | |
} | |
}); | |
})(); | |
require(['sammy', 'mustachePlugin'], function (sammy) { | |
"use strict"; | |
var app = sammy('#content', function () { | |
// first param is a function name | |
this.use('Mustache', 'html'); | |
// the callback is the entire route wrapped in a closure | |
this.around(function (callback) { | |
var context = this; | |
this.load('data/3-products.txt', { json: true }) | |
.then(function (items) { | |
context.items = items; | |
}) | |
.then(callback); | |
}); | |
this.get('#/', function (context) { | |
context.app.swap(''); | |
context.$element().append('<h1>Main page</h1>'); | |
}); | |
this.get('#/data', function (context) { | |
context.app.swap(''); | |
context.$element().append(JSON.stringify(context.items)); | |
}); | |
this.get('#/products/:id', function (context) { | |
var param = context.params['id']; | |
var products = context.items.products; | |
if (!products[param]) { | |
return context.notFound(); | |
} | |
// partial() internally calls render and swap | |
// creates the html and puts it into $element | |
context.partial('Templates/productDetail.html', products[param]); | |
}); | |
this.get('#/products', function (context) { | |
var products = context.items; | |
context.app.swap(''); | |
context.render('templates/productsList.html', products) | |
.appendTo(context.$element()); | |
}); | |
}); | |
$(function () { | |
app.run('#/'); | |
}); | |
})(); | |
</script> | |
</body> | |
</html> |
Sample Code
Sample code for this post is available in the DevDays GitHub repository at https://github.com/devdays/single-page-app/tree/master/Sammy
You can find the example in 6a-RequireSammyTemplates.html.