Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Commit counter 21. Major update, mostly to support new features in Js…

…Views,

as well as improved support for converter functions. Also preparing the
ground for upcoming changes making it possible to opt in to making any
JsRender tag data-bound (i.e. data-linked, when using JsViews)
  • Loading branch information...
commit 52cbe2a571d8044d3c8c59c38e435d65c0cae8b1 1 parent fb077a3
@BorisMoore authored
View
321 jsrender.js
@@ -6,19 +6,22 @@
* Copyright 2012, Boris Moore
* Released under the MIT License.
*/
-// informal pre beta commit counter: 20
+// informal pre beta commit counter: 21
-this.jsviews || this.jQuery && jQuery.views || (function(global, undefined) {
+(function(global, jQuery, undefined) {
+ // global is the this object, which is window when running in the usual browser environment.
+
+ if (jQuery && jQuery.views || global.jsviews) return; // JsRender is already loaded
//========================== Top-level vars ==========================
var versionNumber = "v1.0pre",
$, rTag, rTmplString, $extend,
- delimOpenChar0 = "{", delimOpenChar1 = "{", delimCloseChar0 = "}", delimCloseChar1 = "}",
+// compiledTmplsCache = {},
+ delimOpenChar0 = "{", delimOpenChar1 = "{", delimCloseChar0 = "}", delimCloseChar1 = "}", deferChar = "!",
$viewsSub = {},
FALSE = false, TRUE = true,
- jQuery = global.jQuery,
rPath = /^(?:null|true|false|\d[\d.]*|([\w$]+|~([\w$]+)|#(view|([\w$]+))?)([\w$.]*?)(?:[.[]([\w$]+)\]?)?|(['"]).*\8)$/g,
// nil object helper view viewProperty pathTokens leafToken string
@@ -59,7 +62,7 @@ this.jsviews || this.jQuery && jQuery.views || (function(global, undefined) {
View: View,
_convert: convert,
_err: function(e) {
- return $views.debugMode ? ("Error: " + (e.message || e)) + ". " : '""';
+ return $views.debugMode ? ("Error: " + (e.message || e)) + ". " : '';
},
_tmplFn: tmplFn,
_tag: renderTag,
@@ -73,13 +76,14 @@ this.jsviews || this.jQuery && jQuery.views || (function(global, undefined) {
}
(JsViewsError.prototype = new Error()).constructor = JsViewsError;
+
//========================== Top-level functions ==========================
//===================
// jsviews.delimiters
//===================
- function $viewsDelimiters(openChars, closeChars) {
+ function $viewsDelimiters(openChars, closeChars, defer) {
// Set the tag opening and closing delimiters. Default is "{{" and "}}"
// openChar, closeChars: opening and closing strings, each with two characters
@@ -88,11 +92,12 @@ this.jsviews || this.jQuery && jQuery.views || (function(global, undefined) {
delimOpenChar1 = openChars ? "\\" + openChars.charAt(1) : delimOpenChar1;
delimCloseChar0 = closeChars ? "\\" + closeChars.charAt(0) : delimCloseChar0;
delimCloseChar1 = closeChars ? "\\" + closeChars.charAt(0) : delimCloseChar1;
+ defer = defer ? "\\" + defer : deferChar;
// Build regex with new delimiters
$views.rTag = rTag // make rTag available to JsViews (or other components) for parsing binding expressions
// tag (followed by / space or }) or cvtr+colon or html or code
- = "(?:(?:(\\w+(?=[\\/\\s" + delimCloseChar0 + "]))|(?:(\\w+)?(:)|(>)|(\\*)))"
+ = "(\\w*" + defer + ")?(?:(?:(\\w+(?=[\\/\\s" + delimCloseChar0 + "]))|(?:(\\w+)?(:)|(>)|(\\*)))"
// params
+ "\\s*((?:[^" + delimCloseChar0 + "]|" + delimCloseChar0 + "(?!" + delimCloseChar1 + "))*?)";
@@ -105,7 +110,7 @@ this.jsviews || this.jQuery && jQuery.views || (function(global, undefined) {
rTmplString = new RegExp("<.*>|([^\\\\]|^)[{}]|" + delimOpenChar0 + delimOpenChar1 + ".*" + delimCloseChar0 + delimCloseChar1);
// rTmplString looks for html tags or { or } char not preceeded by \\, or JsRender tags {{xxx}}. Each of these strings are considered NOT to be jQuery selectors
}
- return [delimOpenChar0, delimOpenChar1, delimCloseChar0, delimCloseChar1];
+ return [delimOpenChar0, delimOpenChar1, delimCloseChar0, delimCloseChar1, deferChar];
}
//=================
@@ -118,13 +123,15 @@ this.jsviews || this.jQuery && jQuery.views || (function(global, undefined) {
tmplHelpers = view.tmpl.helpers || {};
helper = (
- view.ctx[helper] !== undefined
- ? view.ctx
- : tmplHelpers[helper] !== undefined
- ? tmplHelpers
- : $viewsHelpers[helper] !== undefined
- ? $viewsHelpers
- : {}
+ view.dynCtx && view.dynCtx[helper] !== undefined
+ ? view.dynCtx
+ : view.ctx[helper] !== undefined
+ ? view.ctx
+ : tmplHelpers[helper] !== undefined
+ ? tmplHelpers
+ : $viewsHelpers[helper] !== undefined
+ ? $viewsHelpers
+ : {}
)[helper];
return typeof helper !== "function" ? helper : function() {
return helper.apply(view, arguments);
@@ -135,52 +142,69 @@ this.jsviews || this.jQuery && jQuery.views || (function(global, undefined) {
// jsviews._convert
//=================
- function convert(converter, view, tmpl, text) {
- var tmplConverter = tmpl.converters;
+ function convert(converter, view, self, text) {
+ // self is template object or link object
+ var linkContext = !self.markup && self || undefined,
+ tmplConverter = view.tmpl.converters;
tmplConverter = tmplConverter && tmplConverter[converter] || $viewsConverters[converter];
- return tmplConverter ? tmplConverter.call(view, text) : (error("Unknown converter: {{"+ converter + ":"), text);
+ return tmplConverter ? tmplConverter.call(view, text, linkContext) : (error("Unknown converter: {{"+ converter + ":"), text);
}
//=================
// jsviews._tag
//=================
- function renderTag(tag, parentView, parentTmpl, converter, content, tagInstance) {
+ function renderTag(tag, parentView, self, content, tagInstance) {
// Called from within compiled template function, to render a nested tag
// Returns the rendered tag
var ret,
+ linkCtx = !self.markup && self, // self is either a template object (if rendering a tag) or a linkCtx object (if linking using a link tag)
+ parentTmpl = linkCtx ? linkCtx.view.tmpl : self,
tmplTags = parentTmpl.tags,
nestedTemplates = parentTmpl.templates,
props = tagInstance.props = tagInstance.props || {},
tmpl = props.tmpl,
- args = arguments.length > 6 ? slice.call(arguments, 6) : [],
- tagFn = tmplTags && tmplTags[tag] || $viewsTags[tag];
+ args = arguments.length > 5 ? slice.call(arguments, 5) : [],
+ tagObject = tmplTags && tmplTags[tag] || $viewsTags[tag];
- if (!tagFn) {
+ if (!tagObject) {
error("Unknown tag: {{"+ tag + "}}");
return "";
}
- if (tmpl) {
- // We don't want to expose tmpl as a prop to view context in rendered content
- delete props.tmpl;
- }
-
// Set the tmpl property to the content of the block tag, unless set as an override property on the tag
content = content && parentTmpl.tmpls[content - 1];
- tmpl = tmpl || content || undefined;
-
- tagInstance.tmpl =
+ tmpl = tmpl || content || tagObject.template || undefined;
+ tagInstance.view = parentView;
+ tmpl = tagInstance.tmpl =
"" + tmpl === tmpl // if a string
? nestedTemplates && nestedTemplates[tmpl] || $templates[tmpl] || $templates(tmpl)
: tmpl;
- tagInstance.isTag = TRUE;
- tagInstance.converter = converter;
- tagInstance.view = parentView;
- tagInstance.renderContent = renderContent;
+ tagInstance.attr =
+ // Setting attr on tagInstance so renderContent knows whether to include template annotations.
+ self.attr =
+ // Setting attr on self.fn to ensure outputting to the correct target attribute.
+ self.attr || tagObject.attr;
- ret = tagFn.apply(tagInstance, args);
- return ret || (ret == undefined ? "" : ret.toString()); // (If ret is the value 0 or false, will render to string)
+ tagInstance.tagName = tag;
+ tagInstance.renderContent = renderContent;
+ if (linkCtx) {
+ linkCtx.tagCtx = {
+ args: args,
+ props: props,
+ path: tagInstance.path,
+ tag: tagObject
+ };
+ }
+ // If render function is declared, call it. If the return result is undefined, return "", or, if a template (or content) is provide, return the rendered template (using the first parameter as data);
+ if (tagObject.render) {
+ ret = tagObject.render.apply(tagInstance, args);
+ }
+ return ret || (ret == undefined
+ ? (tmpl
+ ? tagInstance.renderContent(args[0], undefined, parentView)
+ : "")
+ : ret.toString()); // (If ret is the value 0 or false, will render to string)
}
//=================
@@ -191,18 +215,22 @@ this.jsviews || this.jQuery && jQuery.views || (function(global, undefined) {
// Constructor for view object in view hierarchy. (Augmented by JsViews if JsViews is loaded)
var views,
self = {
+ data: data,
tmpl: template,
- path: path,
+ views: isArray ? [] : {},
parent: parentView,
- data: data,
ctx: context,
// If the data is an array, this is an 'Array View' with a views array for each child 'Instance View'
// If the data is not an array, this is an 'Instance View' with a views 'map' object for any child nested views
- views: isArray ? [] : {},
// _useKey is non zero if is not an 'Array View' (owning a data array). Uuse this as next key for adding to child views map
+ path: path,
_useKey: isArray ? 0 : 1,
+ _onRender: onRender,
_hlp: getHelper,
- _onRender: onRender
+ renderLink: function(index) {
+ var linkTmpl = this.tmpl.tmpls[index];
+ return linkTmpl.render(data, context, this);
+ }
};
if (parentView) {
@@ -240,16 +268,22 @@ this.jsviews || this.jQuery && jQuery.views || (function(global, undefined) {
}
return self;
}
- if (!name || item === undefined) {
- if (process) {
- item = process(undefined, item || name);
- }
+ if (item === undefined) {
+ item = name;
+ name = undefined;
+ }
+ if (onStore = $viewsSub.onBeforeStoreItem) {
+ // e.g. provide an external compiler or preprocess the item.
+ process = onStore(store, name, item, process) || process;
+ }
+ if (!name) {
+ item = process ? process(item) : item
} else if ("" + name === name) { // name must be a string
if (item === null) {
// If item is null, delete this entry
delete store[name];
- } else if (item = process ? process(name, item) : item) {
- store[name] = item;
+ } else {
+ store[name] = process ? (item = process(item, name)) : item;
}
}
if (onStore = $viewsSub.onStoreItem) {
@@ -259,6 +293,13 @@ this.jsviews || this.jQuery && jQuery.views || (function(global, undefined) {
return item;
}
+ function compileTag(item, name) {
+ item = typeof item === "function" ? { render: item } : item;
+ item.name = name;
+ item.is = "tag";
+ return item;
+ }
+
function $templates(name, tmpl) {
// Register templates
// Setter: Use $.templates( name, tmpl ) or $.templates({ name: tmpl, ... }) to add additional templates to the registered templates collection.
@@ -267,15 +308,15 @@ this.jsviews || this.jQuery && jQuery.views || (function(global, undefined) {
return addToStore(this, $templates, name, tmpl, compile);
}
- function $viewsTags(name, tagFn) {
+ function $viewsTags(name, tag) {
// Register template tags
- // Setter: Use $.view.tags( name, tagFn ) or $.view.tags({ name: tagFn, ... }) to add additional tags to the registered tags collection.
- // Getter: Use var tagFn = $.views.tags( name ) or $.views.tags[name] or $.views.tags.name to return the object for the registered tag.
+ // Setter: Use $.view.tags( name, tag ) or $.view.tags({ name: tag, ... }) to add additional tags to the registered tags collection.
+ // Getter: Use var tag = $.views.tags( name ) or $.views.tags[name] or $.views.tags.name to return the object for the registered tag.
// Remove: Use $.view.tags( name, null ) to remove a registered tag from $.view.tags.
- // When registering for {{foo a b c==d e=f}}, tag should be a function with the signature:
+ // When registering for {{foo a b c==d e=f}}, tag should corresponnd to a function with the signature:
// function(a,b). The 'this' pointer will be a hash with properties c and e.
- return addToStore(this, $viewsTags, name, tagFn);
+ return addToStore(this, $viewsTags, name, tag, compileTag);
}
function $viewsHelpers(name, helperFn) {
@@ -302,7 +343,7 @@ this.jsviews || this.jQuery && jQuery.views || (function(global, undefined) {
function renderContent(data, context, parentView, key, isLayout, path, onRender) {
// Render template against data as a tree of subviews (nested template), or as a string (top-level template).
- var i, l, dataItem, newView, itemResult, parentContext, tmpl, props, swapContent, mergedCtx,
+ var i, l, dataItem, newView, itemResult, parentContext, tmpl, props, swapContent, mergedCtx, dynCtx, hasContext,
self = this,
result = "";
@@ -310,10 +351,10 @@ this.jsviews || this.jQuery && jQuery.views || (function(global, undefined) {
swapContent = TRUE;
key = 0;
}
- if (self.isTag) {
+ if (self.tagName) {
// This is a call from renderTag
tmpl = self.tmpl;
- if (context || self.ctx) {// || hasProp) {
+ if (context || self.ctx) {
// We need to create an augmented context for the view(s) we are about to render
mergedCtx = {};
if (self.ctx) {
@@ -337,13 +378,17 @@ this.jsviews || this.jQuery && jQuery.views || (function(global, undefined) {
parentView = parentView || self.view;
path = path || self.path;
key = key || self.key;
+// onRender = self.attr === "html" && parentView && parentView._onRender;
+ onRender = parentView && parentView._onRender;
} else {
tmpl = self.jquery && (self[0] || error('Unknown template: "' + self.selector + '"')) // This is a call from $(selector).render
|| self;
+ onRender = onRender || parentView && parentView._onRender;
}
if (tmpl) {
if (parentView) {
parentContext = parentView.ctx;
+ dynCtx = parentView.dynCtx;
if (data === parentView) {
// Inherit the data from the parent view.
// This may be the contents of an {{if}} block
@@ -351,17 +396,24 @@ this.jsviews || this.jQuery && jQuery.views || (function(global, undefined) {
data = parentView.data;
isLayout = TRUE;
}
- onRender = onRender || parentView._onRender;
} else {
parentContext = $viewsHelpers;
}
// Set additional context on views created here, (as modified context inherited from the parent, and to be inherited by child views)
- // Note: If no jQuery, $extend does not support chained copies - so limit $extend() to two parameters
+ // Note: If no jQuery, $extend does not support chained copies - so limit extend() to two parameters
// TODO could make this a reusable helper for merging context.
- context = (context && context !== parentContext)
- ? $extend($extend({}, parentContext), context)
- : parentContext;
+ hasContext = (context && context !== parentContext);
+ if (dynCtx || hasContext) {
+ parentContext = $extend({}, parentContext);
+ if (hasContext) {
+ $extend(parentContext, context);
+ }
+ if (dynCtx) {
+ $extend(parentContext, dynCtx);
+ }
+ }
+ context = parentContext;
if (!tmpl.fn) {
tmpl = $templates[tmpl] || $templates(tmpl);
@@ -384,7 +436,7 @@ this.jsviews || this.jQuery && jQuery.views || (function(global, undefined) {
// Create a view for singleton data object.
newView = swapContent ? parentView : View(context, path, parentView, data, tmpl, key, onRender);
newView._onRender = onRender;
- result += tmpl.fn(data, newView, $views);
+ result += tmpl.fn(data, newView, $views, returnVal);
}
return onRender ? onRender(result, tmpl, props, newView.key, path) : result;
}
@@ -393,6 +445,10 @@ this.jsviews || this.jQuery && jQuery.views || (function(global, undefined) {
return "";
}
+ function returnVal(value) {
+ return value;
+ }
+
//===========================
// Build and compile template
//===========================
@@ -410,12 +466,12 @@ this.jsviews || this.jQuery && jQuery.views || (function(global, undefined) {
error("Syntax error\n" + message);
}
-
function tmplFn(markup, tmpl, bind) {
// Compile markup to AST (abtract syntax tree) then build the template function code from the AST nodes
// Used for compiling templates, and also by JsViews to build functions for data link expressions
var newNode,
+ //result,
allowCode = tmpl && tmpl.allowCode,
astTop = [],
loc = 0,
@@ -435,7 +491,7 @@ this.jsviews || this.jQuery && jQuery.views || (function(global, undefined) {
tagName && syntaxError('Unmatched or missing tag: "{{/' + tagName + '}}" in template:\n' + markup);
}
- function parseTag(all, tagName, converter, colon, html, code, params, slash, closeBlock, index) {
+ function parseTag(all, defer, tagName, converter, colon, html, code, params, slash, closeBlock, index) {
// tag converter colon html code params slash closeBlock
// /{{(?:(?:(\w+(?=[\/!\s\}!]))|(?:(\w+)?(:)|(?:(>)|(\*)))((?:[^\}]|}(?!}))*?)(\/)?|(?:\/(\w+)))}}/g;
// Build abstract syntax tree (AST): [ tagName, converter, params, content, hash, contentMarkup, link ]
@@ -464,9 +520,14 @@ this.jsviews || this.jQuery && jQuery.views || (function(global, undefined) {
current = stack.pop();
content = current[3];
block = TRUE;
+ } else if (defer) {
+ stack.push(current);
+ current = ["!", , , [], ,index];
+ content.push(current);
+ content = current[3];
}
params = (params
- ? parseParams(params, bind)
+ ? parseParams(params, bind, defer)
.replace(rBuildHash, function(all, isCtx, keyValue) {
if (isCtx) {
passedCtx += keyValue + ",";
@@ -483,18 +544,26 @@ this.jsviews || this.jQuery && jQuery.views || (function(global, undefined) {
converter || "",
params,
block && [],
- "{" + (hash ? ("props:{" + hash + "},") : "") + "path:'" + params + "'" + (passedCtx ? ",ctx:{" + passedCtx.slice(0, -1) + "}" : "") + "}"
+ "{" + (hash ? ("props:{" + hash + "},") : "") + "data: data" + (passedCtx ? ",ctx:{" + passedCtx.slice(0, -1) + "}" : "") + "}"
];
content.push(newNode);
if (block) {
stack.push(current);
current = newNode;
current[5] = loc; // Store current location of open tag, to be able to add contentMarkup when we reach closing tag
+ } else if (defer) {
+ current[5] = markup.substring(current[5], loc); // contentMarkup for block tag
+ current = stack.pop();
}
} else if (closeBlock) {
current0 = current[0];
- blockTagCheck(closeBlock !== current0 && !(closeBlock === "if" && current0 === "else") && current0);
+ blockTagCheck(closeBlock !== current0 && !(closeBlock === "if" && current0 === "else") && current0);
current[5] = markup.substring(current[5], index); // contentMarkup for block tag
+ if (current0 === "!") {
+ // defer
+ current[5] = markup.substring(current[5], loc); // contentMarkup for block tag
+ current = stack.pop();
+ }
current = stack.pop();
}
blockTagCheck(!current && closeBlock);
@@ -502,26 +571,32 @@ this.jsviews || this.jQuery && jQuery.views || (function(global, undefined) {
}
//==== /end of nested functions ====
- markup = markup.replace(rEscapeQuotes, "\\$1");
- blockTagCheck(stack[0] && stack[0][3].pop()[0]);
+// result = compiledTmplsCache[markup]; // Only cache if template is not named and markup length < ... Consider standard optimization for data-link="a.b.c"
+// if (!result) {
+// result = markup;
+ markup = markup.replace(rEscapeQuotes, "\\$1");
+ blockTagCheck(stack[0] && stack[0][3].pop()[0]);
- // Build the AST (abstract syntax tree) under astTop
- markup.replace(rTag, parseTag);
+ // Build the AST (abstract syntax tree) under astTop
+ markup.replace(rTag, parseTag);
- pushPreceedingContent(markup.length);
+ pushPreceedingContent(markup.length);
+
+// result = compiledTmplsCache[result] = buildCode(astTop, tmpl);
+// }
+// return result;
return buildCode(astTop, tmpl);
}
function buildCode(ast, tmpl) {
// Build the template function code from the AST nodes, and set as property on the passed in template object
// Used for compiling templates, and also by JsViews to build functions for data link expressions
- var node, i, l, code, hasTag, hasEncoder, getsValue, hasConverter, hasViewPath, tag, converter, params, hash, nestedTmpl, allowCode, content,
+ var node, i, l, code, hasTag, hasEncoder, getsValue, hasConverter, hasViewPath, tag, converter, params, hash, nestedTmpl, allowCode, content, attr, quot,
tmplOptions = tmpl ? {
allowCode: allowCode = tmpl.allowCode,
debug: tmpl.debug
} : {},
- nested = tmpl && tmpl.tmpls,
- nestedIndex = 0;
+ nested = tmpl && tmpl.tmpls;
// Use the AST (ast) to build the template function
l = ast.length;
@@ -542,26 +617,39 @@ this.jsviews || this.jQuery && jQuery.views || (function(global, undefined) {
content = node[3];
hash = node[4];
markup = node[5];
-
- if (content) {
+ if (tag.slice(-1) === "!") {
// Create template object for nested template
- nestedTmpl = TmplObject(markup, tmplOptions, tmpl, nestedIndex++);
+ nestedTmpl = TmplObject(markup, tmplOptions, tmpl, nested.length);
// Compile to AST and then to compiled function
- buildCode(node[3], nestedTmpl);
+ buildCode(content, nestedTmpl);
+ if (attr = /\s+[\w-]*\s*\=\s*\\['"]$/.exec(ast[i-1])) {
+ error("'{{!' in attribute:\n..." + ast[i-1] + "{{!...\nUse data-link");
+ }
+ code += 'view.renderLink(' + nested.length + ')+';
+ nestedTmpl.bound = TRUE;
+ nestedTmpl.fn.attr = attr || "leaf";
nested.push(nestedTmpl);
+ } else {
+ if (content) {
+ // Create template object for nested template
+ nestedTmpl = TmplObject(markup, tmplOptions, tmpl, nested.length);
+ // Compile to AST and then to compiled function
+ buildCode(content, nestedTmpl);
+ nested.push(nestedTmpl);
+ }
+ hasViewPath = hasViewPath || hash.indexOf("view") > -1;
+ code += (tag === ":"
+ ? (converter === "html"
+ ? (hasEncoder = TRUE, "h(" + params)
+ : converter
+ ? (hasConverter = TRUE, 'c("' + converter + '",view,this,' + params)
+ : (getsValue = TRUE, "((v=" + params + ')!=u?v:""')
+ )
+ : (hasTag = TRUE, 't("' + tag + '",view,this,'
+ + (content ? nested.length : '""') // For block tags, pass in the key (nested.length) to the nested content template
+ + "," + hash + (params ? "," : "") + params))
+ + ")+";
}
- hasViewPath = hasViewPath || hash.indexOf("view") > -1;
- code += (tag === ":"
- ? (converter === "html"
- ? (hasEncoder = TRUE, "e(" + params)
- : converter
- ? (hasConverter = TRUE, 'c("' + converter + '",view,this,' + params)
- : (getsValue = TRUE, "((v=" + params + ')!=u?v:""')
- )
- : (hasTag = TRUE, 't("' + tag + '",view,this,"' + (converter || "") + '",'
- + (content ? nested.length : '""') // For block tags, pass in the key (nested.length) to the nested content template
- + "," + hash + (params ? "," : "") + params))
- + ")+";
}
}
}
@@ -569,7 +657,7 @@ this.jsviews || this.jQuery && jQuery.views || (function(global, undefined) {
+ (getsValue ? "v," : "")
+ (hasTag ? "t=j._tag," : "")
+ (hasConverter ? "c=j._convert," : "")
- + (hasEncoder ? "e=j.converters.html," : "")
+ + (hasEncoder ? "h=j.converters.html," : "")
+ "ret; try{\n\n"
+ (tmplOptions.debug ? "debugger;" : "")
+ (allowCode ? 'ret=' : 'return ')
@@ -590,8 +678,7 @@ this.jsviews || this.jQuery && jQuery.views || (function(global, undefined) {
return code;
}
-
- function parseParams(params, bind) {
+ function parseParams(params, bind, defer) {
var named,
fnCall = {},
parenDepth = 0,
@@ -607,7 +694,7 @@ this.jsviews || this.jQuery && jQuery.views || (function(global, undefined) {
path = path || path2;
prn = prn || prn2 || "";
operator = operator || "";
- var bindParam = bind && prn !== "(";
+ var bindParam = (bind || defer) && prn !== "(";
function parsePath(all, object, helper, view, viewProperty, pathTokens, leafToken) {
// rPath = /^(?:null|true|false|\d[\d.]*|([\w$]+|~([\w$]+)|#(view|([\w$]+))?)([\w$.]*?)(?:[.[]([\w$]+)\]?)?|(['"]).*\8)$/g,
@@ -628,7 +715,7 @@ this.jsviews || this.jQuery && jQuery.views || (function(global, undefined) {
) + (pathTokens || "")
: (leafToken = helper ? "" : view ? viewProperty || "" : object, ""));
- leaf = (leafToken ? "." + leafToken : "")
+ leaf = (leafToken ? "." + leafToken : "");
if (!bindParam) {
ret = ret + leaf;
}
@@ -696,10 +783,24 @@ this.jsviews || this.jQuery && jQuery.views || (function(global, undefined) {
return params;
}
- function compile(name, tmpl, parent, options) {
+ function compileNested(items, process, options) {
+ var key, nestedItem;
+ if (items) {
+ for (key in items) {
+ // compile nested template declarations
+ nestedItem = items[key];
+ if (!nestedItem.is) {
+ // Not yet compiled
+ items[key] = process(nestedItem, key, options);
+ }
+ }
+ }
+ }
+
+ function compile(tmpl, name, parent, options) {
// tmpl is either a template object, a selector for a template script block, the name of a compiled template, or a template object
// options is the set of template properties, c
- var tmplOrMarkup, elem, key, nested, nestedItem;
+ var tmplOrMarkup, elem;
//==== nested functions ====
function tmplOrMarkupFromStr(value) {
@@ -718,18 +819,19 @@ this.jsviews || this.jQuery && jQuery.views || (function(global, undefined) {
} catch (e) { }
if (elem) {
- if (!elem.type) {
- // If elem is not a script element, return undefined
- return;
- }
- // It is a script element
- // Create a name for data linking if none provided
+ // Generally this is a script element.
+ // However we allow it to be any element, so you can for example take the content of a div,
+ // use it as a template, and replace it by the same content rendered against data.
+ // e.g. for linking the content of a div to a container, and using the initial content as template:
+ // $.link("#content", model, {tmpl: "#content"});
+
+ // Create a name for compiled template if none provided
value = $templates[elem.getAttribute(tmplAttr)];
if (!value) {
// Not already compiled and cached, so compile and cache the name
name = name || "_" + autoTmplName++;
elem.setAttribute(tmplAttr, name);
- value = compile(name, elem.innerHTML, parent, options); // Use tmpl as options
+ value = compile(elem.innerHTML, name, parent, options); // Use tmpl as options
$templates[name] = value;
}
}
@@ -745,7 +847,7 @@ this.jsviews || this.jQuery && jQuery.views || (function(global, undefined) {
// If tmpl is a template object, use it for options
options = options || (tmpl.markup ? tmpl : {});
options.name = name;
- nested = options.templates;
+ options.is = "tmpl";
// If tmpl is not a markup string or a selector string, then it must be a template object
// In that case, get it from the markup property of the object
@@ -777,13 +879,8 @@ this.jsviews || this.jQuery && jQuery.views || (function(global, undefined) {
// Compile to AST and then to compiled function
tmplFn(tmplOrMarkup, tmpl);
}
- for (key in nested) {
- // compile nested template declarations
- nestedItem = nested[key];
- if (nestedItem.name !== key) {
- nested[key] = compile(key, nestedItem, tmpl);
- }
- }
+ compileNested(options.templates, compile, tmpl);
+ compileNested(options.tags, compileTag);
return tmpl;
}
}
@@ -936,4 +1033,4 @@ this.jsviews || this.jQuery && jQuery.views || (function(global, undefined) {
//========================== Define default delimiters ==========================
$viewsDelimiters();
-})(this);
+})(this, this.jQuery);
View
14 test/unit/jsrender-tests-no-jquery.js
@@ -216,7 +216,8 @@ test("templates", function() {
equal( tmpl.render( person ), "A_Jo_B", 'Compile from string: var tmpl = jsviews.templates( tmplString );' );
var fnToString = tmpl.fn.toString();
- equal( jsviews.templates( "", tmplString ).fn.toString() === fnToString && jsviews.templates( null, tmplString ).fn.toString() === fnToString && jsviews.templates( undefined, tmplString ).fn.toString() === fnToString, true, 'if name is "", null, or undefined, then jsviews.templates( name, tmplString ) = jsviews.templates( tmplString );' );
+ equal( jsviews.templates( "", tmplString ).fn.toString() === fnToString && jsviews.templates( null, tmplString ).fn.toString() === fnToString && jsviews.templates( undefined, tmplString ).fn.toString() === fnToString, true,
+ 'if name is "", null, or undefined, then jsviews.templates( name, tmplString ) = jsviews.templates( tmplString );' );
jsviews.templates( "myTmpl", tmplString );
equal( jsviews.render.myTmpl( person ), "A_Jo_B", 'Compile and register named template: jsviews.templates( "myTmpl", tmplString );' );
@@ -320,7 +321,7 @@ test("converters", function() {
equal(locFn === loc && jsviews.converters.loc === loc && jsviews.converters.loc2 === loc, true, 'locFunction === jsviews.converters.loc === jsviews.converters.loc2' );
jsviews.converters({ loc2: null});
- equal(jsviews.converters("loc2"), undefined, 'jsviews.converters({ loc2: null }) to remove registered converter' );
+ equal(jsviews.converters.loc2, undefined, 'jsviews.converters({ loc2: null }) to remove registered converter' );
});
test("tags", function() {
@@ -331,11 +332,11 @@ test("tags", function() {
equal(jsviews.templates( "{{sort reverse=false people reverse=true towns}}{{:name}}{{/sort}}" ).render({ people: people, towns:towns }), "DelhiParisSeattleBillJo", "Duplicate named parameters - last wins: {{sort reverse=false people reverse=true towns}}" );
- var sortFn = jsviews.tags("sort2", sort);
- equal(sortFn === sort && jsviews.tags.sort === sort && jsviews.tags.sort2 === sort, true, 'sortFunction === jsviews.tags.sort === jsviews.tags.sort2' );
+ var sort2 = jsviews.tags("sort2", sort);
+ equal(sort2.render === sort && jsviews.tags.sort.render === sort && jsviews.tags.sort2.render === sort, true, 'sortFunction === jsviews.tags.sort.render === jsviews.tags.sort2.render' );
jsviews.tags("sort2", null);
- equal(jsviews.tags("locsort2"), undefined, 'jsviews.tags( "sort2", null ) to remove registered tag' );
+ equal(jsviews.tags.sort2, undefined, 'jsviews.tags( "sort2", null ) to remove registered tag' );
});
test("helpers", function() {
@@ -360,7 +361,7 @@ test("helpers", function() {
equal( toUpperCaseFn === toUpperCase && jsviews.helpers.toUpperCase === toUpperCase && jsviews.helpers.toUpperCase2 === toUpperCase, true, 'sortFunction === jsviews.helpers.toUpperCase === jsviews.helpers("toUpperCase")' );
jsviews.helpers("toUpperCase2", null);
- equal(jsviews.helpers("toUpperCase2"), undefined, 'jsviews.helpers( "toUpperCase2", null ) to remove registered helper' );
+ equal(jsviews.helpers.toUpperCase2, undefined, 'jsviews.helpers( "toUpperCase2", null ) to remove registered helper' );
});
test("template encapsulation", function() {
@@ -455,6 +456,7 @@ test("template encapsulation", function() {
return "contextualNot2" + !value;
}
};
+ jsviews.render.tmplWithNestedResources({ a: "aVal" })
equal( jsviews.render.tmplWithNestedResources({ a: "aVal" }), "aval aValbtrue% (double:aVal&aVal) (override outer double:AVAL|AVAL)", 'Access nested resources from template' );
equal( jsviews.render.useLower({ a: "aVal" }), "Error: Unknown tag: {{lower}}. ", 'Cannot access nested resources from a different template' );
equal( jsviews.render.tmplWithNestedResources({ a: "aVal" }, context), "aval aValbcontextualNot2true% (double:aVal&aVal) (override outer double:contextualUpperAVAL|contextualUpperAVAL)", 'Resources passed in with context override nested resources' );
View
2  test/unit/jsrender-tests-with-jquery.js
@@ -125,7 +125,7 @@ test("tags", function() {
equal( $.templates( "{{sort people reverse=true}}{{:name}}{{/sort}}" ).render({ people: people }), "BillJo", "$.views.tags({ sort: sortFunction })" );
$.views.tags( "sort2", sort );
- equal( $.views.tags.sort === sort, true, 'sortFunction === $.views.tags.sort' );
+ equal( $.views.tags.sort.render === sort, true, 'sortFunction === $.views.tags.sort' );
$.views.tags( "sort2", null );
equal( $.views.tags.sort2, undefined, 'Remove a registered tag: $.views.tag({ sor: null })' );
Please sign in to comment.
Something went wrong with that request. Please try again.