Proyectos de Subversion LeadersLinked - Antes de SPA

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
6056 efrain 1
/*! JsRender v1.0.7: http://jsviews.com/#jsrender */
2
/*! **VERSION FOR WEB** (For NODE.JS see http://jsviews.com/download/jsrender-node.js) */
3
/*
4
 * Best-of-breed templating in browser or on Node.js.
5
 * Does not require jQuery, or HTML DOM
6
 * Integrates with JsViews (http://jsviews.com/#jsviews)
7
 *
8
 * Copyright 2020, Boris Moore
9
 * Released under the MIT License.
10
 */
11
 
12
//jshint -W018, -W041, -W120
13
 
14
(function(factory, global) {
15
	// global var is the this object, which is window when running in the usual browser environment
16
	var $ = global.jQuery;
17
 
18
	if (typeof exports === "object") { // CommonJS e.g. Browserify
19
		module.exports = $
20
			? factory(global, $)
21
			: function($) { // If no global jQuery, take optional jQuery passed as parameter: require('jsrender')(jQuery)
22
				if ($ && !$.fn) {
23
					throw "Provide jQuery or null";
24
				}
25
				return factory(global, $);
26
			};
27
	} else if (typeof define === "function" && define.amd) { // AMD script loader, e.g. RequireJS
28
		define(function() {
29
			return factory(global);
30
		});
31
	} else { // Browser using plain <script> tag
32
		factory(global, false);
33
	}
34
} (
35
 
36
// factory (for jsrender.js)
37
function(global, $) {
38
"use strict";
39
 
40
//========================== Top-level vars ==========================
41
 
42
// global var is the this object, which is window when running in the usual browser environment
43
var setGlobals = $ === false; // Only set globals if script block in browser (not AMD and not CommonJS)
44
 
45
$ = $ && $.fn ? $ : global.jQuery; // $ is jQuery passed in by CommonJS loader (Browserify), or global jQuery.
46
 
47
var versionNumber = "v1.0.7",
48
	jsvStoreName, rTag, rTmplString, topView, $views, $expando,
49
	_ocp = "_ocp",      // Observable contextual parameter
50
 
51
	$isFunction, $isArray, $templates, $converters, $helpers, $tags, $sub, $subSettings, $subSettingsAdvanced, $viewsSettings,
52
	delimOpenChar0, delimOpenChar1, delimCloseChar0, delimCloseChar1, linkChar, setting, baseOnError,
53
 
54
	isRenderCall,
55
	rNewLine = /[ \t]*(\r\n|\n|\r)/g,
56
	rUnescapeQuotes = /\\(['"\\])/g, // Unescape quotes and trim
57
	rEscapeQuotes = /['"\\]/g, // Escape quotes and \ character
58
	rBuildHash = /(?:\x08|^)(onerror:)?(?:(~?)(([\w$.]+):)?([^\x08]+))\x08(,)?([^\x08]+)/gi,
59
	rTestElseIf = /^if\s/,
60
	rFirstElem = /<(\w+)[>\s]/,
61
	rAttrEncode = /[\x00`><"'&=]/g, // Includes > encoding since rConvertMarkers in JsViews does not skip > characters in attribute strings
62
	rIsHtml = /[\x00`><\"'&=]/,
63
	rHasHandlers = /^on[A-Z]|^convert(Back)?$/,
64
	rWrappedInViewMarker = /^\#\d+_`[\s\S]*\/\d+_`$/,
65
	rHtmlEncode = rAttrEncode,
66
	rDataEncode = /[&<>]/g,
67
	rDataUnencode = /&(amp|gt|lt);/g,
68
	rBracketQuote = /\[['"]?|['"]?\]/g,
69
	viewId = 0,
70
	charEntities = {
71
		"&": "&amp;",
72
		"<": "&lt;",
73
		">": "&gt;",
74
		"\x00": "&#0;",
75
		"'": "&#39;",
76
		'"': "&#34;",
77
		"`": "&#96;",
78
		"=": "&#61;"
79
	},
80
	charsFromEntities = {
81
		amp: "&",
82
		gt: ">",
83
		lt: "<"
84
	},
85
	HTML = "html",
86
	OBJECT = "object",
87
	tmplAttr = "data-jsv-tmpl",
88
	jsvTmpl = "jsvTmpl",
89
	indexStr = "For #index in nested block use #getIndex().",
90
	cpFnStore = {},     // Compiled furnctions for computed values in template expressions (properties, methods, helpers)
91
	$render = {},
92
 
93
	jsr = global.jsrender,
94
	jsrToJq = jsr && $ && !$.render, // JsRender already loaded, without jQuery. but we will re-load it now to attach to jQuery
95
 
96
	jsvStores = {
97
		template: {
98
			compile: compileTmpl
99
		},
100
		tag: {
101
			compile: compileTag
102
		},
103
		viewModel: {
104
			compile: compileViewModel
105
		},
106
		helper: {},
107
		converter: {}
108
	};
109
 
110
	// views object ($.views if jQuery is loaded, jsrender.views if no jQuery, e.g. in Node.js)
111
	$views = {
112
		jsviews: versionNumber,
113
		sub: {
114
			// subscription, e.g. JsViews integration
115
			rPath: /^(!*?)(?:null|true|false|\d[\d.]*|([\w$]+|\.|~([\w$]+)|#(view|([\w$]+))?)([\w$.^]*?)(?:[.[^]([\w$]+)\]?)?)$/g,
116
			//        not                               object     helper    view  viewProperty pathTokens      leafToken
117
 
118
			rPrm: /(\()(?=\s*\()|(?:([([])\s*)?(?:(\^?)(~?[\w$.^]+)?\s*((\+\+|--)|\+|-|~(?![\w$])|&&|\|\||===|!==|==|!=|<=|>=|[<>%*:?\/]|(=))\s*|(!*?(@)?[#~]?[\w$.^]+)([([])?)|(,\s*)|(?:(\()\s*)?\\?(?:(')|("))|(?:\s*(([)\]])(?=[.^]|\s*$|[^([])|[)\]])([([]?))|(\s+)/g,
119
			//   lftPrn0           lftPrn         bound     path               operator     err                                          eq      path2 late            prn      comma  lftPrn2          apos quot        rtPrn  rtPrnDot                  prn2     space
120
 
121
			View: View,
122
			Err: JsViewsError,
123
			tmplFn: tmplFn,
124
			parse: parseParams,
125
			extend: $extend,
126
			extendCtx: extendCtx,
127
			syntaxErr: syntaxError,
128
			onStore: {
129
				template: function(name, item) {
130
					if (item === null) {
131
						delete $render[name];
132
					} else if (name) {
133
						$render[name] = item;
134
					}
135
				}
136
			},
137
			addSetting: addSetting,
138
			settings: {
139
				allowCode: false
140
			},
141
			advSet: noop, // Update advanced settings
142
			_thp: tagHandlersFromProps,
143
			_gm: getMethod,
144
			_tg: function() {}, // Constructor for tagDef
145
			_cnvt: convertVal,
146
			_tag: renderTag,
147
			_er: error,
148
			_err: onRenderError,
149
			_cp: retVal, // Get observable contextual parameters (or properties) ~foo=expr. In JsRender, simply returns val.
150
			_sq: function(token) {
151
				if (token === "constructor") {
152
					syntaxError("");
153
				}
154
				return token;
155
			}
156
		},
157
		settings: {
158
			delimiters: $viewsDelimiters,
159
			advanced: function(value) {
160
				return value
161
					? (
162
							$extend($subSettingsAdvanced, value),
163
							$sub.advSet(),
164
							$viewsSettings
165
						)
166
						: $subSettingsAdvanced;
167
				}
168
		},
169
		map: dataMap // If jsObservable loaded first, use that definition of dataMap
170
	};
171
 
172
function getDerivedMethod(baseMethod, method) {
173
	return function() {
174
		var ret,
175
			tag = this,
176
			prevBase = tag.base;
177
 
178
		tag.base = baseMethod; // Within method call, calling this.base will call the base method
179
		ret = method.apply(tag, arguments); // Call the method
180
		tag.base = prevBase; // Replace this.base to be the base method of the previous call, for chained calls
181
		return ret;
182
	};
183
}
184
 
185
function getMethod(baseMethod, method) {
186
	// For derived methods (or handlers declared declaratively as in {{:foo onChange=~fooChanged}} replace by a derived method, to allow using this.base(...)
187
	// or this.baseApply(arguments) to call the base implementation. (Equivalent to this._super(...) and this._superApply(arguments) in jQuery UI)
188
	if ($isFunction(method)) {
189
		method = getDerivedMethod(
190
				!baseMethod
191
					? noop // no base method implementation, so use noop as base method
192
					: baseMethod._d
193
						? baseMethod // baseMethod is a derived method, so use it
194
						: getDerivedMethod(noop, baseMethod), // baseMethod is not derived so make its base method be the noop method
195
				method
196
			);
197
		method._d = (baseMethod && baseMethod._d || 0) + 1; // Add flag for derived method (incremented for derived of derived...)
198
	}
199
	return method;
200
}
201
 
202
function tagHandlersFromProps(tag, tagCtx) {
203
	var prop,
204
		props = tagCtx.props;
205
	for (prop in props) {
206
		if (rHasHandlers.test(prop) && !(tag[prop] && tag[prop].fix)) { // Don't override handlers with fix expando (used in datepicker and spinner)
207
			tag[prop] = prop !== "convert" ? getMethod(tag.constructor.prototype[prop], props[prop]) : props[prop];
208
			// Copy over the onFoo props, convert and convertBack from tagCtx.props to tag (overrides values in tagDef).
209
			// Note: unsupported scenario: if handlers are dynamically added ^onFoo=expression this will work, but dynamically removing will not work.
210
		}
211
	}
212
}
213
 
214
function retVal(val) {
215
	return val;
216
}
217
 
218
function noop() {
219
	return "";
220
}
221
 
222
function dbgBreak(val) {
223
	// Usage examples: {{dbg:...}}, {{:~dbg(...)}}, {{dbg .../}}, {^{for ... onAfterLink=~dbg}} etc.
224
	try {
225
		console.log("JsRender dbg breakpoint: " + val);
226
		throw "dbg breakpoint"; // To break here, stop on caught exceptions.
227
	}
228
	catch (e) {}
229
	return this.base ? this.baseApply(arguments) : val;
230
}
231
 
232
function JsViewsError(message) {
233
	// Error exception type for JsViews/JsRender
234
	// Override of $.views.sub.Error is possible
235
	this.name = ($.link ? "JsViews" : "JsRender") + " Error";
236
	this.message = message || this.name;
237
}
238
 
239
function $extend(target, source) {
240
	if (target) {
241
		for (var name in source) {
242
			target[name] = source[name];
243
		}
244
		return target;
245
	}
246
}
247
 
248
(JsViewsError.prototype = new Error()).constructor = JsViewsError;
249
 
250
//========================== Top-level functions ==========================
251
 
252
//===================
253
// views.delimiters
254
//===================
255
 
256
	/**
257
	* Set the tag opening and closing delimiters and 'link' character. Default is "{{", "}}" and "^"
258
	* openChars, closeChars: opening and closing strings, each with two characters
259
	* $.views.settings.delimiters(...)
260
	*
261
	* @param {string}   openChars
262
	* @param {string}   [closeChars]
263
	* @param {string}   [link]
264
	* @returns {Settings}
265
	*
266
	* Get delimiters
267
	* delimsArray = $.views.settings.delimiters()
268
	*
269
	* @returns {string[]}
270
	*/
271
function $viewsDelimiters(openChars, closeChars, link) {
272
	if (!openChars) {
273
		return $subSettings.delimiters;
274
	}
275
	if ($isArray(openChars)) {
276
		return $viewsDelimiters.apply($views, openChars);
277
	}
278
	linkChar = link ? link[0] : linkChar;
279
	if (!/^(\W|_){5}$/.test(openChars + closeChars + linkChar)) {
280
		error("Invalid delimiters"); // Must be non-word characters, and openChars and closeChars must each be length 2
281
	}
282
	delimOpenChar0 = openChars[0];
283
	delimOpenChar1 = openChars[1];
284
	delimCloseChar0 = closeChars[0];
285
	delimCloseChar1 = closeChars[1];
286
 
287
	$subSettings.delimiters = [delimOpenChar0 + delimOpenChar1, delimCloseChar0 + delimCloseChar1, linkChar];
288
 
289
	// Escape the characters - since they could be regex special characters
290
	openChars = "\\" + delimOpenChar0 + "(\\" + linkChar + ")?\\" + delimOpenChar1; // Default is "{^{"
291
	closeChars = "\\" + delimCloseChar0 + "\\" + delimCloseChar1;                   // Default is "}}"
292
	// Build regex with new delimiters
293
	//          [tag    (followed by / space or })  or cvtr+colon or html or code] followed by space+params then convertBack?
294
	rTag = "(?:(\\w+(?=[\\/\\s\\" + delimCloseChar0 + "]))|(\\w+)?(:)|(>)|(\\*))\\s*((?:[^\\"
295
		+ delimCloseChar0 + "]|\\" + delimCloseChar0 + "(?!\\" + delimCloseChar1 + "))*?)";
296
 
297
	// Make rTag available to JsViews (or other components) for parsing binding expressions
298
	$sub.rTag = "(?:" + rTag + ")";
299
	//                        { ^? {   tag+params slash?  or closingTag                                                   or comment
300
	rTag = new RegExp("(?:" + openChars + rTag + "(\\/)?|\\" + delimOpenChar0 + "(\\" + linkChar + ")?\\" + delimOpenChar1 + "(?:(?:\\/(\\w+))\\s*|!--[\\s\\S]*?--))" + closeChars, "g");
301
 
302
	// Default:  bind     tagName         cvt   cln html code    params            slash   bind2         closeBlk  comment
303
	//      /(?:{(\^)?{(?:(\w+(?=[\/\s}]))|(\w+)?(:)|(>)|(\*))\s*((?:[^}]|}(?!}))*?)(\/)?|{(\^)?{(?:(?:\/(\w+))\s*|!--[\s\S]*?--))}}
304
 
305
	$sub.rTmpl = new RegExp("^\\s|\\s$|<.*>|([^\\\\]|^)[{}]|" + openChars + ".*" + closeChars);
306
	// $sub.rTmpl looks for initial or final white space, html tags or { or } char not preceded by \\, or JsRender tags {{xxx}}.
307
	// Each of these strings are considered NOT to be jQuery selectors
308
	return $viewsSettings;
309
}
310
 
311
//=========
312
// View.get
313
//=========
314
 
315
function getView(inner, type) { //view.get(inner, type)
316
	if (!type && inner !== true) {
317
		// view.get(type)
318
		type = inner;
319
		inner = undefined;
320
	}
321
 
322
	var views, i, l, found,
323
		view = this,
324
		root = type === "root";
325
		// view.get("root") returns view.root, view.get() returns view.parent, view.get(true) returns view.views[0].
326
 
327
	if (inner) {
328
		// Go through views - this one, and all nested ones, depth-first - and return first one with given type.
329
		// If type is undefined, i.e. view.get(true), return first child view.
330
		found = type && view.type === type && view;
331
		if (!found) {
332
			views = view.views;
333
			if (view._.useKey) {
334
				for (i in views) {
335
					if (found = type ? views[i].get(inner, type) : views[i]) {
336
						break;
337
					}
338
				}
339
			} else {
340
				for (i = 0, l = views.length; !found && i < l; i++) {
341
					found = type ? views[i].get(inner, type) : views[i];
342
				}
343
			}
344
		}
345
	} else if (root) {
346
		// Find root view. (view whose parent is top view)
347
		found = view.root;
348
	} else if (type) {
349
		while (view && !found) {
350
			// Go through views - this one, and all parent ones - and return first one with given type.
351
			found = view.type === type ? view : undefined;
352
			view = view.parent;
353
		}
354
	} else {
355
		found = view.parent;
356
	}
357
	return found || undefined;
358
}
359
 
360
function getNestedIndex() {
361
	var view = this.get("item");
362
	return view ? view.index : undefined;
363
}
364
 
365
getNestedIndex.depends = function() {
366
	return [this.get("item"), "index"];
367
};
368
 
369
function getIndex() {
370
	return this.index;
371
}
372
 
373
getIndex.depends = "index";
374
 
375
//==================
376
// View.ctxPrm, etc.
377
//==================
378
 
379
/* Internal private: view._getOb() */
380
function getPathObject(ob, path, ltOb, fn) {
381
	// Iterate through path to late paths: @a.b.c paths
382
	// Return "" (or noop if leaf is a function @a.b.c(...) ) if intermediate object not yet available
383
	var prevOb, tokens, l,
384
		i = 0;
385
	if (ltOb === 1) {
386
		fn = 1;
387
		ltOb = undefined;
388
	}
389
	// Paths like ^a^b^c or ~^a^b^c will not throw if an object in path is undefined.
390
	if (path) {
391
		tokens = path.split(".");
392
		l = tokens.length;
393
 
394
		for (; ob && i < l; i++) {
395
			prevOb = ob;
396
			ob = tokens[i] ? ob[tokens[i]] : ob;
397
		}
398
	}
399
	if (ltOb) {
400
		ltOb.lt = ltOb.lt || i<l; // If i < l there was an object in the path not yet available
401
	}
402
	return ob === undefined
403
		? fn ? noop : ""
404
		: fn ? function() {
405
			return ob.apply(prevOb, arguments);
406
		} : ob;
407
}
408
 
409
function contextParameter(key, value, get) {
410
	// Helper method called as view.ctxPrm(key) for helpers or template parameters ~foo - from compiled template or from context callback
411
	var wrapped, deps, res, obsCtxPrm, tagElse, callView, newRes,
412
		storeView = this,
413
		isUpdate = !isRenderCall && arguments.length > 1,
414
		store = storeView.ctx;
415
	if (key) {
416
		if (!storeView._) { // tagCtx.ctxPrm() call
417
			tagElse = storeView.index;
418
			storeView = storeView.tag;
419
		}
420
		callView = storeView;
421
		if (store && store.hasOwnProperty(key) || (store = $helpers).hasOwnProperty(key)) {
422
			res = store[key];
423
			if (key === "tag" || key === "tagCtx" || key === "root" || key === "parentTags") {
424
				return res;
425
			}
426
		} else {
427
			store = undefined;
428
		}
429
		if (!isRenderCall && storeView.tagCtx || storeView.linked) { // Data-linked view, or tag instance
430
			if (!res || !res._cxp) {
431
				// Not a contextual parameter
432
				// Set storeView to tag (if this is a tag.ctxPrm() call) or to root view ("data" view of linked template)
433
				storeView = storeView.tagCtx || $isFunction(res)
434
					? storeView // Is a tag, not a view, or is a computed contextual parameter, so scope to the callView, no the 'scope view'
435
					: (storeView = storeView.scope || storeView,
436
						!storeView.isTop && storeView.ctx.tag // If this view is in a tag, set storeView to the tag
437
							|| storeView);
438
				if (res !== undefined && storeView.tagCtx) {
439
					// If storeView is a tag, but the contextual parameter has been set at at higher level (e.g. helpers)...
440
					storeView = storeView.tagCtx.view.scope; // then move storeView to the outer level (scope of tag container view)
441
				}
442
				store = storeView._ocps;
443
				res = store && store.hasOwnProperty(key) && store[key] || res;
444
				if (!(res && res._cxp) && (get || isUpdate)) {
445
					// Create observable contextual parameter
446
					(store || (storeView._ocps = storeView._ocps || {}))[key]
447
						= res
448
						= [{
449
							_ocp: res, // The observable contextual parameter value
450
							_vw: callView,
451
							_key: key
452
						}];
453
					res._cxp = {
454
						path: _ocp,
455
						ind: 0,
456
						updateValue: function(val, path) {
457
							$.observable(res[0]).setProperty(_ocp, val); // Set the value (res[0]._ocp)
458
							return this;
459
						}
460
					};
461
				}
462
			}
463
			if (obsCtxPrm = res && res._cxp) {
464
				// If this helper resource is an observable contextual parameter
465
				if (arguments.length > 2) {
466
					deps = res[1] ? $sub._ceo(res[1].deps) : [_ocp]; // fn deps (with any exprObs cloned using $sub._ceo)
467
					deps.unshift(res[0]); // view
468
					deps._cxp = obsCtxPrm;
469
					// In a context callback for a contextual param, we set get = true, to get ctxPrm [view, dependencies...] array - needed for observe call
470
					return deps;
471
				}
472
				tagElse = obsCtxPrm.tagElse;
473
				newRes = res[1] // linkFn for compiled expression
474
					? obsCtxPrm.tag && obsCtxPrm.tag.cvtArgs
475
						? obsCtxPrm.tag.cvtArgs(tagElse, 1)[obsCtxPrm.ind] // = tag.bndArgs() - for tag contextual parameter
476
						: res[1](res[0].data, res[0], $sub) // = fn(data, view, $sub) for compiled binding expression
477
					: res[0]._ocp; // Observable contextual parameter (uninitialized, or initialized as static expression, so no path dependencies)
478
				if (isUpdate) {
479
					$sub._ucp(key, value, storeView, obsCtxPrm); // Update observable contextual parameter
480
					return storeView;
481
				}
482
				res = newRes;
483
			}
484
		}
485
		if (res && $isFunction(res)) {
486
			// If a helper is of type function we will wrap it, so if called with no this pointer it will be called with the
487
			// view as 'this' context. If the helper ~foo() was in a data-link expression, the view will have a 'temporary' linkCtx property too.
488
			// Note that helper functions on deeper paths will have specific this pointers, from the preceding path.
489
			// For example, ~util.foo() will have the ~util object as 'this' pointer
490
			wrapped = function() {
491
				return res.apply((!this || this === global) ? callView : this, arguments);
492
			};
493
			$extend(wrapped, res); // Attach same expandos (if any) to the wrapped function
494
		}
495
		return wrapped || res;
496
	}
497
}
498
 
499
/* Internal private: view._getTmpl() */
500
function getTemplate(tmpl) {
501
	return tmpl && (tmpl.fn
502
		? tmpl
503
		: this.getRsc("templates", tmpl) || $templates(tmpl)); // not yet compiled
504
}
505
 
506
//==============
507
// views._cnvt
508
//==============
509
 
510
function convertVal(converter, view, tagCtx, onError) {
511
	// Called from compiled template code for {{:}}
512
	// self is template object or linkCtx object
513
	var tag, linkCtx, value, argsLen, bindTo,
514
		// If tagCtx is an integer, then it is the key for the compiled function to return the boundTag tagCtx
515
		boundTag = typeof tagCtx === "number" && view.tmpl.bnds[tagCtx-1];
516
 
517
	if (onError === undefined && boundTag && boundTag._lr) { // lateRender
518
		onError = "";
519
	}
520
	if (onError !== undefined) {
521
		tagCtx = onError = {props: {}, args: [onError]};
522
	} else if (boundTag) {
523
		tagCtx = boundTag(view.data, view, $sub);
524
	}
525
	boundTag = boundTag._bd && boundTag;
526
	if (converter || boundTag) {
527
		linkCtx = view._lc; // For data-link="{cvt:...}"... See onDataLinkedTagChange
528
		tag = linkCtx && linkCtx.tag;
529
		tagCtx.view = view;
530
		if (!tag) {
531
			tag = $extend(new $sub._tg(), {
532
				_: {
533
					bnd: boundTag,
534
					unlinked: true,
535
					lt: tagCtx.lt // If a late path @some.path has not returned @some object, mark tag as late
536
				},
537
				inline: !linkCtx,
538
				tagName: ":",
539
				convert: converter,
540
				onArrayChange: true,
541
				flow: true,
542
				tagCtx: tagCtx,
543
				tagCtxs: [tagCtx],
544
				_is: "tag"
545
			});
546
			argsLen = tagCtx.args.length;
547
			if (argsLen>1) {
548
				bindTo = tag.bindTo = [];
549
				while (argsLen--) {
550
					bindTo.unshift(argsLen); // Bind to all the arguments - generate bindTo array: [0,1,2...]
551
				}
552
			}
553
			if (linkCtx) {
554
				linkCtx.tag = tag;
555
				tag.linkCtx = linkCtx;
556
			}
557
			tagCtx.ctx = extendCtx(tagCtx.ctx, (linkCtx ? linkCtx.view : view).ctx);
558
			tagHandlersFromProps(tag, tagCtx);
559
		}
560
		tag._er = onError && value;
561
		tag.ctx = tagCtx.ctx || tag.ctx || {};
562
		tagCtx.ctx = undefined;
563
		value = tag.cvtArgs()[0]; // If there is a convertBack but no convert, converter will be "true"
564
		tag._er = onError && value;
565
	} else {
566
		value = tagCtx.args[0];
567
	}
568
 
569
	// Call onRender (used by JsViews if present, to add binding annotations around rendered content)
570
	value = boundTag && view._.onRender
571
		? view._.onRender(value, view, tag)
572
		: value;
573
	return value != undefined ? value : "";
574
}
575
 
576
function convertArgs(tagElse, bound) { // tag.cvtArgs() or tag.cvtArgs(tagElse?, true?)
577
	var l, key, boundArgs, args, bindFrom, tag, converter,
578
		tagCtx = this;
579
 
580
	if (tagCtx.tagName) {
581
		tag = tagCtx;
582
		tagCtx = (tag.tagCtxs || [tagCtx])[tagElse||0];
583
		if (!tagCtx) {
584
			return;
585
		}
586
	} else {
587
		tag = tagCtx.tag;
588
	}
589
 
590
	bindFrom = tag.bindFrom;
591
	args = tagCtx.args;
592
 
593
	if ((converter = tag.convert) && "" + converter === converter) {
594
		converter = converter === "true"
595
			? undefined
596
			: (tagCtx.view.getRsc("converters", converter) || error("Unknown converter: '" + converter + "'"));
597
	}
598
 
599
	if (converter && !bound) { // If there is a converter, use a copy of the tagCtx.args array for rendering, and replace the args[0] in
600
		args = args.slice(); // the copied array with the converted value. But we do not modify the value of tag.tagCtx.args[0] (the original args array)
601
	}
602
	if (bindFrom) { // Get the values of the boundArgs
603
		boundArgs = [];
604
		l = bindFrom.length;
605
		while (l--) {
606
			key = bindFrom[l];
607
			boundArgs.unshift(argOrProp(tagCtx, key));
608
		}
609
		if (bound) {
610
			args = boundArgs; // Call to bndArgs() - returns the boundArgs
611
		}
612
	}
613
	if (converter) {
614
		converter = converter.apply(tag, boundArgs || args);
615
		if (converter === undefined) {
616
			return args; // Returning undefined from a converter is equivalent to not having a converter.
617
		}
618
		bindFrom = bindFrom || [0];
619
		l = bindFrom.length;
620
		if (!$isArray(converter) || converter.length !== l) {
621
			converter = [converter];
622
			bindFrom = [0];
623
			l = 1;
624
		}
625
		if (bound) {        // Call to bndArgs() - so apply converter to all boundArgs
626
			args = converter; // The array of values returned from the converter
627
		} else {            // Call to cvtArgs()
628
			while (l--) {
629
				key = bindFrom[l];
630
				if (+key === key) {
631
					args[key] = converter[l];
632
				}
633
			}
634
		}
635
	}
636
	return args;
637
}
638
 
639
function argOrProp(context, key) {
640
	context = context[+key === key ? "args" : "props"];
641
	return context && context[key];
642
}
643
 
644
function convertBoundArgs(tagElse) { // tag.bndArgs()
645
	return this.cvtArgs(tagElse, 1);
646
}
647
 
648
//=============
649
// views.tag
650
//=============
651
 
652
/* view.getRsc() */
653
function getResource(resourceType, itemName) {
654
	var res, store,
655
		view = this;
656
	if ("" + itemName === itemName) {
657
		while ((res === undefined) && view) {
658
			store = view.tmpl && view.tmpl[resourceType];
659
			res = store && store[itemName];
660
			view = view.parent;
661
		}
662
		return res || $views[resourceType][itemName];
663
	}
664
}
665
 
666
function renderTag(tagName, parentView, tmpl, tagCtxs, isUpdate, onError) {
667
	function bindToOrBindFrom(type) {
668
		var bindArray = tag[type];
669
 
670
		if (bindArray !== undefined) {
671
			bindArray = $isArray(bindArray) ? bindArray : [bindArray];
672
			m = bindArray.length;
673
			while (m--) {
674
				key = bindArray[m];
675
				if (!isNaN(parseInt(key))) {
676
					bindArray[m] = parseInt(key); // Convert "0" to 0, etc.
677
				}
678
			}
679
		}
680
 
681
		return bindArray || [0];
682
	}
683
 
684
	parentView = parentView || topView;
685
	var tag, tagDef, template, tags, attr, parentTag, l, m, n, itemRet, tagCtx, tagCtxCtx, ctxPrm, bindTo, bindFrom, initVal,
686
		content, callInit, mapDef, thisMap, args, bdArgs, props, tagDataMap, contentCtx, key, bindFromLength, bindToLength, linkedElement, defaultCtx,
687
		i = 0,
688
		ret = "",
689
		linkCtx = parentView._lc || false, // For data-link="{myTag...}"... See onDataLinkedTagChange
690
		ctx = parentView.ctx,
691
		parentTmpl = tmpl || parentView.tmpl,
692
		// If tagCtxs is an integer, then it is the key for the compiled function to return the boundTag tagCtxs
693
		boundTag = typeof tagCtxs === "number" && parentView.tmpl.bnds[tagCtxs-1];
694
 
695
	if (tagName._is === "tag") {
696
		tag = tagName;
697
		tagName = tag.tagName;
698
		tagCtxs = tag.tagCtxs;
699
		template = tag.template;
700
	} else {
701
		tagDef = parentView.getRsc("tags", tagName) || error("Unknown tag: {{" + tagName + "}} ");
702
		template = tagDef.template;
703
	}
704
	if (onError === undefined && boundTag && (boundTag._lr = (tagDef.lateRender && boundTag._lr!== false || boundTag._lr))) {
705
		onError = ""; // If lateRender, set temporary onError, to skip initial rendering (and render just "")
706
	}
707
	if (onError !== undefined) {
708
		ret += onError;
709
		tagCtxs = onError = [{props: {}, args: [], params: {props:{}}}];
710
	} else if (boundTag) {
711
		tagCtxs = boundTag(parentView.data, parentView, $sub);
712
	}
713
 
714
	l = tagCtxs.length;
715
	for (; i < l; i++) {
716
		tagCtx = tagCtxs[i];
717
		content = tagCtx.tmpl;
718
		if (!linkCtx || !linkCtx.tag || i && !linkCtx.tag.inline || tag._er || content && +content===content) {
719
			// Initialize tagCtx
720
			// For block tags, tagCtx.tmpl is an integer > 0
721
			if (content && parentTmpl.tmpls) {
722
				tagCtx.tmpl = tagCtx.content = parentTmpl.tmpls[content - 1]; // Set the tmpl property to the content of the block tag
723
			}
724
			tagCtx.index = i;
725
			tagCtx.ctxPrm = contextParameter;
726
			tagCtx.render = renderContent;
727
			tagCtx.cvtArgs = convertArgs;
728
			tagCtx.bndArgs = convertBoundArgs;
729
			tagCtx.view = parentView;
730
			tagCtx.ctx = extendCtx(extendCtx(tagCtx.ctx, tagDef && tagDef.ctx), ctx); // Clone and extend parentView.ctx
731
		}
732
		if (tmpl = tagCtx.props.tmpl) {
733
			// If the tmpl property is overridden, set the value (when initializing, or, in case of binding: ^tmpl=..., when updating)
734
			tagCtx.tmpl = parentView._getTmpl(tmpl);
735
			tagCtx.content = tagCtx.content || tagCtx.tmpl;
736
		}
737
 
738
		if (!tag) {
739
			// This will only be hit for initial tagCtx (not for {{else}}) - if the tag instance does not exist yet
740
			// If the tag has not already been instantiated, we will create a new instance.
741
			// ~tag will access the tag, even within the rendering of the template content of this tag.
742
			// From child/descendant tags, can access using ~tag.parent, or ~parentTags.tagName
743
			tag = new tagDef._ctr();
744
			callInit = !!tag.init;
745
 
746
			tag.parent = parentTag = ctx && ctx.tag;
747
			tag.tagCtxs = tagCtxs;
748
 
749
			if (linkCtx) {
750
				tag.inline = false;
751
				linkCtx.tag = tag;
752
			}
753
			tag.linkCtx = linkCtx;
754
			if (tag._.bnd = boundTag || linkCtx.fn) {
755
				// Bound if {^{tag...}} or data-link="{tag...}"
756
				tag._.ths = tagCtx.params.props.this; // Tag has a this=expr binding, to get javascript reference to tag instance
757
				tag._.lt = tagCtxs.lt; // If a late path @some.path has not returned @some object, mark tag as late
758
				tag._.arrVws = {};
759
			} else if (tag.dataBoundOnly) {
760
				error(tagName + " must be data-bound:\n{^{" + tagName + "}}");
761
			}
762
			//TODO better perf for childTags() - keep child tag.tags array, (and remove child, when disposed)
763
			// tag.tags = [];
764
		} else if (linkCtx && linkCtx.fn._lr) {
765
			callInit = !!tag.init;
766
		}
767
		tagDataMap = tag.dataMap;
768
 
769
		tagCtx.tag = tag;
770
		if (tagDataMap && tagCtxs) {
771
			tagCtx.map = tagCtxs[i].map; // Copy over the compiled map instance from the previous tagCtxs to the refreshed ones
772
		}
773
		if (!tag.flow) {
774
			tagCtxCtx = tagCtx.ctx = tagCtx.ctx || {};
775
 
776
			// tags hash: tag.ctx.tags, merged with parentView.ctx.tags,
777
			tags = tag.parents = tagCtxCtx.parentTags = ctx && extendCtx(tagCtxCtx.parentTags, ctx.parentTags) || {};
778
			if (parentTag) {
779
				tags[parentTag.tagName] = parentTag;
780
				//TODO better perf for childTags: parentTag.tags.push(tag);
781
			}
782
			tags[tag.tagName] = tagCtxCtx.tag = tag;
783
			tagCtxCtx.tagCtx = tagCtx;
784
		}
785
	}
786
	if (!(tag._er = onError)) {
787
		tagHandlersFromProps(tag, tagCtxs[0]);
788
		tag.rendering = {rndr: tag.rendering}; // Provide object for state during render calls to tag and elses. (Used by {{if}} and {{for}}...)
789
		for (i = 0; i < l; i++) { // Iterate tagCtx for each {{else}} block
790
			tagCtx = tag.tagCtx = tagCtxs[i];
791
			props = tagCtx.props;
792
			tag.ctx = tagCtx.ctx;
793
 
794
			if (!i) {
795
				if (callInit) {
796
					tag.init(tagCtx, linkCtx, tag.ctx);
797
					callInit = undefined;
798
				}
799
				if (!tagCtx.args.length && tagCtx.argDefault !== false && tag.argDefault !== false) {
800
					tagCtx.args = args = [tagCtx.view.data]; // Missing first arg defaults to the current data context
801
					tagCtx.params.args = ["#data"];
802
				}
803
 
804
				bindTo = bindToOrBindFrom("bindTo");
805
 
806
				if (tag.bindTo !== undefined) {
807
					tag.bindTo = bindTo;
808
				}
809
 
810
				if (tag.bindFrom !== undefined) {
811
					tag.bindFrom = bindToOrBindFrom("bindFrom");
812
				} else if (tag.bindTo) {
813
					tag.bindFrom = tag.bindTo = bindTo;
814
				}
815
				bindFrom = tag.bindFrom || bindTo;
816
 
817
				bindToLength = bindTo.length;
818
				bindFromLength = bindFrom.length;
819
 
820
				if (tag._.bnd && (linkedElement = tag.linkedElement)) {
821
					tag.linkedElement = linkedElement = $isArray(linkedElement) ? linkedElement: [linkedElement];
822
 
823
					if (bindToLength !== linkedElement.length) {
824
						error("linkedElement not same length as bindTo");
825
					}
826
				}
827
				if (linkedElement = tag.linkedCtxParam) {
828
					tag.linkedCtxParam = linkedElement = $isArray(linkedElement) ? linkedElement: [linkedElement];
829
 
830
					if (bindFromLength !== linkedElement.length) {
831
						error("linkedCtxParam not same length as bindFrom/bindTo");
832
					}
833
				}
834
 
835
				if (bindFrom) {
836
					tag._.fromIndex = {}; // Hash of bindFrom index which has same path value as bindTo index. fromIndex = tag._.fromIndex[toIndex]
837
					tag._.toIndex = {}; // Hash of bindFrom index which has same path value as bindTo index. fromIndex = tag._.fromIndex[toIndex]
838
					n = bindFromLength;
839
					while (n--) {
840
						key = bindFrom[n];
841
						m = bindToLength;
842
						while (m--) {
843
							if (key === bindTo[m]) {
844
								tag._.fromIndex[m] = n;
845
								tag._.toIndex[n] = m;
846
							}
847
						}
848
					}
849
				}
850
 
851
				if (linkCtx) {
852
					// Set attr on linkCtx to ensure outputting to the correct target attribute.
853
					// Setting either linkCtx.attr or this.attr in the init() allows per-instance choice of target attrib.
854
					linkCtx.attr = tag.attr = linkCtx.attr || tag.attr || linkCtx._dfAt;
855
				}
856
				attr = tag.attr;
857
				tag._.noVws = attr && attr !== HTML;
858
			}
859
			args = tag.cvtArgs(i);
860
			if (tag.linkedCtxParam) {
861
				bdArgs = tag.cvtArgs(i, 1);
862
				m = bindFromLength;
863
				defaultCtx = tag.constructor.prototype.ctx;
864
				while (m--) {
865
					if (ctxPrm = tag.linkedCtxParam[m]) {
866
						key = bindFrom[m];
867
						initVal = bdArgs[m];
868
						// Create tag contextual parameter
869
						tagCtx.ctx[ctxPrm] = $sub._cp(
870
							defaultCtx && initVal === undefined ? defaultCtx[ctxPrm]: initVal,
871
							initVal !== undefined && argOrProp(tagCtx.params, key),
872
							tagCtx.view,
873
							tag._.bnd && {tag: tag, cvt: tag.convert, ind: m, tagElse: i}
874
						);
875
					}
876
				}
877
			}
878
			if ((mapDef = props.dataMap || tagDataMap) && (args.length || props.dataMap)) {
879
				thisMap = tagCtx.map;
880
				if (!thisMap || thisMap.src !== args[0] || isUpdate) {
881
					if (thisMap && thisMap.src) {
882
						thisMap.unmap(); // only called if observable map - not when only used in JsRender, e.g. by {{props}}
883
					}
884
					mapDef.map(args[0], tagCtx, thisMap, !tag._.bnd);
885
					thisMap = tagCtx.map;
886
				}
887
				args = [thisMap.tgt];
888
			}
889
 
890
			itemRet = undefined;
891
			if (tag.render) {
892
				itemRet = tag.render.apply(tag, args);
893
				if (parentView.linked && itemRet && !rWrappedInViewMarker.test(itemRet)) {
894
					// When a tag renders content from the render method, with data linking then we need to wrap with view markers, if absent,
895
					// to provide a contentView for the tag, which will correctly dispose bindings if deleted. The 'tmpl' for this view will
896
					// be a dumbed-down template which will always return the itemRet string (no matter what the data is). The itemRet string
897
					// is not compiled as template markup, so can include "{{" or "}}" without triggering syntax errors
898
					tmpl = { // 'Dumbed-down' template which always renders 'static' itemRet string
899
						links: []
900
					};
901
					tmpl.render = tmpl.fn = function() {
902
						return itemRet;
903
					};
904
					itemRet = renderWithViews(tmpl, parentView.data, undefined, true, parentView, undefined, undefined, tag);
905
				}
906
			}
907
			if (!args.length) {
908
				args = [parentView]; // no arguments - (e.g. {{else}}) get data context from view.
909
			}
910
			if (itemRet === undefined) {
911
				contentCtx = args[0]; // Default data context for wrapped block content is the first argument
912
				if (tag.contentCtx) { // Set tag.contentCtx to true, to inherit parent context, or to a function to provide alternate context.
913
					contentCtx = tag.contentCtx === true ? parentView : tag.contentCtx(contentCtx);
914
				}
915
				itemRet = tagCtx.render(contentCtx, true) || (isUpdate ? undefined : "");
916
			}
917
			ret = ret
918
				? ret + (itemRet || "")
919
				: itemRet !== undefined
920
					? "" + itemRet
921
					: undefined; // If no return value from render, and no template/content tagCtx.render(...), return undefined
922
		}
923
		tag.rendering = tag.rendering.rndr; // Remove tag.rendering object (if this is outermost render call. (In case of nested calls)
924
	}
925
	tag.tagCtx = tagCtxs[0];
926
	tag.ctx = tag.tagCtx.ctx;
927
 
928
	if (tag._.noVws && tag.inline) {
929
		// inline tag with attr set to "text" will insert HTML-encoded content - as if it was element-based innerText
930
		ret = attr === "text"
931
			? $converters.html(ret)
932
			: "";
933
	}
934
	return boundTag && parentView._.onRender
935
		// Call onRender (used by JsViews if present, to add binding annotations around rendered content)
936
		? parentView._.onRender(ret, parentView, tag)
937
		: ret;
938
}
939
 
940
//=================
941
// View constructor
942
//=================
943
 
944
function View(context, type, parentView, data, template, key, onRender, contentTmpl) {
945
	// Constructor for view object in view hierarchy. (Augmented by JsViews if JsViews is loaded)
946
	var views, parentView_, tag, self_,
947
		self = this,
948
		isArray = type === "array";
949
		// If the data is an array, this is an 'array view' with a views array for each child 'item view'
950
		// If the data is not an array, this is an 'item view' with a views 'hash' object for any child nested views
951
 
952
	self.content = contentTmpl;
953
	self.views = isArray ? [] : {};
954
	self.data = data;
955
	self.tmpl = template;
956
	self_ = self._ = {
957
		key: 0,
958
		// ._.useKey is non zero if is not an 'array view' (owning a data array). Use this as next key for adding to child views hash
959
		useKey: isArray ? 0 : 1,
960
		id: "" + viewId++,
961
		onRender: onRender,
962
		bnds: {}
963
	};
964
	self.linked = !!onRender;
965
	self.type = type || "top";
966
	if (type) {
967
		self.cache = {_ct: $subSettings._cchCt}; // Used for caching results of computed properties and helpers (view.getCache)
968
	}
969
 
970
	if (!parentView || parentView.type === "top") {
971
		(self.ctx = context || {}).root = self.data;
972
	}
973
 
974
	if (self.parent = parentView) {
975
		self.root = parentView.root || self; // view whose parent is top view
976
		views = parentView.views;
977
		parentView_ = parentView._;
978
		self.isTop = parentView_.scp; // Is top content view of a link("#container", ...) call
979
		self.scope = (!context.tag || context.tag === parentView.ctx.tag) && !self.isTop && parentView.scope || self;
980
		// Scope for contextParams - closest non flow tag ancestor or root view
981
		if (parentView_.useKey) {
982
			// Parent is not an 'array view'. Add this view to its views object
983
			// self._key = is the key in the parent view hash
984
			views[self_.key = "_" + parentView_.useKey++] = self;
985
			self.index = indexStr;
986
			self.getIndex = getNestedIndex;
987
		} else if (views.length === (self_.key = self.index = key)) { // Parent is an 'array view'. Add this view to its views array
988
			views.push(self); // Adding to end of views array. (Using push when possible - better perf than splice)
989
		} else {
990
			views.splice(key, 0, self); // Inserting in views array
991
		}
992
		// If no context was passed in, use parent context
993
		// If context was passed in, it should have been merged already with parent context
994
		self.ctx = context || parentView.ctx;
995
	} else if (type) {
996
		self.root = self; // view whose parent is top view
997
	}
998
}
999
 
1000
View.prototype = {
1001
	get: getView,
1002
	getIndex: getIndex,
1003
	ctxPrm: contextParameter,
1004
	getRsc: getResource,
1005
	_getTmpl: getTemplate,
1006
	_getOb: getPathObject,
1007
	getCache: function(key) { // Get cached value of computed value
1008
		if ($subSettings._cchCt > this.cache._ct) {
1009
			this.cache = {_ct: $subSettings._cchCt};
1010
		}
1011
		return this.cache[key] || (this.cache[key] = cpFnStore[key](this.data, this, $sub));
1012
	},
1013
	_is: "view"
1014
};
1015
 
1016
//====================================================
1017
// Registration
1018
//====================================================
1019
 
1020
function compileChildResources(parentTmpl) {
1021
	var storeName, storeNames, resources;
1022
	for (storeName in jsvStores) {
1023
		storeNames = storeName + "s";
1024
		if (parentTmpl[storeNames]) {
1025
			resources = parentTmpl[storeNames];        // Resources not yet compiled
1026
			parentTmpl[storeNames] = {};               // Remove uncompiled resources
1027
			$views[storeNames](resources, parentTmpl); // Add back in the compiled resources
1028
		}
1029
	}
1030
}
1031
 
1032
//===============
1033
// compileTag
1034
//===============
1035
 
1036
function compileTag(name, tagDef, parentTmpl) {
1037
	var tmpl, baseTag, prop,
1038
		compiledDef = new $sub._tg();
1039
 
1040
	function Tag() {
1041
		var tag = this;
1042
		tag._ = {
1043
			unlinked: true
1044
		};
1045
		tag.inline = true;
1046
		tag.tagName = name;
1047
	}
1048
 
1049
	if ($isFunction(tagDef)) {
1050
		// Simple tag declared as function. No presenter instantation.
1051
		tagDef = {
1052
			depends: tagDef.depends,
1053
			render: tagDef
1054
		};
1055
	} else if ("" + tagDef === tagDef) {
1056
		tagDef = {template: tagDef};
1057
	}
1058
 
1059
	if (baseTag = tagDef.baseTag) {
1060
		tagDef.flow = !!tagDef.flow; // Set flow property, so defaults to false even if baseTag has flow=true
1061
		baseTag = "" + baseTag === baseTag
1062
			? (parentTmpl && parentTmpl.tags[baseTag] || $tags[baseTag])
1063
			: baseTag;
1064
		if (!baseTag) {
1065
			error('baseTag: "' + tagDef.baseTag + '" not found');
1066
		}
1067
		compiledDef = $extend(compiledDef, baseTag);
1068
 
1069
		for (prop in tagDef) {
1070
			compiledDef[prop] = getMethod(baseTag[prop], tagDef[prop]);
1071
		}
1072
	} else {
1073
		compiledDef = $extend(compiledDef, tagDef);
1074
	}
1075
 
1076
	// Tag declared as object, used as the prototype for tag instantiation (control/presenter)
1077
	if ((tmpl = compiledDef.template) !== undefined) {
1078
		compiledDef.template = "" + tmpl === tmpl ? ($templates[tmpl] || $templates(tmpl)) : tmpl;
1079
	}
1080
	(Tag.prototype = compiledDef).constructor = compiledDef._ctr = Tag;
1081
 
1082
	if (parentTmpl) {
1083
		compiledDef._parentTmpl = parentTmpl;
1084
	}
1085
	return compiledDef;
1086
}
1087
 
1088
function baseApply(args) {
1089
	// In derived method (or handler declared declaratively as in {{:foo onChange=~fooChanged}} can call base method,
1090
	// using this.baseApply(arguments) (Equivalent to this._superApply(arguments) in jQuery UI)
1091
	return this.base.apply(this, args);
1092
}
1093
 
1094
//===============
1095
// compileTmpl
1096
//===============
1097
 
1098
function compileTmpl(name, tmpl, parentTmpl, options) {
1099
	// tmpl is either a template object, a selector for a template script block, or the name of a compiled template
1100
 
1101
	//==== nested functions ====
1102
	function lookupTemplate(value) {
1103
		// If value is of type string - treat as selector, or name of compiled template
1104
		// Return the template object, if already compiled, or the markup string
1105
		var currentName, tmpl;
1106
		if (("" + value === value) || value.nodeType > 0 && (elem = value)) {
1107
			if (!elem) {
1108
				if (/^\.?\/[^\\:*?"<>]*$/.test(value)) {
1109
					// value="./some/file.html" (or "/some/file.html")
1110
					// If the template is not named, use "./some/file.html" as name.
1111
					if (tmpl = $templates[name = name || value]) {
1112
						value = tmpl;
1113
					} else {
1114
						// BROWSER-SPECIFIC CODE (not on Node.js):
1115
						// Look for server-generated script block with id "./some/file.html"
1116
						elem = document.getElementById(value);
1117
					}
1118
				} else if ($.fn && !$sub.rTmpl.test(value)) {
1119
					try {
1120
						elem = $(value, document)[0]; // if jQuery is loaded, test for selector returning elements, and get first element
1121
					} catch (e) {}
1122
				}// END BROWSER-SPECIFIC CODE
1123
			} //BROWSER-SPECIFIC CODE
1124
			if (elem) {
1125
				if (elem.tagName !== "SCRIPT") {
1126
					error(value + ": Use script block, not " + elem.tagName);
1127
				}
1128
				if (options) {
1129
					// We will compile a new template using the markup in the script element
1130
					value = elem.innerHTML;
1131
				} else {
1132
					// We will cache a single copy of the compiled template, and associate it with the name
1133
					// (renaming from a previous name if there was one).
1134
					currentName = elem.getAttribute(tmplAttr);
1135
					if (currentName) {
1136
						if (currentName !== jsvTmpl) {
1137
							value = $templates[currentName];
1138
							delete $templates[currentName];
1139
						} else if ($.fn) {
1140
							value = $.data(elem)[jsvTmpl]; // Get cached compiled template
1141
						}
1142
					}
1143
					if (!currentName || !value) { // Not yet compiled, or cached version lost
1144
						name = name || ($.fn ? jsvTmpl : value);
1145
						value = compileTmpl(name, elem.innerHTML, parentTmpl, options);
1146
					}
1147
					value.tmplName = name = name || currentName;
1148
					if (name !== jsvTmpl) {
1149
						$templates[name] = value;
1150
					}
1151
					elem.setAttribute(tmplAttr, name);
1152
					if ($.fn) {
1153
						$.data(elem, jsvTmpl, value);
1154
					}
1155
				}
1156
			} // END BROWSER-SPECIFIC CODE
1157
			elem = undefined;
1158
		} else if (!value.fn) {
1159
			value = undefined;
1160
			// If value is not a string. HTML element, or compiled template, return undefined
1161
		}
1162
		return value;
1163
	}
1164
 
1165
	var elem, compiledTmpl,
1166
		tmplOrMarkup = tmpl = tmpl || "";
1167
	$sub._html = $converters.html;
1168
 
1169
	//==== Compile the template ====
1170
	if (options === 0) {
1171
		options = undefined;
1172
		tmplOrMarkup = lookupTemplate(tmplOrMarkup); // Top-level compile so do a template lookup
1173
	}
1174
 
1175
	// If options, then this was already compiled from a (script) element template declaration.
1176
	// If not, then if tmpl is a template object, use it for options
1177
	options = options || (tmpl.markup
1178
		? tmpl.bnds
1179
			? $extend({}, tmpl)
1180
			: tmpl
1181
		: {}
1182
	);
1183
 
1184
	options.tmplName = options.tmplName || name || "unnamed";
1185
	if (parentTmpl) {
1186
		options._parentTmpl = parentTmpl;
1187
	}
1188
	// If tmpl is not a markup string or a selector string, then it must be a template object
1189
	// In that case, get it from the markup property of the object
1190
	if (!tmplOrMarkup && tmpl.markup && (tmplOrMarkup = lookupTemplate(tmpl.markup)) && tmplOrMarkup.fn) {
1191
		// If the string references a compiled template object, need to recompile to merge any modified options
1192
		tmplOrMarkup = tmplOrMarkup.markup;
1193
	}
1194
	if (tmplOrMarkup !== undefined) {
1195
		if (tmplOrMarkup.render || tmpl.render) {
1196
			// tmpl is already compiled, so use it
1197
			if (tmplOrMarkup.tmpls) {
1198
				compiledTmpl = tmplOrMarkup;
1199
			}
1200
		} else {
1201
			// tmplOrMarkup is a markup string, not a compiled template
1202
			// Create template object
1203
			tmpl = tmplObject(tmplOrMarkup, options);
1204
			// Compile to AST and then to compiled function
1205
			tmplFn(tmplOrMarkup.replace(rEscapeQuotes, "\\$&"), tmpl);
1206
		}
1207
		if (!compiledTmpl) {
1208
			compiledTmpl = $extend(function() {
1209
				return compiledTmpl.render.apply(compiledTmpl, arguments);
1210
			}, tmpl);
1211
 
1212
			compileChildResources(compiledTmpl);
1213
		}
1214
		return compiledTmpl;
1215
	}
1216
}
1217
 
1218
//==== /end of function compileTmpl ====
1219
 
1220
//=================
1221
// compileViewModel
1222
//=================
1223
 
1224
function getDefaultVal(defaultVal, data) {
1225
	return $isFunction(defaultVal)
1226
		? defaultVal.call(data)
1227
		: defaultVal;
1228
}
1229
 
1230
function addParentRef(ob, ref, parent) {
1231
	Object.defineProperty(ob, ref, {
1232
		value: parent,
1233
		configurable: true
1234
	});
1235
}
1236
 
1237
function compileViewModel(name, type) {
1238
	var i, constructor, parent,
1239
		viewModels = this,
1240
		getters = type.getters,
1241
		extend = type.extend,
1242
		id = type.id,
1243
		proto = $.extend({
1244
			_is: name || "unnamed",
1245
			unmap: unmap,
1246
			merge: merge
1247
		}, extend),
1248
		args = "",
1249
		cnstr = "",
1250
		getterCount = getters ? getters.length : 0,
1251
		$observable = $.observable,
1252
		getterNames = {};
1253
 
1254
	function JsvVm(args) {
1255
		constructor.apply(this, args);
1256
	}
1257
 
1258
	function vm() {
1259
		return new JsvVm(arguments);
1260
	}
1261
 
1262
	function iterate(data, action) {
1263
		var getterType, defaultVal, prop, ob, parentRef,
1264
			j = 0;
1265
		for (; j < getterCount; j++) {
1266
			prop = getters[j];
1267
			getterType = undefined;
1268
			if (prop + "" !== prop) {
1269
				getterType = prop;
1270
				prop = getterType.getter;
1271
				parentRef = getterType.parentRef;
1272
			}
1273
			if ((ob = data[prop]) === undefined && getterType && (defaultVal = getterType.defaultVal) !== undefined) {
1274
				ob = getDefaultVal(defaultVal, data);
1275
			}
1276
			action(ob, getterType && viewModels[getterType.type], prop, parentRef);
1277
		}
1278
	}
1279
 
1280
	function map(data) {
1281
		data = data + "" === data
1282
			? JSON.parse(data) // Accept JSON string
1283
			: data;            // or object/array
1284
		var l, prop, childOb, parentRef,
1285
			j = 0,
1286
			ob = data,
1287
			arr = [];
1288
 
1289
		if ($isArray(data)) {
1290
			data = data || [];
1291
			l = data.length;
1292
			for (; j<l; j++) {
1293
				arr.push(this.map(data[j]));
1294
			}
1295
			arr._is = name;
1296
			arr.unmap = unmap;
1297
			arr.merge = merge;
1298
			return arr;
1299
		}
1300
 
1301
		if (data) {
1302
			iterate(data, function(ob, viewModel) {
1303
				if (viewModel) { // Iterate to build getters arg array (value, or mapped value)
1304
					ob = viewModel.map(ob);
1305
				}
1306
				arr.push(ob);
1307
			});
1308
			ob = this.apply(this, arr); // Instantiate this View Model, passing getters args array to constructor
1309
			j = getterCount;
1310
			while (j--) {
1311
				childOb = arr[j];
1312
				parentRef = getters[j].parentRef;
1313
				if (parentRef && childOb && childOb.unmap) {
1314
					if ($isArray(childOb)) {
1315
						l = childOb.length;
1316
						while (l--) {
1317
							addParentRef(childOb[l], parentRef, ob);
1318
						}
1319
					} else {
1320
						addParentRef(childOb, parentRef, ob);
1321
					}
1322
				}
1323
			}
1324
			for (prop in data) { // Copy over any other properties. that are not get/set properties
1325
				if (prop !== $expando && !getterNames[prop]) {
1326
					ob[prop] = data[prop];
1327
				}
1328
			}
1329
		}
1330
		return ob;
1331
	}
1332
 
1333
	function merge(data, parent, parentRef) {
1334
		data = data + "" === data
1335
			? JSON.parse(data) // Accept JSON string
1336
			: data;            // or object/array
1337
 
1338
		var j, l, m, prop, mod, found, assigned, ob, newModArr, childOb,
1339
			k = 0,
1340
			model = this;
1341
 
1342
		if ($isArray(model)) {
1343
			assigned = {};
1344
			newModArr = [];
1345
			l = data.length;
1346
			m = model.length;
1347
			for (; k<l; k++) {
1348
				ob = data[k];
1349
				found = false;
1350
				for (j=0; j<m && !found; j++) {
1351
					if (assigned[j]) {
1352
						continue;
1353
					}
1354
					mod = model[j];
1355
 
1356
					if (id) {
1357
						assigned[j] = found = id + "" === id
1358
						? (ob[id] && (getterNames[id] ? mod[id]() : mod[id]) === ob[id])
1359
						: id(mod, ob);
1360
					}
1361
				}
1362
				if (found) {
1363
					mod.merge(ob);
1364
					newModArr.push(mod);
1365
				} else {
1366
					newModArr.push(childOb = vm.map(ob));
1367
					if (parentRef) {
1368
						addParentRef(childOb, parentRef, parent);
1369
					}
1370
				}
1371
			}
1372
			if ($observable) {
1373
				$observable(model).refresh(newModArr, true);
1374
			} else {
1375
				model.splice.apply(model, [0, model.length].concat(newModArr));
1376
			}
1377
			return;
1378
		}
1379
		iterate(data, function(ob, viewModel, getter, parentRef) {
1380
			if (viewModel) {
1381
				model[getter]().merge(ob, model, parentRef); // Update typed property
1382
			} else if (model[getter]() !== ob) {
1383
				model[getter](ob); // Update non-typed property
1384
			}
1385
		});
1386
		for (prop in data) {
1387
			if (prop !== $expando && !getterNames[prop]) {
1388
				model[prop] = data[prop];
1389
			}
1390
		}
1391
	}
1392
 
1393
	function unmap() {
1394
		var ob, prop, getterType, arr, value,
1395
			k = 0,
1396
			model = this;
1397
 
1398
		function unmapArray(modelArr) {
1399
			var arr = [],
1400
				i = 0,
1401
				l = modelArr.length;
1402
			for (; i<l; i++) {
1403
				arr.push(modelArr[i].unmap());
1404
			}
1405
			return arr;
1406
		}
1407
 
1408
		if ($isArray(model)) {
1409
			return unmapArray(model);
1410
		}
1411
		ob = {};
1412
		for (; k < getterCount; k++) {
1413
			prop = getters[k];
1414
			getterType = undefined;
1415
			if (prop + "" !== prop) {
1416
				getterType = prop;
1417
				prop = getterType.getter;
1418
			}
1419
			value = model[prop]();
1420
			ob[prop] = getterType && value && viewModels[getterType.type]
1421
				? $isArray(value)
1422
					? unmapArray(value)
1423
					: value.unmap()
1424
				: value;
1425
		}
1426
		for (prop in model) {
1427
			if (model.hasOwnProperty(prop) && (prop.charAt(0) !== "_" || !getterNames[prop.slice(1)]) && prop !== $expando && !$isFunction(model[prop])) {
1428
				ob[prop] = model[prop];
1429
			}
1430
		}
1431
		return ob;
1432
	}
1433
 
1434
	JsvVm.prototype = proto;
1435
 
1436
	for (i=0; i < getterCount; i++) {
1437
		(function(getter) {
1438
			getter = getter.getter || getter;
1439
			getterNames[getter] = i+1;
1440
			var privField = "_" + getter;
1441
 
1442
			args += (args ? "," : "") + getter;
1443
			cnstr += "this." + privField + " = " + getter + ";\n";
1444
			proto[getter] = proto[getter] || function(val) {
1445
				if (!arguments.length) {
1446
					return this[privField]; // If there is no argument, use as a getter
1447
				}
1448
				if ($observable) {
1449
					$observable(this).setProperty(getter, val);
1450
				} else {
1451
					this[privField] = val;
1452
				}
1453
			};
1454
 
1455
			if ($observable) {
1456
				proto[getter].set = proto[getter].set || function(val) {
1457
					this[privField] = val; // Setter called by observable property change
1458
				};
1459
			}
1460
		})(getters[i]);
1461
	}
1462
 
1463
	// Constructor for new viewModel instance.
1464
	cnstr = new Function(args, cnstr);
1465
 
1466
	constructor = function() {
1467
		cnstr.apply(this, arguments);
1468
		// Pass additional parentRef str and parent obj to have a parentRef pointer on instance
1469
		if (parent = arguments[getterCount + 1]) {
1470
			addParentRef(this, arguments[getterCount], parent);
1471
		}
1472
	};
1473
 
1474
	constructor.prototype = proto;
1475
	proto.constructor = constructor;
1476
 
1477
	vm.map = map;
1478
	vm.getters = getters;
1479
	vm.extend = extend;
1480
	vm.id = id;
1481
	return vm;
1482
}
1483
 
1484
function tmplObject(markup, options) {
1485
	// Template object constructor
1486
	var htmlTag,
1487
		wrapMap = $subSettingsAdvanced._wm || {}, // Only used in JsViews. Otherwise empty: {}
1488
		tmpl = {
1489
			tmpls: [],
1490
			links: {}, // Compiled functions for link expressions
1491
			bnds: [],
1492
			_is: "template",
1493
			render: renderContent
1494
		};
1495
 
1496
	if (options) {
1497
		tmpl = $extend(tmpl, options);
1498
	}
1499
 
1500
	tmpl.markup = markup;
1501
	if (!tmpl.htmlTag) {
1502
		// Set tmpl.tag to the top-level HTML tag used in the template, if any...
1503
		htmlTag = rFirstElem.exec(markup);
1504
		tmpl.htmlTag = htmlTag ? htmlTag[1].toLowerCase() : "";
1505
	}
1506
	htmlTag = wrapMap[tmpl.htmlTag];
1507
	if (htmlTag && htmlTag !== wrapMap.div) {
1508
		// When using JsViews, we trim templates which are inserted into HTML contexts where text nodes are not rendered (i.e. not 'Phrasing Content').
1509
		// Currently not trimmed for <li> tag. (Not worth adding perf cost)
1510
		tmpl.markup = $.trim(tmpl.markup);
1511
	}
1512
 
1513
	return tmpl;
1514
}
1515
 
1516
//==============
1517
// registerStore
1518
//==============
1519
 
1520
/**
1521
* Internal. Register a store type (used for template, tags, helpers, converters)
1522
*/
1523
function registerStore(storeName, storeSettings) {
1524
 
1525
/**
1526
* Generic store() function to register item, named item, or hash of items
1527
* Also used as hash to store the registered items
1528
* Used as implementation of $.templates(), $.views.templates(), $.views.tags(), $.views.helpers() and $.views.converters()
1529
*
1530
* @param {string|hash} name         name - or selector, in case of $.templates(). Or hash of items
1531
* @param {any}         [item]       (e.g. markup for named template)
1532
* @param {template}    [parentTmpl] For item being registered as private resource of template
1533
* @returns {any|$.views} item, e.g. compiled template - or $.views in case of registering hash of items
1534
*/
1535
	function theStore(name, item, parentTmpl) {
1536
		// The store is also the function used to add items to the store. e.g. $.templates, or $.views.tags
1537
 
1538
		// For store of name 'thing', Call as:
1539
		//    $.views.things(items[, parentTmpl]),
1540
		// or $.views.things(name[, item, parentTmpl])
1541
 
1542
		var compile, itemName, thisStore, cnt,
1543
			onStore = $sub.onStore[storeName];
1544
 
1545
		if (name && typeof name === OBJECT && !name.nodeType && !name.markup && !name.getTgt && !(storeName === "viewModel" && name.getters || name.extend)) {
1546
			// Call to $.views.things(items[, parentTmpl]),
1547
 
1548
			// Adding items to the store
1549
			// If name is a hash, then item is parentTmpl. Iterate over hash and call store for key.
1550
			for (itemName in name) {
1551
				theStore(itemName, name[itemName], item);
1552
			}
1553
			return item || $views;
1554
		}
1555
		// Adding a single unnamed item to the store
1556
		if (name && "" + name !== name) { // name must be a string
1557
			parentTmpl = item;
1558
			item = name;
1559
			name = undefined;
1560
		}
1561
		thisStore = parentTmpl
1562
			? storeName === "viewModel"
1563
				? parentTmpl
1564
				: (parentTmpl[storeNames] = parentTmpl[storeNames] || {})
1565
			: theStore;
1566
		compile = storeSettings.compile;
1567
 
1568
		if (item === undefined) {
1569
			item = compile ? name : thisStore[name];
1570
			name = undefined;
1571
		}
1572
		if (item === null) {
1573
			// If item is null, delete this entry
1574
			if (name) {
1575
				delete thisStore[name];
1576
			}
1577
		} else {
1578
			if (compile) {
1579
				item = compile.call(thisStore, name, item, parentTmpl, 0) || {};
1580
				item._is = storeName; // Only do this for compiled objects (tags, templates...)
1581
			}
1582
			if (name) {
1583
				thisStore[name] = item;
1584
			}
1585
		}
1586
		if (onStore) {
1587
			// e.g. JsViews integration
1588
			onStore(name, item, parentTmpl, compile);
1589
		}
1590
		return item;
1591
	}
1592
 
1593
	var storeNames = storeName + "s";
1594
	$views[storeNames] = theStore;
1595
}
1596
 
1597
/**
1598
* Add settings such as:
1599
* $.views.settings.allowCode(true)
1600
* @param {boolean} value
1601
* @returns {Settings}
1602
*
1603
* allowCode = $.views.settings.allowCode()
1604
* @returns {boolean}
1605
*/
1606
function addSetting(st) {
1607
	$viewsSettings[st] = $viewsSettings[st] || function(value) {
1608
		return arguments.length
1609
			? ($subSettings[st] = value, $viewsSettings)
1610
			: $subSettings[st];
1611
	};
1612
}
1613
 
1614
//========================
1615
// dataMap for render only
1616
//========================
1617
 
1618
function dataMap(mapDef) {
1619
	function Map(source, options) {
1620
		this.tgt = mapDef.getTgt(source, options);
1621
		options.map = this;
1622
	}
1623
 
1624
	if ($isFunction(mapDef)) {
1625
		// Simple map declared as function
1626
		mapDef = {
1627
			getTgt: mapDef
1628
		};
1629
	}
1630
 
1631
	if (mapDef.baseMap) {
1632
		mapDef = $extend($extend({}, mapDef.baseMap), mapDef);
1633
	}
1634
 
1635
	mapDef.map = function(source, options) {
1636
		return new Map(source, options);
1637
	};
1638
	return mapDef;
1639
}
1640
 
1641
//==============
1642
// renderContent
1643
//==============
1644
 
1645
/** Render the template as a string, using the specified data and helpers/context
1646
* $("#tmpl").render(), tmpl.render(), tagCtx.render(), $.render.namedTmpl()
1647
*
1648
* @param {any}        data
1649
* @param {hash}       [context]           helpers or context
1650
* @param {boolean}    [noIteration]
1651
* @param {View}       [parentView]        internal
1652
* @param {string}     [key]               internal
1653
* @param {function}   [onRender]          internal
1654
* @returns {string}   rendered template   internal
1655
*/
1656
function renderContent(data, context, noIteration, parentView, key, onRender) {
1657
	var i, l, tag, tmpl, tagCtx, isTopRenderCall, prevData, prevIndex,
1658
		view = parentView,
1659
		result = "";
1660
 
1661
	if (context === true) {
1662
		noIteration = context; // passing boolean as second param - noIteration
1663
		context = undefined;
1664
	} else if (typeof context !== OBJECT) {
1665
		context = undefined; // context must be a boolean (noIteration) or a plain object
1666
	}
1667
 
1668
	if (tag = this.tag) {
1669
		// This is a call from renderTag or tagCtx.render(...)
1670
		tagCtx = this;
1671
		view = view || tagCtx.view;
1672
		tmpl = view._getTmpl(tag.template || tagCtx.tmpl);
1673
		if (!arguments.length) {
1674
			data = tag.contentCtx && $isFunction(tag.contentCtx)
1675
				? data = tag.contentCtx(data)
1676
				: view; // Default data context for wrapped block content is the first argument
1677
		}
1678
	} else {
1679
		// This is a template.render(...) call
1680
		tmpl = this;
1681
	}
1682
 
1683
	if (tmpl) {
1684
		if (!parentView && data && data._is === "view") {
1685
			view = data; // When passing in a view to render or link (and not passing in a parent view) use the passed-in view as parentView
1686
		}
1687
 
1688
		if (view && data === view) {
1689
			// Inherit the data from the parent view.
1690
			data = view.data;
1691
		}
1692
 
1693
		isTopRenderCall = !view;
1694
		isRenderCall = isRenderCall || isTopRenderCall;
1695
		if (isTopRenderCall) {
1696
			(context = context || {}).root = data; // Provide ~root as shortcut to top-level data.
1697
		}
1698
		if (!isRenderCall || $subSettingsAdvanced.useViews || tmpl.useViews || view && view !== topView) {
1699
			result = renderWithViews(tmpl, data, context, noIteration, view, key, onRender, tag);
1700
		} else {
1701
			if (view) { // In a block
1702
				prevData = view.data;
1703
				prevIndex = view.index;
1704
				view.index = indexStr;
1705
			} else {
1706
				view = topView;
1707
				prevData = view.data;
1708
				view.data = data;
1709
				view.ctx = context;
1710
			}
1711
			if ($isArray(data) && !noIteration) {
1712
				// Create a view for the array, whose child views correspond to each data item. (Note: if key and parentView are passed in
1713
				// along with parent view, treat as insert -e.g. from view.addViews - so parentView is already the view item for array)
1714
				for (i = 0, l = data.length; i < l; i++) {
1715
					view.index = i;
1716
					view.data = data[i];
1717
					result += tmpl.fn(data[i], view, $sub);
1718
				}
1719
			} else {
1720
				view.data = data;
1721
				result += tmpl.fn(data, view, $sub);
1722
			}
1723
			view.data = prevData;
1724
			view.index = prevIndex;
1725
		}
1726
		if (isTopRenderCall) {
1727
			isRenderCall = undefined;
1728
		}
1729
	}
1730
	return result;
1731
}
1732
 
1733
function renderWithViews(tmpl, data, context, noIteration, view, key, onRender, tag) {
1734
	// Render template against data as a tree of subviews (nested rendered template instances), or as a string (top-level template).
1735
	// If the data is the parent view, treat as noIteration, re-render with the same data context.
1736
	// tmpl can be a string (e.g. rendered by a tag.render() method), or a compiled template.
1737
	var i, l, newView, childView, itemResult, swapContent, contentTmpl, outerOnRender, tmplName, itemVar, newCtx, tagCtx, noLinking,
1738
		result = "";
1739
 
1740
	if (tag) {
1741
		// This is a call from renderTag or tagCtx.render(...)
1742
		tmplName = tag.tagName;
1743
		tagCtx = tag.tagCtx;
1744
		context = context ? extendCtx(context, tag.ctx) : tag.ctx;
1745
 
1746
		if (tmpl === view.content) { // {{xxx tmpl=#content}}
1747
			contentTmpl = tmpl !== view.ctx._wrp // We are rendering the #content
1748
				? 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
1749
				: undefined; // #content was the view.ctx._wrp block content - so within this view, there is no longer any #content to wrap.
1750
		} else if (tmpl !== tagCtx.content) {
1751
			if (tmpl === tag.template) { // Rendering {{tag}} tag.template, replacing block content.
1752
				contentTmpl = tagCtx.tmpl; // Set #content to block content (or wrapped block content if tagCtx.props.tmpl is set)
1753
				context._wrp = tagCtx.content; // Pass wrapped block content to nested views
1754
			} else { // Rendering tagCtx.props.tmpl wrapper
1755
				contentTmpl = tagCtx.content || view.content; // Set #content to wrapped block content
1756
			}
1757
		} else {
1758
			contentTmpl = view.content; // Nested views inherit same wrapped #content property
1759
		}
1760
 
1761
		if (tagCtx.props.link === false) {
1762
			// link=false setting on block tag
1763
			// We will override inherited value of link by the explicit setting link=false taken from props
1764
			// The child views of an unlinked view are also unlinked. So setting child back to true will not have any effect.
1765
			context = context || {};
1766
			context.link = false;
1767
		}
1768
	}
1769
 
1770
	if (view) {
1771
		onRender = onRender || view._.onRender;
1772
		noLinking = context && context.link === false;
1773
 
1774
		if (noLinking && view._.nl) {
1775
			onRender = undefined;
1776
		}
1777
 
1778
		context = extendCtx(context, view.ctx);
1779
		tagCtx = !tag && view.tag
1780
			? view.tag.tagCtxs[view.tagElse]
1781
			: tagCtx;
1782
	}
1783
 
1784
	if (itemVar = tagCtx && tagCtx.props.itemVar) {
1785
		if (itemVar[0] !== "~") {
1786
			syntaxError("Use itemVar='~myItem'");
1787
		}
1788
		itemVar = itemVar.slice(1);
1789
	}
1790
 
1791
	if (key === true) {
1792
		swapContent = true;
1793
		key = 0;
1794
	}
1795
 
1796
	// If link===false, do not call onRender, so no data-linking marker nodes
1797
	if (onRender && tag && tag._.noVws) {
1798
		onRender = undefined;
1799
	}
1800
	outerOnRender = onRender;
1801
	if (onRender === true) {
1802
		// Used by view.refresh(). Don't create a new wrapper view.
1803
		outerOnRender = undefined;
1804
		onRender = view._.onRender;
1805
	}
1806
	// Set additional context on views created here, (as modified context inherited from the parent, and to be inherited by child views)
1807
	context = tmpl.helpers
1808
		? extendCtx(tmpl.helpers, context)
1809
		: context;
1810
 
1811
	newCtx = context;
1812
	if ($isArray(data) && !noIteration) {
1813
		// Create a view for the array, whose child views correspond to each data item. (Note: if key and view are passed in
1814
		// along with parent view, treat as insert -e.g. from view.addViews - so view is already the view item for array)
1815
		newView = swapContent
1816
			? view
1817
			: (key !== undefined && view)
1818
				|| new View(context, "array", view, data, tmpl, key, onRender, contentTmpl);
1819
		newView._.nl= noLinking;
1820
		if (view && view._.useKey) {
1821
			// Parent is not an 'array view'
1822
			newView._.bnd = !tag || tag._.bnd && tag; // For array views that are data bound for collection change events, set the
1823
			// 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 ...}}
1824
			newView.tag = tag;
1825
		}
1826
		for (i = 0, l = data.length; i < l; i++) {
1827
			// Create a view for each data item.
1828
			childView = new View(newCtx, "item", newView, data[i], tmpl, (key || 0) + i, onRender, newView.content);
1829
			if (itemVar) {
1830
				(childView.ctx = $extend({}, newCtx))[itemVar] = $sub._cp(data[i], "#data", childView);
1831
			}
1832
			itemResult = tmpl.fn(data[i], childView, $sub);
1833
			result += newView._.onRender ? newView._.onRender(itemResult, childView) : itemResult;
1834
		}
1835
	} else {
1836
		// Create a view for singleton data object. The type of the view will be the tag name, e.g. "if" or "mytag" except for
1837
		// "item", "array" and "data" views. A "data" view is from programmatic render(object) against a 'singleton'.
1838
		newView = swapContent ? view : new View(newCtx, tmplName || "data", view, data, tmpl, key, onRender, contentTmpl);
1839
 
1840
		if (itemVar) {
1841
			(newView.ctx = $extend({}, newCtx))[itemVar] = $sub._cp(data, "#data", newView);
1842
		}
1843
 
1844
		newView.tag = tag;
1845
		newView._.nl = noLinking;
1846
		result += tmpl.fn(data, newView, $sub);
1847
	}
1848
	if (tag) {
1849
		newView.tagElse = tagCtx.index;
1850
		tagCtx.contentView = newView;
1851
	}
1852
	return outerOnRender ? outerOnRender(result, newView) : result;
1853
}
1854
 
1855
//===========================
1856
// Build and compile template
1857
//===========================
1858
 
1859
// Generate a reusable function that will serve to render a template against data
1860
// (Compile AST then build template function)
1861
 
1862
function onRenderError(e, view, fallback) {
1863
	var message = fallback !== undefined
1864
		? $isFunction(fallback)
1865
			? fallback.call(view.data, e, view)
1866
			: fallback || ""
1867
		: "{Error: " + (e.message||e) + "}";
1868
 
1869
	if ($subSettings.onError && (fallback = $subSettings.onError.call(view.data, e, fallback && message, view)) !== undefined) {
1870
		message = fallback; // There is a settings.debugMode(handler) onError override. Call it, and use return value (if any) to replace message
1871
	}
1872
	return view && !view._lc ? $converters.html(message) : message; // For data-link=\"{... onError=...}"... See onDataLinkedTagChange
1873
}
1874
 
1875
function error(message) {
1876
	throw new $sub.Err(message);
1877
}
1878
 
1879
function syntaxError(message) {
1880
	error("Syntax error\n" + message);
1881
}
1882
 
1883
function tmplFn(markup, tmpl, isLinkExpr, convertBack, hasElse) {
1884
	// Compile markup to AST (abtract syntax tree) then build the template function code from the AST nodes
1885
	// Used for compiling templates, and also by JsViews to build functions for data link expressions
1886
 
1887
	//==== nested functions ====
1888
	function pushprecedingContent(shift) {
1889
		shift -= loc;
1890
		if (shift) {
1891
			content.push(markup.substr(loc, shift).replace(rNewLine, "\\n"));
1892
		}
1893
	}
1894
 
1895
	function blockTagCheck(tagName, block) {
1896
		if (tagName) {
1897
			tagName += '}}';
1898
			//			'{{include}} block has {{/for}} with no open {{for}}'
1899
			syntaxError((
1900
				block
1901
					? '{{' + block + '}} block has {{/' + tagName + ' without {{' + tagName
1902
					: 'Unmatched or missing {{/' + tagName) + ', in template:\n' + markup);
1903
		}
1904
	}
1905
 
1906
	function parseTag(all, bind, tagName, converter, colon, html, codeTag, params, slash, bind2, closeBlock, index) {
1907
/*
1908
 
1909
     bind     tagName         cvt   cln html code    params            slash   bind2         closeBlk  comment
1910
/(?:{(\^)?{(?:(\w+(?=[\/\s}]))|(\w+)?(:)|(>)|(\*))\s*((?:[^}]|}(?!}))*?)(\/)?|{(\^)?{(?:(?:\/(\w+))\s*|!--[\s\S]*?--))}}/g
1911
 
1912
(?:
1913
  {(\^)?{            bind
1914
  (?:
1915
    (\w+             tagName
1916
      (?=[\/\s}])
1917
    )
1918
    |
1919
    (\w+)?(:)        converter colon
1920
    |
1921
    (>)              html
1922
    |
1923
    (\*)             codeTag
1924
  )
1925
  \s*
1926
  (                  params
1927
    (?:[^}]|}(?!}))*?
1928
  )
1929
  (\/)?              slash
1930
  |
1931
  {(\^)?{            bind2
1932
  (?:
1933
    (?:\/(\w+))\s*   closeBlock
1934
    |
1935
    !--[\s\S]*?--    comment
1936
  )
1937
)
1938
}}/g
1939
 
1940
*/
1941
		if (codeTag && bind || slash && !tagName || params && params.slice(-1) === ":" || bind2) {
1942
			syntaxError(all);
1943
		}
1944
 
1945
		// Build abstract syntax tree (AST): [tagName, converter, params, content, hash, bindings, contentMarkup]
1946
		if (html) {
1947
			colon = ":";
1948
			converter = HTML;
1949
		}
1950
		slash = slash || isLinkExpr && !hasElse;
1951
 
1952
		var late, openTagName, isLateOb,
1953
			pathBindings = (bind || isLinkExpr) && [[]], // pathBindings is an array of arrays for arg bindings and a hash of arrays for prop bindings
1954
			props = "",
1955
			args = "",
1956
			ctxProps = "",
1957
			paramsArgs = "",
1958
			paramsProps = "",
1959
			paramsCtxProps = "",
1960
			onError = "",
1961
			useTrigger = "",
1962
			// Block tag if not self-closing and not {{:}} or {{>}} (special case) and not a data-link expression
1963
			block = !slash && !colon;
1964
 
1965
		//==== nested helper function ====
1966
		tagName = tagName || (params = params || "#data", colon); // {{:}} is equivalent to {{:#data}}
1967
		pushprecedingContent(index);
1968
		loc = index + all.length; // location marker - parsed up to here
1969
		if (codeTag) {
1970
			if (allowCode) {
1971
				content.push(["*", "\n" + params.replace(/^:/, "ret+= ").replace(rUnescapeQuotes, "$1") + ";\n"]);
1972
			}
1973
		} else if (tagName) {
1974
			if (tagName === "else") {
1975
				if (rTestElseIf.test(params)) {
1976
					syntaxError('For "{{else if expr}}" use "{{else expr}}"');
1977
				}
1978
				pathBindings = current[9] && [[]];
1979
				current[10] = markup.substring(current[10], index); // contentMarkup for block tag
1980
				openTagName = current[11] || current[0] || syntaxError("Mismatched: " + all);
1981
				// current[0] is tagName, but for {{else}} nodes, current[11] is tagName of preceding open tag
1982
				current = stack.pop();
1983
				content = current[2];
1984
				block = true;
1985
			}
1986
			if (params) {
1987
				// remove newlines from the params string, to avoid compiled code errors for unterminated strings
1988
				parseParams(params.replace(rNewLine, " "), pathBindings, tmpl, isLinkExpr)
1989
					.replace(rBuildHash, function(all, onerror, isCtxPrm, key, keyToken, keyValue, arg, param) {
1990
						if (key === "this:") {
1991
							keyValue = "undefined"; // this=some.path is always a to parameter (one-way), so don't need to compile/evaluate some.path initialization
1992
						}
1993
						if (param) {
1994
							isLateOb = isLateOb || param[0] === "@";
1995
						}
1996
						key = "'" + keyToken + "':";
1997
						if (arg) {
1998
							args += isCtxPrm + keyValue + ",";
1999
							paramsArgs += "'" + param + "',";
2000
						} else if (isCtxPrm) { // Contextual parameter, ~foo=expr
2001
							ctxProps += key + 'j._cp(' + keyValue + ',"' + param + '",view),';
2002
							// Compiled code for evaluating tagCtx on a tag will have: ctx:{'foo':j._cp(compiledExpr, "expr", view)}
2003
							paramsCtxProps += key + "'" + param + "',";
2004
						} else if (onerror) {
2005
							onError += keyValue;
2006
						} else {
2007
							if (keyToken === "trigger") {
2008
								useTrigger += keyValue;
2009
							}
2010
							if (keyToken === "lateRender") {
2011
								late = param !== "false"; // Render after first pass
2012
							}
2013
							props += key + keyValue + ",";
2014
							paramsProps += key + "'" + param + "',";
2015
							hasHandlers = hasHandlers || rHasHandlers.test(keyToken);
2016
						}
2017
						return "";
2018
					}).slice(0, -1);
2019
			}
2020
 
2021
			if (pathBindings && pathBindings[0]) {
2022
				pathBindings.pop(); // Remove the binding that was prepared for next arg. (There is always an extra one ready).
2023
			}
2024
 
2025
			newNode = [
2026
					tagName,
2027
					converter || !!convertBack || hasHandlers || "",
2028
					block && [],
2029
					parsedParam(paramsArgs || (tagName === ":" ? "'#data'," : ""), paramsProps, paramsCtxProps), // {{:}} equivalent to {{:#data}}
2030
					parsedParam(args || (tagName === ":" ? "data," : ""), props, ctxProps),
2031
					onError,
2032
					useTrigger,
2033
					late,
2034
					isLateOb,
2035
					pathBindings || 0
2036
				];
2037
			content.push(newNode);
2038
			if (block) {
2039
				stack.push(current);
2040
				current = newNode;
2041
				current[10] = loc; // Store current location of open tag, to be able to add contentMarkup when we reach closing tag
2042
				current[11] = openTagName; // Used for checking syntax (matching close tag)
2043
			}
2044
		} else if (closeBlock) {
2045
			blockTagCheck(closeBlock !== current[0] && closeBlock !== current[11] && closeBlock, current[0]); // Check matching close tag name
2046
			current[10] = markup.substring(current[10], index); // contentMarkup for block tag
2047
			current = stack.pop();
2048
		}
2049
		blockTagCheck(!current && closeBlock);
2050
		content = current[2];
2051
	}
2052
	//==== /end of nested functions ====
2053
 
2054
	var i, result, newNode, hasHandlers, bindings,
2055
		allowCode = $subSettings.allowCode || tmpl && tmpl.allowCode
2056
			|| $viewsSettings.allowCode === true, // include direct setting of settings.allowCode true for backward compat only
2057
		astTop = [],
2058
		loc = 0,
2059
		stack = [],
2060
		content = astTop,
2061
		current = [,,astTop];
2062
 
2063
	if (allowCode && tmpl._is) {
2064
		tmpl.allowCode = allowCode;
2065
	}
2066
 
2067
//TODO	result = tmplFnsCache[markup]; // Only cache if template is not named and markup length < ...,
2068
//and there are no bindings or subtemplates?? Consider standard optimization for data-link="a.b.c"
2069
//		if (result) {
2070
//			tmpl.fn = result;
2071
//		} else {
2072
 
2073
//		result = markup;
2074
	if (isLinkExpr) {
2075
		if (convertBack !== undefined) {
2076
			markup = markup.slice(0, -convertBack.length - 2) + delimCloseChar0;
2077
		}
2078
		markup = delimOpenChar0 + markup + delimCloseChar1;
2079
	}
2080
 
2081
	blockTagCheck(stack[0] && stack[0][2].pop()[0]);
2082
	// Build the AST (abstract syntax tree) under astTop
2083
	markup.replace(rTag, parseTag);
2084
 
2085
	pushprecedingContent(markup.length);
2086
 
2087
	if (loc = astTop[astTop.length - 1]) {
2088
		blockTagCheck("" + loc !== loc && (+loc[10] === loc[10]) && loc[0]);
2089
	}
2090
//			result = tmplFnsCache[markup] = buildCode(astTop, tmpl);
2091
//		}
2092
 
2093
	if (isLinkExpr) {
2094
		result = buildCode(astTop, markup, isLinkExpr);
2095
		bindings = [];
2096
		i = astTop.length;
2097
		while (i--) {
2098
			bindings.unshift(astTop[i][9]); // With data-link expressions, pathBindings array for tagCtx[i] is astTop[i][9]
2099
		}
2100
		setPaths(result, bindings);
2101
	} else {
2102
		result = buildCode(astTop, tmpl);
2103
	}
2104
	return result;
2105
}
2106
 
2107
function setPaths(fn, pathsArr) {
2108
	var key, paths,
2109
		i = 0,
2110
		l = pathsArr.length;
2111
	fn.deps = [];
2112
	fn.paths = []; // The array of path binding (array/dictionary)s for each tag/else block's args and props
2113
	for (; i < l; i++) {
2114
		fn.paths.push(paths = pathsArr[i]);
2115
		for (key in paths) {
2116
			if (key !== "_jsvto" && paths.hasOwnProperty(key) && paths[key].length && !paths[key].skp) {
2117
				fn.deps = fn.deps.concat(paths[key]); // deps is the concatenation of the paths arrays for the different bindings
2118
			}
2119
		}
2120
	}
2121
}
2122
 
2123
function parsedParam(args, props, ctx) {
2124
	return [args.slice(0, -1), props.slice(0, -1), ctx.slice(0, -1)];
2125
}
2126
 
2127
function paramStructure(paramCode, paramVals) {
2128
	return '\n\tparams:{args:[' + paramCode[0] + '],\n\tprops:{' + paramCode[1] + '}'
2129
		+ (paramCode[2] ? ',\n\tctx:{' + paramCode[2] + '}' : "")
2130
		+ '},\n\targs:[' + paramVals[0] + '],\n\tprops:{' + paramVals[1] + '}'
2131
		+ (paramVals[2] ? ',\n\tctx:{' + paramVals[2] + '}' : "");
2132
}
2133
 
2134
function parseParams(params, pathBindings, tmpl, isLinkExpr) {
2135
 
2136
	function parseTokens(all, lftPrn0, lftPrn, bound, path, operator, err, eq, path2, late, prn,
2137
												comma, lftPrn2, apos, quot, rtPrn, rtPrnDot, prn2, space, index, full) {
2138
	// /(\()(?=\s*\()|(?:([([])\s*)?(?:(\^?)(~?[\w$.^]+)?\s*((\+\+|--)|\+|-|~(?![\w$])|&&|\|\||===|!==|==|!=|<=|>=|[<>%*:?\/]|(=))\s*|(!*?(@)?[#~]?[\w$.^]+)([([])?)|(,\s*)|(?:(\()\s*)?\\?(?:(')|("))|(?:\s*(([)\]])(?=[.^]|\s*$|[^([])|[)\]])([([]?))|(\s+)/g,
2139
	//lftPrn0           lftPrn         bound     path               operator     err                                          eq      path2 late            prn      comma  lftPrn2          apos quot        rtPrn  rtPrnDot                  prn2     space
2140
	// (left paren? followed by (path? followed by operator) or (path followed by paren?)) or comma or apos or quot or right paren or space
2141
 
2142
		function parsePath(allPath, not, object, helper, view, viewProperty, pathTokens, leafToken) {
2143
			// /^(!*?)(?:null|true|false|\d[\d.]*|([\w$]+|\.|~([\w$]+)|#(view|([\w$]+))?)([\w$.^]*?)(?:[.[^]([\w$]+)\]?)?)$/g,
2144
			//    not                               object     helper    view  viewProperty pathTokens      leafToken
2145
			subPath = object === ".";
2146
			if (object) {
2147
				path = path.slice(not.length);
2148
				if (/^\.?constructor$/.test(leafToken||path)) {
2149
					syntaxError(allPath);
2150
				}
2151
				if (!subPath) {
2152
					allPath = (late // late path @a.b.c: not throw on 'property of undefined' if a undefined, and will use _getOb() after linking to resolve late.
2153
							? (isLinkExpr ? '' : '(ltOb.lt=ltOb.lt||') + '(ob='
2154
							: ""
2155
						)
2156
						+ (helper
2157
							? 'view.ctxPrm("' + helper + '")'
2158
							: view
2159
								? "view"
2160
								: "data")
2161
						+ (late
2162
							? ')===undefined' + (isLinkExpr ? '' : ')') + '?"":view._getOb(ob,"'
2163
							: ""
2164
						)
2165
						+ (leafToken
2166
							? (viewProperty
2167
								? "." + viewProperty
2168
								: helper
2169
									? ""
2170
									: (view ? "" : "." + object)
2171
								) + (pathTokens || "")
2172
							: (leafToken = helper ? "" : view ? viewProperty || "" : object, ""));
2173
					allPath = allPath + (leafToken ? "." + leafToken : "");
2174
 
2175
					allPath = not + (allPath.slice(0, 9) === "view.data"
2176
						? allPath.slice(5) // convert #view.data... to data...
2177
						: allPath)
2178
					+ (late
2179
							? (isLinkExpr ? '"': '",ltOb') + (prn ? ',1)':')')
2180
							: ""
2181
						);
2182
				}
2183
				if (bindings) {
2184
					binds = named === "_linkTo" ? (bindto = pathBindings._jsvto = pathBindings._jsvto || []) : bndCtx.bd;
2185
					if (theOb = subPath && binds[binds.length-1]) {
2186
						if (theOb._cpfn) { // Computed property exprOb
2187
							while (theOb.sb) {
2188
								theOb = theOb.sb;
2189
							}
2190
							if (theOb.prm) {
2191
								if (theOb.bnd) {
2192
									path = "^" + path.slice(1);
2193
								}
2194
								theOb.sb = path;
2195
								theOb.bnd = theOb.bnd || path[0] === "^";
2196
							}
2197
						}
2198
					} else {
2199
						binds.push(path);
2200
					}
2201
					if (prn && !subPath) {
2202
						pathStart[fnDp] = ind;
2203
						compiledPathStart[fnDp] = compiledPath[fnDp].length;
2204
					}
2205
				}
2206
			}
2207
			return allPath;
2208
		}
2209
 
2210
		//bound = bindings && bound;
2211
		if (bound && !eq) {
2212
			path = bound + path; // e.g. some.fn(...)^some.path - so here path is "^some.path"
2213
		}
2214
		operator = operator || "";
2215
		lftPrn2 = lftPrn2 || "";
2216
		lftPrn = lftPrn || lftPrn0 || lftPrn2;
2217
		path = path || path2;
2218
 
2219
		if (late && (late = !/\)|]/.test(full[index-1]))) {
2220
			path = path.slice(1).split(".").join("^"); // Late path @z.b.c. Use "^" rather than "." to ensure that deep binding will be used
2221
		}
2222
		// Could do this - but not worth perf cost?? :-
2223
		// if (!path.lastIndexOf("#data.", 0)) { path = path.slice(6); } // If path starts with "#data.", remove that.
2224
		prn = prn || prn2 || "";
2225
		var expr, binds, theOb, newOb, subPath, lftPrnFCall, ret,
2226
			ind = index;
2227
 
2228
		if (!aposed && !quoted) {
2229
			if (err) {
2230
				syntaxError(params);
2231
			}
2232
			if (rtPrnDot && bindings) {
2233
				// 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
2234
				// 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)
2235
				expr = pathStart[fnDp-1];
2236
				if (full.length - 1 > ind - (expr || 0)) { // We need to compile a subexpression
2237
					expr = $.trim(full.slice(expr, ind + all.length));
2238
					binds = bindto || bndStack[fnDp-1].bd;
2239
					// Insert exprOb object, to be used during binding to return the computed object
2240
					theOb = binds[binds.length-1];
2241
					if (theOb && theOb.prm) {
2242
						while (theOb.sb && theOb.sb.prm) {
2243
							theOb = theOb.sb;
2244
						}
2245
						newOb = theOb.sb = {path: theOb.sb, bnd: theOb.bnd};
2246
					} else {
2247
						binds.push(newOb = {path: binds.pop()}); // Insert exprOb object, to be used during binding to return the computed object
2248
					}
2249
					if (theOb && theOb.sb === newOb) {
2250
						compiledPath[fnDp] = compiledPath[fnDp-1].slice(theOb._cpPthSt) + compiledPath[fnDp];
2251
						compiledPath[fnDp-1] = compiledPath[fnDp-1].slice(0, theOb._cpPthSt);
2252
					}
2253
					newOb._cpPthSt = compiledPathStart[fnDp-1];
2254
					newOb._cpKey = expr;
2255
 
2256
					compiledPath[fnDp] += full.slice(prevIndex, index);
2257
					prevIndex = index;
2258
 
2259
					newOb._cpfn = cpFnStore[expr] = cpFnStore[expr] || // Compiled function for computed value: get from store, or compile and store
2260
						new Function("data,view,j", // Compiled function for computed value in template
2261
					"//" + expr + "\nvar v;\nreturn ((v=" + compiledPath[fnDp] + (rtPrn === "]" ? ")]" : rtPrn) + ")!=null?v:null);");
2262
 
2263
					compiledPath[fnDp-1] += (fnCall[prnDp] && $subSettingsAdvanced.cache ? "view.getCache(\"" + expr.replace(rEscapeQuotes, "\\$&") + "\"" : compiledPath[fnDp]);
2264
 
2265
					newOb.prm = bndCtx.bd;
2266
					newOb.bnd = newOb.bnd || newOb.path && newOb.path.indexOf("^") >= 0;
2267
				}
2268
				compiledPath[fnDp] = "";
2269
			}
2270
			if (prn === "[") {
2271
				prn = "[j._sq(";
2272
			}
2273
			if (lftPrn === "[") {
2274
				lftPrn = "[j._sq(";
2275
			}
2276
		}
2277
		ret = (aposed
2278
			// within single-quoted string
2279
			? (aposed = !apos, (aposed ? all : lftPrn2 + '"'))
2280
			: quoted
2281
			// within double-quoted string
2282
				? (quoted = !quot, (quoted ? all : lftPrn2 + '"'))
2283
				:
2284
			(
2285
				(lftPrn
2286
					? (
2287
						prnStack[++prnDp] = true,
2288
						prnInd[prnDp] = 0,
2289
						bindings && (
2290
							pathStart[fnDp++] = ind++,
2291
							bndCtx = bndStack[fnDp] = {bd: []},
2292
							compiledPath[fnDp] = "",
2293
							compiledPathStart[fnDp] = 1
2294
						),
2295
						lftPrn) // Left paren, (not a function call paren)
2296
					: "")
2297
				+ (space
2298
					? (prnDp
2299
						? "" // A space within parens or within function call parens, so not a separator for tag args
2300
			// New arg or prop - so insert backspace \b (\x08) as separator for named params, used subsequently by rBuildHash, and prepare new bindings array
2301
						: (paramIndex = full.slice(paramIndex, ind), named
2302
							? (named = boundName = bindto = false, "\b")
2303
							: "\b,") + paramIndex + (paramIndex = ind + all.length, bindings && pathBindings.push(bndCtx.bd = []), "\b")
2304
					)
2305
					: eq
2306
			// named param. Remove bindings for arg and create instead bindings array for prop
2307
						? (fnDp && syntaxError(params), bindings && pathBindings.pop(), named = "_" + path, boundName = bound, paramIndex = ind + all.length,
2308
								bindings && ((bindings = bndCtx.bd = pathBindings[named] = []), bindings.skp = !bound), path + ':')
2309
						: path
2310
			// path
2311
							? (path.split("^").join(".").replace($sub.rPath, parsePath)
2312
								+ (prn || operator)
2313
							)
2314
							: operator
2315
			// operator
2316
								? operator
2317
								: rtPrn
2318
			// function
2319
									? rtPrn === "]" ? ")]" : ")"
2320
									: comma
2321
										? (fnCall[prnDp] || syntaxError(params), ",") // We don't allow top-level literal arrays or objects
2322
										: lftPrn0
2323
											? ""
2324
											: (aposed = apos, quoted = quot, '"')
2325
			))
2326
		);
2327
 
2328
		if (!aposed && !quoted) {
2329
			if (rtPrn) {
2330
				fnCall[prnDp] = false;
2331
				prnDp--;
2332
			}
2333
		}
2334
 
2335
		if (bindings) {
2336
			if (!aposed && !quoted) {
2337
				if (rtPrn) {
2338
					if (prnStack[prnDp+1]) {
2339
						bndCtx = bndStack[--fnDp];
2340
						prnStack[prnDp+1] = false;
2341
					}
2342
					prnStart = prnInd[prnDp+1];
2343
				}
2344
				if (prn) {
2345
					prnInd[prnDp+1] = compiledPath[fnDp].length + (lftPrn ? 1 : 0);
2346
					if (path || rtPrn) {
2347
						bndCtx = bndStack[++fnDp] = {bd: []};
2348
						prnStack[prnDp+1] = true;
2349
					}
2350
				}
2351
			}
2352
 
2353
			compiledPath[fnDp] = (compiledPath[fnDp]||"") + full.slice(prevIndex, index);
2354
			prevIndex = index+all.length;
2355
 
2356
			if (!aposed && !quoted) {
2357
				if (lftPrnFCall = lftPrn && prnStack[prnDp+1]) {
2358
					compiledPath[fnDp-1] += lftPrn;
2359
					compiledPathStart[fnDp-1]++;
2360
				}
2361
				if (prn === "(" && subPath && !newOb) {
2362
					compiledPath[fnDp] = compiledPath[fnDp-1].slice(prnStart) + compiledPath[fnDp];
2363
					compiledPath[fnDp-1] = compiledPath[fnDp-1].slice(0, prnStart);
2364
				}
2365
			}
2366
			compiledPath[fnDp] += lftPrnFCall ? ret.slice(1) : ret;
2367
		}
2368
 
2369
		if (!aposed && !quoted && prn) {
2370
			prnDp++;
2371
			if (path && prn === "(") {
2372
				fnCall[prnDp] = true;
2373
			}
2374
		}
2375
 
2376
		if (!aposed && !quoted && prn2) {
2377
			if (bindings) {
2378
				compiledPath[fnDp] += prn;
2379
			}
2380
			ret += prn;
2381
		}
2382
		return ret;
2383
	}
2384
 
2385
	var named, bindto, boundName, result,
2386
		quoted, // boolean for string content in double quotes
2387
		aposed, // or in single quotes
2388
		bindings = pathBindings && pathBindings[0], // bindings array for the first arg
2389
		bndCtx = {bd: bindings},
2390
		bndStack = {0: bndCtx},
2391
		paramIndex = 0, // list,
2392
		// The following are used for tracking path parsing including nested paths, such as "a.b(c^d + (e))^f", and chained computed paths such as
2393
		// "a.b().c^d().e.f().g" - which has four chained paths, "a.b()", "^c.d()", ".e.f()" and ".g"
2394
		prnDp = 0,     // For tracking paren depth (not function call parens)
2395
		fnDp = 0,      // For tracking depth of function call parens
2396
		prnInd = {},   // We are in a function call
2397
		prnStart = 0,  // tracks the start of the current path such as c^d() in the above example
2398
		prnStack = {}, // tracks parens which are not function calls, and so are associated with new bndStack contexts
2399
		fnCall = {},   // We are in a function call
2400
		pathStart = {},// tracks the start of the current path such as c^d() in the above example
2401
		compiledPathStart = {0: 0},
2402
		compiledPath = {0:""},
2403
		prevIndex = 0;
2404
 
2405
	if (params[0] === "@") {
2406
		params = params.replace(rBracketQuote, ".");
2407
	}
2408
	result = (params + (tmpl ? " " : "")).replace($sub.rPrm, parseTokens);
2409
 
2410
	if (bindings) {
2411
		result = compiledPath[0];
2412
	}
2413
 
2414
	return !prnDp && result || syntaxError(params); // Syntax error if unbalanced parens in params expression
2415
}
2416
 
2417
function buildCode(ast, tmpl, isLinkExpr) {
2418
	// Build the template function code from the AST nodes, and set as property on the passed-in template object
2419
	// Used for compiling templates, and also by JsViews to build functions for data link expressions
2420
	var i, node, tagName, converter, tagCtx, hasTag, hasEncoder, getsVal, hasCnvt, useCnvt, tmplBindings, pathBindings, params, boundOnErrStart,
2421
		boundOnErrEnd, tagRender, nestedTmpls, tmplName, nestedTmpl, tagAndElses, content, markup, nextIsElse, oldCode, isElse, isGetVal, tagCtxFn,
2422
		onError, tagStart, trigger, lateRender, retStrOpen, retStrClose,
2423
		tmplBindingKey = 0,
2424
		useViews = $subSettingsAdvanced.useViews || tmpl.useViews || tmpl.tags || tmpl.templates || tmpl.helpers || tmpl.converters,
2425
		code = "",
2426
		tmplOptions = {},
2427
		l = ast.length;
2428
 
2429
	if ("" + tmpl === tmpl) {
2430
		tmplName = isLinkExpr ? 'data-link="' + tmpl.replace(rNewLine, " ").slice(1, -1) + '"' : tmpl;
2431
		tmpl = 0;
2432
	} else {
2433
		tmplName = tmpl.tmplName || "unnamed";
2434
		if (tmpl.allowCode) {
2435
			tmplOptions.allowCode = true;
2436
		}
2437
		if (tmpl.debug) {
2438
			tmplOptions.debug = true;
2439
		}
2440
		tmplBindings = tmpl.bnds;
2441
		nestedTmpls = tmpl.tmpls;
2442
	}
2443
	for (i = 0; i < l; i++) {
2444
		// AST nodes: [0: tagName, 1: converter, 2: content, 3: params, 4: code, 5: onError, 6: trigger, 7:pathBindings, 8: contentMarkup]
2445
		node = ast[i];
2446
 
2447
		// Add newline for each callout to t() c() etc. and each markup string
2448
		if ("" + node === node) {
2449
			// a markup string to be inserted
2450
			code += '+"' + node + '"';
2451
		} else {
2452
			// a compiled tag expression to be inserted
2453
			tagName = node[0];
2454
			if (tagName === "*") {
2455
				// Code tag: {{* }}
2456
				code += ";\n" + node[1] + "\nret=ret";
2457
			} else {
2458
				converter = node[1];
2459
				content = !isLinkExpr && node[2];
2460
				tagCtx = paramStructure(node[3], params = node[4]);
2461
				trigger = node[6];
2462
				lateRender = node[7];
2463
				if (node[8]) { // latePath @a.b.c or @~a.b.c
2464
					retStrOpen = "\nvar ob,ltOb={},ctxs=";
2465
					retStrClose = ";\nctxs.lt=ltOb.lt;\nreturn ctxs;";
2466
				} else {
2467
					retStrOpen = "\nreturn ";
2468
					retStrClose = "";
2469
				}
2470
				markup = node[10] && node[10].replace(rUnescapeQuotes, "$1");
2471
				if (isElse = tagName === "else") {
2472
					if (pathBindings) {
2473
						pathBindings.push(node[9]);
2474
					}
2475
				} else {
2476
					onError = node[5] || $subSettings.debugMode !== false && "undefined"; // If debugMode not false, set default onError handler on tag to "undefined" (see onRenderError)
2477
					if (tmplBindings && (pathBindings = node[9])) { // Array of paths, or false if not data-bound
2478
						pathBindings = [pathBindings];
2479
						tmplBindingKey = tmplBindings.push(1); // Add placeholder in tmplBindings for compiled function
2480
					}
2481
				}
2482
				useViews = useViews || params[1] || params[2] || pathBindings || /view.(?!index)/.test(params[0]);
2483
				// useViews is for perf optimization. For render() we only use views if necessary - for the more advanced scenarios.
2484
				// We use views if there are props, contextual properties or args with #... (other than #index) - but you can force
2485
				// using the full view infrastructure, (and pay a perf price) by opting in: Set useViews: true on the template, manually...
2486
				if (isGetVal = tagName === ":") {
2487
					if (converter) {
2488
						tagName = converter === HTML ? ">" : converter + tagName;
2489
					}
2490
				} else {
2491
					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!!
2492
						// Create template object for nested template
2493
						nestedTmpl = tmplObject(markup, tmplOptions);
2494
						nestedTmpl.tmplName = tmplName + "/" + tagName;
2495
						// Compile to AST and then to compiled function
2496
						nestedTmpl.useViews = nestedTmpl.useViews || useViews;
2497
						buildCode(content, nestedTmpl);
2498
						useViews = nestedTmpl.useViews;
2499
						nestedTmpls.push(nestedTmpl);
2500
					}
2501
 
2502
					if (!isElse) {
2503
						// This is not an else tag.
2504
						tagAndElses = tagName;
2505
						useViews = useViews || tagName && (!$tags[tagName] || !$tags[tagName].flow);
2506
						// Switch to a new code string for this bound tag (and its elses, if it has any) - for returning the tagCtxs array
2507
						oldCode = code;
2508
						code = "";
2509
					}
2510
					nextIsElse = ast[i + 1];
2511
					nextIsElse = nextIsElse && nextIsElse[0] === "else";
2512
				}
2513
				tagStart = onError ? ";\ntry{\nret+=" : "\n+";
2514
				boundOnErrStart = "";
2515
				boundOnErrEnd = "";
2516
 
2517
				if (isGetVal && (pathBindings || trigger || converter && converter !== HTML || lateRender)) {
2518
					// For convertVal we need a compiled function to return the new tagCtx(s)
2519
					tagCtxFn = new Function("data,view,j", "// " + tmplName + " " + (++tmplBindingKey) + " " + tagName
2520
						+ retStrOpen + "{" + tagCtx + "};" + retStrClose);
2521
					tagCtxFn._er = onError;
2522
					tagCtxFn._tag = tagName;
2523
					tagCtxFn._bd = !!pathBindings; // data-linked tag {^{.../}}
2524
					tagCtxFn._lr = lateRender;
2525
 
2526
					if (isLinkExpr) {
2527
						return tagCtxFn;
2528
					}
2529
 
2530
					setPaths(tagCtxFn, pathBindings);
2531
					tagRender = 'c("' + converter + '",view,';
2532
					useCnvt = true;
2533
					boundOnErrStart = tagRender + tmplBindingKey + ",";
2534
					boundOnErrEnd = ")";
2535
				}
2536
				code += (isGetVal
2537
					? (isLinkExpr ? (onError ? "try{\n" : "") + "return " : tagStart) + (useCnvt // Call _cnvt if there is a converter: {{cnvt: ... }} or {^{cnvt: ... }}
2538
						? (useCnvt = undefined, useViews = hasCnvt = true, tagRender + (tagCtxFn
2539
							? ((tmplBindings[tmplBindingKey - 1] = tagCtxFn), tmplBindingKey) // Store the compiled tagCtxFn in tmpl.bnds, and pass the key to convertVal()
2540
							: "{" + tagCtx + "}") + ")")
2541
						: tagName === ">"
2542
							? (hasEncoder = true, "h(" + params[0] + ")")
2543
							: (getsVal = true, "((v=" + params[0] + ')!=null?v:' + (isLinkExpr ? 'null)' : '"")'))
2544
							// Non strict equality so data-link="title{:expr}" with expr=null/undefined removes title attribute
2545
					)
2546
					: (hasTag = true, "\n{view:view,content:false,tmpl:" // Add this tagCtx to the compiled code for the tagCtxs to be passed to renderTag()
2547
						+ (content ? nestedTmpls.length : "false") + "," // For block tags, pass in the key (nestedTmpls.length) to the nested content template
2548
						+ tagCtx + "},"));
2549
 
2550
				if (tagAndElses && !nextIsElse) {
2551
					// This is a data-link expression or an inline tag without any elses, or the last {{else}} of an inline tag
2552
					// We complete the code for returning the tagCtxs array
2553
					code = "[" + code.slice(0, -1) + "]";
2554
					tagRender = 't("' + tagAndElses + '",view,this,';
2555
					if (isLinkExpr || pathBindings) {
2556
						// This is a bound tag (data-link expression or inline bound tag {^{tag ...}}) so we store a compiled tagCtxs function in tmp.bnds
2557
						code = new Function("data,view,j", " // " + tmplName + " " + tmplBindingKey + " " + tagAndElses + retStrOpen + code
2558
							+ retStrClose);
2559
						code._er = onError;
2560
						code._tag = tagAndElses;
2561
						if (pathBindings) {
2562
							setPaths(tmplBindings[tmplBindingKey - 1] = code, pathBindings);
2563
						}
2564
						code._lr = lateRender;
2565
						if (isLinkExpr) {
2566
							return code; // For a data-link expression we return the compiled tagCtxs function
2567
						}
2568
						boundOnErrStart = tagRender + tmplBindingKey + ",undefined,";
2569
						boundOnErrEnd = ")";
2570
					}
2571
 
2572
					// This is the last {{else}} for an inline tag.
2573
					// For a bound tag, pass the tagCtxs fn lookup key to renderTag.
2574
					// For an unbound tag, include the code directly for evaluating tagCtxs array
2575
					code = oldCode + tagStart + tagRender + (pathBindings && tmplBindingKey || code) + ")";
2576
					pathBindings = 0;
2577
					tagAndElses = 0;
2578
				}
2579
				if (onError && !nextIsElse) {
2580
					useViews = true;
2581
					code += ';\n}catch(e){ret' + (isLinkExpr ? "urn " : "+=") + boundOnErrStart + 'j._err(e,view,' + onError + ')' + boundOnErrEnd + ';}' + (isLinkExpr ? "" : '\nret=ret');
2582
				}
2583
			}
2584
		}
2585
	}
2586
	// Include only the var references that are needed in the code
2587
	code = "// " + tmplName
2588
		+ (tmplOptions.debug ? "\ndebugger;" : "")
2589
		+ "\nvar v"
2590
		+ (hasTag ? ",t=j._tag" : "")                // has tag
2591
		+ (hasCnvt ? ",c=j._cnvt" : "")              // converter
2592
		+ (hasEncoder ? ",h=j._html" : "")           // html converter
2593
		+ (isLinkExpr
2594
				? (node[8] // late @... path?
2595
						? ", ob"
2596
						: ""
2597
					) + ";\n"
2598
				: ',ret=""')
2599
		+ code
2600
		+ (isLinkExpr ? "\n" : ";\nreturn ret;");
2601
 
2602
	try {
2603
		code = new Function("data,view,j", code);
2604
	} catch (e) {
2605
		syntaxError("Compiled template code:\n\n" + code + '\n: "' + (e.message||e) + '"');
2606
	}
2607
	if (tmpl) {
2608
		tmpl.fn = code;
2609
		tmpl.useViews = !!useViews;
2610
	}
2611
	return code;
2612
}
2613
 
2614
//==========
2615
// Utilities
2616
//==========
2617
 
2618
// Merge objects, in particular contexts which inherit from parent contexts
2619
function extendCtx(context, parentContext) {
2620
	// Return copy of parentContext, unless context is defined and is different, in which case return a new merged context
2621
	// If neither context nor parentContext are defined, return undefined
2622
	return context && context !== parentContext
2623
		? (parentContext
2624
			? $extend($extend({}, parentContext), context)
2625
			: context)
2626
		: parentContext && $extend({}, parentContext);
2627
}
2628
 
2629
function getTargetProps(source, tagCtx) {
2630
	// this pointer is theMap - which has tagCtx.props too
2631
	// arguments: tagCtx.args.
2632
	var key, prop,
2633
		map = tagCtx.map,
2634
		propsArr = map && map.propsArr;
2635
 
2636
	if (!propsArr) { // map.propsArr is the full array of {key:..., prop:...} objects
2637
		propsArr = [];
2638
		if (typeof source === OBJECT || $isFunction(source)) {
2639
			for (key in source) {
2640
				prop = source[key];
2641
				if (key !== $expando && source.hasOwnProperty(key) && (!tagCtx.props.noFunctions || !$.isFunction(prop))) {
2642
					propsArr.push({key: key, prop: prop});
2643
				}
2644
			}
2645
		}
2646
		if (map) {
2647
			map.propsArr = map.options && propsArr; // If bound {^{props}} and not isRenderCall, store propsArr on map (map.options is defined only for bound, && !isRenderCall)
2648
		}
2649
	}
2650
	return getTargetSorted(propsArr, tagCtx); // Obtains map.tgt, by filtering, sorting and splicing the full propsArr
2651
}
2652
 
2653
function getTargetSorted(value, tagCtx) {
2654
	// getTgt
2655
	var mapped, start, end,
2656
		tag = tagCtx.tag,
2657
		props = tagCtx.props,
2658
		propParams = tagCtx.params.props,
2659
		filter = props.filter,
2660
		sort = props.sort,
2661
		directSort = sort === true,
2662
		step = parseInt(props.step),
2663
		reverse = props.reverse ? -1 : 1;
2664
 
2665
	if (!$isArray(value)) {
2666
		return value;
2667
	}
2668
	if (directSort || sort && "" + sort === sort) {
2669
		// Temporary mapped array holds objects with index and sort-value
2670
		mapped = value.map(function(item, i) {
2671
			item = directSort ? item : getPathObject(item, sort);
2672
			return {i: i, v: "" + item === item ? item.toLowerCase() : item};
2673
		});
2674
		// Sort mapped array
2675
		mapped.sort(function(a, b) {
2676
			return a.v > b.v ? reverse : a.v < b.v ? -reverse : 0;
2677
		});
2678
		// Map to new array with resulting order
2679
		value = mapped.map(function(item){
2680
			return value[item.i];
2681
		});
2682
	} else if ((sort || reverse < 0) && !tag.dataMap) {
2683
		value = value.slice(); // Clone array first if not already a new array
2684
	}
2685
	if ($isFunction(sort)) {
2686
		value = value.sort(function() { // Wrap the sort function to provide tagCtx as 'this' pointer
2687
			return sort.apply(tagCtx, arguments);
2688
		});
2689
	}
2690
	if (reverse < 0 && (!sort || $isFunction(sort))) { // Reverse result if not already reversed in sort
2691
		value = value.reverse();
2692
	}
2693
 
2694
	if (value.filter && filter) { // IE8 does not support filter
2695
		value = value.filter(filter, tagCtx);
2696
		if (tagCtx.tag.onFilter) {
2697
			tagCtx.tag.onFilter(tagCtx);
2698
		}
2699
	}
2700
 
2701
	if (propParams.sorted) {
2702
		mapped = (sort || reverse < 0) ? value : value.slice();
2703
		if (tag.sorted) {
2704
			$.observable(tag.sorted).refresh(mapped); // Note that this might cause the start and end props to be modified - e.g. by pager tag control
2705
		} else {
2706
			tagCtx.map.sorted = mapped;
2707
		}
2708
	}
2709
 
2710
	start = props.start; // Get current value - after possible changes triggered by tag.sorted refresh() above
2711
	end = props.end;
2712
	if (propParams.start && start === undefined || propParams.end && end === undefined) {
2713
		start = end = 0;
2714
	}
2715
	if (!isNaN(start) || !isNaN(end)) { // start or end specified, but not the auto-create Number array scenario of {{for start=xxx end=yyy}}
2716
		start = +start || 0;
2717
		end = end === undefined || end > value.length ? value.length : +end;
2718
		value = value.slice(start, end);
2719
	}
2720
	if (step > 1) {
2721
		start = 0;
2722
		end = value.length;
2723
		mapped = [];
2724
		for (; start<end; start+=step) {
2725
			mapped.push(value[start]);
2726
		}
2727
		value = mapped;
2728
	}
2729
	if (propParams.paged && tag.paged) {
2730
		$observable(tag.paged).refresh(value);
2731
	}
2732
 
2733
	return value;
2734
}
2735
 
2736
/** Render the template as a string, using the specified data and helpers/context
2737
* $("#tmpl").render()
2738
*
2739
* @param {any}        data
2740
* @param {hash}       [helpersOrContext]
2741
* @param {boolean}    [noIteration]
2742
* @returns {string}   rendered template
2743
*/
2744
function $fnRender(data, context, noIteration) {
2745
	var tmplElem = this.jquery && (this[0] || error('Unknown template')), // Targeted element not found for jQuery template selector such as "#myTmpl"
2746
		tmpl = tmplElem.getAttribute(tmplAttr);
2747
 
2748
	return renderContent.call(tmpl && $.data(tmplElem)[jsvTmpl] || $templates(tmplElem),
2749
		data, context, noIteration);
2750
}
2751
 
2752
//========================== Register converters ==========================
2753
 
2754
function getCharEntity(ch) {
2755
	// Get character entity for HTML, Attribute and optional data encoding
2756
	return charEntities[ch] || (charEntities[ch] = "&#" + ch.charCodeAt(0) + ";");
2757
}
2758
 
2759
function getCharFromEntity(match, token) {
2760
	// Get character from HTML entity, for optional data unencoding
2761
	return charsFromEntities[token] || "";
2762
}
2763
 
2764
function htmlEncode(text) {
2765
	// HTML encode: Replace < > & ' " ` etc. by corresponding entities.
2766
	return text != undefined ? rIsHtml.test(text) && ("" + text).replace(rHtmlEncode, getCharEntity) || text : "";
2767
}
2768
 
2769
function dataEncode(text) {
2770
	// Encode just < > and & - intended for 'safe data' along with {{:}} rather than {{>}}
2771
  return "" + text === text ? text.replace(rDataEncode, getCharEntity) : text;
2772
}
2773
 
2774
function dataUnencode(text) {
2775
  // Unencode just < > and & - intended for 'safe data' along with {{:}} rather than {{>}}
2776
  return "" + text === text ? text.replace(rDataUnencode, getCharFromEntity) : text;
2777
}
2778
 
2779
//========================== Initialize ==========================
2780
 
2781
$sub = $views.sub;
2782
$viewsSettings = $views.settings;
2783
 
2784
if (!(jsr || $ && $.render)) {
2785
	// JsRender/JsViews not already loaded (or loaded without jQuery, and we are now moving from jsrender namespace to jQuery namepace)
2786
	for (jsvStoreName in jsvStores) {
2787
		registerStore(jsvStoreName, jsvStores[jsvStoreName]);
2788
	}
2789
 
2790
	$converters = $views.converters;
2791
	$helpers = $views.helpers;
2792
	$tags = $views.tags;
2793
 
2794
	$sub._tg.prototype = {
2795
		baseApply: baseApply,
2796
		cvtArgs: convertArgs,
2797
		bndArgs: convertBoundArgs,
2798
		ctxPrm: contextParameter
2799
	};
2800
 
2801
	topView = $sub.topView = new View();
2802
 
2803
	//BROWSER-SPECIFIC CODE
2804
	if ($) {
2805
 
2806
		////////////////////////////////////////////////////////////////////////////////////////////////
2807
		// jQuery (= $) is loaded
2808
 
2809
		$.fn.render = $fnRender;
2810
		$expando = $.expando;
2811
		if ($.observable) {
2812
			if (versionNumber !== (versionNumber = $.views.jsviews)) {
2813
				// Different version of jsRender was loaded
2814
				throw "jquery.observable.js requires jsrender.js " + versionNumber;
2815
			}
2816
			$extend($sub, $.views.sub); // jquery.observable.js was loaded before jsrender.js
2817
			$views.map = $.views.map;
2818
		}
2819
 
2820
	} else {
2821
		////////////////////////////////////////////////////////////////////////////////////////////////
2822
		// jQuery is not loaded.
2823
 
2824
		$ = {};
2825
 
2826
		if (setGlobals) {
2827
			global.jsrender = $; // We are loading jsrender.js from a script element, not AMD or CommonJS, so set global
2828
		}
2829
 
2830
		// Error warning if jsrender.js is used as template engine on Node.js (e.g. Express or Hapi...)
2831
		// Use jsrender-node.js instead...
2832
		$.renderFile = $.__express = $.compile = function() { throw "Node.js: use npm jsrender, or jsrender-node.js"; };
2833
 
2834
		//END BROWSER-SPECIFIC CODE
2835
		$.isFunction = function(ob) {
2836
			return typeof ob === "function";
2837
		};
2838
 
2839
		$.isArray = Array.isArray || function(obj) {
2840
			return ({}.toString).call(obj) === "[object Array]";
2841
		};
2842
 
2843
		$sub._jq = function(jq) { // private method to move from JsRender APIs from jsrender namespace to jQuery namespace
2844
			if (jq !== $) {
2845
				$extend(jq, $); // map over from jsrender namespace to jQuery namespace
2846
				$ = jq;
2847
				$.fn.render = $fnRender;
2848
				delete $.jsrender;
2849
				$expando = $.expando;
2850
			}
2851
		};
2852
 
2853
		$.jsrender = versionNumber;
2854
	}
2855
	$subSettings = $sub.settings;
2856
	$subSettings.allowCode = false;
2857
	$isFunction = $.isFunction;
2858
	$.render = $render;
2859
	$.views = $views;
2860
	$.templates = $templates = $views.templates;
2861
 
2862
	for (setting in $subSettings) {
2863
		addSetting(setting);
2864
	}
2865
 
2866
	/**
2867
	* $.views.settings.debugMode(true)
2868
	* @param {boolean} debugMode
2869
	* @returns {Settings}
2870
	*
2871
	* debugMode = $.views.settings.debugMode()
2872
	* @returns {boolean}
2873
	*/
2874
	($viewsSettings.debugMode = function(debugMode) {
2875
		return debugMode === undefined
2876
			? $subSettings.debugMode
2877
			: (
2878
				$subSettings._clFns && $subSettings._clFns(), // Clear linkExprStore (cached compiled expressions), since debugMode setting affects compilation for expressions
2879
				$subSettings.debugMode = debugMode,
2880
				$subSettings.onError = debugMode + "" === debugMode
2881
					? function() { return debugMode; }
2882
					: $isFunction(debugMode)
2883
						? debugMode
2884
						: undefined,
2885
				$viewsSettings);
2886
	})(false); // jshint ignore:line
2887
 
2888
	$subSettingsAdvanced = $subSettings.advanced = {
2889
		cache: true, // By default use cached values of computed values (Otherwise, set advanced cache setting to false)
2890
		useViews: false,
2891
		_jsv: false // For global access to JsViews store
2892
	};
2893
 
2894
	//========================== Register tags ==========================
2895
 
2896
	$tags({
2897
		"if": {
2898
			render: function(val) {
2899
				// This function is called once for {{if}} and once for each {{else}}.
2900
				// We will use the tag.rendering object for carrying rendering state across the calls.
2901
				// If not done (a previous block has not been rendered), look at expression for this block and render the block if expression is truthy
2902
				// Otherwise return ""
2903
				var self = this,
2904
					tagCtx = self.tagCtx,
2905
					ret = (self.rendering.done || !val && (tagCtx.args.length || !tagCtx.index))
2906
						? ""
2907
						: (self.rendering.done = true,
2908
							self.selected = tagCtx.index,
2909
							undefined); // Test is satisfied, so render content on current context
2910
				return ret;
2911
			},
2912
			contentCtx: true, // Inherit parent view data context
2913
			flow: true
2914
		},
2915
		"for": {
2916
			sortDataMap: dataMap(getTargetSorted),
2917
			init: function(val, cloned) {
2918
				this.setDataMap(this.tagCtxs);
2919
			},
2920
			render: function(val) {
2921
				// This function is called once for {{for}} and once for each {{else}}.
2922
				// We will use the tag.rendering object for carrying rendering state across the calls.
2923
				var value, filter, srtField, isArray, i, sorted, end, step,
2924
					self = this,
2925
					tagCtx = self.tagCtx,
2926
					range = tagCtx.argDefault === false,
2927
					props = tagCtx.props,
2928
					iterate = range || tagCtx.args.length, // Not final else and not auto-create range
2929
					result = "",
2930
					done = 0;
2931
 
2932
				if (!self.rendering.done) {
2933
					value = iterate ? val : tagCtx.view.data; // For the final else, defaults to current data without iteration.
2934
 
2935
					if (range) {
2936
						range = props.reverse ? "unshift" : "push";
2937
						end = +props.end;
2938
						step = +props.step || 1;
2939
						value = []; // auto-create integer array scenario of {{for start=xxx end=yyy}}
2940
						for (i = +props.start || 0; (end - i) * step > 0; i += step) {
2941
							value[range](i);
2942
						}
2943
					}
2944
					if (value !== undefined) {
2945
						isArray = $isArray(value);
2946
						result += tagCtx.render(value, !iterate || props.noIteration);
2947
						// Iterates if data is an array, except on final else - or if noIteration property
2948
						// set to true. (Use {{include}} to compose templates without array iteration)
2949
						done += isArray ? value.length : 1;
2950
					}
2951
					if (self.rendering.done = done) {
2952
						self.selected = tagCtx.index;
2953
					}
2954
					// If nothing was rendered we will look at the next {{else}}. Otherwise, we are done.
2955
				}
2956
				return result;
2957
			},
2958
			setDataMap: function(tagCtxs) {
2959
				var tagCtx, props, paramsProps,
2960
					self = this,
2961
					l = tagCtxs.length;
2962
				while (l--) {
2963
					tagCtx = tagCtxs[l];
2964
					props = tagCtx.props;
2965
					paramsProps = tagCtx.params.props;
2966
					tagCtx.argDefault = props.end === undefined || tagCtx.args.length > 0; // Default to #data except for auto-create range scenario {{for start=xxx end=yyy step=zzz}}
2967
					props.dataMap = (tagCtx.argDefault !== false && $isArray(tagCtx.args[0]) &&
2968
						(paramsProps.sort || paramsProps.start || paramsProps.end || paramsProps.step || paramsProps.filter || paramsProps.reverse
2969
						|| props.sort || props.start || props.end || props.step || props.filter || props.reverse))
2970
						&& self.sortDataMap;
2971
				}
2972
			},
2973
			flow: true
2974
		},
2975
		props: {
2976
			baseTag: "for",
2977
			dataMap: dataMap(getTargetProps),
2978
			init: noop, // Don't execute the base init() of the "for" tag
2979
			flow: true
2980
		},
2981
		include: {
2982
			flow: true
2983
		},
2984
		"*": {
2985
			// {{* code... }} - Ignored if template.allowCode and $.views.settings.allowCode are false. Otherwise include code in compiled template
2986
			render: retVal,
2987
			flow: true
2988
		},
2989
		":*": {
2990
			// {{:* returnedExpression }} - Ignored if template.allowCode and $.views.settings.allowCode are false. Otherwise include code in compiled template
2991
			render: retVal,
2992
			flow: true
2993
		},
2994
		dbg: $helpers.dbg = $converters.dbg = dbgBreak // Register {{dbg/}}, {{dbg:...}} and ~dbg() to throw and catch, as breakpoints for debugging.
2995
	});
2996
 
2997
	$converters({
2998
		html: htmlEncode,
2999
		attr: htmlEncode, // Includes > encoding since rConvertMarkers in JsViews does not skip > characters in attribute strings
3000
		encode: dataEncode,
3001
		unencode: dataUnencode, // Includes > encoding since rConvertMarkers in JsViews does not skip > characters in attribute strings
3002
		url: function(text) {
3003
			// URL encoding helper.
3004
			return text != undefined ? encodeURI("" + text) : text === null ? text : ""; // null returns null, e.g. to remove attribute. undefined returns ""
3005
		}
3006
	});
3007
}
3008
//========================== Define default delimiters ==========================
3009
$subSettings = $sub.settings;
3010
$isArray = ($||jsr).isArray;
3011
$viewsSettings.delimiters("{{", "}}", "^");
3012
 
3013
if (jsrToJq) { // Moving from jsrender namespace to jQuery namepace - copy over the stored items (templates, converters, helpers...)
3014
	jsr.views.sub._jq($);
3015
}
3016
return $ || jsr;
3017
}, window));