Maintainable Backbone Micro-Templates with RequireJS and Underscore

For me, MarionetteJS was love at first site. Everything just made sense and worked the way I expected it to...everything but the templating. Its template processing and the usage syntax around it is great, but all of the documentation shows the templates being created from HTML-snippets wrapped in <script> tags on the app's main HTML page.


The Problem

This is all well and good, except that I am building a very large single page app that will have a multitude of modules and views. Do I even need to say how non-maintanable having all of your templates for all of your modules and views on your app's single HTML page is?

So then how does one supply templates to views in a way that's maintainable?


My First Solution

Under a time crunch, and with Google-foo failing me, I came up with a solution that got me part of the way there. I created a requireJS module for each Marionette module that exported a big object of template strings.

The file structure went something like this:

App/  
    -module/
        -models/
        -views/
            -mainView.js
        -module.js
        -templates.js

And the templates.js file looked something like this:

define(function(require){  
    "use strict";

    var template1 =
            "<h1>Hey there!</h1>\
            <p>How goes, <%= name %></p>,

        template2 =
            "...";

    return {
        template1: template1,
        template2: template2
    };
});

This solved a lot of my problems, but still left me with some maintainability nightmares. Here's a pro-con list:

Pros

  • No templates in the main HTML file
  • Templates live under their modules, keeping modules more modular
  • Only one requirement import per view

Cons

  • Writing HTML in Javascript (barf)
  • A module with lots of templates will have a bloated templates.js file

A Better Solution

This worked for getting me started, so I rolled with it. However, over time the con's listed above really, really bothered me. My IDE's syntax highlighting hated me for it, I had to constantly fight the ' vs " battle in writing HTML strings, keeping track of \'s at end of the lines was hard, and once my views started to use conditional templating and micro-templating, these files got to be hundreds of lines long. Not good.

So, once I'd had enough, I started asking around on Twitter. A lovely user named Boris (@zbzzn) enlightened me to a part of requireJS I'd overlooked...the text plugin. It allows you to declare HTML files (or other text files) as dependencies.

Great! I dropped the text.js file in my app's main folder and got cracking. Now I could separate each template into its own file and pull them in like this:

module/templates/main.html

<dir>Main Template</dir>  

view.js

define(["marionette", "text!module/templates/main.html"],  
    function (Marionette, mainTemplate) {
        "use strict";

    return Marionette.ItemView.extend({
        template: _.template(mainTemplate);
    });    
});

Pros

  • No templates in the main HTML file
  • Templates live under their modules, keeping modules more modular
  • HTML written in HTML

Cons

  • Views with multiple templates will need to import multiple files (can get overwhelming)

This alone takes a huge stride towards better maintainability, and for many (most?) this is perfect. However, if you're doing conditional templating and/or micro-templating this method also means that many of your views will need to import multiple templates (I have a few views with 10+ templates). Keeping track of that many imports in the require syntax is a nightmare and creates a ton of unique variable names inside the require module that you have to then remember and not use again.


My Solution

Ok, here's where I get crazy. The con of the previous method of handling templates is actually a pro of the first method I used. "Why not combine them?" I thought. So I did.

I ended up with the following:

App/  
    -module/
        -models/
        -views/
            -mainView.js
        -templates/
            -main.html
            -main-alt.html
            -templates.js
        -module.js

templates.js

define(["text!module/templates/main.html",  
        "text!module/templates/main-alt.html"],
        function (main, mainAlt) {
    "use strict";

    return {
        "main": main,
        "mainAlt": mainAlt
    }
});

mainView.js

define(["marionette", "module/templates/templates"],  
    function (Marionette, templates) {
        "use strict";

    return Marionette.ItemView.extend({
        template: function(serializedModel){
            if (someCondition){
                return _.template(templates.mainAlt);
            } 

            return _.template(templates.main);            
        }
    });    
});

In essence, I moved the multiple template imports into a require module whose only responsibility is templates. Each view now only imports one file for templates, giving the view logic the clear focus. If you need to add, remove, or change the name of a template, it is done in one place. Make your changes in templates.js then in your view, simply change the attribute you're looking at without needing to also change your require import paths.


Final Thoughts

Creating a separate template require module for each Marionette module may seem like I'm adding an unneccessary layer. Maybe. But it gives me a much cleaner view as well as one central access point for templates across all of the module's views. What's more, is that in addition to all of the pro's on both of my lists, after I type the . in templates. I get IDE code completion! No more needing to remember the exact variable name I gave them all when importing. Bonus!

Hate it? Love it? Let me know on Twitter (@D_Naish)


comments powered by Disqus