PageSourceSearch

https://www.szfb.sk/Scripts/jquery.ui.jsrender.js

js szfb.sk collected 2026-10-02 18:20:06 UTC 59,231 bytes, 1,571 lines download raw bytes

1/*! JsRender v1.0.0-beta: http://github.com/BorisMoore/jsrender and http://jsviews.com/jsviews
2informal pre V1.0 commit counter: 51 */
3/*
4 * Optimized version of jQuery Templates, for rendering to string.
5 * Does not require jQuery, or HTML DOM
6 * Integrates with JsViews (http://jsviews.com/jsviews)
7 *
8 * Copyright 2013, Boris Moore
9 * Released under the MIT License.
10 */
11
12(function(global, jQuery, undefined) {
13	// global is the this object, which is window when running in the usual browser environment.
14	"use strict";
15
16	if (jQuery && jQuery.views || global.jsviews) { return; } // JsRender is already loaded
17
18	//========================== Top-level vars ==========================
19
20	var versionNumber = "v1.0.0-beta",
21
22		$, jsvStoreName, rTag, rTmplString, indexStr, // nodeJsModule,
23
24//TODO	tmplFnsCache = {},
25		delimOpenChar0 = "{", delimOpenChar1 = "{", delimCloseChar0 = "}", delimCloseChar1 = "}", linkChar = "^",
26
27		rPath = /^(!*?)(?:null|true|false|\d[\d.]*|([\w$]+|\.|~([\w$]+)|#(view|([\w$]+))?)([\w$.^]*?)(?:[.[^]([\w$]+)\]?)?)$/g,
28		//                                     none   object     helper    view  viewProperty pathTokens      leafToken
29
30		rParams = /(\()(?=\s*\()|(?:([([])\s*)?(?:(\^?)(!*?[#~]?[\w$.^]+)?\s*((\+\+|--)|\+|-|&&|\|\||===|!==|==|!=|<=|>=|[<>%*:?\/]|(=))\s*|(!*?[#~]?[\w$.^]+)([([])?)|(,\s*)|(\(?)\\?(?:(')|("))|(?:\s*(([)\]])(?=\s*\.|\s*\^|\s*$)|[)\]])([([]?))|(\s+)/g,
31		//          lftPrn0        lftPrn        bound            path    operator err                                                eq             path2       prn    comma   lftPrn2   apos quot      rtPrn rtPrnDot                        prn2      space
32		// (left paren? followed by (path? followed by operator) or (path followed by left paren?)) or comma or apos or quot or right paren or space
33
34		rNewLine = /[ \t]*(\r\n|\n|\r)/g,
35		rUnescapeQuotes = /\\(['"])/g,
36		rEscapeQuotes = /['"\\]/g, // Escape quotes and \ character
37		rBuildHash = /\x08(~)?([^\x08]+)\x08/g,
38		rTestElseIf = /^if\s/,
39		rFirstElem = /<(\w+)[>\s]/,
40		rAttrEncode = /[\x00`><"'&]/g, // Includes > encoding since rConvertMarkers in JsViews does not skip > characters in attribute strings
41		rHtmlEncode = rAttrEncode,
42		autoTmplName = 0,
43		viewId = 0,
44		charEntities = {
45			"&": "&amp;",
46			"<": "&lt;",
47			">": "&gt;",
48			"\x00": "&#0;",
49			"'": "&#39;",
50			'"': "&#34;",
51			"`": "&#96;"
52		},
53		htmlStr = "html",
54		tmplAttr = "data-jsv-tmpl",
55		$render = {},
56		jsvStores = {
57			template: {
58				compile: compileTmpl
59			},
60			tag: {
61				compile: compileTag
62			},
63			helper: {},
64			converter: {}
65		},
66
67		// jsviews object ($.views if jQuery is loaded)
68		$views = {
69			jsviews: versionNumber,
70			render: $render,
71			settings: function(settings) {
72				$extend($viewsSettings, settings);
73				dbgMode($viewsSettings._dbgMode);
74				if ($viewsSettings.jsv) {
75					$viewsSettings.jsv();
76				}
77			},
78			sub: {
79				// subscription, e.g. JsViews integration
80				View: View,
81				Error: JsViewsError,
82				tmplFn: tmplFn,
83				parse: parseParams,
84				extend: $extend,
85				error: error,
86				syntaxError: syntaxError,
87				DataMap: DataMap
88			},
89			_cnvt: convertVal,
90			_tag: renderTag,
91
92			_err: function(e) {
93				// Place a breakpoint here to intercept template rendering errors
94				return $viewsSettings._dbgMode ? ("Error: " + (e.message || e)) + ". " : '';
95			}
96		};
97
98	function retVal(val) {
99		return val;
100	}
101
102	function dbgBreak(val) {
103		debugger;
104		return val;
105	}
106
107	function dbgMode(debugMode) {
108		$viewsSettings._dbgMode = debugMode;
109		indexStr = debugMode ? "Error: #index in nested view: use #getIndex()" : ""; // If in debug mode set #index to a warning when in nested contexts
110		$tags("dbg", $helpers.dbg = $converters.dbg = debugMode ? dbgBreak : retVal); // If in debug mode, register {{dbg/}}, {{dbg:...}} and ~dbg() to insert break points for debugging.
111	}
112
113	function DataMap(getTarget) {
114		return {
115			getTgt: getTarget,
116			map: function(source) {
117				var theMap = this; // Instance of DataMap
118				if (theMap.src !== source) {
119					if (theMap.src) {
120						theMap.unmap();
121					}
122					if (typeof source === "object") {
123						var changing,
124						target = getTarget.apply(theMap, arguments);
125						theMap.src = source;
126						theMap.tgt = target;
127					}
128				}
129			}
130		}
131	}
132
133	function JsViewsError(message, object) {
134		// Error exception type for JsViews/JsRender
135		// Override of $.views.sub.Error is possible
136		if (object && object.onError) {
137			if (object.onError(message) === false) {
138				return;
139			}
140		}
141		this.name = "JsRender Error";
142		this.message = message || "JsRender error";
143	}
144
145	function $extend(target, source) {
146		var name;
147		target = target || {};
148		for (name in source) {
149			target[name] = source[name];
150		}
151		return target;
152	}
153
154	(JsViewsError.prototype = new Error()).constructor = JsViewsError;
155
156	//========================== Top-level functions ==========================
157
158	//===================
159	// jsviews.delimiters
160	//===================
161	function $viewsDelimiters(openChars, closeChars, link) {
162		// Set the tag opening and closing delimiters and 'link' character. Default is "{{", "}}" and "^"
163		// openChars, closeChars: opening and closing strings, each with two characters
164
165		if (!$viewsSub.rTag || openChars) {
166			delimOpenChar0 = openChars ? openChars.charAt(0) : delimOpenChar0; // Escape the characters - since they could be regex special characters
167			delimOpenChar1 = openChars ? openChars.charAt(1) : delimOpenChar1;
168			delimCloseChar0 = closeChars ? closeChars.charAt(0) : delimCloseChar0;
169			delimCloseChar1 = closeChars ? closeChars.charAt(1) : delimCloseChar1;
170			linkChar = link || linkChar;
171			openChars = "\\" + delimOpenChar0 + "(\\" + linkChar + ")?\\" + delimOpenChar1;  // Default is "{^{"
172			closeChars = "\\" + delimCloseChar0 + "\\" + delimCloseChar1;                   // Default is "}}"
173			// Build regex with new delimiters
174			//          tag    (followed by / space or })   or cvtr+colon or html or code
175			rTag = "(?:(?:(\\w+(?=[\\/\\s\\" + delimCloseChar0 + "]))|(?:(\\w+)?(:)|(>)|!--((?:[^-]|-(?!-))*)--|(\\*)))"
176				+ "\\s*((?:[^\\" + delimCloseChar0 + "]|\\" + delimCloseChar0 + "(?!\\" + delimCloseChar1 + "))*?)";
177
178			// make rTag available to JsViews (or other components) for parsing binding expressions
179			$viewsSub.rTag = rTag + ")";
180
181			rTag = new RegExp(openChars + rTag + "(\\/)?|(?:\\/(\\w+)))" + closeChars, "g");
182
183			// Default:    bind           tag       converter colon html     comment            code      params            slash   closeBlock
184			//           /{(\^)?{(?:(?:(\w+(?=[\/\s}]))|(?:(\w+)?(:)|(>)|!--((?:[^-]|-(?!-))*)--|(\*)))\s*((?:[^}]|}(?!}))*?)(\/)?|(?:\/(\w+)))}}/g
185
186			rTmplString = new RegExp("<.*>|([^\\\\]|^)[{}]|" + openChars + ".*" + closeChars);
187			// rTmplString looks for html tags or { or } char not preceded by \\, or JsRender tags {{xxx}}. Each of these strings are considered
188			// NOT to be jQuery selectors
189		}
190		return [delimOpenChar0, delimOpenChar1, delimCloseChar0, delimCloseChar1, linkChar];
191	}
192
193	//=========
194	// View.get
195	//=========
196
197	function getView(inner, type) { //view.get(inner, type)
198		if (!type) {
199			// view.get(type)
200			type = inner;
201			inner = undefined;
202		}
203
204		var views, i, l, found,
205			view = this,
206			root = !type || type === "root";
207			// If type is undefined, returns root view (view under top view).
208
209		if (inner) {
210			// Go through views - this one, and all nested ones, depth-first - and return first one with given type.
211			found = view.type === type ? view : undefined;
212			if (!found) {
213				views = view.views;
214				if (view._.useKey) {
215					for (i in views) {
216						if (found = views[i].get(inner, type)) {
217							break;
218						}
219					}
220				} else for (i = 0, l = views.length; !found && i < l; i++) {
221					found = views[i].get(inner, type);
222				}
223			}
224		} else if (root) {
225			// Find root view. (view whose parent is top view)
226			while (view.parent.parent) {
227				found = view = view.parent;
228			}
229		} else while (view && !found) {
230			// Go through views - this one, and all parent ones - and return first one with given type.
231			found = view.type === type ? view : undefined;
232			view = view.parent;
233		}
234		return found;
235	}
236
237	function getNestedIndex() {
238		var view = this.get("item");
239		return view ? view.index : undefined;
240	}
241
242	getNestedIndex.depends = function() {
243		return [this.get("item"), "index"];
244	};
245
246	function getIndex() {
247		return this.index;
248	}
249
250	getIndex.depends = function() {
251		return ["index"];
252	};
253
254	//==========
255	// View.hlp
256	//==========
257
258	function getHelper(helper) {
259		// Helper method called as view.hlp(key) from compiled template, for helper functions or template parameters ~foo
260		var wrapped,
261			view = this,
262			ctx = view.linkCtx,
263			res = (view.ctx || {})[helper];
264
265		if (res === undefined && ctx && ctx.ctx) {
266			res = ctx.ctx[helper];
267		}
268		if (res === undefined) {
269			res = $helpers[helper];
270		}
271
272		if (res) {
273			if (typeof res === "function" && !res._wrp) {
274				wrapped = function() {
275					// If it is of type function, and not already wrapped, we will wrap it, so if called with no this pointer it will be called with the
276					// view as 'this' context. If the helper ~foo() was in a data-link expression, the view will have a 'temporary' linkCtx property too.
277					// Note that helper functions on deeper paths will have specific this pointers, from the preceding path.
278					// For example, ~util.foo() will have the ~util object as 'this' pointer
279					return res.apply((!this || this === global) ? view : this, arguments);
280				};
281				wrapped._wrp = 1;
282				$extend(wrapped, res); // Attach same expandos (if any) to the wrapped function
283			}
284		}
285		return wrapped || res;
286	}
287
288	//==============
289	// jsviews._cnvt
290	//==============
291
292	function convertVal(converter, view, tagCtx) {
293		// self is template object or linkCtx object
294		var tmplConverter, tag, value,
295			boundTagCtx = +tagCtx === tagCtx && tagCtx, // if tagCtx is an integer, then it is the key for the boundTagCtx (compiled function to return the tagCtx)
296			linkCtx = view.linkCtx; // For data-link="{cvt:...}"...
297
298		if (boundTagCtx) {
299			// This is a bound tag: {^{xx:yyy}}. Call compiled function which returns the tagCtxs for current data
300			tagCtx = (boundTagCtx = view.tmpl.bnds[boundTagCtx-1])(view.data, view, $views);
301		}
302
303		value = tagCtx.args[0];
304		if (converter || boundTagCtx) {
305			tag = linkCtx && linkCtx.tag || {
306				_: {
307					inline: !linkCtx,
308					bnd: boundTagCtx
309				},
310				tagName: converter + ":",
311				flow: true,
312				_is: "tag"
313			};
314
315			if (linkCtx) {
316				linkCtx.tag = tag;
317				tag.linkCtx = tag.linkCtx || linkCtx;
318				tagCtx.ctx = extendCtx(tagCtx.ctx, linkCtx.view.ctx);
319			}
320			tag.tagCtx = tagCtx;
321			tagCtx.view = view;
322
323			tag.ctx = tagCtx.ctx || {};
324			delete tagCtx.ctx;
325			// Provide this tag on view, for addBindingMarkers on bound tags to add the tag to view._.bnds, associated with the tag id,
326			view._.tag = tag;
327
328			converter = converter !== "true" && converter; // If there is a convertBack but no convert, converter will be "true"
329
330			if (converter && ((tmplConverter = view.getRsc("converters", converter)) || error("Unknown converter: {{"+ converter + ":"))) {
331				// A call to {{cnvt: ... }} or {^{cnvt: ... }} or data-link="{cnvt: ... }"
332				tag.depends = tmplConverter.depends;
333				value = tmplConverter.apply(tag, tagCtx.args);
334			}
335			// Call onRender (used by JsViews if present, to add binding annotations around rendered content)
336			value = value != undefined ? value : "";
337			value = boundTagCtx && view._.onRender
338				? view._.onRender(value, view, boundTagCtx)
339				: value;
340			view._.tag = undefined;
341		}
342		return value;
343	}
344
345	//=============
346	// jsviews._tag
347	//=============
348
349	function getResource(resourceType, itemName) {
350		var res, store,
351			view = this;
352		while ((res === undefined) && view) {
353			store = view.tmpl[resourceType];
354			res = store && store[itemName];
355			view = view.parent;
356		}
357		return res || $views[resourceType][itemName];
358	}
359
360	function renderTag(tagName, parentView, tmpl, tagCtxs, isRefresh) {
361		// Called from within compiled template function, to render a template tag
362		// Returns the rendered tag
363
364		var render, tag, tags, attr, parentTag, i, l, itemRet, tagCtx, tagCtxCtx, content, boundTagFn, tagDef, callInit, map, thisMap, args,
365			ret = "",
366			boundTagKey = +tagCtxs === tagCtxs && tagCtxs, // if tagCtxs is an integer, then it is the boundTagKey
367			linkCtx = parentView.linkCtx || 0,
368			ctx = parentView.ctx,
369			parentTmpl = tmpl || parentView.tmpl,
370			parentView_ = parentView._;
371
372		if (tagName._is === "tag") {
373			tag = tagName;
374			tagName = tag.tagName;
375		}
376
377		// Provide tagCtx, linkCtx and ctx access from tag
378		if (boundTagKey) {
379			// if tagCtxs is an integer, we are data binding
380			// Call compiled function which returns the tagCtxs for current data
381			tagCtxs = (boundTagFn = parentTmpl.bnds[boundTagKey-1])(parentView.data, parentView, $views);
382		}
383
384		l = tagCtxs.length;
385		tag = tag || linkCtx.tag;
386		for (i = 0; i < l; i++) {
387			tagCtx = tagCtxs[i];
388
389			// Set the tmpl property to the content of the block tag, unless set as an override property on the tag
390			content = tagCtx.tmpl;
391			content = tagCtx.content = content && parentTmpl.tmpls[content - 1];
392			tmpl = tagCtx.props.tmpl;
393			if (!i && (!tmpl || !tag)) {
394				tagDef = parentView.getRsc("tags", tagName) || error("Unknown tag: {{"+ tagName + "}}");
395			}
396			tmpl = tmpl || (tag ? tag : tagDef).template || content;
397			tmpl = "" + tmpl === tmpl // if a string
398				? parentView.getRsc("templates", tmpl) || $templates(tmpl)
399				: tmpl;
400
401			$extend(tagCtx, {
402				tmpl: tmpl,
403				render: renderContent,
404				index: i,
405				view: parentView,
406				ctx: extendCtx(tagCtx.ctx, ctx) // Extend parentView.ctx
407			}); // Extend parentView.ctx
408
409			if (!tag) {
410				// This will only be hit for initial tagCtx (not for {{else}}) - if the tag instance does not exist yet
411				// Instantiate tag if it does not yet exist
412				if (tagDef._ctr) {
413					// If the tag has not already been instantiated, we will create a new instance.
414					// ~tag will access the tag, even within the rendering of the template content of this tag.
415					// From child/descendant tags, can access using ~tag.parent, or ~parentTags.tagName
416//	TODO provide error handling owned by the tag - using tag.onError
417//				try {
418					tag = new tagDef._ctr();
419					callInit = !!tag.init;
420//				}
421//				catch(e) {
422//					tagDef.onError(e);
423//				}
424					// Set attr on linkCtx to ensure outputting to the correct target attribute.
425					tag.attr = tag.attr || tagDef.attr || undefined;
426					// Setting either linkCtx.attr or this.attr in the init() allows per-instance choice of target attrib.
427				} else {
428					// This is a simple tag declared as a function, or with init set to false. We won't instantiate a specific tag constructor - just a standard instance object.
429					tag = {
430						// tag instance object if no init constructor
431						render: tagDef.render
432					};
433				}
434				tag._ = {
435					inline: !linkCtx
436				};
437				if (linkCtx) {
438					// Set attr on linkCtx to ensure outputting to the correct target attribute.
439					linkCtx.attr = tag.attr = linkCtx.attr || tag.attr;
440					linkCtx.tag = tag;
441					tag.linkCtx = linkCtx;
442				}
443				if (tag._.bnd = boundTagFn || linkCtx.fn) {
444					// Bound if {^{tag...}} or data-link="{tag...}"
445					tag._.arrVws = {};
446				} else if (tag.dataBoundOnly) {
447					error("{^{" + tagName + "}} tag must be data-bound");
448				}
449				tag.tagName = tagName;
450				tag.parent = parentTag = ctx && ctx.tag;
451				tag._is = "tag";
452				tag._def = tagDef;
453				//TODO better perf for childTags() - keep child tag.tags array, (and remove child, when disposed)
454				// tag.tags = [];
455				// Provide this tag on view, for addBindingMarkers on bound tags to add the tag to view._.bnds, associated with the tag id,
456			}
457			tagCtx.tag = tag;
458			if (tag.map && tag.tagCtxs) {
459				tagCtx.map = tag.tagCtxs[i].map; // Copy over the compiled map instance from the previous tagCtxs to the refreshed ones
460			}
461			if (!tag.flow) {
462				tagCtxCtx = tagCtx.ctx = tagCtx.ctx || {};
463
464				// tags hash: tag.ctx.tags, merged with parentView.ctx.tags,
465				tags = tag.parents = tagCtxCtx.parentTags = ctx && extendCtx(tagCtxCtx.parentTags, ctx.parentTags) || {};
466				if (parentTag) {
467					tags[parentTag.tagName] = parentTag;
468					//TODO better perf for childTags: parentTag.tags.push(tag);
469				}
470				tagCtxCtx.tag = tag;
471			}
472		}
473		tag.tagCtxs = tagCtxs;
474		parentView_.tag = tag;
475		tag.rendering = {}; // Provide object for state during render calls to tag and elses. (Used by {{if}} and {{for}}...)
476		for (i = 0; i < l; i++) {
477			tagCtx = tag.tagCtx = tagCtxs[i];
478			args = tagCtx.args;
479			if ((map = tagCtx.props.map || tag).map) {
480				if (args.length || !i || tagCtx.props.map) {
481					if (!args.length) {
482						args = [parentView.data]; // If opening tag or else with map property have no arg[0] specified, default the datamap source to the current data item.
483					}
484					thisMap = tagCtx.map = $extend(tagCtx.map || { unmap: map.unmap }, tagCtx.props); // Compiled map instance
485					if (thisMap.src !== args[0]) {
486						if (thisMap.src) {
487							thisMap.unmap();
488						}
489						map.map.apply(thisMap, args);
490					}
491					args = [thisMap.tgt];
492				}
493			}
494			tag.ctx = tagCtx.ctx;
495
496			if (!i && callInit) {
497				tag.init(tagCtx, linkCtx, tag.ctx);
498				tagCtx = tag.tagCtx;
499				callInit = undefined;
500			}
501
502			itemRet = undefined;
503			render = tag.render;
504			if (render = tag.render) {
505				itemRet = render.apply(tag, args);
506			}
507			itemRet = itemRet !== undefined
508				? itemRet // Return result of render function unless it is undefined, in which case return rendered template
509				: tagCtx.tmpl && tagCtx.render(args[0], undefined, true) || (isRefresh ? undefined : "");
510				// No return value from render, and no template/content tagCtx.render(), so return undefined
511			ret = ret ? ret + (itemRet || "") : itemRet; // If no rendered content, this will be undefined
512		}
513
514		delete tag.rendering;
515
516		tag.tagCtx = tag.tagCtxs[0];
517		tag.ctx= tag.tagCtx.ctx;
518
519		if (tag._.inline && (attr = tag.attr) && attr !== htmlStr) {
520			// inline tag with attr set to "text" will insert HTML-encoded content - as if it was element-based innerText
521			ret = attr === "text"
522				? $converters.html(ret)
523				: "";
524		}
525		return boundTagKey && parentView._.onRender
526			// Call onRender (used by JsViews if present, to add binding annotations around rendered content)
527			? parentView._.onRender(ret, parentView, boundTagKey)
528			: ret;
529	}
530
531	//=================
532	// View constructor
533	//=================
534
535	function View(context, type, parentView, data, template, key, contentTmpl, onRender) {
536		// Constructor for view object in view hierarchy. (Augmented by JsViews if JsViews is loaded)
537		var views, parentView_, tag,
538			isArray = type === "array",
539			self_ = {
540				key: 0,
541				useKey: isArray ? 0 : 1,
542				id: "" + viewId++,
543				onRender: onRender,
544				bnds: {}
545			},
546			self = {
547				data: data,
548				tmpl: template,
549				content: contentTmpl,
550				views: isArray ? [] : {},
551				parent: parentView,
552				type: type,
553				// If the data is an array, this is an 'array view' with a views array for each child 'item view'
554				// If the data is not an array, this is an 'item view' with a views 'map' object for any child nested views
555				// ._.useKey is non zero if is not an 'array view' (owning a data array). Uuse this as next key for adding to child views map
556				get: getView,
557				getIndex: getIndex,
558				getRsc: getResource,
559				hlp: getHelper,
560				_: self_,
561				_is: "view"
562		};
563		if (parentView) {
564			views = parentView.views;
565			parentView_ = parentView._;
566			if (parentView_.useKey) {
567				// Parent is an 'item view'. Add this view to its views object
568				// self._key = is the key in the parent view map
569				views[self_.key = "_" + parentView_.useKey++] = self;
570				self.index = indexStr;
571				self.getIndex = getNestedIndex;
572				tag = parentView_.tag;
573				self_.bnd = isArray && (!tag || !!tag._.bnd && tag); // For array views that are data bound for collection change events, set the
574				// view._.bnd property to true for top-level link() or data-link="{for}", or to the tag instance for a data-bound tag, e.g. {^{for ...}}
575			} else {
576				// Parent is an 'array view'. Add this view to its views array
577				views.splice(
578					// self._.key = self.index - the index in the parent view array
579					self_.key = self.index = key,
580				0, self);
581			}
582			// If no context was passed in, use parent context
583			// If context was passed in, it should have been merged already with parent context
584			self.ctx = context || parentView.ctx;
585		} else {
586			self.ctx = context;
587		}
588		return self;
589	}
590
591	//=============
592	// Registration
593	//=============
594
595	function compileChildResources(parentTmpl) {
596		var storeName, resources, resourceName, settings, compile;
597		for (storeName in jsvStores) {
598			settings = jsvStores[storeName];
599			if ((compile = settings.compile) && (resources = parentTmpl[storeName + "s"])) {
600				for (resourceName in resources) {
601					// compile child resource declarations (templates, tags, converters or helpers)
602					resources[resourceName] = compile(resourceName, resources[resourceName], parentTmpl, storeName, settings);
603				}
604			}
605		}
606	}
607
608	function compileTag(name, tagDef, parentTmpl) {
609		var init, tmpl;
610		if (typeof tagDef === "function") {
611			// Simple tag declared as function. No presenter instantation.
612			tagDef = {
613				depends: tagDef.depends,
614				render: tagDef
615			};
616		} else {
617			// Tag declared as object, used as the prototype for tag instantiation (control/presenter)
618			if (tmpl = tagDef.template) {
619				tagDef.template = "" + tmpl === tmpl ? ($templates[tmpl] || $templates(tmpl)) : tmpl;
620			}
621			if (tagDef.init !== false) {
622				// Set int: false on tagDef if you want to provide just a render method, or render and template, but no constuctor or prototype.
623				// so equivalent to setting tag to render function, except you can also provide a template.
624				init = tagDef._ctr = function(tagCtx) {};
625				(init.prototype = tagDef).constructor = init;
626			}
627		}
628		if (parentTmpl) {
629			tagDef._parentTmpl = parentTmpl;
630		}
631//TODO	tagDef.onError = function(e) {
632//			var error;
633//			if (error = this.prototype.onError) {
634//				error.call(this, e);
635//			} else {
636//				throw e;
637//			}
638//		}
639		return tagDef;
640	}
641
642	function compileTmpl(name, tmpl, parentTmpl, storeName, storeSettings, options) {
643		// tmpl is either a template object, a selector for a template script block, the name of a compiled template, or a template object
644
645		//==== nested functions ====
646		function tmplOrMarkupFromStr(value) {
647			// If value is of type string - treat as selector, or name of compiled template
648			// Return the template object, if already compiled, or the markup string
649
650			if (("" + value === value) || value.nodeType > 0) {
651				try {
652					elem = value.nodeType > 0
653					? value
654					: !rTmplString.test(value)
655					// If value is a string and does not contain HTML or tag content, then test as selector
656						&& jQuery && jQuery(global.document).find(value)[0];
657					// If selector is valid and returns at least one element, get first element
658					// If invalid, jQuery will throw. We will stay with the original string.
659				} catch (e) {}
660
661				if (elem) {
662					// Generally this is a script element.
663					// However we allow it to be any element, so you can for example take the content of a div,
664					// use it as a template, and replace it by the same content rendered against data.
665					// e.g. for linking the content of a div to a container, and using the initial content as template:
666					// $.link("#content", model, {tmpl: "#content"});
667
668					value = elem.getAttribute(tmplAttr);
669					name = name || value;
670					value = $templates[value];
671					if (!value) {
672						// Not already compiled and cached, so compile and cache the name
673						// Create a name for compiled template if none provided
674						name = name || "_" + autoTmplName++;
675						elem.setAttribute(tmplAttr, name);
676						// Use tmpl as options
677						value = $templates[name] = compileTmpl(name, elem.innerHTML, parentTmpl, storeName, storeSettings, options);
678					}
679				}
680				return value;
681			}
682			// If value is not a string, return undefined
683		}
684
685		var tmplOrMarkup, elem;
686
687		//==== Compile the template ====
688		tmpl = tmpl || "";
689		tmplOrMarkup = tmplOrMarkupFromStr(tmpl);
690
691		// If options, then this was already compiled from a (script) element template declaration.
692		// If not, then if tmpl is a template object, use it for options
693		options = options || (tmpl.markup ? tmpl : {});
694		options.tmplName = name;
695		if (parentTmpl) {
696			options._parentTmpl = parentTmpl;
697		}
698		// If tmpl is not a markup string or a selector string, then it must be a template object
699		// In that case, get it from the markup property of the object
700		if (!tmplOrMarkup && tmpl.markup && (tmplOrMarkup = tmplOrMarkupFromStr(tmpl.markup))) {
701			if (tmplOrMarkup.fn && (tmplOrMarkup.debug !== tmpl.debug || tmplOrMarkup.allowCode !== tmpl.allowCode)) {
702				// if the string references a compiled template object, but the debug or allowCode props are different, need to recompile
703				tmplOrMarkup = tmplOrMarkup.markup;
704			}
705		}
706		if (tmplOrMarkup !== undefined) {
707			if (name && !parentTmpl) {
708				$render[name] = function() {
709					return tmpl.render.apply(tmpl, arguments);
710				};
711			}
712			if (tmplOrMarkup.fn || tmpl.fn) {
713				// tmpl is already compiled, so use it, or if different name is provided, clone it
714				if (tmplOrMarkup.fn) {
715					if (name && name !== tmplOrMarkup.tmplName) {
716						tmpl = extendCtx(options, tmplOrMarkup);
717					} else {
718						tmpl = tmplOrMarkup;
719					}
720				}
721			} else {
722				// tmplOrMarkup is a markup string, not a compiled template
723				// Create template object
724				tmpl = TmplObject(tmplOrMarkup, options);
725				// Compile to AST and then to compiled function
726				tmplFn(tmplOrMarkup.replace(rEscapeQuotes, "\\$&"), tmpl);
727			}
728			compileChildResources(options);
729			return tmpl;
730		}
731	}
732	//==== /end of function compile ====
733
734	function TmplObject(markup, options) {
735		// Template object constructor
736		var htmlTag,
737			wrapMap = $viewsSettings.wrapMap || {},
738			tmpl = $extend(
739				{
740					markup: markup,
741					tmpls: [],
742					links: {}, // Compiled functions for link expressions
743					tags: {}, // Compiled functions for bound tag expressions
744					bnds: [],
745					_is: "template",
746					render: renderContent
747				},
748				options
749			);
750
751		if (!options.htmlTag) {
752			// Set tmpl.tag to the top-level HTML tag used in the template, if any...
753			htmlTag = rFirstElem.exec(markup);
754			tmpl.htmlTag = htmlTag ? htmlTag[1].toLowerCase() : "";
755		}
756		htmlTag = wrapMap[tmpl.htmlTag];
757		if (htmlTag && htmlTag !== wrapMap.div) {
758			// When using JsViews, we trim templates which are inserted into HTML contexts where text nodes are not rendered (i.e. not 'Phrasing Content').
759			// Currently not trimmed for <li> tag. (Not worth adding perf cost)
760			tmpl.markup = $.trim(tmpl.markup);
761		}
762
763		return tmpl;
764	}
765
766	function registerStore(storeName, storeSettings) {
767
768		function theStore(name, item, parentTmpl) {
769			// The store is also the function used to add items to the store. e.g. $.templates, or $.views.tags
770
771			// For store of name 'thing', Call as:
772			//    $.views.things(items[, parentTmpl]),
773			// or $.views.things(name, item[, parentTmpl])
774
775			var onStore, compile, itemName, thisStore;
776
777			if (name && "" + name !== name && !name.nodeType && !name.markup) {
778				// Call to $.views.things(items[, parentTmpl]),
779
780				// Adding items to the store
781				// If name is a map, then item is parentTmpl. Iterate over map and call store for key.
782				for (itemName in name) {
783					theStore(itemName, name[itemName], item);
784				}
785				return $views;
786			}
787			// Adding a single unnamed item to the store
788			if (item === undefined) {
789				item = name;
790				name = undefined;
791			}
792			if (name && "" + name !== name) { // name must be a string
793				parentTmpl = item;
794				item = name;
795				name = undefined;
796			}
797			thisStore = parentTmpl ? parentTmpl[storeNames] = parentTmpl[storeNames] || {} : theStore;
798			compile = storeSettings.compile;
799			if (onStore = $viewsSub.onBeforeStoreItem) {
800				// e.g. provide an external compiler or preprocess the item.
801				compile = onStore(thisStore, name, item, compile) || compile;
802			}
803			if (!name) {
804				item = compile(undefined, item);
805			} else if (item === null) {
806				// If item is null, delete this entry
807				delete thisStore[name];
808			} else {
809				thisStore[name] = compile ? (item = compile(name, item, parentTmpl, storeName, storeSettings)) : item;
810			}
811			if (compile && item) {
812				item._is = storeName;
812 // Only do this for compiled objects (tags, templates...)
813			}
814			if (onStore = $viewsSub.onStoreItem) {
815				// e.g. JsViews integration
816				onStore(thisStore, name, item, compile);
817			}
818			return item;
819		}
820
821		var storeNames = storeName + "s";
822
823		$views[storeNames] = theStore;
824		jsvStores[storeName] = storeSettings;
825	}
826
827	//==============
828	// renderContent
829	//==============
830
831	function renderContent(data, context, noIteration, parentView, key, onRender) {
832		// Render template against data as a tree of subviews (nested rendered template instances), or as a string (top-level template).
833		// If the data is the parent view, treat as noIteration, re-render with the same data context.
834		var i, l, dataItem, newView, childView, itemResult, swapContent, tagCtx, contentTmpl, tag_, outerOnRender, tmplName, tmpl,
835			self = this,
836			allowDataLink = !self.attr || self.attr === htmlStr,
837			result = "";
838
839		if (key === true) {
840			swapContent = true;
841			key = 0;
842		}
843		if (self.tag) {
844			// This is a call from renderTag or tagCtx.render()
845			tagCtx = self;
846			self = self.tag;
847			tag_ = self._;
848			tmplName = self.tagName;
849			tmpl = tagCtx.tmpl;
850			context = extendCtx(context, self.ctx);
851			contentTmpl = tagCtx.content; // The wrapped content - to be added to views, below
852			if (tagCtx.props.link === false) {
853				// link=false setting on block tag
854				// We will override inherited value of link by the explicit setting link=false taken from props
855				// The child views of an unlinked view are also unlinked. So setting child back to true will not have any effect.
856				context = context || {};
857				context.link = false;
858			}
859			parentView = parentView || tagCtx.view;
860			data = data === undefined ? parentView : data;
861		} else {
862			tmpl = self.jquery && (self[0] || error('Unknown template: "' + self.selector + '"')) // This is a call from $(selector).render
863				|| self;
864		}
865		if (tmpl) {
866			if (!parentView && data && data._is === "view") {
867				parentView = data; // When passing in a view to render or link (and not passing in a parent view) use the passed in view as parentView
868			}
869			if (parentView) {
870				contentTmpl = contentTmpl || parentView.content; // The wrapped content - to be added as #content property on views, below
871				onRender = onRender || parentView._.onRender;
872				if (data === parentView) {
873					// Inherit the data from the parent view.
874					// This may be the contents of an {{if}} block
875					// Set isLayout = true so we don't iterate the if block if the data is an array.
876					data = parentView.data;
877					noIteration = true;
878				}
879				context = extendCtx(context, parentView.ctx);
880			}
881			if (!parentView || parentView.data === undefined) {
882				(context = context || {}).root = data; // Provide ~root as shortcut to top-level data.
883			}
884
885			// Set additional context on views created here, (as modified context inherited from the parent, and to be inherited by child views)
886			// Note: If no jQuery, $extend does not support chained copies - so limit extend() to two parameters
887
888			if (!tmpl.fn) {
889				tmpl = $templates[tmpl] || $templates(tmpl);
890			}
891
892			if (tmpl) {
893				onRender = (context && context.link) !== false && allowDataLink && onRender;
894				// If link===false, do not call onRender, so no data-linking marker nodes
895				outerOnRender = onRender;
896				if (onRender === true) {
897					// Used by view.refresh(). Don't create a new wrapper view.
898					outerOnRender = undefined;
899					onRender = parentView._.onRender;
900				}
901				context = tmpl.helpers
902					? extendCtx(tmpl.helpers, context)
903					: context;
904				if ($.isArray(data) && !noIteration) {
905					// Create a view for the array, whose child views correspond to each data item. (Note: if key and parentView are passed in
906					// along with parent view, treat as insert -e.g. from view.addViews - so parentView is already the view item for array)
907					newView = swapContent
908						? parentView :
909						(key !== undefined && parentView) || View(context, "array", parentView, data, tmpl, key, contentTmpl, onRender);
910					for (i = 0, l = data.length; i < l; i++) {
911						// Create a view for each data item.
912						dataItem = data[i];
913						childView = View(context, "item", newView, dataItem, tmpl, (key || 0) + i, contentTmpl, onRender);
914						itemResult = tmpl.fn(dataItem, childView, $views);
915						result += newView._.onRender ? newView._.onRender(itemResult, childView) : itemResult;
916					}
917				} else {
918					// Create a view for singleton data object. The type of the view will be the tag name, e.g. "if" or "myTag" except for
919					// "item", "array" and "data" views. A "data" view is from programatic render(object) against a 'singleton'.
920					newView = swapContent ? parentView : View(context, tmplName || "data", parentView, data, tmpl, key, contentTmpl, onRender);
921					if (tag_ && !self.flow) {
922						newView.tag = self;
923					}
924					result += tmpl.fn(data, newView, $views);
925				}
926				return outerOnRender ? outerOnRender(result, newView) : result;
927			}
928		}
929		return "";
930	}
931
932	//===========================
933	// Build and compile template
934	//===========================
935
936	// Generate a reusable function that will serve to render a template against data
937	// (Compile AST then build template function)
938
939	function error(message) {
940		throw new $viewsSub.Error(message);
941	}
942
943	function syntaxError(message) {
944		error("Syntax error\n" + message);
945	}
946
947	function tmplFn(markup, tmpl, isLinkExpr, convertBack) {
948		// Compile markup to AST (abtract syntax tree) then build the template function code from the AST nodes
949		// Used for compiling templates, and also by JsViews to build functions for data link expressions
950
951		//==== nested functions ====
952		function pushprecedingContent(shift) {
953			shift -= loc;
954			if (shift) {
955				content.push(markup.substr(loc, shift).replace(rNewLine, "\\n"));
956			}
957		}
958
959		function blockTagCheck(tagName) {
960			tagName && syntaxError('Unmatched or missing tag: "{{/' + tagName + '}}" in template:\n' + markup);
961		}
962
963		function parseTag(all, bind, tagName, converter, colon, html, comment, codeTag, params, slash, closeBlock, index) {
964
965			//    bind         tag        converter colon html     comment            code      params            slash   closeBlock
966			// /{(\^)?{(?:(?:(\w+(?=[\/\s}]))|(?:(\w+)?(:)|(>)|!--((?:[^-]|-(?!-))*)--|(\*)))\s*((?:[^}]|}(?!}))*?)(\/)?|(?:\/(\w+)))}}/g
967			// Build abstract syntax tree (AST): [tagName, converter, params, content, hash, bindings, contentMarkup]
968			if (html) {
969				colon = ":";
970				converter = htmlStr;
971			}
972			slash = slash || isLinkExpr;
973			var noError, current0,
974				pathBindings = bind && [],
975				code = "",
976				hash = "",
977				passedCtx = "",
978				// Block tag if not self-closing and not {{:}} or {{>}} (special case) and not a data-link expression
979				block = !slash && !colon && !comment;
980
981			//==== nested helper function ====
982			tagName = tagName || colon;
983			pushprecedingContent(index);
984			loc = index + all.length; // location marker - parsed up to here
985			if (codeTag) {
986				if (allowCode) {
987					content.push(["*", "\n" + params.replace(rUnescapeQuotes, "$1") + "\n"]);
988				}
989			} else if (tagName) {
990				if (tagName === "else") {
991					if (rTestElseIf.test(params)) {
992						syntaxError('for "{{else if expr}}" use "{{else expr}}"');
993					}
994					pathBindings = current[6];
995					current[7] = markup.substring(current[7], index); // contentMarkup for block tag
996					current = stack.pop();
997					content = current[3];
998					block = true;
999				}
1000				if (params) {
1001					// remove newlines from the params string, to avoid compiled code errors for unterminated strings
1002					params = params.replace(rNewLine, " ");
1003					code = parseParams(params, pathBindings, tmpl)
1004						.replace(rBuildHash, function(all, isCtx, keyValue) {
1005							if (isCtx) {
1006								passedCtx += keyValue + ",";
1007							} else {
1008								hash += keyValue + ",";
1009							}
1010							return "";
1011						});
1012				}
1013				hash = hash.slice(0, -1);
1014				code = code.slice(0, -1);
1015				noError = hash && (hash.indexOf("noerror:true") + 1) && hash || "";
1016
1017				newNode = [
1018						tagName,
1019						converter || !!convertBack || "",
1020						code,
1021						block && [],
1022						'params:"' + params + '",props:{' + hash + "}"
1023							+ (passedCtx ? ",ctx:{" + passedCtx.slice(0, -1) + "}" : ""),
1024						noError,
1025						pathBindings || 0
1026					];
1027				content.push(newNode);
1028				if (block) {
1029					stack.push(current);
1030					current = newNode;
1031					current[7] = loc; // Store current location of open tag, to be able to add contentMarkup when we reach closing tag
1032				}
1033			} else if (closeBlock) {
1034				current0 = current[0];
1035				blockTagCheck(closeBlock !== current0 && current0 !== "else" && closeBlock);
1036				current[7] = markup.substring(current[7], index); // contentMarkup for block tag
1037				current = stack.pop();
1038			}
1039			blockTagCheck(!current && closeBlock);
1040			content = current[3];
1041		}
1042		//==== /end of nested functions ====
1043
1044		var newNode,
1045			allowCode = tmpl && tmpl.allowCode,
1046			astTop = [],
1047			loc = 0,
1048			stack = [],
1049			content = astTop,
1050			current = [, , , astTop];
1051
1052//TODO	result = tmplFnsCache[markup]; // Only cache if template is not named and markup length < ...,
1053//and there are no bindings or subtemplates?? Consider standard optimization for data-link="a.b.c"
1054//		if (result) {
1055//			tmpl.fn = result;
1056//		} else {
1057
1058//		result = markup;
1059
1060		blockTagCheck(stack[0] && stack[0][3].pop()[0]);
1061		// Build the AST (abstract syntax tree) under astTop
1062		markup.replace(rTag, parseTag);
1063
1064		pushprecedingContent(markup.length);
1065
1066		if (loc = astTop[astTop.length - 1]) {
1067			blockTagCheck("" + loc !== loc && (+loc[7] === loc[7]) && loc[0]);
1068		}
1069//			result = tmplFnsCache[markup] = buildCode(astTop, tmpl);
1070//		}
1071		return buildCode(astTop, isLinkExpr ? markup : tmpl, isLinkExpr);
1072	}
1073
1074	function buildCode(ast, tmpl, isLinkExpr) {
1075		// Build the template function code from the AST nodes, and set as property on the passed-in template object
1076		// Used for compiling templates, and also by JsViews to build functions for data link expressions
1077		var i, node, tagName, converter, params, hash, hasTag, hasEncoder, getsVal, hasCnvt, useCnvt, tmplBindings, pathBindings,
1078			nestedTmpls, tmplName, nestedTmpl, tagAndElses, content, markup, nextIsElse, oldCode, isElse, isGetVal, prm, tagCtxFn,
1079			tmplBindingKey = 0,
1080			code = "",
1081			noError = "",
1082			tmplOptions = {},
1083			l = ast.length;
1084
1085		if ("" + tmpl === tmpl) {
1086			tmplName = isLinkExpr ? 'data-link="' + tmpl.replace(rNewLine, " ").slice(1, -1) + '"' : tmpl;
1087			tmpl = 0;
1088		} else {
1089			tmplName = tmpl.tmplName || "unnamed";
1090			if (tmpl.allowCode) {
1091				tmplOptions.allowCode = true;
1092			}
1093			if (tmpl.debug) {
1094				tmplOptions.debug = true;
1095			}
1096			tmplBindings = tmpl.bnds;
1097			nestedTmpls = tmpl.tmpls;
1098		}
1099		for (i = 0; i < l; i++) {
1100			// AST nodes: [tagName, converter, params, content, hash, noError, pathBindings, contentMarkup, link]
1101			node = ast[i];
1102
1103			// Add newline for each callout to t() c() etc. and each markup string
1104			if ("" + node === node) {
1105				// a markup string to be inserted
1106				code += '\nret+="' + node + '";';
1107			} else {
1108				// a compiled tag expression to be inserted
1109				tagName = node[0];
1110				if (tagName === "*") {
1111					// Code tag: {{* }}
1112					code += "" + node[1];
1113				} else {
1114					converter = node[1];
1115					params = node[2];
1116					content = node[3];
1117					hash = node[4];
1118					noError = node[5];
1119					markup = node[7];
1120
1121					if (!(isElse = tagName === "else")) {
1122						tmplBindingKey = 0;
1123						if (tmplBindings && (pathBindings = node[6])) { // Array of paths, or false if not data-bound
1124							tmplBindingKey = tmplBindings.push(pathBindings);
1125						}
1126					}
1127					if (isGetVal = tagName === ":") {
1128						if (converter) {
1129							tagName = converter === htmlStr ? ">" : converter + tagName;
1130						}
1131						if (noError) {
1132							// If the tag includes noerror=true, we will do a try catch around expressions for named or unnamed parameters
1133							// passed to the tag, and return the empty string for each expression if it throws during evaluation
1134							//TODO This does not work for general case - supporting noError on multiple expressions, e.g. tag args and properties.
1135							//Consider replacing with try<a.b.c(p,q) + a.d, xxx> and return the value of the expression a.b.c(p,q) + a.d, or, if it throws, return xxx||'' (rather than always the empty string)
1136							prm = "prm" + i;
1137							noError = "try{var " + prm + "=[" + params + "][0];}catch(e){" + prm + '="";}\n';
1138							params = prm;
1139						}
1140					} else {
1141						if (content) {
1142							// Create template object for nested template
1143							nestedTmpl = TmplObject(markup, tmplOptions);
1144							nestedTmpl.tmplName = tmplName + "/" + tagName;
1145							// Compile to AST and then to compiled function
1146							buildCode(content, nestedTmpl);
1147							nestedTmpls.push(nestedTmpl);
1148						}
1149
1150						if (!isElse) {
1151							// This is not an else tag.
1152							tagAndElses = tagName;
1153							// Switch to a new code string for this bound tag (and its elses, if it has any) - for returning the tagCtxs array
1154							oldCode = code;
1155							code = "";
1156						}
1157						nextIsElse = ast[i + 1];
1158						nextIsElse = nextIsElse && nextIsElse[0] === "else";
1159					}
1160
1161					hash += ",args:[" + params + "]}";
1162
1163					if (isGetVal && pathBindings || converter && tagName !== ">") {
1164						// For convertVal we need a compiled function to return the new tagCtx(s)
1165						tagCtxFn = new Function("data,view,j,u", " // "
1166									+ tmplName + " " + tmplBindingKey + " " + tagName + "\n" + noError + "return {" + hash + ";");
1167						tagCtxFn.paths = pathBindings;
1168						tagCtxFn._ctxs = tagName;
1169						if (isLinkExpr) {
1170							return tagCtxFn;
1171						}
1172						useCnvt = 1;
1173					}
1174
1175					code += (isGetVal
1176						? "\n" + (pathBindings ? "" : noError) + (isLinkExpr ? "return " : "ret+=") + (useCnvt // Call _cnvt if there is a converter: {{cnvt: ... }} or {^{cnvt: ... }}
1177							? (useCnvt = 0, hasCnvt = true, 'c("' + converter + '",view,' + (pathBindings
1178								? ((tmplBindings[tmplBindingKey - 1] = tagCtxFn), tmplBindingKey) // Store the compiled tagCtxFn in tmpl.bnds, and pass the key to convertVal()
1179								: "{" + hash) + ");")
1180							: tagName === ">"
1181								? (hasEncoder = true, "h(" + params + ");")
1182								: (getsVal = true, "(v=" + params + ")!=" + (isLinkExpr ? "=" : "") + 'u?v:"";') // Strict equality just for data-link="title{:expr}" so expr=null will remove title attribute
1183						)
1184						: (hasTag = true, "{view:view,tmpl:" // Add this tagCtx to the compiled code for the tagCtxs to be passed to renderTag()
1185							+ (content ? nestedTmpls.length: "0") + "," // For block tags, pass in the key (nestedTmpls.length) to the nested content template
1186							+ hash + ","));
1187
1188					if (tagAndElses && !nextIsElse) {
1189						code = "[" + code.slice(0, -1) + "]"; // This is a data-link expression or the last {{else}} of an inline bound tag. We complete the code for returning the tagCtxs array
1190						if (isLinkExpr || pathBindings) {
1191							// This is a bound tag (data-link expression or inline bound tag {^{tag ...}}) so we store a compiled tagCtxs function in tmp.bnds
1192							code = new Function("data,view,j,u", " // " + tmplName + " " + tmplBindingKey + " " + tagAndElses + "\nreturn " + code + ";");
1193							if (pathBindings) {
1194								(tmplBindings[tmplBindingKey - 1] = code).paths = pathBindings;
1195							}
1196							code._ctxs = tagName;
1197							if (isLinkExpr) {
1198								return code; // For a data-link expression we return the compiled tagCtxs function
1199							}
1200						}
1201
1202						// This is the last {{else}} for an inline tag.
1203						// For a bound tag, pass the tagCtxs fn lookup key to renderTag.
1204						// For an unbound tag, include the code directly for evaluating tagCtxs array
1205						code = oldCode + '\nret+=t("' + tagAndElses + '",view,this,' + (tmplBindingKey || code) + ");";
1206						pathBindings = 0;
1207						tagAndElses = 0;
1208					}
1209				}
1210			}
1211		}
1212		// Include only the var references that are needed in the code
1213		code = "// " + tmplName
1214			+ "\nvar j=j||" + (jQuery ? "jQuery." : "js") + "views"
1215			+ (getsVal ? ",v" : "")                      // gets value
1216			+ (hasTag ? ",t=j._tag" : "")                // has tag
1217			+ (hasCnvt ? ",c=j._cnvt" : "")              // converter
1218			+ (hasEncoder ? ",h=j.converters.html" : "") // html converter
1219			+ (isLinkExpr ? ";\n" : ',ret="";\n')
1220			+ ($viewsSettings.tryCatch ? "try{\n" : "")
1221			+ (tmplOptions.debug ? "debugger;" : "")
1222			+ code + (isLinkExpr ? "\n" : "\nreturn ret;\n")
1223			+ ($viewsSettings.tryCatch ? "\n}catch(e){return j._err(e);}" : "");
1224		try {
1225			code = new Function("data,view,j,u", code);
1226		} catch (e) {
1227			syntaxError("Compiled template code:\n\n" + code, e);
1228		}
1229		if (tmpl) {
1230			tmpl.fn = code;
1231		}
1232		return code;
1233	}
1234
1235	function parseParams(params, bindings, tmpl) {
1236
1237		//function pushBindings() { // Consider structured path bindings
1238		//	if (bindings) {
1239		//		named ? bindings[named] = bindings.pop(): bindings.push(list = []);
1240		//	}
1241		//}
1242
1243		function parseTokens(all, lftPrn0, lftPrn, bound, path, operator, err, eq, path2, prn, comma, lftPrn2, apos, quot, rtPrn, rtPrnDot, prn2, space, index, full) {
1244			//rParams = /(\()(?=\s*\()|(?:([([])\s*)?(?:(\^?)(!*?[#~]?[\w$.^]+)?\s*((\+\+|--)|\+|-|&&|\|\||===|!==|==|!=|<=|>=|[<>%*:?\/]|(=))\s*|(!*?[#~]?[\w$.^]+)([([])?)|(,\s*)|(\(?)\\?(?:(')|("))|(?:\s*(([)\]])(?=\s*\.|\s*\^)|[)\]])([([]?))|(\s+)/g,
1245			//          lftPrn0        lftPrn        bound            path    operator err                                                eq             path2       prn    comma   lftPrn2   apos quot      rtPrn rtPrnDot                        prn2      space
1246			// (left paren? followed by (path? followed by operator) or (path followed by paren?)) or comma or apos or quot or right paren or space
1247			var expr;
1248			operator = operator || "";
1249			lftPrn = lftPrn || lftPrn0 || lftPrn2;
1250			path = path || path2;
1251			prn = prn || prn2 || "";
1252
1253			function parsePath(allPath, not, object, helper, view, viewProperty, pathTokens, leafToken) {
1254				// rPath = /^(?:null|true|false|\d[\d.]*|(!*?)([\w$]+|\.|~([\w$]+)|#(view|([\w$]+))?)([\w$.^]*?)(?:[.[^]([\w$]+)\]?)?)$/g,
1255				//                                        none   object     helper    view  viewProperty pathTokens      leafToken
1256				if (object) {
1257					if (bindings) {
1258						if (named === "linkTo") {
1259							bindto = bindings._jsvto = bindings._jsvto || [];
1260							bindto.push(path);
1261						}
1262						if (!named || boundName) {
1263							bindings.push(path.slice(not.length)); // Add path binding for paths on props and args,
1264//							list.push(path);
1265						}
1266					}
1267					if (object !== ".") {
1268						var ret = (helper
1269								? 'view.hlp("' + helper + '")'
1270								: view
1271									? "view"
1272									: "data")
1273							+ (leafToken
1274								? (viewProperty
1275									? "." + viewProperty
1276									: helper
1277										? ""
1278										: (view ? "" : "." + object)
1279									) + (pathTokens || "")
1280								: (leafToken = helper ? "" : view ? viewProperty || "" : object, ""));
1281
1282						ret = ret + (leafToken ? "." + leafToken : "");
1283
1284						return not + (ret.slice(0, 9) === "view.data"
1285							? ret.slice(5) // convert #view.data... to data...
1286							: ret);
1287					}
1288				}
1289				return allPath;
1290			}
1291
1292			if (err && !aposed && !quoted) {
1293				syntaxError(params);
1294			} else {
1295				if (bindings && rtPrnDot && !aposed && !quoted) {
1296					// This is a binding to a path in which an object is returned by a helper/data function/expression, e.g. foo()^x.y or (a?b:c)^x.y
1297					// We create a compiled function to get the object instance (which will be called when the dependent data of the subexpression changes, to return the new object, and trigger re-binding of the subsequent path)
1298					if (!named || boundName || bindto) {
1299						expr = pathStart[parenDepth];
1300						if (full.length - 1 > index - expr) { // We need to compile a subexpression
1301							expr = full.slice(expr, index + 1);
1302							rtPrnDot = delimOpenChar1 + ":" + expr + delimCloseChar0; // The parameter or function subexpression
1303							//TODO Optimize along the lines of:
1304							//var paths = [];
1305							//rtPrnDot = tmplLinks[rtPrnDot] = tmplLinks[rtPrnDot] || tmplFn(delimOpenChar0 + rtPrnDot + delimCloseChar1, tmpl, true, paths); // Compile the expression (or use cached copy already in tmpl.links)
1306							//rtPrnDot.paths = rtPrnDot.paths || paths;
1307
1308							rtPrnDot = tmplLinks[rtPrnDot] = tmplLinks[rtPrnDot] || tmplFn(delimOpenChar0 + rtPrnDot + delimCloseChar1, tmpl, true); // Compile the expression (or use cached copy already in tmpl.links)
1309							if (!rtPrnDot.paths) {
1310								parseParams(expr, rtPrnDot.paths = [], tmpl);
1311							}
1312							(bindto || bindings).push({_jsvOb: rtPrnDot}); // Insert special object for in path bindings, to be used for binding the compiled sub expression ()
1313							//list.push({_jsvOb: rtPrnDot});
1314						}
1315					}
1316				}
1317				return (aposed
1318					// within single-quoted string
1319					? (aposed = !apos, (aposed ? all : '"'))
1320					: quoted
1321					// within double-quoted string
1322						? (quoted = !quot, (quoted ? all : '"'))
1323						:
1324					(
1325						(lftPrn
1326								? (parenDepth++, pathStart[parenDepth] = index++, lftPrn)
1327								: "")
1328						+ (space
1329							? (parenDepth
1330								? ""
1331								//: (pushBindings(), named
1332								//	: ",")
1333								: named
1334									? (named = boundName = bindto = false, "\b")
1335									: ","
1336							)
1337							: eq
1338					// named param
1339					// Insert backspace \b (\x08) as separator for named params, used subsequently by rBuildHash
1340								? (parenDepth && syntaxError(params), named = path, boundName = bound, /*pushBindings(),*/ '\b' + path + ':')
1341								: path
1342					// path
1343									? (path.split("^").join(".").replace(rPath, parsePath)
1344										+ (prn
1345											? (fnCall[++parenDepth] = true, path.charAt(0) !== "." && (pathStart[parenDepth] = index), prn)
1346											: operator)
1347									)
1348									: operator
1349										? operator
1350										: rtPrn
1351					// function
1352											? ((fnCall[parenDepth--] = false, rtPrn)
1353												+ (prn
1354													? (fnCall[++parenDepth] = true, prn)
1355													: "")
1356											)
1357											: comma
1358												? (fnCall[parenDepth] || syntaxError(params), ",") // We don't allow top-level literal arrays or objects
1359												: lftPrn0
1360													? ""
1361													: (aposed = apos, quoted = quot, '"')
1362					))
1363				);
1364			}
1365		}
1366
1367		var named, bindto, boundName, // list,
1368			tmplLinks = tmpl.links,
1369			fnCall = {},
1370			pathStart = {0:-1},
1371			parenDepth = 0,
1372			quoted = false, // boolean for string content in double quotes
1373			aposed = false; // or in single quotes
1374
1375		//pushBindings();
1376
1377		return (params + " ")
1378			.replace(/\)\^/g, ").") // Treat "...foo()^bar..." as equivalent to "...foo().bar..."
1379								//since preceding computed observables in the path will always be updated if their dependencies change
1380			.replace(rParams, parseTokens);
1381	}
1382
1383	//==========
1384	// Utilities
1385	//==========
1386
1387	// Merge objects, in particular contexts which inherit from parent contexts
1388	function extendCtx(context, parentContext) {
1389		// Return copy of parentContext, unless context is defined and is different, in which case return a new merged context
1390		// If neither context nor parentContext are defined, return undefined
1391		return context && context !== parentContext
1392			? (parentContext
1393				? $extend($extend({}, parentContext), context)
1394				: context)
1395			: parentContext && $extend({}, parentContext);
1396	}
1397
1398	// Get character entity for HTML and Attribute encoding
1399	function getCharEntity(ch) {
1400		return charEntities[ch] || (charEntities[ch] = "&#" + ch.charCodeAt(0) + ";");
1401	}
1402
1403	//========================== Initialize ==========================
1404
1405	for (jsvStoreName in jsvStores) {
1406		registerStore(jsvStoreName, jsvStores[jsvStoreName]);
1407	}
1408
1409	var $observable,
1410		$templates = $views.templates,
1411		$converters = $views.converters,
1412		$helpers = $views.helpers,
1413		$tags = $views.tags,
1414		$viewsSub = $views.sub,
1415		$viewsSettings = $views.settings;
1416
1417	if (jQuery) {
1418		////////////////////////////////////////////////////////////////////////////////////////////////
1419		// jQuery is loaded, so make $ the jQuery object
1420		$ = jQuery;
1421		$.fn.render = renderContent;
1422		if ($observable = $.observable) {
1423			$extend($viewsSub, $observable.sub); // jquery.observable.js was loaded before jsrender.js
1424			delete $observable.sub;
1425		}
1426	} else {
1427		////////////////////////////////////////////////////////////////////////////////////////////////
1428		// jQuery is not loaded.
1429
1430		$ = global.jsviews = {};
1431
1432		$.isArray = Array && Array.isArray || function(obj) {
1433			return Object.prototype.toString.call(obj) === "[object Array]";
1434		};
1435
1436	//	//========================== Future Node.js support ==========================
1437	//	if ((nodeJsModule = global.module) && nodeJsModule.exports) {
1438	//		nodeJsModule.exports = $;
1439	//	}
1440	}
1441
1442	$.render = $render;
1443	$.views = $views;
1444	$.templates = $templates = $views.templates;
1445
1446	$viewsSettings({
1447		debugMode: dbgMode,
1448		delimiters: $viewsDelimiters,
1449		_dbgMode: true,
1450		tryCatch: true
1451	});
1452
1453	//========================== Register tags ==========================
1454
1455	$tags({
1456		"else": function() {}, // Does nothing but ensures {{else}} tags are recognized as valid
1457		"if": {
1458			render: function(val) {
1459				// This function is called once for {{if}} and once for each {{else}}.
1460				// We will use the tag.rendering object for carrying rendering state across the calls.
1461				// If not done (a previous block has not been rendered), look at expression for this block and render the block if expression is truthy
1462				// Otherwise return ""
1463				var self = this,
1464					ret = (self.rendering.done || !val && (arguments.length || !self.tagCtx.index))
1465						? ""
1466						: (self.rendering.done = true, self.selected = self.tagCtx.index,
1467							// Test is satisfied, so render content on current context. We call tagCtx.render() rather than return undefined
1468							// (which would also render the tmpl/content on the current context but would iterate if it is an array)
1469							self.tagCtx.render());
1470				return ret;
1471			},
1472			onUpdate: function(ev, eventArgs, tagCtxs) {
1473				var tci, prevArg, different;
1474				for (tci = 0; (prevArg = this.tagCtxs[tci]) && prevArg.args.length; tci++) {
1475					prevArg = prevArg.args[0];
1476					different = !prevArg !== !tagCtxs[tci].args[0];
1477					if (!!prevArg || different) {
1478						return different;
1479						// If newArg and prevArg are both truthy, return false to cancel update. (Even if values on later elses are different, we still don't want to update, since rendered output would be unchanged)
1480						// If newArg and prevArg are different, return true, to update
1481						// If newArg and prevArg are both falsey, move to the next {{else ...}}
1482					}
1483				}
1484				// Boolean value of all args are unchanged (falsey), so return false to cancel update
1485				return false;
1486			},
1487			flow: true
1488		},
1489		"for": {
1490			render: function(val) {
1491				// This function is called once for {{for}} and once for each {{else}}.
1492				// We will use the tag.rendering object for carrying rendering state across the calls.
1493				var self = this,
1494					tagCtx = self.tagCtx,
1495					result = "",
1496					done = 0;
1497
1498				if (!arguments.length) {
1499					val = tagCtx.view.data; // Defaults to current data. If current data is an array, iterates over data. (Use {{include}} to compose templates without array iteration)
1500				}
1501
1502				if (!self.rendering.done) {
1503					if (val !== undefined) {
1504						result += tagCtx.render(val);
1505						done += $.isArray(val) ? val.length : 1;
1506					}
1507					if (self.rendering.done = done) {
1508						self.selected = tagCtx.index;
1509					}
1510					// If nothing was rendered we will look at the next {{else}}. Otherwise, we are done.
1511				}
1512				return result;
1513			},
1514			flow: true
1515		},
1516		include: {
1517			flow: true
1518		},
1519		"*": {
1520			// {{* code... }} - Ignored if template.allowCode is false. Otherwise include code in compiled template
1521			render: retVal,
1522			flow: true
1523		}
1524	});
1525
1526	function getTargetProps(source) {
1527		// this pointer is theMap - which has tagCtx.props too
1528		// arguments: tagCtx.args.
1529		var key, prop,
1530			props = [];
1531
1532		if (typeof source === "object") {
1533			for (key in source) {
1534				prop = source[key];
1535				if (!prop || !prop.toJSON || prop.toJSON()) {
1536					if (typeof prop !== "function") {
1537						props.push({ key: key, prop: source[key] });
1538					}
1539				}
1540			}
1541		}
1542		return props;
1543	}
1544
1545	$tags({
1546		props: $extend($extend({}, $tags["for"]),
1547			DataMap(getTargetProps)
1548		)
1549	});
1550
1551	//========================== Register converters ==========================
1552
1553	$converters({
1554		html: function(text) {
1555			// HTML encode: Replace < > & and ' and " by corresponding entities.
1556			return text != undefined ? String(text).replace(rHtmlEncode, getCharEntity) : ""; // null and undefined return ""
1557		},
1558		attr: function(text) {
1559			// Attribute encode: Replace < > & ' and " by corresponding entities.
1560			return text != undefined ? String(text).replace(rAttrEncode, getCharEntity) : text === null ? text : ""; // null returns null, e.g. to remove attribute. undefined returns ""
1561		},
1562		url: function(text) {
1563			// URL encoding helper.
1564			return text != undefined ? encodeURI(String(text)) : text === null ? text : ""; // null returns null, e.g. to remove attribute. undefined returns ""
1565		}
1566	});
1567
1568	//========================== Define default delimiters ==========================
1569	$viewsDelimiters();
1570
1571})(this, this.jQuery);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.