Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

External template in Underscore

Here is a simple solution:

var rendered_html = render('mytemplate', {});

function render(tmpl_name, tmpl_data) {
    if ( !render.tmpl_cache ) { 
        render.tmpl_cache = {};
    }

    if ( ! render.tmpl_cache[tmpl_name] ) {
        var tmpl_dir = '/static/templates';
        var tmpl_url = tmpl_dir + '/' + tmpl_name + '.html';

        var tmpl_string;
        $.ajax({
            url: tmpl_url,
            method: 'GET',
            dataType: 'html', //** Must add 
            async: false,
            success: function(data) {
                tmpl_string = data;
            }
        });

        render.tmpl_cache[tmpl_name] = _.template(tmpl_string);
    }

    return render.tmpl_cache[tmpl_name](tmpl_data);
}

Using "async: false" here is not a bad way because in any case you must wait until template will be loaded.

So, "render" function

  1. allows you to store each template in separate html file in static dir
  2. is very lightweight
  3. compiles and caches templates
  4. abstracts template loading logic. For example, in future you can use preloaded and precompiled templates.
  5. is easy to use

[I am editing the answer instead of leaving a comment because I believe this to be important.]

if templates are not showing up in native app, and you see HIERARCHY_REQUEST_ERROR: DOM Exception 3, look at answer by Dave Robinson to What exactly can cause an "HIERARCHY_REQUEST_ERR: DOM Exception 3"-Error?.

Basically, you must add

dataType: 'html'

to the $.ajax request.


EDIT: This answer is old and outdated. I'd delete it, but it is the "accepted" answer. I'll inject my opinion instead.

I wouldn't advocate doing this anymore. Instead, I would separate all templates into individual HTML files. Some would suggest loading these asynchronously (Require.js or a template cache of sorts). That works well on small projects but on large projects with lots of templates, you find yourself making a ton of small async requests on page load which I really dislike. (ugh... ok, you can get around it with Require.js by pre-compiling your initial dependencies with r.js, but for templates, this still feels wrong to me)

I like using a grunt task (grunt-contrib-jst) to compile all of the HTML templates into a single templates.js file and include that. You get the best of all worlds IMO... templates live in a file, compilation of said templates happen at build time (not runtime), and you don't have one hundred tiny async requests when the page starts up.

Everything below is junk

For me, I prefer the simplicity of including a JS file with my template. So, I might create a file called view_template.js which includes the template as a variable:

app.templates.view = " \
    <h3>something code</h3> \
";

Then, it is as simple as including the script file like a normal one and then using it in your view:

template: _.template(app.templates.view)

Taking it a step further, I actually use coffeescript, so my code actually looks more like this and avoid the end-of-line escape characters:

app.templates.view = '''
    <h3>something code</h3>
'''

Using this approach avoids brining in require.js where it really isn't necessary.


This mixin allows you to render external template using Underscore in very simple way: _.templateFromUrl(url, [data], [settings]). Method API is almost the same as Underscore's _.template(). Caching included.

_.mixin({templateFromUrl: function (url, data, settings) {
    var templateHtml = "";
    this.cache = this.cache || {};

    if (this.cache[url]) {
        templateHtml = this.cache[url];
    } else {
        $.ajax({
            url: url,
            method: "GET",
            async: false,
            success: function(data) {
                templateHtml = data;
            }
        });

        this.cache[url] = templateHtml;
    }

    return _.template(templateHtml, data, settings);
}});

Usage:

var someHtml = _.templateFromUrl("http://example.com/template.html", {"var": "value"});

I didn't want to use require.js for this simple task, so I used modified koorchik's solution.

function require_template(templateName, cb) {
    var template = $('#template_' + templateName);
    if (template.length === 0) {
        var tmpl_dir = './templates';
        var tmpl_url = tmpl_dir + '/' + templateName + '.tmpl';
        var tmpl_string = '';

        $.ajax({
            url: tmpl_url,
            method: 'GET',
            contentType: 'text',
            complete: function (data, text) {
                tmpl_string = data.responseText;
                $('head').append('<script id="template_' + templateName + '" type="text/template">' + tmpl_string + '<\/script>');
                if (typeof cb === 'function')
                    cb('tmpl_added');
            }
        });
    } else {
        callback('tmpl_already_exists');
    }
}

require_template('a', function(resp) {
    if (resp == 'tmpl_added' || 'tmpl_already_exists') {
        // init your template 'a' rendering
    }
});
require_template('b', function(resp) {
    if (resp == 'tmpl_added' || 'tmpl_already_exists') {
        // init your template 'b' rendering
    }
});

Why to append templates to document, rather than storing them in javascript object? Because in production version I would like to generate html file with all templates already included, so I won't need to make any additional ajax requests. And in the same time I won't need to make any refactoring in my code, as I use

this.template = _.template($('#template_name').html());

in my Backbone views.


This might be slightly off topic, but you could use Grunt (http://gruntjs.com/) - which runs on node.js (http://nodejs.org/, available for all major platforms) to run tasks from the command line. There are a bunch of plugins for this tool, like a template compiler, https://npmjs.org/package/grunt-contrib-jst. See documentation on GitHub, https://github.com/gruntjs/grunt-contrib-jst. (You will also need to understand how to run node package manager, https://npmjs.org/. Don't worry, it's incredibly easy and versatile. )

You can then keep all your templates in separate html files, run the tool to precompile them all using underscore (which I believe is a dependency for the JST plugin, but don't worry, node package manager will auto install dependencies for you).

This compiles all your templates to one script, say

templates.js

Loading the script will set a global - "JST" by default - which is an array of functions, and can be accessed like so:

JST['templates/listView.html']()

which would be similar to

_.template( $('#selector-to-your-script-template'))

if you put the content of that script tag in (templates/)listView.html

However, the real kicker is this: Grunt comes with this task called 'watch', which will basically monitor changes to files that you have defined in your local grunt.js file (which is basically a config file for your Grunt project, in javascript). If you have grunt start this task for you, by typing:

grunt watch

from the command line, Grunt will monitor all changes you make to the files and auto-execute all tasks that you have setup for it in that grunt.js file if it detects changes - like the jst task described above. Edit and then save your files, and all your templates recompile into one js file, even if they are spread out over a number of directories and subdirectories.

Similar tasks can be configured for linting your javascript, running tests, concatenating and minifying / uglifying your script files. And all can be tied to the watch task so changes to your files will automatically trigger a new 'build' of your project.

It takes some time to set things up and understand how to configure the grunt.js file, but it well, well worth the time invested, and I don't think you will ever go back to a pre-grunt way of working


I think this is what might help you. Everything in the solution revolves around require.js library which is a JavaScript file and module loader.

The tutorial at the link above shows very nicely how a backbone project could be organized. A sample implementation is also provided. Hope this helps.