Skip to content

Commit

Permalink
#53 fix issue where partial rendered from view with layout got also a…
Browse files Browse the repository at this point in the history
… layout, moved verbatim tag into separate file, started to work on tr tag
  • Loading branch information
kof committed Jan 10, 2013
1 parent 947fafb commit 356317c
Show file tree
Hide file tree
Showing 11 changed files with 287 additions and 132 deletions.
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ test:
test-jqtpl:
./node_modules/qunit/bin/cli.js -c ./lib/jqtpl.js -t ./test/jqtpl.js -l "{assertions: true, globalSummary: true, errors: true}"

test-tags:
NODE_PATH=$(CURDIR)/lib ./node_modules/qunit/bin/cli.js -d lib/tags/tr.js lib/tags/verbatim.js -c ./lib/jqtpl.js -t ./test/tags.js -l "{assertions: true, globalSummary: true, errors: true}"

test-express:
./node_modules/qunit/bin/cli.js -c ./lib/express.js -t ./test/express.js -l "{assertions: true, globalSummary: true, errors: true}"

Expand Down
4 changes: 3 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
module.exports = exports = require('./lib/jqtpl');

exports.version = require('./package.json').version;

require('./lib/tags/tr');
require('./lib/tags/verbatim');

var express;

try {
Expand Down
4 changes: 3 additions & 1 deletion lib/express.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ jqtpl.$.partial = function(path, options, currentOptions) {

path = resolvePath(path, options);

options.__isPartial = true;

return exports.render(path, options);
};

Expand Down Expand Up @@ -136,7 +138,7 @@ function render(path, options, callback) {
throw err;
}

if (!options.__isLayout) {
if (!options.__isLayout && !options.__isPartial) {
layoutPath = resolveLayoutPath(options);
if (layoutPath) {
options.__parent = _.clone(options);
Expand Down
264 changes: 146 additions & 118 deletions lib/jqtpl.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,50 +11,18 @@
* @author Oleg Slobodskoi
*/

var isArray = Array.isArray;

/**
* Use this technique because, because Array.isArray is not in
* couchdb env and instanceof will not work if different global contexts used.
*
* @param {Object} obj any object.
* @return {Boolean}
* @api private
*/
if (!isArray) {
isArray = function(obj) {
return {}.toString.call(obj) === '[object Array]';
};
}

/**
* Escape template.
*
* @param {String} template.
* @return {String}
* @api private
*/
function escape(str) {
return str.replace(/([\\"])/g, '\\$1')
.replace(/\n/g, '\\n')
.replace(/\r/g, '\\r')
.replace(/\t/g, '\\t');
}

/**
* Unescape template.
*
* @param {String} template.
* @return {String}
* @api private
*/
function unescape(args) {
return args ? args.replace(/\\'/g, "'").replace(/\\"/g, '"').replace(/\\\\/g, '\\') : null;
}

var rVerbatim = /\{\{verbatim\}\}((.|\n)*?)\{\{\/verbatim\}\}/g,
rTags = /\$\{([^\}]*)\}/g,
rParser = /\{\{(\/?)(\w+|.)(?:\(((?:[^\}]|\}(?!\}))*?)?\))?(?:\s+(.*?)?)?(\(((?:[^\}]|\}(?!\}))*?)\))?\s*\}\}/g,
var rTags = /\$\{([^\}]*)\}/g,
/*
/\{\{ - "{{" start tag
(\/?) - "/" closing tag
(\w+|.) - "tag" tag name
(?:\(((?:[^\}]|\}(?!\}))*?)?\))? - "(args)" function call
(?:\s+(.*?)?)? - " bla" white space and property name
(\((?:[^\}]|\}(?!\})*?)\))? - "(args)" function call
\s* - whitespace
\}\}/g; - "}}" end tag
*/
rParser = /\{\{(\/?)(\w+|.)(?:\(((?:[^\}]|\}(?!\}))*?)?\))?(?:\s+(.*?)?)?(\((?:[^\}]|\}(?!\})*?)\))?\s*\}\}/g,
rEscapedWhite = /\\n|\\t|\\r/g;

/**
Expand All @@ -65,79 +33,90 @@ var rVerbatim = /\{\{verbatim\}\}((.|\n)*?)\{\{\/verbatim\}\}/g,
* @api private
*/
function build(markup) {
var verbatims = [],
body;
var compiled, body,

// A common context for pre/post compilers for state sharing.
context = {};

markup = markup.trim();

if (exports.pre.length) {
$.each(exports.pre, function(fn) {
markup = fn.call(context, markup);
});
}

// Convert the template into pure JavaScript.
compiled = $.escape(markup)
.replace(rTags, '{{= $1}}')
.replace(rParser, function(match, slash, type, fnargs, target, parens, offset, str) {
var tag = exports.tag[type], def, expr, exprAutoFnDetect,
args,
tagImpl,
inner,
ret;

if (!tag) {
throw new Error('Unknown template tag: ' + type);
}

def = tag.default || [];

if (parens && !/\w$/.test(target)) {
target += parens;
parens = '';
}

if (target) {
target = $.unescape(target).replace(rEscapedWhite, '');
args = parens ? ')' : '';

// Supported port for target being things like a.toLowerCase().
// In that case don't call with template item as 'this' pointer. Just evaluate...
expr = parens ? (target.indexOf('.') > -1 ? target + $.unescape(parens) : ('(' + target + ').call(__data' + args)) : target;
exprAutoFnDetect = parens ? expr : '(typeof(' + target + ')==="function"?(' + target + ').call(__data):(' + target + '))';
} else {
exprAutoFnDetect = expr = def.__1 || null;
}

fnargs = $.unescape(fnargs);

ret = '";';

tagImpl = tag[slash ? 'close' : 'open'];

if (tagImpl) {
ret += tagImpl
.split('__notnull_1').join(target ? 'typeof(' + target + ')!=="undefined" && (' + target + ')!=null' : true)
.split('__1a').join(exprAutoFnDetect)
.split('__1').join(expr)
.split('__2').join(fnargs || def.__2 || null);
}

ret += '__body+="';

return ret;
});

if (exports.post.length) {
$.each(exports.post, function(fn) {
compiled = fn.call(context, compiled);
});
}

// Function body
body =
'var __body="";' +

// Introduce the data as local variables using with(){}.
'with(__data){' +
'__body+="' +

// Convert the template into pure JavaScript.
escape(markup)
.trim()
// Save all the data inside the verbatim tags.
.replace(rVerbatim, function(all, content) {
verbatims.push(content);

// Replace the {{verbatim}}data{{/verbatim}} with just {{verbatim}}
// this tag will let the parser know where to inject the corresponding data.
return "{{verbatim}}";
})
.replace(rTags, '{{= $1}}')
.replace(rParser, function(all, slash, type, fnargs, target, parens, args) {
var tag = exports.tag[type], def, expr, exprAutoFnDetect;

if (!tag) {
throw new Error('Unknown template tag: ' + type);
}

if (type == 'verbatim') {

// Inject the corresponding verbatim data.
return escape(verbatims.shift());
}

def = tag.default || [];

if (parens && !/\w$/.test(target)) {
target += parens;
parens = '';
}

if (target) {
target = unescape(target).replace(rEscapedWhite, '');

if (args) {
args = ',' + unescape(args) + ')';
args = args.replace(rEscapedWhite, '');
} else {
args = parens ? ')' : '';
}

// Support for target being things like a.toLowerCase();
// In that case don't call with template item as 'this' pointer. Just evaluate...
expr = parens ? (target.indexOf('.') > -1 ? target + unescape(parens) : ('(' + target + ').call(__data' + args)) : target;
exprAutoFnDetect = parens ? expr : '(typeof(' + target + ')==="function"?(' + target + ').call(__data):(' + target + '))';
} else {
exprAutoFnDetect = expr = def.__1 || 'null';
}
fnargs = unescape(fnargs);
return '";' +
tag[slash ? 'close' : 'open']
.split('__notnull_1').join(target ? 'typeof(' + target + ')!=="undefined" && (' + target + ')!=null' : 'true')
.split('__1a').join(exprAutoFnDetect)
.split('__1').join(expr)
.split('__2').join(fnargs || def.__2 || '') +
'__body += "';
}) +
'"' +
'__body+="' + compiled + '";' +
'}' +
'return __body;';

return new Function('$', '__data', body);
console.log(body);

return new Function('$', '__data', 'undefined', body);
}

/**
Expand All @@ -152,7 +131,7 @@ function render(name, data) {
var fn = exports.cache[name],
ret, i;

if (data && isArray(data)) {
if (data && $.isArray(data)) {
ret = '';
for (i = 0; i < data.length; ++i) {
ret += fn.call({}, $, data[i]);
Expand All @@ -172,21 +151,19 @@ function render(name, data) {
*/
exports.tag = {
partial: {
default: {__2: 'null'},

// Partal target parameter can be of type function, so use __1,
// Partial target parameter can be of type function, so use __1,
// not __1a (so not auto detection of functions) This means that
// {{partial foo}} treats foo as a template (which IS a function).
// Explicit parens can be used if foo is a function that returns
// a template: {{partial foo()}}.
open: 'if(__notnull_1){__body+=$.partial(__1,__2,__data)}'
},
each: {
default: {__2: '$value, $index'},
default: {__2: '$value,$index'},
open: 'if(__notnull_1){$.each(__1a,function(__2){',
close: '});}'
},
verbatim: {},

// Uncoded expression evaluation.
html: {
Expand All @@ -197,7 +174,7 @@ exports.tag = {
close: '}'
},
'else': {
default: {__1: 'true'},
default: {__1: true},
open: '}else if((__notnull_1) && __1a){'
},

Expand All @@ -208,11 +185,25 @@ exports.tag = {
},

// Comment tag. Skipped by parser.
'!': {
open: ''
}
'!': {}
};

/**
* Precompiler functions, which will be called before the main compilation steps.
*
* @type {Array}.
* @api public
*/
exports.pre = [];

/**
* Postcompiler functions, which will be called after the main compilation steps.
*
* @type {Array}.
* @api public
*/
exports.post = [];

/**
* Cached template generator functions.
*
Expand Down Expand Up @@ -294,7 +285,7 @@ $.partial = exports.render;
$.each = function(obj, callback) {
var key;

if (isArray(obj)) {
if ($.isArray(obj)) {
for (key = 0; key < obj.length; ++key) {
callback.call(obj[key], obj[key], key);
}
Expand All @@ -321,4 +312,41 @@ $.escapeHtml = function(str) {
.replace(/"/g, '&quot;');
};

/**
* Escape template.
*
* @param {String} template.
* @return {String}
* @api private
*/
$.escape = function(str) {
return str.replace(/([\\"])/g, '\\$1')
.replace(/\n/g, '\\n')
.replace(/\r/g, '\\r')
.replace(/\t/g, '\\t');
};

/**
* Unescape template.
*
* @param {String} template.
* @return {String}
* @api private
*/
$.unescape = function(args) {
return args ? args.replace(/\\'/g, "'").replace(/\\"/g, '"').replace(/\\\\/g, '\\') : null;
};

/**
* Use this technique because, because Array.isArray is not in
* couchdb env and instanceof will not work if different global contexts used.
*
* @param {Object} obj any object.
* @return {Boolean}
* @api private
*/
$.isArray = Array.isArray || function(obj) {
return {}.toString.call(obj) === '[object Array]';
};

}(typeof exports == 'object' ? exports : (this.jqtpl = {})));
Loading

0 comments on commit 356317c

Please sign in to comment.