TRANSCODE Explorations into the Code Transcendental.

Peanut Butter and Chocolate Handlebars

Peanut Butter and Chocolate Handlebars

This week I’ve been working on the Shomen documentation project. In particular I’ve been converting the templates of my three web-based documentation viewers to compiled templates in order to boost rendering speed. Originally these viewers were using jqote2 in one case (hypervisor) and jquery-tmpl in the other two cases (rebecca and rubyfaux). While ascertaining what compilation solution to use, I tried the jquery-tmpl-jst for the later two, but immediately ran into fundamental errors. (Sorry, I don’t recollect exactly what the errors were now). They were problematic enough to send me looking else where, and ultimately I decided on Handlebars.js.

Handlebars is great… mostly. Handlebars a variant of Mustache and is billed like Mustache as “logic-less” templates. But, whereas Mustache is pure about this ideal, Handlebars backs off a bit and at least allows some computation to take place via helpers. Unfortunately, from my experience I have found the ideal of “logic-less” templates to be largely misplaced, and not even Handlebars goes far enough to rectify the situation. What one ends up doing is just moving the “logic” to a separate file where it mostly consists of bits of trivial code and where it is more difficult to maintain because it is separated from the specific bit of markup it supports.

Take this example. It’s the last bit of jqeury-tmpl I needed to convert to Handlebars for rubyfaux.


  ${divy  = Rubyfaux.divy_methods(methods),''}
  ${scope = ['class','instance'],''}
  ${sight = ['public','protected','private'],''}

  
    
      
        <h3>${v.capitalize()} ${s.capitalize()} METHODS</h3>
        <ul class="reference-list">
        
          
        
        </ul>
      
    
  

How does one translate this to Handlbars.js? It isn’t a straight forward process. Handlebars doesn’t allow the embedded assignments. So right off we see that scope and sight must be extracted. Secondly, the fact that some of the template is only shown if the combination of scope and sight has any entries, means this can’t be rendered via a single Handlebars block helper. It has to be broken up so we can use a helper to render only if there are entries for each combination. But then it becomes difficult, if not impossible, to loop over scope and sight to keep our code DRY. Instead we end up with something like:



  
    <h3>PUBLIC CLASS METHODS</h3>
    <ul class="reference-list">
    
      
        
      
    
  

  ... ditto for each combination of scope and sight ...


Thank goodness there are only six combinations!

Of course, there is a way around this explosion in code. We can use a single block helper, but only if we render the h3 and ul tags via the block helper too.

Handlebars.registerHelper('methods_categorized', function(block) {
  var meths = Rubyfaux.divy_methods(this.methods);
  var scope = ['class','instance'];
  var sight = ['public','protected','private'];
  var out = '';

  if (this.methods.length > 0) {
    for (i in scope) {
      for (j in sight) {
        var combination = meths[scope[i]][sight[j]];
        if (combination.length > 0) {
          out = out + "<h3>" + scope[i].capitalize() + " " + sight[j].capitalize() + " METHODS</h3>\n";
          out = out + '<ul class="reference-list">' + "\n";
          for (k in combination) {
            out = out + block(meths);
          };
          out = out + "\n</ul>";
        };
      };
    };    
  };

  return out;
});

Then our Handlebars template becomes the very simple:


  

But what on earth have we done!? Pursuing the ideal of keeping our views cleansed of logic, and DRY, we’ve ended up putting our view in our logic! You would think this is an obvious “no no” too, but look at some of the examples on Handlebars.js website and you will see they do this quite regularly.

Perhaps there is a more appropriate way to handle the above example, and I am all ears. But after giving the whole concept of keeping logic completely out of views, I’ve come to the conclusion that it’s really not so ideal. Like Reese’s mixing chocolate and peanut butter, mixing views and logic can be a good thing. The trick is limiting the logic to particular domains, let’s call it “view logic” as opposed to “data logic”.

View logic should be limited to these purposes:

  • Conditions
  • Sorting
  • Categorizing
  • Formatting

By limiting view logic to these, I’d say we have still achieved the ideal of “logic-less templates”. By being zealots about the logic-less concept we end-up just creating the opposite problem of mixing our templates into our logic, which is just as bad, if not worse. Template engines, like Handlebars.js, would do well to take this into account and expand their capabilities to include more flexible conditions, sorting, categorizing and formatting constructs and helpers out of the box.