From 5d9b310b0d082e279f9b388059885c9a061fb61a Mon Sep 17 00:00:00 2001 From: BorisMoore Date: Wed, 23 Jul 2014 14:45:45 -0700 Subject: [PATCH] Commit 55 (Beta Candidate) - New 'trigger' feature: Two-way data-linking now allows opting in to additional trigger events, so that data updates on every keyup event, as text is entered. This applies to data-linked inputs, textareas, and content-editable elements, as well as to custom tags, such as {{textbox}} which provide two-way binding through linkedElem. For example, setting means that updates to the name property will happen as you type, not just on leaving the textbox. See unit tests for full range of options on this feature. - Many new unit tests - Several minor bug fixes - All samples updated to take advantage of new 'trigger' feature. --- .../computed-data-prototype.html | 8 +- .../features/observability/computed-data.html | 8 +- .../observability/computed-helper.html | 8 +- .../observability/observing-paths.html | 4 +- .../06_data-binding.html | 6 +- .../06_data-binding2.html | 2 +- .../jQueryConfDemosOct2011/07_observable.html | 2 +- .../07_observable2.html | 2 +- .../07_observable3.html | 2 +- .../11_editable-data.html | 4 +- .../jQueryConfDemosOct2011/13_converters.html | 2 +- demos/step-by-step/03_top-level-linking.html | 12 +- .../04_form-elements_if-binding.html | 4 +- jquery.observable.js | 37 +- jquery.observable.min.js | 2 +- jquery.views.js | 719 +++++++------- jquery.views.min.js | 4 +- jsrender.js | 140 +-- jsrender.min.js | 4 +- jsviews.js | 891 ++++++++++-------- jsviews.min.js | 4 +- test/unit-tests/tests-jsrender-no-jquery.js | 146 +-- test/unit-tests/tests-jsrender-with-jquery.js | 19 +- test/unit-tests/tests-jsviews.js | 741 ++++++++++++++- 24 files changed, 1759 insertions(+), 1012 deletions(-) diff --git a/demos/features/observability/computed-data-prototype.html b/demos/features/observability/computed-data-prototype.html index 54ee807..6a9a760 100644 --- a/demos/features/observability/computed-data-prototype.html +++ b/demos/features/observability/computed-data-prototype.html @@ -30,21 +30,21 @@

Computed data properties on prototype, using constructor

first: - + last: - + full name: - + reversed full name: - + {{!-- Parameterized setter not yet supported --}} diff --git a/demos/features/observability/computed-data.html b/demos/features/observability/computed-data.html index a1f7fa9..9af9c17 100644 --- a/demos/features/observability/computed-data.html +++ b/demos/features/observability/computed-data.html @@ -30,21 +30,21 @@

Computed data properties (declared on instance)

first: - + last: - + full name: - + reversed full name: - + {{!-- Parameterized setter not yet supported --}} diff --git a/demos/features/observability/computed-helper.html b/demos/features/observability/computed-helper.html index c0c47b2..cdb35af 100644 --- a/demos/features/observability/computed-helper.html +++ b/demos/features/observability/computed-helper.html @@ -30,17 +30,17 @@

Computed observables as helper functions (with declared dependencies, and op first: - + last: - + full name: - - + + reversed full name: diff --git a/demos/features/observability/observing-paths.html b/demos/features/observability/observing-paths.html index 6149efb..e5e4b10 100644 --- a/demos/features/observability/observing-paths.html +++ b/demos/features/observability/observing-paths.html @@ -46,8 +46,8 @@

Data-linking to deep paths - observing changes higher up the path

- - + + diff --git a/demos/jQueryConfDemosOct2011/06_data-binding.html b/demos/jQueryConfDemosOct2011/06_data-binding.html index 5adfbc5..05c8dc7 100644 --- a/demos/jQueryConfDemosOct2011/06_data-binding.html +++ b/demos/jQueryConfDemosOct2011/06_data-binding.html @@ -23,7 +23,7 @@

JsViews: Data binding

- + @@ -48,8 +48,6 @@

JsViews: Data binding

template.link( "#details", people ); // Alternative syntax: -// $("#details").link(template, people); -// or: // $.link(template, "#details", people); @@ -77,8 +75,6 @@

Script:

template.link( "#details", people ); // Alternative syntax: -// $("#details").link(template, people); -// or: // $.link(template, "#details", people); diff --git a/demos/jQueryConfDemosOct2011/06_data-binding2.html b/demos/jQueryConfDemosOct2011/06_data-binding2.html index cc989c5..d30e5c6 100644 --- a/demos/jQueryConfDemosOct2011/06_data-binding2.html +++ b/demos/jQueryConfDemosOct2011/06_data-binding2.html @@ -23,7 +23,7 @@

JsViews: Data binding - compact linking API syntax

- + diff --git a/demos/jQueryConfDemosOct2011/07_observable.html b/demos/jQueryConfDemosOct2011/07_observable.html index 885fc48..9102112 100644 --- a/demos/jQueryConfDemosOct2011/07_observable.html +++ b/demos/jQueryConfDemosOct2011/07_observable.html @@ -25,7 +25,7 @@

JsViews: Observable property changes

- + diff --git a/demos/jQueryConfDemosOct2011/07_observable2.html b/demos/jQueryConfDemosOct2011/07_observable2.html index f2fb836..507921f 100644 --- a/demos/jQueryConfDemosOct2011/07_observable2.html +++ b/demos/jQueryConfDemosOct2011/07_observable2.html @@ -25,7 +25,7 @@

JsViews: Observable collection changes

- + diff --git a/demos/jQueryConfDemosOct2011/07_observable3.html b/demos/jQueryConfDemosOct2011/07_observable3.html index bcf6b79..c18a0e2 100644 --- a/demos/jQueryConfDemosOct2011/07_observable3.html +++ b/demos/jQueryConfDemosOct2011/07_observable3.html @@ -27,7 +27,7 @@

JsViews: Two containers data-linked to the same array

- + diff --git a/demos/jQueryConfDemosOct2011/11_editable-data.html b/demos/jQueryConfDemosOct2011/11_editable-data.html index 6674fbd..1663918 100644 --- a/demos/jQueryConfDemosOct2011/11_editable-data.html +++ b/demos/jQueryConfDemosOct2011/11_editable-data.html @@ -48,10 +48,10 @@

JsViews: Fully editable data: JsViews

{^{for movies[selectedIndex]}}
Title:
-
+
Languages: Add
{^{for languages}} - + {{/for}}
{{/for}} diff --git a/demos/jQueryConfDemosOct2011/13_converters.html b/demos/jQueryConfDemosOct2011/13_converters.html index 5264523..8516d17 100644 --- a/demos/jQueryConfDemosOct2011/13_converters.html +++ b/demos/jQueryConfDemosOct2011/13_converters.html @@ -41,7 +41,7 @@

JsRender and JsViews: Use converters for custom encoding, data formatting, l - + diff --git a/demos/step-by-step/03_top-level-linking.html b/demos/step-by-step/03_top-level-linking.html index becb89b..e0e93e9 100644 --- a/demos/step-by-step/03_top-level-linking.html +++ b/demos/step-by-step/03_top-level-linking.html @@ -30,15 +30,15 @@

Data-Linking to static content in the page

- - - - + + + +

Cars: - Quantity - Price + Quantity + Price Total Value:

diff --git a/demos/step-by-step/04_form-elements_if-binding.html b/demos/step-by-step/04_form-elements_if-binding.html index 2517bb2..ab61220 100644 --- a/demos/step-by-step/04_form-elements_if-binding.html +++ b/demos/step-by-step/04_form-elements_if-binding.html @@ -31,11 +31,11 @@

Purchase a movie ticket

{^{if selectedMovie!=='none'}}
-
+
{^{if name}}
-
+
{{/if}} {{/if}} diff --git a/jquery.observable.js b/jquery.observable.js index dac6811..cbaf80f 100644 --- a/jquery.observable.js +++ b/jquery.observable.js @@ -1,5 +1,5 @@ /*! JsObservable v1.0.0-alpha: http://github.com/BorisMoore/jsviews and http://jsviews.com/jsviews -informal pre V1.0 commit counter: 54 (Beta Candidate) */ +informal pre V1.0 commit counter: 55 (Beta Candidate) */ /* * Subcomponent of JsViews * Data change events for data-linking @@ -23,19 +23,18 @@ informal pre V1.0 commit counter: 54 (Beta Candidate) */ var versionNumber = "v1.0.0-alpha", $eventSpecial = $.event.special, - $viewsSub = $.views + $sub = $.views ? $.views.sub // jsrender was loaded before jquery.observable : ($observable.sub = {}), // jsrender not loaded so store sub on $observable, and merge back in to $.views.sub in jsrender if loaded afterwards - cbBindingKey = 1, splice = [].splice, $isArray = $.isArray, $expando = $.expando, OBJECT = "object", PARSEINT = parseInt, rNotWhite = /\S+/g, - propertyChangeStr = $viewsSub.propChng = $viewsSub.propChng || "propertyChange",// These two settings can be overridden on settings after loading - arrayChangeStr = $viewsSub.arrChng = $viewsSub.arrChng || "arrayChange", // jsRender, and prior to loading jquery.observable.js and/or JsViews - cbBindingsStore = $viewsSub._cbBnds = $viewsSub._cbBnds || {}, + propertyChangeStr = $sub.propChng = $sub.propChng || "propertyChange",// These two settings can be overridden on settings after loading + arrayChangeStr = $sub.arrChng = $sub.arrChng || "arrayChange", // jsRender, and prior to loading jquery.observable.js and/or JsViews + cbBindingsStore = $sub._cbBnds = $sub._cbBnds || {}, observeStr = propertyChangeStr + ".observe", $isFunction = $.isFunction, observeObjKey = 1, @@ -45,7 +44,7 @@ informal pre V1.0 commit counter: 54 (Beta Candidate) */ //========================== Top-level functions ========================== - $viewsSub.getDeps = function() { + $sub.getDeps = function() { var args = arguments; return function() { var arg, dep, @@ -59,8 +58,8 @@ informal pre V1.0 commit counter: 54 (Beta Candidate) */ } } return deps; - } - } + }; + }; function $observable(data) { return $isArray(data) @@ -222,7 +221,7 @@ informal pre V1.0 commit counter: 54 (Beta Candidate) */ }, filter: filter, ns: initialNs - } + }; } $(boundObOrArr).on(namespace, null, evData, onObservableChange); if (cbBindings) { @@ -255,7 +254,7 @@ informal pre V1.0 commit counter: 54 (Beta Candidate) */ } } callback(ev, eventArgs); - } + }; } function bindArray(arr, unbind, isArray, relPath) { @@ -280,7 +279,7 @@ informal pre V1.0 commit counter: 54 (Beta Candidate) */ } } - var i, p, skip, parts, prop, path, isArray, dep, unobserve, callback, cbId, el, data, events, contextCb, items, cbBindings, depth, innerCb, parentObs, + var i, p, skip, parts, prop, path, dep, unobserve, callback, cbId, el, data, events, contextCb, items, cbBindings, depth, innerCb, parentObs, allPath, filter, initialNs, initNsArr, initNsArrLen, allowArray = this != false, // If this === false, this is a call from observeAndBind - doing binding of datalink expressions. We don't bind // arrayChange events in this scenario. Instead, {^{for}} and similar do specific arrayChange binding to the tagCtx.args[0] value, in onAfterLink. @@ -534,7 +533,7 @@ informal pre V1.0 commit counter: 54 (Beta Candidate) */ } } } - } + }; } //========================== Initialize ========================== @@ -591,7 +590,7 @@ informal pre V1.0 commit counter: 54 (Beta Candidate) */ parentObs = oldParentObs; } - var l, prop, isObject, newAllPath, newObject; + var l, isObject, newAllPath, newObject; if (typeof object === OBJECT) { isObject = $isArray(object) ? "" : "*"; @@ -628,7 +627,7 @@ informal pre V1.0 commit counter: 54 (Beta Candidate) */ } } - $viewsSub.DataMap = DataMap; + $sub.DataMap = DataMap; $.observable = $observable; $observable._fltr = function(key, object, allPath, filter) { var prop = (filter && $isFunction(filter) @@ -641,7 +640,7 @@ informal pre V1.0 commit counter: 54 (Beta Candidate) */ : prop; } return typeof prop === OBJECT && prop; - } + }; $observable.Object = ObjectObservable; $observable.Array = ArrayObservable; @@ -660,7 +659,7 @@ informal pre V1.0 commit counter: 54 (Beta Candidate) */ }, setProperty: function(path, value, nonStrict) { - var leaf, key, pair, parts, + var key, pair, parts, self = this, object = self._data; @@ -672,7 +671,7 @@ informal pre V1.0 commit counter: 54 (Beta Candidate) */ key = path.length; while (key--) { pair = path[key]; - self.setProperty(pair.name, pair.value, nonStrict === undefined || nonStrict) //If nonStrict not specified, default to true; + self.setProperty(pair.name, pair.value, nonStrict === undefined || nonStrict); //If nonStrict not specified, default to true; } } else if ("" + path !== path) { // Object representation where property name is path and property value is value. @@ -692,7 +691,7 @@ informal pre V1.0 commit counter: 54 (Beta Candidate) */ }, removeProperty: function(path) { - this.setProperty(path, remove) + this.setProperty(path, remove); return this; }, diff --git a/jquery.observable.min.js b/jquery.observable.min.js index 78f99ec..ff3dce6 100644 --- a/jquery.observable.min.js +++ b/jquery.observable.min.js @@ -1,5 +1,5 @@ /*! JsObservable v1.0.0-alpha: http://github.com/BorisMoore/jsviews and http://jsviews.com/jsviews -informal pre V1.0 commit counter: 54 (Beta Candidate) */ +informal pre V1.0 commit counter: 55 (Beta Candidate) */ (function(n,t,i){"use strict";function f(n){return r(n)?new k(n):new b(n)}function b(n){return this._data=n,this}function k(n){return this._data=n,this}function lt(n){return r(n)?[n]:n}function ut(n,t){n=r(n)?n:[n];for(var i,e=t,o=e,h=n.length,f=[],s=0;s1;)e=e[s.shift()];e&&o._setProperty(e,s[0],t,u)}return o},removeProperty:function(n){return this.setProperty(n,rt),this},_setProperty:function(n,t,r,f){var o,s,h,e=t?n[t]:n;u(e)&&e.set&&(s=e,o=e.set===!0?e:e.set,e=e.call(n));(e!==r||f&&e!=r)&&(!(e instanceof Date)||e>r||e-1&&n<=i.length&&(t=r(t)?t:[t],t.length&&this._insert(n,t)),this},_insert:function(n,t){var i=this._data,r=i.length;p.apply(i,[n,0].concat(t));this._trigger({change:"insert",index:n,items:t},r)},remove:function(n,t){var r,u=this._data;return n===i&&(n=u.length-1),n=h(n),t=t?h(t):t===0?0:1,t>-1&&n>-1&&(r=u.slice(n,n+t),t=r.length,t&&this._remove(n,t,r)),this},_remove:function(n,t,i){var r=this._data,u=r.length;r.splice(n,t);this._trigger({change:"remove",index:n,items:i},u)},move:function(n,t,i){if(i=i?h(i):i===0?0:1,n=h(n),t=h(t),i>0&&n>-1&&t>-1&&n!==t){var r=this._data.slice(n,n+i);i=r.length;i&&this._move(n,t,i,r)}return this},_move:function(n,t,i,r){var u=this._data,f=u.length;u.splice(n,i);u.splice.apply(u,[t,0].concat(r));this._trigger({change:"move",oldIndex:n,index:t,items:r},f)},refresh:function(n){var t=this._data.slice();return this._refresh(t,n),this},_refresh:function(n,t){var i=this._data,r=i.length;p.apply(i,[0,i.length].concat(t));this._trigger({change:"refresh",oldItems:n},r)},_trigger:function(n,i){var r=this._data,u=r.length,f=t([r]);f.triggerHandler(l,n);u!==i&&f.triggerHandler(c,{change:"set",path:"length",value:u,oldValue:i})}};nt[c]=nt[l]={remove:function(n){var r,u,f,e,o,i=n.data;if(i&&(i.off=1,i=i.cb)&&(r=y[i._cId])){for(f=t._data(this).events[n.type],e=f.length;e--&&!u;)u=(o=f[e].data)&&o.cb===i;u||(delete r[t.data(this,"obId")],ft(r,i._cId))}}}}})(this,this.jQuery); /* //# sourceMappingURL=jquery.observable.min.js.map diff --git a/jquery.views.js b/jquery.views.js index 1c3d0e9..6678fa1 100644 --- a/jquery.views.js +++ b/jquery.views.js @@ -1,5 +1,5 @@ /*! JsViews v1.0.0-alpha: http://github.com/BorisMoore/jsviews and http://jsviews.com/jsviews -informal pre V1.0 commit counter: 54 (Beta Candidate) */ +informal pre V1.0 commit counter: 55 (Beta Candidate) */ /* * Interactive data-driven views using templates and data-linking. * Requires jQuery and jsrender.js (next-generation jQuery Templates, optimized for pure string-based rendering) @@ -21,25 +21,24 @@ informal pre V1.0 commit counter: 54 (Beta Candidate) */ var versionNumber = "v1.0.0-alpha", requiresStr = "JsViews requires ", activeBody, $view, rTag, delimOpenChar0, delimOpenChar1, delimCloseChar0, delimCloseChar1, linkChar, noDomLevel0, error, - propsTag, $viewsLinkAttr, linkViewsSel, wrapMap, topView, viewStore, + $viewsLinkAttr, linkMethods, linkViewsSel, wrapMap, topView, viewStore, document = global.document, $views = $.views, - $viewsSub = $views.sub, + $sub = $views.sub, $viewsSettings = $views.settings, - $extend = $viewsSub.extend, + $extend = $sub.extend, $isFunction = $.isFunction, - $templates = $views.templates, $converters = $views.converters, + $tags = $views.tags, $observable = $.observable, $observe = $observable.observe, jsvAttrStr = "data-jsv", // These two settings can be overridden on settings after loading jsRender, and prior to loading jquery.observable.js and/or JsViews - propertyChangeStr = $viewsSub.propChng = $viewsSub.propChng || "propertyChange", - arrayChangeStr = $viewsSub.arrChng = $viewsSub.arrChng || "arrayChange", + propertyChangeStr = $sub.propChng = $sub.propChng || "propertyChange", + arrayChangeStr = $sub.arrChng = $sub.arrChng || "arrayChange", - cbBindingsStore = $viewsSub._cbBnds = $viewsSub._cbBnds || {}, elementChangeStr = "change.jsv", onBeforeChangeStr = "onBeforeChange", onAfterChangeStr = "onAfterChange", @@ -61,7 +60,7 @@ informal pre V1.0 commit counter: 54 (Beta Candidate) */ isCleanCall = 0, oldCleanData = $.cleanData, oldJsvDelimiters = $viewsSettings.delimiters, - syntaxError = $viewsSub.syntaxErr, + syntaxError = $sub.syntaxErr, rFirstElem = /<(?!script)(\w+)(?:[^>]*(on\w+)\s*=)?[^>]*>/, rEscapeQuotes = /['"\\]/g, // Escape quotes and \ character safeFragment = document.createDocumentFragment(), @@ -108,17 +107,9 @@ informal pre V1.0 commit counter: 54 (Beta Candidate) */ //=============== // Event handlers //=============== - function updateTag(value) { - var linkedElem = this.linkedElem; - if (linkedElem) { - elemChangeHandler({ - target: linkedElem[0] - }, undefined, value); - } - } function elemChangeHandler(ev, params, sourceValue) { - var setter, cancel, fromAttr, linkCtx, cvtBack, cnvtName, target, $source, view, binding, bindings, l, oldLinkCtx, onBeforeChange, onAfterChange, tag, to, eventArgs, + var setter, cancel, fromAttr, linkCtx, cvtBack, cnvtName, target, $source, view, binding, oldLinkCtx, onBeforeChange, onAfterChange, tag, to, eventArgs, source = ev.target, bindings = source._jsvBnd, splitBindings = /&(\d+)\+?/g; @@ -150,7 +141,7 @@ informal pre V1.0 commit counter: 54 (Beta Candidate) */ if ($isFunction(cnvtName)) { cvtBack = cnvtName; } else { - cvtBack = view.getRsc("converters", cnvtName) + cvtBack = view.getRsc("converters", cnvtName); } } if (cvtBack) { @@ -192,6 +183,14 @@ informal pre V1.0 commit counter: 54 (Beta Candidate) */ } } } + return false; + } + + function elemChangeNoValidateHandler(ev) { + var noVal = $viewsSettings.noValidate; + $viewsSettings.noValidate = true; + elemChangeHandler(ev); + $viewsSettings.noValidate = noVal; } function propertyChangeHandler(ev, eventArgs, linkFn) { @@ -201,9 +200,7 @@ informal pre V1.0 commit counter: 54 (Beta Candidate) */ target = linkCtx.elem, cvt = linkCtx.convert, parentElem = target.parentNode, - targetElem = parentElem, view = linkCtx.view, - oldCtx = view.ctx, oldLinkCtx = view.linkCtx, onEvent = view.hlp(onBeforeChangeStr); @@ -227,9 +224,8 @@ informal pre V1.0 commit counter: 54 (Beta Candidate) */ ); if (sourceValue && sourceValue.error !== undefined) { // tagCtxs for tag with onError=... and error was thrown. We will update with the fallback value from onError - sourceValue = sourceValue.error - } - else if (tag) { + sourceValue = sourceValue.error; + } else if (tag) { // Existing tag instance sourceValue = sourceValue[0] ? sourceValue : [sourceValue]; noUpdate = eventArgs && tag.onUpdate && tag.onUpdate(ev, eventArgs, sourceValue) === false; @@ -252,8 +248,8 @@ informal pre V1.0 commit counter: 54 (Beta Candidate) */ return; } - sourceValue = tag.tagName.slice(-1) === ":" // Call convertVal if it is a {{cvt:...}} - otherwise call renderTag - ? $views._cnvt(tag.tagName.slice(0, -1), view, sourceValue[0]) + sourceValue = tag.tagName === ":" // Call convertVal if it is a {{cvt:...}} - otherwise call renderTag + ? $views._cnvt(tag.cvt, view, sourceValue[0]) : $views._tag(tag, view, view.tmpl, sourceValue, true); } else if (linkFn._tag) { // For {{: ...}} without a convert or convertBack, we already have the sourceValue, and we are done @@ -263,7 +259,8 @@ informal pre V1.0 commit counter: 54 (Beta Candidate) */ sourceValue = cvt // Call convertVal if it is a {{cvt:...}} - otherwise call renderTag ? $views._cnvt(cvt, view, sourceValue) // convertVal : $views._tag(linkFn._tag, view, view.tmpl, sourceValue, true); // renderTag - tag = view._.tag; // In both convertVal and renderTag we have instantiated a tag + + tag = linkCtx.tag; // In both convertVal and renderTag we have instantiated a tag attr = linkCtx.attr || attr; // linkCtx.attr may have been set to tag.attr during tag instantiation in renderTag } @@ -272,16 +269,12 @@ informal pre V1.0 commit counter: 54 (Beta Candidate) */ && (onEvent = view.hlp(onAfterChangeStr))) { onEvent.call(linkCtx, ev, eventArgs); } - } - if (tag) { - tag.contents = getContents; - tag.nodes = getNodes; - tag.childTags = getChildTags; - tag.update = updateTag; - tag.refresh = refreshTag; - callAfterLink(tag, tag.tagCtx); + if (tag) { + callAfterLink(tag, tag.tagCtx); + } } + observeAndBind(linkCtx, source, target); // Remove dynamically added linkCtx from view @@ -336,7 +329,7 @@ informal pre V1.0 commit counter: 54 (Beta Candidate) */ // When called for a tag, either in tag.refresh() or propertyChangeHandler(), returns a promise (and supports async) // When called (in propertyChangeHandler) for target HTML returns true // When called (in propertyChangeHandler) for other targets returns boolean for "changed" - var setter, changed, prevNode, nextNode, promise, nodesToRemove, useProp, tokens, id, openIndex, closeIndex, + var setter, prevNode, nextNode, promise, nodesToRemove, useProp, tokens, id, openIndex, closeIndex, renders = sourceValue !== undefined, source = linkCtx.data, target = tag && tag.parentElem || linkCtx.elem, @@ -675,7 +668,7 @@ informal pre V1.0 commit counter: 54 (Beta Candidate) */ // within view.link, prior to inserting into the DOM. Linking will then bind based on these markers in the DOM. // Added view markers: #m_...VIEW.../m_ // Added tag markers: #m^...TAG..../m^ - var id, tag, end, elem; + var id, tag, end; if (tmplBindingKey) { // This is a binding marker for a data-linked tag {^{...}} end = "^`"; @@ -691,7 +684,6 @@ informal pre V1.0 commit counter: 54 (Beta Candidate) */ flow: true }; id = tag._tgId; -//?? tag.refresh = refreshTag; if (!id) { bindingStore[id = bindingKey++] = tag; // Store the tag temporarily, ready for databinding. // During linking, in addDataBinding, the tag will be attached to the linkCtx, @@ -815,15 +807,10 @@ informal pre V1.0 commit counter: 54 (Beta Candidate) */ activeBody = document.body; $(activeBody) .on(elementChangeStr, elemChangeHandler) - .on('blur', '[contenteditable]', function(ev) { - var noVal = $viewsSettings.noValidate; - $viewsSettings.noValidate = true; - elemChangeHandler(ev) - $viewsSettings.noValidate = noVal; - }); + .on('blur', '[contenteditable]', elemChangeNoValidateHandler); } - var i, k, html, vwInfos, view, placeholderParent, targetEl, oldCtx, oldData, + var i, k, html, vwInfos, view, placeholderParent, targetEl, oldCtx, onRender = addBindingMarkers, replaceMode = context && context.target === "replace", l = to.length; @@ -1066,8 +1053,6 @@ informal pre V1.0 commit counter: 54 (Beta Candidate) */ // '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 // its placeholder is the 'previousSibling' of the prevNode, which is also the nextNode. if (vwInfos) { - //targetParent = targetParent || targetElem && targetElem.previousSibling; - //targetParent = targetElem ? targetElem.previousSibling : targetParent; if (vwInfos._tkns.charAt(0) === "@") { // We are processing newly inserted content. This is a special script element that was created in convertMarkers() to process deferred bindings, // and inserted following the target parent element - because no element tags (outside elCnt) were encountered to carry those binding tokens. @@ -1234,6 +1219,9 @@ informal pre V1.0 commit counter: 54 (Beta Candidate) */ bindEls.push([elem]); // A data-linked element so add to bindEls too } } + if (self === topView && html === undefined && parentNode.getAttribute($viewsLinkAttr)) { + bindEls.push([parentNode]); // Support data-linking top-level element directly (not within a data-linked container) + } // Remove temporary marker script nodes they were added by markPrevOrNextNode unmarkPrevOrNextNode(prevNode, elCnt); @@ -1301,11 +1289,6 @@ informal pre V1.0 commit counter: 54 (Beta Candidate) */ // Add data binding tagCtx = tag.tagCtx; view = tagCtx.view; - tag.contents = getContents; - tag.nodes = getNodes; - tag.childTags = getChildTags; - tag.update = updateTag; - tag.refresh = refreshTag; delete tag._.linking; if (!tag._.bound) { tag._.bound = true; @@ -1325,8 +1308,8 @@ informal pre V1.0 commit counter: 54 (Beta Candidate) */ } //==== /end of nested functions ==== - var inTag, linkCtx, tag, i, l, j, len, elems, elem, view, vwInfos, vwInfo, linkInfo, prevNodes, token, prevView, nextView, node, tags, deep, tagName, tagCtx, cvt, - tagDepth, get, depth, fragment, copiedNode, firstTag, parentTag, isVoid, wrapper, div, tokens, elCnt, prevElCnt, htmlTag, ids, prevIds, found, lazyLink, linkedElem, + var inTag, linkCtx, tag, i, l, j, len, elems, elem, view, vwInfo, linkInfo, prevNodes, token, prevView, nextView, node, tags, deep, tagName, tagCtx, + tagDepth, get, depth, fragment, copiedNode, firstTag, parentTag, isVoid, wrapper, div, tokens, elCnt, prevElCnt, htmlTag, ids, prevIds, found, lazyLink, self = this, thisId = self._.id + "_", defer = "", @@ -1504,7 +1487,7 @@ informal pre V1.0 commit counter: 54 (Beta Candidate) */ linkCtx = { data: data, // source - elem: tag && tag._elCnt ? tag.parentElem : node, // target + elem: node, // target view: currentView, ctx: context || currentView.ctx, attr: attr, @@ -1522,7 +1505,6 @@ informal pre V1.0 commit counter: 54 (Beta Candidate) */ // There is a convertBack function trimLen = - convertBack.length -1; tagExpr = tagExpr.slice(0, trimLen - 1) + delimCloseChar0; // Remove the convertBack string from expression. - params = params.slice(0, trimLen); } } if (convertBack === null) { @@ -1537,7 +1519,7 @@ informal pre V1.0 commit counter: 54 (Beta Candidate) */ linkCtx.expr = attr + tagExpr; linkFn = tmpl.links[tagExpr]; if (!linkFn) { - tmpl.links[tagExpr] = linkFn = $viewsSub.tmplFn(tagExpr, tmpl, true, convertBack); + tmpl.links[tagExpr] = linkFn = $sub.tmplFn(tagExpr, tmpl, true, convertBack); } linkCtx.fn = linkFn; if (!attr && convertBack !== undefined) { @@ -1656,85 +1638,116 @@ informal pre V1.0 commit counter: 54 (Beta Candidate) */ // Methods for views and tags //=========================== - function getContents(deep, select) { - // For a view or a tag, return jQuery object with the content nodes, - if (deep !== !!deep) { - // deep not boolean, so this is getContents(selector) - select = deep; + linkMethods = { + contents: function(deep, select) { + // For a view or a tag, return jQuery object with the content nodes, + if (deep !== !!deep) { + // deep not boolean, so this is getContents(selector) + select = deep; + deep = undefined; + } + var filtered, + nodes = $(this.nodes()); + if (nodes[0]) { + filtered = select ? nodes.filter(select) : nodes; + nodes = deep && select ? filtered.add(nodes.find(select)) : filtered; + } + return nodes; + }, + + nodes: function(withMarkers, prevNode, nextNode) { + // For a view or a tag, return top-level nodes + // Do not return any script marker nodes, unless withMarkers is true + // Optionally limit range, by passing in prevNode or nextNode parameters + + var node, + self = this, + elCnt = self._elCnt, + prevIsFirstNode = !prevNode && elCnt, + nodes = []; + + prevNode = prevNode || self._prv; + nextNode = nextNode || self._nxt; + + node = prevIsFirstNode + ? (prevNode === self._nxt + ? self.parentElem.lastSibling + : prevNode) + : (self._.inline === false + ? prevNode || self.linkCtx.elem.firstChild + : prevNode && prevNode.nextSibling); + + while (node && (!nextNode || node !== nextNode)) { + if (withMarkers || elCnt || node.tagName !== "SCRIPT") { + // All the top-level nodes in the view + // (except script marker nodes, unless withMarkers = true) + // (Note: If a script marker node, viewInfo.elCnt undefined) + nodes.push(node); + } + node = node.nextSibling; + } + return nodes; + }, + + childTags: function(deep, tagName) { + // For a view or a tag, return child tags - at any depth, or as immediate children only. + if (deep !== !!deep) { + // deep not boolean, so this is childTags(tagName) - which looks for top-level tags of given tagName + tagName = deep; deep = undefined; } - var filtered, - nodes = $(this.nodes()); - if (nodes[0]) { - filtered = select ? nodes.filter(select) : nodes; - nodes = deep && select ? filtered.add(nodes.find(select)) : filtered; - } - return nodes; - } - function getNodes(withMarkers, prevNode, nextNode) { - // For a view or a tag, return top-level nodes - // Do not return any script marker nodes, unless withMarkers is true - // Optionally limit range, by passing in prevNode or nextNode parameters + var self = this, + 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 + prevNode = self._prv, + elCnt = self._elCnt, + tags = []; + + if (prevNode) { + view.link( + undefined, + self.parentElem, + elCnt ? prevNode.previousSibling : prevNode, + self._nxt, + undefined, + {get:{tags:tags, deep: deep, name: tagName, id: elCnt && self._tgId}} + ); + } + return tags; + }, - var node, - self = this, - elCnt = self._elCnt, - prevIsFirstNode = !prevNode && elCnt, - nodes = []; - - prevNode = prevNode || self._prv; - nextNode = nextNode || self._nxt; - - node = prevIsFirstNode - ? (prevNode === self._nxt - ? self.parentElem.lastSibling - : prevNode) - : (self._.inline === false - ? prevNode || self.linkCtx.elem.firstChild - : prevNode && prevNode.nextSibling); - - while (node && (!nextNode || node !== nextNode)) { - if (withMarkers || elCnt || node.tagName !== "SCRIPT") { - // All the top-level nodes in the view - // (except script marker nodes, unless withMarkers = true) - // (Note: If a script marker node, viewInfo.elCnt undefined) - nodes.push(node); - } - node = node.nextSibling; - } - return nodes; - } + refresh: function(sourceValue) { + var promise, attr, + tag = this, + linkCtx = tag.linkCtx, + view = tag.tagCtx.view; - function getChildTags(deep, tagName) { - // For a view or a tag, return child tags - at any depth, or as immediate children only. - if (deep !== !!deep) { - // deep not boolean, so this is childTags(tagName) - which looks for top-level tags of given tagName - tagName = deep; - deep = undefined; - } + if (tag.disposed) { error("Removed tag"); } + if (sourceValue === undefined) { + sourceValue = $views._tag(tag, view, view.tmpl, mergeCtxs(tag), true); // Get rendered HTML for tag, based on refreshed tagCtxs + } + if (sourceValue + "" === sourceValue) { + // If no rendered content, sourceValue will not be a string (can be 0 or undefined) + attr = tag._.inline ? htmlStr : (linkCtx.attr || defaultAttr(tag.parentElem, true)); + promise = updateContent(sourceValue, linkCtx, attr, tag); + } - var self = this, - 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 - prevNode = self._prv, - elCnt = self._elCnt, - tags = []; + callAfterLink(tag, tag.tagCtx); + return promise || tag; + }, - if (prevNode) { - view.link( - undefined, - self.parentElem, - elCnt ? prevNode.previousSibling : prevNode, - self._nxt, - undefined, - {get:{tags:tags, deep: deep, name: tagName, id: elCnt && self._tgId}} - ); + update: function(value) { + var linkedElem = this.linkedElem; + if (linkedElem) { + elemChangeHandler({ + target: linkedElem[0] + }, undefined, value); + } } - return tags; - } + }; function callAfterLink(tag, tagCtx) { - var cvt, linkedElem, elem, radioButtons, val, bindings, binding, i, l, linkedTag, + var linkedElem, elem, radioButtons, val, bindings, i, l, linkedTag, oldTrig, newTrig, view = tagCtx.view, linkCtx = tag.linkCtx = tag.linkCtx || { tag: tag, @@ -1747,12 +1760,12 @@ informal pre V1.0 commit counter: 54 (Beta Candidate) */ tag.onAfterLink(tagCtx, linkCtx); } linkedElem = tag.targetTag ? tag.targetTag.linkedElem : tag.linkedElem; - if (linkedElem && (elem = linkedElem[0])) { + if (elem = linkedElem && linkedElem[0]) { if (radioButtons = tag._.radio) { linkedElem = linkedElem.children("input[type=radio]"); } if (radioButtons || !tag._.chging) { - val = $viewsSub.cvt(tag, tag.convert)[0]; + val = $sub.cvt(tag, tag.convert)[0]; if (radioButtons || elem !== linkCtx.elem) { l = linkedElem.length; @@ -1789,6 +1802,18 @@ informal pre V1.0 commit counter: 54 (Beta Candidate) */ } } } + if (elem = elem || tag.tagName === ":" && linkCtx.elem) { + oldTrig = elem._jsvTr; + newTrig = tagCtx.props.trigger; + newTrig = newTrig === true ? 'keyup' : newTrig; + if (oldTrig !== newTrig) { + elem._jsvTr = newTrig + elem = $(elem); + + oldTrig && elem.off(oldTrig, elemChangeHandler); + newTrig && elem.on(newTrig, elemChangeHandler); + } + } } function bindTo(binding, cvtBk) { @@ -1807,7 +1832,7 @@ informal pre V1.0 commit counter: 54 (Beta Candidate) */ paths = bindto; } pathIndex = paths.length; - while (pathIndex && "" + (lastPath = paths[--pathIndex]) !== lastPath) {}; // If the lastPath is an object (e.g. with _jsvOb property), take preceding one + while (pathIndex && "" + (lastPath = paths[--pathIndex]) !== lastPath) {} // If the lastPath is an object (e.g. with _jsvOb property), take preceding one if (lastPath && (!lct.tag || lct.tag.tagCtx.args.length)) { lastPath = lastPath.split("^").join("."); // We don't need the "^" since binding has happened. For to binding, require just "."s binding.to = lastPath.charAt(0) === "." @@ -1833,7 +1858,7 @@ informal pre V1.0 commit counter: 54 (Beta Candidate) */ l = tagCtxs.length, refresh = !newCtxs; - newCtxs = newCtxs || tag._.bnd.call(view.tmpl, view.data, view, $views); + newCtxs = newCtxs || tag._.bnd.call(view.tmpl, (tag.linkCtx || view).data, view, $views); while (l--) { tagCtx = tagCtxs[l]; @@ -1848,33 +1873,13 @@ informal pre V1.0 commit counter: 54 (Beta Candidate) */ return tagCtxs; } - function refreshTag(sourceValue) { - var promise, attr, - tag = this, - linkCtx = tag.linkCtx, - view = tag.tagCtx.view; - - if (tag.disposed) { error("Removed tag"); } - if (sourceValue === undefined) { - sourceValue = $views._tag(tag, view, view.tmpl, mergeCtxs(tag), true); // Get rendered HTML for tag, based on refreshed tagCtxs - } - if (sourceValue + "" === sourceValue) { - // If no rendered content, sourceValue will not be a string (can be 0 or undefined) - attr = tag._.inline ? htmlStr : (linkCtx.attr || defaultAttr(tag.parentElem, true)); - promise = updateContent(sourceValue, linkCtx, attr, tag); - } - - callAfterLink(tag, tag.tagCtx); - return promise || tag; - } - //========= // Disposal //========= function clean(elems) { // Remove data-link bindings, or contained views - var j, l, l2, elem, vwInfos, vwItem, bindings, + var l, elem, bindings, elemArray = [], len = elems.length, i = len; @@ -1907,7 +1912,7 @@ informal pre V1.0 commit counter: 54 (Beta Candidate) */ function removeViewBinding(bindId, linkedElemTag, elem) { // Unbind - var objId, linkCtx, tag, object, obsId, tagCtxs, l, map, + var objId, linkCtx, tag, object, obsId, tagCtxs, l, map, linkedElem, trigger, binding = bindingStore[bindId]; if (linkedElemTag) { @@ -1916,6 +1921,7 @@ informal pre V1.0 commit counter: 54 (Beta Candidate) */ delete linkedElemTag.linkedElem; } } else if (binding) { + delete bindingStore[bindId]; // Delete already, so call to onDispose handler below cannot trigger recursive deletion (through recursive call to jQuery cleanData) for (objId in binding.bnd) { object = binding.bnd[objId]; obsId = ".obs" + binding.cbId; @@ -1937,19 +1943,26 @@ informal pre V1.0 commit counter: 54 (Beta Candidate) */ } } } + linkedElem = tag.linkedElem; + linkedElem = linkedElem && linkedElem[0] || linkCtx.elem; + + if (trigger = linkedElem && linkedElem._jsvTr) { + $(linkedElem).off(trigger, elemChangeHandler); + linkedElem._jsvTr = undefined; + } + if (tag.onDispose) { tag.onDispose(); } + if (!tag._elCnt) { tag._prv && tag._prv.parentNode.removeChild(tag._prv); tag._nxt && tag._nxt.parentNode.removeChild(tag._nxt); } - tag.disposed = true; } delete linkCtx.view._.bnds[bindId]; } - delete bindingStore[bindId]; - delete $viewsSub._cbBnds[binding.cbId]; + delete $sub._cbBnds[binding.cbId]; } } @@ -1957,38 +1970,52 @@ informal pre V1.0 commit counter: 54 (Beta Candidate) */ if (tmplOrLinkTag === undefined) { // Call to $.unlink() is equivalent to $.unlink(true, "body") if (activeBody) { - $(activeBody).off(elementChangeStr, elemChangeHandler); + $(activeBody) + .off(elementChangeStr, elemChangeHandler) + .off('blur', '[contenteditable]', elemChangeNoValidateHandler); activeBody = undefined; } tmplOrLinkTag = true; topView.removeViews(); clean(document.body.getElementsByTagName("*")); - } else if (to) { + } else if (to && tmplOrLinkTag === true) { to = to.jquery ? to : $(to); // to is a jquery object or an element or selector - if (tmplOrLinkTag === true) { - // Call to $(el).unlink(true) - unlink content of element, but don't remove bindings on element itself - to.each(function() { - var innerView; -//TODO fix this for better perf. Rather that calling inner view multiple times which does querySelectorAll each time, consider a single querySelectorAll -// or simply call view.removeViews() on the top-level views under the target 'to' node, then clean(...) - while ((innerView = $view(this, true)) && innerView.parent) { - innerView.parent.removeViews(innerView._.key, undefined, true); - } - clean(this.getElementsByTagName("*")); - }); -// } else if (tmplOrLinkTag === undefined) { -// // Call to $(el).unlink() // Not currently supported -// clean(to); -//TODO provide this unlink API -// } else if ("" + tmplOrLinkTag === tmplOrLinkTag) { -// // Call to $(el).unlink(tmplOrLinkTag ...) -// $.each(to, function() { -// ... -// }); - } -//TODO - unlink the content and the arrayChange, but not any other bindings on the element (if container rather than "replace") + to.each(function() { + var innerView; + while ((innerView = $view(this, true)) && innerView.parent) { + innerView.parent.removeViews(innerView._.key, undefined, true); + } + clean(this.getElementsByTagName("*")); + clean([this]); + }); } return to; // Allow chaining, to attach event handlers, etc. + +//} else if (to) { +// to = to.jquery ? to : $(to); // to is a jquery object or an element or selector +// if (tmplOrLinkTag === true) { +// // Call to $(el).unlink(true) - unlink content of element, but don't remove bindings on element itself +// to.each(function() { +// var innerView; +////TODO fix this for better perf. Rather that calling inner view multiple times which does querySelectorAll each time, consider a single querySelectorAll +//// or simply call view.removeViews() on the top-level views under the target 'to' node, then clean(...) +// while ((innerView = $view(this, true)) && innerView.parent) { +// innerView.parent.removeViews(innerView._.key, undefined, true); +// } +// clean(this.getElementsByTagName("*")); +// clean([this]); +// }); +// } else if (tmplOrLinkTag === undefined) { +// // Call to $(el).unlink() // Not currently supported +// clean(to); +////TODO provide this unlink API +// } else if ("" + tmplOrLinkTag === tmplOrLinkTag) { +// // Call to $(el).unlink(tmplOrLinkTag ...) +// $.each(to, function() { +// //... +// }); +// } +//TODO - unlink the content and the arrayChange, but not any other bindings on the element (if container rather than "replace") } function tmplUnlink(to, from) { @@ -2053,20 +2080,32 @@ informal pre V1.0 commit counter: 54 (Beta Candidate) */ // JsRender integration //===================== - $viewsSub.onStoreItem = function(store, name, item) { - if (item && store === $templates) { - item.link = tmplLink; - item.unlink = tmplUnlink; - if (name) { - $.link[name] = function() { - return tmplLink.apply(item, arguments); - }; - $.unlink[name] = function() { - return tmplUnlink.apply(item, arguments); - }; + $extend($sub, { + onStoreItem: function(storeName, name, item) { + if (item) { + if (storeName === "template") { + item.link = tmplLink; + item.unlink = tmplUnlink; + if (name) { + $.link[name] = function() { + return tmplLink.apply(item, arguments); + }; + $.unlink[name] = function() { + return tmplUnlink.apply(item, arguments); + }; + } + } else if (storeName === "tag") { + $sub._lnk(item); + } } - } - }; + }, + + _lnk: function(item) { + return $extend(item, linkMethods); + }, + + viewInfos: viewInfos // Expose viewInfos() as public helper method + }); // Initialize default delimiters ($viewsSettings.delimiters = function() { @@ -2076,20 +2115,13 @@ informal pre V1.0 commit counter: 54 (Beta Candidate) */ delimCloseChar0 = delimChars[2]; delimCloseChar1 = delimChars[3]; linkChar = delimChars[4]; - rTag = new RegExp("(?:^|\\s*)([\\w-]*)(\\" + linkChar + ")?(\\" + delimOpenChar1 + $viewsSub.rTag + "\\" + delimCloseChar0 + ")", "g"); + rTag = new RegExp("(?:^|\\s*)([\\w-]*)(\\" + linkChar + ")?(\\" + delimOpenChar1 + $sub.rTag + "\\" + delimCloseChar0 + ")", "g"); // Default rTag: attr bind tagExpr tag converter colon html comment code params // (?:^|\s*)([\w-]*)(\^)?({(?:(?:(\w+(?=[\/\s}]))|(?:(\w+)?(:)|(>)|!--((?:[^-]|-(?!-))*)--|(\*)))\s*((?:[^}]|}(?!}))*?))}) return this; })(); - //=============== - // Public helpers - //=============== - - $viewsSub.viewInfos = viewInfos; - // Expose viewInfos() as public helper method - //==================================== // Additional members for linked views //==================================== @@ -2181,150 +2213,149 @@ informal pre V1.0 commit counter: 54 (Beta Candidate) */ // Add linked view methods to view prototype //==================================== - $extend($viewsSub.View.prototype, { - // Note: a linked view will also, after linking have nodes[], _prv (prevNode), _nxt (nextNode) ... - addViews: function(index, dataItems, tmpl) { - // if view is not an array view, do nothing - var i, viewsCount, - self = this, - itemsCount = dataItems.length, - views = self.views; - - if (!self._.useKey && itemsCount && (tmpl = self.tmpl)) { - // view is of type "array" - // Use passed-in template if provided, since self added view may use a different template than the original one used to render the array. - viewsCount = views.length + itemsCount; - - if (renderAndLink(self, index, tmpl, views, dataItems, self.ctx) !== false) { - for (i = index + itemsCount; i < viewsCount; i++) { - $observable(views[i]).setProperty("index", i); - // This is fixing up index, but not key, and not index on child views. From child views, use view.getIndex() + $extend( + $sub._lnk($sub.View.prototype), + { + // Note: a linked view will also, after linking have nodes[], _prv (prevNode), _nxt (nextNode) ... + addViews: function(index, dataItems, tmpl) { + // if view is not an array view, do nothing + var i, viewsCount, + self = this, + itemsCount = dataItems.length, + views = self.views; + + if (!self._.useKey && itemsCount && (tmpl = self.tmpl)) { + // view is of type "array" + // Use passed-in template if provided, since self added view may use a different template than the original one used to render the array. + viewsCount = views.length + itemsCount; + + if (renderAndLink(self, index, tmpl, views, dataItems, self.ctx) !== false) { + for (i = index + itemsCount; i < viewsCount; i++) { + $observable(views[i]).setProperty("index", i); + // This is fixing up index, but not key, and not index on child views. From child views, use view.getIndex() + } } } - } - return self; - }, + return self; + }, - removeViews: function(index, itemsCount, keepNodes) { - // view.removeViews() removes all the child views - // view.removeViews(index) removes the child view with specified index or key - // view.removeViews(index, count) removes the specified nummber of child views, starting with the specified index - function removeView(index) { - var id, bindId, parentElem, prevNode, nextNode, nodesToRemove, - viewToRemove = views[index]; - - if (viewToRemove && viewToRemove.link) { - id = viewToRemove._.id; - if (!keepNodes) { - // Remove the HTML nodes from the DOM, unless they have already been removed, including nodes of child views - nodesToRemove = viewToRemove.nodes(); - } + removeViews: function(index, itemsCount, keepNodes) { + // view.removeViews() removes all the child views + // view.removeViews(index) removes the child view with specified index or key + // view.removeViews(index, count) removes the specified nummber of child views, starting with the specified index + function removeView(index) { + var id, bindId, parentElem, prevNode, nextNode, nodesToRemove, + viewToRemove = views[index]; + + if (viewToRemove && viewToRemove.link) { + id = viewToRemove._.id; + if (!keepNodes) { + // Remove the HTML nodes from the DOM, unless they have already been removed, including nodes of child views + nodesToRemove = viewToRemove.nodes(); + } - // Remove child views, without removing nodes - viewToRemove.removeViews(undefined, undefined, true); - - viewToRemove.data = undefined; // Set data to undefined: used as a flag that this view is being removed - prevNode = viewToRemove._prv; - nextNode = viewToRemove._nxt; - parentElem = viewToRemove.parentElem; - // If prevNode and nextNode are the same, the view is empty - if (!keepNodes) { - // Remove the HTML nodes from the DOM, unless they have already been removed, including nodes of child views - if (viewToRemove._elCnt) { - // if keepNodes is false (and transferring of tokens has not already been done at a higher level) - // then transfer tokens from prevNode which is being removed, to nextNode. - transferViewTokens(prevNode, nextNode, parentElem, id, "_"); + // Remove child views, without removing nodes + viewToRemove.removeViews(undefined, undefined, true); + + viewToRemove.data = undefined; // Set data to undefined: used as a flag that this view is being removed + prevNode = viewToRemove._prv; + nextNode = viewToRemove._nxt; + parentElem = viewToRemove.parentElem; + // If prevNode and nextNode are the same, the view is empty + if (!keepNodes) { + // Remove the HTML nodes from the DOM, unless they have already been removed, including nodes of child views + if (viewToRemove._elCnt) { + // if keepNodes is false (and transferring of tokens has not already been done at a higher level) + // then transfer tokens from prevNode which is being removed, to nextNode. + transferViewTokens(prevNode, nextNode, parentElem, id, "_"); + } + $(nodesToRemove).remove(); } - $(nodesToRemove).remove(); - } - if (!viewToRemove._elCnt) { - try { - prevNode.parentNode.removeChild(prevNode); // (prevNode.parentNode is parentElem, except if jQuery Mobile or similar has inserted an intermediate wrapper) - nextNode.parentNode.removeChild(nextNode); - } catch(e) {} - } - setArrayChangeLink(viewToRemove); - for (bindId in viewToRemove._.bnds) { - removeViewBinding(bindId); + if (!viewToRemove._elCnt) { + try { + prevNode.parentNode.removeChild(prevNode); // (prevNode.parentNode is parentElem, except if jQuery Mobile or similar has inserted an intermediate wrapper) + nextNode.parentNode.removeChild(nextNode); + } catch (e) {} + } + setArrayChangeLink(viewToRemove); + for (bindId in viewToRemove._.bnds) { + removeViewBinding(bindId); + } + delete viewStore[id]; } - delete viewStore[id]; } - } - var current, view, viewsCount, - self = this, - isArray = !self._.useKey, - views = self.views; + var current, view, viewsCount, + self = this, + isArray = !self._.useKey, + views = self.views; - if (isArray) { - viewsCount = views.length; - } - if (index === undefined) { - // Remove all child views if (isArray) { - // views and data are arrays - current = viewsCount; - while (current--) { - removeView(current); - } - self.views = []; - } else { - // views and data are objects - for (view in views) { - // Remove by key - removeView(view); - } - self.views = {}; + viewsCount = views.length; } - } else { - if (itemsCount === undefined) { + if (index === undefined) { + // Remove all child views if (isArray) { - // The parentView is data array view. - // Set itemsCount to 1, to remove this item - itemsCount = 1; + // views and data are arrays + current = viewsCount; + while (current--) { + removeView(current); + } + self.views = []; } else { - // Remove child view with key 'index' - removeView(index); - delete views[index]; + // views and data are objects + for (view in views) { + // Remove by key + removeView(view); + } + self.views = {}; } - } - if (isArray && itemsCount) { - current = index + itemsCount; - // Remove indexed items (parentView is data array view); - while (current-- > index) { - removeView(current); + } else { + if (itemsCount === undefined) { + if (isArray) { + // The parentView is data array view. + // Set itemsCount to 1, to remove this item + itemsCount = 1; + } else { + // Remove child view with key 'index' + removeView(index); + delete views[index]; + } } - views.splice(index, itemsCount); - if (viewsCount = views.length) { - // Fixup index on following view items... - while (index < viewsCount) { - $observable(views[index]).setProperty("index", index++); + if (isArray && itemsCount) { + current = index + itemsCount; + // Remove indexed items (parentView is data array view); + while (current-- > index) { + removeView(current); + } + views.splice(index, itemsCount); + if (viewsCount = views.length) { + // Fixup index on following view items... + while (index < viewsCount) { + $observable(views[index]).setProperty("index", index++); + } } } } - } - return this; - }, + return this; + }, - refresh: function(context) { - var self = this, - parent = self.parent; + refresh: function(context) { + var self = this, + parent = self.parent; - if (parent) { - renderAndLink(self, self.index, self.tmpl, parent.views, self.data, context, true); - setArrayChangeLink(self); - } - return self; - }, + if (parent) { + renderAndLink(self, self.index, self.tmpl, parent.views, self.data, context, true); + setArrayChangeLink(self); + } + return self; + }, - nodes: getNodes, - contents: getContents, - childTags: getChildTags, - link: viewLink - }); + link: viewLink + } + ); - topView = new $viewsSub.View(undefined, "top"); // Top-level view - viewStore = { 0: topView }; + viewStore = { 0: topView = new $sub.View(undefined, "top") }; // Top-level view //======================== // JsViews-specific converters @@ -2356,36 +2387,44 @@ informal pre V1.0 commit counter: 54 (Beta Candidate) */ // JsViews-specific tags //======================== - $views.tags("on", { + $tags("on", { attr: "none", onAfterLink: function(tagCtx, linkCtx) { var self = this, - elem = $(linkCtx.elem), - args = tagCtx.args, + args = tagCtx.args, // [events,] [selector,] handler data = tagCtx.props.data, view = tagCtx.view, handler = args.pop(), - selector = args[1] || null, - contextOb = tagCtx.props.context; - - data = data !== undefined ? data : null; + contextOb = tagCtx.props.context; // Context ('this' pointer) for attached handler if (!contextOb) { // Get the path for the preceding object (context object) of handler (which is the last arg), compile function // to return that context object, and run compiled function against data contextOb = /^(.*)[\.^][\w$]+$/.exec(tagCtx.params.args.slice(-1)[0]); - contextOb = contextOb && $viewsSub.tmplFn("{:" + contextOb[1] + "}", view.tmpl, true)(linkCtx.data, view); + contextOb = contextOb && $sub.tmplFn("{:" + contextOb[1] + "}", view.tmpl, true)(linkCtx.data, view); } if (handler && handler.call) { - elem.on(args[0] || "click", selector, data, function(ev) { - handler.call(contextOb || linkCtx.data, ev, {change: ev.type, view: view, linkCtx: linkCtx}); - }); + if (self._evs) { + self.onDispose(); + } + $(linkCtx.elem).on( + self._evs = args[0] || "click", // events defaults to "click" + self._sel = args[1], + data == undefined ? null : data, + self._hlr = function(ev) { + handler.call(contextOb || linkCtx.data, ev, {change: ev.type, view: view, linkCtx: linkCtx}); + return false; + } + ); } }, + onDispose: function() { + $(this.parentElem).off(this._evs, this._sel, this._hlr); + }, flow: true }); - $extend($views.tags["for"], { + $extend($tags.for, { //onUpdate: function(ev, eventArgs, tagCtxs) { //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 depends. //var i, l, tci, prevArg; @@ -2459,6 +2498,10 @@ informal pre V1.0 commit counter: 54 (Beta Candidate) */ } }); + $extend($tags.for, linkMethods); + $extend($tags.if, linkMethods); + $extend($tags.include, linkMethods); + function observeProps(source, target, ev, eventArgs) { switch (eventArgs.change) { case "set": @@ -2510,8 +2553,8 @@ informal pre V1.0 commit counter: 54 (Beta Candidate) */ return (allPath.indexOf(".") < 0) && object[key]; } - $views.tags({ - props: $.extend({}, $views.tags["for"], $viewsSub.DataMap($views.tags.props.getTgt, observeProps, observeMappedProps, undefined, shallowArrayFilter)) + $tags({ + props: $.extend({}, $tags.for, $sub.DataMap($tags.props.getTgt, observeProps, observeMappedProps, undefined, shallowArrayFilter)) }); //======================== @@ -2622,7 +2665,7 @@ informal pre V1.0 commit counter: 54 (Beta Candidate) */ return e.message; } } - } + }; //=============================== // Extend jQuery instance plugins @@ -2656,7 +2699,7 @@ informal pre V1.0 commit counter: 54 (Beta Candidate) */ isCleanCall = 0; } return result; - } + }; }); //=============== diff --git a/jquery.views.min.js b/jquery.views.min.js index 91f6d42..5fd0b62 100644 --- a/jquery.views.min.js +++ b/jquery.views.min.js @@ -1,6 +1,6 @@ /*! JsViews v1.0.0-alpha: http://github.com/BorisMoore/jsviews and http://jsviews.com/jsviews -informal pre V1.0 commit counter: 54 (Beta Candidate) */ -(function(n,t,i){"use strict";function fr(n){var t=this.linkedElem;t&&et({target:t[0]},i,n)}function et(n,r,u){var w,tt,l,e,b,a,o,k,s,v,it,d,nt,f,h,y,p=n.target,ut=p._jsvBnd,ft=/&(\d+)\+?/g;if(ut)while(v=ft.exec(ut))if((v=c[v[1]])&&(h=v.to)){if(e=v.linkCtx,s=e.view,f=e.tag,k=t(p),d=s.hlp(pt),nt=s.hlp(wt),l=ot(p),w=dt[l],u===i&&(u=rt(l)?l(p):w?k[w]():k.attr(l)),a=h[1],h=h[0],a&&(b=rt(a)?a:s.getRsc("converters",a)),b&&(u=b.call(f,u)),it=s.linkCtx,s.linkCtx=e,y={change:"change",oldValue:e._val,value:u},(!d||!(tt=d.call(e,n,y)===!1))&&(!f||!f.onBeforeChange||!(tt=f.onBeforeChange(n,y)===!1))&&u!==i&&(o=h[0],u!==i&&o)){if(o=o._jsvOb?o._ob:o,f&&(f._.chging=!0),g(o).setProperty(h[2]||h[1],u),nt&&nt.call(e,n,y),f){if(f.onAfterChange)f.onAfterChange(n,y);delete f._.chging}e._val=u}s.linkCtx=it}}function vu(n,t,u){var c,e,f,p,o=this,y=o.data,a=o.elem,l=o.convert,w=a.parentNode,k=w,s=o.view,d=s.ctx,b=s.linkCtx,v=s.hlp(pt);if(s.linkCtx=o,w&&(!v||!(t&&v.call(o,n,t)===!1))&&!(t&&n.data.prop!=="*"&&n.data.prop!==t.path)){if(t&&(o.eventArgs=t),t||o._initVal){if(delete o._initVal,e=u.call(s.tmpl,y,s,r),c=yu(e,o,f=o.tag,o.attr||ot(a,!0,l!==i)),e&&e.error!==i)e=e.error;else if(f){if(e=e[0]?e:[e],p=t&&f.onUpdate&&f.onUpdate(n,t,e)===!1,yr(f,e),p||c==="none"){c===h&&f.onBeforeLink&&f.onBeforeLink();ct(f,f.tagCtx);ei(o,y,a);s.linkCtx=b;return}if(f._.chging)return;e=f.tagName.slice(-1)===":"?r._cnvt(f.tagName.slice(0,-1),s,e[0]):r._tag(f,s,s.tmpl,e,!0)}else u._tag&&(l=l===""?"true":l,e=l?r._cnvt(l,s,e):r._tag(u._tag,s,s.tmpl,e,!0),f=s._.tag,c=o.attr||c);er(e,o,c,f)&&t&&(v=s.hlp(wt))&&v.call(o,n,t)}f&&(f.contents=hi,f.nodes=ci,f.childTags=li,f.update=fr,f.refresh=pr,ct(f,f.tagCtx));ei(o,y,a);s.linkCtx=b}}function yu(n,r,u,f){var e,h,c,o,s=u&&u.parentElem||r.elem;if(n!==i){if(o=t(s),f=u&&u.attr||f,rt(n)&&k(r.expr+": missing parens"),f==="visible"&&(f="css-display"),c=/^css-/.test(f)&&f.slice(4))e=t.style(s,c),+n===n&&(e=parseInt(e));else if(f!=="link"){if(f==="value")s.type===bt&&(e=o.prop(f=p));else if(f===ft)if(s.value===""+n)e=o.prop(p);else return f;e===i&&(h=dt[f],e=h?o[h]():o.attr(f))}r._val=e}return f}function er(n,r,u,f){var tt,o,c,k,it,w,a,b,v,y,d=n!==i,rt=r.data,e=f&&f.parentElem||r.elem,g=t(e),s=r.view,nt=r._val,ut=s.ctx,et=s.linkCtx,l=f||u===h;if(f&&(f.parentElem=f.parentElem||r.expr||f._elCnt?e:e.parentNode,o=f._prv,c=f._nxt),!d){u===h&&f&&f.onBeforeLink&&f.onBeforeLink();return}if(/^css-/.test(u))r.attr==="visible"&&(n=n?pu(e):"none"),(l=l||nt!==n)&&t.style(e,u.slice(4),n);else if(u!=="link"){if(u===p)w=1,n=n&&n!=="false";else if(u===ft)if(e.value===""+n)n=!0,w=1,u=p;else{ei(r,rt,e);return}else(u==="selected"||u==="disabled"||u==="multiple"||u==="readonly")&&(n=n&&n!=="false"?u:null);(tt=dt[u])?u===h?(s.linkCtx=r,s.ctx=r.ctx,f&&f._.inline?(it=f.nodes(!0),f._elCnt&&(o&&o!==c?yi(o,c,e,f._tgId,"^",!0):(a=e._dfr)&&(b=f._tgId+"^",v=a.indexOf("#"+b)+1,y=a.indexOf("/"+b),v&&y>0&&(v+=b.length,y>v&&(e._dfr=a.slice(0,v)+a.slice(y),br(a.slice(v,y))))),o=o?o.previousSibling:c?c.previousSibling:e.lastChild),t(it).remove(),f&&f.onBeforeLink&&f.onBeforeLink(),k=s.link(s.data,e,o,c,n,f&&{tag:f._tgId,lazyLink:f.tagCtx.props.lazyLink})):(d&&g.empty(),f&&f.onBeforeLink&&f.onBeforeLink(),d&&(k=s.link(rt,e,o,c,n,f&&{tag:f._tgId}))),s.linkCtx=et,s.ctx=ut):(l=l||nt!==n)&&(u==="text"&&e.children&&!e.children[0]?e.textContent!==i?e.textContent=n:e.innerText=n===null?"":n:g[tt](n)):(l=l||nt!==n)&&g[w?"prop":"attr"](u,n===i&&!w?null:n);r._val=n}return k||l}function or(n,t){var i=this,r=i.hlp(pt),u=i.hlp(wt);if(!r||r.call(this,n,t)!==!1){if(t){var o=t.change,f=t.index,e=t.items;switch(o){case"insert":i.addViews(f,e);break;case"remove":i.removeViews(f,e.length);break;case"move":i.refresh();break;case"refresh":i.refresh()}}u&&u.call(this,n,t)}}function pu(t){var i,u,f=n.getComputedStyle,r=(t.currentStyle||f.call(n,t,"")).display;return r!=="none"||(r=ir[u=t.nodeName])||(i=e.createElement(u),e.body.appendChild(i),r=(f?f.call(n,i,""):i.currentStyle).display,ir[u]=r,e.body.removeChild(i)),r}function ui(n){var f,e,u=n.data,r=n._.bnd;if(!n._.useKey&&r)if((e=n._.bndArr)&&(t([e[1]]).off(yt,e[0]),n._.bndArr=i),r!==!!r&&r._.inline)u?r._.arrVws[n._.id]=n:delete r._.arrVws[n._.id];else if(u){f=function(t){t.data&&t.data.off||or.apply(n,arguments)};t([u]).on(yt,f);n._.bndArr=[f,u]}}function ot(n,t,i){var u=n.nodeName.toLowerCase(),r=f.merge[u]||n.contentEditable==="true"&&{to:h,from:h};return r?t?u==="input"&&n.type===ft?ft:r.to:r.from:t?i?"text":h:""}function sr(n,r,u,f,e,o,s){var p,c,v,w,b,l=n.parentElem,h=n._prv,a=n._nxt,y=n._elCnt;if(h&&h.parentNode!==l&&k("Missing parentNode"),s){w=n.nodes();y&&h&&h!==a&&yi(h,a,l,n._.id,"_",!0);n.removeViews(i,i,!0);c=a;y&&(h=h?h.previousSibling:a?a.previousSibling:l.lastChild);t(w).remove();for(b in n._.bnds)lt(b)}else{if(r){if(v=f[r-1],!v)return!1;h=v._nxt}y?(c=h,h=c?c.previousSibling:l.lastChild):c=h.nextSibling}p=u.render(e,o,n._.useKey&&s,n,s||r,!0);n.link(e,l,h,c,p,v)}function fi(n,t,r){var u,f,e;return r?(e="^`",f=t._.tag||{_:{inline:!0,bnd:r},tagCtx:{view:t},flow:!0},u=f._tgId,u||(c[u=rr++]=f,f._tgId=""+u)):(e="_`",a[u=t._.id]=t),"#"+u+e+(n!=i?n:"")+"/"+u+e}function ei(n,t,r){var e,l,f,u=n.tag,a=n.convertBack,o=[],h=n._bndId||""+rr++,v=n._hdlr;if(delete n._bndId,u&&(o=u.depends||o,o=rt(o)?u.depends(u):o,f=u.linkedElem),(!n._depends||""+n._depends!=""+o)&&(n._depends&&g._apply(!1,[t],n._depends,v,!0),e=g._apply(!1,[t],n.fn.paths,o,v,n._ctxCb),e.elem=r,e.linkCtx=n,e._tgId=h,r._jsvBnd=r._jsvBnd||"",r._jsvBnd+="&"+h,n._depends=o,n.view._.bnds[h]=h,c[h]=e,f&&(e.to=[[],a]),(f||a!==i)&&vr(e,u&&u.convertBack||a),u)){if(u.onAfterBind)u.onAfterBind(e);u.flow||u._.inline||(r.setAttribute(s,(r.getAttribute(s)||"")+"#"+h+"^/"+h+"^"),u._tgId=""+h)}if(f&&f[0])for(u._.radio&&(f=f.children("input[type=radio]")),l=f.length;l--;)f[l]._jsvBnd=f[l]._jsvBnd||r._jsvBnd+"+",f[l]._jsvLnkdEl=u}function hr(n,t,i,r,u,f,e){return oi(this,n,t,i,r,u,f,e)}function oi(n,r,u,o,s,h,c,v){if(n&&r){if(r=r.jquery?r:t(r),!w){w=e.body;t(w).on(ki,et).on("blur","[contenteditable]",function(n){var t=f.noValidate;f.noValidate=!0;et(n);f.noValidate=t})}for(var k,nt,tt,g,p,d,y,it,rt=fi,ft=o&&o.target==="replace",ut=r.length;ut--;)if(y=r[ut],""+n===n)p=b(y),it=p.ctx,p.ctx=o,st(n,y,p,u),p.ctx=it;else{if(h=h||b(y),n.markup!==i)h.link===!1&&(o=o||{},o.link=rt=!1),ft&&(d=y.parentNode),tt=n.render(u,o,s,h,i,rt),d?(c=y.previousSibling,v=y.nextSibling,t.cleanData([y],!0),d.removeChild(y),y=d):(c=v=i,t(y).empty());else if(n!==!0)break;if(y._dfr&&!v){for(g=l(y._dfr,1,ur),k=0,nt=g.length;k' in:\n"+y)),ur=ut,rt=bt.shift(),ut=ii[rt],a=a?"<\/"+a+">":"",ur&&(lt+=vt,vt="",ut?lt+="-":(d=a+gi+"@"+lt+di+(v||""),lt=ou.shift()))),ut?(o?vt+=o:t=a||p||"",g&&(t+=g,vt&&(t+=" "+s+'="'+vt+'"',vt=""))):t=o?t+d+e+gi+o+di+h+g:d||n,oi&&o&&ni(" No {^{ tags within elem markup ("+oi+' ). Use data-link="..."'),g&&(oi=g,bt.unshift(rt),rt=g.slice(1),bt[0]&&bt[0]===eu[rt]&&k("Parent of must be "),vi=ri[rt],(ut=ii[rt])&&!ur&&(ou.unshift(lt),lt=""),ur=ut,lt&&ut&&(lt+="+")),t)}function pi(n,t){var o,l,u,e,f,v,s,h=[];if(n){for(n._tkns.charAt(0)==="@"&&(t=nt.previousSibling,nt.parentNode.removeChild(nt),nt=i),pt=n.length;pt--;){if(et=n[pt],u=et.ch,o=et.path)for(ft=o.length-1;l=o.charAt(ft--);)l==="+"?o.charAt(ft)==="-"?(ft--,t=t.previousSibling):t=t.parentNode:t=t.lastChild;u==="^"?(g=c[f=et.id])&&(s=t&&(!nt||nt.parentNode!==t),(!nt||s)&&(g.parentElem=t),et.elCnt&&s&&(t._dfr=(et.open?"#":"/")+f+u+(t._dfr||"")),h.push([s?null:nt,et])):(tt=a[f=et.id])&&(tt.parentElem||(tt.parentElem=t||nt&&nt.parentNode||r,tt._.onRender=fi,tt._.onArrayChange=or,ui(tt)),e=tt.parentElem,et.open?(tt._elCnt=et.elCnt,t?t._dfr="#"+f+u+(t._dfr||""):(tt._prv||(e._dfr=ht(e._dfr,"#"+f+u)),tt._prv=nt)):(t&&(!nt||nt.parentNode!==t)?(t._dfr="/"+f+u+(t._dfr||""),tt._nxt=i):nt&&(tt._nxt||(e._dfr=ht(e._dfr,"/"+f+u)),tt._nxt=nt),si=tt.linkCtx,(v=tt.ctx&&tt.ctx.onAfterCreate||vu)&&v.call(si,tt)))}for(pt=h.length;pt--;)sr.push(h[pt])}return!n||n.elCnt}function au(n){var t,i;if(n)for(pt=n.length,ft=0;ft' in:\n"+y),d)return;for(tr.appendChild(rr),ei=o[ei]||o.div,nu=ei[0],yi.innerHTML=ei[1]+y+ei[2];nu--;)yi=yi.lastChild;for(tr.removeChild(rr),kr=e.createDocumentFragment();iu=yi.firstChild;)kr.appendChild(iu);r.insertBefore(kr,h)}return wt?setTimeout(gr,0):gr(),wt&&wt.promise()}function st(n,t,r,f,e,o){var b,v,p,l,k,d,y,w,a,s,g;if(f=r.data===i?f||{}:r.data,e)s=c[e],s=s.linkCtx?s.linkCtx.tag:s,a=s.linkCtx||{data:f,elem:s._elCnt?s.parentElem:t,view:r,ctx:r.ctx,attr:h,fn:s._.bnd,tag:s,_bndId:e},cr(a,a.fn);else if(n&&t)for(b=r.tmpl,n=bu(n,ot(t)),nt.lastIndex=0;v=nt.exec(n);)g=nt.lastIndex,p=e?h:v[1],y=v[3],k=v[10],l=i,a={data:f,elem:s&&s._elCnt?s.parentElem:t,view:r,ctx:o||r.ctx,attr:p,_initVal:!v[2]},v[6]&&(!p&&(l=/:([\w$]*)$/.exec(k))&&(l=l[1],l!==i&&(d=-l.length-1,y=y.slice(0,d-1)+tt,k=k.slice(0,d))),l===null&&(l=i),a.convert=v[5]||""),a.expr=p+y,w=b.links[y],w||(b.links[y]=w=u.tmplFn(y,b,!0,l)),a.fn=w,p||l===i||(a.convertBack=l),cr(a,w),nt.lastIndex=g}function cr(n,t){function i(i,r){vu.call(n,i,r,t)}i.noArray=!0;n._ctxCb=ku(n.view);n._hdlr=i;i(!0)}function ht(n,t){var i;return n?(i=n.indexOf(t),i+1?n.slice(0,i)+n.slice(i+t.length):n):""}function si(n){return n&&(""+n===n?n:n.tagName==="SCRIPT"?n.type.slice(3):n.nodeType===1&&n.getAttribute(s)||"")}function l(n,t,i){function e(n,t,i,r,e,o){u.push({elCnt:f,id:r,ch:e,open:t,close:i,path:o,token:n})}var f,r,u=[];if(r=t?n:si(n))return u.elCnt=!n.type,f=r.charAt(0)==="@"||!n.type,u._tkns=r,r.replace(i||au,e),u}function lr(n,t){n&&(n.type==="jsv"?n.parentNode.removeChild(n):t&&n.getAttribute(v)===""&&n.removeAttribute(v))}function ar(n,t){for(var i=n;t&&i&&i.nodeType!==1;)i=i.previousSibling;return i&&(i.nodeType!==1?(i=e.createElement("SCRIPT"),i.type="jsv",n.parentNode.insertBefore(i,n)):si(i)||i.getAttribute(v)||i.setAttribute(v,"")),i}function bu(n,i){return n=t.trim(n).replace(fu,"\\$&"),n.slice(-1)!==tt?n=vt+":"+n+(i?":":"")+tt:n}function hi(n,r){n!==!!n&&(r=n,n=i);var f,u=t(this.nodes());return u[0]&&(f=r?u.filter(r):u,u=n&&r?f.add(u.find(r)):f),u}function ci(n,t,i){var r,u=this,f=u._elCnt,o=!t&&f,e=[];for(t=t||u._prv,i=i||u._nxt,r=o?t===u._nxt?u.parentElem.lastSibling:t:u._.inline===!1?t||u.linkCtx.elem.firstChild:t&&t.nextSibling;r&&(!i||r!==i);)(n||f||r.tagName!=="SCRIPT")&&e.push(r),r=r.nextSibling;return e}function li(n,t){n!==!!n&&(t=n,n=i);var r=this,o=r.link?r:r.tagCtx.view,u=r._prv,f=r._elCnt,e=[];return u&&o.link(i,r.parentElem,f?u.previousSibling:u,r._nxt,i,{get:{tags:e,deep:n,name:t,id:f&&r._tgId}}),e}function ct(n,t){var f,r,s,e,o,a,v,l,y=t.view,h=n.linkCtx=n.linkCtx||{tag:n,data:y.data,view:y,ctx:y.ctx};if(n.onAfterLink)n.onAfterLink(t,h);if(f=n.targetTag?n.targetTag.linkedElem:n.linkedElem,f&&(r=f[0])&&((s=n._.radio)&&(f=f.children("input[type=radio]")),s||!n._.chging)){if(e=u.cvt(n,n.convert)[0],s||r!==h.elem){for(v=f.length;v--;){if(r=f[v],l=r._jsvLnkdEl,n._.inline&&(!l||l!==n&&l.targetTag!==n))for(r._jsvLnkdEl=n,o=h.elem?h.elem._jsvBnd:n._prv._jsvBnd,r._jsvBnd=o+"+",o=o.slice(1).split("&"),a=o.length;a--;)vr(c[o[a]],n.convertBack);s&&(r[p]=e===r.value)}h._val=e}s||r.value===i||e===i||(r.type===bt?r[p]=e&&e!=="false":f.val(e))}}function vr(n,t){var e,f,s,i,o,r=n.linkCtx,h=r.data,u=r.fn.paths;if(n){for((e=u._jsvto)&&(u=e),f=u.length;f&&""+(i=u[--f])!==i;);i&&(!r.tag||r.tag.tagCtx.args.length)?(i=i.split("^").join("."),n.to=i.charAt(0)==="."?[[o=u[f-1],i.slice(1)],t]:[r._ctxCb(s=f?u[0].split("^").join("."):i)||[h,s],t],e&&o&&(n.to[0][0]=r._ctxCb(o,h))):n.to=[[],t]}}function yr(n,i){var u,f,e=n.tagCtx.view,o=n.tagCtxs||[n.tagCtx],s=o.length,h=!i;for(i=i||n._.bnd.call(e.tmpl,e.data,e,r);s--;)u=o[s],f=i[s],t.observable(u.props).setProperty(f.props),d(u.ctx,f.ctx),u.args=f.args,h&&(u.tmpl=f.tmpl);return o}function pr(n){var u,f,t=this,e=t.linkCtx,o=t.tagCtx.view;return t.disposed&&k("Removed tag"),n===i&&(n=r._tag(t,o,o.tmpl,yr(t),!0)),n+""===n&&(f=t._.inline?h:e.attr||ot(t.parentElem,!0),u=er(n,e,f,t)),ct(t,t.tagCtx),u||t}function ai(n){for(var u,t,i,f=[],e=n.length,r=e;r--;)f.push(n[r]);for(r=e;r--;)if(t=f[r],t.parentNode){if(i=t._jsvBnd)for(i=i.slice(1).split("&"),t._jsvBnd="",u=i.length;u--;)lt(i[u],t._jsvLnkdEl,t);br(si(t)+(t._dfr||""))}}function lt(n,i,r){var h,l,f,o,s,a,v,y,e=c[n];if(i)r===i.linkedElem[0]&&(delete r._jsvLnkdEl,delete i.linkedElem);else if(e){for(h in e.bnd)o=e.bnd[h],s=".obs"+e.cbId,t.isArray(o)?t([o]).off(yt+s).off(bi+s):t(o).off(bi+s),delete e.bnd[h];if(l=e.linkCtx){if(f=l.tag){if(a=f.tagCtxs)for(v=a.length;v--;)(y=a[v].map)&&y.unmap();f.onDispose&&f.onDispose();f._elCnt||(f._prv&&f._prv.parentNode.removeChild(f._prv),f._nxt&&f._nxt.parentNode.removeChild(f._nxt));f.disposed=!0}delete l.view._.bnds[n]}delete c[n];delete u._cbBnds[e.cbId]}}function vi(n,r){return n===i?(w&&(t(w).off(ki,et),w=i),n=!0,y.removeViews(),ai(e.body.getElementsByTagName("*"))):r&&(r=r.jquery?r:t(r),n===!0&&r.each(function(){for(var n;(n=b(this,!0))&&n.parent;)n.parent.removeViews(n._.key,i,!0);ai(this.getElementsByTagName("*"))})),r}function wr(n,t){return vi(this,n,t)}function ku(n){return n=n||t.view(),function(t,i){var f,u,e=[i];if(n&&t){if(t._jsvOb)return t._jsvOb.call(n.tmpl,i,n,r);if(t.charAt(0)==="~")return t.slice(0,4)==="~tag"&&(u=n.ctx,t.charAt(4)==="."&&(f=t.slice(5).split("."),u=u.tag),f)?u?[u,f.join("."),i]:[]:(t=t.slice(1).split("."),(i=n.hlp(t.shift()))&&(t.length&&e.unshift(t.join(".")),e.unshift(i)),i?e:[]);if(t.charAt(0)==="#")return t==="#data"?[]:[n,t.replace(ou,""),i]}}}function du(n){return n.type===bt?n[p]:n.value}function yi(n,t,i,r,u,f){var y,h,p,o,b,v,e,w=0,k=n===t;if(n){for(p=l(n)||[],y=0,h=p.length;y<\/script>',gi='