Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Initial Commit

  • Loading branch information...
commit 58601b35c592ec8b1ade777dcbfc2696b4c8ae41 0 parents
Malte Ubl authored
20 LICENSE
... ... @@ -0,0 +1,20 @@
  1 +Copyright (c) 2009 Malte Ubl
  2 +
  3 +Permission is hereby granted, free of charge, to any person obtaining
  4 +a copy of this software and associated documentation files (the
  5 +"Software"), to deal in the Software without restriction, including
  6 +without limitation the rights to use, copy, modify, merge, publish,
  7 +distribute, sublicense, and/or sell copies of the Software, and to
  8 +permit persons to whom the Software is furnished to do so, subject to
  9 +the following conditions:
  10 +
  11 +The above copyright notice and this permission notice shall be
  12 +included in all copies or substantial portions of the Software.
  13 +
  14 +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  15 +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  16 +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  17 +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  18 +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  19 +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  20 +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
125 README.md
Source Rendered
... ... @@ -0,0 +1,125 @@
  1 +# node-syncEJS - 0.0.1
  2 +
  3 +> An asynchronous ERB-like templating system for node.js
  4 +
  5 +node-asyncEJS implements a templating language for embedding JavaScript into
  6 +other text documents such as HTML. It adds new features to the classic ERB
  7 +syntax to enable asynchronous execution of the template.
  8 +
  9 +## When to use
  10 +You can always use the synchronous features. So use it whenever you need a
  11 +templating solution.
  12 +
  13 +The asyncronous features come in handy when you
  14 +1. Want to flush the output to the client as early as possible
  15 +2. Need to stream your generated content
  16 +
  17 +## Usage
  18 +
  19 +### Template
  20 +
  21 + <html>
  22 + <head>
  23 + <% ctx.hello = "World"; %>
  24 + <title><%= "Hello " + ctx.hello %></title>
  25 + </head>
  26 + <body>
  27 +
  28 + <h1><%? setTimeout(function () { res.print("Async Header"); res.finish(); }, 2000) %></h1>
  29 + <p><%? setTimeout(function () { res.print("Body"); res.finish(); }, 1000) %></p>
  30 +
  31 + </body>
  32 + </html>
  33 +
  34 +### JavaScript
  35 +
  36 + var te = require("../lib/asyncEJS").Engine();
  37 +
  38 + var template = te.template("template.t.html");
  39 +
  40 + var templateResponse = template(paras);
  41 +
  42 + templateResponse.addListener("body", function (chunk) {
  43 + sys.print(chunk);
  44 + });
  45 +
  46 + templateResponse.addListener("complete", function () {
  47 + sys.puts("COMPLETE")
  48 + });
  49 +
  50 +## Templates
  51 +
  52 +### Special Variables
  53 +
  54 +`ctx` contains the parameter that was passed to the template function
  55 +
  56 +`res` represents the output of the template.
  57 +
  58 +`res.print("string")` prints more output into the template
  59 +
  60 +`res.finish()` tells the template that the current asynchronous blocks has finished execution
  61 +
  62 +### Basic Template Commands
  63 +
  64 +`<% var javascript = "code" %>` executes arbitrary JavaScript
  65 +
  66 +`<%= "Hello " + ctx.hello %>` outputs the statement result into the template
  67 +
  68 +### Asynchronous Templates
  69 +
  70 + <%? setTimeout(function () { res.print("Async Header"); res.finish(); }, 2000) %>
  71 +
  72 +`<%?` introduces a block that will be expected to execute asynchronously with respect
  73 +to the rest of the template. The rest of the template will continue executing but no
  74 +output will be returned until the `res.finish()` method will be called.
  75 +
  76 +### Escaping
  77 +
  78 +There is currently no escaping of output but this will change.
  79 +
  80 +## JavaScript Usage
  81 +
  82 +### Template Engine
  83 +
  84 +Construct a template engine with
  85 +
  86 + var te = require("../lib/asyncEJS").Engine({
  87 + autoUpdate: false
  88 + });
  89 +
  90 +If `autoUpdate` is true, asyncEJS will continously look for changes in templates and automatically
  91 +update them when they change on disk.
  92 +
  93 +### Loading Templates
  94 +
  95 +After you instatiated a template engine use the `template` method to create a template
  96 +function.
  97 +
  98 + var template = te.template("template.t.html");
  99 +
  100 +Executing the template function with `paras` will return a templateResponse. The `paras` are
  101 +accessible as `ctx` variable inside the template.
  102 +
  103 + var templateResponse = template(paras);
  104 +
  105 +### Template Responses
  106 +
  107 +The template response emits two events `body` and `complete`
  108 +
  109 + templateResponse.addListener("body", function (chunk) {
  110 + sys.print(chunk);
  111 + });
  112 +
  113 + templateResponse.addListener("complete", function () {
  114 + sys.puts("COMPLETE")
  115 + });
  116 +
  117 +`body` is emitted whenever a part of the template has been completed.
  118 +
  119 +`complete` will fire once when the template has fully executed.
  120 +
  121 +## Examples
  122 +
  123 +See `examples/` and `test/`
  124 +
  125 +
8 examples/async.js.html
... ... @@ -0,0 +1,8 @@
  1 +<html>
  2 + <body>
  3 +
  4 + <h1><%? setTimeout(function () { res.print("Async Header"); res.finish(); }, 2000) %></h1>
  5 + <p><%? setTimeout(function () { res.print("Body"); res.finish(); }, 1000) %></p>
  6 +
  7 + </body>
  8 +</html>
20 examples/run.js
... ... @@ -0,0 +1,20 @@
  1 +var sys = require("sys"),
  2 + posix = require("posix");
  3 +
  4 +var filename = process.ARGV[2];
  5 +var paras = JSON.parse(process.ARGV[3] || "{}")
  6 +
  7 +// The template Engine
  8 +var te = require("../lib/asyncEJS").Engine();
  9 +
  10 +var template = te.template(filename);
  11 +
  12 +var templateResponse = template(paras);
  13 +
  14 +templateResponse.addListener("body", function (chunk) {
  15 + sys.print(chunk);
  16 +});
  17 +
  18 +templateResponse.addListener("complete", function () {
  19 + sys.puts("COMPLETE")
  20 +});
1  examples/simple.js.html
... ... @@ -0,0 +1 @@
  1 +<%= "Hello World" %>
12 examples/usage.js.html
... ... @@ -0,0 +1,12 @@
  1 +<html>
  2 + <head>
  3 + <% ctx.hello = "World"; %>
  4 + <title><%= "Hello " + ctx.hello %></title>
  5 + </head>
  6 + <body>
  7 +
  8 + <h1><%? setTimeout(function () { res.print("Async Header"); res.finish(); }, 2000) %></h1>
  9 + <p><%? setTimeout(function () { res.print("Body"); res.finish(); }, 1000) %></p>
  10 +
  11 + </body>
  12 +</html>
249 lib/asyncEJS.js
... ... @@ -0,0 +1,249 @@
  1 +var posix = require('posix'),
  2 + sys = require('sys');
  3 +
  4 +var templateCache = {};
  5 +
  6 +// A TemplateEngine holds template factories
  7 +// plus a config for all templates
  8 +var TemplateEngine = function (config) {
  9 + config = config || {};
  10 + this.autoUpdate = config.autoUpdate
  11 +}
  12 +
  13 +exports.Engine = function (config) {
  14 + return new TemplateEngine(config);
  15 +}
  16 +
  17 +TemplateEngine.prototype = {
  18 +
  19 + // Synchronous template constructor
  20 + template: function (filename) {
  21 + return this.templateAsync(filename).wait();
  22 + },
  23 +
  24 + // Asynchronous template constructor
  25 + // Returns a promise for a template
  26 + templateAsync: function (filename) {
  27 + var self = this;
  28 + var templatePromise = new process.Promise();
  29 +
  30 + if(templateCache[filename]) {
  31 + setTimeout(function () {
  32 + templatePromise.emitSuccess(templateCache[filename]);
  33 + }, 0);
  34 + return templatePromise;
  35 + }
  36 +
  37 + load(filename, function (fn) {
  38 +
  39 + var t = function (ctx) {
  40 + var res = new TemplateResponse(self, filename);
  41 + fn(ctx, res);
  42 + setTimeout(function () {
  43 + res.partFinished();
  44 + }, 0)
  45 + return res;
  46 + };
  47 +
  48 + templateCache[filename] = t;
  49 +
  50 + if(self.autoUpdate) {
  51 + process.watchFile(filename, {
  52 + persistent: true,
  53 + interval: 2000
  54 + }, function () {
  55 + load(filename, function (newFn) {
  56 + fn = newFn;
  57 + })
  58 + });
  59 + }
  60 +
  61 + templatePromise.emitSuccess(t);
  62 + }, function (e) {
  63 + templatePromise.emitError(e)
  64 + });
  65 + return templatePromise;
  66 + },
  67 +
  68 + // Should not be public :)
  69 + partial: function (filename, res, ctx) {
  70 + var partialPromise = new process.Promise();
  71 + var p = this.templateAsync(filename);
  72 +
  73 + var partRes;
  74 +
  75 + if(res) {
  76 + res.schedule(function (res) {
  77 + partRes = res;
  78 + })
  79 + }
  80 +
  81 + p.addCallback(function (t) {
  82 + var partialResponse = t(ctx);
  83 + //sys.puts("Partial "+partialResponse.filename)
  84 + partialResponse.addListener("body", function (chunk) {
  85 + //sys.puts("PartialChunk"+partialResponse.filename)
  86 + partRes.print(chunk);
  87 + });
  88 + partialResponse.addListener("complete", function () {
  89 + //sys.error("Partial done "+partialResponse.filename);
  90 + partialResponse.finished = true;
  91 + partRes.finish();
  92 + res.partFinished();
  93 + partialPromise.emitSuccess();
  94 + });
  95 + })
  96 + return partialPromise;
  97 + }
  98 +};
  99 +
  100 +function load(filename, cb, errb) {
  101 + posix.cat(filename).addCallback(function (str) {
  102 + //sys.puts(str.toString());
  103 +
  104 + var src = parseAndGenerate(str);
  105 +
  106 + //sys.puts(src);
  107 + var fn = process.compile(src, filename);
  108 + cb(fn);
  109 + });
  110 +}
  111 +
  112 +
  113 +function TemplateResponse(engine, filename) {
  114 + var self = this;
  115 + process.EventEmitter.call(this);
  116 + this.engine = engine;
  117 + this.filename = filename;
  118 + this.p = [];
  119 + this.returned = 0;
  120 + this.finished = false;
  121 +}
  122 +
  123 +process.inherits(TemplateResponse, process.EventEmitter);
  124 +process.mixin(TemplateResponse.prototype, {
  125 + print: function () {
  126 + this.p.push.apply(this.p, arguments);
  127 + },
  128 +
  129 + toString: function () {
  130 + return "<a TemplateResponse for "+this.filename+" "+this.finished+">"
  131 + },
  132 +
  133 + finish: function () {
  134 + this.emit("complete");
  135 + },
  136 +
  137 + schedule: function (fn) {
  138 + var part = this._schedule(fn);
  139 + this.p.push(part);
  140 + },
  141 +
  142 + _schedule: function (fn) {
  143 + var self = this;
  144 + var part = new TemplateResponse(this.engine, this.filename+"->async");
  145 + part.addListener("complete", function () {
  146 + part.finished = true;
  147 + self.partFinished();
  148 + })
  149 + fn.call(part, part);
  150 + return part;
  151 + },
  152 +
  153 + partial: function (filename, ctx) {
  154 + return this.engine.partial(filename, this, ctx);
  155 + },
  156 +
  157 + partFinished: function () {
  158 + var self = this;
  159 + if(this.returned < this.p.length) {
  160 + var done = this._partFinished(function (chunk) {
  161 + self.emit("body", chunk);
  162 + //sys.error("EMIT "+self.filename+" -> "+JSON.stringify(chunk))
  163 + });
  164 +
  165 + if(done === this.p.length) {
  166 + this.emit("complete");
  167 + }
  168 + }
  169 + },
  170 +
  171 + _partFinished: function (cb, start) {
  172 + var p = this.p;
  173 + //sys.puts(this.filename + " -> "+JSON.stringify(this.p))
  174 + if(start != null) {
  175 + this.returned = start
  176 + }
  177 + for(var i = this.returned, len = p.length; i < len; ++i) {
  178 + var part = p[i];
  179 + if(!(part instanceof TemplateResponse)) {
  180 + cb(part);
  181 + this.returned++;
  182 + }
  183 + else if(part.finished) {
  184 + part._partFinished(cb, 0)
  185 + this.returned++;
  186 + } else {
  187 + break;
  188 + }
  189 + }
  190 + return i;
  191 + }
  192 +});
  193 +
  194 +function parseAndGenerate(str) {
  195 + var open = "<%";
  196 + var close = "%>";
  197 + var index;
  198 + var dynPart = [];
  199 + // TODO tweak the second replace so that the first can go away (and the later which reverse it)
  200 + var staticParts = str.replace(/[\n\r]/g, "<;;;;>").replace(/<%(.*?)%>/g, function (part, code) {
  201 + dynPart.push(code.replace(/<;;;;>/g, "\n"));
  202 + return "<%%>"
  203 + }).replace(/<;;;;>/g, "\n").replace(/'/g, "\\'").replace(/[\n\r]/g, "\\n\\\n").split(/<%%>/);
  204 +
  205 + var src = 'function __template__ (ctx, res) {';
  206 +
  207 + src = staticParts.reduce(function(src, part, index){
  208 + src = src + "res.p.push('" + part + "');";
  209 + var code = dynPart[index];
  210 + if(code != null) {
  211 + if(code.charAt(0) === "=") {
  212 + code = code.substr(1);
  213 + src = src + "res.p.push("+code+");";
  214 + }
  215 + else if(code.charAt(0) === "?") {
  216 + code = code.substr(1);
  217 + src = src + "res.p.push(res._schedule(function (res) {"+code+"}));"
  218 + }
  219 + else {
  220 + src = src + code
  221 + }
  222 + }
  223 + return src;
  224 + }, src) + "return res}__template__;";
  225 + return src;
  226 +}
  227 +
  228 +function parseJohnResig(str) {
  229 + // Stolen from underscore.js http://documentcloud.github.com/underscore/underscore.js
  230 + // JavaScript templating a-la ERB, pilfered from John Resig's
  231 + // "Secrets of the JavaScript Ninja", page 83.
  232 + var src =
  233 + 'function __template__ (ctx, res) {' +
  234 + 'res.p.push(\'' +
  235 + str
  236 + .replace(/[\t]/g, " ")
  237 + .replace(/[\n\r]/g, "<%\n%>\\n")
  238 + .split("<%").join("\t")
  239 + .replace(/((^|%>)[^\t]*)'/g, "$1\n")
  240 + .replace(/\t=(.*?)%>/g, "',$1,'")
  241 + .replace(/\t\?(.*?)%>/g, "',res._schedule(function (res) {$1}),'")
  242 + .split("\t").join("');")
  243 + .split("%>").join("res.p.push('")
  244 + .split("\r").join("\\'")
  245 + + "');return res}__template__;";
  246 +
  247 + return src;
  248 +}
  249 +
3  test/fixtures/partial.js.html
... ... @@ -0,0 +1,3 @@
  1 +<p>Hello: <%? setTimeout(function () { res.print(ctx.hello); res.finish(); }, 1000); %></p>
  2 +
  3 +<em><% res.partial("fixtures/partialStatic.js.html"); %></em>
1  test/fixtures/partialStatic.js.html
... ... @@ -0,0 +1 @@
  1 +Static
18 test/fixtures/reference.html
... ... @@ -0,0 +1,18 @@
  1 +<html>
  2 + <body>
  3 +
  4 + <h2>{"hello":[],"test":1}</h2>
  5 +
  6 + <h1>Async1</h1>
  7 + <p>Async2</p>
  8 +
  9 + <h2>Async3</h2>
  10 +
  11 + <p>Hello: world</p>
  12 +
  13 +<em>Static</em>
  14 +
  15 + <h6>Async4Static</h6>
  16 +
  17 + </body>
  18 +</html>
22 test/fixtures/template.js.html
... ... @@ -0,0 +1,22 @@
  1 +<html>
  2 + <body>
  3 + <% ctx.test = 1; %>
  4 + <h2><%= JSON.stringify(ctx) %></h2>
  5 +
  6 + <h1><%? setTimeout(function () { res.print("Async1"); res.finish(); }, 400) %></h1>
  7 + <p><%? setTimeout(function () { res.print("Async2"); res.finish(); }, 100) %></p>
  8 +
  9 + <h2><% res.schedule(function (res) {setTimeout(function () { %>Async3<% res.finish(); }, 300)}); %></h2>
  10 +
  11 + <% res.partial("fixtures/partial.js.html", { hello: "world" }); %>
  12 +
  13 + <h6><%? setTimeout(function () {
  14 + res.print("Async4");
  15 + res.partial("fixtures/partialStatic.js.html")
  16 + .addCallback(function () {
  17 + res.finish();
  18 + })
  19 + }, 200) %></h6>
  20 +
  21 + </body>
  22 +</html>
25 test/test.js
... ... @@ -0,0 +1,25 @@
  1 +var sys = require("sys"),
  2 + posix = require("posix");
  3 +
  4 +var reference = posix.cat("fixtures/reference.html").wait();
  5 +
  6 +var te = require("../lib/asyncEJS").Engine();
  7 +
  8 +var t1 = te.template("fixtures/template.js.html");
  9 +
  10 +var templateResponse = t1({
  11 + hello: []
  12 +});
  13 +var body = ""
  14 +templateResponse.addListener("body", function (chunk) {
  15 + body += chunk;
  16 + sys.print(chunk);
  17 +});
  18 +
  19 +templateResponse.addListener("complete", function () {
  20 + if(body === reference) {
  21 + sys.puts("OK");
  22 + } else {
  23 + sys.puts("NOT OK")
  24 + }
  25 +});

0 comments on commit 58601b3

Please sign in to comment.
Something went wrong with that request. Please try again.