PageSourceSearch

https://www.metooo.io/assets/plugins/jsviews/jsviews.min.js?01211

js metooo.io collected 2026-10-01 10:11:44 UTC 239,030 bytes, 6,472 lines download raw bytes

vendor: 4,855 bytes, lines 1-131
1/*! jsviews.js v0.9.81 (Beta) single-file version: http://jsviews.com/ */
2/*! includes JsRender, JsObservable and JsViews - see: http://jsviews.com/#download */
3
4/* Interactive data-driven views using JsRender templates */
5
6//<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< JsRender >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
7/* JsRender:
8 * See http://jsviews.com/#jsrender and http://github.com/BorisMoore/jsrender
9 * Copyright 2016, Boris Moore
10 * Released under the MIT License.
11 */
12
13//jshint -W018, -W041
14
15(function(factory, global) {
16	// global var is the this object, which is window when running in the usual browser environment
17	var $ = global.jQuery;
18
19	if (typeof exports === "object") { // CommonJS e.g. Browserify
20		module.exports = $
21			? factory(global, $)
22			: function($) { // If no global jQuery, take jQuery passed as parameter: require("jsviews")(jQuery)
23				return factory(global, $);
24			};
25	} else if (typeof define === "function" && define.amd) { // AMD script loader, e.g. RequireJS
26		define(["jquery"], function($) {
27			return factory(global, $);
28		}); // Require jQuery
29	} else { // Browser using plain <script> tag
30		factory(global, false);
31	}
32} (
33
34// factory (for jsviews.js)
35function(global, $) {
36"use strict";
37
38//========================== Top-level vars ==========================
39
40// global var is the this object, which is window when running in the usual browser environment
41var setGlobals = $ === false; // Only set globals if script block in browser (not AMD and not CommonJS)
42
43$ = $ || global.jQuery; // $ is jQuery passed in by CommonJS loader (Browserify), or global jQuery.
44
45if (!$ || !$.fn) {
46	// jQuery is not loaded.
47	throw "JsViews requires jQuery"; // We require jQuery
48}
49
50var versionNumber = "v0.9.81",
51
52	jsvStoreName, rTag, rTmplString, topView, $views, $observe, $observable, $expando,
53
54//TODO	tmplFnsCache = {},
55	$isFunction, $isArray, $templates, $converters, $helpers, $tags, $sub, $subSettings, $subSettingsAdvanced, $viewsSettings, delimOpenChar0, delimOpenChar1, delimCloseChar0, delimCloseChar1, linkChar, setting, baseOnError,
56
57	rPath = /^(!*?)(?:null|true|false|\d[\d.]*|([\w$]+|\.|~([\w$]+)|#(view|([\w$]+))?)([\w$.^]*?)(?:[.[^]([\w$]+)\]?)?)$/g,
58	//        not                               object     helper    view  viewProperty pathTokens      leafToken
59
60	rParams = /(\()(?=\s*\()|(?:([([])\s*)?(?:(\^?)(!*?[#~]?[\w$.^]+)?\s*((\+\+|--)|\+|-|&&|\|\||===|!==|==|!=|<=|>=|[<>%*:?\/]|(=))\s*|(!*?[#~]?[\w$.^]+)([([])?)|(,\s*)|(\(?)\\?(?:(')|("))|(?:\s*(([)\]])(?=\s*[.^]|\s*$|[^([])|[)\]])([([]?))|(\s+)/g,
61	//          lftPrn0        lftPrn        bound            path    operator err                                                eq             path2       prn    comma   lftPrn2   apos quot      rtPrn rtPrnDot                           prn2  space
62	// (left paren? followed by (path? followed by operator) or (path followed by left paren?)) or comma or apos or quot or right paren or space
63
64	isRenderCall,
65	rNewLine = /[ \t]*(\r\n|\n|\r)/g,
66	rUnescapeQuotes = /\\(['"])/g,
67	rEscapeQuotes = /['"\\]/g, // Escape quotes and \ character
68	rBuildHash = /(?:\x08|^)(onerror:)?(?:(~?)(([\w$_\.]+):)?([^\x08]+))\x08(,)?([^\x08]+)/gi,
69	rTestElseIf = /^if\s/,
70	rFirstElem = /<(\w+)[>\s]/,
71	rAttrEncode = /[\x00`><"'&=]/g, // Includes > encoding since rConvertMarkers in JsViews does not skip > characters in attribute strings
72	rIsHtml = /[\x00`><\"'&=]/,
73	rHasHandlers = /^on[A-Z]|^convert(Back)?$/,
74	rWrappedInViewMarker = /^\#\d+_`[\s\S]*\/\d+_`$/,
75	rHtmlEncode = rAttrEncode,
76	viewId = 0,
77	charEntities = {
78		"&": "&amp;",
79		"<": "&lt;",
80		">": "&gt;",
81		"\x00": "&#0;",
82		"'": "&#39;",
83		'"': "&#34;",
84		"`": "&#96;",
85		"=": "&#61;"
86	},
87	HTML = "html",
88	OBJECT = "object",
89	tmplAttr = "data-jsv-tmpl",
90	jsvTmpl = "jsvTmpl",
91	indexStr = "For #index in nested block use #getIndex().",
92	$render = {},
93
94	jsr = global.jsrender,
95	jsrToJq = jsr && $ && !$.render, // JsRender already loaded, without jQuery. but we will re-load it now to attach to jQuery
96
97	jsvStores = {
98		template: {
99			compile: compileTmpl
100		},
101		tag: {
102			compile: compileTag
103		},
104		viewModel: {
105			compile: compileViewModel
106		},
107		helper: {},
108		converter: {}
109	};
110
111	// views object ($.views if jQuery is loaded, jsrender.views if no jQuery, e.g. in Node.js)
112	$views = {
113		jsviews: versionNumber,
114		sub: {
115			// subscription, e.g. JsViews integration
116			View: View,
117			Err: JsViewsError,
118			tmplFn: tmplFn,
119			parse: parseParams,
120			extend: $extend,
121			extendCtx: extendCtx,
122			syntaxErr: syntaxError,
123			onStore: {},
124			addSetting: addSetting,
125			settings: {
126				allowCode: false
127			},
128			advSet: noop, // Update advanced settings
129			_ths: tagHandlersFromProps,
130			_tg: function() {}, // Constructor for tagDef
131			_cnvt: convertVal,
132			_tag: renderTag,
133			_er: error,
134			_err: onRenderError,
135			_html: htmlEncode,
136			_cp: retVal, // Get compiled contextual parameters (or properties) ~foo=expr. In JsRender, simply returns val.
137			_sq: function(token) {
138				if (token === "constructor") {
139					syntaxError("");
140				}
141				return token;
142			}
143		},
144		settings: {
145			delimiters: $viewsDelimiters,
146			advanced: function(value) {
147				return value
148					? (
149							$extend($subSettingsAdvanced, value),
150							$sub.advSet(),
151							$viewsSettings
152						)
153						: $subSettingsAdvanced;
154				}
155		},
156		getCtx: retVal, // Get ctx.foo value. In JsRender, simply returns val.
157		map: dataMap    // If jsObservable loaded first, use that definition of dataMap
158	};
159
160function getDerivedMethod(baseMethod, method) {
161	return function() {
162		var ret,
163			tag = this,
164			prevBase = tag.base;
165
166		tag.base = baseMethod; // Within method call, calling this.base will call the base method
167		ret = method.apply(tag, arguments); // Call the method
168		tag.base = prevBase; // Replace this.base to be the base method of the previous call, for chained calls
169		return ret;
170	};
171}
172
173function getMethod(baseMethod, method) {
174	// For derived methods (or handlers declared declaratively as in {{:foo onChange=~fooChanged}} replace by a derived method, to allow using this.base(...)
175	// or this.baseApply(arguments) to call the base implementation. (Equivalent to this._super(...) and this._superApply(arguments) in jQuery UI)
176	if ($isFunction(method)) {
177		method = getDerivedMethod(
178				!baseMethod
179					? noop // no base method implementation, so use noop as base method
180					: baseMethod._d
181						? baseMethod // baseMethod is a derived method, so us it
182						: getDerivedMethod(noop, baseMethod), // baseMethod is not derived so make its base method be the noop method
183				method
184			);
185		method._d = 1; // Add flag that this is a derived method
186	}
187	return method;
188}
189
190function tagHandlersFromProps(tag, tagCtx) {
191	for (var prop in tagCtx.props) {
192		if (rHasHandlers.test(prop)) {
193			tag[prop] = getMethod(tag[prop], tagCtx.props[prop]);
194			// Copy over the onFoo props, convert and convertBack from tagCtx.props to tag (overrides values in tagDef).
195			// Note: unsupported scenario: if handlers are dynamically added ^onFoo=expression this will work, but dynamically removing will not work.
196		}
197	}
198}
199
200function retVal(val) {
201	return val;
202}
203
204function noop() {
205	return "";
206}
207
208function dbgBreak(val) {
209	// Usage examples: {{dbg:...}}, {{:~dbg(...)}}, {{dbg .../}}, {^{for ... onAfterLink=~dbg}} etc.
210	try {
211		console.log("JsRender dbg breakpoint: " + val);
212		throw "dbg breakpoint"; // To break here, stop on caught exceptions.
213	}
214	catch (e) {}
215	return this.base ? this.baseApply(arguments) : val;
216}
217
218function JsViewsError(message) {
219	// Error exception type for JsViews/JsRender
220	// Override of $.views.sub.Error is possible
221	this.name = ($.link ? "JsViews" : "JsRender") + " Error";
222	this.message = message || this.name;
223}
224
225function $extend(target, source) {
226	for (var name in source) {
227		target[name] = source[name];
228	}
229	return target;
230}
231
232(JsViewsError.prototype = new Error()).constructor = JsViewsError;
233
234//========================== Top-level functions ==========================
235
236//===================
237// views.delimiters
238//===================
239
240function $viewsDelimiters(openChars, closeChars, link) {
241	// Set the tag opening and closing delimiters and 'link' character. Default is "{{", "}}" and "^"
242	// openChars, closeChars: opening and closing strings, each with two characters
243	if (!openChars) {
244		return $subSettings.delimiters;
245	}
246	if ($isArray(openChars)) {
247		return $viewsDelimiters.apply($views, openChars);
248	}
249
250	$subSettings.delimiters = [openChars, closeChars, linkChar = link ? link.charAt(0) : linkChar];
251
252	delimOpenChar0 = openChars.charAt(0); // Escape the characters - since they could be regex special characters
253	delimOpenChar1 = openChars.charAt(1);
254	delimCloseChar0 = closeChars.charAt(0);
255	delimCloseChar1 = closeChars.charAt(1);
256	openChars = "\\" + delimOpenChar0 + "(\\" + linkChar + ")?\\" + delimOpenChar1; // Default is "{^{"
257	closeChars = "\\" + delimCloseChar0 + "\\" + delimCloseChar1;                   // Default is "}}"
258	// Build regex with new delimiters
259	//          [tag    (followed by / space or })  or cvtr+colon or html or code] followed by space+params then convertBack?
260	rTag = "(?:(\\w+(?=[\\/\\s\\" + delimCloseChar0 + "]))|(\\w+)?(:)|(>)|(\\*))\\s*((?:[^\\"
261		+ delimCloseChar0 + "]|\\" + delimCloseChar0 + "(?!\\" + delimCloseChar1 + "))*?)";
262
263	// Make rTag available to JsViews (or other components) for parsing binding expressions
264	$sub.rTag = "(?:" + rTag + ")";
265	//                        { ^? {   tag+params slash?  or closingTag                                                   or comment
266	rTag = new RegExp("(?:" + openChars + rTag + "(\\/)?|\\" + delimOpenChar0 + "(\\" + linkChar + ")?\\" + delimOpenChar1 + "(?:(?:\\/(\\w+))\\s*|!--[\\s\\S]*?--))" + closeChars, "g");
267
268	// Default:  bind     tagName         cvt   cln html code    params            slash   bind2         closeBlk  comment
269	//      /(?:{(\^)?{(?:(\w+(?=[\/\s}]))|(\w+)?(:)|(>)|(\*))\s*((?:[^}]|}(?!}))*?)(\/)?|{(\^)?{(?:(?:\/(\w+))\s*|!--[\s\S]*?--))}}
270
271	$sub.rTmpl = new RegExp("<.*>|([^\\\\]|^)[{}]|" + openChars + ".*" + closeChars);
272	// $sub.rTmpl looks for html tags or { or } char not preceded by \\, or JsRender tags {{xxx}}. Each of these strings are considered
273	// NOT to be jQuery selectors
274	return $viewsSettings;
275}
276
277//=========
278// View.get
279//=========
280
281function getView(inner, type) { //view.get(inner, type)
282	if (!type && inner !== true) {
283		// view.get(type)
284		type = inner;
285		inner = undefined;
286	}
287
288	var views, i, l, found,
289		view = this,
290		root = !type || type === "root";
291		// If type is undefined, returns root view (view under top view).
292
293	if (inner) {
294		// Go through views - this one, and all nested ones, depth-first - and return first one with given type.
295		// If type is undefined, i.e. view.get(true), return first child view.
296		found = type && view.type === type && view;
297		if (!found) {
298			views = view.views;
299			if (view._.useKey) {
300				for (i in views) {
301					if (found = type ? views[i].get(inner, type) : views[i]) {
302						break;
303					}
304				}
305			} else {
306				for (i = 0, l = views.length; !found && i < l; i++) {
307					found = type ? views[i].get(inner, type) : views[i];
308				}
309			}
310		}
311	} else if (root) {
312		// Find root view. (view whose parent is top view)
313		while (view.parent) {
314			found = view;
315			view = view.parent;
316		}
317	} else {
318		while (view && !found) {
319			// Go through views - this one, and all parent ones - and return first one with given type.
320			found = view.type === type ? view : undefined;
321			view = view.parent;
322		}
323	}
324	return found;
325}
326
327function getNestedIndex() {
328	var view = this.get("item");
329	return view ? view.index : undefined;
330}
331
332getNestedIndex.depends = function() {
333	return [this.get("item"), "index"];
334};
335
336function getIndex() {
337	return this.index;
338}
339
340getIndex.depends = "index";
341
342//==========
343// View.hlp
344//==========
345
346function getHelper(helper, isContextCb) {
347	// Helper method called as view.hlp(key) from compiled template, for helpers or template parameters ~foo
348	var wrapped, deps,
349	view = this,
350	res = view.ctx;
351
352	if (res) {
353		res = res[helper];
354	}
355	if (res === undefined) {
356		res = $helpers[helper];
357	}
358	if (res && res._cp) { // If this helper resource is a contextual parameter, ~foo=expr
359		if (isContextCb) { // In a context callback for a contextual param, return the [currentData, dependencies...] array - needed for observe call
360			deps = $sub._ceo(res[1].deps);  // fn deps, with any exprObs cloned
361			deps.unshift(res[0].data);      // view.data
362			deps._cp = true;
363			return deps;
364		}
365		res = $views.getCtx(res); // If a contextual param, but not a context callback, return evaluated param - fn(data, view, j)
366	}
367
368	if (res) {
369		if ($isFunction(res) && !res._wrp) {
370			// If it is of type function, and not already wrapped, we will wrap it, so if called with no this pointer it will be called with the
371			// view as 'this' context. If the helper ~foo() was in a data-link expression, the view will have a 'temporary' linkCtx property too.
372			// Note that helper functions on deeper paths will have specific this pointers, from the preceding path.
373			// For example, ~util.foo() will have the ~util object as 'this' pointer
374			wrapped = function() {
375				return res.apply((!this || this === global) ? view : this, arguments);
376			};
377			wrapped._wrp = view;
378			$extend(wrapped, res); // Attach same expandos (if any) to the wrapped function
379		}
380	}
381	return wrapped || res;
382}
383
384function getTemplate(tmpl) {
385	return tmpl && (tmpl.fn
386		? tmpl
387		: this.getRsc("templates", tmpl) || $templates(tmpl)); // not yet compiled
388}
389
390//==============
391// views._cnvt
392//==============
393
394function convertVal(converter, view, tagCtx, onError) {
395	// self is template object or linkCtx object
396	var tag, value,
397		// if tagCtx is an integer, then it is the key for the compiled function to return the boundTag tagCtx
398		boundTag = typeof tagCtx === "number" && view.tmpl.bnds[tagCtx-1],
399		linkCtx = view.linkCtx; // For data-link="{cvt:...}"...
400
401	if (onError !== undefined) {
402		tagCtx = onError = {props: {}, args: [onError]};
403	} else if (boundTag) {
404		tagCtx = boundTag(view.data, view, $sub);
405	}
406
407	value = tagCtx.args[0];
408	if (converter || boundTag) {
409		tag = linkCtx && linkCtx.tag;
410		if (!tag) {
411			tag = $extend(new $sub._tg(), {
412				_: {
413					inline: !linkCtx,
414					bnd: boundTag,
415					unlinked: true
416				},
417				tagName: ":",
418				cvt: converter,
419				flow: true,
420				tagCtx: tagCtx
421			});
422			if (linkCtx) {
423				linkCtx.tag = tag;
424				tag.linkCtx = linkCtx;
425			}
426			tagCtx.ctx = extendCtx(tagCtx.ctx, (linkCtx ? linkCtx.view : view).ctx);
427		}
428		tag._er = onError && value;
429		tagHandlersFromProps(tag, tagCtx);
430
431		tagCtx.view = view;
432
433		tag.ctx = tagCtx.ctx || tag.ctx || {};
434		tagCtx.ctx = undefined;
435
436		value = tag.cvtArgs(converter !== "true" && converter)[0]; // If there is a convertBack but no convert, converter will be "true"
437
438		// Call onRender (used by JsViews if present, to add binding annotations around rendered content)
439		value = boundTag && view._.onRender
440			? view._.onRender(value, view, tag)
441			: value;
442	}
443	return value != undefined ? value : "";
444}
445
446function convertArgs(converter) {
447	var tag = this,
448		tagCtx = tag.tagCtx,
449		view = tagCtx.view,
450		args = tagCtx.args;
451
452	converter = converter || tag.convert;
453	converter = converter && ("" + converter === converter
454		? (view.getRsc("converters", converter) || error("Unknown converter: '" + converter + "'"))
455		: converter);
456
457	args = !args.length && !tagCtx.index // On the opening tag with no args, bind to the current data context
458		? [view.data]
459		: converter
460			? args.slice() // If there is a converter, use a copy of the tagCtx.args array for rendering, and replace the args[0] in
461			// the copied array with the converted value. But we do not modify the value of tag.tagCtx.args[0] (the original args array)
462			: args; // If no converter, get the original tagCtx.args
463
464	if (converter) {
465		if (converter.depends) {
466			tag.depends = $sub.getDeps(tag.depends, tag, converter.depends, converter);
467		}
468		args[0] = converter.apply(tag, args);
469	}
470	return args;
471}
472
473//=============
474// views._tag
475//=============
476
477function getResource(resourceType, itemName) {
478	var res, store,
479		view = this;
480	while ((res === undefined) && view) {
481		store = view.tmpl && view.tmpl[resourceType];
482		res = store && store[itemName];
483		view = view.parent;
484	}
485	return res || $views[resourceType][itemName];
486}
487
488function renderTag(tagName, parentView, tmpl, tagCtxs, isUpdate, onError) {
489	parentView = parentView || topView;
490	var tag, tag_, tagDef, template, tags, attr, parentTag, i, l, itemRet, tagCtx, tagCtxCtx, content, callInit, mapDef, thisMap, args, props, initialTmpl, tagDataMap,
491		ret = "",
492		linkCtx = parentView.linkCtx || 0,
493		ctx = parentView.ctx,
494		parentTmpl = tmpl || parentView.tmpl,
495		// if tagCtx is an integer, then it is the key for the compiled function to return the boundTag tagCtxs
496		boundTag = typeof tagCtxs === "number" && parentView.tmpl.bnds[tagCtxs-1];
497
498	if (tagName._is === "tag") {
499		tag = tagName;
500		tagName = tag.tagName;
501		tagCtxs = tag.tagCtxs;
502		template = tag.template;
503	} else {
504		tagDef = parentView.getRsc("tags", tagName) || error("Unknown tag: {{" + tagName + "}} ");
505		template = tagDef.template;
506	}
507
508	if (onError !== undefined) {
509		ret += onError;
510		tagCtxs = onError = [{props: {}, args: []}];
511	} else if (boundTag) {
512		tagCtxs = boundTag(parentView.data, parentView, $sub);
513	}
514
515	l = tagCtxs.length;
516	for (i = 0; i < l; i++) {
517		tagCtx = tagCtxs[i];
518		if (!linkCtx || !linkCtx.tag || i && !linkCtx.tag._.inline || tag._er) {
519			// Initialize tagCtx
520			// For block tags, tagCtx.tmpl is an integer > 0
521			if (content = parentTmpl.tmpls && tagCtx.tmpl) {
522				content = tagCtx.content = parentTmpl.tmpls[content - 1];
523			}
524			tagCtx.index = i;
525			tagCtx.tmpl = content; // Set the tmpl property to the content of the block tag
526			tagCtx.render = renderContent;
527			tagCtx.view = parentView;
528			tagCtx.ctx = extendCtx(tagCtx.ctx, ctx); // Clone and extend parentView.ctx
529		}
530		if (tmpl = tagCtx.props.tmpl) {
531			// If the tmpl property is overridden, set the value (when initializing, or, in case of binding: ^tmpl=..., when updating)
532			tagCtx.tmpl = parentView.getTmpl(tmpl);
533		}
534
535		if (!tag) {
536			// This will only be hit for initial tagCtx (not for {{else}}) - if the tag instance does not exist yet
537			// Instantiate tag if it does not yet exist
538			// If the tag has not already been instantiated, we will create a new instance.
539			// ~tag will access the tag, even within the rendering of the template content of this tag.
540			// From child/descendant tags, can access using ~tag.parent, or ~parentTags.tagName
541			tag = new tagDef._ctr();
542			callInit = !!tag.init;
543
544			tag.parent = parentTag = ctx && ctx.tag;
545			tag.tagCtxs = tagCtxs;
546			tagDataMap = tag.dataMap;
547
548			if (linkCtx) {
549				tag._.inline = false;
550				linkCtx.tag = tag;
551				tag.linkCtx = linkCtx;
552			}
553			if (tag._.bnd = boundTag || linkCtx.fn) {
554				// Bound if {^{tag...}} or data-link="{tag...}"
555				tag._.arrVws = {};
556			} else if (tag.dataBoundOnly) {
557				error("{^{" + tagName + "}} tag must be data-bound");
558			}
559			//TODO better perf for childTags() - keep child tag.tags array, (and remove child, when disposed)
560			// tag.tags = [];
561		}
562		tagCtxs = tag.tagCtxs;
563		tagDataMap = tag.dataMap;
564
565		tagCtx.tag = tag;
566		if (tagDataMap && tagCtxs) {
567			tagCtx.map = tagCtxs[i].map; // Copy over the compiled map instance from the previous tagCtxs to the refreshed ones
568		}
569		if (!tag.flow) {
570			tagCtxCtx = tagCtx.ctx = tagCtx.ctx || {};
571
572			// tags hash: tag.ctx.tags, merged with parentView.ctx.tags,
573			tags = tag.parents = tagCtxCtx.parentTags = ctx && extendCtx(tagCtxCtx.parentTags, ctx.parentTags) || {};
574			if (parentTag) {
575				tags[parentTag.tagName] = parentTag;
576				//TODO better perf for childTags: parentTag.tags.push(tag);
577			}
578			tags[tag.tagName] = tagCtxCtx.tag = tag;
579		}
580	}
581	if (!(tag._er = onError)) {
582		tagHandlersFromProps(tag, tagCtxs[0]);
583		tag.rendering = {}; // Provide object for state during render calls to tag and elses. (Used by {{if}} and {{for}}...)
584		for (i = 0; i < l; i++) {
585			tagCtx = tag.tagCtx = tagCtxs[i];
586			props = tagCtx.props;
587			args = tag.cvtArgs();
588
589			if (mapDef = props.dataMap || tagDataMap) {
590				if (args.length || props.dataMap) {
591					thisMap = tagCtx.map;
592					if (!thisMap || thisMap.src !== args[0] || isUpdate) {
593						if (thisMap && thisMap.src) {
594							thisMap.unmap();
594 // only called if observable map - not when only used in JsRender, e.g. by {{props}}
595						}
596						thisMap = tagCtx.map = mapDef.map(args[0], props, undefined, !tag._.bnd);
597					}
598					args = [thisMap.tgt];
599				}
600			}
601			tag.ctx = tagCtx.ctx;
602
603			if (!i) {
604				if (callInit) {
605					initialTmpl = tag.template;
606					tag.init(tagCtx, linkCtx, tag.ctx);
607					callInit = undefined;
608				}
609				if (linkCtx) {
610					// Set attr on linkCtx to ensure outputting to the correct target attribute.
611					// Setting either linkCtx.attr or this.attr in the init() allows per-instance choice of target attrib.
612					linkCtx.attr = tag.attr = linkCtx.attr || tag.attr;
613				}
614				attr = tag.attr;
615				tag._.noVws = attr && attr !== HTML;
616			}
617
618			itemRet = undefined;
619			if (tag.render) {
620				itemRet = tag.render.apply(tag, args);
621				if (parentView.linked && itemRet && tag.linkedElem && !rWrappedInViewMarker.test(itemRet)) {
622					// When a tag renders content from the render method, with data linking, and has a linkedElem binding, then we need to wrap with
623					// view markers, if absent, so the content is a view associated with the tag, which will correctly dispose bindings if deleted.
624					itemRet = renderWithViews($.templates(itemRet), args[0], undefined, undefined, parentView, undefined, undefined, tag);
625				}
626			}
627			if (!args.length) {
628				args = [parentView]; // no arguments - (e.g. {{else}}) get data context from view.
629			}
630			if (itemRet === undefined) {
631				itemRet = tagCtx.render(args[0], true) || (isUpdate ? undefined : "");
632			}
633			// No return value from render, and no template/content tagCtx.render(...), so return undefined
634			ret = ret ? ret + (itemRet || "") : itemRet; // If no rendered content, this will be undefined
635		}
636		tag.rendering = undefined;
637	}
638	tag.tagCtx = tagCtxs[0];
639	tag.ctx = tag.tagCtx.ctx;
640
641	if (tag._.noVws) {
642			if (tag._.inline) {
643			// inline tag with attr set to "text" will insert HTML-encoded content - as if it was element-based innerText
644			ret = attr === "text"
645				? $converters.html(ret)
646				: "";
647		}
648	}
649	return boundTag && parentView._.onRender
650		// Call onRender (used by JsViews if present, to add binding annotations around rendered content)
651		? parentView._.onRender(ret, parentView, tag)
652		: ret;
653}
654
655//=================
656// View constructor
657//=================
658
659function View(context, type, parentView, data, template, key, onRender, contentTmpl) {
660	// Constructor for view object in view hierarchy. (Augmented by JsViews if JsViews is loaded)
661	var views, parentView_, tag, self_,
662		self = this,
663		isArray = type === "array";
664
665	self.content = contentTmpl;
666	self.views = isArray ? [] : {};
667	self.parent = parentView;
668	self.type = type || "top";
669	self.data = data;
670	self.tmpl = template;
671	// If the data is an array, this is an 'array view' with a views array for each child 'item view'
672	// If the data is not an array, this is an 'item view' with a views 'hash' object for any child nested views
673	// ._.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
674	self_ = self._ = {
675		key: 0,
676		useKey: isArray ? 0 : 1,
677		id: "" + viewId++,
678		onRender: onRender,
679		bnds: {}
680	};
681	self.linked = !!onRender;
682	if (parentView) {
683		views = parentView.views;
684		parentView_ = parentView._;
685		if (parentView_.useKey) {
686			// Parent is not an 'array view'. Add this view to its views object
687			// self._key = is the key in the parent view hash
688			views[self_.key = "_" + parentView_.useKey++] = self;
689			self.index = indexStr;
690			self.getIndex = getNestedIndex;
691		} else if (views.length === (self_.key = self.index = key)) { // Parent is an 'array view'. Add this view to its views array
692			views.push(self); // Adding to end of views array. (Using push when possible - better perf than splice)
693		} else {
694			views.splice(key, 0, self); // Inserting in views array
695		}
696		// If no context was passed in, use parent context
697		// If context was passed in, it should have been merged already with parent context
698		self.ctx = context || parentView.ctx;
699	} else {
700		self.ctx = context;
701	}
702}
703
704View.prototype = {
705	get: getView,
706	getIndex: getIndex,
707	getRsc: getResource,
708	getTmpl: getTemplate,
709	hlp: getHelper,
710	_is: "view"
711};
712
713//====================================================
714// Registration
715//====================================================
716
717function compileChildResources(parentTmpl) {
718	var storeName, storeNames, resources;
719	for (storeName in jsvStores) {
720		storeNames = storeName + "s";
721		if (parentTmpl[storeNames]) {
722			resources = parentTmpl[storeNames];    // Resources not yet compiled
723			parentTmpl[storeNames] = {};               // Remove uncompiled resources
724			$views[storeNames](resources, parentTmpl); // Add back in the compiled resources
725		}
726	}
727}
728
729//===============
730// compileTag
731//===============
732
733function compileTag(name, tagDef, parentTmpl) {
734	var tmpl, baseTag, prop,
735		compiledDef = new $sub._tg();
736
737	function Tag() {
738		var tag = this;
739		tag._ = {
740			inline: true,
741			unlinked: true
742		};
743
744		tag.tagName = name;
745	}
746
747	if ($isFunction(tagDef)) {
748		// Simple tag declared as function. No presenter instantation.
749		tagDef = {
750			depends: tagDef.depends,
751			render: tagDef
752		};
753	} else if ("" + tagDef === tagDef) {
754		tagDef = {template: tagDef};
755	}
756	if (baseTag = tagDef.baseTag) {
757		tagDef.flow = !!tagDef.flow; // Set flow property, so defaults to false even if baseTag has flow=true
758		tagDef.baseTag = baseTag = "" + baseTag === baseTag
759			? (parentTmpl && parentTmpl.tags[baseTag] || $tags[baseTag])
760			: baseTag;
761
762		compiledDef = $extend(compiledDef, baseTag);
763
764		for (prop in tagDef) {
765			compiledDef[prop] = getMethod(baseTag[prop], tagDef[prop]);
766		}
767	} else {
768		compiledDef = $extend(compiledDef, tagDef);
769	}
770
771	// Tag declared as object, used as the prototype for tag instantiation (control/presenter)
772	if ((tmpl = compiledDef.template) !== undefined) {
773		compiledDef.template = "" + tmpl === tmpl ? ($templates[tmpl] || $templates(tmpl)) : tmpl;
774	}
775	if (compiledDef.init !== false) {
776		// Set init: false on tagDef if you want to provide just a render method, or render and template, but no constructor or prototype.
777		// so equivalent to setting tag to render function, except you can also provide a template.
778		(Tag.prototype = compiledDef).constructor = compiledDef._ctr = Tag;
779	}
780
781	if (parentTmpl) {
782		compiledDef._parentTmpl = parentTmpl;
783	}
784	return compiledDef;
785}
786
787function baseApply(args) {
788	// In derived method (or handler declared declaratively as in {{:foo onCh
788ange=~fooChanged}} can call base method,
789	// using this.baseApply(arguments) (Equivalent to this._superApply(arguments) in jQuery UI)
790	return this.base.apply(this, args);
791}
792
793//===============
794// compileTmpl
795//===============
796
797function compileTmpl(name, tmpl, parentTmpl, options) {
798	// tmpl is either a template object, a selector for a template script block, the name of a compiled template, or a template object
799
800	//==== nested functions ====
801	function lookupTemplate(value) {
802		// If value is of type string - treat as selector, or name of compiled template
803		// Return the template object, if already compiled, or the markup string
804		var currentName, tmpl;
805		if (("" + value === value) || value.nodeType > 0 && (elem = value)) {
806			if (!elem) {
807				if (/^\.\/[^\\:*?"<>]*$/.test(value)) {
808					// tmpl="./some/file.html"
809					// If the template is not named, use "./some/file.html" as name.
810					if (tmpl = $templates[name = name || value]) {
811						value = tmpl;
812					} else {
813						// BROWSER-SPECIFIC CODE (not on Node.js):
814						// Look for server-generated script block with id "./some/file.html"
815						elem = document.getElementById(value);
816					}
817				} else if ($.fn && !$sub.rTmpl.test(value)) {
818					try {
819						elem = $(document).find(value)[0]; // if jQuery is loaded, test for selector returning elements, and get first element
820					} catch (e) {}
821				}// END BROWSER-SPECIFIC CODE
822			} //BROWSER-SPECIFIC CODE
823			if (elem) {
824				// Generally this is a script element.
825				// However we allow it to be any element, so you can for example take the content of a div,
826				// use it as a template, and replace it by the same content rendered against data.
827				// e.g. for linking the content of a div to a container, and using the initial content as template:
828				// $.link("#content", model, {tmpl: "#content"});
829				if (options) {
830					// We will compile a new template using the markup in the script element
831					value = elem.innerHTML;
832				} else {
833					// We will cache a single copy of the compiled template, and associate it with the name
834					// (renaming from a previous name if there was one).
835					currentName = elem.getAttribute(tmplAttr);
836					if (currentName) {
837						if (currentName !== jsvTmpl) {
838							value = $templates[currentName];
839							delete $templates[currentName];
840						} else if ($.fn) {
841							value = $.data(elem)[jsvTmpl];
842						}
843					} else {
844						name = name || ($.fn ? jsvTmpl : value);
845						value = compileTmpl(name, elem.innerHTML, parentTmpl, options);
846					}
847					value.tmplName = name = name || currentName;
848					if (name !== jsvTmpl) {
849						$templates[name] = value;
850					}
851					elem.setAttribute(tmplAttr, name);
852					if ($.fn) {
853						$.data(elem, jsvTmpl, value);
854					}
855				}
856			} // END BROWSER-SPECIFIC CODE
857			elem = undefined;
858		} else if (!value.fn) {
859			value = undefined;
860			// If value is not a string. HTML element, or compiled template, return undefined
861		}
862		return value;
863	}
864
865	var elem, compiledTmpl,
866		tmplOrMarkup = tmpl = tmpl || "";
867
868	//==== Compile the template ====
869	if (options === 0) {
870		options = undefined;
871		tmplOrMarkup = lookupTemplate(tmplOrMarkup); // Top-level compile so do a template lookup
872	}
873
874	// If options, then this was already compiled from a (script) element template declaration.
875	// If not, then if tmpl is a template object, use it for options
876	options = options || (tmpl.markup ? tmpl : {});
877	options.tmplName = name;
878	if (parentTmpl) {
879		options._parentTmpl = parentTmpl;
880	}
881	// If tmpl is not a markup string or a selector string, then it must be a template object
882	// In that case, get it from the markup property of the object
883	if (!tmplOrMarkup && tmpl.markup && (tmplOrMarkup = lookupTemplate(tmpl.markup))) {
884		if (tmplOrMarkup.fn) {
885			// If the string references a compiled template object, need to recompile to merge any modified options
886			tmplOrMarkup = tmplOrMarkup.markup;
887		}
888	}
889	if (tmplOrMarkup !== undefined) {
890		if (tmplOrMarkup.fn || tmpl.fn) {
891			// tmpl is already compiled, so use it
892			if (tmplOrMarkup.fn) {
893				compiledTmpl = tmplOrMarkup;
894			}
895		} else {
896			// tmplOrMarkup is a markup string, not a compiled template
897			// Create template object
898			tmpl = tmplObject(tmplOrMarkup, options);
899			// Compile to AST and then to compiled function
900			tmplFn(tmplOrMarkup.replace(rEscapeQuotes, "\\$&"), tmpl);
901		}
902		if (!compiledTmpl) {
903			compiledTmpl = $extend(function() {
904				return compiledTmpl.render.apply(compiledTmpl, arguments);
905			}, tmpl);
906
907			compileChildResources(compiledTmpl);
908		}
909		if (name && !parentTmpl && name !== jsvTmpl) {
910			$render[name] = compiledTmpl;
911		}
912		return compiledTmpl;
913	}
914}
915
916//==== /end of function compileTmpl ====
917
918//=================
919// compileViewModel
920//=================
921
922function getDefaultVal(defaultVal, data) {
923	return $.isFunction(defaultVal)
924		? defaultVal.call(data)
925		: defaultVal;
926}
927
928function unmapArray(modelArr) {
929		var i, arr = [],
930			l = modelArr.length;
931		for (i=0; i<l; i++) {
932			arr.push(modelArr[i].unmap());
933		}
934		return arr;
935}
936
937function compileViewModel(name, type) {
938	var i, constructor,
939		viewModels = this,
940		getters = type.getters,
941		extend = type.extend,
942		id = type.id,
943		proto = $.extend({
944			_is: name || "unnamed",
945			unmap: unmap,
946			merge: merge
947		}, extend),
948		args = "",
949		body = "",
950		l = getters ? getters.length : 0,
951		$observable = $.observable,
952		getterNames = {};
953
954	function GetNew(args) {
955		constructor.apply(this, args);
956	}
957
958	function vm() {
959		return new GetNew(arguments);
960	}
961
962	function iterate(data, action) {
963		var j, getterType, defaultVal, prop, ob,
964			m = getters.length;
965		for (j=0; j<m; j++) {
966			prop = getters[j];
967			getterType = undefined;
968			if (prop + "" !== prop) {
969				getterType = prop;
970				prop = getterType.getter;
971			}
972			if ((ob = data[prop]) === undefined && getterType && (defaultVal = getterType.defaultVal) !== undefined) {
973				ob = getDefaultVal(defaultVal, data);
974			}
975			action(ob, getterType && viewModels[getterType.type], prop);
976		}
977	}
978
979	function map(data) {
980		data = data + "" === data
981			? JSON.parse(data) // Accept JSON string
982			: data;            // or object/array
983		var i, j,  l, m, prop,
984			ob = data,
985			arr = [];
986
987		if ($isArray(data)) {
988			data = data || [];
989			l = data.length;
990			for (i=0; i<l; i++) {
991				arr.push(this.map(data[i]));
992			}
993			arr._is = name;
994			arr.unmap = unmap;
995			arr.merge = merge;
996			return arr;
997		}
998
999		if (data) {
1000			iterate(data, function(ob, viewModel) {
1001				if (viewModel) { // Iterate to build getters arg array (value, or mapped value)
1002					ob = viewModel.map(ob);
1003				}
1004				arr.push(ob);
1005			});
1006
1007			ob = this.apply(this, arr); // Insantiate this View Model, passing getters args array to constructor
1008			for (prop in data) { // Copy over any other properties. that are not get/set properties
1009				if (prop !== $expando  && !getterNames[prop]) {
1010					ob[prop] = data[prop];
1011				}
1012			}
1013		}
1014		return ob;
1015	}
1016
1017	function merge(data) {
1018		data = data + "" === data
1019			? JSON.parse(data) // Accept JSON string
1020			: data;            // or object/array
1021		var i, j, l, m, prop, mod, found, assigned, ob, newModArr,
1022			model = this;
1023
1024		if ($isArray(model)) {
1025			assigned = {};
1026			newModArr = [];
1027			l = data.length;
1028			m = model.length;
1029			for (i=0; i<l; i++) {
1030				ob = data[i];
1031				found = false;
1032				for (j=0; j<m && !found; j++) {
1033					if (assigned[j]) {
1034						continue;
1035					}
1036					mod = model[j];
1037
1038					if (id) {
1039						assigned[j] = found = id + "" === id
1040						? (ob[id] && (getterNames[id] ? mod[id]() : mod[id]) === ob[id])
1041						: id(mod, ob);
1042					}
1043				}
1044				if (found) {
1045					mod.merge(ob);
1046					newModArr.push(mod);
1047				} else {
1048					newModArr.push(vm.map(ob));
1049				}
1050			}
1051			if ($observable) {
1052				$observable(model).refresh(newModArr, true);
1053			} else {
1054				model.splice.apply(model, [0, model.length].concat(newModArr));
1055			}
1056			return;
1057		}
1058		iterate(data, function(ob, viewModel, getter) {
1059			if (viewModel) {
1060				model[getter]().merge(ob); // Update typed property
1061			} else {
1062				model[getter](ob); // Update non-typed property
1063			}
1064		});
1065		for (prop in data) {
1066			if (prop !== $expando && !getterNames[prop]) {
1067				model[prop] = data[prop];
1068			}
1069		}
1070	}
1071
1072	function unmap() {
1073		var ob, prop, i, l, getterType, arr, value,
1074			model = this;
1075
1076		if ($isArray(model)) {
1077			return unmapArray(model);
1078		}
1079		ob = {};
1080		l = getters.length;
1081		for (i=0; i<l; i++) {
1082			prop = getters[i];
1083			getterType = undefined;
1084			if (prop + "" !== prop) {
1085				getterType = prop;
1086				prop = getterType.getter;
1087			}
1088			value = model[prop]();
1089			ob[prop] = getterType && value && viewModels[getterType.type]
1090				? $isArray(value)
1091					? unmapArray(value)
1092					: value.unmap()
1093				: value;
1094		}
1095		for (prop in model) {
1096			if (prop !== "_is" && !getterNames[prop] && prop !== $expando  && (prop.charAt(0) !== "_" || !getterNames[prop.slice(1)]) && !$.isFunction(model[prop])) {
1097				ob[prop] = model[prop];
1098			}
1099		}
1100		return ob;
1101	}
1102
1103	GetNew.prototype = proto;
1104
1105	for (i=0; i<l; i++) {
1106		(function(getter) {
1107			getter = getter.getter || getter;
1108			getterNames[getter] = i+1;
1109			var privField = "_" + getter;
1110
1111			args += (args ? "," : "") + getter;
1112			body += "this." + privField + " = " + getter + ";\n";
1113			proto[getter] = proto[getter] || function(val) {
1114				if (!arguments.length) {
1115					return this[privField]; // If there is no argument, use as a getter
1116				}
1117				if ($observable) {
1118					$observable(this).setProperty(getter, val);
1119				} else {
1120					this[privField] = val;
1121				}
1122			};
1123
1124			if ($observable) {
1125				proto[getter].set = proto[getter].set || function(val) {
1126					this[privField] = val; // Setter called by observable property change
1127				};
1128			}
1129		})(getters[i]);
1130	}
1131
1132	constructor = new Function(args, body.slice(0, -1));
1133	constructor.prototype = proto;
1134	proto.constructor = constructor;
1135
1136	vm.map = map;
1137	vm.getters = getters;
1138	vm.extend = extend;
1139	vm.id = id;
1140	return vm;
1141}
1142
1143function tmplObject(markup, options) {
1144	// Template object constructor
1145	var htmlTag,
1146		wrapMap = $subSettingsAdvanced._wm || {}, // Only used in JsViews. Otherwise empty: {}
1147		tmpl = $extend(
1148			{
1149				tmpls: [],
1150				links: {}, // Compiled functions for link expressions
1151				bnds: [],
1152				_is: "template",
1153				render: renderContent
1154			},
1155			options
1156		);
1157
1158	tmpl.markup = markup;
1159	if (!options.htmlTag) {
1160		// Set tmpl.tag to the top-level HTML tag used in the template, if any...
1161		htmlTag = rFirstElem.exec(markup);
1162		tmpl.htmlTag = htmlTag ? htmlTag[1].toLowerCase() : "";
1163	}
1164	htmlTag = wrapMap[tmpl.htmlTag];
1165	if (htmlTag && htmlTag !== wrapMap.div) {
1166		// When using JsViews, we trim templates which are inserted into HTML contexts where text nodes are not rendered (i.e. not 'Phrasing Content').
1167		// Currently not trimmed for <li> tag. (Not worth adding perf cost)
1168		tmpl.markup = $.trim(tmpl.markup);
1169	}
1170
1171	return tmpl;
1172}
1173
1174//==============
1175// registerStore
1176//==============
1177
1178function registerStore(storeName, storeSettings) {
1179
1180	function theStore(name, item, parentTmpl) {
1181		// The store is also the function used to add items to the store. e.g. $.templates, or $.views.tags
1182
1183		// For store of name 'thing', Call as:
1184		//    $.views.things(items[, parentTmpl]),
1185		// or $.views.things(name, item[, parentTmpl])
1186
1187		var onStore, compile, itemName, thisStore;
1188		if (name && typeof name === OBJECT && !name.nodeType && !name.markup && !name.getTgt && !(storeName === "viewModel" && name.getters || name.extend)) {
1189			// Call to $.views.things(items[, parentTmpl]),
1190
1191			// Adding items to the store
1192			// If name is a hash, then item is parentTmpl. Iterate over hash and call store for key.
1193			for (itemName in name) {
1194				theStore(itemName, name[itemName], item);
1195			}
1196			return item || $views;
1197		}
1198		// Adding a single unnamed item to the store
1199		if (item === undefined) {
1200			item = name;
1201			name = undefined;
1202		}
1203		if (name && "" + name !== name) { // name must be a string
1204			parentTmpl = item;
1205			item = name;
1206			name = undefined;
1207		}
1208		thisStore = parentTmpl
1209			? storeName === "viewModel"
1210				? parentTmpl
1211				: (parentTmpl[storeNames] = parentTmpl[storeNames] || {})
1212			: theStore;
1213		compile = storeSettings.compile;
1214		if (item === null) {
1215			// If item is null, delete this entry
1216			if (name) {
1217				delete thisStore[name];
1218			}
1219		} else {
1220			item = compile ? compile.call(thisStore, name, item, parentTmpl, 0) : item;
1221			if (name) {
1222				thisStore[name] = item;
1223			}
1224		}
1225		if (compile && item) {
1226			item._is = storeName;
vendor: 5,508 bytes, lines 1226-1391
1226 // Only do this for compiled objects (tags, templates...)
1227		}
1228		if (item && (onStore = $sub.onStore[storeName])) {
1229			// e.g. JsViews integration
1230			onStore(name, item, compile);
1231		}
1232		return item;
1233	}
1234
1235	var storeNames = storeName + "s";
1236
1237	$views[storeNames] = theStore;
1238}
1239
1240function addSetting(st) {
1241	$viewsSettings[st] = function(value) {
1242		return arguments.length
1243			? ($subSettings[st] = value, $viewsSettings)
1244			: $subSettings[st];
1245	};
1246}
1247
1248//=========
1249// dataMap
1250//=========
1251
1252function dataMap(mapDef) {
1253	function Map(source, options) {
1254		this.tgt = mapDef.getTgt(source, options);
1255	}
1256
1257	if ($isFunction(mapDef)) {
1258		// Simple map declared as function
1259		mapDef = {
1260			getTgt: mapDef
1261		};
1262	}
1263
1264	if (mapDef.baseMap) {
1265		mapDef = $extend($extend({}, mapDef.baseMap), mapDef);
1266	}
1267
1268	mapDef.map = function(source, options) {
1269		return new Map(source, options);
1270	};
1271	return mapDef;
1272}
1273
1274//==============
1275// renderContent
1276//==============
1277
1278function renderContent(data, context, noIteration, parentView, key, onRender) {
1279	var i, l, tag, tmpl, tagCtx, isTopRenderCall, prevData, prevIndex,
1280		view = parentView,
1281		result = "";
1282
1283	if (context === true) {
1284		noIteration = context; // passing boolean as second param - noIteration
1285		context = undefined;
1286	} else if (typeof context !== OBJECT) {
1287		context = undefined; // context must be a boolean (noIteration) or a plain object
1288	}
1289
1290	if (tag = this.tag) {
1291		// This is a call from renderTag or tagCtx.render(...)
1292		tagCtx = this;
1293		view = view || tagCtx.view;
1294		tmpl = view.getTmpl(tag.template || tagCtx.tmpl);
1295		if (!arguments.length) {
1296			data = view;
1297		}
1298	} else {
1299		// This is a template.render(...) call
1300		tmpl = this;
1301	}
1302
1303	if (tmpl) {
1304		if (!view && data && data._is === "view") {
1305			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
1306		}
1307
1308		if (view) {
1309			if (data === view) {
1310				// Inherit the data from the parent view.
1311				// This may be the contents of an {{if}} block
1312				data = view.data;
1313			}
1314		}
1315
1316		isTopRenderCall = !view;
1317		isRenderCall = isRenderCall || isTopRenderCall;
1318		if (!view) {
1319			(context = context || {}).root = data; // Provide ~root as shortcut to top-level data.
1320		}
1321		if (!isRenderCall || $subSettingsAdvanced.useViews || tmpl.useViews || view && view !== topView) {
1322			result = renderWithViews(tmpl, data, context, noIteration, view, key, onRender, tag);
1323		} else {
1324			if (view) { // In a block
1325				prevData = view.data;
1326				prevIndex = view.index;
1327				view.index = indexStr;
1328			} else {
1329				view = topView;
1330				view.data = data;
1331				view.ctx = context;
1332			}
1333			if ($isArray(data) && !noIteration) {
1334				// Create a view for the array, whose child views correspond to each data item. (Note: if key and parentView are passed in
1335				// along with parent view, treat as insert -e.g. from view.addViews - so parentView is already the view item for array)
1336				for (i = 0, l = data.length; i < l; i++) {
1337					view.index = i;
1338					view.data = data[i];
1339					result += tmpl.fn(data[i], view, $sub);
1340				}
1341			} else {
1342				view.data = data;
1343				result += tmpl.fn(data, view, $sub);
1344			}
1345			view.data = prevData;
1346			view.index = prevIndex;
1347		}
1348		if (isTopRenderCall) {
1349			isRenderCall = undefined;
1350		}
1351	}
1352	return result;
1353}
1354
1355function renderWithViews(tmpl, data, context, noIteration, view, key, onRender, tag) {
1356	function setItemVar(item) {
1357		// When itemVar is specified, set modified ctx with user-named ~item
1358		newCtx = $extend({}, context);
1359		newCtx[itemVar] = item;
1360	}
1361
1362	// Render template against data as a tree of subviews (nested rendered template instances), or as a string (top-level template).
1363	// If the data is the parent view, treat as noIteration, re-render with the same data context.
1364	var i, l, newView, childView, itemResult, swapContent, contentTmpl, outerOnRender, tmplName, itemVar, newCtx, tagCtx,
1365		result = "";
1366
1367	if (tag) {
1368		// This is a call from renderTag or tagCtx.render(...)
1369		tmplName = tag.tagName;
1370		tagCtx = tag.tagCtx;
1371		context = context ? extendCtx(context, tag.ctx) : tag.ctx;
1372
1373		if (tmpl === view.content) { // {{xxx tmpl=#content}}
1374			contentTmpl = tmpl !== view.ctx._wrp // We are rendering the #content
1375				? 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
1376				: undefined; // #content was the view.ctx._wrp block content - so within this view, there is no longer any #content to wrap.
1377		} else if (tmpl !== tagCtx.content) {
1378			if (tmpl === tag.template) { // Rendering {{tag}} tag.template, replacing block content.
1379				contentTmpl = tagCtx.tmpl; // Set #content to block content (or wrapped block content if tagCtx.props.tmpl is set)
1380				context._wrp = tagCtx.content; // Pass wrapped block content to nested views
1381			} else { // Rendering tagCtx.props.tmpl wrapper
1382				contentTmpl = tagCtx.content || view.content; // Set #content to wrapped block content
1383			}
1384		} else {
1385			contentTmpl = view.content; // Nested views inherit same wrapped #content property
1386		}
1387
1388		if (tagCtx.props.link === false) {
1389			// link=false setting on block tag
1390			// We will override inherited value of link by the explicit setting link=false taken from props
1391			// The child views of an unlinked view are also unlinked. So setting child back to true will not have any effect.
1392			context = context || {};
1393			context.link = false;
1394		}
1395
1396		if (itemVar = tagCtx.props.itemVar) {
1397			if (itemVar.charAt(0) !== "~") {
1398				syntaxError("Use itemVar='~myItem'");
1399			}
1400			itemVar = itemVar.slice(1);
1401		}
1402	}
1403
1404	if (view) {
1405		onRender = onRender || view._.onRender;
1406		context = extendCtx(context, view.ctx);
1407	}
1408
1409	if (key === true) {
1410		swapContent = true;
1411		key = 0;
1412	}
1413
1414	// If link===false, do not call onRender, so no data-linking marker nodes
1415	if (onRender && (context && context.link === false || tag && tag._.noVws)) {
1416		onRender = undefined;
1417	}
1418	outerOnRender = onRender;
1419	if (onRender === true) {
1420		// Used by view.refresh(). Don't create a new wrapper view.
1421		outerOnRender = undefined;
1422		onRender = view._.onRender;
1423	}
1424	// Set additional context on views created here, (as modified context inherited from the parent, and to be inherited by child views)
1425	context = tmpl.helpers
1426		? extendCtx(tmpl.helpers, context)
1427		: context;
1428
1429	newCtx = context;
1430	if ($isArray(data) && !noIteration) {
1431		// Create a view for the array, whose child views correspond to each data item. (Note: if key and view are passed in
1432		// along with parent view, treat as insert -e.g. from view.addViews - so view is already the view item for array)
1433		newView = swapContent
1434			? view
1435			: (key !== undefined && view)
1436				|| new View(context, "array", view, data, tmpl, key, onRender);
1437		if (view && view._.useKey) {
1438			// Parent is not an 'array view'
1439			newView._.bnd = !tag || tag._.bnd && tag; // For array views that are data bound for collection change events, set the
1440			// 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 ...}}
1441		}
1442		if (itemVar) {
1443			newView.it = itemVar;
1444		}
1445		itemVar = newView.it;
1446		for (i = 0, l = data.length; i < l; i++) {
1447			// Create a view for each data item.
1448			if (itemVar) {
1449				setItemVar(data[i]); // use modified ctx with user-named ~item
1450			}
1451			childView = new View(newCtx, "item", newView, data[i], tmpl, (key || 0) + i, onRender, contentTmpl);
1452
1453			itemResult = tmpl.fn(data[i], childView, $sub);
1454			result += newView._.onRender ? newView._.onRender(itemResult, childView) : itemResult;
1455		}
1456	} else {
1457		// Create a view for singleton data object. The type of the view will be the tag name, e.g. "if" or "myTag" except for
1458		// "item", "array" and "data" views. A "data" view is from programmatic render(object) against a 'singleton'.
1459		if (itemVar) {
1460			setItemVar(data);
1461		}
1462		newView = swapContent ? view : new View(newCtx, tmplName || "data", view, data, tmpl, key, onRender, contentTmpl);
1463		if (tag && !tag.flow) {
1464			newView.tag = tag;
1465		}
1466		result += tmpl.fn(data, newView, $sub);
1467	}
1468	return outerOnRender ? outerOnRender(result, newView) : result;
1469}
1470
1471//===========================
1472// Build and compile template
1473//===========================
1474
1475// Generate a reusable function that will serve to render a template against data
1476// (Compile AST then build template function)
1477
1478function onRenderError(e, view, fallback) {
1479	var message = fallback !== undefined
1480		? $isFunction(fallback)
1481			? fallback.call(view.data, e, view)
1482			: fallback || ""
1483		: "{Error: " + e.message + "}";
1484
1485	if ($subSettings.onError && (fallback = $subSettings.onError.call(view.data, e, fallback && message, view)) !== undefined) {
1486		message = fallback; // There is a settings.debugMode(handler) onError override. Call it, and use return value (if any) to replace message
1487	}
1488
1489	return view && !view.linkCtx ? $converters.html(message) : message;
1490}
1491
1492function error(message) {
1493	throw new $sub.Err(message);
1494}
1495
1496function syntaxError(message) {
1497	error("Syntax error\n" + message);
1498}
1499
1500function tmplFn(markup, tmpl, isLinkExpr, convertBack, hasElse) {
1501	// Compile markup to AST (abtract syntax tree) then build the template function code from the AST nodes
1502	// Used for compiling templates, and also by JsViews to build functions for data link expressions
1503
1504	//==== nested functions ====
1505	function pushprecedingContent(shift) {
1506		shift -= loc;
1507		if (shift) {
1508			content.push(markup.substr(loc, shift).replace(rNewLine, "\\n"));
1509		}
1510	}
1511
1512	function blockTagCheck(tagName, block) {
1513		if (tagName) {
1514			tagName += '}}';
1515			//			'{{include}} block has {{/for}} with no open {{for}}'
1516			syntaxError((
1517				block
1518					? '{{' + block + '}} block has {{/' + tagName + ' without {{' + tagName
vendor: 7,344 bytes, lines 1519-1744
1519					: 'Unmatched or missing {{/' + tagName) + ', in template:\n' + markup);
1520		}
1521	}
1522
1523	function parseTag(all, bind, tagName, converter, colon, html, codeTag, params, slash, bind2, closeBlock, index) {
1524/*
1525
1526     bind     tagName         cvt   cln html code    params            slash   bind2         closeBlk  comment
1527/(?:{(\^)?{(?:(\w+(?=[\/\s}]))|(\w+)?(:)|(>)|(\*))\s*((?:[^}]|}(?!}))*?)(\/)?|{(\^)?{(?:(?:\/(\w+))\s*|!--[\s\S]*?--))}}/g
1528
1529(?:
1530  {(\^)?{            bind
1531  (?:
1532    (\w+             tagName
1533      (?=[\/\s}])
1534    )
1535    |
1536    (\w+)?(:)        converter colon
1537    |
1538    (>)              html
1539    |
1540    (\*)             codeTag
1541  )
1542  \s*
1543  (                  params
1544    (?:[^}]|}(?!}))*?
1545  )
1546  (\/)?              slash
1547  |
1548  {(\^)?{            bind2
1549  (?:
1550    (?:\/(\w+))\s*   closeBlock
1551    |
1552    !--[\s\S]*?--    comment
1553  )
1554)
1555}}/g
1556
1557*/
1558		if (codeTag && bind || slash && !tagName || params && params.slice(-1) === ":" || bind2) {
1559			syntaxError(all);
1560		}
1561
1562		// Build abstract syntax tree (AST): [tagName, converter, params, content, hash, bindings, contentMarkup]
1563		if (html) {
1564			colon = ":";
1565			converter = HTML;
1566		}
1567		slash = slash || isLinkExpr && !hasElse;
1568
1569		var pathBindings = (bind || isLinkExpr) && [[]],
1570			props = "",
1571			args = "",
1572			ctxProps = "",
1573			paramsArgs = "",
1574			paramsProps = "",
1575			paramsCtxProps = "",
1576			onError = "",
1577			useTrigger = "",
1578			// Block tag if not self-closing and not {{:}} or {{>}} (special case) and not a data-link expression
1579			block = !slash && !colon;
1580
1581		//==== nested helper function ====
1582		tagName = tagName || (params = params || "#data", colon); // {{:}} is equivalent to {{:#data}}
1583		pushprecedingContent(index);
1584		loc = index + all.length; // location marker - parsed up to here
1585		if (codeTag) {
1586			if (allowCode) {
1587				content.push(["*", "\n" + params.replace(/^:/, "ret+= ").replace(rUnescapeQuotes, "$1") + ";\n"]);
1588			}
1589		} else if (tagName) {
1590			if (tagName === "else") {
1591				if (rTestElseIf.test(params)) {
1592					syntaxError('for "{{else if expr}}" use "{{else expr}}"');
1593				}
1594				pathBindings = current[7] && [[]];
1595				current[8] = markup.substring(current[8], index); // contentMarkup for block tag
1596				current = stack.pop();
1597				content = current[2];
1598				block = true;
1599			}
1600			if (params) {
1601				// remove newlines from the params string, to avoid compiled code errors for unterminated strings
1602				parseParams(params.replace(rNewLine, " "), pathBindings, tmpl)
1603					.replace(rBuildHash, function(all, onerror, isCtx, key, keyToken, keyValue, arg, param) {
1604						key = "'" + keyToken + "':";
1605						if (arg) {
1606							args += keyValue + ",";
1607							paramsArgs += "'" + param + "',";
1608						} else if (isCtx) {
1609							ctxProps += key + 'j._cp(' + keyValue + ',"' + param + '",view),';
1610							// Compiled code for evaluating tagCtx on a tag will have: ctx:{'foo':j._cp(compiledExpr, "expr", view)}
1611							paramsCtxProps += key + "'" + param + "',";
1612						} else if (onerror) {
1613							onError += keyValue;
1614						} else {
1615							if (keyToken === "trigger") {
1616								useTrigger += keyValue;
1617							}
1618							props += key + keyValue + ",";
1619							paramsProps += key + "'" + param + "',";
1620							hasHandlers = hasHandlers || rHasHandlers.test(keyToken);
1621						}
1622						return "";
1623					}).slice(0, -1);
1624			}
1625
1626			if (pathBindings && pathBindings[0]) {
1627				pathBindings.pop(); // Remove the bindings that was prepared for next arg. (There is always an extra one ready).
1628			}
1629
1630			newNode = [
1631					tagName,
1632					converter || !!convertBack || hasHandlers || "",
1633					block && [],
1634					parsedParam(paramsArgs || (tagName === ":" ? "'#data'," : ""), paramsProps, paramsCtxProps), // {{:}} equivalent to {{:#data}}
1635					parsedParam(args || (tagName === ":" ? "data," : ""), props, ctxProps),
1636					onError,
1637					useTrigger,
1638					pathBindings || 0
1639				];
1640			content.push(newNode);
1641			if (block) {
1642				stack.push(current);
1643				current = newNode;
1644				current[8] = loc; // Store current location of open tag, to be able to add contentMarkup when we reach closing tag
1645			}
1646		} else if (closeBlock) {
1647			blockTagCheck(closeBlock !== current[0] && current[0] !== "else" && closeBlock, current[0]);
1648			current[8] = markup.substring(current[8], index); // contentMarkup for block tag
1649			current = stack.pop();
1650		}
1651		blockTagCheck(!current && closeBlock);
1652		content = current[2];
1653	}
1654	//==== /end of nested functions ====
1655
1656	var i, result, newNode, hasHandlers, bindings,
1657		allowCode = $subSettings.allowCode || tmpl && tmpl.allowCode
1658			|| $viewsSettings.allowCode === true, // include direct setting of settings.allowCode true for backward compat only
1659		astTop = [],
1660		loc = 0,
1661		stack = [],
1662		content = astTop,
1663		current = [,,astTop];
1664
1665	if (allowCode && tmpl._is) {
1666		tmpl.allowCode = allowCode;
1667	}
1668
1669//TODO	result = tmplFnsCache[markup]; // Only cache if template is not named and markup length < ...,
1670//and there are no bindings or subtemplates?? Consider standard optimization for data-link="a.b.c"
1671//		if (result) {
1672//			tmpl.fn = result;
1673//		} else {
1674
1675//		result = markup;
1676	if (isLinkExpr) {
1677		if (convertBack !== undefined) {
1678			markup = markup.slice(0, -convertBack.length - 2) + delimCloseChar0;
1679		}
1680		markup = delimOpenChar0 + markup + delimCloseChar1;
1681	}
1682
1683	blockTagCheck(stack[0] && stack[0][2].pop()[0]);
1684	// Build the AST (abstract syntax tree) under astTop
1685	markup.replace(rTag, parseTag);
1686
1687	pushprecedingContent(markup.length);
1688
1689	if (loc = astTop[astTop.length - 1]) {
1690		blockTagCheck("" + loc !== loc && (+loc[8] === loc[8]) && loc[0]);
1691	}
1692//			result = tmplFnsCache[markup] = buildCode(astTop, tmpl);
1693//		}
1694
1695	if (isLinkExpr) {
1696		result = buildCode(astTop, markup, isLinkExpr);
1697		bindings = [];
1698		i = astTop.length;
1699		while (i--) {
1700			bindings.unshift(astTop[i][7]);  // With data-link expressions, pathBindings array for tagCtx[i] is astTop[i][7]
1701		}
1702		setPaths(result, bindings);
1703	} else {
1704		result = buildCode(astTop, tmpl);
1705	}
1706	return result;
1707}
1708
1709function setPaths(fn, pathsArr) {
1710	var key, paths,
1711		i = 0,
1712		l = pathsArr.length;
1713	fn.deps = [];
1714	for (; i < l; i++) {
1715		paths = pathsArr[i];
1716		for (key in paths) {
1717			if (key !== "_jsvto" && paths.hasOwnProperty(key) && paths[key].length) {
1718				fn.deps = fn.deps.concat(paths[key]); // deps is the concatenation of the paths arrays for the different bindings
1719			}
1720		}
1721	}
1722	fn.paths = paths; // The array of paths arrays for the different bindings
1723}
1724
1725function parsedParam(args, props, ctx) {
1726	return [args.slice(0, -1), props.slice(0, -1), ctx.slice(0, -1)];
1727}
1728
1729function paramStructure(parts, type) {
1730	return '\n\t'
1731		+ (type
1732			? type + ':{'
1733			: '')
1734		+ 'args:[' + parts[0] + ']'
1735		+ (parts[1] || !type
1736			? ',\n\tprops:{' + parts[1] + '}'
1737			: "")
1738		+ (parts[2] ? ',\n\tctx:{' + parts[2] + '}' : "");
1739}
1740
1741function parseParams(params, pathBindings, tmpl) {
1742
1743	function parseTokens(all, lftPrn0, lftPrn, bound, path, operator, err, eq, path2, prn, comma, lftPrn2, apos, quot, rtPrn, rtPrnDot, prn2, space, index, full) {
1744	// /(\()(?=\s*\()|(?:([([])\s*)?(?:(\^?)(!*?[#~]?[\w$.^]+)?\s*((\+\+|--)|\+|-|&&|\|\||===|!==|==|!=|<=|>=|[<>%*:?\/]|(=))\s*|(!*?[#~]?[\w$.^]+)([([])?)|(,\s*)|(\(?)\\?(?:(')|("))|(?:\s*(([)\]])(?
1744=\s*[.^]|\s*$|[^([])|[)\]])([([]?))|(\s+)/g,
1745	//   lftPrn0        lftPrn        bound            path    operator err                                                eq             path2       prn    comma   lftPrn2   apos quot      rtPrn rtPrnDot                        prn2  space
1746		// (left paren? followed by (path? followed by operator) or (path followed by paren?)) or comma or apos or quot or right paren or space
1747		function parsePath(allPath, not, object, helper, view, viewProperty, pathTokens, leafToken) {
1748			//rPath = /^(!*?)(?:null|true|false|\d[\d.]*|([\w$]+|\.|~([\w$]+)|#(view|([\w$]+))?)([\w$.^]*?)(?:[.[^]([\w$]+)\]?)?)$/g,
1749			//          not                               object     helper    view  viewProperty pathTokens      leafToken
1750			var subPath = object === ".";
1751			if (object) {
1752				path = path.slice(not.length);
1753				if (/^\.?constructor$/.test(leafToken||path)) {
1754					syntaxError(allPath);
1755				}
1756				if (!subPath) {
1757					allPath = (helper
1758							? 'view.hlp("' + helper + '")'
1759							: view
1760								? "view"
1761								: "data")
1762						+ (leafToken
1763							? (viewProperty
1764								? "." + viewProperty
1765								: helper
1766									? ""
1767									: (view ? "" : "." + object)
1768								) + (pathTokens || "")
1769							: (leafToken = helper ? "" : view ? viewProperty || "" : object, ""));
1770
1771					allPath = allPath + (leafToken ? "." + leafToken : "");
1772
1773					allPath = not + (allPath.slice(0, 9) === "view.data"
1774						? allPath.slice(5) // convert #view.data... to data...
1775						: allPath);
1776				}
1777				if (bindings) {
1778					binds = named === "linkTo" ? (bindto = pathBindings._jsvto = pathBindings._jsvto || []) : bndCtx.bd;
1779					if (theOb = subPath && binds[binds.length-1]) {
1780						if (theOb._jsv) {
1781							while (theOb.sb) {
1782								theOb = theOb.sb;
1783							}
1784							if (theOb.bnd) {
1785								path = "^" + path.slice(1);
1786							}
1787							theOb.sb = path;
1788							theOb.bnd = theOb.bnd || path.charAt(0) === "^";
1789						}
1790					} else {
1791						binds.push(path);
1792					}
1793					pathStart[parenDepth] = index + (subPath ? 1 : 0);
1794				}
1795			}
1796			return allPath;
1797		}
1798
1799		//bound = bindings && bound;
1800		if (bound && !eq) {
1801			path = bound + path; // e.g. some.fn(...)^some.path - so here path is "^some.path"
1802		}
1803		operator = operator || "";
1804		lftPrn = lftPrn || lftPrn0 || lftPrn2;
1805		path = path || path2;
1806		// Could do this - but not worth perf cost?? :-
1807		// if (!path.lastIndexOf("#data.", 0)) { path = path.slice(6); } // If path starts with "#data.", remove that.
1808		prn = prn || prn2 || "";
1809
1810		var expr, exprFn, binds, theOb, newOb,
1811			rtSq = ")";
1812
1813		if (prn === "[") {
1814			prn  ="[j._sq(";
1815			rtSq = ")]";
1816		}
1817
1818		if (err && !aposed && !quoted) {
1819			syntaxError(params);
1820		} else {
1821			if (bindings && rtPrnDot && !aposed && !quoted) {
1822				// 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
1823				// 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)
1824				if (!named || boundName || bindto) {
1825					expr = pathStart[parenDepth - 1];
1826					if (full.length - 1 > index - (expr || 0)) { // We need to compile a subexpression
1827						expr = full.slice(expr, index + all.length);
1828						if (exprFn !== true) { // If not reentrant call during compilation
1829							binds = bindto || bndStack[parenDepth-1].bd;
1830							// Insert exprOb object, to be used during binding to return the computed object
1831							theOb = binds[binds.length-1];
1832							if (theOb && theOb.prm) {
1833								while (theOb.sb && theOb.sb.prm) {
1834									theOb = theOb.sb;
1835								}
1836								newOb = theOb.sb = {path: theOb.sb, bnd: theOb.bnd};
1837							} else {
1838								binds.push(newOb = {path: binds.pop()}); // Insert exprOb object, to be used during binding to return the computed object
1839							}											 // (e.g. "some.object()" in "some.object().a.b" - to be used as context for binding the following tokens "a.b")
1840						}
1841						rtPrnDot = delimOpenChar1 + ":" + expr // The parameter or function subexpression
1842							+ " onerror=''" // set onerror='' in order to wrap generated code with a try catch - returning '' as object instance if there is an error/missing parent
1843							+ delimCloseChar0;
1844						exprFn = tmplLinks[rtPrnDot];
1845						if (!exprFn) {
1846							tmplLinks[rtPrnDot] = true; // Flag that this exprFn (for rtPrnDot) is being compiled
1847							tmplLinks[rtPrnDot] = exprFn = tmplFn(rtPrnDot, tmpl, true); // Compile the expression (or use cached copy already in tmpl.links)
1848						}
1849						if (exprFn !== true && newOb) {
1850							// If not reentrant call during compilation
1851							newOb._jsv = exprFn;
1852							newOb.prm = bndCtx.bd;
1853							newOb.bnd = newOb.bnd || newOb.path && newOb.path.indexOf("^") >= 0;
1854						}
1855					}
1856				}
1857			}
1858			return (aposed
1859				// within single-quoted string
1860				? (aposed = !apos, (aposed ? all : lftPrn2 + '"'))
1861				: quoted
1862				// within double-quoted string
1863					? (quoted = !quot, (quoted ? all : lftPrn2 + '"'))
1864					:
1865				(
1866					(lftPrn
1867						? (pathStart[parenDepth] = index++, bndCtx = bndStack[++parenDepth] = {bd: []}, lftPrn)
1868						: "")
1869					+ (space
1870						? (parenDepth
1871							? ""
1872				// New arg or prop - so insert backspace \b (\x08) as separator for named params, used subsequently by rBuildHash, and prepare new bindings array
1873							: (paramIndex = full.slice(paramIndex, index), named
1874								? (named = boundName = bindto = false, "\b")
1875								: "\b,") + paramIndex + (paramIndex = index + all.length, bindings && pathBindings.push(bndCtx.bd = []), "\b")
1876						)
1877						: eq
1878				// named param. Remove bindings for arg and create instead bindings array for prop
1879							? (parenDepth && syntaxError(params), bindings && pathBindings.pop(), named = path, boundName = bound, paramIndex = index + all.length, bound && (bindings = bndCtx.bd = pathBindings[named] = []), path + ':')
1880							: path
1881				// path
1882								? (path.split("^").join(".").replace(rPath, parsePath)
1883									+ (prn
1884				// some.fncall(
1885										? (bndCtx = bndStack[++parenDepth] = {bd: []}, fnCall[parenDepth] = rtSq, prn)
1886										: operator)
1887								)
1888								: operator
1889				// operator
1890									? operator
1891									: rtPrn
1892				// function
1893										? ((rtPrn = fnCall[parenDepth] || rtPrn, fnCall[parenDepth] = false, bndCtx = bndStack[--parenDepth], rtPrn)
1894											+ (prn // rtPrn and prn, e.g )( in (a)() or a()(), or )[ in a()[]
1895												? (bndCtx = bndStack[++parenDepth], fnCall[parenDepth] = rtSq, prn)
1896												: "")
1897										)
1898										: comma
1899											? (fnCall[parenDepth] || syntaxError(params), ",") // We don't allow top-level literal arrays or objects
1900											: lftPrn0
1901												? ""
1902												: (aposed = apos, quoted = quot, '"')
1903				))
1904			);
1905		}
1906	}
1907
1908	var named, bindto, boundName,
1909		quoted, // boolean for string content in double quotes
1910		aposed, // or in single quotes
1911		bindings = pathBindings && pathBindings[0], // bindings array for the first arg
1912		bndCtx = {bd: bindings},
1913		bndStack = {0: bndCtx},
1914		paramIndex = 0, // list,
1915		tmplLinks = (tmpl ? tmpl.links : bindings && (bindings.links = bindings.links || {})) || topView.tmpl.links,
1916		// 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
1917		// "a.b().c^d().e.f().g" - which has four chained paths, "a.b()", "^c.d()", ".e.f()" and ".g"
1918		parenDepth = 0,
1919		fnCall = {}, // We are in a function call
1920		pathStart = {}, // tracks the start of the current path such as c^d() in the above example
1921		result = (params + (tmpl ? " " : "")).replace(rParams, parseTokens);
1922
1923	return !parenDepth && result || syntaxError(params); // Syntax error if unbalanced parens in params expression
1924}
1925
1926function buildCode(ast, tmpl, isLinkExpr) {
1927	// Build the template function code from the AST nodes, and set as property on the passed-in template object
1928	// Used for compiling templates, and also by JsViews to build functions for data link expressions
1929	var i, node, tagName, converter, tagCtx, hasTag, hasEncoder, getsVal, hasCnvt, useCnvt, tmplBindings, pathBindings, params, boundOnErrStart, boundOnErrEnd,
1930		tagRender, nestedTmpls, tmplName, nestedTmpl, tagAndElses, content, markup, nextIsElse, oldCode, isElse, isGetVal, tagCtxFn, onError, tagStart, trigger,
1931		tmplBindingKey = 0,
1932		useViews = $subSettingsAdvanced.useViews || tmpl.useViews || tmpl.tags || tmpl.templates || tmpl.helpers || tmpl.converters,
1933		code = "",
1934		tmplOptions = {},
1935		l = ast.length;
1936
1937	if ("" + tmpl === tmpl) {
1938		tmplName = isLinkExpr ? 'data-link="' + tmpl.replace(rNewLine, " ").slice(1, -1) + '"' : tmpl;
1939		tmpl = 0;
1940	} else {
1941		tmplName = tmpl.tmplName || "unnamed";
1942		if (tmpl.allowCode) {
1943			tmplOptions.allowCode = true;
1944		}
1945		if (tmpl.debug) {
1946			tmplOptions.debug = true;
1947		}
1948		tmplBindings = tmpl.bnds;
1949		nestedTmpls = tmpl.tmpls;
1950	}
1951	for (i = 0; i < l; i++) {
1952		// AST nodes: [0: tagName, 1: converter, 2: content, 3: params, 4: code, 5: onError, 6: trigger, 7:pathBindings, 8: contentMarkup]
1953		node = ast[i];
1954
1955		// Add newline for each callout to t() c() etc. and each markup string
1956		if ("" + node === node) {
1957			// a markup string to be inserted
1958			code += '\n+"' + node + '"';
1959		} else {
1960			// a compiled tag expression to be inserted
1961			tagName = node[0];
1962			if (tagName === "*") {
1963				// Code tag: {{* }}
1964				code += ";\n" + node[1] + "\nret=ret";
1965			} else {
1966				converter = node[1];
1967				content = !isLinkExpr && node[2];
1968				tagCtx = paramStructure(node[3], 'params') + '},' + paramStructure(params = node[4]);
1969				onError = node[5];
1970				trigger = node[6];
1971				markup = node[8] && node[8].replace(rUnescapeQuotes, "$1");
1972				if (isElse = tagName === "else") {
1973					if (pathBindings) {
1974						pathBindings.push(node[7]);
1975					}
1976				} else {
1977					tmplBindingKey = 0;
vendor: 4,177 bytes, lines 1978-2052
1978//					if (tmplBindings && (pathBindings = node[7])) { // Array of paths, or false if not data-bound
1979//						pathBindings = [pathBindings];
1980//						tmplBindingKey = tmplBindings.push(1); // Add placeholder in tmplBindings for compiled function
1981//					}
1982                                        if (tmplBindings && (pathBindings = node[7])) { // Array of paths, or false if not data-bound
1983                                            pathBindings = [pathBindings];
1984                                            tmplBindingKey = tmplBindings.push(1); // Add placeholder in tmplBindings for compiled function
1985                                            tmplBindingKey = tmplBindings.length;
1986                                       }
1987				}
1988				useViews = useViews || params[1] || params[2] || pathBindings || /view.(?!index)/.test(params[0]);
1989				// useViews is for perf optimization. For render() we only use views if necessary - for the more advanced scenarios.
1990				// We use views if there are props, contextual properties or args with #... (other than #index) - but you can force
1991				// using the full view infrastructure, (and pay a perf price) by opting in: Set useViews: true on the template, manually...
1992				if (isGetVal = tagName === ":") {
1993					if (converter) {
1994						tagName = converter === HTML ? ">" : converter + tagName;
1995					}
1996				} else {
1997					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!!
1998						// Create template object for nested template
1999						nestedTmpl = tmplObject(markup, tmplOptions);
2000						nestedTmpl.tmplName = tmplName + "/" + tagName;
2001						// Compile to AST and then to compiled function
2002						nestedTmpl.useViews = nestedTmpl.useViews || useViews;
2003						buildCode(content, nestedTmpl);
2004						useViews = nestedTmpl.useViews;
2005						nestedTmpls.push(nestedTmpl);
2006					}
2007
2008					if (!isElse) {
2009						// This is not an else tag.
2010						tagAndElses = tagName;
2011						useViews = useViews || tagName && (!$tags[tagName] || !$tags[tagName].flow);
2012						// Switch to a new code string for this bound tag (and its elses, if it has any) - for returning the tagCtxs array
2013						oldCode = code;
2014						code = "";
2015					}
2016					nextIsElse = ast[i + 1];
2017					nextIsElse = nextIsElse && nextIsElse[0] === "else";
2018				}
2019				tagStart = onError ? ";\ntry{\nret+=" : "\n+";
2020				boundOnErrStart = "";
2021				boundOnErrEnd = "";
2022
2023				if (isGetVal && (pathBindings || trigger || converter && converter !== HTML)) {
2024					// For convertVal we need a compiled function to return the new tagCtx(s)
2025					tagCtxFn = new Function("data,view,j,u", " // " + tmplName + " " + tmplBindingKey + " " + tagName
2026										+ "\nreturn {" + tagCtx + "};");
2027					tagCtxFn._er = onError;
2028					tagCtxFn._tag = tagName;
2029
2030					if (isLinkExpr) {
2031						return tagCtxFn;
2032					}
2033
2034					setPaths(tagCtxFn, pathBindings);
2035					tagRender = 'c("' + converter + '",view,';
2036					useCnvt = true;
2037					boundOnErrStart = tagRender + tmplBindingKey + ",";
2038					boundOnErrEnd = ")";
2039				}
2040				code += (isGetVal
2041					? (isLinkExpr ? (onError ? "try{\n" : "") + "return " : tagStart) + (useCnvt // Call _cnvt if there is a converter: {{cnvt: ... }} or {^{cnvt: ... }}
2042						? (useCnvt = undefined, useViews = hasCnvt = true, tagRender + (pathBindings
2043							? ((tmplBindings[tmplBindingKey - 1] = tagCtxFn), tmplBindingKey) // Store the compiled tagCtxFn in tmpl.bnds, and pass the key to convertVal()
2044							: "{" + tagCtx + "}") + ")")
2045						: tagName === ">"
2046							? (hasEncoder = true, "h(" + params[0] + ")")
2047							: (getsVal = true, "((v=" + params[0] + ')!=null?v:' + (isLinkExpr ? 'null)' : '"")'))
2048							// Non strict equality so data-link="title{:expr}" with expr=null/undefined removes title attribute
2049					)
2050					: (hasTag = true, "\n{view:view,tmpl:" // Add this tagCtx to the compiled code for the tagCtxs to be passed to renderTag()
2051						+ (content ? nestedTmpls.length : "0") + "," // For block tags, pass in the key (nestedTmpls.length) to the nested content template
2052						+ tagCtx + "},"));
2053
2054				if (tagAndElses && !nextIsElse) {
2055					// This is a data-link expression or an inline tag without any elses, or the last {{else}} of an inline tag
2056					// We complete the code for returning the tagCtxs array
2057					code = "[" + code.slice(0, -1) + "]";
2058					tagRender = 't("' + tagAndElses + '",view,this,';
2059					if (isLinkExpr || pathBindings) {
2060						// This is a bound tag (data-link expression or inline bound tag {^{tag ...}}) so we store a compiled tagCtxs function in tmp.bnds
2061						code = new Function("data,view,j,u", " // " + tmplName + " " + tmplBindingKey + " " + tagAndElses + "\nreturn " + code + ";");
2062						code._er = onError;
2063						code._tag = tagAndElses;
2064						if (pathBindings) {
2065							setPaths(tmplBindings[tmplBindingKey - 1] = code, pathBindings);
2066						}
2067						if (isLinkExpr) {
2068							return code; // For a data-link expression we return the compiled tagCtxs function
2069						}
2070						boundOnErrStart = tagRender + tmplBindingKey + ",undefined,";
2071						boundOnErrEnd = ")";
2072					}
2073
2074					// This is the last {{else}} for an inline tag.
2075					// For a bound tag, pass the tagCtxs fn lookup key to renderTag.
2076					// For an unbound tag, include the code directly for evaluating tagCtxs array
2077					code = oldCode + tagStart + tagRender + (tmplBindingKey || code) + ")";
2078					pathBindings = 0;
2079					tagAndElses = 0;
2080				}
2081				if (onError) {
2082					useViews = true;
2083					code += ';\n}catch(e){ret' + (isLinkExpr ? "urn " : "+=") + boundOnErrStart + 'j._err(e,view,' + onError + ')' + boundOnErrEnd + ';}' + (isLinkExpr ? "" : 'ret=ret');
2084				}
2085			}
2086		}
2087	}
2088	// Include only the var references that are needed in the code
2089	code = "// " + tmplName
2090
2091		+ "\nvar v"
2092		+ (hasTag ? ",t=j._tag" : "")                // has tag
2093		+ (hasCnvt ? ",c=j._cnvt" : "")              // converter
2094		+ (hasEncoder ? ",h=j._html" : "")           // html converter
2095		+ (isLinkExpr ? ";\n" : ',ret=""\n')
2096		+ (tmplOptions.debug ? "debugger;" : "")
2097		+ code
2098		+ (isLinkExpr ? "\n" : ";\nreturn ret;");
2099
2100	if ($subSettings.debugMode !== false) {
2101		code = "try {\n" + code + "\n}catch(e){\nreturn j._err(e, view);\n}";
2102	}
2103
2104	try {
2105		code = new Function("data,view,j,u", code);
2106	} catch (e) {
2107		syntaxError("Compiled template code:\n\n" + code + '\n: "' + e.message + '"');
2108	}
2109	if (tmpl) {
2110		tmpl.fn = code;
2111		tmpl.useViews = !!useViews;
2112	}
2113	return code;
2114}
2115
2116//==========
2117// Utilities
2118//==========
2119
2120// Merge objects, in particular contexts which inherit from parent contexts
2121function extendCtx(context, parentContext) {
2122	// Return copy of parentContext, unless context is defined and is different, in which case return a new merged context
2123	// If neither context nor parentContext are defined, return undefined
2124	return context && context !== parentContext
2125		? (parentContext
2126			? $extend($extend({}, parentContext), context)
2127			: context)
2128		: parentContext && $extend({}, parentContext);
2129}
2130
2131// Get character entity for HTML and Attribute encoding
2132function getCharEntity(ch) {
2133	return charEntities[ch] || (charEntities[ch] = "&#" + ch.charCodeAt(0) + ";");
2134}
2135
2136function getTargetProps(source) {
2137	// this pointer is theMap - which has tagCtx.props too
2138	// arguments: tagCtx.args.
2139	var key, prop,
2140		props = [];
2141
2142	if (typeof source === OBJECT) {
2143		for (key in source) {
2144			prop = source[key];
2145			if (key !== $expando && source.hasOwnProperty(key) && !$isFunction(prop)) {
2146				props.push({key: key, prop: prop});
2147			}
2148		}
2149	}
2150	return props;
2151}
2152
2153function $fnRender(data, context, noIteration) {
2154	var tmplElem = this.jquery && (this[0] || error('Unknown template: "' + this.selector + '"')),
2155		tmpl = tmplElem.getAttribute(tmplAttr);
2156
2157	return renderContent.call(tmpl ? $.data(tmplElem)[jsvTmpl] : $templates(tmplElem), data, context, noIteration);
2158}
2159
2160//========================== Register converters ==========================
2161
2162function htmlEncode(text) {
2163	// HTML encode: Replace < > & ' and " by corresponding entities.
2164	return text != undefined ? rIsHtml.test(text) && ("" + text).replace(rHtmlEncode, getCharEntity) || text : "";
2165}
2166
2167//========================== Initialize ==========================
2168
2169$sub = $views.sub;
2170$viewsSettings = $views.settings;
2171
2172if (!(jsr || $ && $.render)) {
2173	// JsRender not already loaded, or loaded without jQuery, and we are now moving from jsrender namespace to jQuery namepace
2174	for (jsvStoreName in jsvStores) {
2175		registerStore(jsvStoreName, jsvStores[jsvStoreName]);
2176	}
2177
2178	$converters = $views.converters;
2179	$helpers = $views.helpers;
2180	$tags = $views.tags;
2181
2182	$sub._tg.prototype = {
2183		baseApply: baseApply,
2184		cvtArgs: convertArgs
2185	};
2186
2187	topView = $sub.topView = new View();
2188
2189	//BROWSER-SPECIFIC CODE
2190	if ($) {
2191
2192		////////////////////////////////////////////////////////////////////////////////////////////////
2193		// jQuery (= $) is loaded
2194
2195		$.fn.render = $fnRender;
2196		$expando = $.expando;
2197		if ($.observable) {
2198			$extend($sub, $.views.sub); // jquery.observable.js was loaded before jsrender.js
2199			$views.map = $.views.map;
2200		}
2201
2202	} else {
2203		////////////////////////////////////////////////////////////////////////////////////////////////
2204		// jQuery is not loaded.
2205
2206		$ = {};
2207
2208		if (setGlobals) {
2209			global.jsrender = $; // We are loading jsrender.js from a script element, not AMD or CommonJS, so set global
2210		}
2211
2212		// Error warning if jsrender.js is used as template engine on Node.js (e.g. Express or Hapi...)
2213		// Use jsrender-node.js instead...
2214		$.renderFile = $.__express = $.compile = function() { throw "Node.js: use npm jsrender, or jsrender-node.js"; };
2215
2216		//END BROWSER-SPECIFIC CODE
2217		$.isFunction = function(ob) {
2218			return typeof ob === "function";
2219		};
2220
2221		$.isArray = Array.isArray || function(obj) {
2222			return ({}.toString).call(obj) === "[object Array]";
2223		};
2224
2225		$sub._jq = function(jq) { // private method to move from JsRender APIs from jsrender namespace to jQuery namespace
2226			if (jq !== $) {
2227				$extend(jq, $); // map over from jsrender namespace to jQuery namespace
2228				$ = jq;
2229				$.fn.render = $fnRender;
2230				delete $.jsrender;
2231				$expando = $.expando;
2232			}
2233		};
2234
2235		$.jsrender = versionNumber;
2236	}
2237	$subSettings = $sub.settings;
2238	$subSettings.allowCode = false;
2239	$isFunction = $.isFunction;
2240	$.render = $render;
2241	$.views = $views;
2242	$.templates = $templates = $views.templates;
2243
2244	for (setting in $subSettings) {
2245		addSetting(setting);
2246	}
2247
2248	($viewsSettings.debugMode = function(debugMode) {
2249		return debugMode === undefined
2250			? $subSettings.debugMode
2251			: (
2252				$subSettings.debugMode = debugMode,
2253				$subSettings.onError = debugMode + "" === debugMode
2254					? new Function("", "return '" + debugMode + "';" )
2255					: $isFunction(debugMode)
2256						? debugMode
2257						: undefined,
2258				$viewsSettings);
2259	})(false); // jshint ignore:line
2260
2261	$subSettingsAdvanced = $subSettings.advanced = {
2262		useViews: false,
2263		_jsv: false // For global access to JsViews store
2264	};
2265
2266	//========================== Register tags ==========================
2267
2268	$tags({
2269		"if": {
2270			render: function(val) {
2271				// This function is called once for {{if}} and once for each {{else}}.
2272				// We will use the tag.rendering object for carrying rendering state across the calls.
2273				// If not done (a previous block has not been rendered), look at expression for this block and render the block if expression is truthy
2274				// Otherwise return ""
2275				var self = this,
2276					tagCtx = self.tagCtx,
2277					ret = (self.rendering.done || !val && (arguments.length || !tagCtx.index))
2278						? ""
2279						: (self.rendering.done = true, self.selected = tagCtx.index,
2280							// Test is satisfied, so render content on current context. We call tagCtx.render() rather than return undefined
2281							// (which would also render the tmpl/content on the current context but would iterate if it is an array)
2282							tagCtx.render(tagCtx.view, true)); // no arg, so renders against parentView.data
2283				return ret;
2284			},
2285			flow: true
2286		},
2287		"for": {
2288			render: function(val) {
2289				// This function is called once for {{for}} and once for each {{else}}.
2290				// We will use the tag.rendering object for carrying rendering state across the calls.
2291				var finalElse = !arguments.length,
2292					value,
2293					self = this,
2294					tagCtx = self.tagCtx,
2295					result = "",
2296					done = 0;
2297
2298				if (!self.rendering.done) {
2299					value = finalElse ? tagCtx.view.data : val; // For the final else, defaults to current data without iteration.
2300					if (value !== undefined) {
2301						result += tagCtx.render(value, finalElse); // Iterates except on final else, if data is an array. (Use {{include}} to compose templates without array iteration)
2302						done += $isArray(value) ? value.length : 1;
2303					}
2304					if (self.rendering.done = done) {
2305						self.selected = tagCtx.index;
2306					}
2307					// If nothing was rendered we will look at the next {{else}}. Otherwise, we are done.
2308				}
2309				return result;
2310			},
2311			flow: true
2312		},
2313		props: {
2314			baseTag: "for",
2315			dataMap: dataMap(getTargetProps),
2316			flow: true
2317		},
2318		include: {
2319			flow: true
2320		},
2321		"*": {
2322			// {{* code... }} - Ignored if template.allowCode and $.views.settings.allowCode are false. Otherwise include code in compiled template
2323			render: retVal,
2324			flow: true
2325		},
2326		":*": {
2327			// {{:* returnedExpression }} - Ignored if template.allowCode and $.views.settings.allowCode are false. Otherwise include code in compiled template
2328			render: retVal,
2329			flow: true
2330		},
2331		dbg: $helpers.dbg = $converters.dbg = dbgBreak // Register {{dbg/}}, {{dbg:...}} and ~dbg() to throw and catch, as breakpoints for debugging.
2332	});
2333
2334	$converters({
2335		html: htmlEncode,
2336		attr: htmlEncode, // Includes > encoding since rConvertMarkers in JsViews does not skip > characters in attribute strings
2337		url: function(text) {
2338			// URL encoding helper.
2339			return text != undefined ? encodeURI("" + text) : text === null ? text : ""; // null returns null, e.g. to remove attribute. undefined returns ""
2340		}
2341	});
2342}
2343//========================== Define default delimiters ==========================
2344$subSettings = $sub.settings;
2345$isArray = ($||jsr).isArray;
2346$viewsSettings.delimiters("{{", "}}", "^");
2347
2348if (jsrToJq) { // Moving from jsrender namespace to jQuery namepace - copy over the stored items (templates, converters, helpers...)
2349	jsr.views.sub._jq($);
2350}
2351
2352//<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< JsObservable >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
2353/* JsObservable:
2354 * See http://www.jsviews.com/#jsobservable and http://github.com/borismoore/jsviews
2355 * Copyright 2016, Boris Moore
2356 * Released under the MIT License.
2357 */
2358
2359//========================== Top-level vars ==========================
2360
2361$views = $.views;
2362$sub = $views.sub;
2363$isFunction = $.isFunction;
2364$isArray = $.isArray;
2365$expando = $.expando;
2366if (!$.observe) {
2367
2368	var $eventSpecial = $.event.special,
2369		slice = [].slice,
2370		splice = [].splice,
2371		concat = [].concat,
2372		PARSEINT = parseInt,
2373		rNotWhite = /\S+/g,
2374		propertyChangeStr = $sub.propChng = $sub.propChng || "propertyChange",// These two settings can be overridden on settings after loading
2375		arrayChangeStr = $sub.arrChng = $sub.arrChng || "arrayChange",        // jsRender, and prior to loading jquery.observable.js and/or JsViews
2376		cbBindingsStore = {},
2377		observeStr = propertyChangeStr + ".observe",
2378		observeObjKey = 1,
2379		observeCbKey = 1,
2380		observeInnerCbKey = 1,
2381		$hasData = $.hasData,
2382		$data = $.data,
2383		remove = {}, // flag for removeProperty
2384
2385	//========================== Top-level functions ==========================
2386
2387	getCbKey = function(cb) {
2388		return cb._cId = cb._cId || (".obs" + observeCbKey++);
2389	},
2390
2391	ObjectObservable = function(ns, data) {
2392		this._data = data;
2393		this._ns = ns;
2394		return this;
2395	},
2396
2397	ArrayObservable = function(ns, data) {
2398		this._data = data;
2399		this._ns = ns;
2400		return this;
2401	},
2402
2403	wrapArray = function(data) {
2404		return $isArray(data)
2405			? [data]
2406			: data;
2407	},
2408
2409	resolvePathObjects = function(paths, root, callback) {
2410		paths = paths
2411			? $isArray(paths)
2412				? paths
2413				: [paths]
2414			: [];
2415
2416		var i, path,
2417			object = root,
2418			nextObj = object,
2419			l = paths && paths.length,
2420			out = [];
2421
2422		for (i = 0; i < l; i++) {
2423			path = paths[i];
2424			if ($isFunction(path)) {
2425				out = out.concat(resolvePathObjects(path.call(root, root, callback), root));
2426				continue;
2427			} else if ("" + path !== path) {
2428				root = nextObj = path;
2429				if (nextObj !== object) {
2430					out.push(object = nextObj);
2431				}
2432				continue;
2433			}
2434			if (nextObj !== object) {
2435				out.push(object = nextObj);
2436			}
2437			out.push(path);
2438		}
2439		return out;
2440	},
2441
2442	removeCbBindings = function(cbBindings, cbBindingsId) {
2443		// If the cbBindings collection is empty we will remove it from the cbBindingsStore
2444		for (var cb in cbBindings) {
2445			return;
2446		}
2447		delete cbBindingsStore[cbBindingsId]; // This binding collection is empty, so remove from store
2448	},
2449
2450	onObservableChange = function(ev, eventArgs) {
2451		function isOb(val) {
2452			return typeof val === OBJECT && (paths[0] || allowArray && $isArray(val));
2453		}
2454
2455		if (!(ev.data && ev.data.off)) {
2456			// Skip if !!ev.data.off: - a handler that has already been removed (maybe was on handler collection at call time - then removed by another handler)
2457			var allPath, filter, parentObs,
2458				oldValue = eventArgs.oldValue,
2459				value = eventArgs.value,
2460				ctx = ev.data,
2461				observeAll = ctx.observeAll,
2462				cb = ctx.cb,
2463				allowArray = !cb.noArray,
2464				paths = ctx.paths,
2465				ns = ctx.ns;
2466
2467			if (ev.type === arrayChangeStr) {
2468				(cb.array || cb).call(ctx, ev, eventArgs); // If there is an arrayHandler expando on the regular handler, use it, otherwise use the regular handler for arrayChange events also - for example: $.observe(array, handler)
2469				// or observeAll() with an array in the graph. Note that on data-link bindings we ensure always to have an array handler - $.noop if none is specified e.g. on the data-linked tag.
2470			} else if (ctx.prop === eventArgs.path || ctx.prop === "*") {
2471				if (observeAll) {
2472					allPath = observeAll._path + "." + eventArgs.path;
2473					filter = observeAll.filter;
2474					parentObs = [ev.target].concat(observeAll.parents());
2475
2476					if (isOb(oldValue)) {
2477						observe_apply(allowArray, ns, [oldValue], paths, cb, true, filter, [parentObs], allPath); // unobserve
2478					}
2479					if (isOb(value)) {
2480						observe_apply(allowArray, ns, [value], paths, cb, undefined, filter, [parentObs], allPath);
2481					}
2482				} else {
2483					if (isOb(oldValue)) { // oldValue is an object, so unobserve
2484						observe_apply(allowArray, ns, [oldValue], paths, cb, true); // unobserve
2485					}
2486					if (isOb(value)) { // value is an object, so observe
2487						observe_apply(allowArray, ns, [value], paths, cb);
2488					}
2489				}
2490				ctx.cb(ev, eventArgs);
2491			}
2492		}
2493	},
2494
2495	observe_apply = function() {
2496		// $.observe(), but allowing you to include arrays within the arguments - which you want flattened.
2497		var args = concat.apply([], arguments); // Flatten the arguments
2498		return $observe.apply(args.shift(), args);
2499	},
2500
2501	$observeAll = function(cb, filter, unobserve) {
2502		observeAll(this._ns, this._data, cb, filter, [], "root", unobserve);
2503	},
2504
2505	$unobserveAll = function(cb, filter) {
2506		$observeAll.call(this, cb, filter, true);
2507	},
2508
2509	observeAll = function(namespace, object, cb, filter, parentObs, allPath, unobserve, objMap) {
2510		function observeArrayItems(arr, unobs) {
2511			l = arr.length;
2512			newAllPath = allPath + "[]";
2513			while (l--) {
2514				filterAndObserveAll(arr, l, unobs, 1);
2515			}
2516		}
2517
2518		function filterAndObserveAll(obj, prop, unobs, nestedArray) {
2519			var newObject, newParentObs;
2520			if (prop !== $expando) {
2521				if (newObject = $observable._fltr(newAllPath, obj[prop], nextParentObs, filter)) {
2522					newParentObs = nextParentObs.slice();
2523					if (nestedArray && updatedTgt && newParentObs[0] !== updatedTgt) {
2524						newParentObs.unshift(updatedTgt); // For array change events when observing an array which is not the root, need to add updated array to parentObs
2525					}
2526					observeAll(namespace, newObject, cb, filter || (nestedArray ? undefined : 0), newParentObs, newAllPath, unobs, objMap);
2527					// If nested array, need to observe the array too - so set filter to undefined
2528				}
2529			}
2530		}
2531
2532		function wrappedCb(ev, eventArgs) {
2533			// This object is changing.
2534			allPath = ev.data.observeAll._path;
2535			updatedTgt = ev.target;
2536			switch (eventArgs.change) { // observeAll/unobserveAll on added or removed objects
2537				case "insert":
2538					observeArrayItems(eventArgs.items);
2539					break;
2540				case "remove":
2541					observeArrayItems(eventArgs.items, true); // unobserveAll on removed items
2542					break;
2543				case "set":
2544					newAllPath = allPath + "." + eventArgs.path;
2545					filterAndObserveAll(eventArgs, "oldValue", true); // unobserve old value
2546					filterAndObserveAll(eventArgs, "value"); // observe new value
2547			}
2548			updatedTgt = undefined;
2549			cb.apply(this, arguments); // Observe this object (invoke the callback)
2550		}
2551
2552		var l, isObject, newAllPath, nextParentObs, updatedTgt, obId,
2553			notRemoving = !objMap || objMap.un || !unobserve; // true unless it is an observeAll call (not unobserveAll) and we are removing a listener (not adding one)
2554
2555		if (object && typeof object === OBJECT) {
2556			nextParentObs = [object].concat(parentObs); // The parentObs chain for the next depth of observeAll
2557			isObject = $isArray(object) ? "" : "*";
2558			if (objMap && notRemoving && $hasData(object) && objMap[obId = $data(object).obId]) {
2559				objMap[obId]++;
2560				return; // This object has already being observed/unobserved by this observeAll/unobserveAll call (must be a cyclic object graph) so skip, to avoid
2561				// stack overflow/multiple instances of listener. See jsviews/pull/305
2562				// NOTE - WE DO NOT support ObserveAll on data with cyclic graphs which include DUPLICATE REFERENCES TO ARRAY PROPERTIES - such as data.children = data.descendants = []
2563			}
2564			if (!objMap) {
2565				objMap = {un: unobserve}; // Map object to register observed objects for this observeAll
2566			}
2567
2568			if (cb) {
2569				// Observe this object or array - and also listen for changes to object graph, to add or remove observers from the modified object graph
2570				if (isObject || filter !== 0) {
2571					// If an object, observe the object. If an array, only add arrayChange binding if has filter or if filter is undefined (!== 0) - which
2572					// is the case for top-level calls or for nested array (array item of an array - e.g. member of 2-dimensional array).
2573					// For array properties lower in the tree, with no filter, filter is set to 0 in filterAndObserveAll, so no arrayChange binding here,
2574					// since they get arrayChange binding added during regular $.observe(array ...) binding.
2575					wrappedCb._cId = getCbKey(cb); // Identify wrapped callback with unwrapped callback, so unobserveAll will
2576													// remove previous observeAll wrapped callback, if inner callback was the same;
2577					if (notRemoving) {
2578						$observe(namespace, object, isObject, wrappedCb, unobserve, filter, nextParentObs, allPath);
2579						obId = $data(object).obId;
2580						objMap[obId] = (objMap[obId] || 0) + 1; // Register on map of objects observed/unobserved by this observeAll/unobserveAll call
2581							//- or remove from map if we are removing this object from observeAll call. (Avoid dups, for cyclic graphs)
2582					} else {
2583						if (--objMap[$data(object).obId]) {
2584							// Register on map of objects observed/unobserved by this observeAll/unobserveAll call
2585							//- or remove from map if we are removing this object from observeAll call. (Avoid dups, for cyclic graphs)
2586							return;
2587						}
2588						$observe(namespace, object, isObject, wrappedCb, unobserve, filter, nextParentObs, allPath);
2589					}
2590				}
2591			} else {
2592				// No callback. Just unobserve if unobserve === true.
2593				if (objMap) {
2594					objMap[$data(object).obId] = 1; // Register on map of objects unobserved by this unobserveAll call. (Avoid dups, for cyclic graphs)
2595				}
2596				$observe(namespace, object, isObject, undefined, unobserve, filter, nextParentObs, allPath);
2597			}
2598
2599			if (isObject) {
2600				// Continue stepping through object graph, observing object and arrays
2601				// To override filtering, pass in filter function, or replace $.observable._fltr
2602				for (l in object) {
2603					newAllPath = allPath + "." + l;
2604					filterAndObserveAll(object, l, unobserve);
2605				}
2606			} else { // Observe items in Array
2607				observeArrayItems(object, unobserve);
2608			}
2609		}
2610	},
2611
2612	shallowFilter = function(allPath /*, object, parentObs*/) {
2613		return allPath.indexOf(".") < 0 && allPath.indexOf("[") < 0;
2614	},
2615
2616	$unobserve = function() {
2617		[].push.call(arguments, true); // Add true as additional final argument
2618		return $observe.apply(this, arguments);
2619	};
2620
2621	$observe = function() {
2622		// $.observe([namespace, ]root, [1 or more objects, path or path Array params...], callback[, contextCallback][, unobserve])
2623
2624		function innerObserve() {
2625
2626			function observeOnOff(namespace, pathStr, isArrayBinding, off) {
2627				var j, evData,
2628					obIdExpando = $hasData(object),
2629					boundObOrArr = wrapArray(object),
2630					prntObs = parentObs,
2631					allPth = allPath;
2632
2633				namespace = initialNs ? namespace + "." + initialNs : namespace;
2634
2635				if (!unobserve && (off || isArrayBinding)) {
2636					events = obIdExpando && $._data(object);
2637					events = events && events.events;
2638					events = events && events[isArrayBinding ? arrayChangeStr : propertyChangeStr];
2639					el = events && events.length;
2640					while (el--) { // Skip duplicates
2641						data = events[el] && events[el].data;
2642						if (data && (off && data.ns !== initialNs
2643							// When observing, don't unbind dups unless they have the same namespace
2644							|| !off && data.ns === initialNs && data.cb && data.cb._cId === callback._cId))
2645							// When observing and doing array binding, don't bind dups if they have the same namespace (Dups can happen e.g. with {^{for people ~foo=people}})
2646						{
2647							return;
2648						}
2649					}
2650				}
2651				if (unobserve || off) {
2652					$(boundObOrArr).off(namespace, onObservableChange);
2653				} else {
2654					evData = isArrayBinding ? {}
2655						: {
2656							fullPath: path,
2657							paths: pathStr ? [pathStr] : [],
2658							prop: prop
2659						};
2660					evData.ns = initialNs;
2661					evData.cb = callback;
2662
2663					if (allPath) {
2664						// This is an observeAll call
2665						evData.observeAll = {
2666							_path: allPth,
2667							path: function() { // Step through path and parentObs parent chain, replacing '[]' by '[n]' based on current index of objects in parent arrays.
2668								j = prntObs.length;
2669								return allPth.replace(/[[.]/g, function(all) {
2670									j--;
2671									return all === "["
2672										? "[" + $.inArray(prntObs[j - 1], prntObs[j])
2673										: ".";
2674								});
2675							},
2676							parents: function() {
2677								return prntObs; // The chain of parents between the modified object and the root object used in the observeAll() call
2678							},
2679							filter: filter
2680						};
2681					}
2682					$(boundObOrArr).on(namespace, null, evData, onObservableChange);
2683					if (cbBindings) {
2684						// Add object to cbBindings
2685						cbBindings[$data(object).obId || $data(object, "obId", observeObjKey++)] = object;
2686					}
2687				}
2688			}
2689
2690			function getInnerCb(exprOb) {
2691				// Returns the innerCb used for updating a computed in a compiled expression (setting the new instance as exprOb.ob, unobserving the previous object,
2692				// and observing the new one), then calling the outerCB - i.e. the handler for the whole compiled expression.
2693				// Initialized exprOb.ob to the current object.
2694				// Uses the contextCb callback to execute the compiled exprOb template in the context of the view/data etc. to get the returned value, typically an object or array.
2695				// If it is an array, registers array binding
2696				var origRt = root;
2697				// Note: For jsviews/issues/292 ctxCb will need var ctxCb = contextCb || function(exprOb, origRt) {return exprOb._jsv(origRt);};
2698
2699				exprOb.ob = contextCb(exprOb, origRt); // Initialize object
2700
2701				return exprOb.cb = function(ev, eventArgs) {
2702					var obj = exprOb.ob, // The old object
2703						sub = exprOb.sb,
2704						newObj = contextCb(exprOb, origRt);
2705
2706					if (newObj !== obj) {
2707						if (typeof obj === OBJECT) {
2708							bindArray(obj, true);
2709							if (sub || allowArray && $isArray(obj)) {
2710								innerObserve([obj], sub, callback, contextCb, true); // unobserve on the old object
2711							}
2712						}
2713						exprOb.ob = newObj;
2714						// Put the updated object instance onto the exprOb in the paths array, so subsequent string paths are relative to this object
2715						if (typeof newObj === OBJECT) {
2716							bindArray(newObj);
2717							if (sub || allowArray && $isArray(newObj)) {
2718								// Register array binding
2719								innerObserve([newObj], sub, callback, contextCb);
2720							}
2721						}
2722					}
2723					// Call the outerCb - to execute the compiled expression that this computed is part of
2724					callback(ev, eventArgs);
2725				};
2726			}
2727
2728			function bindArray(arr, unbind, isArray, relPath) {
2729				if (allowArray) {
2730					// This is a call to observe that does not come from observeAndBind (tag binding), so we allow arrayChange binding
2731					var prevObj = object,
2732						prevAllPath = allPath;
2733
2734					object = arr;
2735					if (relPath) {
2736						object = arr[relPath];
2737						allPath += "." + relPath;
2738					}
2739					if (filter && object) {
2740						object = $observable._fltr(allPath, object, relPath ? [arr].concat(parentObs) : parentObs, filter);
2741					}
2742					if (object && (isArray || $isArray(object))) {
2743						observeOnOff(arrayChangeStr + ".observe" + (callback ? getCbKey(callback) : ""), undefined, true, unbind);
2744					}
2745					object = prevObj;
2746					allPath = prevAllPath;
2747				}
2748			}
2749
2750			var i, p, skip, parts, prop, path, dep, unobserve, callback, cbId, inId, el, data, events, contextCb, items, cbBindings,
2751				depth, innerCb, parentObs, allPath, filter, initNsArr, initNsArrLen,
2752				ns = observeStr,
2753				paths = this != 1 // Using != for IE<10 bug- see jsviews/issues/237
2754					? concat.apply([], arguments) // Flatten the arguments - this is a 'recursive call' with params using the 'wrapped array'
2755													// style - such as innerObserve([object], path.path, [origRoot], path.prm, innerCb, ...);
2756					: slice.call(arguments), // Don't flatten - this is the first 'top-level call, to innerObserve.apply(1, paths)
2757				lastArg = paths.pop() || false,
2758				root = paths.shift(),
2759				object = root,
2760				l = paths.length;
2761
2762			if (lastArg + "" === lastArg) { // If last arg is a string then this observe call is part of an observeAll call,
2763				allPath = lastArg;          // and the last three args are the parentObs array, the filter, and the allPath string.
2764				parentObs = paths.pop();
2765				filter = paths.pop();
2766				lastArg = !!paths.pop(); // unobserve
2767				l -= 3;
2768			}
2769			if (lastArg === !!lastArg) {
2770				unobserve = lastArg;
2771				lastArg = paths[l-1];
2772				lastArg = l && lastArg + "" !== lastArg && (!lastArg || $isFunction(lastArg)) ? (l--, paths.pop()) : undefined;
2773				if (unobserve && !l && $isFunction(root)) {
2774					lastArg = root;
2775					root = undefined;
2776				}
2777			}
2778			callback = lastArg;
2779			if (l && $isFunction(paths[l - 1])) {
2780				contextCb = callback;
2781				callback = paths.pop();
2782				l--;
2783			}
2784
2785			if (unobserve && callback && !callback._cId) {
2786				return;
2787			}
2788
2789			// Use a unique namespace (e.g. obs7) associated with each observe() callback to allow unobserve to remove handlers
2790			ns += callback
2791				? ((inId = callback._inId || ""), unobserve)
2792					? callback._cId + inId
2793					: (cbId = getCbKey(callback)) + inId
2794				: "";
2795
2796			if (cbId && !unobserve) {
2797				cbBindings = cbBindingsStore[cbId] = cbBindingsStore[cbId] || {};
2798			}
2799
2800			initNsArr = initialNs && initialNs.match(rNotWhite) || [""];
2801			initNsArrLen = initNsArr.length;
2802
2803			while (initNsArrLen--) {
2804				initialNs = initNsArr[initNsArrLen];
2805				if (root && (path = paths[0], !path || path + "" !== path)) {
2806					if ($isArray(root)) {
2807						bindArray(root, unobserve, true);
2808					} else if (unobserve) {
2809						// remove onObservableChange handlers that wrap that callback
2810						observeOnOff(ns, "");
2811					}
2812				}
2813				if (unobserve && !l && !root) { // unobserve() - unobserves all
2814					for (p in cbBindingsStore) {
2815						p = cbBindingsStore[p];
2816						for (data in p) {
2817							object = p[data];
2818							if ($isArray(object)) {
2819								bindArray(object, unobserve, unobserve);
2820							} else {
2821								// remove onObservableChange handlers that wrap that callback
2822								observeOnOff(ns, "");
2823							}
2824						}
2825					}
2826				}
2827				depth = 0;
2828				for (i = 0; i < l; i++) {
2829					path = paths[i];
2830					if (path === "") {
2831						continue;
2832					}
2833					object = root;
2834					if ("" + path === path) {
2835						// Consider support for computed paths: jsviews/issues/292
2836						//if (/[\(\[\+]/.test(path)) {
2837						//	var b={links:{}}, t = $sub.tmplFn("{:"+path+"}", b, true), items = t.paths[0];
2838						//	l += items.length - 1;
2839						//	splice.apply(paths, [i--, 1].concat(items));
2840						//	continue;
2841						//}
2842						parts = path.split("^");
2843						if (parts[1]) {
2844							// We bind the leaf, plus additional nodes based on depth.
2845							// "a.b.c^d.e" is depth 2, so listens to changes of e, plus changes of d and of c
2846							depth = parts[0].split(".").length;
2847							path = parts.join(".");
2848							depth = path.split(".").length - depth;
2849							// if more than one ^ in the path, the first one determines depth
2850						}
2851						if (contextCb && (items = contextCb(path, root, depth))) {
2852							// If the array of objects and paths returned by contextCb is non empty, insert them
2853							// into the sequence, replacing the current item (path). Otherwise simply remove current item (path)
2854							l += items.length - 1;
2855							splice.apply(paths, [i--, 1].concat(items));
2856							continue;
2857						}
2858						parts = path.split(".");
2859					} else {
2860						if (!$isFunction(path)) {
2861							if (path && path._jsv) {
2862								// This is a compiled function for binding to an object returned by a helper/data function.
2863								// Set current object on exprOb.ob, and get innerCb for updating the object
2864								innerCb = unobserve ? path.cb : getInnerCb(path);
2865								innerCb.noArray = !allowArray;
2866								// innerCb._ctx = callback._ctx; Could pass context (e.g. linkCtx) for use in a depends = function() {} call, so depends is different for different linkCtx's
2867								innerCb._cId = callback._cId;
2868								// Set the same cbBindingsStore key as for callback, so when callback is disposed, disposal of innerCb happens too.
2869								innerCb._inId = innerCb._inId || ".obIn" + observeInnerCbKey++;
2870								if (path.bnd || path.prm && path.prm.length || !path.sb) {
2871									// If the exprOb is bound e.g. foo()^sub.path, or has parameters e.g. foo(bar) or is a leaf object (so no sub path) e.g. foo()
2872									// then observe changes on the object, or its parameters and sub-path
2873									innerObserve([object], path.path, [origRoot], path.prm, innerCb, contextCb, unobserve);
2874								}
2875								if (path.sb) { // subPath
2876									innerObserve([path.ob], path.sb, callback, contextCb, unobserve);
2877								}
2878								path = origRoot;
2879								object = undefined;
2880							} else {
2881								object = path; // For top-level calls, objects in the paths array become the origRoot for subsequent paths.
2882							}
2883						}
2884						parts = [root = path];
2885					}
2886					while (object && (prop = parts.shift()) !== undefined) {
2887						if (typeof object === OBJECT) {
2888							if ("" + prop === prop) {
2889								if (prop === "") {
2890									continue;
2891								}
2892								if ((parts.length < depth + 1) && !object.nodeType) {
2893									// Add observer for each token in path starting at depth, and on to the leaf
2894									if (!unobserve && (events = $hasData(object) && $._data(object))) {
2895										events = events.events;
2896										events = events && events[propertyChangeStr];
2897										el = events && events.length;
2898										skip = 0;
2899										while (el--) { // Skip duplicates
2900											data = events[el].data;
2901											if (data
2902												&& data.ns === initialNs
2903												&& data.cb._cId === callback._cId
2904												&& data.cb._inId === callback._inId
2905												&& (data.prop === prop || data.prop === "*" || data.prop === "**")) {
2906												if (p = parts.join(".")) {
2907													data.paths.push(p); // We will skip this binding, but if it is not a leaf binding,
2908													// need to keep bindings for rest of path, ready for if the object gets swapped.
2909												}
2910												skip++;
2911											}
2912										}
2913										if (skip) {
2914											// Duplicate binding(s) found, so move on
2915											object = object[prop];
2916											continue;
2917										}
2918									}
2919									if (prop === "*" || prop === "**") { // "*" => all properties. "**" => all properties and sub-properties (i.e. deep observeAll behavior)
2920										if (!unobserve && events && events.length) {
2921											// Remove existing bindings, since they will be duplicates with "*" or "**"
2922											observeOnOff(ns, "", false, true);
2923										}
2924										if (prop === "*") {
2925											observeOnOff(ns, ""); // observe the object for any property change
2926											for (p in object) {
2927												// observing "*": So (in addition to listening to prop change, above) listen to arraychange on props of type array
2928												if (p !== $expando) {
2929													bindArray(object, unobserve, undefined, p);
2930												}
2931											}
2932										} else {
2933											$.observable(initialNs, object)[(unobserve ? "un" : "") + "observeAll"](callback); // observe or unobserve the object for any property change
2934										}
2935										break;
2936									} else if (prop) {
2937										observeOnOff(ns + ".p_" + prop, parts.join("^")); // By using "^" rather than "." we ensure that deep binding will be used on newly inserted object graphs
2938									}
2939								}
2940								if (allPath) {
2941									allPath += "." + prop;
2942								}
2943								prop = object[prop];
2944							}
2945							if ($isFunction(prop)) {
2946								if (dep = prop.depends) {
2947									// This is a computed observable. We will observe any declared dependencies
2948									innerObserve([object], resolvePathObjects(dep, object, callback), callback, contextCb, unobserve);
2949								}
2950								break;
2951							}
2952							object = prop;
2953							if (unobserve && object === root && (i>l-2 || paths[i+1] + "" !== paths[i+1])) {
2954								// unobserve all handlers of object, if not followed by string path.
2955								// e.g.$.unobserve(object1, object2, "path", object3) will unobserve all from object1 and object3, and just "path" listener from object2
2956								observeOnOff(ns, "");
2957							}
2958						}
2959					}
2960					bindArray(object, unobserve);
2961				}
2962			}
2963			if (cbId) {
2964				removeCbBindings(cbBindings, cbId);
2965			}
2966
2967			// Return the cbBindings to the top-level caller, along with the cbId
2968			return {cbId: cbId, bnd: cbBindings};
2969		}
2970
2971		var initialNs,
2972			allowArray = this != false, // If this === false, this is a call from observeAndBind - doing binding of datalink expressions. We don't bind
2973			// arrayChange events in this scenario. Instead, {^{for}} and similar do specific arrayChange binding to the tagCtx.args[0] value, in onAfterLink.
2974			// Note deliberately using this != false, rather than this !== false because of IE<10 bug- see jsviews/issues/237
2975			paths = slice.call(arguments),
2976			origRoot = paths[0];
2977
2978		if (origRoot + "" === origRoot && allowArray) {
2979			initialNs = origRoot; // The first arg is a namespace, since it is a string, and this call is not from observeAndBind
2980			paths.shift();
2981			origRoot = paths[0];
2982		}
2983
2984		return innerObserve.apply(1, paths);
2985	};
2986
2987	$observable = function(ns, data) {
2988		if (arguments.length === 1) {
2989			data = ns;
2990			ns = "";
2991		}
2992		return $isArray(data)
2993			? new ArrayObservable(ns, data)
2994			: new ObjectObservable(ns, data);
2995	};
2996
2997	//========================== Initialize ==========================
2998
2999	$sub.getDeps = function() {
3000		var args = arguments;
3001		return function() {
3002			var arg, dep,
3003				deps = [],
3004				l = args.length;
3005			while (l--) {
3006				arg = args[l--];
3007				dep = args[l];
3008				if (dep) {
3009					deps = deps.concat($isFunction(dep) ? dep(arg, arg) : dep);
3010				}
3011			}
3012			return deps;
3013		};
3014	};
3015
3016	$.observable = $observable;
3017	$observable._fltr = function(allPath, object, parentObs, filter) {
3018		if (filter && $isFunction(filter)
3019			? filter(allPath, object, parentObs)
3020			: true // TODO Consider supporting filter being a string or strings to do RegEx filtering based on key and/or allPath
3021		) {
3022			object = $isFunction(object)
3023				? object.set && object.call(parentObs[0]) // It is a getter/setter
3024				: object;
3025			return typeof object === OBJECT && object;
3026		}
3027	};
3028
3029	$observable.Object = ObjectObservable;
3030	$observable.Array = ArrayObservable;
3031	$.observe = $observable.observe = $observe;
3032	$.unobserve = $observable.unobserve = $unobserve;
3033	$observable._apply = observe_apply;
3034
3035	ObjectObservable.prototype = {
3036		_data: null,
3037
3038		observeAll: $observeAll,
3039		unobserveAll: $unobserveAll,
3040
3041		data: function() {
3042			return this._data;
3043		},
3044
3045		setProperty: function(path, value, nonStrict) {
3046			path = path || "";
3047			var key, pair, parts,
3048				multi = path + "" !== path,
3049				self = this,
3050				object = self._data;
3051
3052			if (object) {
3053				if (multi) {
3054					nonStrict = value;
3055					if ($isArray(path)) {
3056						// This is the array format generated by serializeArray. However, this has the problem that it coerces types to string,
3057						// and does not provide simple support of convertTo and convertFrom functions.
3058						key = path.length;
3059						while (key--) {
3060							pair = path[key];
3061							self.setProperty(pair.name, pair.value, nonStrict === undefined || nonStrict); //If nonStrict not specified, default to true;
3062						}
3063					} else {
3064						// Object representation where property name is path and property value is value.
3065						for (key in path) {
3066							self.setProperty(key, path[key], nonStrict);
3067						}
3068					}
3069				} else if (path !== $expando) {
3070					// Simple single property case.
3071					parts = path.split(/[.^]/);
3072					while (object && parts.length > 1) {
3073						object = object[parts.shift()];
3074					}
3075					if (object) {
3076						self._setProperty(object, parts[0], value, nonStrict);
3077					}
3078				}
3079			}
3080			return self;
3081		},
3082
3083		removeProperty: function(path) {
3084			this.setProperty(path, remove);
3085			return this;
3086		},
3087
3088		_setProperty: function(leaf, path, value, nonStrict) {
3089			var setter, getter, removeProp,
3090				property = path ? leaf[path] : leaf;
3091
3092			if ($isFunction(property)) {
3093				if (property.set) {
3094					// Case of property setter/getter - with convention that property is getter and property.set is setter
3095					leaf = leaf._wrp  // Case of JsViews 2-way data-linking to a helper function as getter, with a setter.
3096						// The view will be the this pointer for getter and setter. Note: this is the one scenario where path is "".
3097						|| leaf;
3098					getter = property;
3099					setter = getter.set === true ? getter : getter.set;
3100					property = getter.call(leaf); // get - only treated as getter if also a setter. Otherwise it is simply a property of type function. See unit tests 'Can observe properties of type function'.
3101				}
3102			}
3103
3104			if (property !== value || nonStrict && property != value) { // Optional non-strict equality, since serializeArray, and form-based editors can map numbers to strings, etc.
3105				// Date objects don't support != comparison. Treat as special case.
3106				if (!(property instanceof Date) || property > value || property < value) {
3107					if (setter) {
3108						setter.call(leaf, value);   //set
3109						value = getter.call(leaf);  //get updated value
3110					} else if (removeProp = value === remove) {
3111						if (property !== undefined) {
3112							delete leaf[path];
3113							value = undefined;
3114						} else {
3115							path = undefined; // If value was already undefined, don't trigger handler for removeProp
3116						}
3117					} else if (path) {
3118						leaf[path] = value;
3119					}
3120					if (path) {
3121						this._trigger(leaf, {change: "set", path: path, value: value, oldValue: property, remove: removeProp});
3122					}
3123				}
3124			}
3125		},
3126
3127		_trigger: function(target, eventArgs) {
3128			$(target).triggerHandler(propertyChangeStr + (this._ns ? "." + /^\S+/.exec(this._ns)[0] : ""), eventArgs); // If white-space separated namespaces, use first one only
3129		}
3130	};
3131
3132	ArrayObservable.prototype = {
3133		_data: null,
3134
3135		observeAll: $observeAll,
3136		unobserveAll: $unobserveAll,
3137
3138		data: function() {
3139			return this._data;
3140		},
3141
3142		insert: function(index, data) {
3143			var _data = this._data;
3144			if (arguments.length === 1) {
3145				data = index;
3146				index = _data.length;
3147			}
3148			index = PARSEINT(index);
3149			if (index > -1) {
3150				data = $isArray(data) ? data : [data];
3151				// data can be a single item (including a null/undefined value) or an array of items.
3152				// Note the provided items are inserted without being cloned, as direct references to the provided objects
3153
3154				if (data.length) {
3155					this._insert(index, data);
3156				}
3157			}
3158			return this;
3159		},
3160
3161		_insert: function(index, data) {
3162			var _data = this._data,
3163				oldLength = _data.length;
3164			if (index > oldLength) {
3165				index = oldLength;
3166			}
3167			splice.apply(_data, [index, 0].concat(data));
3168			this._trigger({change: "insert", index: index, items: data}, oldLength);
3169		},
3170
3171		remove: function(index, numToRemove) {
3172			var items,
3173				_data = this._data;
3174
3175			if (index === undefined) {
3176				index = _data.length - 1;
3177			}
3178
3179			index = PARSEINT(index);
3180			numToRemove = numToRemove ? PARSEINT(numToRemove) : numToRemove === 0 ? 0 : 1; // if null or undefined: remove 1
3181			if (numToRemove > 0 && index > -1) {
3182				items = _data.slice(index, index + numToRemove);
3183				if (numToRemove = items.length) {
3184					this._remove(index, numToRemove, items);
3185				}
3186			}
3187			return this;
3188		},
3189
3190		_remove: function(index, numToRemove, items) {
3191			var _data = this._data,
3192				oldLength = _data.length;
3193
3194			_data.splice(index, numToRemove);
3195			this._trigger({change: "remove", index: index, items: items}, oldLength);
3196		},
3197
3198		move: function(oldIndex, newIndex, numToMove) {
3199			numToMove = numToMove ? PARSEINT(numToMove) : numToMove === 0 ? 0 : 1; // if null or undefined: move 1
3200			oldIndex = PARSEINT(oldIndex);
3201			newIndex = PARSEINT(newIndex);
3202
3203			if (numToMove > 0 && oldIndex > -1 && newIndex > -1 && oldIndex !== newIndex) {
3204				this._move(oldIndex, newIndex, numToMove);
3205			}
3206			return this;
3207		},
3208
3209		_move: function(oldIndex, newIndex, numToMove) {
3210			var items,
3211				_data = this._data,
3212				oldLength = _data.length,
3213				excess = oldIndex + numToMove - oldLength;
3214			if (excess > 0) {
3215				numToMove -= excess;
3216			}
3217			if (numToMove) {
3218				items = _data.splice(oldIndex, numToMove); // remove
3219				if (newIndex > _data.length) {
3220					newIndex = _data.length;
3221				}
3222				splice.apply(_data, [newIndex, 0].concat(items)); //re-insert
3223				this._trigger({change: "move", oldIndex: oldIndex, index: newIndex, items: items}, oldLength);
3224			}
3225		},
3226
3227		refresh: function(newItems, sort) {
3228			function insertAdded() {
3229				if (k) {
3230					self.insert(j-k, addedItems); // Not found in original array - so insert
3231					dataLength += k;
3232					i += k;
3233					k = 0;
3234					addedItems = [];
3235				}
3236			}
3237
3238			// For refresh operation we iteratively step through the target array and sort by move/add/remove operations on the source array until they match
3239			var i, j, k, newItem, num,
3240				self = this,
3241				addedItems = [],
3242				data = self._data,
3243				oldItems = data.slice(),
3244				oldLength = data.length,
3245				dataLength = oldLength,
3246				newLength = newItems.length;
3247			self._srt = true; // Flag for sorting during refresh
3248			for (j=k=0; j<newLength; j++) {
3249				if ((newItem = newItems[j]) === data[j-k]) {
3250						insertAdded();
3251				} else {
3252					for (i=j-k; i<dataLength; i++) {
3253						if (newItem === data[i]) {
3254							break;
3255						}
3256					}
3257					if (i<dataLength) {
3258						insertAdded();
3259						num = 0;
3260						while (num++ < newLength-i && newItems[j+num] === data[i+num]);
3261						self.move(i, j, num); // Found newItem in original array - so move it to new position
3262						j += num - 1;
3263					} else {
3264						k++;
3265						addedItems.push(newItem); // Not found in original array - so insert
3266					}
3267				}
3268			}
3269			insertAdded();
3270			if (dataLength > j) {
3271				self.remove(j, dataLength - j);
3272			}
3273			self._srt = undefined; // We have finished sort operations during refresh
3274			self._trigger({change: "refresh", oldItems: oldItems}, oldLength);
3275			return self;
3276		},
3277
3278		_trigger: function(eventArgs, oldLength) {
3279			var self = this,
3280				_data = self._data,
3281				length = _data.length,
3282				$_data = $([_data]);
3283			if (self._srt) {
3284				eventArgs.refresh = true; // We are sorting during refresh
3285			} else if (length !== oldLength) {  // We have finished sort operations during refresh
3286				$_data.triggerHandler(propertyChangeStr, {change: "set", path: "length", value: length, oldValue: oldLength});
3287			}
3288			$_data.triggerHandler(arrayChangeStr + (self._ns ? "." + /^\S+/.exec(self._ns)[0] : ""), eventArgs); // If white-space separated namespaces, use first one only
3289		}
3290	};
3291
3292	$eventSpecial[propertyChangeStr] = $eventSpecial[arrayChangeStr] = {
3293		// Register a jQuery special 'remove' event, to access the data associated with handlers being removed by jQuery.off().
3294		// We get data.cb._cId from the event handleObj and get the corresponding cbBindings hash from the cbBindingsStore,
3295		// then remove this object from that bindings hash - if the object does not have any other handlers associated with the same callback.
3296		remove: function (handleObj) {
3297			var cbBindings, found, events, l, data,
3298				evData = handleObj.data;
3299			if ((evData) && (evData.off = true, evData = evData.cb)) { //Set off = true as marker for disposed event
3300				// Get the cb._cId from handleObj.data.cb._cId
3301				if (cbBindings = cbBindingsStore[evData._cId]) {
3302					// There were bindings for this callback. If this was the last one, we'll remove it.
3303					events = $._data(this).events[handleObj.type];
3304					l = events.length;
3305					while (l-- && !found) {
3306						found = (data = events[l].data) && data.cb && data.cb._cId === evData._cId;
3307						// Found another one with same callback (though may be a different innerCallback)
3308					}
3309					if (!found) {
3310						// This was the last handler for this callback and object, so remove the binding entry
3311						delete cbBindings[$data(this).obId];
3312						removeCbBindings(cbBindings, evData._cId);
3313					}
3314				}
3315			}
3316		}
3317	};
3318
3319	$views.map = function(mapDef) {
3320		function Map(source, options, target, unbound) {
3321			var changing,
3322				map = this;
3323			if (this.src) {
3324				this.unmap(); // We are re-mapping a new source
3325			}
3326			if (typeof source === OBJECT) {
3327				map.src = source;
3328				map.tgt = target || map.tgt || [];
3329				map.options = options || map.options;
3330				map.update();
3331				if (!unbound) {
3332					if (mapDef.obsSrc) {
3333						$observable(map.src).observeAll(map.obs = function(ev, eventArgs) {
3334							if (!changing) {
3335								changing = true;
3336								mapDef.obsSrc(map, ev, eventArgs);
3337								changing = undefined;
3338							}
3339						}, map.srcFlt);
3340					}
3341					if (mapDef.obsTgt ) {
3342						$observable(map.tgt).observeAll(map.obt = function(ev, eventArgs) {
3343						if (!changing) {
3344							changing = true;
3345							mapDef.obsTgt(map, ev, eventArgs);
3346							changing = undefined;
3347						}
3348						}, map.tgtFlt);
3349					}
3350				}
3351			}
3352		}
3353
3354		if ($isFunction(mapDef)) {
3355			// Simple map declared as function
3356			mapDef = {
3357				getTgt: mapDef
3358			};
3359		}
3360
3361		if (mapDef.baseMap) {
3362			mapDef = $.extend({}, mapDef.baseMap, mapDef);
3363		}
3364
3365		mapDef.map = function(source, options, target, unbound) {
3366			return new Map(source, options, target, unbound);
3367		};
3368
3369		(Map.prototype = {
3370			srcFlt: mapDef.srcFlt || shallowFilter, // default to shallowFilter
3371			tgtFlt: mapDef.tgtFlt || shallowFilter,
3372			update: function(options) {
3373				var map = this;
3374				$observable(map.tgt).refresh(mapDef.getTgt(map.src, map.options = options || map.options));
3375			},
3376			unmap: function() {
3377				var map = this;
3378				if (map.src) {
3379					if (map.obs) {
3380						$observable(map.src).unobserveAll(map.obs, map.srcFlt);
3381					}
3382					if (map.obt) {
3383						$observable(map.tgt).unobserveAll(map.obt, map.tgtFlt);
3384					}
3385					map.src = undefined;
3386				}
3387			},
3388			map: Map,
3389			_def: mapDef
3390		}).constructor = Map;
3391
3392		return mapDef;
3393	};
3394
3395	$sub.advSet = function() { // refresh advanced settings
3396		global._jsv = $subSettings.advanced._jsv
3397			? { // create global _jsv, for accessing views, etc
3398					cbBindings: cbBindingsStore
3399				}
3400			: undefined; // In IE8 cannot do delete global._jsv
3401	};
3402}
3403
3404//<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< JsViews >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
3405/* JsViews:
3406 * Interactive data-driven views using templates and data-linking.
3407 * See http://www.jsviews.com/#jsviews and http://github.com/BorisMoore/jsviews
3408 * Copyright 2016, Boris Moore
3409 * Released under the MIT License.
3410 */
3411
3412//========================== Top-level vars ==========================
3413
3414$viewsSettings = $views.settings;
3415$subSettings = $sub.settings;
3416$subSettingsAdvanced = $subSettings.advanced;
3417$converters = $views.converters;
3418$.templates = $templates = $views.templates;
3419$tags = $views.tags;
3420rFirstElem = /<(?!script)(\w+)[>\s]/;
3421
3422if ($.link) { return $; } // JsViews is already loaded
3423
3424$subSettings.trigger = true;
3425var activeBody, rTagDatalink, $view, $viewsLinkAttr, linkViewsSel, wrapMap, viewStore, oldAdvSet,
3426	jsvAttrStr = "data-jsv",
3427	elementChangeStr = "change.jsv",
3428	onBeforeChangeStr = "onBeforeChange",
3429	onAfterChangeStr = "onAfterChange",
3430	onAfterCreateStr = "onAfterCreate",
3431	CHECKED = "checked",
3432	CHECKBOX = "checkbox",
3433	RADIO = "radio",
3434	RADIOINPUT = "input[type=radio]",
3435	NONE = "none",
3436	SCRIPT = "SCRIPT",
3437	TRUE = "true",
3438	closeScript = '"></script>',
3439	openScript = '<script type="jsv',
3440	deferAttr = jsvAttrStr + "-df",
3441	bindElsSel = "script,[" + jsvAttrStr + "]",
3442	fnSetters = {
3443		value: "val",
3444		input: "val",
3445		html: HTML,
3446		text: "text"
3447	},
3448	valueBinding = {from: "value", to: "value"},
3449	isCleanCall = 0,
3450	oldCleanData = $.cleanData,
3451	oldJsvDelimiters = $viewsSettings.delimiters,
3452	safeFragment = document.createDocumentFragment(),
3453	qsa = document.querySelector,
3454
3455	// elContent maps tagNames which have only element content, so may not support script nodes.
3456	elContent = {ol: 1, ul: 1, table: 1, tbody: 1, thead: 1, tfoot: 1, tr: 1, colgroup: 1, dl: 1, select: 1, optgroup: 1, svg: 1, svg_ns: 1},
3457	badParent = {tr: "table"},
3458	voidElems = {br: 1, img: 1, input: 1, hr: 1, area: 1, base: 1, col: 1, link: 1, meta: 1,
3459		command: 1, embed: 1, keygen: 1, param: 1, source: 1, track: 1, wbr: 1},
3460	displayStyles = {},
3461	bindingStore = {},
3462	bindingKey = 1,
3463	rViewPath = /^#(view\.?)?/,
3464	rConvertMarkers = /((\/>)|<\/(\w+)>|)(\s*)([#\/]\d+(?:_|(\^)))`(\s*)(<\w+(?=[\s\/>]))?|\s*(?:(<\w+(?=[\s\/>]))|<\/(\w+)>(\s*)|(\/>)\s*|(>)|$)/g,
3465	rOpenViewMarkers = /(#)()(\d+)(_)/g,
3466	rOpenMarkers = /(#)()(\d+)([_^])/g,
3467	rViewMarkers = /(?:(#)|(\/))(\d+)(_)/g,
3468	rOpenTagMarkers = /(#)()(\d+)(\^)/g,
3469	rMarkerTokens = /(?:(#)|(\/))(\d+)([_^])([-+@\d]+)?/g,
3470	rSplitBindings = /&(\d+)\+?/g,
3471	getComputedStyle = global.getComputedStyle;
3472
3473$observable = $.observable;
3474
3475if (!$observable) {
3476	// JsObservable is not loaded.
3477	throw requiresStr + "JsObservable"; // jquery.observable.js must be loaded before JsViews
3478}
3479
3480$observe = $observable.observe;
3481
3482//========================== Top-level functions ==========================
3483
3484//===============
3485// Event handlers
3486//===============
3487
3488function elemChangeHandler(ev, params, sourceValue) {
3489	var setter, cancel, fromAttr, linkCtx, cvtBack, cnvtName, target, $source, view, binding, oldLinkCtx, onBeforeChange, onAfterChange, tag, to, eventArgs, exprOb,
3490		source = ev.target,
3491		bindings = source._jsvBnd;
3492
3493	// _jsvBnd is a string with the syntax: "&bindingId1&bindingId2"
3494	if (bindings) {
3495		while (binding = rSplitBindings.exec(bindings)) {
3496			if (binding = bindingStore[binding[1]]) {
3497				if (to = binding.to) {
3498					// The binding has a 'to' field, which is of the form [[targetObject, toPath], cvtBack]
3499					linkCtx = binding.linkCtx;
3500					view = linkCtx.view;
3501					tag = linkCtx.tag || view.tag;
3502					$source = $(source);
3503					onBeforeChange = view.hlp(onBeforeChangeStr); // TODO Can we optimize this and other instances of same?
3504					onAfterChange = view.hlp(onAfterChangeStr); // TODO Can we optimize this and other instances of same
3505					fromAttr = defaultAttr(source);
3506					setter = fnSetters[fromAttr];
3507					if (sourceValue === undefined) {
3508						sourceValue = $isFunction(fromAttr)
3509							? fromAttr(source)
3510							: setter
3511								? $source[setter]()
3512								: $source.attr(fromAttr);
3513					}
3514					cnvtName = to[1];
3515					to = to[0]; // [object, path]
3516					to = to + "" === to ? [linkCtx.data, to] : to;
3517					if (cnvtName) {
3518						if ($isFunction(cnvtName)) {
3519							cvtBack = cnvtName;
3520						} else {
3521							cvtBack = view.getRsc("converters", cnvtName);
3522						}
3523					}
3524					if (linkCtx.elem.nodeName === "SELECT") {
3525						linkCtx.elem._jsvSel = sourceValue = sourceValue || (linkCtx.elem.multiple ? [] : sourceValue);
3526						// data-link <select>
3526 to string or (multiselect) array of strings
3527					}
3528					if (cvtBack) {
3529						sourceValue = cvtBack.call(tag, sourceValue);
3530					}
3531
3532					// Set linkCtx on view, dynamically, just during this handler call
3533					oldLinkCtx = view.linkCtx;
3534					view.linkCtx = linkCtx;
3535					eventArgs = {
3536						change: "change",
3537						oldValue: linkCtx._val,
3538						value: sourceValue
3539					};
3540					if ((!onBeforeChange || !(cancel = onBeforeChange.call(linkCtx, ev, eventArgs) === false)) &&
3541							(!tag || !tag.onBeforeChange || !(cancel = tag.onBeforeChange(ev, eventArgs) === false)) &&
3542							sourceValue !== undefined) {
3543						target = to[0]; // [object, path]
3544						if (sourceValue !== undefined && target) {
3545							if (target._jsv) {
3546								exprOb = target;
3547								target = linkCtx.data;
3548								while (exprOb && exprOb.sb) {
3549									target = linkCtx._ctxCb(exprOb, target);
3550									exprOb = exprOb.sb;
3551								}
3552							}
3553							if (tag) {
3554								tag._.chging = true; // marker to prevent tag change event triggering its own refresh
3555							}
3556							$observable(target).setProperty(to[1], sourceValue); // 2way binding change event - observably updating bound object
3557							if (onAfterChange) {
3558								onAfterChange.call(linkCtx, ev, eventArgs);
3559							}
3560							if (tag) {
3561								if (tag.onAfterChange) {
3562									tag.onAfterChange(ev, eventArgs);
3563								}
3564								tag._.chging = undefined; // clear the marker
3565							}
3566							linkCtx._val = sourceValue;
3567						}
3568					}
3569					view.linkCtx = oldLinkCtx;
3570				}
3571			}
3572		}
3573	}
3574}
3575
3576function propertyChangeHandler(ev, eventArgs, linkFn) {
3577	var attr, sourceValue, noUpdate, forceUpdate, hasError, onError,
3578		linkCtx = this,
3579		tag = linkCtx.tag,
3580		source = linkCtx.data,
3581		target = linkCtx.elem,
3582		cvt = linkCtx.convert,
3583		parentElem = target.parentNode,
3584		view = linkCtx.view,
3585		oldLinkCtx = view.linkCtx,
3586		onEvent = view.hlp(onBeforeChangeStr);
3587
3588	// Set linkCtx on view, dynamically, just during this handler call
3589	view.linkCtx = linkCtx;
3590
3591	if (parentElem && (!onEvent || !(eventArgs && onEvent.call(linkCtx, ev, eventArgs) === false))
3592			// If data changed, the ev.data is set to be the path. Use that to filter the handler action...
3593			&& !(eventArgs && ev.data.prop !== "*" && ev.data.prop !== eventArgs.path)) {
3594
3595		if (eventArgs) {
3596			linkCtx.eventArgs = eventArgs;
3597		}
3598		if (eventArgs || linkCtx._toLk) {
3599			// If eventArgs are defined, this is a data update
3600			// Otherwise this is the initial data-link rendering call. Bind on this the first time it get called
3601			linkCtx._toLk = 0; // Remove flag to skip unneccessary rebinding next time
3602			if (linkFn._er) {
3603				// data-link="exprUsingTagOrCvt with onerror=..." - e.g. {tag ... {cvt:... {:... convert='cvt'
3604				try {
3605					sourceValue = linkFn(source, view);
3606				} catch (e) {
3607					hasError = linkFn._er;
3608					onError = onRenderError(e,view,(new Function("data,view", "return " + hasError + ";"))(source, view));
3609					sourceValue = [{props: {}, args: [onError]}];
3610				}
3611			} else {
3612				sourceValue = linkFn(source, view, $sub);
3613			}
3614			// Compiled link expression for linkTag: return value for data-link="{:xxx}" with no cvt or cvtBk, otherwise tagCtx or tagCtxs
3615
3616			attr = getTargetVal(sourceValue, linkCtx, tag = linkCtx.tag,
3617					linkCtx.attr || defaultAttr(target, true, cvt !== undefined)
3618				);
3619
3620			if (tag) {
3621				// Existing tag instance
3622				forceUpdate = hasError || tag._er;
3623				// If the new tagCtxs hasError or the previous tagCtxs had error, then force update
3624				sourceValue = sourceValue[0] ? sourceValue : [sourceValue];
3625				noUpdate = !forceUpdate && eventArgs && tag.onUpdate && tag.onUpdate(ev, eventArgs, sourceValue) === false;
3626
3627				mergeCtxs(tag, sourceValue, forceUpdate);
3628
3629				if (noUpdate || attr === NONE) {
3630					// onUpdate returned false, or attr === "none", or this is an update coming from the tag's own change event
3631					// - so don't refresh the tag: we just use the new tagCtxs merged from the sourceValue,
3632					// (which may optionally have been modifed in onUpdate()...) and then bind, and we are done
3633					callAfterLink(tag, ev, eventArgs);
3634					observeAndBind(linkCtx, source, target);
3635					view.linkCtx = oldLinkCtx;
3636					return;
3637				}
3638				if (tag._.chging) {
3639					return;
3640				}
3641
3642				if (tag.onUnbind) {
3643					tag.onUnbind(tag.tagCtx, linkCtx, tag.ctx, ev, eventArgs);
3644				}
3645
3646				sourceValue = tag.tagName === ":" // Call convertVal if it is a {{cvt:...}} - otherwise call renderTag
3647					? $sub._cnvt(tag.cvt, view, sourceValue[0])
3648					: $sub._tag(tag, view, view.tmpl, sourceValue, true, onError);
3649			} else if (linkFn._tag) {
3650				// For {{: ...}} without a convert or convertBack, we already have the sourceValue, and we are done
3651				// For {{: ...}} with either cvt or cvtBack we call convertVal to get the sourceValue and instantiate the tag
3652				// If cvt is undefined then this is a tag, and we call renderTag to get the rendered content and instantiate the tag
3653				cvt = cvt === "" ? TRUE : cvt; // If there is a cvtBack but no cvt, set cvt to "true"
3654				sourceValue = cvt // Call convertVal if it is a {{cvt:...}} - otherwise call renderTag
3655					? $sub._cnvt(cvt, view, sourceValue[0] || sourceValue) // convertVal
3656					: $sub._tag(linkFn._tag, view, view.tmpl, sourceValue, true, onError); // renderTag
3657
3658				addLinkMethods(tag = linkCtx.tag, true); // In both convertVal and renderTag we have instantiated a tag
3659				attr = linkCtx.attr || attr; // linkCtx.attr may have been set to tag.attr during tag instantiation in renderTag
3660			}
3661
3662			if (updateContent(sourceValue, linkCtx, attr, tag)
3663					&& eventArgs
3664					&& (onEvent = view.hlp(onAfterChangeStr))) {
3665				onEvent.call(linkCtx, ev, eventArgs);
3666			}
3667			linkCtx._noUpd = 0; // For data-link="^{...}" remove _noUpd flag so updates on subsequent calls
3668
3669			if (tag) {
3670				tag._er = hasError;
3671				callAfterLink(tag, ev, eventArgs);
3672			}
3673		}
3674
3675		observeAndBind(linkCtx, source, target);
3676
3677		// Remove dynamically added linkCtx from view
3678		view.linkCtx = oldLinkCtx;
3679	}
3680}
3681
3682function getTargetVal(sourceValue, linkCtx, tag, attr) {
3683	var currentValue, setter, css, $target,
3684		target = tag && tag.parentElem || linkCtx.elem;
3685
3686	if (sourceValue !== undefined) {
3687		$target = $(target);
3688		attr = tag && tag.attr || attr;
3689		if ($isFunction(sourceValue)) {
3690			error(linkCtx.expr + ": missing parens");
3691		}
3692
3693		if (css = /^css-/.test(attr) && attr.slice(4)) {
3694			currentValue = $.style(target, css);
3695			if (+sourceValue === sourceValue) {
3696				// Optimization for perf on integer values - e.g. css-width{:width+'px'}
3697				currentValue = parseInt(currentValue);
3698			}
3699		} else if (attr !== "link") { // attr === "link" is for tag controls which do data binding but have no rendered output or target
3700			if (attr === "value") {
3701				if (target.type === CHECKBOX) {
3702					currentValue = $target.prop(attr = CHECKED);
3703				}
3704			} else if (attr === RADIO) {
3705				if (target.value === ("" + sourceValue)) {
3706					currentValue = $target.prop(CHECKED);
3707				} else {
3708					return attr;
3709				}
3710			}
3711
3712			if (currentValue === undefined) {
3713				setter = fnSetters[attr];
3714				currentValue = setter ? $target[setter]() : $target.attr(attr);
3715			}
3716		}
3717		linkCtx._val = currentValue;
3718	}
3719	return attr;
3720}
3721
3722function setDefer(elem, value) {
3723	elem._df = value; // Use both an expando and an attribute to track defered tokens. Attribute is needed for querySelectorAll for getViewInfos (childTags)
3724	elem[(value ? "set" : "remove") + "Attribute"](deferAttr, "");
3725}
3726
3727function updateContent(sourceValue, linkCtx, attr, tag) {
3728	// When called for a tag, either in tag.refresh() or propertyChangeHandler(), returns a promise (and supports async)
3729	// When called (in propertyChangeHandler) for target HTML returns true
3730	// When called (in propertyChangeHandler) for other targets returns boolean for "changed"
3731	var setter, prevNode, nextNode, promise, nodesToRemove, useProp, tokens, id, openIndex, closeIndex, testElem, nodeName, cStyle, jsvSel,
3732		renders = attr !== NONE && sourceValue !== undefined && !linkCtx._noUpd, // For data-link="^{...}", don't update the first time (no initial render) - e.g. to leave server rendered values.
3733		source = linkCtx.data,
3734		target = tag && tag.parentElem || linkCtx.elem,
3735		targetParent = target.parentNode,
3736		$target = $(target),
3737		view = linkCtx.view,
3738		targetVal = linkCtx._val,
3739		oldLinkCtx = view.linkCtx,
3740		// If not a tag we can use the ._val obtained from getTargetVal()
3741		// and only update when the new value (sourceValue) has changed from the previous one
3742		change = tag;
3743
3744	if (tag) {
3745		// Initialize the tag with element references
3746		tag._.unlinked = true; // Set to unlinked, so initialization is triggered after re-rendering, e.g. for setting linkedElem, and calling onBind
3747		tag.parentElem = tag.parentElem || (linkCtx.expr || tag._elCnt) ? target : targetParent;
3748		prevNode = tag._prv;
3749		nextNode = tag._nxt;
3750	}
3751	if (!renders) {
3752		return;
3753	}
3754
3755	if (attr === "visible") {
3756		attr = "css-display";
3757	}
3758	if (/^css-/.test(attr)) {
3759		if (linkCtx.attr === "visible") {
3760			// Get the current display style
3761			cStyle = (target.currentStyle || getComputedStyle.call(global, target, "")).display;
3762
3763			if (sourceValue) {
3764				// We are showing the element.
3765				// Get the cached 'visible' display value from the -jsvd expando
3766				sourceValue = target._jsvd
3767					// Or, if not yet cached, get the current display value
3768					|| cStyle;
3769				if (sourceValue === NONE && !(sourceValue = displayStyles[nodeName = target.nodeName])) {
3770					// Currently display value is 'none', and the 'visible' style has not been cached.
3771					// We create an element to find the correct 'visible' display style for this nodeName
3772					testElem = document.createElement(nodeName);
3773					document.body.appendChild(testElem);
3774
3775					// Get the default style for this HTML tag to use as 'visible' style
3776					sourceValue
3777						// and cache it as a hash against nodeName
3778						= displayStyles[nodeName]
3779						= (testElem.currentStyle || getComputedStyle.call(global, testElem, "")).display;
3780					document.body.removeChild(testElem);
3781				}
3782			} else {
3783				// We are hiding the element.
3784				// Cache the current display value as 'visible' style, on _jsvd expando, for when we show the element again
3785				target._jsvd = cStyle;
3786				sourceValue = NONE; // Hide the element
3787			}
3788		}
3789		if (change = change || targetVal !== sourceValue) {
3790			$.style(target, attr.slice(4), sourceValue);
3791		}
3792	} else if (attr !== "link") { // attr === "link" is for tag controls which do data binding but have no rendered output or target
3793		if (/^data-/.test(attr)) {
3794			$.data(target, attr.slice(5), sourceValue);
3794 // Support for binding to data attributes: data-foo{:expr}: data-foo attribute will be
3795			// expr.toString(), but $.data(element, "foo") and $(element).data("foo") will actually return value of expr, even if of type object
3796		}
3797		if (attr === CHECKED) {
3798			useProp = true;
3799			sourceValue = sourceValue && sourceValue !== "false";
3800			// The string value "false" can occur with data-link="checked{attr:expr}" - as a result of attr, and hence using convertVal()
3801			// We will set the "checked" property
3802			// We will compare this with the current value
3803		} else if (attr === RADIO) {
3804			// This is a special binding attribute for radio buttons, which corresponds to the default 'to' binding.
3805			// This allows binding both to value (for each input) and to the default checked radio button (for each input in named group,
3806			// e.g. binding to parent data).
3807			// Place value binding first: <input type="radio" data-link="value{:name} {:#get('data').data.currency:} " .../>
3808			// or (allowing any order for the binding expressions):
3809			// <input type="radio" value="{{:name}}" data-link="{:#get('data').data.currency:} value^{:name}" .../>
3810
3811			if (target.value === ("" + sourceValue)) {
3812				// If the data value corresponds to the value attribute of this radio button input, set the checked property to true
3813				sourceValue = useProp = true;
3814				attr = CHECKED;
3815			} else {
3816				// Otherwise, go straight to observeAndBind, without updating.
3817				// (The browser will remove the 'checked' attribute, when another radio button in the group is checked).
3818				observeAndBind(linkCtx, source, target);
3819				return;
3820			}
3821		} else if (attr === "selected" || attr === "disabled" || attr === "multiple" || attr === "readonly") {
3822			sourceValue = (sourceValue && sourceValue !== "false") ? attr : null;
3823			// Use attr, not prop, so when the options (for example) are changed dynamically, but include the previously selected value,
3824			// they will still be selected after the change
3825		} else if (attr === "value" && target.nodeName === "SELECT") {
3826			target._jsvSel = $isArray(sourceValue)
3827				? sourceValue
3828				: "" + sourceValue; // If not array, coerce to string
3829		}
3830
3831		if (setter = fnSetters[attr]) {
3832			if (attr === HTML) {
3833				// Set linkCtx on view, dynamically, just during this handler call
3834				view.linkCtx = linkCtx;
3835				if (tag && tag._.inline) {
3836					nodesToRemove = tag.nodes(true);
3837					if (tag._elCnt) {
3838						if (prevNode && prevNode !== nextNode) {
3839							// This prevNode will be removed from the DOM, so transfer the view tokens on prevNode to nextNode of this 'viewToRefresh'
3840							transferViewTokens(prevNode, nextNode, target, tag._tgId, "^", true);
3841						} else if (tokens = target._df) { // This occurs when there is no nextNode, and so the target._df may include tokens referencing
3842							// view and tag bindings contained within the open and close tokens of the updated tag control. They need to be processed (disposed)
3843							id = tag._tgId + "^";
3844							openIndex = tokens.indexOf("#" + id) + 1;
3845							closeIndex = tokens.indexOf("/" + id);
3846
3847							if (openIndex && closeIndex > 0) {
3848								openIndex += id.length;
3849								if (closeIndex > openIndex) {
3850									setDefer(target, tokens.slice(0, openIndex) + tokens.slice(closeIndex));
3851									disposeTokens(tokens.slice(openIndex, closeIndex));
3852								}
3853							}
3854						}
3855						prevNode = prevNode
3856							? prevNode.previousSibling
3857							: nextNode
3858								? nextNode.previousSibling
3859								: target.lastChild;
3860					}
3861					// Remove HTML nodes
3862					$(nodesToRemove).remove(); // Note if !tag._elCnt removing the nodesToRemove will process and dispose view and tag bindings contained within the updated tag control
3863
3864					// Insert and link new content
3865					promise = view.link(view.data, target, prevNode, nextNode, sourceValue, tag && {tag: tag._tgId, lazyLink: tag.tagCtx.props.lazyLink});
3866				} else {
3867					// data-linked value targeting innerHTML: data-link="html{:expr}" or contenteditable="true"
3868					renders = renders && targetVal !== sourceValue;
3869					if (renders) {
3870						$target.empty();
3871					}
3872					if (renders) {
3873						promise = view.link(source, target, prevNode, nextNode, sourceValue, tag && {tag: tag._tgId});
3874					}
3875				}
3876				// Remove dynamically added linkCtx and ctx from view
3877				view.linkCtx = oldLinkCtx;
3878			} else {
3879				if (change = change || targetVal !== sourceValue) {
3880					if (attr === "text" && target.children && !target.children[0]) {
3881						// This code is faster then $target.text()
3882						if (target.textContent !== undefined) {
3883							target.textContent = sourceValue;
3884						} else {
3885							target.innerText = sourceValue === null ? "" : sourceValue;
3886						}
3887					} else {
3888						$target[setter](sourceValue);
3889					}
3890				}
3891				if ((jsvSel = targetParent._jsvSel)
3892					// Setting value of <option> element
3893					&& (attr === "value" || !$target.attr("value"))) { // Setting value attribute, or setting textContent if attribute is null
3894					// Set/unselect selection based on value set on parent <select>. Works for multiselect too
3895					target.selected = $.inArray("" + sourceValue, $isArray(jsvSel) ? jsvSel : [jsvSel]) > -1;
3896				}
3897			}
3898		} else if (change = change || targetVal !== sourceValue) {
3899			// Setting an attribute to undefined should remove the attribute
3900			$target[useProp ? "prop" : "attr"](attr, sourceValue === undefined && !useProp ? null : sourceValue);
3901		}
3902		linkCtx._val = sourceValue;
3903	}
3904	return promise || change;
3905}
3906
3907function arrayChangeHandler(ev, eventArgs) {
3908	var self = this,
3909		onBeforeChange = self.hlp(onBeforeChangeStr),
3910		onAfterChange = self.hlp(onAfterChangeStr);
3911	if (!onBeforeChange || onBeforeChange.call(this, ev, eventArgs) !== false) {
3912		if (eventArgs) {
3913			// This is an observable action (not a trigger/handler call from pushValues, or similar, for which eventArgs will be null)
3914			var action = eventArgs.change,
3915				index = eventArgs.index,
3916				items = eventArgs.items;
3917			self._.srt = eventArgs.refresh; // true if part of a 'sort' on refresh
3918			switch (action) {
3919				case "insert":
3920					self.addViews(index, items);
3921					break;
3922				case "remove":
3923					self.removeViews(index, items.length);
3924					break;
3925				case "move":
3926					self.removeViews(eventArgs.oldIndex, items.length, undefined, true); // remove
3927					self.addViews(index, items); // re-insert
3928					break;
3929				case "refresh":
3930					self._.srt = undefined;
3931					self.fixIndex(0);
3932					// Other cases: (e.g.undefined, for setProperty on observable object) etc. do nothing
3933			}
3934		}
3935		if (onAfterChange) {
3936			onAfterChange.call(this, ev, eventArgs);
3937		}
3938	}
3939}
3940
3941//=============================
3942// Utilities for event handlers
3943//=============================
3944
3945function setArrayChangeLink(view) {
3946	// Add/remove arrayChange handler on view
3947	var handler, arrayBinding,
3948		type = view.type, // undefined if view is being removed
3949		data = view.data,
3950		bound = view._.bnd; // true for top-level link() or data-link="{for}", or the for tag instance for {^{for}} (or for any custom tag that has an onArrayChange handler)
3951
3952	if (!view._.useKey && bound) {
3953		// This is an array view. (view._.useKey not defined => data is array), and is data-bound to collection change events
3954
3955		if (arrayBinding = view._.bndArr) {
3956			// First remove the current handler if there is one
3957			$([arrayBinding[1]]).off(arrayChangeStr, arrayBinding[0]);
3958			view._.bndArr = undefined;
3959		}
3960		if (bound !== !!bound) {
3961			// bound is not a boolean, so it is the data-linked tag that 'owns' this array binding - e.g. {^{for...}}
3962			if (type) {
3963				bound._.arrVws[view._.id] = view;
3964			} else {
3965				delete bound._.arrVws[view._.id]; // if view.type is undefined, view is being removed
3966			}
3967		} else if (type && data) {
3968			// If this view is not being removed, but the data array has been replaced, then bind to the new data array
3969			handler = function(ev) {
3970				if (!(ev.data && ev.data.off)) {
3971					// Skip if !!ev.data.off: - a handler that has already been removed (maybe was on handler collection at call time - then removed by another handler)
3972					// If view.type is undefined, do nothing. (Corresponds to case where there is another handler on the same data whose
3973					// effect was to remove this view, and which happened to precede this event in the trigger sequence. So although this
3974					// event has been removed now, it is still called since already on the trigger sequence)
3975					arrayChangeHandler.apply(view, arguments);
3976				}
3977			};
3978			$([data]).on(arrayChangeStr, handler);
3979			view._.bndArr = [handler, data];
3980		}
3981	}
3982}
3983
3984function defaultAttr(elem, to, linkGetVal) {
3985	// to: true - default attribute for setting data value on HTML element; false: default attribute for getting value from HTML element
3986	// Merge in the default attribute bindings for this target element
3987	var nodeName = elem.nodeName.toLowerCase(),
3988		attr =
3989			$subSettingsAdvanced._fe[nodeName] // get form element binding settings for input textarea select or optgroup
3990			|| elem.contentEditable === TRUE && {to: HTML, from: HTML}; // Or if contentEditable set to "true" set attr to "html"
3991	return attr
3992		? (to
3993			? ((nodeName === "input" && elem.type === RADIO) // For radio buttons, bind from value, but bind to 'radio' - special value.
3994				? RADIO
3995				: attr.to)
3996			: attr.from)
3997		: to
3998			? linkGetVal ? "text" : HTML // Default innerText for data-link="a.b.c" or data-link="{:a.b.c}" (with or without converters)- otherwise innerHTML
3999			: ""; // Default is not to bind from
4000}
4001
4002//==============================
4003// Rendering and DOM insertion
4004//==============================
4005
4006function renderAndLink(view, index, tmpl, views, data, context, refresh) {
4007	var html, linkToNode, prevView, nodesToRemove, bindId,
4008		parentNode = view.parentElem,
4009		prevNode = view._prv,
4010		nextNode = view._nxt,
4011		elCnt = view._elCnt;
4012
4013	if (prevNode && prevNode.parentNode !== parentNode) {
4014		error("Missing parentNode");
4015		// Abandon, since node has already been removed, or wrapper element has been inserted between prevNode and parentNode
4016	}
4017
4018	if (refresh) {
4019		nodesToRemove = view.nodes();
4020		if (elCnt && prevNode && prevNode !== nextNode) {
4021			// This prevNode will be removed from the DOM, so transfer the view tokens on prevNode to nextNode of this 'viewToRefresh'
4022			transferViewTokens(prevNode, nextNode, parentNode, view._.id, "_", true);
4023		}
4024		// Remove child views
4025		view.removeViews(undefined, undefined, true);
4026		linkToNode = nextNode;
4027
4028		if (elCnt) {
4029			prevNode = prevNode
4030				? prevNode.previousSibling
4031				: nextNode
4032					? nextNode.previousSibling
4033					: parentNode.lastChild;
4034		}
4035
4036		// Remove HTML nodes
4037		$(nodesToRemove).remove();
4038
4039		for (bindId in view._.bnds) {
4040			// The view bindings may have already been removed above in: $(nodesToRemove).remove();
4041			// If not, remove them here:
4042			removeViewBinding(bindId);
4043		}
4044	} else {
4045		// addViews. Only called if view is of type "array"
4046		if (index) {
4047			// index is a number, so indexed view in view array
4048			prevView = views[index - 1];
4049			if (!prevView) {
4050				return false; // If subview for provided index does not exist, do nothing
4051			}
4052			prevNode = prevView._nxt;
4053		}
4054		if (elCnt) {
4055			linkToNode = prevNode;
4056			prevNode = linkToNode
4057				? linkToNode.previousSibling         // There is a linkToNode, so insert after previousSibling, or at the beginning
4058				: parentNode.lastChild;              // If no prevView and no prevNode, index is 0 and the container is empty,
4059				// so prevNode = linkToNode = null. But if prevView._nxt is null then we set prevNode to parentNode.lastChild
4060				// (which must be before the prevView) so we insert after that node - and only link the inserted nodes
4061		} else {
4062			linkToNode = prevNode.nextSibling;
4063		}
4064	}
4065	html = tmpl.render(data, context, view._.useKey && refresh, view, refresh || index, true);
4066	// Pass in view._.useKey as test for noIteration (which corresponds to when self._.useKey > 0 and self.data is an array)
4067
4068	// Link the new HTML nodes to the data
4069	view.link(data, parentNode, prevNode, linkToNode, html, prevView);
4070//}, 0);
4071}
4072
4073//=====================
4074// addBindingMarkers
4075//=====================
4076
4077function addBindingMarkers(value, view, tag) {
4078	// Insert binding markers into the rendered template output, which will get converted to appropriate
4079	// data-jsv attributes (element-only content) or script marker nodes (phrasing or flow content), in convertMarkers,
4080	// within view.link, prior to inserting into the DOM. Linking will then bind based on these markers in the DOM.
4081	// Added view markers: #m_...VIEW.../m_
4082	// Added tag markers: #m^...TAG..../m^
4083	var id, end;
4084	if (tag) {
4085		// This is a binding marker for a data-linked tag {^{...}}
4086		end = "^`";
4087		addLinkMethods(tag, true); // This is {^{>...}} or {^{tag ...}}, {{cvt:...} or {^{:...}}, and tag was defined in convertVal or renderTag
4088		id = tag._tgId;
4089		if (!id) {
4090			bindingStore[id = bindingKey++] = tag; // Store the tag temporarily, ready for databinding.
4091			// During linking, in addDataBinding, the tag will be attached to the linkCtx,
4092			// and then in observeAndBind, bindingStore[bindId] will be replaced by binding info.
4093			tag._tgId = "" + id;
4094		}
4095	} else {
4096		// This is a binding marker for a view
4097		// Add the view to the store of current linked views
4098		end = "_`";
4099		addLinkMethods(viewStore[id = view._.id] = view);
4100	}
4101	// Example: "#23^TheValue/23^"
4102	return "#" + id + end
4103		+ (value != undefined ? value : "") // For {^{:name}} this gives the equivalent semantics to compiled
4104											// (v=data.name)!=null?v:""; used in {{:name}} or data-link="name"
4105		+ "/" + id + end;
4106}
4107
4108//==============================
4109// Data-linking and data binding
4110//==============================
4111
4112//---------------
4113// observeAndBind
4114//---------------
4115
4116function observeAndBind(linkCtx, source, target) { //TODO? linkFnArgs) {;
4117	var binding, l, linkedElem, exprFnDeps, exprOb,
4118		tag = linkCtx.tag,
4119		cvtBk = linkCtx.convertBack,
4120		depends = [],
4121		bindId = linkCtx._bndId || "" + bindingKey++,
4122		handler = linkCtx._hdl;
4123
4124	linkCtx._bndId = undefined;
4125
4126	if (tag) {
4127		// Use the 'depends' paths set on linkCtx.tag - which may have been set on declaration
4128		// or in events: init, render, onAfterLink etc.
4129		depends = tag.depends || depends;
4130		depends = $isFunction(depends) ? tag.depends(tag) : depends;
4131		linkedElem = tag.linkedElem;
4132	}
4133	if (!linkCtx._depends || ("" + linkCtx._depends !== "" + depends)) {
4134		// Only bind the first time, or if the new depends (toString) has changed from when last bound
4135		if (linkCtx._depends) {
4136			// Unobserve previous binding
4137			$observable._apply(false, [source], linkCtx._depends, handler, true);
4138		}
4139
4140		exprFnDeps = linkCtx.fn.deps.slice(); // Make a copy of the dependency paths for the compiled linkCtx expression - to pass to observe(). In getInnerCb(),
4141		// (and whenever the object is updated, in innerCb), we will set exprOb.ob to the current object returned by that computed expression, for this view.
4142		l = exprFnDeps.length;
4143		while (l--) {
4144			exprOb = exprFnDeps[l];
4145			if (exprOb._jsv) {
4146				// This path is an 'exprOb', corresponding to a computed, returning an object. We replace the exprOb by
4147				// a view-binding-specific exprOb instance. The current object will be stored as exprOb.ob.
4148				exprFnDeps[l] = $extend({}, exprOb);
4149			}
4150		}
4151
4152		binding = $observable._apply(
4153			false,
4154			[source],
4155			exprFnDeps, // flatten the paths - to gather all the dependencies across args and bound params
4156			depends,
4157			handler,
4158			linkCtx._ctxCb);
4159		// The binding returned by $observe has a bnd array with the source objects of the individual bindings.
4160
4161		binding.elem = target; // The target of all the individual bindings
4162		binding.linkCtx = linkCtx;
4163		binding._tgId = bindId;
4164
4165		// Add to the _jsvBnd on the target the view id and binding id - for unbinding when the target element is removed
4166		target._jsvBnd = target._jsvBnd || "";
4167		target._jsvBnd += "&" + bindId;
4168		linkCtx._depends = depends;
4169		// Store the binding key on the view, for disposal when the view is removed
4170		linkCtx.view._.bnds[bindId] = bindId;
4171		// Store the binding.
4172		bindingStore[bindId] = binding; // Note: If this corresponds to a data-linked tag, we are replacing the
4173		// temporarily stored tag by the stored binding. The tag will now be at binding.linkCtx.tag
4174
4175		if (linkedElem) {
4176			binding.to = [[], cvtBk];
4177		}
4178		if (linkedElem || cvtBk !== undefined) {
4179			bindTo(binding, tag, linkedElem && linkedElem[0] || target, cvtBk);
4180		}
4181		if (tag) {
4182			if (!tag.flow && !tag._.inline) {
4183				target.setAttribute(jsvAttrStr, (target.getAttribute(jsvAttrStr)||"") + "#" + bindId + "^/" + bindId + "^");
4184				tag._tgId = "" + bindId;
4185			}
4186		}
4187	}
4188	if (linkedElem && linkedElem[0]) {
4189		l = linkedElem.length;
4190		while (l--) {
4191			linkedElem[l]._jsvBnd = linkedElem[l]._jsvBnd || (target._jsvBnd + "+");
4192			// Add a "+" for cloned binding - so removing elems with cloned bindings will not remove the 'parent' binding from the bindingStore.
4193			linkedElem[l]._jsvLkEl = tag;
4194		}
4195	}
4196}
4197
4198//-------
4199// $.link
4200//-------
4201
4202function tmplLink(to, from, context, noIteration, parentView, prevNode, nextNode) {
4203	return $link(this, to, from, context, noIteration, parentView, prevNode, nextNode);
4204}
4205
4206function $link(tmplOrLinkExpr, to, from, context, noIteration, parentView, prevNode, nextNode) {
4207	// When linking from a template, prevNode and nextNode parameters are ignored
4208	if (context === true) {
4209		noIteration = context; // passing boolean as third param - noIteration
4210		context = undefined;
4211	} else if (typeof context !== "object") {
4212		context = undefined; // context must be a boolean (noIteration) or a plain object
4213	} else {
4214		context = $extend({}, context);
4215	}
4216	if (tmplOrLinkExpr && to) {
4217		to = to.jquery ? to : $(to); // to is a jquery object or an element or selector
4218
4219		if (!activeBody) {
4220			activeBody = document.body;
4221			$(activeBody)
4222				.on(elementChangeStr, elemChangeHandler)
4223				.on('blur', '[contenteditable]', elemChangeHandler);
4224		}
4225
4226		var i, k, html, vwInfos, view, placeholderParent, targetEl, refresh, topLevelCall,
4227			onRender = addBindingMarkers,
4228			replaceMode = context && context.target === "replace",
4229			l = to.length;
4230
4231		while (l--) {
4232			targetEl = to[l];
4233
4234			parentView = parentView || $view(targetEl);
4235
4236			if (topLevelCall = parentView === topView) {
4237				topView.data = (topView.ctx = context || {}).root = from;
4238			}
4239			if ("" + tmplOrLinkExpr === tmplOrLinkExpr) {
4240				// tmplOrLinkExpr is a string: treat as data-link expression.
4241				addDataBinding(tmplOrLinkExpr, targetEl, parentView, undefined, true, from, context);
4242			} else {
4243				if (tmplOrLinkExpr.markup !== undefined) {
4244					// This is a call to template.link()
4245					if (replaceMode) {
4246						placeholderParent = targetEl.parentNode;
4247					}
4248
4249					html = tmplOrLinkExpr.render(from, context, noIteration, parentView, undefined, onRender);
4250					// TODO Consider finding a way to bind data (link) within template without html being different for each view, the HTML can
4251					// be evaluated once outside the while (l--), and pushed into a document fragment, then cloned and inserted at each target.
4252
4253					if (placeholderParent) {
4254						// This is target="replace" mode
4255						prevNode = targetEl.previousSibling;
4256						nextNode = targetEl.nextSibling;
4257						$.cleanData([targetEl], true);
4258						placeholderParent.removeChild(targetEl);
4259
4260						targetEl = placeholderParent;
4261					} else {
4262						prevNode = nextNode = undefined; // When linking from a template, prevNode and nextNode parameters are ignored
4263						$(targetEl).empty();
4264					}
4265				} else if (tmplOrLinkExpr === true && parentView === topView) {
4266					// $.link(true, selector, data, ctx) - where selector points to elem in top-level content. (If not top-level content, no-op)
4267					refresh = {lnk: 1};
4268				} else {
4269					break;
4270				}
4271
4272// TODO Consider deferred linking API feature on per-template basis - {@{ instead of {^{ which allows the user to see the rendered content
4273// before that content is linked, with better perceived perf. Have view.link return a deferred, and pass that to onAfterLink...
4274// or something along those lines.
4275// setTimeout(function() {
4276
4277				if (targetEl._df && !nextNode) {
4278					// We are inserting new content and the target element has some deferred binding annotations,and there is no nextNode.
4279					// Those views may be stale views (that will be recreated in this new linking action) so we will first remove them
4280					// (if not already removed).
4281					vwInfos = viewInfos(targetEl._df, true, rOpenViewMarkers);
4282
4283					for (i = 0, k = vwInfos.length; i < k; i++) {
4284						view = vwInfos[i];
4285						if ((view = viewStore[view.id]) && view.data !== undefined) {
4286							// If this is the _prv (prevNode) for a view, remove the view
4287							// - unless view.data is undefined, in which case it is already being removed
4288							view.parent.removeViews(view._.key, undefined, true);
4289						}
4290					}
4291					setDefer(targetEl); // remove defer tokens
4292				}
4293
4294				// Link the content of the element, since this is a call to template.link(), or to $(el).link(true, ...),
4295				parentView.link(from, targetEl, prevNode, nextNode, html, refresh, context);
4296//}, 0);
4297			}
4298			if (topLevelCall) {
4299				topView.data = topView.ctx = undefined;
4300			}
4301		}
4302	}
4303	return to; // Allow chaining, to attach event handlers, etc.
4304}
4305
4306//----------
4307// view.link
4308//----------
4309
4310function viewLink(outerData, parentNode, prevNode, nextNode, html, refresh, context, validateOnly) {
4311	// Optionally insert HTML into DOM using documentFragments (and wrapping HTML appropriately).
4312	// Data-link existing contents of parentNode, or the inserted HTML, if provided
4313
4314	// Depending on the content model for the HTML elements, the standard data-linking markers inserted in the HTML by addBindingMarkers during
4315	// template rendering will be converted either to script marker nodes or, for element-only content sections, to data-jsv element annotations.
4316
4317	// Data-linking will then add _prv and _nxt to views, where:
4318	//     _prv: References the previous node (script element of type "jsv123"), or (for elCnt=true), the first element node in the view (or if none, set _prv = _nxt)
4319	//     _nxt: References the last node (script element of type "jsv/123"), or (for elCnt=true), the next element node after the view.
4320
4321	//==== nested functions ====
4322	function convertMarkers(all, preceding, selfClose, closeTag, spaceBefore, id, boundId, spaceAfter, tag1, tag2, closeTag2, spaceAfterClose, selfClose2, endOpenTag) {
4323		// rConvertMarkers = /(^|(\/>)|<\/(\w+)>|)(\s*)([#\/]\d+(?:_|(\^)))`(\s*)(<\w+(?=[\s\/>]))?|\s*(?:(<\w+(?=[\s\/>]))|<\/(\w+)>(\s*)|(\/>)\s*|(>))/g,
4324		//                 prec, slfCl, clsTag,  spBefore, id,      bndId  spAfter,tag1,                   tag2,               clTag2,sac  slfCl2, endOpenTag
4325		// Convert the markers that were included by addBindingMarkers in template output, to appropriate DOM annotations:
4326		// data-jsv attributes (for element-only content) or script marker nodes (within phrasing or flow content).
4327
4328// TODO consider detecting 'quoted' contexts (attribute strings) so that attribute encoding does not need to encode >
4329// Currently rAttrEncode = /[><"'&]/g includes '>' encoding in order to avoid erroneous parsing of <span title="&lt;a/>"></span>">
4330		var errorMsg, bndId,
4331			endOfElCnt = "";
4332		if (endOpenTag) {
4333			inTag = 0;
4334			return all;
4335		}
4336		tag = tag1 || tag2 || "";
4337		closeTag = closeTag || closeTag2;
4338		selfClose = selfClose || selfClose2;
4339		if (isVoid && !selfClose && ( !all || closeTag || tag || id && !inTag)) { // !all = end of string
4340			isVoid = undefined;
4341			parentTag = tagStack.shift(); // preceding tag was a void element, with no closing slash, such as <br>.
4342		}
4343		closeTag = closeTag || selfClose;
4344		if (closeTag) {
4345			inTag = 0;
4346			isVoid = undefined;
4347			// TODO: smart insertion of <tbody> - to be completed for robust insertion of deferred bindings etc.
4348			//if (closeTag === "table" && parentTag === "tbody") {
4349			//	preceding = "</tbody>" + preceding;
4350			//	parentTag = "table";
4351			//	tagStack.shift();
4352			//}
4353			if (validate) {
4354				if (selfClose || selfClose2) {
4355					if (!voidElems[parentTag] && !/;svg;|;math;/.test(";" + tagStack.join(";") + ";")) {
4356						// Only self-closing elements must be legitimate void elements, such as <br/>
4356, per HTML schema,
4357						// or under svg or math foreign namespace elements.
4358						errorMsg = "'<" + parentTag + ".../";
4359					}
4360				} else if (voidElems[closeTag]) {
4361					errorMsg = "'</" + closeTag; // closing tag such as </input>
4362				} else if (!tagStack.length || closeTag !== parentTag) {
4363					errorMsg = "Mismatch: '</" + closeTag;
4364				}
4365				if (errorMsg) {
4366					syntaxError(errorMsg + ">' in:\n" + html);
4367				}
4368			}
4369			prevElCnt = elCnt;
4370			parentTag = tagStack.shift();
4371			elCnt = elContent[parentTag];
4372			closeTag2 = closeTag2 ? ("</" + closeTag2 + ">") : "";
4373			if (prevElCnt) {
4374				// If there are ids (markers since the last tag), move them to the defer string
4375				defer += ids;
4376				ids = "";
4377				if (!elCnt) {
4378					endOfElCnt = closeTag2 + openScript + "@" + defer + closeScript + (spaceAfterClose || "");
4379					defer = deferStack.shift();
4380				} else {
4381					defer += "-"; // Will be used for stepping back through deferred tokens
4382				}
4383			}
4384		}
4385		if (elCnt) {
4386			// elContent maps tagNames which have only element content, so may not support script nodes.
4387			// We are in element-only content, can remove white space, and use data-jsv attributes on elements as markers
4388			// Example: <tr data-jsv="/2_#6_"> - close marker for view 2 and open marker for view 6
4389
4390			if (id) {
4391				// append marker for this id, to ids string
4392				ids += id;
4393			} else {
4394				preceding = (closeTag2 || selfClose2 || "");
4395			}
4396			if (tag) {
4397				// TODO: smart insertion of <tbody> - to be completed for robust insertion of deferred bindings etc.
4398				//if (tag === "<tr" && parentTag === "table") {
4399				//	tagStack.unshift(parentTag);
4400				//	parentTag = "tbody";
4401				//	preceding += "<" + parentTag + ">";
4402				//	if (defer) {
4403				//		defer += "+"; // Will be used for stepping back through deferred tokens
4404				//	}
4405				//	// TODO: move this to design-time validation check
4406				//	//	error('"' + parentTag + '" has incorrect parent tag');
4407				//}
4408				preceding += tag;
4409				if (ids) {
4410					preceding += ' ' + jsvAttrStr + '="' + ids + '"';
4411					ids = "";
4412				}
4413			}
4414		} else {
4415			// We are in phrasing or flow content, so use script marker nodes
4416			// Example: <script type="jsv3/"></script> - data-linked tag, close marker
4417			// We validate with inTag so no script markers are inserted in attribute context e.g. for:
4418			// "<table {{if ...}}...{{/if}}... >" or "<table {{if ...}}...> ...{{/if}}..."
4419			preceding = id
4420				? (preceding + endOfElCnt + spaceBefore + (inTag ? "" : openScript + id + closeScript)+ spaceAfter + tag)
4421				: endOfElCnt || all;
4422		}
4423
4424		if (validate && boundId) {
4425			if (inTag) {
4426				// JsViews data-linking tags are not allowed within element markup.
4427				// See jsviews/issues/303
4428				syntaxError('{^{ within elem markup (' + inTag + ' ). Use data-link="..."');
4429			}
4430			if (id.charAt(0) === "#") {
4431				tagStack.unshift(id.slice(1));
4432			} else if (id.slice(1) !== (bndId = tagStack.shift())) {
4433				// See jsviews/issues/213
4434				syntaxError('Closing tag for {^{...}} under different elem: <' + bndId + '>');
4435			}
4436		}
4437		if (tag) {
4438			inTag = tag;
4439			// If there are ids (markers since the last tag), move them to the defer string
4440			tagStack.unshift(parentTag);
4441			parentTag = tag.slice(1);
4442			if (validate && tagStack[0] && tagStack[0] === badParent[parentTag]) {
4443				// Missing <tbody>
4444				// TODO: replace this by smart insertion of <tbody> tags
4445				error('Parent of <tr> must be <tbody>');
4446			}
4447			isVoid = voidElems[parentTag];
4448			if ((elCnt = elContent[parentTag]) && !prevElCnt) {
4449				deferStack.unshift(defer);
4450				defer = "";
4451			}
4452			prevElCnt = elCnt;
4453//TODO Consider providing validation which throws if you place <span> as child of <tr>, etc. - since if not caught,
4454//this can cause errors subsequently which are difficult to debug.
4455//				if (elContent[tagStack[0]]>2 && !elCnt) {
4456//					error(parentTag + " in " + tagStack[0]);
4457//				}
4458			if (defer && elCnt) {
4459				defer += "+"; // Will be used for stepping back through deferred tokens
4460			}
4461		}
4462		return preceding;
4463	}
4464
4465	function processViewInfos(vwInfos, targetParent) {
4466		// If targetParent, we are processing viewInfos (which may include navigation through '+-' paths) and hooking up to the right parentElem etc.
4467		// (and elem may also be defined - the next node)
4468		// If no targetParent, then we are processing viewInfos on newly inserted content
4469		var deferPath, deferChar, bindChar, parentElem, id, onAftCr, deep,
4470			addedBindEls = [];
4471
4472		// In elCnt context (element-only content model), prevNode is the first node after the open, nextNode is the first node after the close.
4473		// If both are null/undefined, then open and close are at end of parent content, so the view is empty, and its placeholder is the
4474		// 'lastChild' of the parentNode. If there is a prevNode, then it is either the first node in the view, or the view is empty and
4475		// its placeholder is the 'previousSibling' of the prevNode, which is also the nextNode.
4476		if (vwInfos) {
4477			if (vwInfos._tkns.charAt(0) === "@") {
4478				// We are processing newly inserted content. This is a special script element that was created in convertMarkers() to process deferred bindings,
4479				// and inserted following the target parent element - because no element tags (outside elCnt) were encountered to carry those binding tokens.
4480				// We will step back from the preceding sibling of this element, looking at targetParent elements until we find the one that the current binding
4481				// token belongs to. Set elem to null (the special script element), and remove it from the DOM.
4482				targetParent = elem.previousSibling;
4483				elem.parentNode.removeChild(elem);
4484				elem = undefined;
4485			}
4486			len = vwInfos.length;
4487			while (len--) {
4488				vwInfo = vwInfos[len];
4489//if (prevIds.indexOf(vwInfo.token) < 0) { // This token is a newly created view or tag binding
4490				bindChar = vwInfo.ch;
4491				if (deferPath = vwInfo.path) {
4492					// We have a 'deferred path'
4493					j = deferPath.length - 1;
4494					while (deferChar = deferPath.charAt(j--)) {
4495						// Use the "+" and"-" characters to navigate the path back to the original parent node where the deferred bindings ocurred
4496						if (deferChar === "+") {
4497							if (deferPath.charAt(j) === "-") {
4498								j--;
4499								targetParent = targetParent.previousSibling;
4500							} else {
4501								targetParent = targetParent.parentNode;
4502							}
4503						} else {
4504							targetParent = targetParent.lastChild;
4505						}
4506						// Note: Can use previousSibling and lastChild, not previousElementSibling and lastElementChild,
4507						// since we have removed white space within elCnt. Hence support IE < 9
4508					}
4509				}
4510				if (bindChar === "^") {
4511					if (tag = bindingStore[id = vwInfo.id]) {
4512						// The binding may have been deleted, for example in a different handler to an array collectionChange event
4513						// This is a tag binding
4514						deep = targetParent && (!elem || elem.parentNode !== targetParent); // We are stepping back looking for the right targetParent,
4515						// or we are linking existing content and this element is in elCnt, not an immediate child of the targetParent.
4516						if (!elem || deep) {
4517							tag.parentElem = targetParent;
4518						}
4519						if (vwInfo.elCnt && deep) {
4520							// With element only content, if there is no following element, or if the binding is deeper than the following element
4521							// then we need to set the open or close token as a deferred binding annotation on the parent
4522							setDefer(targetParent, (vwInfo.open ? "#" : "/") + id + bindChar + (targetParent._df || ""));
4523						}
4524						// This is an open or close marker for a data-linked tag {^{...}}. Add it to bindEls.
4525						addedBindEls.push([deep ? null : elem, vwInfo]);
4526					}
4527				} else if (view = viewStore[id = vwInfo.id]) {
4528					// The view may have been deleted, for example in a different handler to an array collectionChange event
4529					if (!view.parentElem) {
4530						// If view is not already extended for JsViews, extend and initialize the view object created in JsRender, as a JsViews view
4531						view.parentElem = targetParent || elem && elem.parentNode || parentNode;
4532						view._.onRender = addBindingMarkers;
4533						view._.onArrayChange = arrayChangeHandler;
4534						setArrayChangeLink(view);
4535					}
4536					parentElem = view.parentElem;
4537					if (vwInfo.open) {
4538						// This is an 'open view' node (preceding script marker node,
4539						// or if elCnt, the first element in the view, with a data-jsv annotation) for binding
4540						view._elCnt = vwInfo.elCnt;
4541						if (targetParent && !elem) {
4542							setDefer(targetParent, "#" + id + bindChar + (targetParent._df || ""));
4543						} else {
4544							// No targetParent, so there is a ._nxt elem (and this is processing tokens on the elem)
4545							if (!view._prv) {
4546								setDefer(parentElem, removeSubStr(parentElem._df, "#" + id + bindChar));
4547							}
4548							view._prv = elem;
4549						}
4550					} else {
4551						// This is a 'close view' marker node for binding
4552						if (targetParent && (!elem || elem.parentNode !== targetParent)) {
4553							// There is no ._nxt so add token to _df. It is deferred.
4554							setDefer(targetParent, "/" + id + bindChar + (targetParent._df || ""));
4555							view._nxt = undefined;
4556						} else if (elem) {
4557							// This view did not have a ._nxt, but has one now, so token may be in _df, and must be removed. (No longer deferred)
4558							if (!view._nxt) {
4559								setDefer(parentElem, removeSubStr(parentElem._df, "/" + id + bindChar));
4560							}
4561							view._nxt = elem;
4562						}
4563						linkCtx = view.linkCtx;
4564						if (onAftCr = view.ctx && view.ctx.onAfterCreate || onAfterCreate) {
4565							onAftCr.call(linkCtx, view);
4566						}
4567					}
4568//}
4569				}
4570			}
4571			len = addedBindEls.length;
4572			while (len--) {
4573				// These were added in reverse order to addedBindEls. We push them in BindEls in the correct order.
4574				bindEls.push(addedBindEls[len]);
4575			}
4576		}
4577		return !vwInfos || vwInfos.elCnt;
4578	}
4579
4580	function getViewInfos(vwInfos) {
4581		// Used by view.childTags() and tag.childTags()
4582		// Similar to processViewInfos in how it steps through bindings to find tags. Only finds data-linked tags.
4583		var level, parentTag, named;
4584
4585		if (vwInfos) {
4586			len = vwInfos.length;
4587			for (j = 0; j < len; j++) {
4588				vwInfo = vwInfos[j];
4589				// This is an open marker for a data-linked tag {^{...}}, within the content of the tag whose id is get.id. Add it to bindEls.
4590				// Note - if bindingStore[vwInfo.id]._is === "tag" then getViewInfos is being called too soon - during first linking pass
4591				parentTag = tag = bindingStore[vwInfo.id].linkCtx.tag;
4592				named = tag.tagName === tagName;
4593				if (!tag.flow || named) {
4594					if (!deep) {
4595						level = 1;
4596						while (parentTag = parentTag.parent) {
4597							level++;
4598						}
4599						tagDepth = tagDepth || level; // The level of the first tag encountered.
4600					}
4601					if ((deep || level === tagDepth) && (!tagName || named)) {
4602						// Filter on top-level or tagName as appropriate
4603						tags.push(tag);
4604					}
4605				}
4606			}
4607		}
4608	}
4609
4610	function dataLink() {
4611		//================ Data-link and fixup of data-jsv annotations ================
4612		var j, index,
4613			tokens = "",
4614			wrap = {},
4615			selector = linkViewsSel + (get ? ",[" + deferAttr + "]" : "");
4616			// If a childTags() call, get = ",[" + deferAttr + "]" - since we need to include elements that have a ._df expando for deferred tokens
4617
4618		elems = qsa ? parentNode.querySelectorAll(selector) : $(selector, parentNode).get();
4619		l = elems.length;
4620
4621		// The prevNode will be in the returned query, since we called markPrevOrNextNode() on it.
4622		// But it may have contained nodes that satisfy the selector also.
4623		if (prevNode && prevNode.innerHTML) {
4624			// Find the last contained node of prevNode, to use as the prevNode - so we only link subsequent elems in the query
4625			prevNodes = qsa ? prevNode.querySelectorAll(selector) : $(selector, prevNode).get();
4626			prevNode = prevNodes.length ? prevNodes[prevNodes.length - 1] : prevNode;
4627		}
4628
4629		tagDepth = 0;
4630		for (i = 0; i < l; i++) {
4631			elem = elems[i];
4632			if (prevNode && !found) {
4633				// If prevNode is set, not false, skip linking. If this element is the prevNode, set to false so subsequent elements will link.
4634				found = (elem === prevNode);
4635			} else if (nextNode && elem === nextNode) {
4636				// If nextNode is set then break when we get to nextNode
4637				if (get) {
4638					tokens += markerNodeInfo(elem);
4639				}
4640				break;
4641			} else if (elem.parentNode) {
4642				// elem has not been removed from DOM
4643				if (get) {
4644					tokens += markerNodeInfo(elem);
4645					if (elem._df) {
4646						j = i + 1;
4647						while (j < l && elem.contains(elems[j])) {
4648							j++;
4649						}
4650						// Add defered tokens after any tokens on descendant elements of this one
4651						wrap[j-1] = elem._df;
4652					}
4653					if (wrap[i]) {
4654						tokens += wrap[i] || "";
4655					}
4656				} else {
4657					if (isLink && (vwInfo = viewInfos(elem, undefined, rViewMarkers)) && (vwInfo = vwInfo[0])) {
4658						// If this is a link(trueOrString ...) call we will avoid re-binding to elems that are within template-rendered views
4659						skip = skip ? (vwInfo.id !== skip && skip) : vwInfo.open && vwInfo.id;
4660					}
4661					if (!skip && processInfos(viewInfos(elem))
4662						// If a link() call, processViewInfos() adds bindings to bindEls, and returns true for non-script nodes, for adding data-link bindings
4663						// If a childTags() call, getViewInfos returns array of tag bindings.
4664							&& elem.getAttribute($viewsLinkAttr)) {
4665						bindEls.push([elem]); // A data-linked element so add to bindEls too
4666					}
4667				}
4668			}
4669		}
4670
4671		if (get) {
4672			tokens += parentNode._df || "";
4673			if (index = tokens.indexOf("#" + get.id) + 1) {
4674				// We are looking for view.childTags() or tag.childTags() - so start after the open token of the parent view or tag.
4675				tokens = tokens.slice(index + get.id.length);
4676			}
4677			index = tokens.indexOf("/" + get.id);
4678			if (index + 1) {
4679				// We are looking for view.childTags() or tag.childTags() - so don't look beyond the close token of the parent view or tag.
4680				tokens = tokens.slice(0, index);
4681			}
4682			// Call getViewInfos to add the found childTags to the tags array
4683			getViewInfos(viewInfos(tokens, undefined, rOpenTagMarkers));
4684		}
4685
4686		if (html === undefined && parentNode.getAttribute($viewsLinkAttr)) {
4687			bindEls.push([parentNode]);
4687 // Support data-linking top-level element directly (not within a data-linked container)
4688		}
4689
4690		// Remove temporary marker script nodes they were added by markPrevOrNextNode
4691		unmarkPrevOrNextNode(prevNode, elCnt);
4692		unmarkPrevOrNextNode(nextNode, elCnt);
4693
4694		if (get) {
4695			if (lazyLink) {
4696				lazyLink.resolve();
4697			}
4698			return; // We have added childTags to the tags array, so we are done
4699		}
4700
4701		if (elCnt && defer + ids) {
4702			// There are some views with elCnt, for which the open or close did not precede any HTML tag - so they have not been processed yet
4703			elem = nextNode;
4704			if (defer) {
4705				if (nextNode) {
4706					processViewInfos(viewInfos(defer + "+", true), nextNode);
4707				} else {
4708					processViewInfos(viewInfos(defer, true), parentNode);
4709				}
4710			}
4711			processViewInfos(viewInfos(ids, true), parentNode);
4712			// If there were any tokens on nextNode which have now been associated with inserted HTML tags, remove them from nextNode
4713			if (nextNode) {
4714				tokens = nextNode.getAttribute(jsvAttrStr);
4715				if (l = tokens.indexOf(prevIds) + 1) {
4716					tokens = tokens.slice(l + prevIds.length - 1);
4717				}
4718				nextNode.setAttribute(jsvAttrStr, ids + tokens);
4719			}
4720		}
4721
4722		//================ Bind the data-linked elements and tags ================
4723		l = bindEls.length;
4724		for (i = 0; i < l; i++) {
4725			elem = bindEls[i];
4726			linkInfo = elem[1];
4727			elem = elem[0];
4728			if (linkInfo) {
4729				if (tag = bindingStore[linkInfo.id]) {
4730					if (linkCtx = tag.linkCtx) {
4731						// The tag may have been stored temporarily on the bindingStore - or may have already been replaced by the actual binding
4732						tag = linkCtx.tag;
4733						tag.linkCtx = linkCtx;
4734					}
4735					if (linkInfo.open) {
4736						// This is an 'open linked tag' binding annotation for a data-linked tag {^{...}}
4737						if (elem) {
4738							tag.parentElem = elem.parentNode;
4739							tag._prv = elem;
4740						}
4741						tag._elCnt = linkInfo.elCnt;
4742						// We data-link depth-first ("on the way in"), which is better for perf - and allows setting parent tags etc.
4743						view = tag.tagCtx.view;
4744						addDataBinding(undefined, tag._prv, view, linkInfo.id);
4745					} else {
4746						tag._nxt = elem;
4747						if (tag._.unlinked) {
4748							// This is a 'close linked tag' binding annotation
4749							// Add data binding
4750							tagCtx = tag.tagCtx;
4751							view = tagCtx.view;
4752							callAfterLink(tag);
4753						}
4754					}
4755				}
4756			} else {
4757				// Add data binding for a data-linked element (with data-link attribute)
4758				addDataBinding(elem.getAttribute($viewsLinkAttr), elem, $view(elem), undefined, isLink, outerData, context);
4759			}
4760		}
4761		if (lazyLink) {
4762			lazyLink.resolve();
4763		}
4764	}
4765	//==== /end of nested functions ====
4766
4767	var inTag, linkCtx, tag, i, l, j, len, elems, elem, view, vwInfo, linkInfo, prevNodes, token, prevView, nextView,
4768		node, tags, deep, tagName, tagCtx, validate, tagDepth, depth, fragment, copiedNode, firstTag, parentTag,
4769		isVoid, wrapper, div, tokens, elCnt, prevElCnt, htmlTag, ids, prevIds, found, skip, lazyLink, isLink, get,
4770		self = this,
4771		thisId = self._.id + "_",
4772		defer = "",
4773		// The marker ids for which no tag was encountered (empty views or final closing markers) which we carry over to container tag
4774		bindEls = [],
4775		tagStack = [],
4776		deferStack = [],
4777		onAfterCreate = self.hlp(onAfterCreateStr),
4778		processInfos = processViewInfos;
4779
4780	if (refresh) {
4781		lazyLink = refresh.lazyLink && $.Deferred();
4782		if (refresh.tmpl) {
4783			// refresh is the prevView, passed in from addViews()
4784			prevView = "/" + refresh._.id + "_";
4785		} else {
4786			isLink = refresh.lnk; // Top-level linking
4787			if (refresh.tag) {
4788				thisId = refresh.tag + "^";
4789				refresh = true;
4790			}
4791			if (get = refresh.get) {
4792				processInfos = getViewInfos;
4793				tags = get.tags;
4794				deep = get.deep;
4795				tagName = get.name;
4796			}
4797		}
4798		refresh = refresh === true;
4799	}
4800
4801	parentNode = parentNode
4802		? ("" + parentNode === parentNode
4803			? $(parentNode)[0]  // It is a string, so treat as selector
4804			: parentNode.jquery
4805				? parentNode[0] // A jQuery object - take first element.
4806				: parentNode)
4807		: (self.parentElem      // view.link()
4808			|| document.body);  // link(null, data) to link the whole document
4809
4810	validate = !$subSettingsAdvanced.noValidate && parentNode.contentEditable !== TRUE;
4811	parentTag = parentNode.tagName.toLowerCase();
4812	elCnt = !!elContent[parentTag];
4813
4814	prevNode = prevNode && markPrevOrNextNode(prevNode, elCnt);
4815	nextNode = nextNode && markPrevOrNextNode(nextNode, elCnt) || null;
4816
4817	if (html != undefined) {
4818		//================ Insert html into DOM using documentFragments (and wrapping HTML appropriately). ================
4819		// Also convert markers to DOM annotations, based on content model.
4820		// Corresponds to nextNode ? $(nextNode).before(html) : $(parentNode).html(html);
4821		// but allows insertion to wrap correctly even with inserted script nodes. jQuery version will fail e.g. under tbody or select.
4822		// This version should also be slightly faster
4823		div = document.createElement("div");
4824		wrapper = div;
4825		prevIds = ids = "";
4826		htmlTag = parentNode.namespaceURI === "http://www.w3.org/2000/svg" ? "svg_ns" : (firstTag = rFirstElem.exec(html)) && firstTag[1] || "";
4827		if (elCnt) {
4828			// Now look for following view, and find its tokens, or if not found, get the parentNode._df tokens
4829			node = nextNode;
4830			while (node && !(nextView = viewInfos(node))) {
4831				node = node.nextSibling;
4832			}
4833			if (tokens = nextView ? nextView._tkns : parentNode._df) {
4834				token = prevView || "";
4835				if (refresh || !prevView) {
4836					token += "#" + thisId;
4837				}
4838				j = tokens.indexOf(token);
4839				if (j + 1) {
4840					j += token.length;
4841					// Transfer the initial tokens to inserted nodes, by setting them as the ids variable, picked up in convertMarkers
4842					prevIds = ids = tokens.slice(0, j);
4843					tokens = tokens.slice(j);
4844					if (nextView) {
4845						node.setAttribute(jsvAttrStr, tokens);
4846					} else {
4847						setDefer(parentNode, tokens);
4848					}
4849				}
4850			}
4851		}
4852
4853		//================ Convert the markers to DOM annotations, based on content model. ================
4854//			oldElCnt = elCnt;
4855		isVoid = undefined;
4856		html = ("" + html).replace(rConvertMarkers, convertMarkers);
4857//			if (!!oldElCnt !== !!elCnt) {
4858//				error("Parse: " + html); // Parse error. Content not well-formed?
4859//			}
4860		if (validate && tagStack.length) {
4861			syntaxError("Mismatched '<" + parentTag + "...>' in:\n" + html); // Unmatched tag
4862		}
4863		if (validateOnly) {
4864			return;
4865		}
4866		// Append wrapper element to doc fragment
4867		safeFragment.appendChild(div);
4868
4869		// Go to html and back, then peel off extra wrappers
4870		// Corresponds to jQuery $(nextNode).before(html) or $(parentNode).html(html);
4871		// but supports svg elements, and other features missing from jQuery version (and this version should also be slightly faster)
4872		htmlTag = wrapMap[htmlTag] || wrapMap.div;
4873		depth = htmlTag[0];
4874		wrapper.innerHTML = htmlTag[1] + html + htmlTag[2];
4875		while (depth--) {
4876			wrapper = wrapper.lastChild;
4877		}
4878		safeFragment.removeChild(div);
4879		fragment = document.createDocumentFragment();
4880		while (copiedNode = wrapper.firstChild) {
4881			fragment.appendChild(copiedNode);
4882		}
4883		// Insert into the DOM
4884		parentNode.insertBefore(fragment, nextNode);
4885	}
4886
4887	if (lazyLink) {
4888		setTimeout(dataLink, 0);
4889	} else {
4890		dataLink();
4891	}
4892
4893	return lazyLink && lazyLink.promise();
4894}
4895
4896function addDataBinding(linkMarkup, node, currentView, boundTagId, isLink, data, context) {
4897	// Add data binding for data-linked elements or {^{...}} data-linked tags
4898	var tmpl, tokens, attr, convertBack, tagExpr, linkFn, linkCtx, tag, rTagIndex, hasElse, lastIndex,
4899		linkExpressions = [];
4900
4901	if (boundTagId) {
4902		// boundTagId is a string for {^{...}} data-linked tag. So only one linkTag in linkMarkup
4903		// data and context parameters are undefined
4904		tag = bindingStore[boundTagId];
4905		tag = tag.linkCtx ? tag.linkCtx.tag : tag;
4906
4907		linkCtx = tag.linkCtx || {
4908			type: "inline",
4909			data: currentView.data,                   // source
4910			elem: tag._elCnt ? tag.parentElem : node, // target
4911			view: currentView,
4912			ctx: currentView.ctx,
4913			attr: HTML, // Script marker nodes are associated with {^{ and always target HTML.
4914			fn: tag._.bnd,
4915			tag: tag,
4916			// Pass the boundTagId in the linkCtx, so that it can be picked up in observeAndBind
4917			_bndId: boundTagId
4918		};
4919		bindDataLinkTarget(linkCtx, linkCtx.fn);
4920	} else if (linkMarkup && node) {
4921		// If isLink then this is a top-level linking: .link(expression, target, data, ....) or
4922		// .link(true, target, data, ....) scenario - and data and context are passed in separately from the view
4923		data = isLink ? data : currentView.data;
4924
4925		// Compiled linkFn expressions could be stored in the tmpl.links array of the template
4926		// TODO - consider also caching globally so that if {{:foo}} or data-link="foo" occurs in different places,
4927		// the compiled template for this is cached and only compiled once...
4928		//links = currentView.links || currentView.tmpl.links;
4929
4930		tmpl = currentView.tmpl;
4931
4932//			if (!(linkTags = links[linkMarkup])) {
4933		// This is the first time this view template has been linked, so we compile the data-link expressions, and store them on the template.
4934
4935		linkMarkup = normalizeLinkTag(linkMarkup, defaultAttr(node));
4936		lastIndex = rTagDatalink.lastIndex = 0;
4937		while (tokens = rTagDatalink.exec(linkMarkup)) { // TODO require } to be followed by whitespace or $, and remove the \}(!\}) option.
4938			linkExpressions.push(tokens);
4939			lastIndex = rTagDatalink.lastIndex;
4940		}
4941		if (lastIndex < linkMarkup.length) {
4942			syntaxError(linkMarkup);
4943		}
4944		while (tokens = linkExpressions.shift()) {
4945			// Iterate over the data-link expressions, for different target attrs,
4946			// e.g. <input data-link="{:firstName:} title{>~description(firstName, lastName)}"
4947			// tokens: [all, attr, bindOnly, tagExpr, tagName, converter, colon, html, comment, code, params]
4948			rTagIndex = rTagDatalink.lastIndex;
4949			attr = tokens[1];
4950			tagExpr = tokens[3];
4951			while (linkExpressions[0] && linkExpressions[0][4] === "else") { // If this is {someTag...} and is followed by an {else...} add to tagExpr
4952				tagExpr += delimCloseChar1 + delimOpenChar0 + linkExpressions.shift()[3];
4953				hasElse = true;
4954			}
4955			if (hasElse) { // If an {else} has been added, need also to add closing {{/someTag}}
4956				tagExpr += delimCloseChar1 + delimOpenChar0 + delimOpenChar1 + "/" + tokens[4] + delimCloseChar0;
4957			}
4958			linkCtx = {
4959				type: isLink ? "top" : "link",
4960				data: data, // source
4961				elem: node, // target
4962				view: currentView,
4963				ctx: context,
4964				attr: attr,
4965				isLk: isLink, // top-level linking?
4966				_toLk : 1, // Flag to data-link on initial data-link call rendering call
4967				_noUpd : tokens[2] // Flag for data-link="^{...}" so on initial data-link call will bind, but not render)
4968			};
4969
4970			convertBack = undefined;
4971			if (tokens[6]) {
4972				convertBack = tokens[10] || undefined;
4973				linkCtx.convert = tokens[5] || "";
4974				if (!attr && convertBack !== undefined && defaultAttr(node)) {
4975					// Default target, so allow 2 way binding
4976					linkCtx.convertBack = convertBack = convertBack.slice(1);
4977				}
4978			}
4979			// Compile the linkFn expression which evaluates and binds a data-link expression
4980			// TODO - optimize for the case of simple data path with no conversion, helpers, etc.:
4981			//     i.e. data-link="a.b.c". Avoid creating new instances of Function every time. Can use a default function for all of these...
4982
4983			linkCtx.expr = attr + tagExpr;
4984			linkFn = tmpl.links[tagExpr];
4985			if (!linkFn) {
4986				tmpl.links[tagExpr] = linkFn = $sub.tmplFn(tagExpr, tmpl, true, convertBack, hasElse);
4987			}
4988			linkCtx.fn = linkFn;
4989			bindDataLinkTarget(linkCtx, linkFn);
4990			// We store rTagIndex in local scope, since this addDataBinding method can sometimes be called recursively,
4991			// and each is using the same rTagDatalink instance.
4992			rTagDatalink.lastIndex = rTagIndex;
4993		}
4994//		}
4995	}
4996}
4997
4998function bindDataLinkTarget(linkCtx, linkFn) {
4999	// Add data link bindings for a link expression in data-link attribute markup
5000	function handler(ev, eventArgs) {
5001		propertyChangeHandler.call(linkCtx, ev, eventArgs, linkFn);
5002		// If the link expression uses a custom tag, the propertyChangeHandler call will call renderTag, which will set tagCtx on linkCtx
5003	}
5004	handler.noArray = true;
5005	if (linkCtx.isLk) {
5006		// Top-level linking: .link(expressionOrTrue, data, context) - so we need to create a view for the linking, with the data and ctx
5007		// which may be different than the current context of the target. Note that this view is not a standard data-linked view, so it will
5008		// be disposed only when its parent view is disposed.
5009		addLinkMethods(linkCtx.view = new $sub.View(
5010			$sub.extendCtx(linkCtx.ctx, linkCtx.view.ctx),
5011			"link", linkCtx.view, linkCtx.data, linkCtx.expr, undefined, addBindingMarkers));
5012	}
5013	linkCtx._ctxCb = getContextCb(linkCtx.view); // _ctxCb is for filtering/appending to dependency paths: function(path, object) { return [(object|path)*]}
5014	linkCtx._hdl = handler;
5015	// handler._ctx = linkCtx; Could pass linkCtx for use in a depends = function() {}
5015 call, so depends is different for different linkCtx's
5016	handler(true);
5017}
5018
5019//=====================
5020// Data-linking helpers
5021//=====================
5022
5023function removeSubStr(str, substr) {
5024	var k;
5025	return str
5026		? (k = str.indexOf(substr),
5027			(k + 1
5028				? str.slice(0, k) + str.slice(k + substr.length)
5029				: str))
5030		: "";
5031}
5032
5033function markerNodeInfo(node) {
5034	return node &&
5035		("" + node === node
5036			? node
5037			: node.tagName === SCRIPT
5038				? node.type.slice(3)
5039				: node.nodeType === 1 && node.getAttribute(jsvAttrStr) || "");
5040}
5041
5042function viewInfos(node, isVal, rBinding) {
5043	// Test whether node is a script marker node, and if so, return metadata
5044	function getInfos(all, open, close, id, ch, elPath) {
5045		infos.push({
5046			elCnt: elCnt,
5047			id: id,
5048			ch: ch,
5049			open: open,
5050			close: close,
5051			path: elPath,
5052			token: all
5053		});
5054	}
5055	var elCnt, tokens,
5056		infos = [];
5057	if (tokens = isVal ? node : markerNodeInfo(node)) {
5058		elCnt = infos.elCnt = node.tagName !== SCRIPT;
5059		elCnt = tokens.charAt(0) === "@" || elCnt;
5060		infos._tkns = tokens;
5061		// rMarkerTokens = /(?:(#)|(\/))(\d+)([_^])([-+@\d]+)?/g;
5062		tokens.replace(rBinding || rMarkerTokens, getInfos);
5063		return infos;
5064	}
5065}
5066
5067function unmarkPrevOrNextNode(node, elCnt) {
5068	if (node) {
5069		if (node.type === "jsv") {
5070			node.parentNode.removeChild(node);
5071		} else if (elCnt && node.getAttribute($viewsLinkAttr) === "") {
5072			node.removeAttribute($viewsLinkAttr);
5073		}
5074	}
5075}
5076
5077function markPrevOrNextNode(node, elCnt) {
5078	var marker = node;
5079	while (elCnt && marker && marker.nodeType !== 1) {
5080		marker = marker.previousSibling;
5081	}
5082	if (marker) {
5083		if (marker.nodeType !== 1) {
5084			// For text nodes, we will add a script node before
5085			marker = document.createElement(SCRIPT);
5086			marker.type = "jsv";
5087			node.parentNode.insertBefore(marker, node);
5088		} else if (!markerNodeInfo(marker) && !marker.getAttribute($viewsLinkAttr)) {
5089			// For element nodes, we will add a data-link attribute (unless there is already one)
5090			// so that this node gets included in the node linking process.
5091			marker.setAttribute($viewsLinkAttr, "");
5092		}
5093	}
5094	return marker;
5095}
5096
5097function normalizeLinkTag(linkMarkup, twoway) {
5098	linkMarkup = $.trim(linkMarkup).replace(rEscapeQuotes, "\\$&");
5099	return linkMarkup.slice(-1) !== delimCloseChar0
5100	// If simplified syntax is used: data-link="expression", convert to data-link="{:expression}",
5101	// or for inputs, data-link="{:expression:}" for (default) two-way binding
5102		? linkMarkup = delimOpenChar1 + ":" + linkMarkup + (twoway ? ":" : "") + delimCloseChar0
5103		: linkMarkup;
5104}
5105
5106//===========================
5107// Methods for views and tags
5108//===========================
5109
5110function callAfterLink(tag, ev, eventArgs) {
5111	var $linkedElem, linkedElem, radioButtons, val, l, linkedTag, oldTrig, newTrig, tagProps, propsExpr, linkedElemView, prop, propDef,
5112		tagCtx = tag.tagCtx,
5113		view = tagCtx.view,
5114		props = tagCtx.props,
5115		linkCtx = tag.linkCtx;
5116
5117	if (tag._.unlinked) { // First call to onAfterLink, or first call after onUpdate: updateContent. Initialize and call onBind and set properties
5118		if (tag.linkedElement !== undefined) {
5119			// linkedElement: - selector for identifying linked element in template/rendered content
5120			tag.linkedElem = tag._.inline ? tag.contents(true, tag.linkedElement || "*").first() : $(linkCtx.elem);
5121		}
5122		if (tag.onBind) {
5123			tag.onBind(tagCtx, linkCtx, tag.ctx, ev, eventArgs);
5124		}
5125	}
5126
5127	if (tag.onAfterLink) {
5128		tag.onAfterLink(tagCtx, linkCtx, tag.ctx, ev, eventArgs);
5129	}
5130
5131	tag._.unlinked = undefined;
5132	$linkedElem = tag.targetTag ? tag.targetTag.linkedElem : tag.linkedElem;
5133	if (linkedElem = $linkedElem && $linkedElem[0]) {
5134		if (!tag.noVal) {
5135			if (!tag._.chging) {
5136				val = tag.cvtArgs()[0];
5137
5138				if (linkedElem !== linkCtx.elem) {
5139					l = $linkedElem.length;
5140					while (l--) {
5141						linkedElem = $linkedElem[l];
5142						linkedTag = linkedElem._jsvLkEl;
5143						if (tag._.inline && (!linkedTag || linkedTag !== tag && linkedTag.targetTag !== tag)) {
5144							// For data-linked tags, identify the linkedElem with the tag, for "to" binding
5145							// (For data-linked elements, if not yet bound, we identify later when the linkCtx.elem is bound)
5146							linkedElem._jsvLkEl = tag;
5147							bindTo(bindingStore[tag._tgId], tag, linkedElem);
5148							linkedElem._jsvBnd = "&" + tag._tgId + "+"; // Add a "+" for cloned binding - so removing
5149							// elems with cloned bindings will not remove the 'parent' binding from the bindingStore.
5150						}
5151					}
5152					linkCtx._val = val;
5153				}
5154				if (val !== undefined) {
5155					if (linkedElem.value !== undefined) {
5156						if (linkedElem.type === CHECKBOX) {
5157							linkedElem[CHECKED] = val && val !== "false";
5158						} else if (linkedElem.type === "text") {
5159							linkedElem.value = val;
5160						}
5161					} else if (linkedElem.contentEditable === TRUE) {
5162						linkedElem.innerHTML = val;
5163					}
5164				}
5165			}
5166		}
5167		if (tag.setSize) {
5168			if (props.height) {
5169				$linkedElem.height(props.height);
5170			}
5171			if (props.width) {
5172				$linkedElem.width(props.width);
5173			}
5174		}
5175		if (props.title !== undefined) {
5176			$linkedElem.attr("title", props.title);
5177		}
5178		if (props["class"]) {
5179			// This code supports dynamic binding to class - where it adds the class if absent, and removes/adds if a previous value is present
5180			if (eventArgs && $linkedElem.hasClass(eventArgs.oldValue)) {
5181				$linkedElem.removeClass(eventArgs.oldValue);
5182			}
5183			$linkedElem.addClass(props["class"]);
5184		}
5185		if (props.id) {
5186			$linkedElem[0].id = props.id;
5187		}
5188		if (props.name) {
5189			$linkedElem.attr("name", props.name);
5190		}
5191	}
5192}
5193
5194function asyncElemChangeHandler(ev) {
5195	setTimeout(function() {
5196		elemChangeHandler(ev);
5197	}, 0);
5198}
5199
5200function bindElChange($elem, trig, onoff) {
5201	if (trig) {
5202		trig = "" + trig === trig ? trig : "keydown"; // Set trigger to (true || truey non-string (e.g. 1) || 'keydown'): Get 'keydown' with async
5203		$elem[onoff](trig, trig === "keydown" ? asyncElemChangeHandler : elemChangeHandler);
5204	}
5205}
5206
5207function bindTo(binding, tag, linkedElem, cvtBk) {
5208	// Two-way binding.
5209	// We set the binding.to[1] to be the cvtBack, and binding.to[0] to be either the path to the target, or [object, path] where the target is the path on the provided object.
5210	// So for a computed path with an object call: a.b.getObject().d.e, then we set to[0] to be [exprOb, "d.e"], and we bind to the path on the returned object, exprOb.ob, as target
5211	// Otherwise our target is the first path, paths[0], which we will convert with contextCb() for paths like ~a.b.c or #x.y.z
5212
5213	var bindto, pathIndex, path, lastPath, bindtoOb, $linkedElem, newTrig, oldTrig, to, l, totry,
5214		linkCtx = binding.linkCtx,
5215		source = linkCtx.data,
5216		paths = linkCtx.fn.paths;
5217
5218	tag = tag || linkedElem._jsvLkEl;
5219
5220	if (binding && paths) {
5221		oldTrig = linkedElem._jsvTr || false;
5222		if (tag) {
5223			cvtBk = tag.convertBack || cvtBk;
5224			newTrig = tag.tagCtx.props.trigger;
5225		}
5226		newTrig = newTrig === undefined ? $subSettings.trigger : newTrig;
5227		if (oldTrig !== newTrig) {
5228			$linkedElem = $(linkedElem);
5229			bindElChange($linkedElem, oldTrig, "off");
5230			bindElChange(
5231				$linkedElem,
5232				linkedElem._jsvTr
5233					// Trigger is noop except for text box, textarea, contenteditable...
5234					= (linkedElem.tagName === "INPUT" && linkedElem.type !== CHECKBOX && linkedElem.type !== RADIO || linkedElem.type === "textarea" || linkedElem.contentEditable === TRUE) && newTrig,
5235				"on"
5236			);
5237		}
5238
5239		paths = (bindto = paths._jsvto) || paths[0];
5240		pathIndex = paths && paths.length;
5241		if (pathIndex && (!tag || tag.tagCtx.args.length)) {
5242			lastPath = paths[pathIndex - 1];
5243			if (lastPath._jsv) {
5244				bindtoOb = lastPath;
5245				while (lastPath.sb && lastPath.sb._jsv) {
5246					path = lastPath = lastPath.sb;
5247				}
5248				path = lastPath.sb || path && path.path;
5249				lastPath = path ? path.slice(1) : bindtoOb.path;
5250			}
5251			if (path) {
5252				to = [
5253						bindtoOb, // 'exprOb' for this expression and view-binding. So bindtoOb.ob is current object returned by expression.
5254						lastPath
5255					];
5256			} else {
5257				while ((totry = linkCtx._ctxCb(path = lastPath.split("^").join("."), source)) && (l = totry.length)) {
5258					// Recursively dereference any ~foo or #bar tokens in the path. (Recursive because ~foo may be a contextual param which has
5259					// its own dependencies on other ~foo #bar components)
5260					to = totry;
5261					if (to._cp) { // Two-way binding to a contextual parameter reference, ~foo (declared as ~foo=expr on a parent tag)
5262						to = [to[l-3], to[l-2]];
5263						lastPath = to[1];
5264						if (lastPath._jsv) {
5265							bindtoOb = lastPath;
5266							while (lastPath.sb && lastPath.sb._jsv) {
5267								path = lastPath = lastPath.sb;
5268							}
5269							path = lastPath.sb || path && path.path;
5270							lastPath = path ? path.slice(1) : bindtoOb.path;
5271							to = [
5272								bindtoOb, // 'exprOb' for this expression and view-binding. So bindtoOb.ob is current object returned by expression.
5273								lastPath
5274							];
5275						}
5276					} else { // Two-way binding to a helper - e.g. ~address.street, or computed, e.g. ~fullName(), or view property e.g. #data.foo
5277						to = l>2
5278							? [to[l-3], to[l-2]] // With path: [object, path]
5279							: [to[l-2]];         // No path, (e.g. [function] for computed with setter)
5280					}
5281					source = to[0];
5282					lastPath = to[1];
5283				}
5284				to = to || [source, path]; // Two way binding to an object (neither ~foo nor #bar)
5285			}
5286		} else {
5287			to = [];
5288		}
5289		binding.to = [
5290			to,
5291			cvtBk
5292		];
5293	}
5294}
5295
5296function mergeCtxs(tag, newCtxs, replace) { // Merge updated tagCtxs into tag.tagCtxs
5297	var tagCtx, newTagCtx,
5298		view = tag.tagCtx.view,
5299		tagCtxs = tag.tagCtxs || [tag.tagCtx],
5300		l = tagCtxs.length,
5301		refresh = !newCtxs;
5302
5303	newCtxs = newCtxs || tag._.bnd.call(view.tmpl, (tag.linkCtx || view).data, view, $sub);
5304
5305	if (replace) {
5306		// Replace previous tagCtxs by new ones, rather than merging
5307		tagCtxs = tag.tagCtxs = newCtxs;
5308		tag.tagCtx = tagCtxs[0];
5309	} else {
5310		while (l--) {
5311			tagCtx = tagCtxs[l];
5312			newTagCtx = newCtxs[l];
5313			$observable(tagCtx.props).setProperty(newTagCtx.props);
5314			$extend(tagCtx.ctx, newTagCtx.ctx); // We don't support propagating ctx variables, ~foo, observably, to nested views. So extend, not setProperty...
5315			tagCtx.args = newTagCtx.args;
5316			if (refresh) {
5317				tagCtx.tmpl = newTagCtx.tmpl;
5318			}
5319		}
5320	}
5321	$sub._ths(tag, tagCtxs[0]); // tagHandlersFromProps
5322	return tagCtxs;
5323}
5324
5325//=========
5326// Disposal
5327//=========
5328
5329function clean(elems) {
5330	// Remove data-link bindings, or contained views
5331	var l, elem, bindings,
5332		elemArray = [],
5333		len = elems.length,
5334		i = len;
5335	while (i--) {
5336		// Copy into an array, so that deletion of nodes from DOM will not cause our 'i' counter to get shifted
5337		// (Note: This seems as fast or faster than elemArray = [].slice.call(elems); ...)
5338		elemArray.push(elems[i]);
5339	}
5340	i = len;
5341	while (i--) {
5342		elem = elemArray[i];
5343		if (elem.parentNode) {
5344			// Has not already been removed from the DOM
5345			if (bindings = elem._jsvBnd) {
5346				// Get propertyChange bindings for this element
5347				// This may be an element with data-link, or the opening script marker node for a data-linked tag {^{...}}
5348				// bindings is a string with the syntax: "(&bindingId)*"
5349				bindings = bindings.slice(1).split("&");
5350				elem._jsvBnd = "";
5351				l = bindings.length;
5352				while (l--) {
5353					// Remove associated bindings
5354					removeViewBinding(bindings[l], elem._jsvLkEl, elem); // unbind bindings with this bindingId on this view
5355				}
5356			}
5357			disposeTokens(markerNodeInfo(elem) + (elem._df || ""));
5358		}
5359	}
5360}
5361
5362function removeViewBinding(bindId, linkedElemTag, elem) {
5363	// Unbind
5364	var objId, linkCtx, tag, object, obsId, tagCtxs, l, map, $linkedElem, linkedElem, trigger, view,
5365		binding = bindingStore[bindId];
5366
5367	if (linkedElemTag) {
5368		if (elem === linkedElemTag.linkedElem[0]) {
5369			elem._jsvLkEl = undefined;
5370			linkedElemTag.linkedElem = undefined;
5371		}
5372	} else if (binding) {
5373		delete bindingStore[bindId]; // Delete already, so call to onDispose handler below cannot trigger recursive deletion (through recursive call to jQuery cleanData)
5374		for (objId in binding.bnd) {
5375			object = binding.bnd[objId];
5376			obsId = binding.cbId;
5377			if ($isArray(object)) {
5378				$([object]).off(arrayChangeStr + obsId).off(propertyChangeStr + obsId); // There may be either or both of arrayChange and propertyChange
5379			} else {
5380				$(object).off(propertyChangeStr + obsId);
5381			}
5382			delete binding.bnd[objId];
5383		}
5384
5385		if (linkCtx = binding.linkCtx) {
5386			if (tag = linkCtx.tag) {
5387				if (tagCtxs = tag.tagCtxs) {
5388					l = tagCtxs.length;
5389					while (l--) {
5390						if (map = tagCtxs[l].map) {
5391							map.unmap(); //unobserve
5392						}
5393					}
5394				}
5395				$linkedElem = tag.linkedElem;
5396
5397				if (tag.onUnbind) {
5398					tag.onUnbind(tag.tagCtx, linkCtx, tag.ctx, true);
5399				}
5400				if (tag.onDispose) {
5401					tag.onDispose();
5402				}
5403
5404				if (!tag._elCnt) {
5405					if (tag._prv) {
5406						tag._prv.parentNode.removeChild(tag._prv);
5407					}
5408					if (tag._nxt) {
5409						tag._nxt.parentNode.removeChild(tag._nxt);
5410					}
5411				}
5412			}
5413			linkedElem = $linkedElem && $linkedElem[0] || linkCtx.elem;
5414
5415			if (trigger = linkedElem && linkedElem._jsvTr) {
5416				bindElChange($linkedElem || $(linkedElem), trigger, "off");
5417				linkedElem._jsvTr = undefined;
5418			}
5419			view = linkCtx.view;
5420			if (view.type === "link") {
5421				view.parent.removeViews(view._.key, undefined, true); // A "link" view is associated with the binding, so should be disposed with binding.
5422			} else {
5423				delete view._.bnds[bindId];
5424			}
5425		}
5426		delete cbBindingsStore[binding.cbId];
5427	}
5428}
5429
5430function $unlink(to) {
5431	if (to) {
5432		to = to.jquery ? to : $(to);
5433		to.each(function() {
5434			var innerView;
5435			//TODO fix this for better perf. Rather that calling inner view multiple times which does querySelectorAll each time, consider a single querySelectorAll
5436			// or simply call view.removeViews() on the top-level views under the target 'to' node, then clean(...)
5437			while ((innerView = $view(this, true)) && innerView.parent) {
5438				innerView.parent.removeViews(innerView._.key, undefined, true);
5439			}
5440			clean(this.getElementsByTagName("*"));
5441		});
5442		clean(to);
5443	} else {
5444		// Call to $.unlink() is equivalent to $.unlink(true, "body")
5445		if (activeBody) {
5446			$(activeBody)
5447				.off(elementChangeStr, elemChangeHandler)
5448				.off('blur', '[contenteditable]', elemChangeHandler);
5449			activeBody = undefined;
5450		}
5451		topView.removeViews();
5452		clean(document.body.getElementsByTagName("*"));
5453	}
5454}
5455
5456//========
5457// Helpers
5458//========
5459
5460function getContextCb(view) { // Return a callback for accessing the context of a template/data-link expression - and convert
5460ing ~foo, #foo etc.
5461	// TODO Consider exposing or allowing override, as public API
5462	return function(path, object, depth) {
5463		// TODO consider only calling the contextCb on the initial token in path '~a.b.c' and not calling again on
5464		// the individual tokens, 'a', 'b', 'c'... Currently it is called multiple times
5465		var tokens, tag, items, helper, last, nextPath, l;
5466		if (view && path) {
5467			if (path._jsv) {
5468				return path._jsv.call(view.tmpl, object, view, $sub);
5469			}
5470			if (path.charAt(0) === "~") {
5471				// We return new items to insert into the sequence, replacing the "~a.b.c" string:
5472				// [helperObject 'a', "a.b.c" currentDataItem] so currentDataItem becomes the object for subsequent paths.
5473				if (path.slice(0, 4) === "~tag") {
5474					tag = view.ctx;
5475					if (path.charAt(4) === ".") {
5476						// "~tag.xxx"
5477						tokens = path.slice(5).split(".");
5478						tag = tag.tag;
5479					}
5480					if (tokens) {
5481						return tag ? [tag, tokens.join("."), object] : [];
5482					}
5483				}
5484				path = path.slice(1).split(".");
5485				if (helper = view.hlp(last = path.shift(), true)) {
5486					if (helper._cp) {  // helper for (contextual parameter ~foo=...) is an array - [data, ctxPrmDependencies ...]
5487						if (path.length) {
5488							nextPath = "." + path.join(".");
5489							last = helper[l = helper.length-1];
5490							if (last._jsv) {
5491								last.sb = nextPath;
5492								last.bnd = !!depth;
5493							} else {
5494								helper[l] = (last + nextPath).replace("#data.", "");
5495								if (last.slice(0, 5) === "#view") {
5496									helper[l] = helper[l].slice(6);
5497									helper.splice(l, 0, view);
5498								}
5499							}
5500						}
5501						helper.push(object);
5502						items = helper;
5503					} else if (path.length || $isFunction(helper)) {
5504						items = [helper, path.join("."), object]; // 2way bindng on ~foo.helperLeafProperty or ~computed() or ~contextualParam
5505					}
5506				}
5507				return items || [];
5508			}
5509			if (path.charAt(0) === "#") {
5510				// We return new items to insert into the sequence, replacing the "#a.b.c" string: [view, "a.b.c" currentDataItem]
5511				// so currentDataItem becomes the object for subsequent paths. The 'true' flag makes the paths bind only to leaf changes.
5512				return path === "#data" ? [] : [view, path.replace(rViewPath, ""), object];
5513			}
5514		}
5515	};
5516}
5517
5518function inputAttrib(elem) {
5519	return elem.type === CHECKBOX ? elem[CHECKED] : elem.value;
5520}
5521
5522//========================== Initialize ==========================
5523
5524//=====================
5525// JsRender integration
5526//=====================
5527
5528$sub.onStore.template = function(name, item) {
5529	item.link = tmplLink;
5530	if (name) {
5531		$.link[name] = function() {
5532			return tmplLink.apply(item, arguments);
5533		};
5534	}
5535};
5536
5537$sub.viewInfos = viewInfos; // Expose viewInfos() as public helper method
5538
5539// Define JsViews version of delimiters(), and initialize
5540($viewsSettings.delimiters = function() {
5541	// Run delimiters initialization in context of jsrender.js
5542	var ret = oldJsvDelimiters.apply(0, arguments);
5543
5544	if (oldJsvDelimiters !== $viewsDelimiters) {
5545		// If JsRender was loaded before JsViews, then need also to initialize and set globals in that JsRender instance
5546		ret = $viewsDelimiters.apply(0, arguments);
5547	}
5548
5549	// Data-linking must use new delimiters
5550	rTagDatalink = new RegExp("(?:^|\\s*)([\\w-]*)(\\" + linkChar + ")?(\\" + delimOpenChar1 + $sub.rTag + "(:\\w*)?\\" + delimCloseChar0 + ")", "g");
5551	return ret;
5552})(); // jshint ignore:line
5553
5554$sub.addSetting("trigger");
5555
5556//====================================
5557// Additional members for linked views
5558//====================================
5559
5560function transferViewTokens(prevNode, nextNode, parentElem, id, viewOrTagChar, refresh) {
5561	// Transfer tokens on prevNode of viewToRemove/viewToRefresh to nextNode or parentElem._df
5562	var i, l, vwInfos, vwInfo, viewOrTag, viewId, tokens,
5563		precedingLength = 0,
5564		emptyView = prevNode === nextNode;
5565
5566	if (prevNode) {
5567		// prevNode is either the first node in the viewOrTag, or has been replaced by the vwInfos tokens string
5568		vwInfos = viewInfos(prevNode) || [];
5569		for (i = 0, l = vwInfos.length; i < l; i++) {
5570			// Step through views or tags on the prevNode
5571			vwInfo = vwInfos[i];
5572			viewId = vwInfo.id;
5573			if (viewId === id && vwInfo.ch === viewOrTagChar) {
5574				if (refresh) {
5575					// This is viewOrTagToRefresh, this is the last viewOrTag to process...
5576					l = 0;
5577				} else {
5578					// This is viewOrTagToRemove, so we are done...
5579					break;
5580				}
5581			}
5582			if (!emptyView) {
5583				viewOrTag = vwInfo.ch === "_"
5584					? viewStore[viewId]
5585					: bindingStore[viewId].linkCtx.tag;
5586				if (vwInfo.open) {
5587					// A "#m" token
5588					viewOrTag._prv = nextNode;
5589				} else if (vwInfo.close) {
5590					// A "/m" token
5591					viewOrTag._nxt = nextNode;
5592				}
5593			}
5594			precedingLength += viewId.length + 2;
5595		}
5596
5597		if (precedingLength) {
5598			prevNode.setAttribute(jsvAttrStr, prevNode.getAttribute(jsvAttrStr).slice(precedingLength));
5599		}
5600		tokens = nextNode ? nextNode.getAttribute(jsvAttrStr) : parentElem._df;
5601		if (l = tokens.indexOf("/" + id + viewOrTagChar) + 1) {
5602			tokens = vwInfos._tkns.slice(0, precedingLength) + tokens.slice(l + (refresh ? -1 : id.length + 1));
5603		}
5604		if (tokens) {
5605			if (nextNode) {
5606				// If viewOrTagToRemove was an empty viewOrTag, we will remove both #n and /n
5607				// (and any intervening tokens) from the nextNode (=== prevNode)
5608				// If viewOrTagToRemove was not empty, we will take tokens preceding #n from prevNode,
5609				// and concatenate with tokens following /n on nextNode
5610				nextNode.setAttribute(jsvAttrStr, tokens);
5611			} else {
5612				setDefer(parentElem, tokens);
5613			}
5614		}
5615	} else {
5616		// !prevNode, so there may be a deferred nodes token on the parentElem. Remove it.
5617		setDefer(parentElem, removeSubStr(parentElem._df, "#" + id + viewOrTagChar));
5618		if (!refresh && !nextNode) {
5619			// If this viewOrTag is being removed, and there was no .nxt, remove closing token from deferred tokens
5620			setDefer(parentElem, removeSubStr(parentElem._df, "/" + id + viewOrTagChar));
5621		}
5622	}
5623}
5624
5625function disposeTokens(tokens) {
5626	var i, l, vwItem, vwInfos;
5627	if (vwInfos = viewInfos(tokens, true, rOpenMarkers)) {
5628		for (i = 0, l = vwInfos.length; i < l; i++) {
5629			vwItem = vwInfos[i];
5630			if (vwItem.ch === "_") {
5631				if ((vwItem = viewStore[vwItem.id]) && vwItem.type) {
5632					// If this is the _prv (prevNode) for a view, remove the view
5633					// - unless view.type is undefined, in which case it is already being removed
5634					vwItem.parent.removeViews(vwItem._.key, undefined, true);
5635				}
5636			} else {
5637				removeViewBinding(vwItem.id); // unbind bindings with this bindingId on this view
5638			}
5639		}
5640	}
5641}
5642
5643//====================================
5644// Add link methods to data-linked view or tag
5645//====================================
5646function addLinkMethods(tagOrView, isTag) {
5647
5648	tagOrView.contents = function(deep, select) {
5649		// For a view or a tag, return jQuery object with the content nodes,
5650		if (deep !== !!deep) {
5651			// deep not boolean, so this is contents(selector)
5652			select = deep;
5653			deep = undefined;
5654		}
5655		var filtered,
5656			nodes = $(this.nodes());
5657		if (nodes[0]) {
5658			filtered = select ? nodes.filter(select) : nodes;
5659			nodes = deep && select ? filtered.add(nodes.find(select)) : filtered;
5660		}
5661		return nodes;
5662	};
5663
5664	tagOrView.nodes = function(withMarkers, prevNode, nextNode) {
5665		// For a view or a tag, return top-level nodes
5666		// Do not return any script marker nodes, unless withMarkers is true
5667		// Optionally limit range, by passing in prevNode or nextNode parameters
5668
5669		var node,
5670			self = this,
5671			elCnt = self._elCnt,
5672			prevIsFirstNode = !prevNode && elCnt,
5673			nodes = [];
5674
5675		prevNode = prevNode || self._prv;
5676		nextNode = nextNode || self._nxt;
5677
5678		node = prevIsFirstNode
5679			? (prevNode === self._nxt
5680				? self.parentElem.lastSibling
5681				: prevNode)
5682			: (self._.inline === false
5683				? prevNode || self.linkCtx.elem.firstChild
5684				: prevNode && prevNode.nextSibling);
5685
5686		while (node && (!nextNode || node !== nextNode)) {
5687			if (withMarkers || elCnt || node.tagName !== SCRIPT) {
5688				// All the top-level nodes in the view
5689				// (except script marker nodes, unless withMarkers = true)
5690				// (Note: If a script marker node, viewInfo.elCnt undefined)
5691				nodes.push(node);
5692			}
5693			node = node.nextSibling;
5694		}
5695		return nodes;
5696	};
5697
5698	tagOrView.childTags = function(deep, tagName) {
5699		// For a view or a tag, return child tags - at any depth, or as immediate children only.
5700		if (deep !== !!deep) {
5701			// deep not boolean, so this is childTags(tagName) - which looks for top-level tags of given tagName
5702			tagName = deep;
5703			deep = undefined;
5704		}
5705
5706		var self = this,
5707			view = self.link ? self : self.tagCtx.view, // this may be a view or a tag. If a tag, get the view from tag.view.tagCtx
5708			prevNode = self._prv,
5709			elCnt = self._elCnt,
5710			tags = [];
5711
5712		view.link(
5713			undefined,
5714			self.parentElem,
5715			elCnt ? prevNode && prevNode.previousSibling : prevNode,
5716			self._nxt,
5717			undefined,
5718			{get:{
5719				tags: tags,
5720				deep: deep,
5721				name: tagName,
5722				id: self.link ? self._.id + "_" : self._tgId + "^"
5723			}}
5724		);
5725		return tags;
5726	};
5727
5728	tagOrView.refresh = function(sourceValue) {
5729		var promise, attr,
5730			tag = this,
5731			linkCtx = tag.linkCtx,
5732			view = tag.tagCtx.view;
5733
5734		if (tag.disposed) { error("Removed tag"); }
5735		if (sourceValue === undefined) {
5736			sourceValue = $sub._tag(tag, view, view.tmpl, mergeCtxs(tag), true); // Get rendered HTML for tag, based on refreshed tagCtxs
5737		}
5738		if (sourceValue + "" === sourceValue) {
5739			// If no rendered content, sourceValue will not be a string (can be 0 or undefined)
5740			attr = tag._.inline ? HTML : (linkCtx.attr || defaultAttr(tag.parentElem, true));
5741			promise = updateContent(sourceValue, linkCtx, attr, tag);
5742		}
5743
5744		callAfterLink(tag);
5745		return promise || tag;
5746	};
5747
5748	tagOrView.update = function(value) {
5749		var linkedElem = this.linkedElem;
5750		if (linkedElem) {
5751			elemChangeHandler({
5752				target: linkedElem[0]
5753			}, undefined, value);
5754		}
5755	};
5756
5757	if (isTag) {
5758		// This is a TAG
5759		tagOrView.domChange = function() { // domChange notification support
5760			var elem = this.parentElem,
5761				hasListener = $.hasData(elem) && $._data(elem).events,
5762				domChangeNotification = "jsv-domchange";
5763
5764			if (hasListener && hasListener[domChangeNotification]) {
5765				// Only trigger handler if there is a handler listening for this event. (Note using triggerHandler - so no event bubbling.)
5766				$(elem).triggerHandler(domChangeNotification, arguments);
5767			}
5768		};
5769	} else {
5770		// This is a VIEW
5771		// Note: a linked view will also, after linking have nodes[], _prv (prevNode), _nxt (nextNode) ...
5772		tagOrView.addViews = function(index, dataItems) {
5773			// if view is not an array view, do nothing
5774			var i, viewsCount,
5775				self = this,
5776				itemsCount = dataItems.length,
5777				views = self.views;
5778
5779			if (!self._.useKey && itemsCount) {
5780				// view is of type "array"
5781				viewsCount = views.length + itemsCount;
5782
5783				if (viewsCount === self.data.length // If views not already synced to array (e.g. triggered by array.length propertyChange - jsviews/issues/301)
5784						&& renderAndLink(self, index, self.tmpl, views, dataItems, self.ctx) !== false) {
5785					if (!self._.srt) { // Not part of a 'sort' on refresh
5786						self.fixIndex(index + itemsCount);
5787					}
5788				}
5789			}
5790		};
5791
5792		tagOrView.removeViews = function(index, itemsCount, keepNodes, isMove) {
5793			// view.removeViews() removes all the child views
5794			// view.removeViews(index) removes the child view with specified index or key
5795			// view.removeViews(index, count) removes the specified nummber of child views, starting with the specified index
5796			function removeView(index) {
5797				var id, bindId, parentElem, prevNode, nextNode, nodesToRemove,
5798					viewToRemove = views[index];
5799
5800				if (viewToRemove && viewToRemove.link) {
5801					id = viewToRemove._.id;
5802					if (!keepNodes) {
5803						// Remove the HTML nodes from the DOM, unless they have already been removed, including nodes of child views
5804						nodesToRemove = viewToRemove.nodes();
5805					}
5806
5807					// Remove child views, without removing nodes
5808					viewToRemove.removeViews(undefined, undefined, true);
5809
5810					viewToRemove.type = undefined; // Set type to undefined: used as a flag that this view is being removed
5811					prevNode = viewToRemove._prv;
5812					nextNode = viewToRemove._nxt;
5813					parentElem = viewToRemove.parentElem;
5814					// If prevNode and nextNode are the same, the view is empty
5815					if (!keepNodes) {
5816						// Remove the HTML nodes from the DOM, unless they have already been removed, including nodes of child views
5817						if (viewToRemove._elCnt) {
5818							// if keepNodes is false (and transferring of tokens has not already been done at a higher level)
5819							// then transfer tokens from prevNode which is being removed, to nextNode.
5820							transferViewTokens(prevNode, nextNode, parentElem, id, "_");
5821						}
5822						$(nodesToRemove).remove();
5823					}
5824					if (!viewToRemove._elCnt) {
5825						try {
5826							prevNode.parentNode.removeChild(prevNode); // (prevNode.parentNode is parentElem, except if jQuery Mobile or similar has inserted an intermediate wrapper
5827							nextNode.parentNode.removeChild(nextNode);
5828						} catch (e) {}
5829					}
5830					setArrayChangeLink(viewToRemove);
5831					for (bindId in viewToRemove._.bnds) {
5832						removeViewBinding(bindId);
5833					}
5834					delete viewStore[id];
5835				}
5836			}
5837
5838			var current, view, viewsCount,
5839				self = this,
5840				isArray = !self._.useKey,
5841				views = self.views;
5842
5843			if (isArray) {
5844				viewsCount = views.length;
5845			}
5846			if (index === undefined) {
5847				// Remove all child views
5848				if (isArray) {
5849					// views and data are arrays
5850					current = viewsCount;
5851					while (current--) {
5852						removeView(current);
5853					}
5854					self.views = [];
5855				} else {
5856					// views and data are objects
5857					for (view in views) {
5858						// Remove by key
5859						removeView(view);
5860					}
5861					self.views = {};
5862				}
5863			} else {
5864				if (itemsCount === undefined) {
5865					if (isArray) {
5866						// The parentView is data array view.
5867						// Set itemsCount to 1, to remove this item
5868						itemsCount = 1;
5869					} else {
5870						// Remove child view with key 'index'
5871						removeView(index);
5872						delete views[index];
5873					}
5874				}
5875				if (isArray && itemsCount
5876					&& (isMove || viewsCount - itemsCount === self.data.length)) { // If views not already synced to array (e.g. triggered by array.length propertyChange - jsviews/issues/301)
5877					current = index + itemsCount;
5878					// Remove indexed items (parentView is data array view);
5879					while (current-- > index) {
5880						removeView(current);
5881					}
5882					views.splice(index, itemsCount);
5883					if (!self._.sort) {
5884						self.fixIndex(index);
5885					}
5886				}
5887			}
5888		};
5889
5890		tagOrView.refresh = function() {
5891			var self = this,
5892				parent = self.parent;
5893
5894			if (parent) {
5895				renderAndLink(self, self.index, self.tmpl, parent.views, self.data, undefined, true);
5896				setArrayChangeLink(self);
5897			}
5898		};
5899
5900		tagOrView.fixIndex = function(fromIndex) {
5901			// Fixup index on following view items...
5902			var views = this.views,
5903				index = views.length;
5904			while (fromIndex < index--) {
5905				if (views[index].index !== index) {
5906					$observable(views[index]).setProperty("index", index);
5907					// This is fixing up index, but not key, and not index on child views. From child views, use view.getIndex()
5908				}
5909			}
5910		};
5911
5912		tagOrView.link = viewLink;
5913	}
5914}
5915
5916//========================
5917// JsViews-specific converters
5918//========================
5919
5920$converters.merge = function(val) {
5921	// Special converter used in data-linking to space-separated lists, such as className:
5922	// Currently only supports toggle semantics - and has no effect if toggle string is not specified
5923	// data-link="class{merge:boolExpr toggle=className}"
5924	var regularExpression,
5925		currentValue = this.linkCtx._val || "",
5926		toggle = this.tagCtx.props.toggle;
5927
5928	if (toggle) {
5929		// We are toggling the class specified by the toggle property,
5930		// and the boolean val binding is driving the insert/remove toggle
5931
5932		regularExpression = toggle.replace(/[\\^$.|?*+()[{]/g, "\\$&");
5933		// Escape any regular expression special characters (metacharacters) within the toggle string
5934		regularExpression = "(\\s(?=" + regularExpression + "$)|(\\s)|^)(" + regularExpression + "(\\s|$))";
5935		// Example: /(\s(?=myclass$)|(\s)|^)?(myclass(\s|$))/ - so matches (" myclass" or " " or ^ ) followed by ("myclass " or "myclass$") where ^/$ are beginning/end of string
5936		currentValue = currentValue.replace(new RegExp(regularExpression), "$2");
5937		val = currentValue + (val ? (currentValue && " ") + toggle : "");
5938	}
5939	return val;
5940};
5941
5942//========================
5943// JsViews-specific tags
5944//========================
5945
5946$tags("on", {
5947	attr: NONE,
5948	noVal: true, // This tag control does not bind to arg[0] - no default binding to current #data context
5949	init: function(tagCtx) {
5950		var content,
5951			tag = this,
5952			i = 0,
5953			args = tagCtx.args, // [events,] [selector,] handler
5954			l = args.length;
5955
5956		for (; i<l && !$isFunction(args[i]); i++); // Handler is first arg of type function
5957		tag._hi = l>i && i+1; // handler index
5958		if (tag._.inline) {
5959			if (!$sub.rTmpl.exec(content = tagCtx.tmpl.markup)) {
5960				// Inline {^{on}} tag with no content (or external template content) or with content containing
5961				// no HTML or JsRender tags: We will wrap the (text) content, or the operation name in a <button> element
5962				// (Otherwise we will attach the handler to the element content after data-linking)
5963				tag.template = "<button>" + ($.trim(content) || tagCtx.params.args[i] || "noop") + "</button>";
5964			}
5965			tag.attr = HTML;
5966		}
5967	},
5968	render: function() {
5969		var tagCtx = this.tagCtx;
5970		return this._.inline && tagCtx.render(tagCtx.view, true); // no arg, so renders against parentView.data
5971	},
5972	onAfterLink: function(tagCtx, linkCtx) {
5973		var handler, params, find, activeElem,
5974			tag = this,
5975			i = tag._hi,
5976			args = tagCtx.args, // [events,] [selector,] handler
5977			l = args.length,
5978			props = tagCtx.props,
5979			data = props.data,
5980			view = tagCtx.view,
5981			contextOb = props.context; // Context ('this' pointer) for attached handler
5982
5983		if (i) { // There is a handler
5984			handler = args[i-1];
5985			params = args.slice(i); // Subsequent args are params
5986			args = args.slice(0, i-1); // Preceding args (if any) are events and selector
5987			tag._sel = args[1]; // Selector for descendant elements - for delegated events on those elements, delegating to the activeElem
5988
5989			activeElem = tag.activeElem = tag.activeElem || $(tag._.inline
5990				? (tag._sel = args[1] || "*", tag.parentElem)
5991				// If inline, attach to child elements of tag parent element (filtered by selector argument if provided.
5992				// (In handler we'll filter out events from sibling elements preceding or following tag.)
5993				// This allows us to use the delegated pattern where the attached event works even for added elements satisfying the selector
5994				: linkCtx.elem);
5995
5996			if (!contextOb) {
5997				// Get the path for the preceding object (context object) of handler (which is the last arg), compile function
5998				// to return that context object, and run compiled function against data
5999				contextOb = /^(.*)[\.^][\w$]+$/.exec(tagCtx.params.args.slice(-params.length - 1)[0]);
6000				contextOb = contextOb && $sub.tmplFn(delimOpenChar1 + ":" + contextOb[1] + delimCloseChar0, view.tmpl, true)(linkCtx.data, view);
6001			}
6002
6003			if (tag._evs) {
6004				tag.onUnbind();
6005			}
6006
6007			activeElem.on(
6008				tag._evs = args[0] || "click", // events defaults to "click"
6009				tag._sel,
6010				data == undefined ? null : data,
6011				tag._hlr = function hndlr(ev) {
6012					var nodes, length,
6013						found = !tag._.inline;
6014
6015					if (!found) { // If inline, filter out events from sibling elements preceding or following tag.
6016						nodes = tag.contents("*");
6017						l = nodes.length;
6018						while (!found && l--) {
6019							if (nodes[l].contains(ev.target)) {
6020								found = true;
6021							}
6022						}
6023					}
6024					if (found) { // target elem is indeed within the tag, so call the {on} handler
6025						return handler.apply(contextOb || linkCtx.data, [].concat(
6026							params, // e.g. par1, par2
6027							ev,
6028							{change: ev.type, view: view, linkCtx: linkCtx},
6029							params.slice.call(arguments, 1) // If triggering event (e.g. jsv-domchange) has additional arguments after ev, pass them too
6030						));
6031						// for {on 'click' handler par1 par2} use handler(par1, par2, ev, domchangeEventArgs)
6032						// for {on 'jsv-domchange' handler par1 par2} use handler(par1, par2, ev, domchangeEventArgs, tagCtx, linkCtx, observableEventArgs)
6033					}
6034				}
6035			);
6036		}
6037	},
6038	onUpdate: function() {
6039		return false;
6040	},
6041	onUnbind: function() {
6042		var self = this;
6043		if (self.activeElem) {
6044			self.activeElem.off(self._evs, self._sel, self._hlr);
6045		}
6046	},
6047	flow: true,
6048	dataBoundOnly: true
6049});
6050
6051$extend($tags["for"], {
6052	//onUpdate: function(ev, eventArgs, newTagCtxs) {
6053		//Consider adding filtering for perf optimization. However the below prevents update on some scenarios which _should_ update - namely when there is another array on which for also depen
6053ds.
6054		//var i, l, tci, prevArg;
6055		//for (tci = 0; (prevArg = this.tagCtxs[tci]) && prevArg.args.length; tci++) {
6056		//	if (prevArg.args[0] !== newTagCtxs[tci].args[0]) {
6057		//		return true;
6058		//	}
6059		//}
6060		//return false;
6061	//},
6062	onArrayChange: function(ev, eventArgs, tagCtx, linkCtx) {
6063		var arrayView,
6064			target = ev.target,
6065			targetLength = target.length,
6066			tag = this,
6067			change = eventArgs.change;
6068		if (tag._.noVws // Child views not supported because target is not html - e.g. data-link="title{for ...}"
6069			|| tag.tagCtxs[1] && ( // There is an {{else}}
6070				change === "insert" && targetLength === eventArgs.items.length // inserting, and new length is same as inserted length, so going from 0 to n
6071				|| change === "remove" && !targetLength // removing , and new length 0, so going from n to 0
6072			)) {
6073			tag.refresh();
6074		} else {
6075			for (arrayView in tag._.arrVws) {
6076				arrayView = tag._.arrVws[arrayView];
6077				if (arrayView.data === target) {
6078					arrayView._.onArrayChange.apply(arrayView, arguments);
6079				}
6080			}
6081		}
6082		tag.domChange(tagCtx, linkCtx, eventArgs);
6083		ev.done = true;
6084	},
6085	onAfterLink: function(tagCtx, linkCtx) {
6086		var i, arrHandler, arrBinding, data,
6087			tag = this,
6088			arrayBindings = tag._ars || {},
6089			tagCtxs = tag.tagCtxs,
6090			l = tagCtxs.length,
6091			selected = tag.selected || 0;
6092
6093		for (i = 0; i <= selected; i++) {
6094			tagCtx = tagCtxs[i];        // loop through tagCtxs up to selected
6095			data = tagCtx.map
6096				? tagCtx.map.tgt        // 'data' is mapped data
6097				: tagCtx.args.length
6098					? tagCtx.args[0]    // or args[0]
6099					: tagCtx.view.data; // or defaults to current data.
6100
6101			if ((arrBinding = arrayBindings[i]) && data !== arrBinding[0]) { // Is there previous array data on this tagCtx, different from new data
6102				$observe(arrBinding[0], arrBinding[1], true); //unobserve previous array
6103				delete arrayBindings[i];
6104			}
6105			if (!arrayBindings[i] && $isArray(data)) {
6106				$observe(data, arrHandler = function(ev, eventArgs) {
6107					var tagCt = tagCtx;
6108					tag.onArrayChange(ev, eventArgs, tagCt, linkCtx);
6109				});
6110				arrayBindings[i] = [data, arrHandler]; // Store array data and arrayChangeHandler on tag._ars[i]
6111			}
6112		}
6113		for (i = selected + 1; i < l; i++) { // If there were previous bindings on later tagCtxs, remove them
6114			if (arrBinding = arrayBindings[i]) {
6115				$observe(arrBinding[0], arrBinding[1], true); //unobserve previous binding
6116				delete arrayBindings[i];
6117			}
6118		}
6119		tag._ars = arrayBindings;
6120	},
6121	onDispose: function() {
6122		var l, tag = this;
6123		for (l in tag._ars) {
6124			$observe(tag._ars[l][0], tag._ars[l][1], true); //unobserve
6125		}
6126	}
6127});
6128
6129$extend($tags["if"], {
6130	onUpdate: function(ev, eventArgs, newTagCtxs) {
6131		var tci, prevArg, different;
6132		for (tci = 0; (prevArg = this.tagCtxs[tci]); tci++) {
6133			different = prevArg.props.tmpl !== newTagCtxs[tci].props.tmpl || prevArg.args.length && !(prevArg = prevArg.args[0]) !== !newTagCtxs[tci].args[0];
6134			if ((!this.convert && !!prevArg) || different) {
6135				return different;
6136				// If there is not a change of template, and there is no converter, and newArg and prevArg are both truthy, return false to cancel update.
6137				// (Even if values on later elses are different, we still don't want to update, since rendered output would be unchanged)
6138				// If newArg and prevArg are different, return true, to update
6139				// If newArg and prevArg are both falsey, move to the next {{else ...}}
6140			}
6141		}
6142		// Boolean value of all args are unchanged (falsey), so return false to cancel update
6143		return false;
6144	},
6145	onAfterLink: function(tagCtx, linkCtx, ctx, ev, eventArgs) {
6146		if (eventArgs) {
6147			this.domChange(tagCtx, linkCtx, eventArgs);
6148		}
6149	}
6150});
6151
6152function observeProps(map, ev, eventArgs) {
6153	if (eventArgs.change === "set") {
6154		var target = map.tgt,
6155			l = target.length;
6156		while (l--) {
6157			if (target[l].key === eventArgs.path) {
6158				break;
6159			}
6160		}
6161		if (l === -1) {
6162			if (eventArgs.path && !eventArgs.remove) {
6163				$observable(target).insert({key: eventArgs.path, prop: eventArgs.value});
6164			}
6165		} else if (eventArgs.remove) {
6166			$observable(target).remove(l);
6167		} else {
6168			$observable(target[l]).setProperty("prop", eventArgs.value);
6169		}
6170	}
6171}
6172
6173function observeMappedProps(map, ev, eventArgs) {
6174	var item,
6175		source = map.src,
6176		change = eventArgs.change;
6177
6178	if (change === "set") {
6179		if (eventArgs.path === "prop") {
6180			$observable(source).setProperty(ev.target.key, eventArgs.value);
6181		} else { // path === "key"
6182			$observable(source).removeProperty(eventArgs.oldValue); // When key is modified observably, remove old one and set new one
6183			$observable(source).setProperty(eventArgs.value, ev.target.prop);
6184		}
6185	} else if (change === "remove") {
6186		item = eventArgs.items[0];
6187		$observable(source).removeProperty(item.key);
6188		delete source[item.key];
6189	} else if (change === "insert") {
6190		item = eventArgs.items[0];
6191		if (item.key) {
6192			$observable(source).setProperty(item.key, item.prop);
6193		}
6194	}
6195}
6196
6197function shallowArrayFilter(allPath /*, object, parentObs*/) { // Filter used by {{props}} for the mappedProps target array
6198	return allPath.indexOf(".") < 0;
6199}
6200
6201$tags("props", {
6202	baseTag: "for",
6203	dataMap: $views.map({
6204		getTgt: $tags.props.dataMap.getTgt,
6205		obsSrc: observeProps,
6206		obsTgt: observeMappedProps,
6207		tgtFlt: shallowArrayFilter
6208	}),
6209	flow: true
6210});
6211
6212//========================
6213// Extend jQuery namespace
6214//========================
6215
6216$extend($, {
6217
6218	//=======================
6219	// jQuery $.view() plugin
6220	//=======================
6221
6222	view: $view = function(node, inner, type) {
6223		// $.view() returns top view
6224		// $.view(node) returns view that contains node
6225		// $.view(selector) returns view that contains first selected element
6226		// $.view(nodeOrSelector, type) returns nearest containing view of given type
6227		// $.view(nodeOrSelector, "root") returns root containing view (child of top view)
6228		// $.view(nodeOrSelector, true, type) returns nearest inner (contained) view of given type
6229
6230		function getInnerView(nd, isVl) {
6231			if (nd) {
6232				vwInfos = viewInfos(nd, isVl, rOpenViewMarkers);
6233				for (j = 0, k = vwInfos.length; j < k; j++) {
6234					if ((view = viewStore[vwInfos[j].id]) && (view = view && type ? view.get(true, type) : view)) {
6235						break;
6236					}
6237				}
6238			}
6239		}
6240
6241		if (inner !== !!inner) {
6242			// inner not boolean, so this is view(nodeOrSelector, type)
6243			type = inner;
6244			inner = undefined;
6245		}
6246		var view, vwInfos, i, j, k, l, elems,
6247			level = 0,
6248			body = document.body;
6249
6250		if (node && node !== body && topView._.useKey > 1) {
6251			// Perf optimization for common cases
6252
6253			node = "" + node === node
6254				? $(node)[0]
6255				: node.jquery
6256					? node[0]
6257					: node;
6258
6259			if (node) {
6260				if (inner) {
6261					getInnerView(node._df, true);
6262					if (!view) {
6263						// Treat supplied node as a container element and return the first view encountered.
6264						elems = qsa ? node.querySelectorAll(bindElsSel) : $(bindElsSel, node).get();
6265						l = elems.length;
6266						for (i = 0; !view && i < l; i++) {
6267							getInnerView(elems[i]);
6268						}
6269					}
6270					return view;
6271				}
6272				while (node) {
6273					// Move back through siblings and up through parents to find preceding node which is a _prv (prevNode)
6274					// script marker node for a non-element-content view, or a _prv (first node) for an elCnt view
6275					if (vwInfos = viewInfos(node, undefined, rViewMarkers)) {
6276						l = vwInfos.length;
6277						while (l--) {
6278							view = vwInfos[l];
6279							if (view.open) {
6280								if (level < 1) {
6281									view = viewStore[view.id];
6282									return view && type ? view.get(type) : view || topView;
6283								}
6284								level--;
6285							} else {
6286								// level starts at zero. If we hit a view.close, then we move level to 1, and we don't return a view until
6287								// we are back at level zero (or a parent view with level < 0)
6288								level++;
6289							}
6290						}
6291					}
6292					node = node.previousSibling || node.parentNode;
6293				}
6294			}
6295		}
6296		return topView;
6297	},
6298
6299	link: $link,
6300	unlink: $unlink,
6301
6302	//=====================
6303	// override $.cleanData
6304	//=====================
6305	cleanData: function(elems) {
6306		if (elems.length && isCleanCall) {
6307			// Remove JsViews bindings. Also, remove from the DOM any corresponding script marker nodes
6308			clean(elems);
6309		}
6310		oldCleanData.apply($, arguments);
6311	}
6312});
6313
6314// Possible future addition - e.g. for ckeditor tag control
6315//$views.utility = {
6316//	validate: function(html) {
6317//		try {
6318//			topView.link(undefined, document.createElement("div"), undefined, undefined, html, undefined, undefined, 1);
6319//		}
6320//		catch (e) {
6321//			return e.message;
6322//		}
6323//	}
6324//};
6325
6326//===============================
6327// Extend jQuery instance plugins
6328//===============================
6329
6330$extend($.fn, {
6331	link: function(expr, from, context, noIteration, parentView, prevNode, nextNode) {
6332		return $link(expr, this, from, context, noIteration, parentView, prevNode, nextNode);
6333	},
6334	unlink: function() {
6335		return $unlink(this);
6336	},
6337	view: function(inner, type) {
6338		return $view(this[0], inner, type);
6339	}
6340});
6341
6342//==============================================================================
6343// Override jQuery methods that call our overridden cleanData, for disposal etc.
6344//==============================================================================
6345
6346$.each([HTML, "replaceWith", "empty", "remove"], function(i, name) {
6347	var oldFn = $.fn[name];
6348	$.fn[name] = function() {
6349		var result;
6350		isCleanCall = 1; // Make sure cleanData does disposal only when coming from these calls.
6351		try {
6352			result = oldFn.apply(this, arguments);
6353		}
6354		finally {
6355			isCleanCall = 0;
6356		}
6357		return result;
6358	};
6359});
6360
6361//===============
6362// Extend topView
6363//===============
6364
6365addLinkMethods($extend(topView = $sub.topView, {tmpl: {links: {}}}));
6366
6367viewStore = {0: topView}; // Top-level view
6368//===============
6369// Extend $.views
6370//===============
6371
6372$views.getCtx = function(param) { // Return value of ctx.foo, including for compiled contextual parameters, ~foo=expr
6373	if (param && param._cp) { // If this helper resource is a contextual parameter, ~foo=expr
6374		param =  param[1](param[0].data, param[0], $sub);
6375	}
6376	return param;
6377};
6378
6379//===================
6380// Extend $.views.sub
6381//===================
6382
6383$sub._cp = function(paramVal, params, view) { // Get compiled contextual parameters (or properties) ~foo=expr.
6384	if (view.linked) { // In JsViews, returns [view, linkFn] where linkFn is compiled function for expression
6385		params = delimOpenChar1 + ":" + params + delimCloseChar0;
6386		var tmpl = view.tmpl,
6387			links = topView.tmpl.links, // Use topView links, as for compiled top-level linking expressions. To do - should this ever get disposed?
6388			linkFn = links[params];
6389		if (!linkFn) {
6390			links[params] = linkFn = $sub.tmplFn(params, tmpl, true);
6391		}
6392		paramVal = [view, linkFn];
6393		paramVal._cp = true; // Flag that this is a contextual parameter
6394	}
6395	return paramVal; // In JsRender returns evaluated expression
6396};
6397
6398$sub._ceo = function cloneExprObjects(obs) {  // Clone exprObs so that each referenced contextual parameter ~foo uses its own exprOb instances
6399	var ob,
6400		clones = obs,
6401		l = obs.length;
6402	if (l) {
6403		clones = [];
6404		while (l--) {
6405			ob = obs[l];
6406			if (ob._jsv) {
6407				ob = $extend({}, ob);              // If an exprOb, clone it. If a string, keep as is
6408				ob.prm = cloneExprObjects(ob.prm); // Recursively clone exprObs in parameters, too
6409			}
6410			clones.unshift(ob);
6411		}
6412	}
6413	return clones;
6414};
6415
6416//=========================
6417// Extend $.views.settings
6418//=========================
6419
6420oldAdvSet = $sub.advSet;
6421
6422$sub.advSet = function() { // refresh advanced settings
6423	oldAdvSet();
6424	global._jsv = $subSettingsAdvanced._jsv
6425		? $extend(global._jsv || {}, { // create global _jsv, for accessing views, etc
6426				views: viewStore,
6427				bindings: bindingStore
6428			})
6429		: undefined; // In IE8 cannot do delete global._jsv
6430	$viewsLinkAttr = $subSettingsAdvanced.linkAttr;
6431	linkViewsSel = bindElsSel + ",[" + $viewsLinkAttr + "]";
6432	wrapMap = $subSettingsAdvanced._wm;
6433	wrapMap.optgroup = wrapMap.option;
6434	wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
6435	wrapMap.th = wrapMap.td;
6436};
6437
6438$viewsSettings.advanced({
6439	linkAttr: "data-link",
6440	useViews: false,
6441	noValidate: false,
6442	// wrapMap provide appropriate wrappers for inserting innerHTML, used in insertBefore
6443	// We have to close these tags to support XHTML (#13200)
6444	// TODO investigate whether more recent jQuery implementation using wrapMap in domManip/$().html() etc. is better optimized now...
6445	_wm: {
6446		option: [1, "<select multiple='multiple'>", "</select>"],
6447		legend: [1, "<fieldset>", "</fieldset>"],
6448		area: [1, "<map>", "</map>"],
6449		param: [1, "<object>", "</object>"],
6450		thead: [1, "<table>", "</table>"],
6451		tr: [2, "<table><tbody>", "</tbody></table>"],
6452		td: [3, "<table><tbody><tr>", "</tr></tbody></table>"],
6453		col: [2, "<table><tbody></tbody><colgroup>", "</colgroup></table>"],
6454		svg_ns: [1, "<svg>", "</svg>"],
6455		// IE6-8 can't serialize link, script, style, or any html5 (NoScope) tags,
6456		// unless wrapped in a div with non-breaking characters in front of it.
6457		div: $.support.htmlSerialize ? [0, "", ""] : [1, "X<div>", "</div>"]
6458	},
6459	_fe: {
6460		input: {
6461			from: inputAttrib, to: "value"
6462		},
6463		textarea: valueBinding,
6464		select: valueBinding,
6465		optgroup: {
6466			to: "label"
6467		}
6468	}
6469});
6470
6471return $;
6472}, window));

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