Skip to content

Instantly share code, notes, and snippets.

@mjtko
Created May 12, 2011 22:39
Show Gist options
  • Save mjtko/969621 to your computer and use it in GitHub Desktop.
Save mjtko/969621 to your computer and use it in GitHub Desktop.
knockout template engine for handlebars.js
/*
Handlebars Template Engine for Knockout JavaScript library
*//*!
Copyright (c) 2011 Mark J. Titorenko
License: MIT (http://www.opensource.org/licenses/mit-license.php)
*/
ko.handlebarsTemplateEngine = function () {
// adapted from MooTools.Element
//
// This is necessary to allow us to easily deal with table
// fragment templates.
var setHtml = (function(){
var tableTest = (function() {
try {
var table = document.createElement('table');
table.innerHTML = '<tr><td></td></tr>';
return false;
} catch (e) {
return false;
}
})();
var wrapper = document.createElement('div');
var translations = {
table: [1, '<table>', '</table>'],
select: [1, '<select>', '</select>'],
tbody: [2, '<table><tbody>', '</tbody></table>'],
tr: [3, '<table><tbody><tr>', '</tr></tbody></table>']
};
translations.thead = translations.tfoot = translations.tbody;
var empty = function(target) {
var node;
while ( node = target.firstChild ) {
node.parentNode.removeChild(node);
}
return target;
}
return function(target,html){
var wrap = (!tableTest && translations[target.tagName.toLowerCase()]);
if (wrap){
var first = wrapper;
first.innerHTML = wrap[1] + html + wrap[2];
for (var i = wrap[0]; i--;) first = first.firstChild;
empty(target);
var node;
while (node = first.firstChild) {
target.appendChild(node);
}
} else {
target.innerHTML = html;
}
};
})();
var templates = {};
var parseMemoCommentText = function (memoCommentText) {
var match = memoCommentText.match(/^<!--(\[ko_memo\:.*?\])-->$/);
return match ? match[1] : null;
};
var render = function(data,node,target) {
while (node) {
var nodeOut;
if ( node.tagName === 'SCRIPT' &&
node.getAttribute('data-generator') === 'ko.handlebarsTemplateEngine' ) {
// this needs to be evaluated within the context of
// 'data' in order to get data bindings to work
// correctly when not prefixed with 'data.'
with(data)
nodeOut = document.createComment(parseMemoCommentText(eval(node.innerHTML)));
} else {
// recurse
nodeOut = node.cloneNode(false);
render(data,node.firstChild,nodeOut);
}
target.appendChild(nodeOut);
node = node.nextSibling;
}
return target;
};
this['getTemplateNode'] = function (template) {
var templateNode = document.getElementById(template);
if (templateNode == null)
throw new Error("Cannot find template with ID=" + template);
return templateNode;
};
this['renderTemplate'] = function (templateId, data, options) {
var result = templates[templateId](data);
// we have to deal with anything that contains <tr>
// separately, as we can't append <tr> elements to a <div>.
//
// references:
// http://stackoverflow.com/questions/5090031/regex-get-tr-tags/5091399#5091399
var tabular = result.match(/<tr[\s\S]*?<\/tr>/g);
var container = document.createElement( ( tabular ? 'tbody' : 'div' ) );
// Use our custom setHtml (adapted from MooTools) in order to
// work around readonly tbody under IE.
//
// references:
// http://stackoverflow.com/questions/4729644/cant-innerhtml-on-tbody-in-ie/4729743#4729743
setHtml(container,result);
// deal with late binding for KO
var src = render(data,container.firstChild,document.createElement("div")).childNodes
// clone the nodes so they can be cleanly inserted into the DOM
var target = [];
for ( var i = 0, l = src.length; i < l; i++ ) {
target.push(src[i].cloneNode(true));
}
return target;
};
this['isTemplateRewritten'] = function (templateId) {
return templates[templateId] !== undefined;
};
this['rewriteTemplate'] = function (templateId, rewriterCallback) {
var templateNode = this['getTemplateNode'](templateId);
// elide templateNode from the DOM - no longer needed
templateNode.parentNode.removeChild(templateNode);
templates[templateId] = Handlebars.compile(rewriterCallback(templateNode.innerHTML));
};
this['createJavaScriptEvaluatorBlock'] = function (script) {
return '<script data-generator="ko.handlebarsTemplateEngine" type="text/javascript">// <![CDATA[\n' + script + '\n// ]]></script>';
};
};
Handlebars.registerHelper('dyn', function(observable) {
return observable();
});
ko.handlebarsTemplateEngine.prototype = new ko.templateEngine();
// Use this one by default
ko.setTemplateEngine(new ko.handlebarsTemplateEngine());
ko.exportSymbol('ko.handlebarsTemplateEngine', ko.handlebarsTemplateEngine);
@jelling
Copy link

jelling commented Aug 19, 2013

Replacing rewriteTemplate() with the following adds support for pre-compiled templates:

this['rewriteTemplate'] = function (templateId, rewriterCallback) {

    // first see if we have a pre-compiled template
    if (typeof(Handlebars.templates[templateId]) !== "undefined") {
        templates[templateId] = Handlebars.templates[templateId];
    } else {
        // try loading the template from a DOM node 
        var templateNode = this['getTemplateNode'](templateId);
        // elide templateNode from the DOM - no longer needed
        templateNode.parentNode.removeChild(templateNode);
        templates[templateId] = Handlebars.compile(rewriterCallback(templateNode.innerHTML));            
    }

};

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment