--- a/client/api/scripts/blade/jig.js
+++ b/client/api/scripts/blade/jig.js
@@ -1,51 +1,63 @@
/**
- * @license blade/jig Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved.
+ * @license blade/jig Copyright (c) 2010, The Dojo Foundation All Rights Reserved.
* Available via the MIT, GPL or new BSD license.
* see: http://github.com/jrburke/blade for details
*/
/*jslint nomen: false, plusplus: false */
/*global require: false */
"use strict";
-if (!String.prototype.trim) {
- String.prototype.trim = function () {
- return this.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
- };
-}
-
require.def("blade/jig", ["blade/object"], function (object) {
- //Use _ to indicate current data value.
//Need to allow scope lookup for vars
- //Add comment command
//Fix unit test: something is wrong with it, says it passes, but
//with attachData change, the string is actually different now.
//TODO: for attachData, only generate a new ID when the data value changes,
//and similarly, only attach the data one time per data value.
- //TODO: ALLOW && AND || in the getobject values?
var jig, commands,
ostring = Object.prototype.toString,
decode = typeof decodeURIComponent === "undefined" ? function () {} : decodeURIComponent,
startToken = '{',
endToken = '}',
rawHtmlToken = '^',
argSeparator = ' ',
//First character in an action cannot be something that
//could be the start of a regular JS property name,
//or an array indice indicator, [, or the HTML raw output
//indicator, ^.
propertyRegExp = /[_\[\^\w]/,
defaultArg = '_',
startTagRegExp = /<\s*\w+/,
+ wordRegExp = /^\d+$/,
+ badCommentRegExp = /\/(\/)?\s*\]/,
templateCache = {},
- defaultFuncs = {},
+ defaultFuncs = {
+ gt: function (a, b) {
+ return a > b;
+ },
+ gte: function (a, b) {
+ return a >= b;
+ },
+ lt: function (a, b) {
+ return a < b;
+ },
+ lte: function (a, b) {
+ return a <= b;
+ },
+ or: function (a, b) {
+ return !!(a || b);
+ },
+ and: function (a, b) {
+ return !!(a && b);
+ }
+ },
attachData = true,
dataIdCounter = 0,
dataRegistry = {};
function isArray(it) {
return ostring.call(it) === "[object Array]";
}
@@ -61,32 +73,51 @@ require.def("blade/jig", ["blade/object"
return value ? parseInt(value, 10) : 0;
}
function getObject(name, data, options) {
var brackRegExp = /\[([\w0-9\.'":]+)\]/,
part = name,
parent = data,
match, pre, prop, obj, startIndex, endIndex, indices, result,
- parenStart, parenEnd, func, funcName;
+ parenStart, parenEnd, func, funcName, arg, args, i;
//If asking for the default arg it means giving back the current data.
if (name === defaultArg) {
return data;
}
+ //If name is just an integer, just return it.
+ if (wordRegExp.test(name)) {
+ return strToInt(name);
+ }
+
//First check for function call. Function must be globally visible.
if ((parenStart = name.indexOf('(')) !== -1) {
parenEnd = name.lastIndexOf(')');
funcName = name.substring(0, parenStart);
func = options.funcs[funcName];
if (!func) {
throw new Error('Cannot find function named: ' + funcName + ' for ' + name);
}
- return func(getObject(name.substring(parenStart + 1, parenEnd), data, options));
+ arg = name.substring(parenStart + 1, parenEnd);
+ if (arg.indexOf(',')) {
+ args = arg.split(',');
+ for (i = args.length - 1; i >= 0; i--) {
+ args[i] = getObject(args[i], data, options);
+ }
+ result = func.apply(null, args);
+ } else {
+ result = func(getObject(arg, data, options));
+ }
+ //If a function returns true, then use the current data as the
+ //return object.
+ if (result === true) {
+ return data;
+ }
}
//Now handle regular object references, which could have [] notation.
while ((match = brackRegExp.exec(part))) {
prop = match[1].replace(/['"]/g, "");
pre = part.substring(0, match.index);
part = part.substring(match.index + match[0].length, part.length);
@@ -126,18 +157,29 @@ require.def("blade/jig", ["blade/object"
return result;
}
commands = {
'_default_': {
doc: 'Property reference',
action: function (args, data, options, children, render) {
var value = args[0] ? getObject(args[0], data, options) : data,
+ comparison = args[1] ? getObject(args[1], data, options) : undefined,
i, text = '';
- if (value === null || value === undefined || (isArray(value) && !value.length)) {
+
+ //If comparing to some other value, then the value is the data,
+ //and need to compute if the values compare.
+ if (args[1]) {
+ comparison = value === comparison;
+ value = data;
+ } else {
+ //Just use the value, so the value is used in the comparison.
+ comparison = value;
+ }
+ if (comparison === null || comparison === undefined || (isArray(comparison) && !comparison.length)) {
return '';
} else if (children) {
if (isArray(value)) {
for (i = 0; i < value.length; i++) {
text += render(children, value[i], options);
}
} else {
text = render(children, value, options);
@@ -146,18 +188,30 @@ require.def("blade/jig", ["blade/object"
text = value;
}
return text;
}
},
'!': {
doc: 'Not',
action: function (args, data, options, children, render) {
- var value = getObject(args[0], data, options);
- if (children && !value) {
+ var value = getObject(args[0], data, options),
+ comparison = args[1] ? getObject(args[1], data, options) : undefined;
+
+ //If comparing to some other value, then the value is the data,
+ //and need to compute if the values compare.
+ if (args[1]) {
+ comparison = value === comparison;
+ value = data;
+ } else {
+ //Just use the value, so the value is used in the comparison.
+ comparison = value;
+ }
+
+ if (children && !comparison) {
return render(children, data, options);
}
return '';
}
},
'@': {
doc: 'Template reference',
action: function (args, data, options, children, render) {
@@ -190,17 +244,18 @@ require.def("blade/jig", ["blade/object"
jig.htmlEscape = function (text) {
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
};
function compile(text, options) {
var compiled = [],
start = 0,
useRawHtml = false,
- segment, index, match, tag, command, args, lastArg, lastChar, children;
+ segment, index, match, tag, command, args, lastArg, lastChar,
+ children, i;
while ((index = text.indexOf(options.startToken, start)) !== -1) {
//Output any string that is before the template tag start
if (index !== start) {
compiled.push(text.substring(start, index));
}
//Find the end of the token
@@ -217,16 +272,22 @@ require.def("blade/jig", ["blade/object"
start = index + match[0].length;
//Pull out the command
tag = text.substring(index + options.startToken.length, index + match[0].length - options.endToken.length).trim();
//decode in case the value was in an URL field, like an href or an img src attribute
tag = decode(tag);
+ //if the command is commented out end block call, that messes with stuff,
+ //just throw to let the user know, otherwise browser can lock up.
+ if (badCommentRegExp.test(tag)) {
+ throw new Error('blade/jig: end block tags should not be commented: ' + tag);
+ }
+
command = tag.charAt(0);
if (command && !options.propertyRegExp.test(command)) {
//Have a template command
tag = tag.substring(1).trim();
} else {
command = '_default_';
//Command could contain just the raw HTML indicator.
@@ -260,16 +321,27 @@ require.def("blade/jig", ["blade/object"
return compiled;
}
//If this defines a template, save it off,
//if a comment (starts with /), then ignore it.
if (command === '+') {
options.templates[args[0]] = children;
} else if (command !== '/') {
+ //Adjust args if some end in commas, it means they are function
+ //args.
+ if (args.length > 1) {
+ for (i = args.length - 1; i >= 0; i--) {
+ if (args[i].charAt(args[i].length - 1) === ',') {
+ args[i] = args[i] + args[i + 1];
+ args.splice(i + 1, 1);
+ }
+ }
+ }
+
compiled.push({
action: options.commands[command].action,
useRawHtml: useRawHtml,
args: args,
children: children
});
}
}
@@ -344,20 +416,26 @@ require.def("blade/jig", ["blade/object"
* definition.
* @returns {String} the rendered template.
*/
jig.render = function (compiled, data, options) {
//Normalize options, filling in defaults.
options = options || {};
object.mixin(options, {
templates: templateCache,
- funcs: defaultFuncs,
attachData: attachData
});
+ //Mix in default funcs
+ if (options.funcs) {
+ object.mixin(options.funcs, defaultFuncs);
+ } else {
+ options.funcs = defaultFuncs;
+ }
+
return render(compiled, data, options);
};
/**
* Gets the data bound to a particular rendered template.
* @param {String} dataId the data ID. It can be fetched from the
* data-blade-jig attribute on a rendered template.
* @returns {Object} the bound data. Can return undefined if there is