Single Page Apps – Writing a LoDash/Underscore Plugin for SammyJS

Sammy.jsAlthough SammyJS is a router that provides you with file loading of data and templates. You load templates and data using Sammy’s plugins.

In this tutorial, you will learn how you can use sammy.load to load JSON data, and then use LoDash (or Underscore) to _.find() to retrieve the item based on the value provided in the sammy route. And you will combine the template and data using a custom Sammy plugin.

Why LoDash?

LoDash or Underscore provide great methods for working with collections and arrays. There are subtle differences in these two libraries. But for this tutorial, they provide the same functionality.

Use these libraries to “slice and dice” your data. In the case of this tutorial, you will use _.find(). In your real life applications, there will be more complex ways of manipulating your data, that LoDash can provide.

LoDash includes _.template(). The template method compiles a set of HTML code and turns it into JavaScript. The templates can include _ and complex JavaScript functions.

For a tutorial on how to build LoDash templates, see HTML Templates With Logic Using Underscore, LoDash.

Getting Started

You should already know how to build LoDash templates and how load templates and data into Sammy.

For this tutorial, you will need:

Place the libraries in a folder named Scripts

And you will need starter code that includes data and templates and a starter page for your HTML.

Data/products.txt


{
"products": [
{
"id": 1,
"name": "Squirt gun",
"category": "Toy",
"price": 45.05,
"description":
"<p>This is a <strong>squirt</strong> gun. Shoots water.</p>"
},
{
"id": 2,
"name": "Action figure",
"category": "Toy",
"price": 65.96,
"description": "<p>This is an extraordinary action figure.</p>"
},
{
"id": 3,
"name": "Doll",
"category": "Toy",
"price": 35.68,
"description":
"<p>This is a <emphasis>doll</emphasis> for your doll house.</p>"
},
{
"id": 4,
"name": "Lettuce",
"category": "Grocery",
"price": 3.49,
"description": "<p>This is a vegetable.</p>"
}
]
}

view raw

ojs-data.json

hosted with ❤ by GitHub

Templates/product.html


<div><strong><%- product.name %></strong>
<span>( Cateogry: <span class="value"><%- product.category %></span> )</span></div>
<div>Description: <% product.description %></div>
<div>Equal Description: <%= product.description %></div>
<div>Price: <%- product.price %></div>

Templates/products.html


<ul>
<% _.forEach(products, function(product) { %>
<li>
<strong><%- product.name %></strong>
<span>( Cateogry: <span class="value"><%- product.category %></span> )</span>
<span>( Link: <a href='"#/products/<%- product.id %>'>
<%- product.name %> Details</a> )</span>
<div>Description: <%= product.description %></div>
<div>Price: <%- product.price %></div>
</li>
<% }); %>
</ul>

The starter HTML

The code loads the Sammy plugin that you will write, adds the plugin so Sammy can use it, and then runs the routes.

The code in yellow, loads the Scripts/sammy.lodash.js file that you will write, and uses the sammy.use method to make it available inside Sammy. The Sammy.use() use takes the plugin function you defined and evaluates it within the context of the current application.

<!DOCTYPE html>
<html>
<head>
  <title>Products Using Custom LoDash Plug-in for Sammy</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>
    </ul>
  </nav>

  <div id='content'></div>

  <script src="Scripts/jquery-1.9.1.js"></script>
  http://span
  http://span
  http://span
  <script>
    (function () {
      "use strict";
      console.log("initializing sammy");

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

         this.use('LoDash','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.log('Yo yo yo');
          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) {
          // Insert your code here
        });

        this.get('#/products', function (context) {
          // Transform the data using a LoDash template
        });
      });

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

    })();
  </script>
</body>
</html>

Add the Products Route Code

The implementation of the routes this.get(‘#/products‘, is actually the same code that you used in the previous post, SPA 5–Deep Dive into Loading Templates Using Sammy, Mustache, RequireJS.


this.get('#/products', function (context) {
var products = context.items;
// clear out the $element in this case '#content'
context.app.swap('');
// Uses the plugin to transform the template with the products data
// then appends it to the $element
context.render('templates/4-products.html', products)
.appendTo(context.$element());
});

 

The difference is that because Sammy’s render method will the Sammy.LoDash method in our new sammy.lodash.js plugin because we specified that using the sammy.use() where you pass in the function name(Sammy.LoDash) and the extension type (html).

Add the Product Route Code Using LoDash/Underscore Find

Use LoDash (or Underscore) to find the right product based on the route. _.find uses a function to determine which of the product matches the id in the route.

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

  var param = context.params['id'];
  var products = context.items.products

  // find the product based on the value of :id
  var productData = _.find(products, function (product) {
    return product.id.toString() === param;
  });

  if (!productData) {
    return context.notFound();
  }

  // partial() internally calls render and swap 
  // creates the html and puts it into $element
  context.partial('Templates/4-product.html', { product: productData });
});

Build the LoDash Plugin

A Sammy plugin is really just a Sammy app definition that isn’t evaluated until needed. You can use Sammy plugins for code that you can reuse across applications. Sammy users have provided several plugins. You can find them at Sammy Plugins. And you can model your plugin from that.

Sammy uses plugins to automatically transform the data using templates.

The code uses a factory pattern to create the plugin using define method when the module is used for AMD, or uses window global values.

The object exposes the Sammy.LoDash function that uses the _.template method.

Sammy loads the template from the file and passes it into Sammy.LoDash, which then calls _.template().

You can use the plugin as a helper function too. The code sets up the plugin so you can use it as a help function too.

Scripts/sammy.lodash.js


(function (factory) {
if (typeof define === 'function' && define.amd) {
define(['jquery', 'sammy', 'underscore'], factory);
} else {
(window.Sammy = window.Sammy || {}).Tmpl =
factory(window.jQuery, window.Sammy, window._);
}
}(function ($, Sammy, _) {
// `Sammy.LoDash` is a wrapper around the underscore/lodash templating engine. // http://lodash.com/docs#template
Sammy.LoDash = function (app, method_alias) {
// *Helper:* Uses _.template to parse a template and interpolate // and work with the passed data
// * `template` A String template that will be compiled by _.template()
// * `data` An Object containing the replacement values for the template.
// data is extended with the <tt>EventContext</tt> allowing you to call // its methods within the template.
//
var template = function (template, data) {
data = $.extend({}, this, data);
var results = _.template(template, data);
return results;
};
// set the default method name/extension
if (!method_alias) { method_alias = 'lodash'; }
// create the helper at the method alias
app.helper(method_alias, template);
};
return Sammy.LoDash;
}));

(Note that in the define method, by convention I am using underscore as the name of the require.js module.)

Currently the tests for all plugins are contained in the single file test_sammy_plugins.js

Sample Code

This sample code is available on the DevDay repository on GitHub:  https://github.com/devdays/single-page-app/tree/master/Sammy

See 7-LodashEngine.html

References

Sammy Plugin reference

Sammy Plugins source code

SammyJS loading json from http