Permalink
Browse files

Beginning native template engine

  • Loading branch information...
1 parent d85a1c5 commit bb2b80a21b0c13442107e51e9af109f17d067504 @SteveSanderson SteveSanderson committed Aug 8, 2011
@@ -22,5 +22,6 @@ knockoutDebugCallback([
'src/templating/templating.js',
'src/binding/editDetection/compareArrays.js',
'src/binding/editDetection/arrayToDomNodeChildren.js',
- 'src/templating/jquery.tmpl/jqueryTmplTemplateEngine.js'
+ 'src/templating/native/nativeTemplateEngine.js',
+ 'src/templating/jquery.tmpl/jqueryTmplTemplateEngine.js'
]);
@@ -0,0 +1,38 @@
+describe('Native template engine', {
+ before_each: function () {
+ ko.setTemplateEngine(new ko.nativeTemplateEngine());
+
+ function ensureNodeExistsAndIsEmpty(id, tagName) {
+ var existingNode = document.getElementById(id);
+ if (existingNode != null)
+ existingNode.parentNode.removeChild(existingNode);
+ var resultNode = document.createElement(tagName || "div");
+ resultNode.id = id;
+ resultNode.setAttribute("type", "text/html");
+ document.body.appendChild(resultNode);
+ return resultNode;
+ }
+
+ this.testDivTemplate = ensureNodeExistsAndIsEmpty("testDivTemplate");
+ this.testScriptTemplate = ensureNodeExistsAndIsEmpty("testScriptTemplate", "script");
+ this.templateOutput = ensureNodeExistsAndIsEmpty("templateOutput");
+ },
+
+ 'Named template can display static content from regular DOM element': function () {
+ testDivTemplate.innerHTML = "this is some static content";
+ ko.renderTemplate("testDivTemplate", null, null, templateOutput);
+ value_of(templateOutput).should_contain_html("this is some static content");
+ },
+
+ 'Named template can fetch template from regular DOM element and data-bind on results': function () {
+ testDivTemplate.innerHTML = "name: <div data-bind='text: name'></div>";
+ ko.renderTemplate("testDivTemplate", { name: 'bert' }, null, templateOutput);
+ value_of(templateOutput).should_contain_html("name: <div data-bind=\"text: name\">bert</div>");
+ },
+
+ 'Named template can fetch template from <script> elements and data-bind on results': function () {
+ testScriptTemplate.text = "name: <div data-bind='text: name'></div>";
+ ko.renderTemplate("testScriptTemplate", { name: 'bert' }, null, templateOutput);
+ value_of(templateOutput).should_contain_html("name: <div data-bind=\"text: name\">bert</div>");
+ }
+});
View
@@ -54,6 +54,7 @@
<script type="text/javascript" src="templatingBehaviors.js"></script>
<script type="text/javascript" src="editDetectionBehaviors.js"></script>
<script type="text/javascript" src="jsonPostingBehaviors.js"></script>
+ <script type="text/javascript" src="nativeTemplateEngineBehaviors.js"></script>
</head>
<body>
<div style="display:none;"><p>A</p><p>B</p></div>
@@ -1,90 +1,93 @@
-
-ko.jqueryTmplTemplateEngine = function () {
- // Detect which version of jquery-tmpl you're using. Unfortunately jquery-tmpl
- // doesn't expose a version number, so we have to infer it.
- // Note that as of Knockout 1.3, we only support jQuery.tmpl 1.0.0pre and later,
- // which KO internally refers to as version "2", so older versions are no longer detected.
- var jQueryTmplVersion = (function() {
- if ((typeof(jQuery) == "undefined") || !jQuery['tmpl'])
- return 0;
- // Since it exposes no official version number, we use our own numbering system. To be updated as jquery-tmpl evolves.
- try {
- if (jQuery['tmpl']['tag']['tmpl']['open'].toString().indexOf('__') >= 0) {
- return 2; // Since 1.0.0pre, custom tags should append markup to an array called "__"
- }
- } catch(ex) { /* Apparently not the version we were looking for */ }
+(function() {
+ ko.jqueryTmplTemplateEngine = function () {
+ // Detect which version of jquery-tmpl you're using. Unfortunately jquery-tmpl
+ // doesn't expose a version number, so we have to infer it.
+ // Note that as of Knockout 1.3, we only support jQuery.tmpl 1.0.0pre and later,
+ // which KO internally refers to as version "2", so older versions are no longer detected.
+ var jQueryTmplVersion = this.jQueryTmplVersion = (function() {
+ if ((typeof(jQuery) == "undefined") || !jQuery['tmpl'])
+ return 0;
+ // Since it exposes no official version number, we use our own numbering system. To be updated as jquery-tmpl evolves.
+ try {
+ if (jQuery['tmpl']['tag']['tmpl']['open'].toString().indexOf('__') >= 0) {
+ return 2; // Since 1.0.0pre, custom tags should append markup to an array called "__"
+ }
+ } catch(ex) { /* Apparently not the version we were looking for */ }
+
+ return 1; // Any older version that we don't support
+ })();
- return 1; // Any older version that we don't support
- })();
+ function ensureHasReferencedJQueryTemplates() {
+ var errorMessage = jQueryTmplVersion == 0 ? "jquery.tmpl not detected.\nTo use KO's default template engine, reference jQuery and jquery.tmpl. See Knockout installation documentation for more details."
+ : jQueryTmplVersion == 1 ? "Your version of jQuery.tmpl is too old. Please upgrade to jQuery.tmpl 1.0.0pre or later."
+ : null;
+ if (errorMessage)
+ throw new Error(errorMessage);
+ }
- function ensureHasReferencedJQueryTemplates() {
- var errorMessage = jQueryTmplVersion == 0 ? "jquery.tmpl not detected.\nTo use KO's default template engine, reference jQuery and jquery.tmpl. See Knockout installation documentation for more details."
- : jQueryTmplVersion == 1 ? "Your version of jQuery.tmpl is too old. Please upgrade to jQuery.tmpl 1.0.0pre or later."
- : null;
- if (errorMessage)
- throw new Error(errorMessage);
- }
-
- this['getTemplateNode'] = function (template) {
- var templateNode = document.getElementById(template);
- if (!templateNode)
- throw new Error("Cannot find template with ID=" + template);
- return templateNode;
- }
-
- this['renderTemplate'] = function (templateId, data, options) {
- options = options || {};
- ensureHasReferencedJQueryTemplates();
-
- if (!(templateId in jQuery['template'])) {
- // Precache a precompiled version of this template (don't want to reparse on every render)
- var templateText = this['getTemplateNode'](templateId).text;
- jQuery['template'](templateId, templateText);
- }
- data = [data]; // Prewrap the data in an array to stop jquery.tmpl from trying to unwrap any arrays
-
- var resultNodes = jQuery['tmpl'](templateId, data, options['templateOptions']);
- resultNodes['appendTo'](document.createElement("div")); // Using "appendTo" forces jQuery/jQuery.tmpl to perform necessary cleanup work
- jQuery['fragments'] = {}; // Clear jQuery's fragment cache to avoid a memory leak after a large number of template renders
- return resultNodes;
- },
-
- this['isTemplateRewritten'] = function (templateId) {
- ensureHasReferencedJQueryTemplates.call(this);
+ this['getTemplateNode'] = function (template) {
+ var templateNode = document.getElementById(template);
+ if (!templateNode)
+ throw new Error("Cannot find template with ID=" + template);
+ return templateNode;
+ }
- // It must already be rewritten if we've already got a cached version of it
- // (this optimisation helps on IE < 9, because it greatly reduces the number of getElementById calls)
- if (templateId in jQuery['template'])
- return true;
+ this['renderTemplate'] = function (templateId, data, options) {
+ options = options || {};
+ ensureHasReferencedJQueryTemplates();
+
+ if (!(templateId in jQuery['template'])) {
+ // Precache a precompiled version of this template (don't want to reparse on every render)
+ var templateText = this['getTemplateNode'](templateId).text;
+ jQuery['template'](templateId, templateText);
+ }
+ data = [data]; // Prewrap the data in an array to stop jquery.tmpl from trying to unwrap any arrays
+
+ var resultNodes = jQuery['tmpl'](templateId, data, options['templateOptions']);
+ resultNodes['appendTo'](document.createElement("div")); // Using "appendTo" forces jQuery/jQuery.tmpl to perform necessary cleanup work
+ jQuery['fragments'] = {}; // Clear jQuery's fragment cache to avoid a memory leak after a large number of template renders
+ return resultNodes;
+ },
+
+ this['isTemplateRewritten'] = function (templateId) {
+ ensureHasReferencedJQueryTemplates.call(this);
+
+ // It must already be rewritten if we've already got a cached version of it
+ // (this optimisation helps on IE < 9, because it greatly reduces the number of getElementById calls)
+ if (templateId in jQuery['template'])
+ return true;
+
+ return this['getTemplateNode'](templateId).isRewritten === true;
+ },
+
+ this['rewriteTemplate'] = function (template, rewriterCallback) {
+ var templateNode = this['getTemplateNode'](template);
+ templateNode.text = rewriterCallback(templateNode.text);
+ templateNode.isRewritten = true;
+ },
+
+ this['createJavaScriptEvaluatorBlock'] = function (script) {
+ return "{{ko_code ((function() { return " + script + " })()) }}";
+ },
+
+ this.addTemplate = function (templateName, templateMarkup) {
+ document.write("<script type='text/html' id='" + templateName + "'>" + templateMarkup + "</script>");
+ }
+ ko.exportProperty(this, 'addTemplate', this.addTemplate);
- return this['getTemplateNode'](templateId).isRewritten === true;
- },
-
- this['rewriteTemplate'] = function (template, rewriterCallback) {
- var templateNode = this['getTemplateNode'](template);
- templateNode.text = rewriterCallback(templateNode.text);
- templateNode.isRewritten = true;
- },
-
- this['createJavaScriptEvaluatorBlock'] = function (script) {
- return "{{ko_code ((function() { return " + script + " })()) }}";
- },
-
- this.addTemplate = function (templateName, templateMarkup) {
- document.write("<script type='text/html' id='" + templateName + "'>" + templateMarkup + "</script>");
- }
- ko.exportProperty(this, 'addTemplate', this.addTemplate);
+ if (jQueryTmplVersion >= 2) {
+ jQuery['tmpl']['tag']['ko_code'] = {
+ open: "__.push($1 || '');"
+ };
+ }
+ };
+
+ ko.jqueryTmplTemplateEngine.prototype = new ko.templateEngine();
+
+ // Use this one by default *only if jquery.tmpl is referenced*
+ var jqueryTmplTemplateEngineInstance = new ko.jqueryTmplTemplateEngine();
+ if (jqueryTmplTemplateEngineInstance.jQueryTmplVersion > 0)
+ ko.setTemplateEngine(jqueryTmplTemplateEngineInstance);
- if (jQueryTmplVersion >= 2) {
- jQuery['tmpl']['tag']['ko_code'] = {
- open: "__.push($1 || '');"
- };
- }
-};
-
-ko.jqueryTmplTemplateEngine.prototype = new ko.templateEngine();
-
-// Use this one by default
-ko.setTemplateEngine(new ko.jqueryTmplTemplateEngine());
-
-ko.exportSymbol('ko.jqueryTmplTemplateEngine', ko.jqueryTmplTemplateEngine);
+ ko.exportSymbol('ko.jqueryTmplTemplateEngine', ko.jqueryTmplTemplateEngine);
+})();
@@ -0,0 +1,37 @@
+ko.nativeTemplateEngine = function () {
+ function getTemplateText(templateSource) {
+ // If it's a string, we assume it's the ID of a DOM element whose innerHTML defines the template
+ if (typeof templateSource == "string") {
+ var foundElem = document.getElementById(templateSource);
+ if (!foundElem)
+ throw new Error("Cannot find template with ID " + templateSource);
+ return foundElem.innerHTML;
+ }
+ }
+
+ this['renderTemplate'] = function (templateSource, data, options) {
+ var templateText = getTemplateText(templateSource);
+ var parsedElems = ko.utils.parseHtmlFragment(templateText);
+
+ for (var i = 0, j = parsedElems.length; i < j; i++) {
+ if (parsedElems[i].nodeType === 1)
+ ko.applyBindings(data, parsedElems[i]);
+ }
+
+ return parsedElems;
+ },
+ this['isTemplateRewritten'] = function (templateSource) {
+ return true;
+ },
+ this['rewriteTemplate'] = function (templateSource, rewriterCallback) {
+ // Native template engine requires no rewriting, so do nothing
+ },
+ this['createJavaScriptEvaluatorBlock'] = function (script) {
+ throw new Error("Native template engine doesn't support JavaScript evaluator blocks.")
+ }
+}
+
+ko.nativeTemplateEngine.prototype = new ko.templateEngine();
+ko.setTemplateEngine(new ko.nativeTemplateEngine());
+
+ko.exportSymbol('ko.nativeTemplateEngine', ko.nativeTemplateEngine);

1 comment on commit bb2b80a

looks sweet! I also forked your rep to write some very simple element based templating engine some time ago, no more ugly jquery templates!

Please sign in to comment.