Skip to content

Commit

Permalink
add async functions
Browse files Browse the repository at this point in the history
  • Loading branch information
mgutz committed Feb 20, 2012
1 parent 0636e81 commit 0ce268d
Show file tree
Hide file tree
Showing 6 changed files with 259 additions and 110 deletions.
9 changes: 9 additions & 0 deletions Cakefile
@@ -1,10 +1,19 @@
task "build", "Builds the project", ->
run "coffee --lint -c -b -o . src/"
writeHtml "src/test/index.funcd", "test/index.html"

task "test", "runs tests", ->
run "mocha -u exports src/test/funcdTest.coffee"


writeHtml = (file, out) ->
# lazily require because funcd may not compile cleanly while developing
funcd = require(".")
fs = require("fs")
content = funcd.render(file)
fs.writeFileSync out, content


cp = require("child_process")
run = (command) ->
cp.exec command, (error, stdout, stderr) ->
Expand Down
33 changes: 33 additions & 0 deletions README.md
Expand Up @@ -8,6 +8,7 @@ Template engine in the style of Builder, Markaby, Erector.
* Just functions
* Partials
* Safe HTML
* jQuery asynchronous element updates

## Installation

Expand Down Expand Up @@ -67,6 +68,38 @@ Safe HTML
Funcd.render (t) -> t.a t.raw("<i>apple</i>")


Render from files

# test.funcd
module.exports = (t, name, city) ->
t.div name + " " + city

# <div>foo San Diego</div>
Funcd.render "./test", "foo", "San Diego"


jQuery Asynchronous updates

module.exports = (t) ->
t.html ->
t.body ->
t.div id:"content"

t.coffeescript """
update = ($el) ->
setTimeout (->
$el.funcd (t) -> t.div "bar baby!"
), 2000

template = (t) ->
t.div style:"background-color:#999; color:#000", "inserted via template"
t.div "data-funcd-async":update, "this will change in 2 seconds"

$ ->
$('#content').funcd template
"""


OOP if you prefer

class Layout extends Funcd
Expand Down
170 changes: 109 additions & 61 deletions lib/funcd.js
@@ -1,9 +1,16 @@
var Funcd, attributeList, cs, defaultAttributes, detectCallerPath, doctypes, elements, escapeHtml, htmlChars, mergeElements, mixinShortTag, mixinTag, replaceToken, templateFunction, tokensToReplace, _,
var Funcd, attributeList, cs, defaultAttributes, detectCallerPath, doctypes, elements, escapeHtml, htmlChars, idSequence, mergeElements, mixinShortTag, mixinTag, nextId, rawContentElements, replaceToken, templateFunction, tokensToReplace, _,
__slice = Array.prototype.slice,
__indexOf = Array.prototype.indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };

if (typeof global !== "undefined" && global !== null) _ = require('underscore');

idSequence = 0;

nextId = function() {
idSequence += 1;
return "funcd-async-" + idSequence;
};

doctypes = {
'default': '<!DOCTYPE html>',
'5': '<!DOCTYPE html>',
Expand Down Expand Up @@ -39,6 +46,8 @@ defaultAttributes = {
}
};

rawContentElements = ['script'];

mergeElements = function() {
var arg, args, element, result, _i, _j, _len, _len2, _ref;
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
Expand Down Expand Up @@ -84,37 +93,61 @@ attributeList = function(tag, obj) {
return list;
};

detectCallerPath = function(referencePath, err) {
var i, match, path, _len, _ref;
_ref = err.stack.match(/\(([^:]+).*\)$/mg);
for (i = 0, _len = _ref.length; i < _len; i++) {
match = _ref[i];
path = match.match(/\(([^:]+)/)[1];
if (path !== referencePath) return path;
}
return null;
mixinTag = function(tag) {
return Funcd.prototype[tag] = function(attributes, inner) {
var options;
options = {
tag: tag,
parseBody: tag !== 'textarea',
parseAttributes: true
};
return this._outerHtml(options, attributes, inner);
};
};

templateFunction = function(file) {
var callerPath, content, cs, path, sandbox, template;
callerPath = detectCallerPath(__filename, new Error);
cs = require("coffee-script");
path = require("path");
if (file.indexOf('.') === 0) file = path.join(path.dirname(callerPath), file);
if (!file.match(/\.funcd$/)) file += ".funcd";
content = require("fs").readFileSync(file, 'utf8');
sandbox = {
global: {},
module: {
exports: {}
}
mixinShortTag = function(tag) {
return Funcd.prototype[tag] = function(attributes) {
var attrList;
attrList = "";
if (_.isObject(attributes)) attrList = attributeList(tag, attributes);
return this.buffer += this.lead + ("<" + tag + attrList + "/>") + this.eol;
};
cs.eval(content, {
sandbox: sandbox
});
return template = sandbox.module.exports;
};

if (typeof global !== "undefined" && global !== null) {
detectCallerPath = function(referencePath, err) {
var i, match, path, _len, _ref;
_ref = err.stack.match(/\(([^:]+).*\)$/mg);
for (i = 0, _len = _ref.length; i < _len; i++) {
match = _ref[i];
path = match.match(/\(([^:]+)/)[1];
if (path !== referencePath) return path;
}
return null;
};
templateFunction = function(file) {
var callerPath, content, cs, path, sandbox, template;
cs = require("coffee-script");
path = require("path");
if (file.indexOf('/') !== 0) {
callerPath = detectCallerPath(__filename, new Error);
file = path.join(path.dirname(callerPath), file);
}
if (!file.match(/\.funcd$/)) file += ".funcd";
content = require("fs").readFileSync(file, 'utf8');
sandbox = {
global: {},
module: {
exports: {}
}
};
cs.eval(content, {
sandbox: sandbox
});
return template = sandbox.module.exports;
};
}

Funcd = (function() {

function Funcd(opts) {
Expand All @@ -140,9 +173,22 @@ Funcd = (function() {
this.blocks = {};
this.eol = this.pretty ? '\n' : '';
this.buffers = [];
this.asyncCallbacks = [];
this.buffer = "";
}

Funcd.prototype.applyAsyncCallbacks = function($parent) {
var pair, _i, _len, _ref, _results;
if (!this.asyncCallbacks) return;
_ref = this.asyncCallbacks;
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
pair = _ref[_i];
_results.push(pair.lambda(jQuery('#' + pair.id), $parent));
}
return _results;
};

Funcd.prototype.block = function(name, attributes, inner) {
var exists, options;
this.buffers.push(this.buffer);
Expand Down Expand Up @@ -204,7 +250,7 @@ Funcd = (function() {
}
};

Funcd.render = function() {
Funcd.renderBuilder = function() {
var args, builder, first, options, template;
options = arguments[0], template = arguments[1], args = 3 <= arguments.length ? __slice.call(arguments, 2) : [];
args = Array.prototype.slice.call(arguments);
Expand All @@ -224,6 +270,13 @@ Funcd = (function() {
}
builder = new Funcd(options);
template.apply(builder, [builder].concat(args));
return builder;
};

Funcd.render = function() {
var args, builder;
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
builder = this.renderBuilder.apply(this, args);
return builder.toHtml();
};

Expand All @@ -242,7 +295,7 @@ Funcd = (function() {
};

Funcd.prototype._outerHtml = function(options, attrs, inner) {
var arg, attributes, innerHtmlFn, innerText, parseAttributes, parseBody, tag, _i, _len, _ref;
var arg, asyncfn, attributes, innerHtmlFn, innerText, parseAttributes, parseBody, tag, _i, _len, _ref;
tag = options.tag, parseAttributes = options.parseAttributes, parseBody = options.parseBody;
attributes = "";
innerText = "";
Expand All @@ -252,7 +305,11 @@ Funcd = (function() {
arg = _ref[_i];
switch (typeof arg) {
case 'string':
innerText += escapeHtml(arg);
if (rawContentElements.indexOf(tag) < 0) {
innerText += escapeHtml(arg);
} else {
innerText += arg;
}
break;
case 'number':
innerText += escapeHtml(arg.toString());
Expand All @@ -265,6 +322,15 @@ Funcd = (function() {
innerText = arg.__raw;
} else {
if (parseAttributes) {
if (arg["data-funcd-async"]) {
asyncfn = arg["data-funcd-async"];
delete arg["data-funcd-async"];
if (!arg.id) arg.id = nextId();
this.asyncCallbacks.push({
lambda: asyncfn,
id: arg.id
});
}
attributes += attributeList(tag, arg);
} else if (arg) {
innerText += arg.toString();
Expand All @@ -285,22 +351,15 @@ Funcd = (function() {
if (tag) return this.buffer += this.lead + ("</" + tag + ">" + this.eol);
};

Funcd.prototype._safeString = function(s) {
if (s.__raw) {
return s.__raw;
} else {
return s;
}
};

return Funcd;

})();

if (typeof global !== "undefined" && global !== null) {
cs = require("coffee-script");
Funcd.prototype.coffeescript = function(options, inner) {
var code, js;
var code, js, self;
self = this;
if (arguments.length === 1) {
inner = options;
options = null;
Expand All @@ -312,28 +371,17 @@ if (typeof global !== "undefined" && global !== null) {
type: "text/javascript"
}, js);
};
}

mixinTag = function(tag) {
return Funcd.prototype[tag] = function(attributes, inner) {
var options;
options = {
tag: tag,
parseBody: tag !== 'textarea',
parseAttributes: true
};
return this._outerHtml(options, attributes, inner);
} else {
jQuery.fn.funcd = function(template, args) {
return this.each(function() {
var $obj, builder;
$obj = jQuery(this);
builder = Funcd.renderBuilder(template);
$obj.html(builder.toHtml());
return builder.applyAsyncCallbacks($obj);
});
};
};

mixinShortTag = function(tag) {
return Funcd.prototype[tag] = function(attributes) {
var attrList;
attrList = "";
if (_.isObject(attributes)) attrList = attributeList(tag, attributes);
return this.buffer += this.lead + ("<" + tag + attrList + "/>") + this.eol;
};
};
}

(function() {
var tag, _i, _j, _len, _len2, _ref, _ref2;
Expand All @@ -347,7 +395,7 @@ mixinShortTag = function(tag) {
tag = _ref2[_j];
mixinShortTag(tag);
}
if (module && module.exports) {
if (typeof global !== "undefined" && global !== null) {
return module.exports = Funcd;
} else {
return window.Funcd = Funcd;
Expand Down

0 comments on commit 0ce268d

Please sign in to comment.