VYPR
Medium severityGHSA Advisory· Published Sep 1, 2020· Updated Sep 7, 2023

Template Injection in jsrender

CVE-2016-3942

Description

Affected versions of jsrender are susceptible to a remote code execution vulnerability when used with server delivered client-side tempates which dynamically embed user input.

Proof of

Concept

//POC-REQUEST
{{for ~x!=1?(constructor.constructor("return arguments.callee.caller")()):~y(10)}}
{{:#data}}
{{/for}}
//POC-RESPONSE
function anonymous(data,view,j,u) { // template var v,t=j._tag,ret="" +t("for",view,this,[ {view:view,tmpl:1, params:{args:['~x!=1?(constructor.constructor(\"return arguments.callee.caller\")()):~y(10)']}, args:[view.hlp("x")!=1?(data.constructor.constructor("return arguments.callee.caller")()):view.hlp("y")(10)], props:{}}]); return ret; } 

Recommendation

Update to version 0.9.74 or later.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
jsrendernpm
< 0.9.740.9.74

Affected products

1

Patches

1
f984e139deb0

Commit 74 (v0.9.74 - Beta)

https://github.com/BorisMoore/jsrenderBorisMooreMar 20, 2016via ghsa
9 files changed · +418 299
  • jsrender.js+142 105 modified
    @@ -1,4 +1,4 @@
    -/*! JsRender v0.9.73 (Beta): http://jsviews.com/#jsrender */
    +/*! JsRender v0.9.74 (Beta): http://jsviews.com/#jsrender */
     /*! **VERSION FOR WEB** (For NODE.JS see http://jsviews.com/download/jsrender-node.js) */
     /*
      * Best-of-breed templating in browser or on Node.js.
    @@ -11,46 +11,44 @@
     
     //jshint -W018, -W041
     
    -(function(factory) {
    +(function(factory, global) {
     	// global var is the this object, which is window when running in the usual browser environment
    -	var global = (0, eval)('this'), // jshint ignore:line
    -		$ = global.jQuery;
    +	var $ = global.jQuery;
     
     	if (typeof define === "function" && define.amd) { // AMD script loader, e.g. RequireJS
    -		define(factory);
    +		define(function() {
    +			return factory(global);
    +		});
     	} else if (typeof exports === "object") { // CommonJS e.g. Browserify
     		module.exports = $
    -			? factory($)
    +			? factory(global, $)
     			: function($) { // If no global jQuery, take optional jQuery passed as parameter: require('jsrender')(jQuery)
     				if ($ && !$.fn) {
     					throw "Provide jQuery or null";
     				}
    -				return factory($);
    +				return factory(global, $);
     			};
     	} else { // Browser using plain <script> tag
    -		factory(false);
    +		factory(global, false);
     	}
     } (
     
     // factory (for jsrender.js)
    -function($) {
    +function(global, $) {
     "use strict";
     
     //========================== Top-level vars ==========================
     
     // global var is the this object, which is window when running in the usual browser environment
    -var global = (0, eval)('this'), // jshint ignore:line
    -	setGlobals = $ === false; // Only set globals if script block in browser (not AMD and not CommonJS)
    +var setGlobals = $ === false; // Only set globals if script block in browser (not AMD and not CommonJS)
     
     $ = $ && $.fn ? $ : global.jQuery; // $ is jQuery passed in by CommonJS loader (Browserify), or global jQuery.
     
    -var versionNumber = "v0.9.73",
    +var versionNumber = "v0.9.74",
     	jsvStoreName, rTag, rTmplString, topView, $views,
     
     //TODO	tmplFnsCache = {},
    -	$isFunction, $isArray, $templates, $converters, $helpers, $tags, $sub, $viewsSettings,
    -
    -	delimOpenChar0 = "{", delimOpenChar1 = "{", delimCloseChar0 = "}", delimCloseChar1 = "}", linkChar = "^",
    +	$isFunction, $isArray, $templates, $converters, $helpers, $tags, $sub, $subSettings, $subSettingsAdvanced, $viewsSettings, delimOpenChar0, delimOpenChar1, delimCloseChar0, delimCloseChar1, linkChar, setting, baseOnError,
     
     	rPath = /^(!*?)(?:null|true|false|\d[\d.]*|([\w$]+|\.|~([\w$]+)|#(view|([\w$]+))?)([\w$.^]*?)(?:[.[^]([\w$]+)\]?)?)$/g,
     	//        not                               object     helper    view  viewProperty pathTokens      leafToken
    @@ -104,13 +102,6 @@ var versionNumber = "v0.9.73",
     // views object ($.views if jQuery is loaded, jsrender.views if no jQuery, e.g. in Node.js)
     	$views = {
     		jsviews: versionNumber,
    -		settings: function(settings) {
    -			$extend($viewsSettings, settings);
    -			dbgMode($viewsSettings._dbgMode);
    -			if ($viewsSettings.jsv) {
    -				$viewsSettings.jsv();
    -			}
    -		},
     		sub: {
     			// subscription, e.g. JsViews integration
     			View: View,
    @@ -121,13 +112,32 @@ var versionNumber = "v0.9.73",
     			extendCtx: extendCtx,
     			syntaxErr: syntaxError,
     			onStore: {},
    +			addSetting: addSetting,
    +			settings: {
    +				allowCode: false
    +			},
    +			advSet: noop, // Update advanced settings
     			_ths: tagHandlersFromProps,
    -			_tg: function() {} // Constructor for tagDef
    +			_tg: function() {}, // Constructor for tagDef
    +			_cnvt: convertVal,
    +			_tag: renderTag,
    +			_er: error,
    +			_err: onRenderError,
    +			_html: htmlEncode
    +		},
    +		settings: {
    +			delimiters: $viewsDelimiters,
    +			advanced: function(value) {
    +				return value
    +					? (
    +							$extend($subSettingsAdvanced, value),
    +							$sub.advSet(),
    +							$viewsSettings
    +						)
    +						: $subSettingsAdvanced;
    +				}
     		},
    -		map: dataMap, // If jsObservable loaded first, use that definition of dataMap
    -		_cnvt: convertVal,
    -		_tag: renderTag,
    -		_err: error
    +		map: dataMap // If jsObservable loaded first, use that definition of dataMap
     	};
     
     function getDerivedMethod(baseMethod, method) {
    @@ -179,20 +189,16 @@ function noop() {
     }
     
     function dbgBreak(val) {
    -	// Usage examples: {{dbg:...}}, {{:~dbg(...)}}, {{for ... onAfterLink=~dbg}}, {{dbg .../}} etc.
    -	// To break here, stop on caught exceptions.
    +	// Usage examples: {{dbg:...}}, {{:~dbg(...)}}, {{dbg .../}}, {^{for ... onAfterLink=~dbg}} etc.
     	try {
     		debugger;
    -		throw "dbg breakpoint";
    +		console.log("JsRender dbg breakpoint: " + val);
    +		throw "dbg breakpoint"; // To break here, stop on caught exceptions.
     	}
     	catch (e) {}
     	return this.base ? this.baseApply(arguments) : val;
     }
     
    -function dbgMode(debugMode) {
    -	$viewsSettings._dbgMode = debugMode !== false; // Pass in false to unset. Otherwise sets to true.
    -}
    -
     function JsViewsError(message) {
     	// Error exception type for JsViews/JsRender
     	// Override of $.views.sub.Error is possible
    @@ -215,36 +221,39 @@ function $extend(target, source) {
     //===================
     // views.delimiters
     //===================
    +
     function $viewsDelimiters(openChars, closeChars, link) {
     	// Set the tag opening and closing delimiters and 'link' character. Default is "{{", "}}" and "^"
     	// openChars, closeChars: opening and closing strings, each with two characters
    -
    -	if (this !== 0 || openChars) {
    -		delimOpenChar0 = openChars ? openChars.charAt(0) : delimOpenChar0; // Escape the characters - since they could be regex special characters
    -		delimOpenChar1 = openChars ? openChars.charAt(1) : delimOpenChar1;
    -		delimCloseChar0 = closeChars ? closeChars.charAt(0) : delimCloseChar0;
    -		delimCloseChar1 = closeChars ? closeChars.charAt(1) : delimCloseChar1;
    -		linkChar = link || linkChar;
    -		openChars = "\\" + delimOpenChar0 + "(\\" + linkChar + ")?\\" + delimOpenChar1; // Default is "{^{"
    -		closeChars = "\\" + delimCloseChar0 + "\\" + delimCloseChar1;                   // Default is "}}"
    -		// Build regex with new delimiters
    -		//          [tag    (followed by / space or })  or cvtr+colon or html or code] followed by space+params then convertBack?
    -		rTag = "(?:(\\w+(?=[\\/\\s\\" + delimCloseChar0 + "]))|(\\w+)?(:)|(>)|(\\*))\\s*((?:[^\\"
    -			+ delimCloseChar0 + "]|\\" + delimCloseChar0 + "(?!\\" + delimCloseChar1 + "))*?)";
    -
    -		// make rTag available to JsViews (or other components) for parsing binding expressions
    -		$sub.rTag = "(?:" + rTag + ")";
    -		//                        { ^? {   tag+params slash?  or closingTag                                                   or comment
    -		rTag = new RegExp("(?:" + openChars + rTag + "(\\/)?|\\" + delimOpenChar0 + "(\\" + linkChar + ")?\\" + delimOpenChar1 + "(?:(?:\\/(\\w+))\\s*|!--[\\s\\S]*?--))" + closeChars, "g");
    -
    -		// Default:  bind     tagName         cvt   cln html code    params            slash   bind2         closeBlk  comment
    -		//      /(?:{(\^)?{(?:(\w+(?=[\/\s}]))|(\w+)?(:)|(>)|(\*))\s*((?:[^}]|}(?!}))*?)(\/)?|{(\^)?{(?:(?:\/(\w+))\s*|!--[\s\S]*?--))}}
    -
    -		rTmplString = new RegExp("<.*>|([^\\\\]|^)[{}]|" + openChars + ".*" + closeChars);
    -		// rTmplString looks for html tags or { or } char not preceded by \\, or JsRender tags {{xxx}}. Each of these strings are considered
    -		// NOT to be jQuery selectors
    -	}
    -	return [delimOpenChar0, delimOpenChar1, delimCloseChar0, delimCloseChar1, linkChar];
    +	if (!openChars) {
    +		return $subSettings.delimiters;
    +	}
    +
    +	$subSettings.delimiters = [openChars, closeChars, linkChar = link ? link.charAt(0) : linkChar];
    +
    +	delimOpenChar0 = openChars.charAt(0); // Escape the characters - since they could be regex special characters
    +	delimOpenChar1 = openChars.charAt(1);
    +	delimCloseChar0 = closeChars.charAt(0);
    +	delimCloseChar1 = closeChars.charAt(1);
    +	openChars = "\\" + delimOpenChar0 + "(\\" + linkChar + ")?\\" + delimOpenChar1; // Default is "{^{"
    +	closeChars = "\\" + delimCloseChar0 + "\\" + delimCloseChar1;                   // Default is "}}"
    +	// Build regex with new delimiters
    +	//          [tag    (followed by / space or })  or cvtr+colon or html or code] followed by space+params then convertBack?
    +	rTag = "(?:(\\w+(?=[\\/\\s\\" + delimCloseChar0 + "]))|(\\w+)?(:)|(>)|(\\*))\\s*((?:[^\\"
    +		+ delimCloseChar0 + "]|\\" + delimCloseChar0 + "(?!\\" + delimCloseChar1 + "))*?)";
    +
    +	// make rTag available to JsViews (or other components) for parsing binding expressions
    +	$sub.rTag = "(?:" + rTag + ")";
    +	//                        { ^? {   tag+params slash?  or closingTag                                                   or comment
    +	rTag = new RegExp("(?:" + openChars + rTag + "(\\/)?|\\" + delimOpenChar0 + "(\\" + linkChar + ")?\\" + delimOpenChar1 + "(?:(?:\\/(\\w+))\\s*|!--[\\s\\S]*?--))" + closeChars, "g");
    +
    +	// Default:  bind     tagName         cvt   cln html code    params            slash   bind2         closeBlk  comment
    +	//      /(?:{(\^)?{(?:(\w+(?=[\/\s}]))|(\w+)?(:)|(>)|(\*))\s*((?:[^}]|}(?!}))*?)(\/)?|{(\^)?{(?:(?:\/(\w+))\s*|!--[\s\S]*?--))}}
    +
    +	rTmplString = new RegExp("<.*>|([^\\\\]|^)[{}]|" + openChars + ".*" + closeChars);
    +	// rTmplString looks for html tags or { or } char not preceded by \\, or JsRender tags {{xxx}}. Each of these strings are considered
    +	// NOT to be jQuery selectors
    +	return $viewsSettings;
     }
     
     //=========
    @@ -366,7 +375,7 @@ function convertVal(converter, view, tagCtx, onError) {
     	if (onError !== undefined) {
     		tagCtx = onError = {props: {}, args: [onError]};
     	} else if (boundTag) {
    -		tagCtx = boundTag(view.data, view, $views);
    +		tagCtx = boundTag(view.data, view, $sub);
     	}
     
     	value = tagCtx.args[0];
    @@ -474,7 +483,7 @@ function renderTag(tagName, parentView, tmpl, tagCtxs, isUpdate, onError) {
     		ret += onError;
     		tagCtxs = onError = [{props: {}, args: []}];
     	} else if (boundTag) {
    -		tagCtxs = boundTag(parentView.data, parentView, $views);
    +		tagCtxs = boundTag(parentView.data, parentView, $sub);
     	}
     
     	l = tagCtxs.length;
    @@ -735,7 +744,7 @@ function compileTag(name, tagDef, parentTmpl) {
     		compiledDef.template = "" + tmpl === tmpl ? ($templates[tmpl] || $templates(tmpl)) : tmpl;
     	}
     	if (compiledDef.init !== false) {
    -		// Set init: false on tagDef if you want to provide just a render method, or render and template, but no constuctor or prototype.
    +		// Set init: false on tagDef if you want to provide just a render method, or render and template, but no constructor or prototype.
     		// so equivalent to setting tag to render function, except you can also provide a template.
     		(Tag.prototype = compiledDef).constructor = compiledDef._ctr = Tag;
     	}
    @@ -898,7 +907,7 @@ function dataMap(mapDef) {
     function tmplObject(markup, options) {
     	// Template object constructor
     	var htmlTag,
    -		wrapMap = $viewsSettings.wrapMap || {}, // Only used in JsViews. Otherwise empty: {}
    +		wrapMap = $subSettingsAdvanced._wm || {}, // Only used in JsViews. Otherwise empty: {}
     		tmpl = $extend(
     			{
     				tmpls: [],
    @@ -936,7 +945,6 @@ function registerStore(storeName, storeSettings) {
     		// or $.views.things(name, item[, parentTmpl])
     
     		var onStore, compile, itemName, thisStore;
    -
     		if (name && typeof name === OBJECT && !name.nodeType && !name.markup && !name.getTgt) {
     			// Call to $.views.things(items[, parentTmpl]),
     
    @@ -965,7 +973,7 @@ function registerStore(storeName, storeSettings) {
     				delete thisStore[name];
     			}
     		} else {
    -			item = compile ? compile(name, item, parentTmpl, 0) : item;
    +			item = compile ? compile.call(thisStore, name, item, parentTmpl, 0) : item;
     			if (name) {
     				thisStore[name] = item;
     			}
    @@ -985,6 +993,14 @@ function registerStore(storeName, storeSettings) {
     	$views[storeNames] = theStore;
     }
     
    +function addSetting(st) {
    +	$viewsSettings[st] = function(value) {
    +		return arguments.length
    +			? ($subSettings[st] = value, $viewsSettings)
    +			: $subSettings[st];
    +	};
    +}
    +
     //==============
     // renderContent
     //==============
    @@ -1032,7 +1048,7 @@ function renderContent(data, context, noIteration, parentView, key, onRender) {
     		if (!view) {
     			(context = context || {}).root = data; // Provide ~root as shortcut to top-level data.
     		}
    -		if (!isRenderCall || $viewsSettings.useViews || tmpl.useViews || view && view !== topView) {
    +		if (!isRenderCall || $subSettingsAdvanced.useViews || tmpl.useViews || view && view !== topView) {
     			result = renderWithViews(tmpl, data, context, noIteration, view, key, onRender, tag);
     		} else {
     			if (view) { // In a block
    @@ -1050,11 +1066,11 @@ function renderContent(data, context, noIteration, parentView, key, onRender) {
     				for (i = 0, l = data.length; i < l; i++) {
     					view.index = i;
     					view.data = data[i];
    -					result += tmpl.fn(data[i], view, $views);
    +					result += tmpl.fn(data[i], view, $sub);
     				}
     			} else {
     				view.data = data;
    -				result += tmpl.fn(data, view, $views);
    +				result += tmpl.fn(data, view, $sub);
     			}
     			view.data = prevData;
     			view.index = prevIndex;
    @@ -1086,7 +1102,7 @@ function renderWithViews(tmpl, data, context, noIteration, view, key, onRender,
     
     		if (tmpl === view.content) { // {{xxx tmpl=#content}}
     			contentTmpl = tmpl !== view.ctx._wrp // We are rendering the #content
    -				?  view.ctx._wrp // #content was the tagCtx.props.tmpl wrapper of the block content - so within this view, #content will now be the view.ctx._wrp block content
    +				? view.ctx._wrp // #content was the tagCtx.props.tmpl wrapper of the block content - so within this view, #content will now be the view.ctx._wrp block content
     				: undefined; // #content was the view.ctx._wrp block content - so within this view, there is no longer any #content to wrap.
     		} else if (tmpl !== tagCtx.content) {
     			if (tmpl === tag.template) { // Rendering {{tag}} tag.template, replacing block content.
    @@ -1164,7 +1180,7 @@ function renderWithViews(tmpl, data, context, noIteration, view, key, onRender,
     			}
     			childView = new View(newCtx, "item", newView, data[i], tmpl, (key || 0) + i, onRender, contentTmpl);
     
    -			itemResult = tmpl.fn(data[i], childView, $views);
    +			itemResult = tmpl.fn(data[i], childView, $sub);
     			result += newView._.onRender ? newView._.onRender(itemResult, childView) : itemResult;
     		}
     	} else {
    @@ -1177,7 +1193,7 @@ function renderWithViews(tmpl, data, context, noIteration, view, key, onRender,
     		if (tag && !tag.flow) {
     			newView.tag = tag;
     		}
    -		result += tmpl.fn(data, newView, $views);
    +		result += tmpl.fn(data, newView, $sub);
     	}
     	return outerOnRender ? outerOnRender(result, newView) : result;
     }
    @@ -1189,12 +1205,22 @@ function renderWithViews(tmpl, data, context, noIteration, view, key, onRender,
     // Generate a reusable function that will serve to render a template against data
     // (Compile AST then build template function)
     
    -function error(e, view, fallback) {
    -	var message = $viewsSettings.onError(e, view, fallback);
    -	if ("" + e === e) { // if e is a string, not an Exception, then throw new Exception
    -		throw new $sub.Err(message);
    +function onRenderError(e, view, fallback) {
    +	var message = fallback !== undefined
    +		? $isFunction(fallback)
    +			? fallback.call(view.data, e, view)
    +			: fallback || ""
    +		: "{Error: " + e.message + "}";
    +
    +	if ($subSettings.onError && (fallback = $subSettings.onError.call(view.data, e, fallback && message, view)) !== undefined) {
    +		message = fallback; // There is a settings.debugMode(handler) onError override. Call it, and use return value (if any) to replace message
     	}
    -	return !view.linkCtx && view.linked ? $converters.html(message) : message;
    +
    +	return view && !view.linkCtx && view.linked ? $converters.html(message) : message;
    +}
    +
    +function error(message) {
    +	throw new $sub.Err(message);
     }
     
     function syntaxError(message) {
    @@ -1259,7 +1285,6 @@ function tmplFn(markup, tmpl, isLinkExpr, convertBack, hasElse) {
     }}/g
     
     */
    -
     		if (codeTag && bind || slash && !tagName || params && params.slice(-1) === ":" || bind2) {
     			syntaxError(all);
     		}
    @@ -1335,8 +1360,8 @@ function tmplFn(markup, tmpl, isLinkExpr, convertBack, hasElse) {
     					tagName,
     					converter || !!convertBack || hasHandlers || "",
     					block && [],
    -					parsedParam(paramsArgs, paramsProps, paramsCtxProps),
    -					parsedParam(args, props, ctxProps),
    +					parsedParam(paramsArgs || (tagName === ":" ? "'#data'," : ""), paramsProps, paramsCtxProps), // {{:}} equivalent to {{:#data}}
    +					parsedParam(args || (tagName === ":" ? "data," : ""), props, ctxProps),
     					onError,
     					useTrigger,
     					pathBindings || 0
    @@ -1358,7 +1383,8 @@ function tmplFn(markup, tmpl, isLinkExpr, convertBack, hasElse) {
     	//==== /end of nested functions ====
     
     	var result, newNode, hasHandlers,
    -		allowCode = $viewsSettings.allowCode || tmpl && tmpl.allowCode,
    +		allowCode = $subSettings.allowCode || tmpl && tmpl.allowCode
    +			|| $viewsSettings.allowCode === true, // include direct setting of settings.allowCode true for backward compat only
     		astTop = [],
     		loc = 0,
     		stack = [],
    @@ -1461,6 +1487,9 @@ function parseParams(params, pathBindings, tmpl) {
     			var subPath = object === ".";
     			if (object) {
     				path = path.slice(not.length);
    +				if (/^\.?constructor$/.test(leafToken||path)) {
    +					syntaxError(allPath);
    +				}
     				if (!subPath) {
     					allPath = (helper
     							? 'view.hlp("' + helper + '")'
    @@ -1618,7 +1647,7 @@ function buildCode(ast, tmpl, isLinkExpr) {
     	var i, node, tagName, converter, tagCtx, hasTag, hasEncoder, getsVal, hasCnvt, useCnvt, tmplBindings, pathBindings, params, boundOnErrStart, boundOnErrEnd,
     		tagRender, nestedTmpls, tmplName, nestedTmpl, tagAndElses, content, markup, nextIsElse, oldCode, isElse, isGetVal, tagCtxFn, onError, tagStart, trigger,
     		tmplBindingKey = 0,
    -		useViews = $viewsSettings.useViews || tmpl.useViews || tmpl.tags || tmpl.templates || tmpl.helpers || tmpl.converters,
    +		useViews = $subSettingsAdvanced.useViews || tmpl.useViews || tmpl.tags || tmpl.templates || tmpl.helpers || tmpl.converters,
     		code = "",
     		tmplOptions = {},
     		l = ast.length;
    @@ -1677,6 +1706,7 @@ function buildCode(ast, tmpl, isLinkExpr) {
     					if (converter) {
     						tagName = converter === HTML ? ">" : converter + tagName;
     					}
    +					trigger = node[6] || $subSettings.trigger;
     				} else {
     					if (content) { // TODO optimize - if content.length === 0 or if there is a tmpl="..." specified - set content to null / don't run this compilation code - since content won't get used!!
     						// Create template object for nested template
    @@ -1728,8 +1758,8 @@ function buildCode(ast, tmpl, isLinkExpr) {
     							? ((tmplBindings[tmplBindingKey - 1] = tagCtxFn), tmplBindingKey) // Store the compiled tagCtxFn in tmpl.bnds, and pass the key to convertVal()
     							: "{" + tagCtx + "}") + ")")
     						: tagName === ">"
    -							? (hasEncoder = true, "h(" + params[0] + ')')
    -							: (getsVal = true, "((v=" + (params[0] || 'data') + ')!=null?v:"")') // Strict equality just for data-link="title{:expr}" so expr=null will remove title attribute
    +							? (hasEncoder = true, "h(" + params[0] + ")")
    +							: (getsVal = true, "((v=" + params[0] + ')!=null?v:"")') // Strict equality just for data-link="title{:expr}" so expr=null will remove title attribute
     					)
     					: (hasTag = true, "\n{view:view,tmpl:" // Add this tagCtx to the compiled code for the tagCtxs to be passed to renderTag()
     						+ (content ? nestedTmpls.length : "0") + "," // For block tags, pass in the key (nestedTmpls.length) to the nested content template
    @@ -1775,13 +1805,13 @@ function buildCode(ast, tmpl, isLinkExpr) {
     		+ "\nvar v"
     		+ (hasTag ? ",t=j._tag" : "")                // has tag
     		+ (hasCnvt ? ",c=j._cnvt" : "")              // converter
    -		+ (hasEncoder ? ",h=j.converters.html" : "") // html converter
    +		+ (hasEncoder ? ",h=j._html" : "")           // html converter
     		+ (isLinkExpr ? ";\n" : ',ret=""\n')
     		+ (tmplOptions.debug ? "debugger;" : "")
     		+ code
     		+ (isLinkExpr ? "\n" : ";\nreturn ret;");
     
    -	if ($viewsSettings._dbgMode) {
    +	if ($subSettings.debugMode !== false) {
     		code = "try {\n" + code + "\n}catch(e){\nreturn j._err(e, view);\n}";
     	}
     
    @@ -1920,28 +1950,35 @@ if (!(jsr || $ && $.render)) {
     		$.jsrender = versionNumber;
     	}
     
    +	$subSettings = $sub.settings;
    +	$subSettings.allowCode = false;
     	$isFunction = $.isFunction;
     	$isArray = $.isArray;
     	$.render = $render;
     	$.views = $views;
     	$.templates = $templates = $views.templates;
     
    -	$viewsSettings({
    -		debugMode: dbgMode,
    -		delimiters: $viewsDelimiters,
    -		onError: function(e, view, fallback) {
    -			// Can override using $.views.settings({onError: function(...) {...}});
    -			if (view) {
    -				// For render errors, e is an exception thrown in compiled template, and view is the current view. For other errors, e is an error string.
    -				e = fallback === undefined
    -					? "{Error: " + (e.message || e) + "}"
    -					: $isFunction(fallback)
    -						? fallback(e, view) : fallback;
    -			}
    -			return e == undefined ? "" : e;
    -		},
    -		_dbgMode: false
    -	});
    +	for (setting in $subSettings) {
    +		addSetting(setting);
    +	}
    +
    +	($viewsSettings.debugMode = function(debugMode) {
    +		return debugMode === undefined
    +			? $subSettings.debugMode
    +			: (
    +				$subSettings.debugMode = debugMode,
    +				$subSettings.onError = debugMode + "" === debugMode
    +					? new Function("", "return '" + debugMode + "';" )
    +					: $isFunction(debugMode)
    +						? debugMode
    +						: undefined,
    +				$viewsSettings);
    +	})(false); // jshint ignore:line
    +
    +	$subSettingsAdvanced = $subSettings.advanced = {
    +		useViews: false,
    +		_jsv: false // For global access to JsViews store
    +	};
     
     	//========================== Register tags ==========================
     
    @@ -2021,11 +2058,11 @@ if (!(jsr || $ && $.render)) {
     	});
     
     	//========================== Define default delimiters ==========================
    -	$viewsDelimiters();
    +	$viewsSettings.delimiters("{{", "}}", "^");
     }
     
     if (jsrToJq) { // Moving from jsrender namespace to jQuery namepace - copy over the stored items (templates, converters, helpers...)
     	jsr.views.sub._jq($);
     }
     return $ || jsr;
    -}));
    +}, this));
    
  • jsrender.min.js+2 2 modified
    @@ -1,4 +1,4 @@
    -/*! JsRender v0.9.73 (Beta): http://jsviews.com/#jsrender */
    +/*! JsRender v0.9.74 (Beta): http://jsviews.com/#jsrender */
     /*! **VERSION FOR WEB** (For NODE.JS see http://jsviews.com/download/jsrender-node.js) */
    -!function(e){var t=(0,eval)("this"),n=t.jQuery;"function"==typeof define&&define.amd?define(e):"object"==typeof exports?module.exports=n?e(n):function(t){if(t&&!t.fn)throw"Provide jQuery or null";return e(t)}:e(!1)}(function(e){"use strict";function t(e,t){return function(){var n,r=this,i=r.base;return r.base=e,n=t.apply(r,arguments),r.base=i,n}}function n(e,n){return G(n)&&(n=t(e?e._d?e:t(a,e):a,n),n._d=1),n}function r(e,t){for(var r in t.props)_e.test(r)&&(e[r]=n(e[r],t.props[r]))}function i(e){return e}function a(){return""}function s(e){try{throw"dbg breakpoint"}catch(t){}return this.base?this.baseApply(arguments):e}function o(e){re._dbgMode=e!==!1}function d(t){this.name=(e.link?"JsViews":"JsRender")+" Error",this.message=t||this.name}function p(e,t){var n;for(n in t)e[n]=t[n];return e}function l(e,t,n){return(0!==this||e)&&(se=e?e.charAt(0):se,oe=e?e.charAt(1):oe,de=t?t.charAt(0):de,pe=t?t.charAt(1):pe,le=n||le,e="\\"+se+"(\\"+le+")?\\"+oe,t="\\"+de+"\\"+pe,D="(?:(\\w+(?=[\\/\\s\\"+de+"]))|(\\w+)?(:)|(>)|(\\*))\\s*((?:[^\\"+de+"]|\\"+de+"(?!\\"+pe+"))*?)",ne.rTag="(?:"+D+")",D=new RegExp("(?:"+e+D+"(\\/)?|\\"+se+"(\\"+le+")?\\"+oe+"(?:(?:\\/(\\w+))\\s*|!--[\\s\\S]*?--))"+t,"g"),P=new RegExp("<.*>|([^\\\\]|^)[{}]|"+e+".*"+t)),[se,oe,de,pe,le]}function u(e,t){t||e===!0||(t=e,e=void 0);var n,r,i,a,s=this,o=!t||"root"===t;if(e){if(a=t&&s.type===t&&s,!a)if(n=s.views,s._.useKey){for(r in n)if(a=t?n[r].get(e,t):n[r])break}else for(r=0,i=n.length;!a&&i>r;r++)a=t?n[r].get(e,t):n[r]}else if(o)for(;s.parent;)a=s,s=s.parent;else for(;s&&!a;)a=s.type===t?s:void 0,s=s.parent;return a}function c(){var e=this.get("item");return e?e.index:void 0}function f(){return this.index}function g(e){var t,n=this,r=n.linkCtx,i=(n.ctx||{})[e];return void 0===i&&r&&r.ctx&&(i=r.ctx[e]),void 0===i&&(i=ee[e]),i&&G(i)&&!i._wrp&&(t=function(){return i.apply(this&&this!==L?this:n,arguments)},t._wrp=n,p(t,i)),t||i}function v(e){return e&&(e.fn?e:this.getRsc("templates",e)||X(e))}function m(e,t,n,i){var a,s,o="number"==typeof n&&t.tmpl.bnds[n-1],d=t.linkCtx;return void 0!==i?n=i={props:{},args:[i]}:o&&(n=o(t.data,t,z)),s=n.args[0],(e||o)&&(a=d&&d.tag,a||(a=p(new ne._tg,{_:{inline:!d,bnd:o,unlinked:!0},tagName:":",cvt:e,flow:!0,tagCtx:n}),d&&(d.tag=a,a.linkCtx=d),n.ctx=K(n.ctx,(d?d.view:t).ctx)),a._er=i&&s,r(a,n),n.view=t,a.ctx=n.ctx||{},n.ctx=void 0,s=a.cvtArgs(a.convert||"true"!==e&&e)[0],s=o&&t._.onRender?t._.onRender(s,t,a):s),void 0!=s?s:""}function h(e){var t=this,n=t.tagCtx,r=n.view,i=n.args;return e=t.convert||e,e=e&&(""+e===e?r.getRsc("converters",e)||M("Unknown converter: '"+e+"'"):e),i=i.length||n.index?e?i.slice():i:[r.data],e&&(e.depends&&(t.depends=ne.getDeps(t.depends,t,e.depends,e)),i[0]=e.apply(t,i)),i}function w(e,t){for(var n,r,i=this;void 0===n&&i;)r=i.tmpl&&i.tmpl[e],n=r&&r[t],i=i.parent;return n||z[e][t]}function x(e,t,n,i,a,s){t=t||Z;var o,d,p,l,u,c,f,g,v,m,h,w,x,b,_,y,k,j,C,T="",A=t.linkCtx||0,V=t.ctx,$=n||t.tmpl,N="number"==typeof i&&t.tmpl.bnds[i-1];for("tag"===e._is?(o=e,e=o.tagName,i=o.tagCtxs,p=o.template):(d=t.getRsc("tags",e)||M("Unknown tag: {{"+e+"}} "),p=d.template),void 0!==s?(T+=s,i=s=[{props:{},args:[]}]):N&&(i=N(t.data,t,z)),g=i.length,f=0;g>f;f++)m=i[f],(!A||!A.tag||f&&!A.tag._.inline||o._er)&&((w=$.tmpls&&m.tmpl)&&(w=m.content=$.tmpls[w-1]),m.index=f,m.tmpl=w,m.render=R,m.view=t,m.ctx=K(m.ctx,V)),(n=m.props.tmpl)&&(m.tmpl=t.getTmpl(n)),o||(o=new d._ctr,x=!!o.init,o.parent=c=V&&V.tag,o.tagCtxs=i,C=o.dataMap,A&&(o._.inline=!1,A.tag=o,o.linkCtx=A),(o._.bnd=N||A.fn)?o._.arrVws={}:o.dataBoundOnly&&M("{^{"+e+"}} tag must be data-bound")),i=o.tagCtxs,C=o.dataMap,m.tag=o,C&&i&&(m.map=i[f].map),o.flow||(h=m.ctx=m.ctx||{},l=o.parents=h.parentTags=V&&K(h.parentTags,V.parentTags)||{},c&&(l[c.tagName]=c),l[o.tagName]=h.tag=o);if(!(o._er=s)){for(r(o,i[0]),o.rendering={},f=0;g>f;f++)m=o.tagCtx=i[f],k=m.props,y=o.cvtArgs(),(b=k.dataMap||C)&&(y.length||k.dataMap)&&(_=m.map,(!_||_.src!==y[0]||a)&&(_&&_.src&&_.unmap(),_=m.map=b.map(y[0],k,void 0,!o._.bnd)),y=[_.tgt]),o.ctx=m.ctx,f||(x&&(j=o.template,o.init(m,A,o.ctx),x=void 0),A&&(A.attr=o.attr=A.attr||o.attr),u=o.attr,o._.noVws=u&&u!==Ce),v=void 0,o.render&&(v=o.render.apply(o,y)),y.length||(y=[t]),void 0===v&&(v=m.render(y[0],!0)||(a?void 0:"")),T=T?T+(v||""):v;o.rendering=void 0}return o.tagCtx=i[0],o.ctx=o.tagCtx.ctx,o._.noVws&&o._.inline&&(T="text"===u?Y.html(T):""),N&&t._.onRender?t._.onRender(T,t,o):T}function b(e,t,n,r,i,a,s,o){var d,p,l,u=this,f="array"===t;u.content=o,u.views=f?[]:{},u.parent=n,u.type=t||"top",u.data=r,u.tmpl=i,l=u._={key:0,useKey:f?0:1,id:""+ke++,onRender:s,bnds:{}},u.linked=!!s,n?(d=n.views,p=n._,p.useKey?(d[l.key="_"+p.useKey++]=u,u.index=Ve,u.getIndex=c):d.length===(l.key=u.index=a)?d.push(u):d.splice(a,0,u),u.ctx=e||n.ctx):u.ctx=e}function _(e){var t,n,r,i,a,s,o;for(t in Ee)if(a=Ee[t],(s=a.compile)&&(n=e[t+"s"]))for(r in n)i=n[r]=s(r,n[r],e,0),i._is=t,i&&(o=ne.onStore[t])&&o(r,i,s)}function y(e,t,r){function i(){var t=this;t._={inline:!0,unlinked:!0},t.tagName=e}var a,s,o,d=new ne._tg;if(G(t)?t={depends:t.depends,render:t}:""+t===t&&(t={template:t}),s=t.baseTag){t.flow=!!t.flow,t.baseTag=s=""+s===s?r&&r.tags[s]||te[s]:s,d=p(d,s);for(o in t)d[o]=n(s[o],t[o])}else d=p(d,t);return void 0!==(a=d.template)&&(d.template=""+a===a?X[a]||X(a):a),d.init!==!1&&((i.prototype=d).constructor=d._ctr=i),r&&(d._parentTmpl=r),d}function k(e){return this.base.apply(this,e)}function j(t,n,r,i){function a(n){var a,o;if(""+n===n||n.nodeType>0&&(s=n)){if(!s)if(/^\.\/[^\\:*?"<>]*$/.test(n))(o=X[t=t||n])?n=o:s=document.getElementById(n);else if(e.fn&&!P.test(n))try{s=e(document).find(n)[0]}catch(d){}s&&(i?n=s.innerHTML:(a=s.getAttribute(Ae),a?a!==Re?(n=X[a],delete X[a]):e.fn&&(n=e.data(s)[Re]):(t=t||(e.fn?Re:n),n=j(t,s.innerHTML,r,i)),n.tmplName=t=t||a,t!==Re&&(X[t]=n),s.setAttribute(Ae,t),e.fn&&e.data(s,Re,n))),s=void 0}else n.fn||(n=void 0);return n}var s,o,d=n=n||"";return 0===i&&(i=void 0,d=a(d)),i=i||(n.markup?n:{}),i.tmplName=t,r&&(i._parentTmpl=r),!d&&n.markup&&(d=a(n.markup))&&d.fn&&(d=d.markup),void 0!==d?(d.fn||n.fn?d.fn&&(o=d):(n=T(d,i),N(d.replace(ve,"\\$&"),n)),o||(_(i),o=p(function(){return n.render.apply(n,arguments)},n)),t&&!r&&t!==Re&&(Me[t]=o),o):void 0}function C(e){function t(t,n){this.tgt=e.getTgt(t,n)}return G(e)&&(e={getTgt:e}),e.baseMap&&(e=p(p({},e.baseMap),e)),e.map=function(e,n){return new t(e,n)},e}function T(t,n){var r,i=re.wrapMap||{},a=p({tmpls:[],links:{},bnds:[],_is:"template",render:R},n);return a.markup=t,n.htmlTag||(r=we.exec(t),a.htmlTag=r?r[1].toLowerCase():""),r=i[a.htmlTag],r&&r!==i.div&&(a.markup=e.trim(a.markup)),a}function A(e,t){function n(i,a,s){var o,d,p,l;if(i&&typeof i===Te&&!i.nodeType&&!i.markup&&!i.getTgt){for(p in i)n(p,i[p],a);return z}return void 0===a&&(a=i,i=void 0),i&&""+i!==i&&(s=a,a=i,i=void 0),l=s?s[r]=s[r]||{}:n,d=t.compile,null===a?i&&delete l[i]:(a=d?d(i,a,s,0):a,i&&(l[i]=a)),d&&a&&(a._is=e),a&&(o=ne.onStore[e])&&o(i,a,d),a}var r=e+"s";z[r]=n}function R(e,t,n,r,i,a){var s,o,d,p,l,u,c,f,g=r,v="";if(t===!0?(n=t,t=void 0):typeof t!==Te&&(t=void 0),(d=this.tag)?(l=this,g=g||l.view,p=g.getTmpl(d.template||l.tmpl),arguments.length||(e=g)):p=this,p){if(!g&&e&&"view"===e._is&&(g=e),g&&e===g&&(e=g.data),u=!g,ie=ie||u,g||((t=t||{}).root=e),!ie||re.useViews||p.useViews||g&&g!==Z)v=V(p,e,t,n,g,i,a,d);else{if(g?(c=g.data,f=g.index,g.index=Ve):(g=Z,g.data=e,g.ctx=t),W(e)&&!n)for(s=0,o=e.length;o>s;s++)g.index=s,g.data=e[s],v+=p.fn(e[s],g,z);else g.data=e,v+=p.fn(e,g,z);g.data=c,g.index=f}u&&(ie=void 0)}return v}function V(e,t,n,r,i,a,s,o){function d(e){_=p({},n),_[x]=e}var l,u,c,f,g,v,m,h,w,x,_,y,k="";if(o&&(w=o.tagName,y=o.tagCtx,n=n?K(n,o.ctx):o.ctx,e===i.content?m=e!==i.ctx._wrp?i.ctx._wrp:void 0:e!==y.content?e===o.template?(m=y.tmpl,n._wrp=y.content):m=y.content||i.content:m=i.content,y.props.link===!1&&(n=n||{},n.link=!1),(x=y.props.itemVar)&&("~"!==x.charAt(0)&&$("Use itemVar='~myItem'"),x=x.slice(1))),i&&(s=s||i._.onRender,n=K(n,i.ctx)),a===!0&&(v=!0,a=0),s&&(n&&n.link===!1||o&&o._.noVws)&&(s=void 0),h=s,s===!0&&(h=void 0,s=i._.onRender),n=e.helpers?K(e.helpers,n):n,_=n,W(t)&&!r)for(c=v?i:void 0!==a&&i||new b(n,"array",i,t,e,a,s),i&&i._.useKey&&(c._.bnd=!o||o._.bnd&&o),x&&(c.it=x),x=c.it,l=0,u=t.length;u>l;l++)x&&d(t[l]),f=new b(_,"item",c,t[l],e,(a||0)+l,s,m),g=e.fn(t[l],f,z),k+=c._.onRender?c._.onRender(g,f):g;else x&&d(t),c=v?i:new b(_,w||"data",i,t,e,a,s,m),o&&!o.flow&&(c.tag=o),k+=e.fn(t,c,z);return h?h(k,c):k}function M(e,t,n){var r=re.onError(e,t,n);if(""+e===e)throw new ne.Err(r);return!t.linkCtx&&t.linked?Y.html(r):r}function $(e){M("Syntax error\n"+e)}function N(e,t,n,r,i){function a(t){t-=f,t&&v.push(e.substr(f,t).replace(fe,"\\n"))}function s(t,n){t&&(t+="}}",$((n?"{{"+n+"}} block has {{/"+t+" without {{"+t:"Unmatched or missing {{/"+t)+", in template:\n"+e))}function o(o,d,c,h,w,x,b,_,y,k,j,C){(b&&d||y&&!c||_&&":"===_.slice(-1)||k)&&$(o),x&&(w=":",h=Ce),y=y||n&&!i;var T=(d||n)&&[[]],A="",R="",V="",M="",N="",E="",S="",U="",K=!y&&!w;c=c||(_=_||"#data",w),a(C),f=C+o.length,b?u&&v.push(["*","\n"+_.replace(/^:/,"ret+= ").replace(ge,"$1")+";\n"]):c?("else"===c&&(he.test(_)&&$('for "{{else if expr}}" use "{{else expr}}"'),T=m[7]&&[[]],m[8]=e.substring(m[8],C),m=g.pop(),v=m[2],K=!0),_&&I(_.replace(fe," "),T,t).replace(me,function(e,t,n,r,i,a,s,o){return r="'"+i+"':",s?(R+=a+",",M+="'"+o+"',"):n?(V+=r+a+",",E+=r+"'"+o+"',"):t?S+=a:("trigger"===i&&(U+=a),A+=r+a+",",N+=r+"'"+o+"',",l=l||_e.test(i)),""}).slice(0,-1),T&&T[0]&&T.pop(),p=[c,h||!!r||l||"",K&&[],F(M,N,E),F(R,A,V),S,U,T||0],v.push(p),K&&(g.push(m),m=p,m[8]=f)):j&&(s(j!==m[0]&&"else"!==m[0]&&j,m[0]),m[8]=e.substring(m[8],C),m=g.pop()),s(!m&&j),v=m[2]}var d,p,l,u=re.allowCode||t&&t.allowCode,c=[],f=0,g=[],v=c,m=[,,c];return u&&(t.allowCode=u),n&&(void 0!==r&&(e=e.slice(0,-r.length-2)+pe),e=se+e+pe),s(g[0]&&g[0][2].pop()[0]),e.replace(D,o),a(e.length),(f=c[c.length-1])&&s(""+f!==f&&+f[8]===f[8]&&f[0]),n?(d=U(c,e,n),E(d,[c[0][7]])):d=U(c,t),d}function E(e,t){var n,r,i=0,a=t.length;for(e.deps=[];a>i;i++){r=t[i];for(n in r)"_jsvto"!==n&&r[n].length&&(e.deps=e.deps.concat(r[n]))}e.paths=r}function F(e,t,n){return[e.slice(0,-1),t.slice(0,-1),n.slice(0,-1)]}function S(e,t){return"\n	"+(t?t+":{":"")+"args:["+e[0]+"]"+(e[1]||!t?",\n	props:{"+e[1]+"}":"")+(e[2]?",\n	ctx:{"+e[2]+"}":"")}function I(e,t,n){function r(r,h,w,x,b,_,y,k,j,C,T,A,R,V,M,E,F,S,I,U){function K(e,n,r,s,o,d,u,c){var f="."===r;if(r&&(b=b.slice(n.length),f||(e=(s?'view.hlp("'+s+'")':o?"view":"data")+(c?(d?"."+d:s?"":o?"":"."+r)+(u||""):(c=s?"":o?d||"":r,"")),e+=c?"."+c:"",e=n+("view.data"===e.slice(0,9)?e.slice(5):e)),p)){if(q="linkTo"===i?a=t._jsvto=t._jsvto||[]:l.bd,B=f&&q[q.length-1]){if(B._jsv){for(;B.sb;)B=B.sb;B.bnd&&(b="^"+b.slice(1)),B.sb=b,B.bnd=B.bnd||"^"===b.charAt(0)}}else q.push(b);m[g]=I+(f?1:0)}return e}x=p&&x,x&&!k&&(b=x+b),_=_||"",w=w||h||A,b=b||j,C=C||F||"";var J,O,q,B,L;if(!y||d||o){if(p&&E&&!d&&!o&&(!i||s||a)&&(J=m[g-1],U.length-1>I-(J||0))){if(J=U.slice(J,I+r.length),O!==!0)if(q=a||u[g-1].bd,B=q[q.length-1],B&&B.prm){for(;B.sb&&B.sb.prm;)B=B.sb;L=B.sb={path:B.sb,bnd:B.bnd}}else q.push(L={path:q.pop()});E=oe+":"+J+" onerror=''"+de,O=f[E],O||(f[E]=!0,f[E]=O=N(E,n,!0)),O!==!0&&L&&(L._jsv=O,L.prm=l.bd,L.bnd=L.bnd||L.path&&L.path.indexOf("^")>=0)}return d?(d=!R,d?r:A+'"'):o?(o=!V,o?r:A+'"'):(w?(m[g]=I++,l=u[++g]={bd:[]},w):"")+(S?g?"":(c=U.slice(c,I),(i?(i=s=a=!1,"\b"):"\b,")+c+(c=I+r.length,p&&t.push(l.bd=[]),"\b")):k?(g&&$(e),p&&t.pop(),i=b,s=x,c=I+r.length,x&&(p=l.bd=t[i]=[]),b+":"):b?b.split("^").join(".").replace(ue,K)+(C?(l=u[++g]={bd:[]},v[g]=!0,C):_):_?_:M?(v[g]=!1,l=u[--g],M+(C?(l=u[++g],v[g]=!0,C):"")):T?(v[g]||$(e),","):h?"":(d=R,o=V,'"'))}$(e)}var i,a,s,o,d,p=t&&t[0],l={bd:p},u={0:l},c=0,f=n?n.links:p&&(p.links=p.links||{}),g=0,v={},m={},h=(e+(n?" ":"")).replace(ce,r);return!g&&h||$(e)}function U(e,t,n){var r,i,a,s,o,d,p,l,u,c,f,g,v,m,h,w,x,b,_,y,k,j,C,A,R,V,M,N,F,I,K=0,J=re.useViews||t.useViews||t.tags||t.templates||t.helpers||t.converters,O="",q={},B=e.length;for(""+t===t?(b=n?'data-link="'+t.replace(fe," ").slice(1,-1)+'"':t,t=0):(b=t.tmplName||"unnamed",t.allowCode&&(q.allowCode=!0),t.debug&&(q.debug=!0),f=t.bnds,x=t.tmpls),r=0;B>r;r++)if(i=e[r],""+i===i)O+='\n+"'+i+'"';else if(a=i[0],"*"===a)O+=";\n"+i[1]+"\nret=ret";else{if(s=i[1],k=!n&&i[2],o=S(i[3],"params")+"},"+S(v=i[4]),N=i[5],I=i[6],j=i[8]&&i[8].replace(ge,"$1"),(R="else"===a)?g&&g.push(i[7]):(K=0,f&&(g=i[7])&&(g=[g],K=f.push(1))),J=J||v[1]||v[2]||g||/view.(?!index)/.test(v[0]),(V=":"===a)?s&&(a=s===Ce?">":s+a):(k&&(_=T(j,q),_.tmplName=b+"/"+a,_.useViews=_.useViews||J,U(k,_),J=_.useViews,x.push(_)),R||(y=a,J=J||a&&(!te[a]||!te[a].flow),A=O,O=""),C=e[r+1],C=C&&"else"===C[0]),F=N?";\ntry{\nret+=":"\n+",m="",h="",V&&(g||I||s&&s!==Ce)){if(M="return {"+o+"};",w='c("'+s+'",view,',M=new Function("data,view,j,u"," // "+b+" "+K+" "+a+"\n"+M),M._er=N,m=w+K+",",h=")",M._tag=a,n)return M;E(M,g),c=!0}if(O+=V?(n?(N?"\ntry{\n":"")+"return ":F)+(c?(c=void 0,J=u=!0,w+(g?(f[K-1]=M,K):"{"+o+"}")+")"):">"===a?(p=!0,"h("+v[0]+")"):(l=!0,"((v="+(v[0]||"data")+')!=null?v:"")')):(d=!0,"\n{view:view,tmpl:"+(k?x.length:"0")+","+o+"},"),y&&!C){if(O="["+O.slice(0,-1)+"]",w='t("'+y+'",view,this,',n||g){if(O=new Function("data,view,j,u"," // "+b+" "+K+" "+y+"\nreturn "+O+";"),O._er=N,O._tag=y,g&&E(f[K-1]=O,g),n)return O;m=w+K+",undefined,",h=")"}O=A+F+w+(K||O)+")",g=0,y=0}N&&(J=!0,O+=";\n}catch(e){ret"+(n?"urn ":"+=")+m+"j._err(e,view,"+N+")"+h+";}\n"+(n?"":"ret=ret"))}O="// "+b+"\nvar v"+(d?",t=j._tag":"")+(u?",c=j._cnvt":"")+(p?",h=j.converters.html":"")+(n?";\n":',ret=""\n')+(q.debug?"debugger;":"")+O+(n?"\n":";\nreturn ret;"),re._dbgMode&&(O="try {\n"+O+"\n}catch(e){\nreturn j._err(e, view);\n}");try{O=new Function("data,view,j,u",O)}catch(L){$("Compiled template code:\n\n"+O+'\n: "'+L.message+'"')}return t&&(t.fn=O,t.useViews=!!J),O}function K(e,t){return e&&e!==t?t?p(p({},t),e):e:t&&p({},t)}function J(e){return je[e]||(je[e]="&#"+e.charCodeAt(0)+";")}function O(e){var t,n,r=[];if(typeof e===Te)for(t in e)n=e[t],n&&n.toJSON&&!n.toJSON()||G(n)||r.push({key:t,prop:n});return r}function q(t,n,r){var i=this.jquery&&(this[0]||M('Unknown template: "'+this.selector+'"')),a=i.getAttribute(Ae);return R.call(a?e.data(i)[Re]:X(i),t,n,r)}function B(e){return void 0!=e?be.test(e)&&(""+e).replace(ye,J)||e:""}var L=(0,eval)("this"),Q=e===!1;e=e&&e.fn?e:L.jQuery;var H,D,P,Z,z,G,W,X,Y,ee,te,ne,re,ie,ae="v0.9.73",se="{",oe="{",de="}",pe="}",le="^",ue=/^(!*?)(?:null|true|false|\d[\d.]*|([\w$]+|\.|~([\w$]+)|#(view|([\w$]+))?)([\w$.^]*?)(?:[.[^]([\w$]+)\]?)?)$/g,ce=/(\()(?=\s*\()|(?:([([])\s*)?(?:(\^?)(!*?[#~]?[\w$.^]+)?\s*((\+\+|--)|\+|-|&&|\|\||===|!==|==|!=|<=|>=|[<>%*:?\/]|(=))\s*|(!*?[#~]?[\w$.^]+)([([])?)|(,\s*)|(\(?)\\?(?:(')|("))|(?:\s*(([)\]])(?=\s*[.^]|\s*$|[^([])|[)\]])([([]?))|(\s+)/g,fe=/[ \t]*(\r\n|\n|\r)/g,ge=/\\(['"])/g,ve=/['"\\]/g,me=/(?:\x08|^)(onerror:)?(?:(~?)(([\w$_\.]+):)?([^\x08]+))\x08(,)?([^\x08]+)/gi,he=/^if\s/,we=/<(\w+)[>\s]/,xe=/[\x00`><"'&]/g,be=/[\x00`><\"'&]/,_e=/^on[A-Z]|^convert(Back)?$/,ye=xe,ke=0,je={"&":"&amp;","<":"&lt;",">":"&gt;","\x00":"&#0;","'":"&#39;",'"':"&#34;","`":"&#96;"},Ce="html",Te="object",Ae="data-jsv-tmpl",Re="jsvTmpl",Ve="For #index in nested block use #getIndex().",Me={},$e=L.jsrender,Ne=$e&&e&&!e.render,Ee={template:{compile:j},tag:{compile:y},helper:{},converter:{}};if(z={jsviews:ae,settings:function(e){p(re,e),o(re._dbgMode),re.jsv&&re.jsv()},sub:{View:b,Err:d,tmplFn:N,parse:I,extend:p,extendCtx:K,syntaxErr:$,onStore:{},_ths:r,_tg:function(){}},map:C,_cnvt:m,_tag:x,_err:M},(d.prototype=new Error).constructor=d,c.depends=function(){return[this.get("item"),"index"]},f.depends="index",b.prototype={get:u,getIndex:f,getRsc:w,getTmpl:v,hlp:g,_is:"view"},!($e||e&&e.render)){for(H in Ee)A(H,Ee[H]);X=z.templates,Y=z.converters,ee=z.helpers,te=z.tags,ne=z.sub,re=z.settings,ne._tg.prototype={baseApply:k,cvtArgs:h},Z=ne.topView=new b,e?(e.fn.render=q,e.observable&&(p(ne,e.views.sub),z.map=e.views.map)):(e={},Q&&(L.jsrender=e),e.renderFile=e.__express=e.compile=function(){throw"Node.js: use npm jsrender, or jsrender-node.js"},e.isFunction=function(e){return"function"==typeof e},e.isArray=Array.isArray||function(e){return"[object Array]"==={}.toString.call(e)},ne._jq=function(t){t!==e&&(p(t,e),e=t,e.fn.render=q,delete e.jsrender)},e.jsrender=ae),G=e.isFunction,W=e.isArray,e.render=Me,e.views=z,e.templates=X=z.templates,re({debugMode:o,delimiters:l,onError:function(e,t,n){return t&&(e=void 0===n?"{Error: "+(e.message||e)+"}":G(n)?n(e,t):n),void 0==e?"":e},_dbgMode:!1}),te({"if":{render:function(e){var t=this,n=t.tagCtx,r=t.rendering.done||!e&&(arguments.length||!n.index)?"":(t.rendering.done=!0,t.selected=n.index,n.render(n.view,!0));return r},flow:!0},"for":{render:function(e){var t,n=!arguments.length,r=this,i=r.tagCtx,a="",s=0;return r.rendering.done||(t=n?i.view.data:e,void 0!==t&&(a+=i.render(t,n),s+=W(t)?t.length:1),(r.rendering.done=s)&&(r.selected=i.index)),a},flow:!0},props:{baseTag:"for",dataMap:C(O),flow:!0},include:{flow:!0},"*":{render:i,flow:!0},":*":{render:i,flow:!0},dbg:ee.dbg=Y.dbg=s}),Y({html:B,attr:B,url:function(e){return void 0!=e?encodeURI(""+e):null===e?e:""}}),l()}return Ne&&$e.views.sub._jq(e),e||$e});
    +!function(e,t){var n=t.jQuery;"function"==typeof define&&define.amd?define(function(){return e(t)}):"object"==typeof exports?module.exports=n?e(t,n):function(n){if(n&&!n.fn)throw"Provide jQuery or null";return e(t,n)}:e(t,!1)}(function(e,t){"use strict";function n(e,t){return function(){var n,r=this,i=r.base;return r.base=e,n=t.apply(r,arguments),r.base=i,n}}function r(e,t){return W(t)&&(t=n(e?e._d?e:n(o,e):o,t),t._d=1),t}function i(e,t){for(var n in t.props)Ce.test(n)&&(e[n]=r(e[n],t.props[n]))}function a(e){return e}function o(){return""}function s(e){try{throw console.log("JsRender dbg breakpoint: "+e),"dbg breakpoint"}catch(t){}return this.base?this.baseApply(arguments):e}function d(e){this.name=(t.link?"JsViews":"JsRender")+" Error",this.message=e||this.name}function l(e,t){var n;for(n in t)e[n]=t[n];return e}function u(e,t,n){return e?(ie.delimiters=[e,t,pe=n?n.charAt(0):pe],se=e.charAt(0),de=e.charAt(1),le=t.charAt(0),ue=t.charAt(1),e="\\"+se+"(\\"+pe+")?\\"+de,t="\\"+le+"\\"+ue,P="(?:(\\w+(?=[\\/\\s\\"+le+"]))|(\\w+)?(:)|(>)|(\\*))\\s*((?:[^\\"+le+"]|\\"+le+"(?!\\"+ue+"))*?)",re.rTag="(?:"+P+")",P=new RegExp("(?:"+e+P+"(\\/)?|\\"+se+"(\\"+pe+")?\\"+de+"(?:(?:\\/(\\w+))\\s*|!--[\\s\\S]*?--))"+t,"g"),Z=new RegExp("<.*>|([^\\\\]|^)[{}]|"+e+".*"+t),oe):ie.delimiters}function p(e,t){t||e===!0||(t=e,e=void 0);var n,r,i,a,o=this,s=!t||"root"===t;if(e){if(a=t&&o.type===t&&o,!a)if(n=o.views,o._.useKey){for(r in n)if(a=t?n[r].get(e,t):n[r])break}else for(r=0,i=n.length;!a&&i>r;r++)a=t?n[r].get(e,t):n[r]}else if(s)for(;o.parent;)a=o,o=o.parent;else for(;o&&!a;)a=o.type===t?o:void 0,o=o.parent;return a}function c(){var e=this.get("item");return e?e.index:void 0}function f(){return this.index}function g(t){var n,r=this,i=r.linkCtx,a=(r.ctx||{})[t];return void 0===a&&i&&i.ctx&&(a=i.ctx[t]),void 0===a&&(a=te[t]),a&&W(a)&&!a._wrp&&(n=function(){return a.apply(this&&this!==e?this:r,arguments)},n._wrp=r,l(n,a)),n||a}function v(e){return e&&(e.fn?e:this.getRsc("templates",e)||Y(e))}function m(e,t,n,r){var a,o,s="number"==typeof n&&t.tmpl.bnds[n-1],d=t.linkCtx;return void 0!==r?n=r={props:{},args:[r]}:s&&(n=s(t.data,t,re)),o=n.args[0],(e||s)&&(a=d&&d.tag,a||(a=l(new re._tg,{_:{inline:!d,bnd:s,unlinked:!0},tagName:":",cvt:e,flow:!0,tagCtx:n}),d&&(d.tag=a,a.linkCtx=d),n.ctx=O(n.ctx,(d?d.view:t).ctx)),a._er=r&&o,i(a,n),n.view=t,a.ctx=n.ctx||{},n.ctx=void 0,o=a.cvtArgs(a.convert||"true"!==e&&e)[0],o=s&&t._.onRender?t._.onRender(o,t,a):o),void 0!=o?o:""}function h(e){var t=this,n=t.tagCtx,r=n.view,i=n.args;return e=t.convert||e,e=e&&(""+e===e?r.getRsc("converters",e)||N("Unknown converter: '"+e+"'"):e),i=i.length||n.index?e?i.slice():i:[r.data],e&&(e.depends&&(t.depends=re.getDeps(t.depends,t,e.depends,e)),i[0]=e.apply(t,i)),i}function w(e,t){for(var n,r,i=this;void 0===n&&i;)r=i.tmpl&&i.tmpl[e],n=r&&r[t],i=i.parent;return n||G[e][t]}function x(e,t,n,r,a,o){t=t||z;var s,d,l,u,p,c,f,g,v,m,h,w,x,b,_,y,k,j,C,T="",A=t.linkCtx||0,R=t.ctx,$=n||t.tmpl,M="number"==typeof r&&t.tmpl.bnds[r-1];for("tag"===e._is?(s=e,e=s.tagName,r=s.tagCtxs,l=s.template):(d=t.getRsc("tags",e)||N("Unknown tag: {{"+e+"}} "),l=d.template),void 0!==o?(T+=o,r=o=[{props:{},args:[]}]):M&&(r=M(t.data,t,re)),g=r.length,f=0;g>f;f++)m=r[f],(!A||!A.tag||f&&!A.tag._.inline||s._er)&&((w=$.tmpls&&m.tmpl)&&(w=m.content=$.tmpls[w-1]),m.index=f,m.tmpl=w,m.render=V,m.view=t,m.ctx=O(m.ctx,R)),(n=m.props.tmpl)&&(m.tmpl=t.getTmpl(n)),s||(s=new d._ctr,x=!!s.init,s.parent=c=R&&R.tag,s.tagCtxs=r,C=s.dataMap,A&&(s._.inline=!1,A.tag=s,s.linkCtx=A),(s._.bnd=M||A.fn)?s._.arrVws={}:s.dataBoundOnly&&N("{^{"+e+"}} tag must be data-bound")),r=s.tagCtxs,C=s.dataMap,m.tag=s,C&&r&&(m.map=r[f].map),s.flow||(h=m.ctx=m.ctx||{},u=s.parents=h.parentTags=R&&O(h.parentTags,R.parentTags)||{},c&&(u[c.tagName]=c),u[s.tagName]=h.tag=s);if(!(s._er=o)){for(i(s,r[0]),s.rendering={},f=0;g>f;f++)m=s.tagCtx=r[f],k=m.props,y=s.cvtArgs(),(b=k.dataMap||C)&&(y.length||k.dataMap)&&(_=m.map,(!_||_.src!==y[0]||a)&&(_&&_.src&&_.unmap(),_=m.map=b.map(y[0],k,void 0,!s._.bnd)),y=[_.tgt]),s.ctx=m.ctx,f||(x&&(j=s.template,s.init(m,A,s.ctx),x=void 0),A&&(A.attr=s.attr=A.attr||s.attr),p=s.attr,s._.noVws=p&&p!==Ve),v=void 0,s.render&&(v=s.render.apply(s,y)),y.length||(y=[t]),void 0===v&&(v=m.render(y[0],!0)||(a?void 0:"")),T=T?T+(v||""):v;s.rendering=void 0}return s.tagCtx=r[0],s.ctx=s.tagCtx.ctx,s._.noVws&&s._.inline&&(T="text"===p?ee.html(T):""),M&&t._.onRender?t._.onRender(T,t,s):T}function b(e,t,n,r,i,a,o,s){var d,l,u,p=this,f="array"===t;p.content=s,p.views=f?[]:{},p.parent=n,p.type=t||"top",p.data=r,p.tmpl=i,u=p._={key:0,useKey:f?0:1,id:""+Ae++,onRender:o,bnds:{}},p.linked=!!o,n?(d=n.views,l=n._,l.useKey?(d[u.key="_"+l.useKey++]=p,p.index=Ee,p.getIndex=c):d.length===(u.key=p.index=a)?d.push(p):d.splice(a,0,p),p.ctx=e||n.ctx):p.ctx=e}function _(e){var t,n,r,i,a,o,s;for(t in Ue)if(a=Ue[t],(o=a.compile)&&(n=e[t+"s"]))for(r in n)i=n[r]=o(r,n[r],e,0),i._is=t,i&&(s=re.onStore[t])&&s(r,i,o)}function y(e,t,n){function i(){var t=this;t._={inline:!0,unlinked:!0},t.tagName=e}var a,o,s,d=new re._tg;if(W(t)?t={depends:t.depends,render:t}:""+t===t&&(t={template:t}),o=t.baseTag){t.flow=!!t.flow,t.baseTag=o=""+o===o?n&&n.tags[o]||ne[o]:o,d=l(d,o);for(s in t)d[s]=r(o[s],t[s])}else d=l(d,t);return void 0!==(a=d.template)&&(d.template=""+a===a?Y[a]||Y(a):a),d.init!==!1&&((i.prototype=d).constructor=d._ctr=i),n&&(d._parentTmpl=n),d}function k(e){return this.base.apply(this,e)}function j(e,n,r,i){function a(n){var a,s;if(""+n===n||n.nodeType>0&&(o=n)){if(!o)if(/^\.\/[^\\:*?"<>]*$/.test(n))(s=Y[e=e||n])?n=s:o=document.getElementById(n);else if(t.fn&&!Z.test(n))try{o=t(document).find(n)[0]}catch(d){}o&&(i?n=o.innerHTML:(a=o.getAttribute(Me),a?a!==Ne?(n=Y[a],delete Y[a]):t.fn&&(n=t.data(o)[Ne]):(e=e||(t.fn?Ne:n),n=j(e,o.innerHTML,r,i)),n.tmplName=e=e||a,e!==Ne&&(Y[e]=n),o.setAttribute(Me,e),t.fn&&t.data(o,Ne,n))),o=void 0}else n.fn||(n=void 0);return n}var o,s,d=n=n||"";return 0===i&&(i=void 0,d=a(d)),i=i||(n.markup?n:{}),i.tmplName=e,r&&(i._parentTmpl=r),!d&&n.markup&&(d=a(n.markup))&&d.fn&&(d=d.markup),void 0!==d?(d.fn||n.fn?d.fn&&(s=d):(n=T(d,i),S(d.replace(xe,"\\$&"),n)),s||(_(i),s=l(function(){return n.render.apply(n,arguments)},n)),e&&!r&&e!==Ne&&(Se[e]=s),s):void 0}function C(e){function t(t,n){this.tgt=e.getTgt(t,n)}return W(e)&&(e={getTgt:e}),e.baseMap&&(e=l(l({},e.baseMap),e)),e.map=function(e,n){return new t(e,n)},e}function T(e,n){var r,i=ae._wm||{},a=l({tmpls:[],links:{},bnds:[],_is:"template",render:V},n);return a.markup=e,n.htmlTag||(r=ye.exec(e),a.htmlTag=r?r[1].toLowerCase():""),r=i[a.htmlTag],r&&r!==i.div&&(a.markup=t.trim(a.markup)),a}function A(e,t){function n(i,a,o){var s,d,l,u;if(i&&typeof i===$e&&!i.nodeType&&!i.markup&&!i.getTgt){for(l in i)n(l,i[l],a);return G}return void 0===a&&(a=i,i=void 0),i&&""+i!==i&&(o=a,a=i,i=void 0),u=o?o[r]=o[r]||{}:n,d=t.compile,null===a?i&&delete u[i]:(a=d?d.call(u,i,a,o,0):a,i&&(u[i]=a)),d&&a&&(a._is=e),a&&(s=re.onStore[e])&&s(i,a,d),a}var r=e+"s";G[r]=n}function R(e){oe[e]=function(t){return arguments.length?(ie[e]=t,oe):ie[e]}}function V(e,t,n,r,i,a){var o,s,d,l,u,p,c,f,g=r,v="";if(t===!0?(n=t,t=void 0):typeof t!==$e&&(t=void 0),(d=this.tag)?(u=this,g=g||u.view,l=g.getTmpl(d.template||u.tmpl),arguments.length||(e=g)):l=this,l){if(!g&&e&&"view"===e._is&&(g=e),g&&e===g&&(e=g.data),p=!g,fe=fe||p,g||((t=t||{}).root=e),!fe||ae.useViews||l.useViews||g&&g!==z)v=$(l,e,t,n,g,i,a,d);else{if(g?(c=g.data,f=g.index,g.index=Ee):(g=z,g.data=e,g.ctx=t),X(e)&&!n)for(o=0,s=e.length;s>o;o++)g.index=o,g.data=e[o],v+=l.fn(e[o],g,re);else g.data=e,v+=l.fn(e,g,re);g.data=c,g.index=f}p&&(fe=void 0)}return v}function $(e,t,n,r,i,a,o,s){function d(e){_=l({},n),_[x]=e}var u,p,c,f,g,v,m,h,w,x,_,y,k="";if(s&&(w=s.tagName,y=s.tagCtx,n=n?O(n,s.ctx):s.ctx,e===i.content?m=e!==i.ctx._wrp?i.ctx._wrp:void 0:e!==y.content?e===s.template?(m=y.tmpl,n._wrp=y.content):m=y.content||i.content:m=i.content,y.props.link===!1&&(n=n||{},n.link=!1),(x=y.props.itemVar)&&("~"!==x.charAt(0)&&E("Use itemVar='~myItem'"),x=x.slice(1))),i&&(o=o||i._.onRender,n=O(n,i.ctx)),a===!0&&(v=!0,a=0),o&&(n&&n.link===!1||s&&s._.noVws)&&(o=void 0),h=o,o===!0&&(h=void 0,o=i._.onRender),n=e.helpers?O(e.helpers,n):n,_=n,X(t)&&!r)for(c=v?i:void 0!==a&&i||new b(n,"array",i,t,e,a,o),i&&i._.useKey&&(c._.bnd=!s||s._.bnd&&s),x&&(c.it=x),x=c.it,u=0,p=t.length;p>u;u++)x&&d(t[u]),f=new b(_,"item",c,t[u],e,(a||0)+u,o,m),g=e.fn(t[u],f,re),k+=c._.onRender?c._.onRender(g,f):g;else x&&d(t),c=v?i:new b(_,w||"data",i,t,e,a,o,m),s&&!s.flow&&(c.tag=s),k+=e.fn(t,c,re);return h?h(k,c):k}function M(e,t,n){var r=void 0!==n?W(n)?n.call(t.data,e,t):n||"":"{Error: "+e.message+"}";return ie.onError&&void 0!==(n=ie.onError.call(t.data,e,n&&r,t))&&(r=n),t&&!t.linkCtx&&t.linked?ee.html(r):r}function N(e){throw new re.Err(e)}function E(e){N("Syntax error\n"+e)}function S(e,t,n,r,i){function a(t){t-=f,t&&v.push(e.substr(f,t).replace(he,"\\n"))}function o(t,n){t&&(t+="}}",E((n?"{{"+n+"}} block has {{/"+t+" without {{"+t:"Unmatched or missing {{/"+t)+", in template:\n"+e))}function s(s,d,c,h,w,x,b,_,y,k,j,C){(b&&d||y&&!c||_&&":"===_.slice(-1)||k)&&E(s),x&&(w=":",h=Ve),y=y||n&&!i;var T=(d||n)&&[[]],A="",R="",V="",$="",M="",N="",S="",F="",U=!y&&!w;c=c||(_=_||"#data",w),a(C),f=C+s.length,b?p&&v.push(["*","\n"+_.replace(/^:/,"ret+= ").replace(we,"$1")+";\n"]):c?("else"===c&&(_e.test(_)&&E('for "{{else if expr}}" use "{{else expr}}"'),T=m[7]&&[[]],m[8]=e.substring(m[8],C),m=g.pop(),v=m[2],U=!0),_&&J(_.replace(he," "),T,t).replace(be,function(e,t,n,r,i,a,o,s){return r="'"+i+"':",o?(R+=a+",",$+="'"+s+"',"):n?(V+=r+a+",",N+=r+"'"+s+"',"):t?S+=a:("trigger"===i&&(F+=a),A+=r+a+",",M+=r+"'"+s+"',",u=u||Ce.test(i)),""}).slice(0,-1),T&&T[0]&&T.pop(),l=[c,h||!!r||u||"",U&&[],I($||(":"===c?"'#data',":""),M,N),I(R||(":"===c?"data,":""),A,V),S,F,T||0],v.push(l),U&&(g.push(m),m=l,m[8]=f)):j&&(o(j!==m[0]&&"else"!==m[0]&&j,m[0]),m[8]=e.substring(m[8],C),m=g.pop()),o(!m&&j),v=m[2]}var d,l,u,p=ie.allowCode||t&&t.allowCode||oe.allowCode===!0,c=[],f=0,g=[],v=c,m=[,,c];return p&&(t.allowCode=p),n&&(void 0!==r&&(e=e.slice(0,-r.length-2)+ue),e=se+e+ue),o(g[0]&&g[0][2].pop()[0]),e.replace(P,s),a(e.length),(f=c[c.length-1])&&o(""+f!==f&&+f[8]===f[8]&&f[0]),n?(d=K(c,e,n),F(d,[c[0][7]])):d=K(c,t),d}function F(e,t){var n,r,i=0,a=t.length;for(e.deps=[];a>i;i++){r=t[i];for(n in r)"_jsvto"!==n&&r[n].length&&(e.deps=e.deps.concat(r[n]))}e.paths=r}function I(e,t,n){return[e.slice(0,-1),t.slice(0,-1),n.slice(0,-1)]}function U(e,t){return"\n	"+(t?t+":{":"")+"args:["+e[0]+"]"+(e[1]||!t?",\n	props:{"+e[1]+"}":"")+(e[2]?",\n	ctx:{"+e[2]+"}":"")}function J(e,t,n){function r(r,h,w,x,b,_,y,k,j,C,T,A,R,V,$,M,N,F,I,U){function J(e,n,r,o,s,d,p,c){var f="."===r;if(r&&(b=b.slice(n.length),/^\.?constructor$/.test(c||b)&&E(e),f||(e=(o?'view.hlp("'+o+'")':s?"view":"data")+(c?(d?"."+d:o?"":s?"":"."+r)+(p||""):(c=o?"":s?d||"":r,"")),e+=c?"."+c:"",e=n+("view.data"===e.slice(0,9)?e.slice(5):e)),l)){if(q="linkTo"===i?a=t._jsvto=t._jsvto||[]:u.bd,B=f&&q[q.length-1]){if(B._jsv){for(;B.sb;)B=B.sb;B.bnd&&(b="^"+b.slice(1)),B.sb=b,B.bnd=B.bnd||"^"===b.charAt(0)}}else q.push(b);m[g]=I+(f?1:0)}return e}x=l&&x,x&&!k&&(b=x+b),_=_||"",w=w||h||A,b=b||j,C=C||N||"";var K,O,q,B,L;if(!y||d||s){if(l&&M&&!d&&!s&&(!i||o||a)&&(K=m[g-1],U.length-1>I-(K||0))){if(K=U.slice(K,I+r.length),O!==!0)if(q=a||p[g-1].bd,B=q[q.length-1],B&&B.prm){for(;B.sb&&B.sb.prm;)B=B.sb;L=B.sb={path:B.sb,bnd:B.bnd}}else q.push(L={path:q.pop()});M=de+":"+K+" onerror=''"+le,O=f[M],O||(f[M]=!0,f[M]=O=S(M,n,!0)),O!==!0&&L&&(L._jsv=O,L.prm=u.bd,L.bnd=L.bnd||L.path&&L.path.indexOf("^")>=0)}return d?(d=!R,d?r:A+'"'):s?(s=!V,s?r:A+'"'):(w?(m[g]=I++,u=p[++g]={bd:[]},w):"")+(F?g?"":(c=U.slice(c,I),(i?(i=o=a=!1,"\b"):"\b,")+c+(c=I+r.length,l&&t.push(u.bd=[]),"\b")):k?(g&&E(e),l&&t.pop(),i=b,o=x,c=I+r.length,x&&(l=u.bd=t[i]=[]),b+":"):b?b.split("^").join(".").replace(ve,J)+(C?(u=p[++g]={bd:[]},v[g]=!0,C):_):_?_:$?(v[g]=!1,u=p[--g],$+(C?(u=p[++g],v[g]=!0,C):"")):T?(v[g]||E(e),","):h?"":(d=R,s=V,'"'))}E(e)}var i,a,o,s,d,l=t&&t[0],u={bd:l},p={0:u},c=0,f=n?n.links:l&&(l.links=l.links||{}),g=0,v={},m={},h=(e+(n?" ":"")).replace(me,r);return!g&&h||E(e)}function K(e,t,n){var r,i,a,o,s,d,l,u,p,c,f,g,v,m,h,w,x,b,_,y,k,j,C,A,R,V,$,M,N,S,I=0,J=ae.useViews||t.useViews||t.tags||t.templates||t.helpers||t.converters,O="",q={},B=e.length;for(""+t===t?(b=n?'data-link="'+t.replace(he," ").slice(1,-1)+'"':t,t=0):(b=t.tmplName||"unnamed",t.allowCode&&(q.allowCode=!0),t.debug&&(q.debug=!0),f=t.bnds,x=t.tmpls),r=0;B>r;r++)if(i=e[r],""+i===i)O+='\n+"'+i+'"';else if(a=i[0],"*"===a)O+=";\n"+i[1]+"\nret=ret";else{if(o=i[1],k=!n&&i[2],s=U(i[3],"params")+"},"+U(v=i[4]),M=i[5],S=i[6],j=i[8]&&i[8].replace(we,"$1"),(R="else"===a)?g&&g.push(i[7]):(I=0,f&&(g=i[7])&&(g=[g],I=f.push(1))),J=J||v[1]||v[2]||g||/view.(?!index)/.test(v[0]),(V=":"===a)?(o&&(a=o===Ve?">":o+a),S=i[6]||ie.trigger):(k&&(_=T(j,q),_.tmplName=b+"/"+a,_.useViews=_.useViews||J,K(k,_),J=_.useViews,x.push(_)),R||(y=a,J=J||a&&(!ne[a]||!ne[a].flow),A=O,O=""),C=e[r+1],C=C&&"else"===C[0]),N=M?";\ntry{\nret+=":"\n+",m="",h="",V&&(g||S||o&&o!==Ve)){if($="return {"+s+"};",w='c("'+o+'",view,',$=new Function("data,view,j,u"," // "+b+" "+I+" "+a+"\n"+$),$._er=M,m=w+I+",",h=")",$._tag=a,n)return $;F($,g),c=!0}if(O+=V?(n?(M?"\ntry{\n":"")+"return ":N)+(c?(c=void 0,J=p=!0,w+(g?(f[I-1]=$,I):"{"+s+"}")+")"):">"===a?(l=!0,"h("+v[0]+")"):(u=!0,"((v="+v[0]+')!=null?v:"")')):(d=!0,"\n{view:view,tmpl:"+(k?x.length:"0")+","+s+"},"),y&&!C){if(O="["+O.slice(0,-1)+"]",w='t("'+y+'",view,this,',n||g){if(O=new Function("data,view,j,u"," // "+b+" "+I+" "+y+"\nreturn "+O+";"),O._er=M,O._tag=y,g&&F(f[I-1]=O,g),n)return O;m=w+I+",undefined,",h=")"}O=A+N+w+(I||O)+")",g=0,y=0}M&&(J=!0,O+=";\n}catch(e){ret"+(n?"urn ":"+=")+m+"j._err(e,view,"+M+")"+h+";}\n"+(n?"":"ret=ret"))}O="// "+b+"\nvar v"+(d?",t=j._tag":"")+(p?",c=j._cnvt":"")+(l?",h=j._html":"")+(n?";\n":',ret=""\n')+(q.debug?"debugger;":"")+O+(n?"\n":";\nreturn ret;"),ie.debugMode!==!1&&(O="try {\n"+O+"\n}catch(e){\nreturn j._err(e, view);\n}");try{O=new Function("data,view,j,u",O)}catch(L){E("Compiled template code:\n\n"+O+'\n: "'+L.message+'"')}return t&&(t.fn=O,t.useViews=!!J),O}function O(e,t){return e&&e!==t?t?l(l({},t),e):e:t&&l({},t)}function q(e){return Re[e]||(Re[e]="&#"+e.charCodeAt(0)+";")}function B(e){var t,n,r=[];if(typeof e===$e)for(t in e)n=e[t],n&&n.toJSON&&!n.toJSON()||W(n)||r.push({key:t,prop:n});return r}function L(e,n,r){var i=this.jquery&&(this[0]||N('Unknown template: "'+this.selector+'"')),a=i.getAttribute(Me);return V.call(a?t.data(i)[Ne]:Y(i),e,n,r)}function Q(e){return void 0!=e?je.test(e)&&(""+e).replace(Te,q)||e:""}var H=t===!1;t=t&&t.fn?t:e.jQuery;var D,P,Z,z,G,W,X,Y,ee,te,ne,re,ie,ae,oe,se,de,le,ue,pe,ce,fe,ge="v0.9.74",ve=/^(!*?)(?:null|true|false|\d[\d.]*|([\w$]+|\.|~([\w$]+)|#(view|([\w$]+))?)([\w$.^]*?)(?:[.[^]([\w$]+)\]?)?)$/g,me=/(\()(?=\s*\()|(?:([([])\s*)?(?:(\^?)(!*?[#~]?[\w$.^]+)?\s*((\+\+|--)|\+|-|&&|\|\||===|!==|==|!=|<=|>=|[<>%*:?\/]|(=))\s*|(!*?[#~]?[\w$.^]+)([([])?)|(,\s*)|(\(?)\\?(?:(')|("))|(?:\s*(([)\]])(?=\s*[.^]|\s*$|[^([])|[)\]])([([]?))|(\s+)/g,he=/[ \t]*(\r\n|\n|\r)/g,we=/\\(['"])/g,xe=/['"\\]/g,be=/(?:\x08|^)(onerror:)?(?:(~?)(([\w$_\.]+):)?([^\x08]+))\x08(,)?([^\x08]+)/gi,_e=/^if\s/,ye=/<(\w+)[>\s]/,ke=/[\x00`><"'&]/g,je=/[\x00`><\"'&]/,Ce=/^on[A-Z]|^convert(Back)?$/,Te=ke,Ae=0,Re={"&":"&amp;","<":"&lt;",">":"&gt;","\x00":"&#0;","'":"&#39;",'"':"&#34;","`":"&#96;"},Ve="html",$e="object",Me="data-jsv-tmpl",Ne="jsvTmpl",Ee="For #index in nested block use #getIndex().",Se={},Fe=e.jsrender,Ie=Fe&&t&&!t.render,Ue={template:{compile:j},tag:{compile:y},helper:{},converter:{}};if(G={jsviews:ge,sub:{View:b,Err:d,tmplFn:S,parse:J,extend:l,extendCtx:O,syntaxErr:E,onStore:{},addSetting:R,settings:{allowCode:!1},advSet:o,_ths:i,_tg:function(){},_cnvt:m,_tag:x,_er:N,_err:M,_html:Q},settings:{delimiters:u,advanced:function(e){return e?(l(ae,e),re.advSet(),oe):ae}},map:C},(d.prototype=new Error).constructor=d,c.depends=function(){return[this.get("item"),"index"]},f.depends="index",b.prototype={get:p,getIndex:f,getRsc:w,getTmpl:v,hlp:g,_is:"view"},!(Fe||t&&t.render)){for(D in Ue)A(D,Ue[D]);Y=G.templates,ee=G.converters,te=G.helpers,ne=G.tags,re=G.sub,oe=G.settings,re._tg.prototype={baseApply:k,cvtArgs:h},z=re.topView=new b,t?(t.fn.render=L,t.observable&&(l(re,t.views.sub),G.map=t.views.map)):(t={},H&&(e.jsrender=t),t.renderFile=t.__express=t.compile=function(){throw"Node.js: use npm jsrender, or jsrender-node.js"},t.isFunction=function(e){return"function"==typeof e},t.isArray=Array.isArray||function(e){return"[object Array]"==={}.toString.call(e)},re._jq=function(e){e!==t&&(l(e,t),t=e,t.fn.render=L,delete t.jsrender)},t.jsrender=ge),ie=re.settings,ie.allowCode=!1,W=t.isFunction,X=t.isArray,t.render=Se,t.views=G,t.templates=Y=G.templates;for(ce in ie)R(ce);(oe.debugMode=function(e){return void 0===e?ie.debugMode:(ie.debugMode=e,ie.onError=e+""===e?new Function("","return '"+e+"';"):W(e)?e:void 0,oe)})(!1),ae=ie.advanced={useViews:!1,_jsv:!1},ne({"if":{render:function(e){var t=this,n=t.tagCtx,r=t.rendering.done||!e&&(arguments.length||!n.index)?"":(t.rendering.done=!0,t.selected=n.index,n.render(n.view,!0));return r},flow:!0},"for":{render:function(e){var t,n=!arguments.length,r=this,i=r.tagCtx,a="",o=0;return r.rendering.done||(t=n?i.view.data:e,void 0!==t&&(a+=i.render(t,n),o+=X(t)?t.length:1),(r.rendering.done=o)&&(r.selected=i.index)),a},flow:!0},props:{baseTag:"for",dataMap:C(B),flow:!0},include:{flow:!0},"*":{render:a,flow:!0},":*":{render:a,flow:!0},dbg:te.dbg=ee.dbg=s}),ee({html:Q,attr:Q,url:function(e){return void 0!=e?encodeURI(""+e):null===e?e:""}}),oe.delimiters("{{","}}","^")}return Ie&&Fe.views.sub._jq(t),t||Fe},this);
     //# sourceMappingURL=jsrender.min.js.map
    \ No newline at end of file
    
  • jsrender.min.js.map+1 1 modified
  • jsrender-node.js+133 97 modified
    @@ -1,4 +1,4 @@
    -/*! JsRender v0.9.73 (Beta): http://jsviews.com/#jsrender */
    +/*! JsRender v0.9.74 (Beta): http://jsviews.com/#jsrender */
     /*! **VERSION FOR NODE.JS** (For WEB see http://jsviews.com/download/jsrender.js) */
     /*
      * Best-of-breed templating in browser or on Node.js.
    @@ -11,25 +11,22 @@
     
     //jshint -W018, -W041
     
    -(function() {
    +(function(global) {
     "use strict";
     if (typeof exports !== 'object' ) {
     	throw "Outside Node.js use //jsviews.com/download/jsrender.js";
     }
     
     //========================== Top-level vars ==========================
     
    -var versionNumber = "v0.9.73",
    +var versionNumber = "v0.9.74",
     
     	// global var is the this object, which is window when running in the usual browser environment
    -	global = (0, eval)('this'), // jshint ignore:line
     
     	$, jsvStoreName, rTag, rTmplString, topView, $views,
     
     //TODO	tmplFnsCache = {},
    -	$isFunction, $isArray, $templates, $converters, $helpers, $tags, $sub, $viewsSettings,
    -
    -	delimOpenChar0 = "{", delimOpenChar1 = "{", delimCloseChar0 = "}", delimCloseChar1 = "}", linkChar = "^",
    +	$isFunction, $isArray, $templates, $converters, $helpers, $tags, $sub, $subSettings, $subSettingsAdvanced, $viewsSettings, delimOpenChar0, delimOpenChar1, delimCloseChar0, delimCloseChar1, linkChar, setting, baseOnError,
     
     	rPath = /^(!*?)(?:null|true|false|\d[\d.]*|([\w$]+|\.|~([\w$]+)|#(view|([\w$]+))?)([\w$.^]*?)(?:[.[^]([\w$]+)\]?)?)$/g,
     	//        not                               object     helper    view  viewProperty pathTokens      leafToken
    @@ -80,13 +77,6 @@ var versionNumber = "v0.9.73",
     // views object ($.views if jQuery is loaded, jsrender.views if no jQuery, e.g. in Node.js)
     	$views = {
     		jsviews: versionNumber,
    -		settings: function(settings) {
    -			$extend($viewsSettings, settings);
    -			dbgMode($viewsSettings._dbgMode);
    -			if ($viewsSettings.jsv) {
    -				$viewsSettings.jsv();
    -			}
    -		},
     		sub: {
     			// subscription, e.g. JsViews integration
     			View: View,
    @@ -97,13 +87,32 @@ var versionNumber = "v0.9.73",
     			extendCtx: extendCtx,
     			syntaxErr: syntaxError,
     			onStore: {},
    +			addSetting: addSetting,
    +			settings: {
    +				allowCode: false
    +			},
    +			advSet: noop, // Update advanced settings
     			_ths: tagHandlersFromProps,
    -			_tg: function() {} // Constructor for tagDef
    +			_tg: function() {}, // Constructor for tagDef
    +			_cnvt: convertVal,
    +			_tag: renderTag,
    +			_er: error,
    +			_err: onRenderError,
    +			_html: htmlEncode
    +		},
    +		settings: {
    +			delimiters: $viewsDelimiters,
    +			advanced: function(value) {
    +				return value
    +					? (
    +							$extend($subSettingsAdvanced, value),
    +							$sub.advSet(),
    +							$viewsSettings
    +						)
    +						: $subSettingsAdvanced;
    +				}
     		},
    -		map: dataMap, // If jsObservable loaded first, use that definition of dataMap
    -		_cnvt: convertVal,
    -		_tag: renderTag,
    -		_err: error
    +		map: dataMap // If jsObservable loaded first, use that definition of dataMap
     	};
     
     function getDerivedMethod(baseMethod, method) {
    @@ -155,20 +164,16 @@ function noop() {
     }
     
     function dbgBreak(val) {
    -	// Usage examples: {{dbg:...}}, {{:~dbg(...)}}, {{for ... onAfterLink=~dbg}}, {{dbg .../}} etc.
    -	// To break here, stop on caught exceptions.
    +	// Usage examples: {{dbg:...}}, {{:~dbg(...)}}, {{dbg .../}}, {^{for ... onAfterLink=~dbg}} etc.
     	try {
     		debugger;
    -		throw "dbg breakpoint";
    +		console.log("JsRender dbg breakpoint: " + val);
    +		throw "dbg breakpoint"; // To break here, stop on caught exceptions.
     	}
     	catch (e) {}
     	return this.base ? this.baseApply(arguments) : val;
     }
     
    -function dbgMode(debugMode) {
    -	$viewsSettings._dbgMode = debugMode !== false; // Pass in false to unset. Otherwise sets to true.
    -}
    -
     function JsViewsError(message) {
     	// Error exception type for JsViews/JsRender
     	// Override of $.views.sub.Error is possible
    @@ -191,36 +196,39 @@ function $extend(target, source) {
     //===================
     // views.delimiters
     //===================
    +
     function $viewsDelimiters(openChars, closeChars, link) {
     	// Set the tag opening and closing delimiters and 'link' character. Default is "{{", "}}" and "^"
     	// openChars, closeChars: opening and closing strings, each with two characters
    -
    -	if (this !== 0 || openChars) {
    -		delimOpenChar0 = openChars ? openChars.charAt(0) : delimOpenChar0; // Escape the characters - since they could be regex special characters
    -		delimOpenChar1 = openChars ? openChars.charAt(1) : delimOpenChar1;
    -		delimCloseChar0 = closeChars ? closeChars.charAt(0) : delimCloseChar0;
    -		delimCloseChar1 = closeChars ? closeChars.charAt(1) : delimCloseChar1;
    -		linkChar = link || linkChar;
    -		openChars = "\\" + delimOpenChar0 + "(\\" + linkChar + ")?\\" + delimOpenChar1; // Default is "{^{"
    -		closeChars = "\\" + delimCloseChar0 + "\\" + delimCloseChar1;                   // Default is "}}"
    -		// Build regex with new delimiters
    -		//          [tag    (followed by / space or })  or cvtr+colon or html or code] followed by space+params then convertBack?
    -		rTag = "(?:(\\w+(?=[\\/\\s\\" + delimCloseChar0 + "]))|(\\w+)?(:)|(>)|(\\*))\\s*((?:[^\\"
    -			+ delimCloseChar0 + "]|\\" + delimCloseChar0 + "(?!\\" + delimCloseChar1 + "))*?)";
    -
    -		// make rTag available to JsViews (or other components) for parsing binding expressions
    -		$sub.rTag = "(?:" + rTag + ")";
    -		//                        { ^? {   tag+params slash?  or closingTag                                                   or comment
    -		rTag = new RegExp("(?:" + openChars + rTag + "(\\/)?|\\" + delimOpenChar0 + "(\\" + linkChar + ")?\\" + delimOpenChar1 + "(?:(?:\\/(\\w+))\\s*|!--[\\s\\S]*?--))" + closeChars, "g");
    -
    -		// Default:  bind     tagName         cvt   cln html code    params            slash   bind2         closeBlk  comment
    -		//      /(?:{(\^)?{(?:(\w+(?=[\/\s}]))|(\w+)?(:)|(>)|(\*))\s*((?:[^}]|}(?!}))*?)(\/)?|{(\^)?{(?:(?:\/(\w+))\s*|!--[\s\S]*?--))}}
    -
    -		rTmplString = new RegExp("<.*>|([^\\\\]|^)[{}]|" + openChars + ".*" + closeChars);
    -		// rTmplString looks for html tags or { or } char not preceded by \\, or JsRender tags {{xxx}}. Each of these strings are considered
    -		// NOT to be jQuery selectors
    -	}
    -	return [delimOpenChar0, delimOpenChar1, delimCloseChar0, delimCloseChar1, linkChar];
    +	if (!openChars) {
    +		return $subSettings.delimiters;
    +	}
    +
    +	$subSettings.delimiters = [openChars, closeChars, linkChar = link ? link.charAt(0) : linkChar];
    +
    +	delimOpenChar0 = openChars.charAt(0); // Escape the characters - since they could be regex special characters
    +	delimOpenChar1 = openChars.charAt(1);
    +	delimCloseChar0 = closeChars.charAt(0);
    +	delimCloseChar1 = closeChars.charAt(1);
    +	openChars = "\\" + delimOpenChar0 + "(\\" + linkChar + ")?\\" + delimOpenChar1; // Default is "{^{"
    +	closeChars = "\\" + delimCloseChar0 + "\\" + delimCloseChar1;                   // Default is "}}"
    +	// Build regex with new delimiters
    +	//          [tag    (followed by / space or })  or cvtr+colon or html or code] followed by space+params then convertBack?
    +	rTag = "(?:(\\w+(?=[\\/\\s\\" + delimCloseChar0 + "]))|(\\w+)?(:)|(>)|(\\*))\\s*((?:[^\\"
    +		+ delimCloseChar0 + "]|\\" + delimCloseChar0 + "(?!\\" + delimCloseChar1 + "))*?)";
    +
    +	// make rTag available to JsViews (or other components) for parsing binding expressions
    +	$sub.rTag = "(?:" + rTag + ")";
    +	//                        { ^? {   tag+params slash?  or closingTag                                                   or comment
    +	rTag = new RegExp("(?:" + openChars + rTag + "(\\/)?|\\" + delimOpenChar0 + "(\\" + linkChar + ")?\\" + delimOpenChar1 + "(?:(?:\\/(\\w+))\\s*|!--[\\s\\S]*?--))" + closeChars, "g");
    +
    +	// Default:  bind     tagName         cvt   cln html code    params            slash   bind2         closeBlk  comment
    +	//      /(?:{(\^)?{(?:(\w+(?=[\/\s}]))|(\w+)?(:)|(>)|(\*))\s*((?:[^}]|}(?!}))*?)(\/)?|{(\^)?{(?:(?:\/(\w+))\s*|!--[\s\S]*?--))}}
    +
    +	rTmplString = new RegExp("<.*>|([^\\\\]|^)[{}]|" + openChars + ".*" + closeChars);
    +	// rTmplString looks for html tags or { or } char not preceded by \\, or JsRender tags {{xxx}}. Each of these strings are considered
    +	// NOT to be jQuery selectors
    +	return $viewsSettings;
     }
     
     //=========
    @@ -342,7 +350,7 @@ function convertVal(converter, view, tagCtx, onError) {
     	if (onError !== undefined) {
     		tagCtx = onError = {props: {}, args: [onError]};
     	} else if (boundTag) {
    -		tagCtx = boundTag(view.data, view, $views);
    +		tagCtx = boundTag(view.data, view, $sub);
     	}
     
     	value = tagCtx.args[0];
    @@ -450,7 +458,7 @@ function renderTag(tagName, parentView, tmpl, tagCtxs, isUpdate, onError) {
     		ret += onError;
     		tagCtxs = onError = [{props: {}, args: []}];
     	} else if (boundTag) {
    -		tagCtxs = boundTag(parentView.data, parentView, $views);
    +		tagCtxs = boundTag(parentView.data, parentView, $sub);
     	}
     
     	l = tagCtxs.length;
    @@ -711,7 +719,7 @@ function compileTag(name, tagDef, parentTmpl) {
     		compiledDef.template = "" + tmpl === tmpl ? ($templates[tmpl] || $templates(tmpl)) : tmpl;
     	}
     	if (compiledDef.init !== false) {
    -		// Set init: false on tagDef if you want to provide just a render method, or render and template, but no constuctor or prototype.
    +		// Set init: false on tagDef if you want to provide just a render method, or render and template, but no constructor or prototype.
     		// so equivalent to setting tag to render function, except you can also provide a template.
     		(Tag.prototype = compiledDef).constructor = compiledDef._ctr = Tag;
     	}
    @@ -842,7 +850,7 @@ function dataMap(mapDef) {
     function tmplObject(markup, options) {
     	// Template object constructor
     	var htmlTag,
    -		wrapMap = $viewsSettings.wrapMap || {}, // Only used in JsViews. Otherwise empty: {}
    +		wrapMap = $subSettingsAdvanced._wm || {}, // Only used in JsViews. Otherwise empty: {}
     		tmpl = $extend(
     			{
     				tmpls: [],
    @@ -880,7 +888,6 @@ function registerStore(storeName, storeSettings) {
     		// or $.views.things(name, item[, parentTmpl])
     
     		var onStore, compile, itemName, thisStore;
    -
     		if (name && typeof name === OBJECT && !name.nodeType && !name.markup && !name.getTgt) {
     			// Call to $.views.things(items[, parentTmpl]),
     
    @@ -909,7 +916,7 @@ function registerStore(storeName, storeSettings) {
     				delete thisStore[name];
     			}
     		} else {
    -			item = compile ? compile(name, item, parentTmpl, 0) : item;
    +			item = compile ? compile.call(thisStore, name, item, parentTmpl, 0) : item;
     			if (name) {
     				thisStore[name] = item;
     			}
    @@ -929,6 +936,14 @@ function registerStore(storeName, storeSettings) {
     	$views[storeNames] = theStore;
     }
     
    +function addSetting(st) {
    +	$viewsSettings[st] = function(value) {
    +		return arguments.length
    +			? ($subSettings[st] = value, $viewsSettings)
    +			: $subSettings[st];
    +	};
    +}
    +
     //==============
     // renderContent
     //==============
    @@ -976,7 +991,7 @@ function renderContent(data, context, noIteration, parentView, key, onRender) {
     		if (!view) {
     			(context = context || {}).root = data; // Provide ~root as shortcut to top-level data.
     		}
    -		if (!isRenderCall || $viewsSettings.useViews || tmpl.useViews || view && view !== topView) {
    +		if (!isRenderCall || $subSettingsAdvanced.useViews || tmpl.useViews || view && view !== topView) {
     			result = renderWithViews(tmpl, data, context, noIteration, view, key, onRender, tag);
     		} else {
     			if (view) { // In a block
    @@ -994,11 +1009,11 @@ function renderContent(data, context, noIteration, parentView, key, onRender) {
     				for (i = 0, l = data.length; i < l; i++) {
     					view.index = i;
     					view.data = data[i];
    -					result += tmpl.fn(data[i], view, $views);
    +					result += tmpl.fn(data[i], view, $sub);
     				}
     			} else {
     				view.data = data;
    -				result += tmpl.fn(data, view, $views);
    +				result += tmpl.fn(data, view, $sub);
     			}
     			view.data = prevData;
     			view.index = prevIndex;
    @@ -1030,7 +1045,7 @@ function renderWithViews(tmpl, data, context, noIteration, view, key, onRender,
     
     		if (tmpl === view.content) { // {{xxx tmpl=#content}}
     			contentTmpl = tmpl !== view.ctx._wrp // We are rendering the #content
    -				?  view.ctx._wrp // #content was the tagCtx.props.tmpl wrapper of the block content - so within this view, #content will now be the view.ctx._wrp block content
    +				? view.ctx._wrp // #content was the tagCtx.props.tmpl wrapper of the block content - so within this view, #content will now be the view.ctx._wrp block content
     				: undefined; // #content was the view.ctx._wrp block content - so within this view, there is no longer any #content to wrap.
     		} else if (tmpl !== tagCtx.content) {
     			if (tmpl === tag.template) { // Rendering {{tag}} tag.template, replacing block content.
    @@ -1108,7 +1123,7 @@ function renderWithViews(tmpl, data, context, noIteration, view, key, onRender,
     			}
     			childView = new View(newCtx, "item", newView, data[i], tmpl, (key || 0) + i, onRender, contentTmpl);
     
    -			itemResult = tmpl.fn(data[i], childView, $views);
    +			itemResult = tmpl.fn(data[i], childView, $sub);
     			result += newView._.onRender ? newView._.onRender(itemResult, childView) : itemResult;
     		}
     	} else {
    @@ -1121,7 +1136,7 @@ function renderWithViews(tmpl, data, context, noIteration, view, key, onRender,
     		if (tag && !tag.flow) {
     			newView.tag = tag;
     		}
    -		result += tmpl.fn(data, newView, $views);
    +		result += tmpl.fn(data, newView, $sub);
     	}
     	return outerOnRender ? outerOnRender(result, newView) : result;
     }
    @@ -1133,12 +1148,22 @@ function renderWithViews(tmpl, data, context, noIteration, view, key, onRender,
     // Generate a reusable function that will serve to render a template against data
     // (Compile AST then build template function)
     
    -function error(e, view, fallback) {
    -	var message = $viewsSettings.onError(e, view, fallback);
    -	if ("" + e === e) { // if e is a string, not an Exception, then throw new Exception
    -		throw new $sub.Err(message);
    +function onRenderError(e, view, fallback) {
    +	var message = fallback !== undefined
    +		? $isFunction(fallback)
    +			? fallback.call(view.data, e, view)
    +			: fallback || ""
    +		: "{Error: " + e.message + "}";
    +
    +	if ($subSettings.onError && (fallback = $subSettings.onError.call(view.data, e, fallback && message, view)) !== undefined) {
    +		message = fallback; // There is a settings.debugMode(handler) onError override. Call it, and use return value (if any) to replace message
     	}
    -	return !view.linkCtx && view.linked ? $converters.html(message) : message;
    +
    +	return view && !view.linkCtx && view.linked ? $converters.html(message) : message;
    +}
    +
    +function error(message) {
    +	throw new $sub.Err(message);
     }
     
     function syntaxError(message) {
    @@ -1203,7 +1228,6 @@ function tmplFn(markup, tmpl, isLinkExpr, convertBack, hasElse) {
     }}/g
     
     */
    -
     		if (codeTag && bind || slash && !tagName || params && params.slice(-1) === ":" || bind2) {
     			syntaxError(all);
     		}
    @@ -1279,8 +1303,8 @@ function tmplFn(markup, tmpl, isLinkExpr, convertBack, hasElse) {
     					tagName,
     					converter || !!convertBack || hasHandlers || "",
     					block && [],
    -					parsedParam(paramsArgs, paramsProps, paramsCtxProps),
    -					parsedParam(args, props, ctxProps),
    +					parsedParam(paramsArgs || (tagName === ":" ? "'#data'," : ""), paramsProps, paramsCtxProps), // {{:}} equivalent to {{:#data}}
    +					parsedParam(args || (tagName === ":" ? "data," : ""), props, ctxProps),
     					onError,
     					useTrigger,
     					pathBindings || 0
    @@ -1302,7 +1326,8 @@ function tmplFn(markup, tmpl, isLinkExpr, convertBack, hasElse) {
     	//==== /end of nested functions ====
     
     	var result, newNode, hasHandlers,
    -		allowCode = $viewsSettings.allowCode || tmpl && tmpl.allowCode,
    +		allowCode = $subSettings.allowCode || tmpl && tmpl.allowCode
    +			|| $viewsSettings.allowCode === true, // include direct setting of settings.allowCode true for backward compat only
     		astTop = [],
     		loc = 0,
     		stack = [],
    @@ -1405,6 +1430,9 @@ function parseParams(params, pathBindings, tmpl) {
     			var subPath = object === ".";
     			if (object) {
     				path = path.slice(not.length);
    +				if (/^\.?constructor$/.test(leafToken||path)) {
    +					syntaxError(allPath);
    +				}
     				if (!subPath) {
     					allPath = (helper
     							? 'view.hlp("' + helper + '")'
    @@ -1562,7 +1590,7 @@ function buildCode(ast, tmpl, isLinkExpr) {
     	var i, node, tagName, converter, tagCtx, hasTag, hasEncoder, getsVal, hasCnvt, useCnvt, tmplBindings, pathBindings, params, boundOnErrStart, boundOnErrEnd,
     		tagRender, nestedTmpls, tmplName, nestedTmpl, tagAndElses, content, markup, nextIsElse, oldCode, isElse, isGetVal, tagCtxFn, onError, tagStart, trigger,
     		tmplBindingKey = 0,
    -		useViews = $viewsSettings.useViews || tmpl.useViews || tmpl.tags || tmpl.templates || tmpl.helpers || tmpl.converters,
    +		useViews = $subSettingsAdvanced.useViews || tmpl.useViews || tmpl.tags || tmpl.templates || tmpl.helpers || tmpl.converters,
     		code = "",
     		tmplOptions = {},
     		l = ast.length;
    @@ -1627,6 +1655,7 @@ function buildCode(ast, tmpl, isLinkExpr) {
     					if (converter) {
     						tagName = converter === HTML ? ">" : converter + tagName;
     					}
    +					trigger = node[6] || $subSettings.trigger;
     				} else {
     					if (content) { // TODO optimize - if content.length === 0 or if there is a tmpl="..." specified - set content to null / don't run this compilation code - since content won't get used!!
     						// Create template object for nested template
    @@ -1678,8 +1707,8 @@ function buildCode(ast, tmpl, isLinkExpr) {
     							? ((tmplBindings[tmplBindingKey - 1] = tagCtxFn), tmplBindingKey) // Store the compiled tagCtxFn in tmpl.bnds, and pass the key to convertVal()
     							: "{" + tagCtx + "}") + ")")
     						: tagName === ">"
    -							? (hasEncoder = true, "h(" + params[0] + ')')
    -							: (getsVal = true, "((v=" + (params[0] || 'data') + ')!=null?v:"")') // Strict equality just for data-link="title{:expr}" so expr=null will remove title attribute
    +							? (hasEncoder = true, "h(" + params[0] + ")")
    +							: (getsVal = true, "((v=" + params[0] + ')!=null?v:"")') // Strict equality just for data-link="title{:expr}" so expr=null will remove title attribute
     					)
     					: (hasTag = true, "\n{view:view,tmpl:" // Add this tagCtx to the compiled code for the tagCtxs to be passed to renderTag()
     						+ (content ? nestedTmpls.length : "0") + "," // For block tags, pass in the key (nestedTmpls.length) to the nested content template
    @@ -1725,13 +1754,13 @@ function buildCode(ast, tmpl, isLinkExpr) {
     		+ "\nvar v"
     		+ (hasTag ? ",t=j._tag" : "")                // has tag
     		+ (hasCnvt ? ",c=j._cnvt" : "")              // converter
    -		+ (hasEncoder ? ",h=j.converters.html" : "") // html converter
    +		+ (hasEncoder ? ",h=j._html" : "")           // html converter
     		+ (isLinkExpr ? ";\n" : ',ret=""\n')
     		+ (tmplOptions.debug ? "debugger;" : "")
     		+ code
     		+ (isLinkExpr ? "\n" : ";\nreturn ret;");
     
    -	if ($viewsSettings._dbgMode) {
    +	if ($subSettings.debugMode !== false) {
     		code = "try {\n" + code + "\n}catch(e){\nreturn j._err(e, view);\n}";
     	}
     
    @@ -1836,6 +1865,8 @@ function htmlEncode(text) {
     		$.jsrender = versionNumber;
     	}
     
    +	$subSettings = $sub.settings;
    +	$subSettings.allowCode = false;
     	$isFunction = $.isFunction;
     	$isArray = $.isArray;
     	$.render = $render;
    @@ -1848,22 +1879,27 @@ function htmlEncode(text) {
     		return $templates(options);
     	};
     
    -	$viewsSettings({
    -		debugMode: dbgMode,
    -		delimiters: $viewsDelimiters,
    -		onError: function(e, view, fallback) {
    -			// Can override using $.views.settings({onError: function(...) {...}});
    -			if (view) {
    -				// For render errors, e is an exception thrown in compiled template, and view is the current view. For other errors, e is an error string.
    -				e = fallback === undefined
    -					? "{Error: " + (e.message || e) + "}"
    -					: $isFunction(fallback)
    -						? fallback(e, view) : fallback;
    -			}
    -			return e == undefined ? "" : e;
    -		},
    -		_dbgMode: false
    -	});
    +	for (setting in $subSettings) {
    +		addSetting(setting);
    +	}
    +
    +	($viewsSettings.debugMode = function(debugMode) {
    +		return debugMode === undefined
    +			? $subSettings.debugMode
    +			: (
    +				$subSettings.debugMode = debugMode,
    +				$subSettings.onError = debugMode + "" === debugMode
    +					? new Function("", "return '" + debugMode + "';" )
    +					: $isFunction(debugMode)
    +						? debugMode
    +						: undefined,
    +				$viewsSettings);
    +	})(false); // jshint ignore:line
    +
    +	$subSettingsAdvanced = $subSettings.advanced = {
    +		useViews: false,
    +		_jsv: false // For global access to JsViews store
    +	};
     
     	//========================== Register tags ==========================
     
    @@ -1943,7 +1979,7 @@ function htmlEncode(text) {
     	});
     
     	//========================== Define default delimiters ==========================
    -	$viewsDelimiters();
    +	$viewsSettings.delimiters("{{", "}}", "^");
     }
     
     // NODE.JS-SPECIFIC CODE:
    @@ -1969,4 +2005,4 @@ $views.tags("clientTemplate", function(path) { // Custom tag to render a templat
     
     module.exports = $;
     // END NODE.JS-SPECIFIC CODE
    -}());
    +}(this));
    
  • package.json+1 1 modified
    @@ -1,6 +1,6 @@
     {
       "name": "jsrender",
    -  "version": "v0.9.73",
    +  "version": "v0.9.74",
       "description": "Best-of-breed templating in browser or on Node.js (with Express 4, Hapi and Browserify integration)",
       "main": "./jsrender-node.js",
       "browser": "./jsrender.js",
    
  • test/perf-compare.html+0 24 modified
    @@ -120,18 +120,6 @@ <h3>Perf comparison</h3>
     			ret = tmpl_JsRender.render( movie );
     		});
     
    -		test( "JsRender: Debug mode off - <em>$.views.settings.debugMode(false)</em>", times * 500, 1,
    -			function() {
    -				ret = tmpl_JsRender.render( movie );
    -			},
    -			function() {
    -				$.views.settings.debugMode(false);
    -			},
    -			function() {
    -				$.views.settings.debugMode(true);
    -			}
    -		);
    -
     		// Test html encoding perf
     		$( "#results" ).append( "<tr><td colspan='2'>________________________________________________________</td></tr>" );
     		$( "#results" ).append( "<tr><td colspan='2'><b>Render to string, with HTML encoding</b></td></tr>");
    @@ -157,18 +145,6 @@ <h3>Perf comparison</h3>
     			ret = tmpl_JsRenderEncode.render( movie );
     		});
     
    -		test( "JsRender: Debug mode off - <em>$.views.settings.debugMode(false)</em>", times * 50, 1,
    -			function() {
    -				ret = tmpl_JsRenderEncode.render( movie );
    -			},
    -			function() {
    -				$.views.settings.debugMode(false);
    -			},
    -			function() {
    -				$.views.settings.debugMode(true);
    -			}
    -		);
    -
     		// Test full features perf
     		$( "#results" ).append( "<tr><td colspan='2'>________________________________________________________</td></tr>" );
     		$( "#results" ).append( "<tr><td colspan='2'><b>Render and insert in DOM</b></td></tr>");
    
  • test/unit-tests/tests-jsrender-no-jquery.js+133 63 modified
    @@ -138,7 +138,11 @@ test("types", function() {
     	equal($.templates("{{:-33.33 - 2.2}}").render(), "-35.53", "-33.33 - 2.2");
     	equal($.templates("{{:notdefined}}").render({}), "", "notdefined");
     	equal($.templates("{{:}}").render("aString"), "aString", "{{:}} returns current data item");
    +
    +//$.views.settings.trigger(true);
     	equal($.templates("{{:x=22}}").render("aString"), "aString", "{{:x=...}} returns current data item");
    +	equal($.templates("{{html:x=22}}").render("aString"), "aString", "{{html:x=...}} returns current data item");
    +	equal($.templates("{{>x=22}}").render("aString"), "aString", "{{>x=...}} returns current data item");
     	equal($.templates("{{:'abc('}}").render(), "abc(", "'abc(': final paren in string is rendered correctly"); // https://github.com/BorisMoore/jsviews/issues/300
     	equal($.templates('{{:"abc("}}').render(), "abc(", '"abc(": final paren in string is rendered correctly');
     	equal($.templates("{{:(('(abc('))}}").render(), "(abc(", "(('(abc('))");
    @@ -184,8 +188,9 @@ test("Fallbacks for missing or undefined paths:\nusing {{:some.path onError = 'f
     	equal($.templates("{{>a.missing.willThrow.path onError=myOnErrorDataMethod}}").render(
     		{
     			a: "dataValue",
    -			myOnErrorDataMethod: function(e, view) {
    -				return "Override onError using a callback data method: " + view.data.a;
    +			myOnErrorDataMethod: function(e) {
    +				var data = this;
    +				return "Override onError using a callback data method: " + data.a;
     			}
     		}), "Override onError using a callback data method: dataValue",
     		'{{>a.missing.willThrow.path onError=myOnErrorDataMethod}}" >' +
    @@ -202,7 +207,7 @@ test("Fallbacks for missing or undefined paths:\nusing {{:some.path onError = 'f
     			a:"aVal",
     			defaultVal: "defaultFromData",
     			myCb: function(e, view) {
    -				return "myCallback: " + view.data.a;
    +				return "myCallback: " + this.a;
     			}
     		}), "1: defaultFromData 2: Missing Object 3:  4:  5: aVal 6:  7: myCallback: aVal end",
     		'multiple onError fallbacks in same template - correctly concatenated into output');
    @@ -247,7 +252,7 @@ test("Fallbacks for missing or undefined paths:\nusing {{:some.path onError = 'f
     		a:"aVal",
     		defaultVal: "defaultFromData",
     		myCb: function(e, view) {
    -			return "myCallback: " + view.data.a;
    +			return "myCallback: " + this.a;
     		}
     	}), "1: defaultFromData 2: Missing Object 3:  4: aVal 5:  6: myCallback: aVal 7: undefined prop end",
     	'multiple onError fallbacks or undefined property fallbacks in same template - correctly concatenated into output');
    @@ -272,7 +277,7 @@ test("Fallbacks for missing or undefined paths:\nusing {{:some.path onError = 'f
     			a:"aVal",
     			defaultVal: "defaultFromData",
     			myCb: function(e, view) {
    -				return "myCallback: " + view.data.a;
    +				return "myCallback: " + this.a;
     			}
     		});
     	} catch (e) {
    @@ -324,7 +329,7 @@ test("Fallbacks for missing or undefined paths:\nusing {{:some.path onError = 'f
     		a:"aVal",
     		defaultVal: "defaultFromData",
     		myCb: function(e, view) {
    -			return "myCallback: " + view.data.a;
    +			return "myCallback: " + this.a;
     		}
     	}), "1: defaultFromData 2: Missing Object 3:  4: yes 5:  6: myCallback: aVal 7: undefined prop end 8: Missing Object",
     	'multiple onError fallbacks or undefined property fallbacks in same template - correctly concatenated into output');
    @@ -349,7 +354,7 @@ test("Fallbacks for missing or undefined paths:\nusing {{:some.path onError = 'f
     			a:"aVal",
     			defaultVal: "defaultFromData",
     			myCb: function(e, view) {
    -				return "myCallback: " + view.data.a;
    +				return "myCallback: " + this.a;
     			}
     		});
     	} catch (e) {
    @@ -379,7 +384,7 @@ test("Fallbacks for missing or undefined paths:\nusing {{:some.path onError = 'f
     		a:"aVal",
     		defaultVal: "defaultFromData",
     		myCb: function(e, view) {
    -			return "myCallback: " + view.data.a;
    +			return "myCallback: " + this.a;
     		}
     	}).slice(0, 8), "{Error: ",
     	'In debug mode, onError/fallback converter and regular thrown error message in same template:' +
    @@ -548,7 +553,7 @@ test("{{!-- --}}", function() {
     QUnit.module("allowCode");
     test("{{*}}", function() {
     	// =============================== Arrange ===============================
    -	$.views.settings.allowCode = false;
    +	$.views.settings.allowCode(false);
     	global.glob = {a: "AA"};
     
     	var tmpl = $.templates("_{{*:glob.a}}_");
    @@ -558,7 +563,7 @@ test("{{*}}", function() {
     		"{{*:expression}} returns nothing if allowCode not set to true");
     
     	// =============================== Arrange ===============================
    -	$.views.settings.allowCode = true;
    +	$.views.settings.allowCode(true);
     
     	var result = "" + !!tmpl.allowCode + " " + tmpl.render(); // Still returns "__" until we recompile
     
    @@ -568,7 +573,7 @@ test("{{*}}", function() {
     
     	// ................................ Assert ..................................
     	equal(result, "false __|true __",
    -		"If $.settings.allowCode or tmpl.allowCode are set to true, previously compiled template is unchanged, so {{*}} still inactive");
    +		"If $.settings.allowCode() or tmpl.allowCode are set to true, previously compiled template is unchanged, so {{*}} still inactive");
     
     	// ................................ Act ..................................
     	tmpl = $.templates("_{{*:glob.a}}_");
    @@ -577,10 +582,10 @@ test("{{*}}", function() {
     
     	// ................................ Assert ..................................
     	equal(result, "true _AA_",
    -		"If $.settings.allowCode set to true, {{*: expression}} returns evaluated expression, with access to globals");
    +		"If $.settings.allowCode() set to true, {{*: expression}} returns evaluated expression, with access to globals");
     
     	// =============================== Arrange ===============================
    -	$.views.settings.allowCode = false;
    +	$.views.settings.allowCode(false);
     
     	tmpl = $.templates({
     		markup: "_{{*:glob.a}}_",
    @@ -626,7 +631,7 @@ test("{{*}}", function() {
     		'Can recompile named tmpl to allow code, using $.templates("myTemplateName", {markup: $.templates.myTmpl, allowCode:true})"');
     
     	// =============================== Arrange ===============================
    -	$.views.settings.allowCode = true;
    +	$.views.settings.allowCode(true);
     
     	// ................................ Act ..................................
     	global.myVar = 0;
    @@ -670,15 +675,15 @@ test("{{*}}", function() {
     
     	document.title = "";
     
    -	$.views.settings.allowCode = false;
    +	$.views.settings.allowCode(false);
     
     });
     
     QUnit.module("useViews");
     test("", function() {
     
     	// =============================== Arrange ===============================
    -	$.views.settings.allowCode = true;
    +	$.views.settings.allowCode(true);
     	$.views.tags("exclaim", "!!! ");
     	var message = "",
     
    @@ -710,14 +715,14 @@ test("", function() {
     
     	// ................................ Act ..................................
     	tmpl.useViews = false;
    -	$.views.settings.useViews = true;
     
    +	$.views.settings.advanced({useViews: true});
     	// ................................ Assert ..................................
     	equal(tmpl.render({towns: towns}), "Seattle, Paris and Delhi",
    -		"If tmpl.useViews is set to false, but $.views.settings.useViews is set to true, the template renders with view hierarchy, (without recompiling).");
    +		"If tmpl.useViews is set to false, but $.views.settings.advanced({useViews: ...}) is set to true, the template renders with view hierarchy, (without recompiling).");
     
     	// ................................ Act ..................................
    -	$.views.settings.useViews = false;
    +	$.views.settings.advanced({useViews: false});
     
     	tmpl = $.templates({markup: tmpl,
     		useViews: true
    @@ -746,18 +751,18 @@ test("", function() {
     		"If tmpl.useViews set to false (for an existing template - without recompiling), the template renders without a 'data' view");
     
     	// ................................ Act ..................................
    -	$.views.settings.useViews = true;
    +	$.views.settings.advanced({useViews: true});
     
     	tmpl = $.templates({markup: tmpl});
     
    -	$.views.settings.useViews = false;
    +	$.views.settings.advanced({useViews: false});
     
     	// ................................ Assert ..................................
     	equal(tmpl.useViews && tmpl.render({towns: towns}), "data Seattle, Paris and Delhi",
    -		"If $.views.settings.useViews was true when the template was compiled, then the template renders with views, even if $.views.settings.useViews is no longer set to true");
    +		"If $.views.settings.advanced({useViews: ...}) was true when the template was compiled, then the template renders with views, even if $.views.settings.advanced({useViews: ...}) is no longer set to true");
     
     	// =============================== Arrange ===============================
    -	$.views.settings.useViews = false;
    +	$.views.settings.advanced({useViews: false});
     
     		tmpl = $.templates(
     			"{{exclaim/}}"
    @@ -768,7 +773,7 @@ test("", function() {
     
     	// ................................ Assert ..................................
     	equal(tmpl.useViews && tmpl.render({towns: towns}), "!!! Seattle, Paris and Delhi",
    -		"A template with richer features, (such as a custom tag, or nested tags) will automatically have tmpl.useViews=truew and will render with views, even if $.views.settings.useViews is set to false");
    +		"A template with richer features, (such as a custom tag, or nested tags) will automatically have tmpl.useViews=truew and will render with views, even if $.views.settings.advanced({useViews: ...}) is set to false");
     
     	// ................................ Act ..................................
     	var originalUseViews = tmpl.useViews;
    @@ -795,13 +800,13 @@ test("", function() {
     
     	// ................................ Act ..................................
     	tmpl.useViews = originalUseViews;
    -	$.views.settings.useViews = true;
    +	$.views.settings.advanced({useViews: true});
     
     	// ................................ Assert ..................................
     	equal(!tmpl.useViews && tmpl.render({towns: towns}), "Seattle, Paris and Delhi",
    -		"Setting $.views.settings.useViews=true WILL prevent a simpler template from rendering without views.");
    +		"Setting $.views.settings.advanced({useViews: true}) WILL prevent a simpler template from rendering without views.");
     
    -	$.views.settings.useViews = false;
    +	$.views.settings.advanced({useViews: false});
     	document.title = "";
     });
     
    @@ -1155,7 +1160,7 @@ test("render", 25, function() {
     		+ '{{/for}}');
     
     	$.views.settings.debugMode(true);
    -	var result  = templateWithIndex.render({people: [1,2]});
    +	var result = templateWithIndex.render({people: [1,2]});
     
     	$.views.settings.debugMode(false);
     	var result2 = templateWithIndex.render({people: [1,2]});
    @@ -1753,7 +1758,7 @@ test('{{include}} and wrapping content', function() {
     			+ '{{:number}} '
     		+ '{{/myTag}} | '
     		+ '{{myTag2 tmpl="phonelist"}}'
    -			+ '{{:number}} '
    +		  + '{{:number}} '
     		+ '{{/myTag2}}',
     		templates: {
     			phonelist: "{{for phones}}{{include tmpl=#content/}}{{/for}}"
    @@ -1777,16 +1782,16 @@ test('{{include}} and wrapping content', function() {
     		  '{{myTag tmpl="phonelist"}}'
     			+ '{{:number}}'
     		+ '{{else tmpl="altlist"}}'
    -			+ '{{:alt}}'
    +		  + '{{:alt}}'
     		+ '{{else tmpl="altlist2"}}'
    -			+ '{{:alt}}'
    +		  + '{{:alt}}'
     		+ '{{/myTag}}'
     		+ '{{myTag2 tmpl="phonelist"}}'
    -			+ '{{:number}}'
    +		  + '{{:number}}'
     		+ '{{else tmpl="altlist"}}'
    -			+ '{{:alt}}'
    +		  + '{{:alt}}'
     		+ '{{else tmpl="altlist2"}}'
    -			+ '{{:alt}}'
    +		  + '{{:alt}}'
     		+ '{{/myTag2}}',
     		templates: {
     			phonelist: "A< {{for phones}}{{include tmpl=#content/}} {{/for}} > ",
    @@ -1840,61 +1845,126 @@ test("helpers", 4, function() {
     
     test("settings", function() {
     	// ................................ Act ..................................
    +	// Delimiters
    +
     	$.views.settings.delimiters("@%","%@");
    -	var result = $.templates("A_@%if true%@yes@%/if%@_B").render();
    -	$.views.settings.delimiters("{{","}}");
    -	result += "|" + $.templates("A_{{if true}}YES{{/if}}_B").render();
    +	var result = $.templates("A_@%if true%@yes@%/if%@_B").render()
    +		+ "|" + $.views.settings.delimiters() + "|" + $.views.sub.settings.delimiters;
    +
    +	$.views.settings.delimiters("<<",">>", "*");
    +
    +	result += "|" + $.views.settings.delimiters() + "|" + $.views.sub.settings.delimiters;
    +
    +	$.views.settings.delimiters("{{","}}", "^");
    +	result += "|" + $.templates("A_{{if true}}YES{{/if}}_B").render()
    +		+ "|" + $.views.settings.delimiters() + "|" + $.views.sub.settings.delimiters;
    +
     	// ............................... Assert .................................
    -	equal(result, "A_yes_B|A_YES_B", "Custom delimiters with render()");
    +	equal(result, "A_yes_B|@%,%@,^|@%,%@,^|<<,>>,*|<<,>>,*|A_YES_B|{{,}},^|{{,}},^", "Custom delimiters with render()");
     
     	// =============================== Arrange ===============================
    +	// Debug mode false
    +
    +	var oldDebugMode = $.views.settings.debugMode();
     	var app = {choose: true, name: "Jo"};
     	result = "";
    -	var oldOnError = $.views.settings.onError;
    -
    -	$.views.settings({
    -		onError: function(e, view, fallback) {
    -			return "Override error - " + (fallback ? ("(Fallback string: " + fallback + ") ") : "") + (view ? "Rendering error: " + e.message : "JsViews error: " + e);
    -		}
    -	});
     
     	// ................................ Act ..................................
    -	$.views.settings.debugMode(true);
    -	result = $.templates('{{:missing.willThrow}}').render(app);
    -	$.views.settings.debugMode();
    +	$.views.settings.debugMode(false)
    +	
    +	try {
    +		result = $.templates('{{:missing.willThrow}}').render(app);
    +	}
    +	catch (e) {
    +		result += !!e.message;
    +	}
     
     	// ............................... Assert .................................
    -	equal(result.slice(0, 34), "Override error - Rendering error: ", "Override onError()");
    +	equal($.views.settings.debugMode() + " " + result, 'false true',
    +		'Debug mode false: {{:missing.willThrow}} throws error');
     
     	// ................................ Act ..................................
    -	result = $.templates('{{:missing.willThrow onError="myFallback"}}').render(app);
    +	// Debug mode true
     
    -	// ............................... Assert .................................
    -	equal(result.slice(0, 64), "Override error - (Fallback string: myFallback) Rendering error: ", 'Override onError() - with {{:missing.willThrow onError="myFallback"}}');
    +	$.views.settings.debugMode(true)
     
    -	// ................................ Act ..................................
     	try {
    -		$.templates('{{if}}').render(app);
    +		result = $.templates('{{:missing.willThrow}}').render(app);
     	}
     	catch (e) {
    -		result = e.message;
    +		result += !!e.message;
     	}
     
     	// ............................... Assert .................................
    -	equal(result, 'Override error - JsViews error: Syntax error\nUnmatched or missing {{/if}}, in template:\n{{if}}',
    -		'Override onError() - with thrown syntax error (missing {{/if}})');
    +	equal($.views.settings.debugMode() + " " + result.slice(0, 8), 'true {Error: ',
    +		'Debug mode true: {{:missing.willThrow}} renders error');
    +
    +	// ................................ Act ..................................
    +	// Debug mode 'onError' handler function with return value
    +
    +	$.views.settings.debugMode(function(e, fallback, view) {
    +		var data = this;
    +		return "Override error - " + (fallback||"") + "_" + (view ? data.name + " " + (e.message.indexOf("willThrow")>-1): e); // For syntax errors e is a string, and view is undefined
    +	});
     
     	// ................................ Act ..................................
    -	result = $.templates('{{if missing.willThrow onError="myFallback"}}yes{{/if}}').render(app);
    +	result = typeof $.views.settings.debugMode() + " ";
    +	result += $.templates('{{:missing.willThrow}}').render(app);
     
     	// ............................... Assert .................................
    -	equal(result.slice(0,64), 'Override error - (Fallback string: myFallback) Rendering error: ',
    -		'Override onError() - with {{if missing.willThrow onError="myFallback"}}');
    +	equal(result, "function Override error - _Jo true",
    +		"Debug mode 'onError' handler override, with {{:missing.willThrow}}");
     
    -	// ................................ Reset ..................................
    -	$.views.settings({
    -		onError: oldOnError
    +	// ................................ Act ..................................
    +	result = typeof $.views.settings.debugMode() + " ";
    +	result += $.templates('{{:missing.willThrow onError="myFallback"}}').render(app);
    +
    +	// ............................... Assert .................................
    +	equal(result, "function Override error - myFallback_Jo true",
    +		'Debug mode \'onError\' handler override, with onError fallback: {{:missing.willThrow onError="myFallback"}}');
    +
    +	// ................................ Act ..................................
    +	result = typeof $.views.settings.debugMode() + " ";
    +	result += $.templates('{{if missing.willThrow onError="myFallback"}}yes{{/if}}').render(app);
    +
    +	// ............................... Assert .................................
    +	equal(result, 'function Override error - myFallback_Jo true',
    +		'Debug mode \'onError\' handler override, with onError fallback: {{if missing.willThrow onError="myFallback"}}');
    +
    +	// ................................ Act ..................................
    +	// Debug mode 'onError' handler function without return value
    +	var ret = "";
    +	$.views.settings.debugMode(function(e, fallback, view) {
    +		var data = this;
    +		ret = "Override error - " + (fallback||"") + "_" + data.name + " " + (e.message.indexOf("willThrow")>-1); // For syntax errors e is a string, and view is undefined
     	});
    +
    +	// ................................ Act ..................................
    +	result = typeof $.views.settings.debugMode() + " ";
    +	result += $.templates('{{:missing.willThrow}}').render(app);
    +
    +	// ............................... Assert .................................
    +	equal(ret + "|" + result.slice(0, 17), "Override error - _Jo true|function {Error: ",
    +		"Debug mode 'onError' handler (no return) with {{:missing.willThrow}}");
    +
    +	// ................................ Act ..................................
    +	result = typeof $.views.settings.debugMode() + " ";
    +	result += $.templates('{{:missing.willThrow onError="myFallback"}}').render(app);
    +
    +	// ............................... Assert .................................
    +	equal(ret + "|" + result, "Override error - myFallback_Jo true|function myFallback",
    +		'Debug mode \'onError\' handler (no return) with onError fallback: {{:missing.willThrow onError="myFallback"}}');
    +
    +	// ................................ Act ..................................
    +	result = typeof $.views.settings.debugMode() + " ";
    +	result += $.templates('{{if missing.willThrow onError="myFallback"}}yes{{/if}}').render(app);
    +
    +	// ............................... Assert .................................
    +	equal(ret + "|" + result, "Override error - myFallback_Jo true|function myFallback",
    +		'Debug mode \'onError\' handler (no return) with onError fallback: {{if missing.willThrow onError="myFallback"}}');
    +
    +	// ................................ Reset ..................................
    +	$.views.settings.debugMode(oldDebugMode);
     });
     
     test("template encapsulation", 8, function() {
    
  • test/unit-tests/tests-jsrender-with-jquery.js+5 5 modified
    @@ -22,7 +22,7 @@ var person = { name: "Jo" },
     	people = [{ name: "Jo" },{ name: "Bill" }],
     	towns = [{ name: "Seattle" },{ name: "Paris" },{ name: "Delhi" }];
     
    -var tmplString =  "A_{{:name}}_B";
    +var tmplString = "A_{{:name}}_B";
     
     module("api");
     
    @@ -102,7 +102,7 @@ test("templates", function() {
     	equal($.render.my_tmpl(person), "A_Jo_B", 'Compile a template and then render it: $.templates("my_tmpl", tmplString); $.render.my_tmpl(data);');
     
     	$.templates({ myTmpl2: tmplString });
    -	equal($.render.myTmpl2(person), "A_Jo_B", 'Compile and register templates: $.templates({ "my_tmpl", tmplString, ...  }); $.render.my_tmpl(data);');
    +	equal($.render.myTmpl2(person), "A_Jo_B", 'Compile and register templates: $.templates({ "my_tmpl", tmplString, ... }); $.render.my_tmpl(data);');
     
     	equal($.templates.myTmpl2.render(person), "A_Jo_B", 'Get named template: $.templates.my_tmpl.render(data);');
     
    @@ -149,7 +149,7 @@ test("templates", function() {
     	ok($.templates.cloned2 !== tmpl2 && $.templates.cloned2.tmplName === "cloned2", '$.templates({ cloned: {markup: "#my_tmpl" } }) will clone the cached template');
     
     	$.templates("my_tmpl", null);
    -	equal($.templates.my_tmpl, undefined, 'Remove a named template:  $.templates("my_tmpl", null);');
    +	equal($.templates.my_tmpl, undefined, 'Remove a named template: $.templates("my_tmpl", null);');
     
     	$.templates({
     		"scriptTmpl": {
    @@ -161,7 +161,7 @@ test("templates", function() {
     			debug:true
     		}
     	});
    -	equal($.templates.tmplFromString.fn.toString().indexOf("debugger;") > 0 && $.templates.scriptTmpl.fn.toString().indexOf("debugger;") > 0, true, 'Debug a template:  set debug:true on object');
    +	equal($.templates.tmplFromString.fn.toString().indexOf("debugger;") > 0 && $.templates.scriptTmpl.fn.toString().indexOf("debugger;") > 0, true, 'Debug a template: set debug:true on object');
     
     	// reset
     	$("#my_tmpl")[0].removeAttribute("data-jsv-tmpl");
    @@ -234,7 +234,7 @@ test("helpers", 3, function() {
     	equal($.views.helpers.concat === concat, true, 'concatFunction === $.views.helpers.concat');
     
     	$.views.helpers("concat2", null);
    -	equal($.views.helpers.concat2, undefined,  'Remove a registered helper: $.views.helpers({ concat: null })');
    +	equal($.views.helpers.concat2, undefined, 'Remove a registered helper: $.views.helpers({ concat: null })');
     });
     
     test("template encapsulation", 1, function() {
    
  • tmplify/index.js+1 1 modified
    @@ -1,4 +1,4 @@
    -/*! JsRender tmplify submodule v0.9.73 (Beta): http://jsviews.com/#jsrender */
    +/*! JsRender tmplify submodule v0.9.74 (Beta): http://jsviews.com/#jsrender */
     /*! Browserify transform for JsRender templates */
     /*
      * Copyright 2016, Boris Moore
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

5

News mentions

0

No linked articles in our index yet.