Created
May 12, 2011 22:39
-
-
Save mjtko/969621 to your computer and use it in GitHub Desktop.
knockout template engine for handlebars.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Replacing rewriteTemplate() with the following adds support for pre-compiled templates:
this['rewriteTemplate'] = function (templateId, rewriterCallback) {