Mustache is logic-less but the logic has to go somewhere


Mustache is logic-less but the logic has to go somewhere

As the developer of mustache.java I get a lot of feature requests to break the mustache language and add real logic to the templates. In virtually every case the reason the developer wanted the functionality was because they were using mustache with a template backed with a model without an interposed view. Let me give you a common example. Developer has a user object with a few fields:

class User {
String username;
long createdAt;
}

And they want to show when the user joined in their template:

{{#user}}{{username}} joined on {{createdAt}}{{/user}}

They look at this and think, “I need to be able to format longs into dates in the template”. If you were in .erb or .jsp you might create a helper to do that and call it directly from the template. For mustache, someone will typically suggest changing the language to add filters so you might write the template like this:

{{#user}}{{username}} joined on {{createdAt | date}}{{/user}}

Then there would be a bunch of date formatters you could use, you might even go so far as to ask to parametrize it in the template because it isn’t flexible enough with just a name:

{{#user}}
{{username}} joined on {{createdAt | date("DD/mm/YYYY")}}
{{/user}}

And you start going down this road where more and more of the view logic is going directly into your template making it hard to internationalize, harder to share between mustache implementations and generally making it harder to maintain and test. However, this isn’t the way you should use mustache. The best way is to add an additional layer of logic in your chosen host language that is responsible for the view. So for this case, what you would like end up with is something like this view:

class UserView {
String username;
String joined_date;
UserView(User user) {
username = user.username;
joined_date = new SimpleDateFormat("DD/mm/YYYY")
.format(new Date(user.createdAt));
}
}

with a template much like the first one:

{{#user}}{{username}} joined on {{joined_date}}{{/user}}

This allows you in the future to add special internationalization, user specific date formats, maybe a relative timestamp, etc. All without ever changing the template. It is also easier to test since you can independently check that the formatting of the date is correct without having to execute it within the context of the template text.

This strategy worked great at Bagcheck where there was only myself and a designer and it works great at Twitter where we have tons of engineers and designers working in the code base. It has also made it possible to move between rendering on the client in Javascript to rendering on the server in Ruby to rendering on the server in Scala all with the same set of templates and basically standard mustache implementations.