From e866c087cbc307ac5febed9cc4911cb43217e9e4 Mon Sep 17 00:00:00 2001 From: Ed Date: Thu, 13 Jun 2013 17:35:04 +0100 Subject: [PATCH] Views can now have deferred properties. I need this functionality for a sub-project, and it wasn't sensible to make changes without tests! --- Views/viewParser.js | 53 ++++++++++---------- test/VIews/viewParser.js | 106 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 133 insertions(+), 26 deletions(-) create mode 100644 test/VIews/viewParser.js diff --git a/Views/viewParser.js b/Views/viewParser.js index 0ee861e..594bdba 100644 --- a/Views/viewParser.js +++ b/Views/viewParser.js @@ -1,41 +1,39 @@ // This is based on John Resig's tiny template engine. // I grabbed the code off Rick Strahl's weblog: http://www.west-wind.com/weblog/posts/2008/Oct/13/Client-Templating-with-jQuery -// Edited to work a bit better with node (we don't have DOM in this context). - -ViewParser = module.exports ={ - cache : {}, - - parse : function(name, unParsedTemplate) { - if (this.cache[name]) { - return this.cache[name]; - } +// Edited to allow the view function to include the results of deferreds. +ViewParser = module.exports = { + parse: function(name, unParsedTemplate) { var extracted = this.extractProperties(unParsedTemplate); var properties = extracted.properties; var template = extracted.template; - var str = "var p=[],print=function(){p.push.apply(p,arguments);};with(obj){p.push('"+ + var str = "var d = new Deferred();var dC = 0;var p=[]," + + "stackDeferred=function(index){ dC++;return function (r) { p[index] = r; dC--; if(!dC) {d.complete(p.join(''));}}}" + + ",print=function(){p.push.apply(p,arguments);};with(obj){p.push('" + // Now a big crazy Regex. - template.replace(/[\r\t\n]/g," ") - .replace(/'(?=[^%]*%>)/g,"\t") - .split("'").join("\\'") - .split("\t").join("'") - .replace(/<%=(.+?)%>/g,"',$1,'") - .split("<%").join("');") - .split("%>").join("p.push('") - - + "');}return p.join('');"; + template.replace(/[\r\t\n]/g, " ") + .replace(/'(?=[^%]*%>)/g, "\t") + .split("'").join("\\'") + .split("\t").join("'") + .replace(/<%=(.+?)%>/g, "');if ($1 instanceof Deferred){$1.onComplete(stackDeferred(p.length));p.push('');}else{p.push($1);}p.push('") + .split("<%").join("');") + .split("%>").join("p.push('") + + + "');}if (!dC) {d.complete(p.join(''));}return d;"; var fn = new Function("obj", str); - var output = { properties : properties, view : fn }; - this.cache[name] = output; + var output = { + properties: properties, + view: fn + }; return output; }, - extractProperties : function (viewText) { + extractProperties: function(viewText) { var template = viewText; var unParsedProperties = ''; @@ -46,13 +44,16 @@ ViewParser = module.exports ={ var toParse = viewText.substr(i, e + 2); template = viewText.substr(e + 2); - unParsedProperties += toParse.replace(/<%!(.+?)%>/g,"$1") + ','; + unParsedProperties += toParse.replace(/<%!(.+?)%>/g, "$1") + ','; } - unParsedProperties = unParsedProperties.substr(0, unParsedProperties.length -2); + unParsedProperties = unParsedProperties.substr(0, unParsedProperties.length - 2); properties = new Function("obj", "return {" + unParsedProperties + "};")(); - return { properties : properties, template : template }; + return { + properties: properties, + template: template + }; } -}; +}; \ No newline at end of file diff --git a/test/VIews/viewParser.js b/test/VIews/viewParser.js new file mode 100644 index 0000000..cf111f5 --- /dev/null +++ b/test/VIews/viewParser.js @@ -0,0 +1,106 @@ +describe('ViewParser', function() { + var viewParser; + beforeEach(function() { + viewParser = require('../../Views/viewParser'); + }); + + describe('parse', function() { + it('should return an empty string for an empty view.', function(done) { + var result = viewParser.parse('testViewName', ''); + + result.properties.should.eql([]); + + var deferred = result.view({}); + + deferred.onComplete(function(resultHtml) { + resultHtml.should.equal(''); + + done(); + }); + }); + + it('should return raw HTML from a view with no properties.', function(done) { + var result = viewParser.parse('testViewName', ''); + + result.properties.should.eql([]); + + var deferred = result.view({}); + + deferred.onComplete(function(resultHtml) { + resultHtml.should.equal(''); + + done(); + }); + }); + + it('should return just a variable for a view with only a bound variable.', function(done) { + var result = viewParser.parse('testViewName', '<%= test %>'); + + result.properties.should.eql([]); + + var deferred = result.view({ + test: 'Some data.' + }); + + deferred.onComplete(function(resultHtml) { + resultHtml.should.equal('Some data.'); + + done(); + }); + }); + + it('should return a variable embedded in HTML for a complex view.', function(done) { + var result = viewParser.parse('testViewName', '
<%= test %>
'); + + result.properties.should.eql([]); + + var deferred = result.view({ + test: 'Some data.' + }); + + deferred.onComplete(function(resultHtml) { + resultHtml.should.equal('
Some data.
'); + + done(); + }); + }); + + it('should allow arbitrary code in a view.', function(done) { + var result = viewParser.parse('testViewName', '
<% if (showTest) { %><%= test %><% } %>
'); + + result.properties.should.eql([]); + + var deferred = result.view({ + test: 'Some data.', + showTest: false + }); + + deferred.onComplete(function(resultHtml) { + resultHtml.should.equal('
'); + + done(); + }); + }); + + it('should wait for the result of a deferred parameter.', function(done) { + var result = viewParser.parse('testViewName', '
<% if (showTest) { %><%= test %><% } %>
'); + + result.properties.should.eql([]); + + var testDeferred = new Deferred(); + + var deferred = result.view({ + test: testDeferred, + showTest: true + }); + + deferred.onComplete(function(resultHtml) { + resultHtml.should.equal('
Some data.
'); + + done(); + }); + + testDeferred.complete('Some data.'); + }); + }); +}); \ No newline at end of file