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