Getting to know Sammy.js: Helpers

For part of this month’s writing I’m going to write focused posts about features of Sammy.js that people may not know about or have used. In the end I hope to compile these into part of the new Sammy.js site.

A question came up on the mailing list recently about overriding previously defined routes after struggling thinking of how to do this with the current codebase, the answer was simple: Use helpers!

Coming from Sinatra or Rails the idea of helpers might be associated with just “view helpers” – methods added to the controller which are passed down to the view to help refactor/encapsulate common view logic. In Sammy.js helpers are really a way of refactoring common logic out of routes and into a reusable named method.

Refactorin’

In any app you’ll have startup code or code that needs to be run for every or multiple pages (routes). In Sammy this might looks like:

$.sammy(function() {

this.get('#/', function() {
if (this.params.grid) {
this.$element().addClass('grid');
} else {
this.$element().removeClass('grid');
}
this.partial('index.html');
});

this.get('#/user', function() {
if (this.params.grid) {
this.$element().addClass('grid');
} else {
this.$element().removeClass('grid');
}
this.partial('user.html');
});

})

A contrived example, but you can tell we’re doing almost the exactly same thing in each of the routes. A common JS convention and something you could do pretty easily here, would just be to extract the functionality out into a closure/function and call it for each route.

$.sammy(function() {

var displayGrid = function($element, display) {
if (display) {
$element.addClass('grid');
} else {
$element.removeClass('grid');
}
};

this.get('#/', function() {
displayGrid(this.$element(), this.params.grid);
this.partial('index.html');
});

this.get('#/user', function() {
displayGrid(this.$element(), this.params.grid);
this.partial('user.html');
});

})

This works, it prevents global leakage by declaring it inside the app, and it gets rid of a number of lines of repetitive code. However, we’re still passing a bunch of route attributes into the closure. This is where helpers can shine.

$.sammy(function() {

this.helpers({
displayGrid: function() {
if (this.params.grid) {
this.$element().addClass('grid');
} else {
this.$element().removeClass('grid');
}
});

this.get('#/', function() {
this.displayGrid();
this.partial('index.html');
});

this.get('#/user', function() {
this.displayGrid();
this.partial('user.html');
});

});

This looks a lot cleaner to me. You might not love this but it makes some things really magical. By making displayGrid a helper it automagically has access to the context of the current route. That means that just like the route’s callback, it has access to everything in the EventContext including params, redirect, render and other helpers. This is important, because it basically allows you to extract common code from routes into helpers and then refactor those helpers even further into other helpers.

stOOPid inheritance and mixins

Helpers are actually just methods on the app’s EventContext prototype, and event though there’s no classical inheritance with super’s and all that jazz we can do some fun OOP-like magic.

In the example from the mailing list, we wanted to override an application’s existing route with a new route. Though the way routes are stored and handled makes it difficult on the app level, doing it on the helper level is EZ:

// initializing a new app
var app = $.sammy(function() {

this.helpers({
// define a helper to load the index as the default behavior
loadIndex: function() {
this.partial('index.html');
}
});

this.get('#/', function() {
this.loadIndex();
});
});

// override the specific helper, and hence the index route
// we can also do this by calling `helpers()`
app.helper('loadIndex', function() {
this.partial('specialindex.html');
});

use() is how Sammy includes a plugin in an app. A plugin is really just another app function, though, that get’s evaluated at the time you use() it. In fact, when you call $.sammy() you’re just creating a new instance of Sammy.Application and then calling use() on it with the app function. This is a simple way of doing “mixin” style inheritance. A plugin can do anything an app can, including and especially helpers. So with multiple apps we be all like tag team: OOP there it is

$.sammy('#hiphop', function() {
this.helpers({
playCatchPhrase: $.noop
// noop there it is
// this is sort of like an abstract method
});

this.get('#/catchphrase', function() {
this.playCatchPhrase();
})
});

var atlanta = function() {
this.helper('playCatchPhrase', function() {
alert('whoomp there it is!');
});
};

var la = function() {
this.helper('playCatchPhrase', function() {
alert('do the hump!');
});
};

$.sammy('#hiphop').use(la);
// get '#/catchphrase' //=> 'do the hump!'

Thats still pretty simple, though combining these ideas is actually pretty powerful. Almost all of Sammy’s existing plugins are based on this simple approach. I’m hoping soon, it can get even more abstract and complex. Why not a Sammy.Auth that handles displaying/posting/and doing all the basic authentication? You could use helpers for most of the dirty work and then one could just override the defaults in their specific apps. You could even create another set of plugins that were different displays or authentication systems that override the simple base methods. Do it! ( so I dont have to )

Comments are closed.

About

QuirkeyBlog is Aaron Quint's perspective on the ongoing adventure of Code, Life, Work and the Web.

twitter/@aq.

instagram/@quirkey.

github/quirkey.

QuirkeyBlog is proudly powered by WordPress

Categories