Sammy 0.6: California Suite
It’s a couple months in the making, but I’m happy to say that Sammy.js 0.6 is finally here. Check out the HISTORY and the Changes. I’m calling this release the California Suite in honor of my move to the West Coast and the fact that I did most of this coding on the plane back and forth. There are some awesome big new features that I’ve been using for a little while now and I can say are a big improvement over previous releases.
Before I get to that, I want to mention that work is in full swing on a new Sammy.js website with more documentation and other great stuff. I even have a real logo which I hope to share soon (stickers, anyone?). Before the site launches, though, I set up a new GitHub wiki so that sammy.js users and developers can share projects and code that they’ve been working on.
On to the features!
Context! Context! Context!
The biggest complaint and frustration that I heard from people working with Sammy was about dealing with rendering complex views on the client. If you’re doing full client side applications or any amount of client side templating you’ve felt the pain of deeply nested callbacks and complex workarounds for interpolating data in an asynchronous environment. It’s all like, render this template, wait, first I need the data, hold up, I cant even render that yet I need to get this other template first. Here’s a common example of what I’m talking about:
this.get('#/', function() {
this.partial('index.mustache', function(html) {
$('#main').html(html);
$.getJSON('items.json', function(items) {
this.partial('item.mustache', items, function(item, i) {
$('#main ul').append(item);
});
});
));
});
It’s not just nested callback’s that are the pain – its the knowing (or not knowing) when a template will be there or not. It’s also trying to deal with fetching data and fetching templates within the same context without a unified API. After kind of examining how other people are doing this, I took some queues from jQuery’s API as well as ‘promise’ style programming and Sexy.js. The result is Sammy.RenderContext
. You create a RenderContext
by calling one of three methods within a route/EventContext
– load()
render()
or partial()
. Once you have a every method you chain to it is guaranteed to execute in the order that you chain it regardless of it synchronicity. This means that you can put an appendTo()
after a load()
and the content won’t be appended until its fetched asynchronously from a remote location. The content from the previous method in the chain is also passed along so you don’t have to constantly pull out references. The code above, translated to the new way looks like:
this.get('#/', function() {
this.render('index.mustache')
.replace('#main')
.render('items.json')
.renderEach('item.mustache')
.appendTo('#main ul');
});
A wee bit nicer. Not only does it improve readability but it makes it much easier to do longer more involved client side templating without getting lost in a mire of order and context passing. RenderContext’s can also be invoked side by side in a single route for parallel execution of chains. Each chain is its own object and keeps its own state. I tried to keep the API concise but with just a few methods, I think most situations are covered:
then(function(content, previous) {})
next(content);
wait();
// Loading local or remote content
load('item.template')
load($('.item'))
// rendering templates
interpolate('item.template', {})
interpolate($('.item'), {})
render('item.template', {})
partial('item.template', {})
collect(items, function(item, i) {})
renderEach('item.template', items)
// DOM Manipulation
swap()
appendTo($(element))
prependTo($(element))
replace($(element))
// Events
trigger('name', {})
Along with the readability improvements, the RenderContext
(specifically load()
) allows for DOM style or pre-embedded templating. Meaning, templates don’t have to come from files, they can be DOM elements and embedded into <script>
or hidden tags. This goes along with the two new templating engines added as well . . .
Alchemy
I thought I had this really novel idea. Why not use classes or other attributes of DOM elements to define how they’re tied to a JSON object? I worked on some prototypes and finally found a name and wrote some real code. It turns out, it was a pretty good idea, but also one that had been done before. 0.6 includes plugins for my own Sammy.Meld
and the existing and by default more popular pure.js.
The basic idea of both of these engines is that you take some data like this:
{
"post": {
"title": "My Post",
"body": "Lorem ipsum dolor sit amet.",
"tags": ["one", "two", "three"],
"meta": {
"comments": 5,
"time": "Yesterday"
}
}
}
And some HTML like this:
<div class="post">
<h2 class="title"></h2>
<div class="body"></div>
<span class="tags"></span>
<ul class="meta">
<li>Comments Count: <span class="comments"></span></li>
<li>Posted: <span class="time"></span></li>
</ul>
</div>
Meld them together and you should get something like:
<div class="post">
<h2 class="title">My Post</h2>
<div class="body">Lorem ipsum dolor sit amet.</div>
<span class="tags">one</span>
<span class="tags">two</span>
<span class="tags">three</span>
<ul class="meta">
<li>Comments Count: <span class="comments">5</span></li>
<li>Posted: <span class="time">Yesterday</span></li>
</ul>
</div>
There are some clear benefits to this over traditional tag style (mustache, ejs) style templating. Namely, its just HTML. This means that you can include it in the DOM on the initial render and not have to go fetch it from the server. It also means that a designer can just design it and even use lorem ipsum if you want, and you don’t have to go through and add the templating language. There are some downsides, too. DOM manipulation even with jQuery’s speed is pretty much always going to be slower then just text interpolation. I’m working on improving the speed of Meld though caching and faster manipulations, but it ain’t easy. The other big downside is that complicated iteration or template logic is pretty hard to do in this style. Pure tries to get around this by using a third element called directives
which are further instructions on how to meld the data and the markup. I forgo this extra ability to configure in Meld in favor of very simple transformations (ul,ol = lists) and a much smaller file size (uncompressed meld is only 4K). I’ve been using Meld on some personal projects and hope to test its worth in a real production environment very soon. You can see more examples of it’s power in the tests
What’s next?
I’m still pushing to get Sammy.js to 1.0 in the very near future. I really don’t think theres that much between here and there. I’ll mention that I’ve finally been seriously coding with node and server side js and I’m not that happy with the current state of routing libs there. Server side sammy? Maybe.
More
There were a lot of smaller bug fixes and improvements beyond Meld and the RenderContext so I definitely encourage checking out the full list and making the upgrade soon. Big thanks as always to everyone who helped with the release. Go out and make some apps!
Great news! Congrats!
Looking forward to using this.
Great stuff. Eventually, I’m going to pull this into JavaScriptMVC. I think it’s a perfect ‘controller’ layer to sit above the other tools (including JMVC’s more view-ish controller).
A few other random thoughts/ideas:
I think it’s possible to get away with loading templates synchronously. If you have a build process, you can pack the views so they come with the compressed build file.
We’ve added to EJS the ability to hookup jQuery plugins in the view. So you can do:
<div >
tab 1
content 1
This kind of breaks a strict MVC design but is so damn useful.
Does Meld iterate through the dom after insertion? If so, wouldn’t it be much slower than an insert and forget template?
That’s pretty cool (about build process and hooks). Meld does work with DOM insertion but you’d be surprised its not actually that much slower then pure and mustache at this point (though still a bit behind EJS and the old Sammy.Template module). The API came first, now I’m working on speeding it up.
Download link isn’t responding… is it just me? 🙁
It’s working today!