Anti-templating languages

Published on 2012-3-28

I don't really like templating languages or view engines (especially in JavaScript) - it's something I've been vocal on in person for a while now but never got around to writing about.

Things like this have been ranted on before by other people, but I want to share my particular dislike of the frameworks and technologies here, as well as present up the way I'm currently working.

Logic in your views

Now, we all know this isn't a good idea, but what do we really mean by this? What are the problems we're facing?

EJS

EJS is a view engine similar in nature to WebForms in ASP.NET, which means for .NET devs it's often reached at for its comfortable familiarity and lack of learning requirements.

Let's look at the default example on the EJS website to understand this

<ul>
<% for(var i=0; i < supplies.length; i++) {%>
   <li><%= supplies[i] %></li>
<% } %>
</ul>

EJS promises to "Clean the HTML out of your JavaScript", and the very first demo shows us an example of HTML which has been dirtied with JavaScript.

This is just moving the problem around, this is very much a case of "logic in the view" and it makes it difficult to maintain because it's difficult to read and it's hard to tell where the HTML begins and where the HTML ends.

Standard practise might be to do something like this:

<ul>
    <%= Helpers.RenderList(supplies) %>
</ul>

But this just means I've moved the HTML back into my JavaScript again. I guess what we're saying here, is that trying to arbitrarily separate 'view' from 'logic' in this manner is a fools errand, doomed to fail because all we're doing is moving the problem around.

Mustache

Mustache is a "logic-less" templating language (they say so on their site), there are other "logic-less" templating languages around too and they're all much of a muchness.

I quote:

We call it "logic-less" because there are no if statements, else clauses, or for loops. Instead there are only tags.

for-loop-replacement:


    <ul>
    {{#supplies}}
      <li>{{text}}</li>
    {{/supplies}}
    </ul>

if-else-statement-replacement


{{#shipped}}
    <li>This product shipped on {{shipdate}}</li>
{{/shipped}}
{{^shipped}}
    <li>This product has not yet shipped</li>
{{/shipped}

This is just a for loop and an if-else statement but with different syntax, pretending they're otherwise is doing a disservice to everybody who is going to be reading and writing this code.

Adding onto this, we of course also have the ability to seamlessly call methods from Mustache because we're always going to need to write code somewhere (Well, it's one way to keep logic out of the view by.. err calling logic from the view) (see above re: arbitrary separation).

{{#wrapped}}
  {{name}} is awesome.
{{/wrapped}}

{
  "name": "Rob",
  "wrapped": function() {
    return function(text) {
      return "<b>" + render(text) + "</b>"
    }
  }
}

This serves to just confuse though, as the fact a method is being called is hidden from us and means we're reduced to jumping between template and code to work out what is going on.

A shared concern

I have a problem that's shared across all of these solutions though, and that's the one of dealing with external designers.

I have only once worked in a situation where I was privileged enough to work with a designer who knew her HTML and Webforms syntax and could be taught more if needed because she was a permanent member of our team. (And that was only because I spent months campaigning to get somebody who knew what they were doing when it came to making things look pretty!)

Here in lies a problem - the moment we go to any templating language/system that isn't just HTML, we have to transform what the designer has given us into that templating language - and then translate it back when patching in any amendments that might come as we continue developing.

Performance

Not only those points, but if we're truly going to have a logic-less templating language and we're using third party APIs in any way (whether they be third-third party, or just plain old third party) then in order to get the data into a shape fit for binding directly to your template, transforms must be done which means writing mapping code one way or another.

It's not healthy I tell you - if you're going to transform one set of data into other data that is an exact match of your view requirements, and then transform from that data into another set of data (your view) then you're paying a cost for this. (Throw in your favourite MVC/MVVM framework for JS and even more so, but that's another blog entry entirely).

So what do I like then?

We have a great opportunity in JS, where we have a language that has been built almost for the primary purpose of interacting with the output that the user sees and where we have libraries whose sole purpose is the interaction with that output.

Use the force

Enter the anti-templating system "Plates" (there are others that are similar, but this is what I'm using at the moment, as it's isomorphic, fast with no-frills and hopefully will remain so - despite the "issue" reports asking for "nesting" or "collections" etc - as they're missing the point).

Rather than being a templating language, Plates merely binds data to HTML.

HTML!! You know - the stuff that you're going to give to the browser, the stuff which your designer gives you - the stuff that everybody on the internet and their pet animals know how to use.

Given some HTML:

<div id="test"></div>

And some model:

{ 
    "test": "hello"
}

Then

Plates.bind(html, model);

Combined with a bit of JavaScript this gives us enough power to do everything we'd want to do when it comes to taking some data and displaying it on a page. (Yes, it supports matching by class, yes it supports putting data into attributes on those elements, this is all trivial).

How does it all fit together?

Well, I tend to either keep the HTML snippets which I'm going to hydrate on the page itself in a hidden div, or if they're shared templates, as files which I can pull down with a HTTP GET (not rocket science really).

How do I deal with collections? Easy - I write a for loop. How do I deal with different paths? I want an 'if' statement.

But don't you end up with spaghetti code?

Well no - just because I'm not following an enforced and arbitrary separation of 'view' and 'logic' doesn't mean I'm throwing away sensible software practises.

It's just, that separation comes naturally on a case-by-case basis.

Sometimes I'll end up with some code that matches the model purely on convention and I can write

hydrateTemplate('source', 'target', data);

Sometimes I'll end up with builders that look like this

 startTemplateWithId('targetId')
    .withText('title', data.title)
    .withText('name', data.name)
    .withCollection('itemList', data.items, getItemHtml)
    into('placeholder');

Or similar (although in most simple cases this kind of over-blown code isn't needed).

My mark-up remains clean, my model remains clean, and the code that lies between is kept clean, tidy and to the point - not to mention re-usable where appropriate because it's just code.

When asked where this style fits in, I'd say it's essentially just MVP, with the line between V and P moving around to fit the situation.

Summary

Separation of view and logic isn't going to happen along any sort of neat line without silly amounts of abstraction; use something that allows you to have clean HTML and clean JavaScript with as little bullshit in-between as possible.

You'll be happier, your code will be happier and you'll find it is a lot easier to deliver a product when you stop arguing about whether you have enough separation in your abstractions.

2020 © Rob Ashton. ALL Rights Reserved.