diff --git a/demos/features/observability/observing-paths.html b/demos/features/observability/observing-paths.html index e5e4b10..bd759bf 100644 --- a/demos/features/observability/observing-paths.html +++ b/demos/features/observability/observing-paths.html @@ -91,9 +91,9 @@

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

return ($.inArray(val, this.tagCtx.props.array) + 1) || "0"; }, setObject: function(val) { - var selectedObject = this.tagCtx.props.array[val-1]; + var selectedObject = this.tagCtx.props.array[val-1] || null; $.observable(this.linkCtx.data).setProperty(this.linkCtx.fn.paths[0], selectedObject); - return this.tagCtx.props.array[val-1]; + return selectedObject; } }); diff --git a/jquery.observable.js b/jquery.observable.js index 6e5d53a..f69bf5c 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: 60 (Beta Candidate) */ +informal pre V1.0 commit counter: 61 (Beta Candidate) */ /* * Subcomponent of JsViews * Data change events for data-linking @@ -32,7 +32,7 @@ informal pre V1.0 commit counter: 60 (Beta Candidate) */ splice = [].splice, $isArray = $.isArray, $expando = $.expando, - OBJECT = "object", + objectStr = "object", PARSEINT = parseInt, rNotWhite = /\S+/g, propertyChangeStr = $sub.propChng = $sub.propChng || "propertyChange",// These two settings can be overridden on settings after loading @@ -42,11 +42,16 @@ informal pre V1.0 commit counter: 60 (Beta Candidate) */ $isFunction = $.isFunction, observeObjKey = 1, observeCbKey = 1, + observeInnerCbKey = 1, $hasData = $.hasData, remove = {}; // flag for removeProperty //========================== Top-level functions ========================== + function getCbKey(cb) { + return cb._cId = cb._cId || (".obs" + observeCbKey++); + } + $sub.getDeps = function() { var args = arguments; return function() { @@ -117,21 +122,20 @@ informal pre V1.0 commit counter: 60 (Beta Candidate) */ function removeCbBindings(cbBindings, cbBindingsId) { // If the cbBindings collection is empty we will remove it from the cbBindingsStore - var cb, found; - - for (cb in cbBindings) { - found = true; - break; - } - if (!found) { - delete cbBindingsStore[cbBindingsId]; + for (var cb in cbBindings) { + return; } + delete cbBindingsStore[cbBindingsId]; // This binding collection is empty, so remove from store } function onObservableChange(ev, eventArgs) { + function isOb(val) { + return typeof val === objectStr && (paths[0] || allowArray && $isArray(val)); + } + if (!(ev.data && ev.data.off)) { // 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) - var allPath, filter, parentObs, oldIsOb, isOb, + var allPath, filter, parentObs, oldValue = eventArgs.oldValue, value = eventArgs.value, ctx = ev.data, @@ -143,24 +147,22 @@ informal pre V1.0 commit counter: 60 (Beta Candidate) */ (ctx.cb.array || ctx.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) // 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. } else if (ctx.prop === eventArgs.path || ctx.prop === "*") { - oldIsOb = typeof oldValue === OBJECT && (paths[0] || allowArray && $isArray(oldValue)); // Note: && (paths[0] || $isArray(value)) is for perf optimization - isOb = typeof value === OBJECT && (paths[0] || allowArray && $isArray(value)); if (observeAll) { allPath = observeAll._path + "." + eventArgs.path; filter = observeAll.filter; parentObs = [ev.target].concat(observeAll.parents()); - if (oldIsOb) { + if (isOb(oldValue)) { observe_apply(allowArray, observeAll.ns, [oldValue], paths, ctx.cb, true, filter, [parentObs], allPath); // unobserve } - if (isOb) { + if (isOb(value)) { observe_apply(allowArray, observeAll.ns, [value], paths, ctx.cb, undefined, filter, [parentObs], allPath); } } else { - if (oldIsOb) { // oldValue is an object, so unobserve + if (isOb(oldValue)) { // oldValue is an object, so unobserve observe_apply(allowArray, [oldValue], paths, ctx.cb, true); // unobserve } - if (isOb) { // value is an object, so observe + if (isOb(value)) { // value is an object, so observe observe_apply(allowArray, [value], paths, ctx.cb); } } @@ -170,309 +172,337 @@ informal pre V1.0 commit counter: 60 (Beta Candidate) */ } function $observe() { - // $.observe([namespace, ]root, [1 or more objects, path or path Array params...], callback[, contextCallback][, unobserveOrOrigRoot]) - function observeOnOff(namespace, pathStr, isArrayBinding, off) { - var j, evData, - obIdExpando = $hasData(object), - boundObOrArr = wrapArray(object); + // $.observe([namespace, ]root, [1 or more objects, path or path Array params...], callback[, contextCallback][, unobserve]) - namespace = initialNs ? namespace + "." + initialNs : namespace; + function innerObserve() { - if (unobserve || off) { - if (obIdExpando) { - $(boundObOrArr).off(namespace, onObservableChange); - } - } else { - if (events = obIdExpando && $._data(object)) { - events = events && events.events; - events = events && events[isArrayBinding ? arrayChangeStr : propertyChangeStr]; - el = events && events.length; - - while (el--) { - if ((data = events[el].data) && data.cb._cId === callback._cId && data.ns === initialNs) { - if (isArrayBinding) { - // Duplicate exists, so skip. (This can happen e.g. with {^{for people ~foo=people}}) - return; - } else if (pathStr === "*" && data.prop !== pathStr || data.prop === prop) { - $(object).off(namespace, onObservableChange); + function observeOnOff(namespace, pathStr, isArrayBinding, off) { + var j, evData, + obIdExpando = $hasData(object), + boundObOrArr = wrapArray(object), + prntObs = parentObs, + allPth = allPath; + + namespace = initialNs ? namespace + "." + initialNs : namespace; + + if (unobserve || off) { + if (obIdExpando) { + $(boundObOrArr).off(namespace, onObservableChange); + } + } else { + if (events = obIdExpando && $._data(object)) { + events = events && events.events; + events = events && events[isArrayBinding ? arrayChangeStr : propertyChangeStr]; + el = events && events.length; + + while (el--) { + if ((data = events[el].data) && data.cb._cId === callback._cId && data.ns === initialNs) { + if (isArrayBinding) { + // Duplicate exists, so skip. (This can happen e.g. with {^{for people ~foo=people}}) + return; + } else if (pathStr === "*" && data.prop !== pathStr) { + $(object).off(namespace, onObservableChange); + } } } } - } - evData = isArrayBinding ? {} - : { - fullPath: path, - paths: pathStr ? [pathStr] : [], - prop: prop - }; - evData.ns = initialNs; - evData.cb = callback; - - if (allPath) { - evData.observeAll = { - _path: allPath, - path: function() { // Step through path and parentObs parent chain, replacing '[]' by '[n]' based on current index of objects in parent arrays. - j = parentObs.length; - return allPath.replace(/[[.]/g, function(all) { - j--; - return all === "[" - ? "[" + $.inArray(parentObs[j - 1], parentObs[j]) - : "."; - }); - }, - parents: function() { - return parentObs; // The chain of parents between the modified object and the root object used in the observeAll() call - }, - filter: filter, - ns: initialNs - }; - } - $(boundObOrArr).on(namespace, null, evData, onObservableChange); - if (cbBindings) { - // Add object to cbBindings, and add the counter to the jQuery data on the object - (cbBindingsStore[callback._cId] = cbBindings) // In some scenarios cbBindings was empty and removed - //from store - so defensively add back to store, to ensure correct disposal e.g. when views are removed - [$.data(object, "obId") || $.data(object, "obId", observeObjKey++)] = object; + evData = isArrayBinding ? {} + : { + fullPath: path, + paths: pathStr ? [pathStr] : [], + prop: prop + }; + evData.ns = initialNs; + evData.cb = callback; + + if (allPath) { + // This is an observeAll call + evData.observeAll = { + _path: allPth, + path: function() { // Step through path and parentObs parent chain, replacing '[]' by '[n]' based on current index of objects in parent arrays. + j = prntObs.length; + return allPth.replace(/[[.]/g, function(all) { + j--; + return all === "[" + ? "[" + $.inArray(prntObs[j - 1], prntObs[j]) + : "."; + }); + }, + parents: function() { + return prntObs; // The chain of parents between the modified object and the root object used in the observeAll() call + }, + filter: filter, + ns: initialNs + }; + } + $(boundObOrArr).on(namespace, null, evData, onObservableChange); + if (cbBindings) { + // Add object to cbBindings, and add the counter to the jQuery data on the object + (cbBindingsStore[callback._cId] = cbBindings) // In some scenarios cbBindings was empty and removed + //from store - so defensively add back to store, to ensure correct disposal e.g. when views are removed + [$.data(object, "obId") || $.data(object, "obId", observeObjKey++)] = object; + } } } - } - function onUpdatedExpression(exprOb, paths) { - // Use 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. - // If it is an array, register array binding - exprOb._ob = contextCb(exprOb, origRoot); - var origRt = origRoot; - return function(ev, eventArgs) { - var obj = exprOb._ob, - len = paths.length; - if (typeof obj === OBJECT) { - bindArray(obj, true); - if (len || allowArray && $isArray(obj)) { - observe_apply(allowArray, [obj], paths, callback, contextCb, true); // unobserve - } - } - obj = exprOb._ob = contextCb(exprOb, origRt); - // Put the updated object instance onto the exprOb in the paths array, so subsequent string paths are relative to this object - if (typeof obj === OBJECT) { - bindArray(obj); - if (len || allowArray && $isArray(obj)) { - observe_apply(allowArray, [obj], paths, callback, contextCb, [origRt]); + function getInnerCb(exprOb) { + // Returns the innerCb used for updating a computed in a compiled expression (setting the new instance as exprOb.ob, unobserving the previous object, + // and observing the new one), then calling the outerCB - i.e. the handler for the whole compiled expression. + // Initialized exprOb.ob to the current object. + // 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. + // If it is an array, registers array binding + var origRt = root; + // Note: For https://github.com/BorisMoore/jsviews/issues/292ctxCb will need var ctxCb = contextCb || function(exprOb, origRt) {return exprOb._jsv(origRt);}; + + exprOb.ob = contextCb(exprOb, origRt); // Initialize object + + return exprOb.cb = function(ev, eventArgs) { + var obj = exprOb.ob, // The old object + sub = exprOb.sb, + newObj = contextCb(exprOb, origRt); + + if (newObj !== obj) { + if (typeof obj === objectStr) { + bindArray(obj, true); + if (sub || allowArray && $isArray(obj)) { + innerObserve([obj], sub, callback, contextCb, true); // unobserve on the old object + } + } + exprOb.ob = newObj; + // Put the updated object instance onto the exprOb in the paths array, so subsequent string paths are relative to this object + if (typeof newObj === objectStr) { + bindArray(newObj); + if (sub || allowArray && $isArray(newObj)) { + // Register array binding + innerObserve([newObj], sub, callback, contextCb); + } + } } - } - callback(ev, eventArgs); - }; - } + // Call the outerCb - to execute the compiled expression that this computed is part of + callback(ev, eventArgs); + }; + } - function bindArray(arr, unbind, isArray, relPath) { - if (allowArray) { - // This is a call to observe that does not come from observeAndBind (tag binding), so we allow arrayChange binding - var prevObj = object, - prevAllPath = allPath; + function bindArray(arr, unbind, isArray, relPath) { + if (allowArray) { + // This is a call to observe that does not come from observeAndBind (tag binding), so we allow arrayChange binding + var prevObj = object, + prevAllPath = allPath; - object = arr; - if (relPath) { - object = arr[relPath]; - allPath += "." + relPath; - } - if (filter && object) { - object = $observable._fltr(allPath, object, relPath ? [arr].concat(parentObs) : parentObs, filter); - } - if (object && (isArray || $isArray(object))) { - observeOnOff(arrayChangeStr + ".observe" + (callback ? ".obs" + (cbId = callback._cId = callback._cId || observeCbKey++) : ""), undefined, true, unbind); + object = arr; + if (relPath) { + object = arr[relPath]; + allPath += "." + relPath; + } + if (filter && object) { + object = $observable._fltr(allPath, object, relPath ? [arr].concat(parentObs) : parentObs, filter); + } + if (object && (isArray || $isArray(object))) { + observeOnOff(arrayChangeStr + ".observe" + (callback ? (cbId = getCbKey(callback)) : ""), undefined, true, unbind); + } + object = prevObj; + allPath = prevAllPath; } - object = prevObj; - allPath = prevAllPath; } - } - 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. - // Note deliberately using this != false, rather than this !== false because of IE<10 bug- see https://github.com/BorisMoore/jsviews/issues/237 - topLevel = true, - ns = observeStr, - paths = Array.apply(0, arguments), - lastArg = paths.pop(), - origRoot = paths.shift(), - root = origRoot, - object = root, - l = paths.length; + var i, p, skip, parts, prop, path, dep, unobserve, callback, cbId, el, data, events, contextCb, items, cbBindings, depth, innerCb, parentObs, + allPath, filter, initNsArr, initNsArrLen, + ns = observeStr, + paths = this != 1? // Using != for IE<10 bug- see https://github.com/BorisMoore/jsviews/issues/237 + [].concat.apply([], arguments) // Flatten the arguments - this is a 'recursive call' with params using the 'wrapped array' + // style - such as innerObserve([object], path.path, [origRoot], path.prm, innerCb, ...); + : Array.apply(0, arguments), // Don't flatten - this is the first 'top-level call, to innerObserve.apply(1, paths) + lastArg = paths.pop() || false, + root = paths.shift(), + object = root, + l = paths.length; - if (origRoot + "" === origRoot && allowArray) { - initialNs = origRoot; // The first arg is a namespace, since it is a string, and this call is not from observeAndBind - origRoot = object = root = paths.shift(); - l--; - } - - if ($isFunction(lastArg)) { - callback = lastArg; - } else { if (lastArg + "" === lastArg) { // If last arg is a string then this observe call is part of an observeAll call, allPath = lastArg; // and the last three args are the parentObs array, the filter, and the allPath string. parentObs = paths.pop(); filter = paths.pop(); - lastArg = paths.pop(); - l = l - 3; + lastArg = !!paths.pop(); // unobserve + l -= 3; } - if (lastArg === true) { + if (lastArg === !!lastArg) { unobserve = lastArg; - } else if (lastArg) { - origRoot = lastArg; - topLevel = undefined; + lastArg = paths[l-1]; + lastArg = l && lastArg + "" !== lastArg ? (l--, paths.pop()) : undefined; } - lastArg = paths[l - 1]; - if (l && lastArg === undefined || $isFunction(lastArg)) { - callback = paths.pop(); // If preceding is callback this will be contextCb param - which may be undefined + callback = lastArg; + if (l && $isFunction(paths[l - 1])) { + contextCb = callback; + callback = paths.pop(); l--; } - } - if (l && $isFunction(paths[l - 1])) { - contextCb = callback; - callback = paths.pop(); - l--; - } - // Use a unique namespace (e.g. obs7) associated with each observe() callback to allow unobserve to remove handlers - ns += unobserve - ? (callback ? ".obs" + callback._cId : "") - : ".obs" + (cbId = callback._cId = callback._cId || observeCbKey++); - if (!unobserve) { - cbBindings = cbBindingsStore[cbId] = cbBindingsStore[cbId] || {}; - } + // Use a unique namespace (e.g. obs7) associated with each observe() callback to allow unobserve to remove handlers + ns += unobserve + ? (callback ? callback._cId + (callback._inId || ""): "") + : (cbId = getCbKey(callback)) + (callback._inId || ""); + if (!unobserve) { + cbBindings = cbBindingsStore[cbId] = cbBindingsStore[cbId] || {}; + } - initNsArr = initialNs && initialNs.match(rNotWhite) || [""]; - initNsArrLen = initNsArr.length; + initNsArr = initialNs && initialNs.match(rNotWhite) || [""]; + initNsArrLen = initNsArr.length; - while (initNsArrLen--) { - initialNs = initNsArr[initNsArrLen]; + while (initNsArrLen--) { + initialNs = initNsArr[initNsArrLen]; - if ($isArray(root)) { - bindArray(root, unobserve, true); - } else { - // remove onObservableChange handlers that wrap that callback - if (unobserve && l === 0 && root) { - observeOnOff(ns, ""); - } - } - depth = 0; - for (i = 0; i < l; i++) { - path = paths[i]; - if (path === "") { - continue; - } - object = root; - if ("" + path === path) { - parts = path.split("^"); - if (parts[1]) { - // We bind the leaf, plus additional nodes based on depth. - // "a.b.c^d.e" is depth 2, so listens to changes of e, plus changes of d and of c - depth = parts[0].split(".").length; - path = parts.join("."); - depth = path.split(".").length - depth; - // if more than one ^ in the path, the first one determines depth + if ($isArray(root)) { + bindArray(root, unobserve, true); + } else { + // remove onObservableChange handlers that wrap that callback + if (unobserve && l === 0 && root) { + observeOnOff(ns, ""); } - if (contextCb && (items = contextCb(path, root))) { - // If contextCb returns an array of objects and paths, we will insert them - // into the sequence, replacing the current item (path) - l += items.length - 1; - splice.apply(paths, [i--, 1].concat(items)); + } + depth = 0; + for (i = 0; i < l; i++) { + path = paths[i]; + if (path === "" || path === undefined) { continue; } - parts = path.split("."); - } else { - if (topLevel && !$isFunction(path)) { - if (path && path._jsvOb) { - // Currently this will only occur if !unobserve - // This is a compiled function for binding to an object returned by a helper/data function. - innerCb = onUpdatedExpression(path, paths.slice(i + 1)); - innerCb.noArray = !allowArray; - innerCb._cId = callback._cId; // Set the same cbBindingsStore key as for callback, so when callback is disposed, disposal of innerCb happens too. - observe_apply(allowArray, [origRoot], paths.slice(0, i), innerCb, contextCb); - innerCb = undefined; - path = path._ob; + object = root; + if ("" + path === path) { + // Consider support for computed paths: https://github.com/BorisMoore/jsviews/issues/292 + //if (/[\(\[\+]/.test(path)) { + // var b={links:{}}, t = $sub.tmplFn("{:"+path+"}", b, true), items = t.paths[0]; + // l += items.length - 1; + // splice.apply(paths, [i--, 1].concat(items)); + // continue; + //} + parts = path.split("^"); + if (parts[1]) { + // We bind the leaf, plus additional nodes based on depth. + // "a.b.c^d.e" is depth 2, so listens to changes of e, plus changes of d and of c + depth = parts[0].split(".").length; + path = parts.join("."); + depth = path.split(".").length - depth; + // if more than one ^ in the path, the first one determines depth } - object = path; // For top-level calls, objects in the paths array become the origRoot for subsequent paths. - } - root = path; - parts = [root]; - } - while (object && (prop = parts.shift()) !== undefined) { - if (typeof object === OBJECT) { - if ("" + prop === prop) { - if (prop === "") { - continue; + if (contextCb && (items = contextCb(path, root))) { + // If contextCb returns an array of objects and paths, we will insert them + // into the sequence, replacing the current item (path) + l += items.length - 1; + splice.apply(paths, [i--, 1].concat(items)); + continue; + } + parts = path.split("."); + } else { + if (!$isFunction(path)) { + if (path && path._jsv) { + // This is a compiled function for binding to an object returned by a helper/data function. + // Set current object on exprOb.ob, and get innerCb for updating the object + innerCb = unobserve ? path.cb : getInnerCb(path); + innerCb.noArray = !allowArray; + innerCb._cId = callback._cId; + // Set the same cbBindingsStore key as for callback, so when callback is disposed, disposal of innerCb happens too. + innerCb._inId = innerCb._inId || ".obIn" + observeInnerCbKey++; + if (path.bnd || path.prm && path.prm.length || !path.sb) { + // 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() + // then observe changes on the object, or its parameters and sub-path + innerObserve([object], path.path, [origRoot], path.prm, innerCb, contextCb, unobserve); + } + if (path.sb) { // subPath + innerObserve([path.ob], path.sb, callback, contextCb, unobserve); + } + path = origRoot; + object = undefined; + } else { + object = path; // For top-level calls, objects in the paths array become the origRoot for subsequent paths. } - if ((parts.length < depth + 1) && !object.nodeType) { - // Add observer for each token in path starting at depth, and on to the leaf - if (!unobserve && (events = $hasData(object) && $._data(object))) { - events = events.events; - events = events && events[propertyChangeStr]; - el = events && events.length; - skip = 0; - while (el--) { // Skip duplicates - data = events[el].data; - if (data && data.cb === callback && data.ns === initialNs) { - if (data.prop === prop || data.prop === "*") { - if (p = parts.join(".")) { - data.paths.push(p); // We will skip this binding, but if it is not a leaf binding, - // need to keep bindings for rest of path, ready for if the object gets swapped. + } + parts = [root = path]; + } + while (object && (prop = parts.shift()) !== undefined) { + if (typeof object === objectStr) { + if ("" + prop === prop) { + if (prop === "") { + continue; + } + if ((parts.length < depth + 1) && !object.nodeType) { + // Add observer for each token in path starting at depth, and on to the leaf + if (!unobserve && (events = $hasData(object) && $._data(object))) { + events = events.events; + events = events && events[propertyChangeStr]; + el = events && events.length; + skip = 0; + while (el--) { // Skip duplicates + data = events[el].data; + if (data && data.cb === callback && data.ns === initialNs) { + if (data.prop === prop || data.prop === "*") { + if (p = parts.join(".")) { + data.paths.push(p); // We will skip this binding, but if it is not a leaf binding, + // need to keep bindings for rest of path, ready for if the object gets swapped. + } + skip++; } - skip++; } } + if (skip) { + // Duplicate binding(s) found, so move on + object = object[prop]; + continue; + } } - if (skip) { - // Duplicate binding(s) found, so move on - object = object[prop]; - continue; - } - } - if (prop === "*") { - if (!unobserve && events && events.length) { - // Remove existing bindings, since they will be duplicates with "*" - observeOnOff(ns, "", false, true); - } - if ($isFunction(object)) { - if (dep = object.depends) { - observe_apply(allowArray, [dep], callback, unobserve || origRoot); + if (prop === "*") { + if (!unobserve && events && events.length) { + // Remove existing bindings, since they will be duplicates with "*" + observeOnOff(ns, "", false, true); } - } else { observeOnOff(ns, ""); // observe the object for any property change + for (p in object) { + // observing "*": So (in addition to listening to prop change, above) listen to arraychange on props of type array + bindArray(object, unobserve, undefined, p); + } + break; + } else if (prop) { + observeOnOff(ns + "." + prop, parts.join("^")); // By using "^" rather than "." we ensure that deep binding will be used on newly inserted object graphs } - for (p in object) { - // observing "*": So (in addition to listening to prop change, above) listen to arraychange on props of type array - bindArray(object, unobserve, undefined, p); - } - break; - } else if (prop) { - observeOnOff(ns + "." + prop, parts.join("^")); // By using "^" rather than "." we ensure that deep binding will be used on newly insert object graphs } + if (allPath) { + allPath += "." + prop; + } + prop = object[prop]; } - if (allPath) { - allPath += "." + prop; - } - prop = object[prop]; - } - if ($isFunction(prop)) { - if (dep = prop.depends) { - // This is a computed observable. We will observe any declared dependencies - observe_apply(allowArray, [object], resolvePathObjects(dep, object), callback, contextCb, unobserve || [origRoot]); + if ($isFunction(prop)) { + if (dep = prop.depends) { + // This is a computed observable. We will observe any declared dependencies + innerObserve([object], resolvePathObjects(dep, object), callback, contextCb, unobserve); + } + break; } - break; + object = prop; } - object = prop; } + bindArray(object, unobserve); } - bindArray(object, unobserve); } + if (cbId) { + removeCbBindings(cbBindings, cbId); + } + + // Return the cbBindings to the top-level caller, along with the cbId + return { cbId: cbId, bnd: cbBindings }; } - if (cbId) { - removeCbBindings(cbBindings, cbId); + + var initialNs, + 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. + // Note deliberately using this != false, rather than this !== false because of IE<10 bug- see https://github.com/BorisMoore/jsviews/issues/237 + ns = observeStr, + paths = Array.apply(0, arguments), + origRoot = paths[0]; + + if (origRoot + "" === origRoot && allowArray) { + initialNs = origRoot; // The first arg is a namespace, since it is a string, and this call is not from observeAndBind + paths.shift(); + origRoot = paths[0]; } - // Return the cbBindings to the top-level caller, along with the cbId - return { cbId: cbId, bnd: cbBindings }; + return innerObserve.apply(1, paths); } function $unobserve() { @@ -504,7 +534,6 @@ informal pre V1.0 commit counter: 60 (Beta Candidate) */ function observeAll(namespace, object, cb, filter, parentObs, allPath, unobserve) { function observeArray(arr, unobs) { l = arr.length; - newParentObs = [[arr]].concat(newParentObs); newAllPath = allPath + "[]"; while (l--) { filterAndObserveAll(arr, l, unobs, 1); @@ -512,21 +541,22 @@ informal pre V1.0 commit counter: 60 (Beta Candidate) */ } function filterAndObserveAll(obj, prop, unobs, nestedArray) { - var newObject; + var newObject, newParentObs; if (prop !== $expando) { - if (newObject = $observable._fltr(newAllPath, obj[prop], newParentObs, filter)) { - observeAll(namespace, newObject, cb, filter || (nestedArray ? undefined : 0), newParentObs.slice(), newAllPath, unobs); // If nested array, need to observe the array too - so set filter to "" + if (newObject = $observable._fltr(newAllPath, obj[prop], nextParentObs, filter)) { + newParentObs = nextParentObs.slice(); + if (nestedArray && updatedTgt) { + newParentObs.unshift(updatedTgt); // For array change events need to add updated array to parentObs + } + observeAll(namespace, newObject, cb, filter || (nestedArray ? undefined : 0), newParentObs, newAllPath, unobs); // If nested array, need to observe the array too - so set filter to undefined } } } function wrappedCb(ev, eventArgs) { // This object is changing. - var oldParentObs = parentObs; - allPath = ev.data.observeAll._path; - newParentObs = [ev.target].concat(parentObs); - + updatedTgt = ev.target; switch (eventArgs.change) { // observeAll/unobserveAll on added or removed objects case "insert": observeArray(eventArgs.items); @@ -543,13 +573,14 @@ informal pre V1.0 commit counter: 60 (Beta Candidate) */ filterAndObserveAll(eventArgs, "oldValue", true); filterAndObserveAll(eventArgs, "value"); } + updatedTgt = undefined; cb.apply(this, arguments); // Observe this object (invoke the callback) - parentObs = oldParentObs; } - var l, isObject, newAllPath, newParentObs; + var l, isObject, newAllPath, nextParentObs, updatedTgt; - if (typeof object === OBJECT) { + if (typeof object === objectStr) { + nextParentObs = [object].concat(parentObs); // The parentObs chain for the next depth of observeAll isObject = $isArray(object) ? "" : "*"; if (cb) { // Observe this object or array - and also listen for changes to object graph, to add or remove observers from the modified object graph @@ -558,19 +589,18 @@ informal pre V1.0 commit counter: 60 (Beta Candidate) */ // is the case for top-level calls or for nested array (array item of an array - e.g. member of 2-dimensional array). // For array properties lower in the tree, with no filter, filter is set to 0 in filterAndObserveAll, so no arrayChange binding here, // since they get arrayChange binding added during regular $.observe(array ...) binding. - wrappedCb._cId = cb._cId = cb._cId || observeCbKey++; // Identify wrapped callback with unwrapped callback, so unobserveAll will - // remove previous observeAll wrapped callback, if inner callback was the same; - $observe(namespace, object, isObject, wrappedCb, unobserve, filter, parentObs, allPath); + wrappedCb._cId = getCbKey(cb); // Identify wrapped callback with unwrapped callback, so unobserveAll will + // remove previous observeAll wrapped callback, if inner callback was the same; + $observe(namespace, object, isObject, wrappedCb, unobserve, filter, nextParentObs, allPath); } } else { // No callback. Just unobserve if unobserve === true. - $observe(namespace, object, isObject, undefined, unobserve, filter, parentObs, allPath); + $observe(namespace, object, isObject, undefined, unobserve, filter, nextParentObs, allPath); } if (isObject) { // Continue stepping through object graph, observing object and arrays // To override filtering, pass in filter function, or replace $.observable._fltr - newParentObs = [object].concat(parentObs); // Combine with below in single function that filters, observeAlls and prepends to parentObs for (l in object) { newAllPath = allPath + "." + l; filterAndObserveAll(object, l, unobserve); @@ -590,7 +620,7 @@ informal pre V1.0 commit counter: 60 (Beta Candidate) */ object = $isFunction(object) ? object.set && object.call(parentObs[0]) // It is a getter/setter : object; - return typeof object === OBJECT && object; + return typeof object === objectStr && object; } }; @@ -809,7 +839,8 @@ informal pre V1.0 commit counter: 60 (Beta Candidate) */ events = $._data(this).events[handleObj.type]; l = events.length; while (l-- && !found) { - found = (data = events[l].data) && data.cb === evData; // Found another one with same callback + found = (data = events[l].data) && data.cb._cId === evData._cId; + // Found another one with same callback (though may be a different innerCallback) } if (!found) { // This was the last handler for this callback and object, so remove the binding entry @@ -832,7 +863,7 @@ informal pre V1.0 commit counter: 60 (Beta Candidate) */ if (this.src) { this.unmap(); // We are re-mapping a new source } - if (typeof source === "object") { + if (typeof source === objectStr) { map.src = source; map.tgt = target || map.tgt || []; map.options = options || map.options; diff --git a/jquery.observable.min.js b/jquery.observable.min.js index 4f5d6bc..3738063 100644 --- a/jquery.observable.min.js +++ b/jquery.observable.min.js @@ -1,6 +1,6 @@ /*! JsObservable v1.0.0-alpha: http://github.com/BorisMoore/jsviews and http://jsviews.com/jsviews -informal pre V1.0 commit counter: 60 (Beta Candidate) */ -(function(n,t,i){"use strict";function r(n){return u(n)?new b(n):new w(n)}function w(n){return this._data=n,this}function b(n){return this._data=n,this}function at(n){return u(n)?[n]:n}function ut(n,t){n=u(n)?n:[n];for(var i,e=t,o=e,h=n.length,r=[],s=0;s1;)e=e[s.shift()];e&&o._setProperty(e,s[0],t,r)}return o},removeProperty:function(n){return this.setProperty(n,rt),this},_setProperty:function(n,t,r,u){var o,s,h,e=t?n[t]:n;f(e)&&e.set&&(s=e,o=e.set===!0?e:e.set,e=e.call(n));(e!==r||u&&e!=r)&&(!(e instanceof Date)||e>r||e-1&&n<=i.length&&(t=u(t)?t:[t],t.length&&this._insert(n,t)),this},_insert:function(n,t){var i=this._data,r=i.length;y.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;y.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=!0,i=i.cb)&&(r=a[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))}}};g.map=function(n){function u(t,u,f){var o,e=this;this.src&&this.unmap();typeof t=="object"&&(e.src=t,e.tgt=f||e.tgt||[],e.options=u||e.options,e.update(),n.obsSrc&&r(e.src).observeAll(e.obs=function(t,r){o||(o=!0,n.obsSrc(e,t,r),o=i)},e.srcFlt),n.obsTgt&&r(e.tgt).observeAll(e.obt=function(t,r){o||(o=!0,n.obsTgt(e,t,r),o=i)},e.tgtFlt))}return f(n)&&(n={getTgt:n}),n.baseMap&&(n=t.extend({},n.baseMap,n)),n.map=function(n,t,i){return new u(n,t,i)},(u.prototype={srcFlt:n.srcFlt||st,tgtFlt:n.tgtFlt||st,update:function(t){var i=this;r(i.tgt).refresh(n.getTgt(i.src,i.options=t||i.options))},unmap:function(){var n=this;n.src&&(n.obs&&r(n.src).unobserveAll(n.obs,n.srcFlt),n.obt&&r(n.tgt).unobserveAll(n.obt,n.tgtFlt),n.src=i)},map:u,_def:n}).constructor=u,n}}})(this,this.jQuery); +informal pre V1.0 commit counter: 61 (Beta Candidate) */ +(function(n,t,i){"use strict";function p(n){return n._cId=n._cId||".obs"+at++}function r(n){return u(n)?new b(n):new w(n)}function w(n){return this._data=n,this}function b(n){return this._data=n,this}function yt(n){return u(n)?[n]:n}function ft(n,t){n=u(n)?n:[n];for(var i,e=t,o=e,h=n.length,r=[],s=0;s1;)e=e[s.shift()];e&&o._setProperty(e,s[0],t,r)}return o},removeProperty:function(n){return this.setProperty(n,ut),this},_setProperty:function(n,t,r,u){var o,s,h,e=t?n[t]:n;f(e)&&e.set&&(s=e,o=e.set===!0?e:e.set,e=e.call(n));(e!==r||u&&e!=r)&&(!(e instanceof Date)||e>r||e-1&&n<=i.length&&(t=u(t)?t:[t],t.length&&this._insert(n,t)),this},_insert:function(n,t){var i=this._data,r=i.length;y.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=s(n),t=t?s(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?s(i):i===0?0:1,n=s(n),t=s(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;y.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(c,n);u!==i&&f.triggerHandler(h,{change:"set",path:"length",value:u,oldValue:i})}};nt[h]=nt[c]={remove:function(n){var r,u,f,e,o,i=n.data;if(i&&(i.off=!0,i=i.cb)&&(r=l[i._cId])){for(f=t._data(this).events[n.type],e=f.length;e--&&!u;)u=(o=f[e].data)&&o.cb._cId===i._cId;u||(delete r[t.data(this,"obId")],et(r,i._cId))}}};g.map=function(n){function u(t,u,f){var s,e=this;this.src&&this.unmap();typeof t===o&&(e.src=t,e.tgt=f||e.tgt||[],e.options=u||e.options,e.update(),n.obsSrc&&r(e.src).observeAll(e.obs=function(t,r){s||(s=!0,n.obsSrc(e,t,r),s=i)},e.srcFlt),n.obsTgt&&r(e.tgt).observeAll(e.obt=function(t,r){s||(s=!0,n.obsTgt(e,t,r),s=i)},e.tgtFlt))}return f(n)&&(n={getTgt:n}),n.baseMap&&(n=t.extend({},n.baseMap,n)),n.map=function(n,t,i){return new u(n,t,i)},(u.prototype={srcFlt:n.srcFlt||ht,tgtFlt:n.tgtFlt||ht,update:function(t){var i=this;r(i.tgt).refresh(n.getTgt(i.src,i.options=t||i.options))},unmap:function(){var n=this;n.src&&(n.obs&&r(n.src).unobserveAll(n.obs,n.srcFlt),n.obt&&r(n.tgt).unobserveAll(n.obt,n.tgtFlt),n.src=i)},map:u,_def:n}).constructor=u,n}}})(this,this.jQuery); /* //# sourceMappingURL=jquery.observable.min.js.map */ \ No newline at end of file diff --git a/jquery.observable.min.js.map b/jquery.observable.min.js.map index ff442c1..21c8044 100644 --- a/jquery.observable.min.js.map +++ b/jquery.observable.min.js.map @@ -2,7 +2,7 @@ "version":3, "file":"jquery.observable.min.js", "lineCount":3, -"mappings":"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", +"mappings":";;CAUC,QAAQ,CAACA,CAAM,CAAEC,CAAC,CAAEC,CAAZ,CAAuB,CAG/B,Y,CAqCAC,SAASA,CAAQ,CAACC,CAAD,CAAK,CACrB,OAAOA,CAAEC,KAAM,CAAED,CAAEC,KAAM,EAAI,MAAO,CAAEC,EAAY,EAD7B,CAqBtBC,SAASA,CAAW,CAACC,CAAD,CAAO,CAC1B,OAAOC,CAAQ,CAACD,CAAD,CACd,CAAE,IAAIE,CAAe,CAACF,CAAD,CACrB,CAAE,IAAIG,CAAgB,CAACH,CAAD,CAHG,CAM3BG,SAASA,CAAgB,CAACH,CAAD,CAAO,CAE/B,OADA,IAAII,MAAO,CAAEJ,CAAI,CACV,IAFwB,CAKhCE,SAASA,CAAe,CAACF,CAAD,CAAO,CAE9B,OADA,IAAII,MAAO,CAAEJ,CAAI,CACV,IAFuB,CAK/BK,SAASA,EAAS,CAACL,CAAD,CAAO,CACxB,OAAOC,CAAQ,CAACD,CAAD,CACd,CAAE,CAACA,CAAD,CACF,CAAEA,CAHqB,CAMzBM,SAASA,EAAkB,CAACC,CAAK,CAAEC,CAAR,CAAc,CACxCD,CAAM,CAAEN,CAAQ,CAACM,CAAD,CAAQ,CAAEA,CAAM,CAAE,CAACA,CAAD,CAAO,CAQzC,IANA,IAAOE,EACNC,EAASF,EACTG,EAAUD,EACVE,EAAIL,CAAKM,QACTC,EAAM,CAAA,EAEFC,EAAI,CAAC,CAAEA,CAAE,CAAEH,CAAC,CAAEG,CAAC,EAApB,CAAwB,CAEvB,GADAN,CAAK,CAAEF,CAAM,CAAAQ,CAAA,CAAE,CACXC,CAAW,CAACP,CAAD,EAAQ,CACtBK,CAAI,CAAEA,CAAGG,OAAO,CAACX,EAAkB,CAACG,CAAIS,KAAK,CAACV,CAAI,CAAEA,CAAP,CAAY,CAAEA,CAAxB,CAAnB,CAAiD,CACjE,QAFsB,CAGrB,KAAK,GAAI,EAAG,CAAEC,CAAK,GAAIA,EAAM,CAC9BD,CAAK,CAAEG,CAAQ,CAAEF,CAAI,CACjBE,CAAQ,GAAID,C,EACfI,CAAGK,KAAK,CAACT,CAAO,CAAEC,CAAV,CAAkB,CAE3B,QAL8B,CAO3BA,CAAQ,GAAID,C,EACfI,CAAGK,KAAK,CAACT,CAAO,CAAEC,CAAV,CAAkB,CAE3BG,CAAGK,KAAK,CAACV,CAAD,CAfe,CAiBxB,OAAOK,CA1BiC,CA6BzCM,SAASA,EAAgB,CAACC,CAAU,CAAEC,CAAb,CAA2B,CAEnD,IAAK,IAAI1B,EAAG,GAAGyB,CAAf,CACC,MACD,CACA,OAAOE,CAAgB,CAAAD,CAAA,CAL4B,CAQpDE,SAASA,CAAkB,CAACC,CAAE,CAAEC,CAAL,CAAgB,CAC1CC,SAASA,CAAI,CAACC,CAAD,CAAM,CAClB,OAAO,OAAOA,CAAI,GAAIC,CAAU,EAAG,CAACtB,CAAM,CAAA,CAAA,CAAG,EAAGuB,CAAW,EAAG7B,CAAQ,CAAC2B,CAAD,CAAnC,CADjB,CAInB,GAAI,CAAC,CAACH,CAAEzB,KAAM,EAAGyB,CAAEzB,KAAK+B,IAAnB,EAA0B,CAE9B,IAAIC,EAASC,EAAQC,EACpBC,EAAWT,CAASS,UACpBC,EAAQV,CAASU,OACjBC,EAAMZ,CAAEzB,MACRsC,EAAaD,CAAGC,YAChBR,EAAa,CAACO,CAAGzC,GAAG2C,SACpBhC,EAAQ8B,CAAG9B,MAAM,CAEdkB,CAAEe,KAAM,GAAIC,CAAhB,CACC,CAACJ,CAAGzC,GAAG8C,MAAO,EAAGL,CAAGzC,GAApB,CAAwBsB,KAAK,CAACmB,CAAG,CAAEZ,CAAE,CAAEC,CAAV,CAD9B,EAGWW,CAAGM,KAAM,GAAIjB,CAASjB,KAAM,EAAG4B,CAAGM,KAAM,GAAI,I,GAClDL,CAAJ,EACCN,CAAQ,CAAEM,CAAUM,MAAO,CAAE,GAAI,CAAElB,CAASjB,KAAK,CACjDwB,CAAO,CAAEK,CAAUL,OAAO,CAC1BC,CAAU,CAAE,CAACT,CAAEoB,OAAH,CAAW5B,OAAO,CAACqB,CAAUQ,QAAQ,CAAA,CAAnB,CAAsB,CAEhDnB,CAAI,CAACQ,CAAD,C,EACPY,CAAa,CAACjB,CAAU,CAAEQ,CAAUU,GAAG,CAAE,CAACb,CAAD,CAAU,CAAE5B,CAAK,CAAE8B,CAAGzC,GAAG,CAAE,CAAA,CAAvD,CAA6DqC,CAAM,CAAE,CAACC,CAAD,CAAW,CAAEF,CAAlF,CAA0F,CAEpGL,CAAI,CAACS,CAAD,C,EACPW,CAAa,CAACjB,CAAU,CAAEQ,CAAUU,GAAG,CAAE,CAACZ,CAAD,CAAO,CAAE7B,CAAK,CAAE8B,CAAGzC,GAAG,CAAEF,CAAS,CAAEuC,CAAM,CAAE,CAACC,CAAD,CAAW,CAAEF,CAApF,EATf,EAYKL,CAAI,CAACQ,CAAD,C,EACPY,CAAa,CAACjB,CAAU,CAAE,CAACK,CAAD,CAAU,CAAE5B,CAAK,CAAE8B,CAAGzC,GAAG,CAAE,CAAA,CAAxC,CAA6C,CAEvD+B,CAAI,CAACS,CAAD,C,EACPW,CAAa,CAACjB,CAAU,CAAE,CAACM,CAAD,CAAO,CAAE7B,CAAK,CAAE8B,CAAGzC,GAAhC,E,CAGfyC,CAAGzC,GAAG,CAAC6B,CAAE,CAAEC,CAAL,EAjCuB,CALW,CA2C3CuB,SAASA,CAAQ,CAAA,CAAG,CAGnBC,SAASA,CAAY,CAAA,CAAG,CAEvBC,SAASA,EAAY,CAACC,CAAS,CAAEC,CAAO,CAAEC,CAAc,CAAEvB,CAArC,CAA0C,CAC9D,IAAIwB,EAAGC,EACNC,EAAcC,EAAQ,CAAChD,CAAD,EACtBiD,EAAetD,EAAS,CAACK,CAAD,EACxBkD,EAAU1B,GACV2B,EAAS7B,EAAO,CAIjB,GAFAoB,CAAU,CAAEU,CAAU,CAAEV,CAAU,CAAE,GAAI,CAAEU,CAAU,CAAEV,CAAS,CAE3DW,EAAU,EAAGhC,EACZ0B,C,EACHhE,CAAC,CAACkE,CAAD,CAAc5B,IAAI,CAACqB,CAAS,CAAE5B,CAAZ,CAA+B,CAElD,IAAK,CACN,GAAIwC,CAAO,CAAEP,CAAY,EAAGhE,CAACW,MAAM,CAACM,CAAD,EAGlC,IAFAsD,CAAO,CAAEA,CAAO,EAAGA,CAAMA,OAAO,CAChCA,CAAO,CAAEA,CAAO,EAAGA,CAAO,CAAAV,CAAe,CAAEb,CAAe,CAAEwB,CAAlC,CAAoD,CAC9EC,EAAG,CAAEF,CAAO,EAAGA,CAAMnD,OAArB,CAEOqD,EAAE,EAFT,CAAA,CAGC,GAAI,CAAClE,EAAK,CAAEgE,CAAO,CAAAE,EAAA,CAAGlE,KAAlB,CAAyB,EAAGA,EAAIJ,GAAGC,KAAM,GAAIsE,CAAQtE,KAAM,EAAGG,EAAIgD,GAAI,GAAIc,EAAW,CACxF,GAAIR,EAEH,MACD,CAAWD,CAAQ,GAAI,GAAI,EAAGrD,EAAI2C,KAAM,GAAIU,C,EAC3C5D,CAAC,CAACiB,CAAD,CAAQqB,IAAI,CAACqB,CAAS,CAAE5B,CAAZ,CAL0E,CAU3FgC,CAAO,CAAEF,CAAe,CAAE,CAAA,CACzB,CAAE,CACD,QAAQ,CAAE7C,CAAI,CACd,KAAK,CAAE4C,CAAQ,CAAE,CAACA,CAAD,CAAU,CAAE,CAAA,CAAE,CAC/B,IAAI,CAAEV,CAHL,CAID,CACFa,CAAMR,GAAI,CAAEc,CAAS,CACrBN,CAAM5D,GAAI,CAAEuE,CAAQ,CAEhBnC,E,GAEHwB,CAAMlB,WAAY,CAAE,CACnB,KAAK,CAAEuB,CAAM,CACb,IAAI,CAAEpD,QAAQ,CAAA,CAAG,CAEhB,OADA8C,CAAE,CAAEK,CAAO/C,OAAO,CACXgD,CAAMO,QAAQ,CAAQ,OAAA,CAAE,QAAQ,CAACC,CAAD,CAAM,CAE5C,OADAd,CAAC,EAAE,CACIc,CAAI,GAAI,GACd,CAAE,GAAI,CAAE5E,CAAC6E,QAAQ,CAACV,CAAQ,CAAAL,CAAE,CAAE,CAAJ,CAAM,CAAEK,CAAQ,CAAAL,CAAA,CAAzB,CACjB,CAAE,GAJyC,CAAxB,CAFL,CAQhB,CACD,OAAO,CAAET,QAAQ,CAAA,CAAG,CACnB,OAAOc,CADY,CAEnB,CACD,MAAM,CAAE3B,EAAM,CACd,EAAE,CAAE6B,CAfe,EAgBnB,CAEFrE,CAAC,CAACkE,CAAD,CAAcY,GAAG,CAACnB,CAAS,CAAE,IAAI,CAAEI,CAAM,CAAEhC,CAA1B,CAA6C,CAC3DH,E,GAEH,CAACE,CAAgB,CAAA4C,CAAQtE,KAAR,CAAe,CAAEwB,EAAlC,CAEE,CAAA5B,CAACO,KAAK,CAACU,CAAM,CAAE,MAAT,CAAiB,EAAGjB,CAACO,KAAK,CAACU,CAAM,CAAE,MAAM,CAAE8D,EAAa,EAA9B,CAAhC,CAAmE,CAAE9D,EAnDlE,CAbuD,CAqE/D+D,SAASA,EAAU,CAACC,CAAD,CAAS,CAM3B,IAAIC,EAASnE,EAAI,CAKjB,OAFAkE,CAAME,GAAI,CAAEC,EAAS,CAACH,CAAM,CAAEC,CAAT,CAAgB,CAE9BD,CAAM9E,GAAI,CAAEkF,QAAQ,CAACrD,CAAE,CAAEC,CAAL,CAAgB,CAC1C,IAAIqD,EAAML,CAAME,IACfI,EAAMN,CAAMO,IACZC,EAASL,EAAS,CAACH,CAAM,CAAEC,CAAT,CAAgB,CAE/BO,CAAO,GAAIH,C,GACV,OAAOA,CAAI,GAAIlD,C,GAClBsD,EAAS,CAACJ,CAAG,CAAE,CAAA,CAAN,CAAW,EAChBC,CAAI,EAAGlD,CAAW,EAAG7B,CAAQ,CAAC8E,CAAD,E,EAChC7B,CAAY,CAAC,CAAC6B,CAAD,CAAK,CAAEC,CAAG,CAAEb,CAAQ,CAAEU,EAAS,CAAE,CAAA,CAAlC,EAAuC,CAGrDH,CAAME,GAAI,CAAEM,CAAM,CAEd,OAAOA,CAAO,GAAIrD,C,GACrBsD,EAAS,CAACD,CAAD,CAAQ,EACbF,CAAI,EAAGlD,CAAW,EAAG7B,CAAQ,CAACiF,CAAD,E,EAEhChC,CAAY,CAAC,CAACgC,CAAD,CAAQ,CAAEF,CAAG,CAAEb,CAAQ,CAAEU,EAA1B,GAAoC,CAKnDV,CAAQ,CAAC1C,CAAE,CAAEC,CAAL,CAvBkC,CAXhB,CAsC5ByD,SAASA,EAAS,CAACC,CAAG,CAAEC,CAAM,CAAEC,CAAO,CAAEC,CAAvB,CAAgC,CACjD,GAAIzD,EAAY,CAEf,IAAI0D,EAAU9E,EACb+E,EAAczD,EAAO,CAEtBtB,CAAO,CAAE0E,CAAG,CACRG,C,GACH7E,CAAO,CAAE0E,CAAI,CAAAG,CAAA,CAAQ,CACrBvD,EAAQ,EAAG,GAAI,CAAEuD,EAAO,CAErBtD,EAAO,EAAGvB,C,GACbA,CAAO,CAAEX,CAAW2F,MAAM,CAAC1D,EAAO,CAAEtB,CAAM,CAAE6E,CAAQ,CAAE,CAACH,CAAD,CAAKnE,OAAO,CAACiB,EAAD,CAAY,CAAEA,EAAS,CAAED,EAAjE,EAAwE,CAE/FvB,CAAO,EAAG,CAAC4E,CAAQ,EAAGrF,CAAQ,CAACS,CAAD,CAApB,C,EACbyC,EAAY,CAACV,CAAe,CAAE,UAAW,CAAE,CAAC0B,CAAS,CAAGwB,EAAK,CAAEhG,CAAQ,CAACwE,CAAD,CAAY,CAAE,EAA1C,CAA6C,CAAEzE,CAAS,CAAE,CAAA,CAAzF,CAA+F2F,CAA/F,CAAsG,CAEnH3E,CAAO,CAAE8E,CAAO,CAChBxD,EAAQ,CAAEyD,CAjBK,CADiC,CAsBlD,IAAI1E,GAAG6E,GAAGC,GAAMC,GAAOnD,EAAMlC,EAAMsF,GAAKhC,GAAWI,EAAUwB,GAAMzB,GAAIlE,GAAMgE,EAAQa,GAAWmB,GAAO3E,GAAY4E,GAAOC,GAAShE,GAClIF,GAASC,GAAQkE,GAAWC,GAC5BpD,GAAKqD,GACL9F,GAAQ,IAAK,EAAG,CAAC,CAChB,CAAA,CAAEU,OAAOqF,MAAM,CAAC,CAAA,CAAE,CAAEC,SAAL,CAEf,CAAEC,KAAKF,MAAM,CAAC,CAAC,CAAEC,SAAJ,EACdE,GAAUlG,EAAKmG,IAAI,CAAA,CAAG,EAAG,CAAA,EACzBlG,GAAOD,EAAKoG,MAAM,CAAA,EAClBjG,EAASF,GACTI,GAAIL,EAAKM,OAAO,CA8BjB,IA5BI4F,EAAQ,CAAE,EAAG,GAAIA,E,GACpBzE,EAAQ,CAAEyE,EAAO,CACjBvE,EAAU,CAAE3B,EAAKmG,IAAI,CAAA,CAAE,CACvBzE,EAAO,CAAE1B,EAAKmG,IAAI,CAAA,CAAE,CACpBD,EAAQ,CAAE,CAAC,CAAClG,EAAKmG,IAAI,CAAA,CAAE,CACvB9F,EAAE,EAAG,EAAC,CAEH6F,EAAQ,GAAI,CAAC,CAACA,E,GACjB1C,EAAU,CAAE0C,EAAO,CACnBA,EAAQ,CAAElG,EAAM,CAAAK,EAAC,CAAC,CAAF,CAAI,CACpB6F,EAAQ,CAAE7F,EAAE,EAAG6F,EAAQ,CAAE,EAAG,GAAIA,EAAQ,CAAE,CAAC7F,EAAC,E,CAAIL,EAAKmG,IAAI,CAAA,CAAf,CAAmB,CAAEhH,EAAS,CAEzEyE,CAAS,CAAEsC,EAAO,CACd7F,EAAE,EAAGI,CAAW,CAACT,EAAM,CAAAK,EAAE,CAAE,CAAJ,CAAP,C,GACnBiE,EAAU,CAAEV,CAAQ,CACpBA,CAAS,CAAE5D,EAAKmG,IAAI,CAAA,CAAE,CACtB9F,EAAC,GAAE,CAIJoC,EAAG,EAAGe,EACL,CAAGI,CAAS,CAAEA,CAAQtE,KAAM,CAAE,CAACsE,CAAQyC,MAAO,EAAG,EAAnB,CAAsB,CAAE,EACtD,CAAE,CAACjB,EAAK,CAAEhG,CAAQ,CAACwE,CAAD,CAAhB,CAA4B,CAAE,CAACA,CAAQyC,MAAO,EAAG,EAAnB,CAAsB,CAClD7C,E,GACJ1C,EAAW,CAAEE,CAAgB,CAAAoE,EAAA,CAAM,CAAEpE,CAAgB,CAAAoE,EAAA,CAAM,EAAG,CAAA,EAAE,CAGjEQ,EAAU,CAAErC,CAAU,EAAGA,CAAS+C,MAAM,CAACC,EAAD,CAAY,EAAG,CAAC,EAAD,CAAI,CAC3DV,EAAa,CAAED,EAAStF,OAAxB,CAEOuF,EAAY,EAFnB,CAAA,CAcC,IAXAtC,CAAU,CAAEqC,EAAU,CAAAC,EAAA,CAAa,CAE/BnG,CAAQ,CAACO,EAAD,CAAZ,CACC2E,EAAS,CAAC3E,EAAI,CAAEuD,EAAS,CAAE,CAAA,CAAlB,CADV,CAIKA,EAAU,EAAGnD,EAAE,GAAI,CAAE,EAAGJ,E,EAC3B2C,EAAY,CAACH,EAAE,CAAE,EAAL,C,CAGdiD,EAAM,CAAE,CAAC,CACJlF,EAAE,CAAE,CAAC,CAAEA,EAAE,CAAEH,EAAC,CAAEG,EAAC,EAApB,CAEC,GADAN,CAAK,CAAEF,EAAM,CAAAQ,EAAA,CAAE,CACXN,CAAK,GAAI,EAAG,EAAGA,CAAK,GAAIf,EAAW,CAIvC,GADAgB,CAAO,CAAEF,EAAI,CACT,EAAG,CAAEC,CAAK,GAAIA,EAAM,CAiBvB,GATAqF,EAAM,CAAErF,CAAIsG,MAAM,CAAC,GAAD,CAAK,CACnBjB,EAAM,CAAA,CAAA,C,GAGTG,EAAM,CAAEH,EAAM,CAAA,CAAA,CAAEiB,MAAM,CAAC,GAAD,CAAKlG,OAAO,CAClCJ,CAAK,CAAEqF,EAAKkB,KAAK,CAAC,GAAD,CAAK,CACtBf,EAAM,CAAExF,CAAIsG,MAAM,CAAC,GAAD,CAAKlG,OAAQ,CAAEoF,GAAK,CAGnCpB,EAAU,EAAG,CAACmB,EAAM,CAAEnB,EAAS,CAACpE,CAAI,CAAED,EAAP,CAAlB,EAAiC,CAGjDI,EAAE,EAAGoF,EAAKnF,OAAQ,CAAE,CAAC,CACrBoG,CAAMX,MAAM,CAAC/F,EAAK,CAAE,CAACQ,EAAC,EAAE,CAAE,CAAN,CAAQE,OAAO,CAAC+E,EAAD,CAAvB,CAA+B,CAC3C,QALiD,CAOlDF,EAAM,CAAErF,CAAIsG,MAAM,CAAC,GAAD,CAxBK,CAyBtB,KACI/F,CAAW,CAACP,CAAD,C,GACXA,CAAK,EAAGA,CAAIyG,KAAhB,EAGChB,EAAQ,CAAEnC,EAAU,CAAEtD,CAAIb,GAAI,CAAE6E,EAAU,CAAChE,CAAD,CAAM,CAChDyF,EAAO3D,QAAS,CAAE,CAACT,CAAU,CAC7BoE,EAAOrG,KAAM,CAAEsE,CAAQtE,KAAK,CAE5BqG,EAAOU,MAAO,CAAEV,EAAOU,MAAO,EAAG,OAAQ,CAAEO,EAAiB,EAAE,EAC1D1G,CAAI2G,IAAK,EAAG3G,CAAI4G,IAAK,EAAG5G,CAAI4G,IAAIxG,OAAQ,EAAG,CAACJ,CAAIwE,I,EAGnD/B,CAAY,CAAC,CAACxC,CAAD,CAAQ,CAAED,CAAIA,KAAK,CAAE,CAAC6G,CAAD,CAAU,CAAE7G,CAAI4G,IAAI,CAAEnB,EAAO,CAAErB,EAAS,CAAEd,EAAhE,CAA0E,CAEnFtD,CAAIwE,G,EACP/B,CAAY,CAAC,CAACzC,CAAImE,GAAL,CAAS,CAAEnE,CAAIwE,GAAG,CAAEd,CAAQ,CAAEU,EAAS,CAAEd,EAA1C,CAAoD,CAEjEtD,CAAK,CAAE6G,CAAQ,CACf5G,CAAO,CAAEhB,EAjBV,CAmBCgB,CAAO,CAAED,E,CAGXqF,EAAM,CAAE,CAACtF,EAAK,CAAEC,CAAR,CACT,OACOC,CAAO,EAAG,CAACiC,CAAK,CAAEmD,EAAKa,MAAM,CAAA,CAAnB,CAAuB,GAAIjH,EAC3C,GAAI,OAAOgB,CAAO,GAAImB,EAAW,CAChC,GAAI,EAAG,CAAEc,CAAK,GAAIA,EAAM,CACvB,GAAIA,CAAK,GAAI,GACZ,QACD,CACA,GAAKmD,EAAKjF,OAAQ,CAAEoF,EAAM,CAAE,CAAG,EAAG,CAACvF,CAAM6G,UAAW,CAEnD,GAAI,CAACxD,EAAU,EAAG,CAACC,CAAO,CAAEN,EAAQ,CAAChD,CAAD,CAAS,EAAGjB,CAACW,MAAM,CAACM,CAAD,CAArC,EAAgD,CAIjE,IAHAsD,CAAO,CAAEA,CAAMA,OAAO,CACtBA,CAAO,CAAEA,CAAO,EAAGA,CAAO,CAAAC,CAAA,CAAkB,CAC5CC,EAAG,CAAEF,CAAO,EAAGA,CAAMnD,OAAO,CAC5BgF,EAAK,CAAE,CAAP,CACO3B,EAAE,EADT,CAAA,CAEClE,EAAK,CAAEgE,CAAO,CAAAE,EAAA,CAAGlE,KAAK,CAClBA,EAAK,EAAGA,EAAIJ,GAAI,GAAIuE,CAAS,EAAGnE,EAAIgD,GAAI,GAAIc,C,GAC3C9D,EAAI2C,KAAM,GAAIA,CAAK,EAAG3C,EAAI2C,KAAM,GAAI,I,IACnCiD,EAAE,CAAEE,EAAKkB,KAAK,CAAC,GAAD,E,EACjBhH,EAAIO,MAAMY,KAAK,CAACyE,EAAD,CAAG,CAGnBC,EAAI,GAGP,CACA,GAAIA,GAAM,CAETnF,CAAO,CAAEA,CAAO,CAAAiC,CAAA,CAAK,CACrB,QAHS,CAjBuD,CAuBlE,GAAIA,CAAK,GAAI,IAAK,CACb,CAACoB,EAAU,EAAGC,CAAO,EAAGA,CAAMnD,O,EAEjCsC,EAAY,CAACH,EAAE,CAAE,EAAE,CAAE,CAAA,CAAT,CAAgB,CAAA,CAAhB,CAAqB,CAElCG,EAAY,CAACH,EAAE,CAAE,EAAL,CAAQ,CACpB,IAAK4C,GAAE,GAAGlF,CAAV,CAECyE,EAAS,CAACzE,CAAM,CAAEqD,EAAS,CAAErE,CAAS,CAAEkG,EAA/B,CACV,CACA,KAViB,CAWhB,KAASjD,C,EACVQ,EAAY,CAACH,EAAG,CAAE,GAAI,CAAEL,CAAI,CAAEmD,EAAKkB,KAAK,CAAC,GAAD,CAA5B,CArCsC,CAwChDhF,E,GACHA,EAAQ,EAAG,GAAI,CAAEW,EAAI,CAEtBA,CAAK,CAAEjC,CAAO,CAAAiC,CAAA,CA/CS,CAiDxB,GAAI3B,CAAW,CAAC2B,CAAD,EAAQ,EAClBoD,EAAI,CAAEpD,CAAI6E,S,EAEbtE,CAAY,CAAC,CAACxC,CAAD,CAAQ,CAAEJ,EAAkB,CAACyF,EAAG,CAAErF,CAAN,CAAa,CAAEyD,CAAQ,CAAEU,EAAS,CAAEd,EAAjE,CAA2E,CAExF,KALsB,CAOvBrD,CAAO,CAAEiC,CAzDuB,CA4DlCwC,EAAS,CAACzE,CAAM,CAAEqD,EAAT,CApH8B,CA4HzC,OALI4B,E,EACHvE,EAAgB,CAACC,EAAU,CAAEsE,EAAb,CAAkB,CAI5B,CAAE,IAAI,CAAEA,EAAI,CAAE,GAAG,CAAEtE,EAAnB,CAvTgB,CA0TxB,IAAIyC,EACHhC,EAAa,IAAK,EAAG,CAAA,EAGrBkB,EAAKqD,GACL9F,EAAQiG,KAAKF,MAAM,CAAC,CAAC,CAAEC,SAAJ,EACnBe,EAAW/G,CAAM,CAAA,CAAA,CAAE,CAQpB,OANI+G,CAAS,CAAE,EAAG,GAAIA,CAAS,EAAGxF,C,GACjCgC,CAAU,CAAEwD,CAAQ,CACpB/G,CAAKoG,MAAM,CAAA,CAAE,CACbW,CAAS,CAAE/G,CAAM,CAAA,CAAA,EAAE,CAGb2C,CAAYoD,MAAM,CAAC,CAAC,CAAE/F,CAAJ,CA3UN,CA8UpBkH,SAASA,EAAU,CAAA,CAAG,CAErB,MADA,CAAA,CAAEtG,KAAKD,KAAK,CAACqF,SAAS,CAAE,CAAA,CAAZ,CAAiB,CACtBtD,CAAQqD,MAAM,CAAC,IAAI,CAAEC,SAAP,CAFA,CAKtBxD,SAASA,CAAa,CAAA,CAAG,CAExB,IAAI2E,EAAO,CAAA,CAAEzG,OAAOqF,MAAM,CAAC,CAAA,CAAE,CAAEC,SAAL,CAAe,CACzC,OAAOtD,CAAQqD,MAAM,CAACoB,CAAIf,MAAM,CAAA,CAAE,CAAEe,CAAf,CAHG,CAQzBC,SAASA,CAAW,CAACvE,CAAS,CAAExD,CAAE,CAAEqC,CAAM,CAAE8B,CAAxB,CAAmC,CAClDX,CAAU,CAAE,EAAG,GAAIA,C,GACtBnB,CAAO,CAAErC,CAAE,CACXA,CAAG,CAAEwD,CAAS,CACdA,CAAU,CAAE,GAAE,CAEfd,EAAU,CAACc,CAAS,CAAE,IAAIhD,MAAM,CAAER,CAAE,CAAEqC,CAAM,CAAE,CAAA,CAAE,CAAE,MAAM,CAAE8B,CAAhD,CAN4C,CASvD6D,SAASA,EAAa,CAACxE,CAAS,CAAExD,CAAE,CAAEqC,CAAhB,CAAwB,CAC7C0F,CAAWzG,KAAK,CAAC,IAAI,CAAEkC,CAAS,CAAExD,CAAE,CAAEqC,CAAM,CAAE,CAAA,CAA9B,CAD6B,CAI9CK,SAASA,EAAU,CAACc,CAAS,CAAE1C,CAAM,CAAEd,CAAE,CAAEqC,CAAM,CAAEC,CAAS,CAAEF,CAAO,CAAE+B,CAApD,CAA+D,CACjF8D,SAASA,CAAY,CAACzC,CAAG,CAAE0C,CAAN,CAAa,CAEjC,IADAlH,CAAE,CAAEwE,CAAGvE,OAAO,CACdkH,CAAW,CAAE/F,CAAQ,CAAE,IAAvB,CACOpB,CAAC,EADR,CAAA,CAECoH,CAAmB,CAAC5C,CAAG,CAAExE,CAAC,CAAEkH,CAAK,CAAE,CAAhB,CAJa,CAQlCE,SAASA,CAAmB,CAACjD,CAAG,CAAEpC,CAAI,CAAEmF,CAAK,CAAEG,CAAnB,CAAgC,CAC3D,IAAIC,EAAWC,CAAY,CACvBxF,CAAK,GAAIyF,E,GACRF,CAAU,CAAEnI,CAAW2F,MAAM,CAACqC,CAAU,CAAEhD,CAAI,CAAApC,CAAA,CAAK,CAAE0F,CAAa,CAAEpG,CAAvC,E,GAChCkG,CAAa,CAAEE,CAAaC,MAAM,CAAA,CAAE,CAChCL,CAAY,EAAGM,C,EAClBJ,CAAYK,QAAQ,CAACD,CAAD,CAAY,CAEjCjG,EAAU,CAACc,CAAS,CAAE8E,CAAS,CAAEtI,CAAE,CAAEqC,CAAO,EAAG,CAACgG,CAAY,CAAEvI,CAAU,CAAE,CAA3B,CAA6B,CAAEyI,CAAY,CAAEJ,CAAU,CAAED,CAA9F,EAR+C,CAa5DW,SAASA,CAAS,CAAChH,CAAE,CAAEC,CAAL,CAAgB,CAEjCM,CAAQ,CAAEP,CAAEzB,KAAKsC,WAAWM,MAAM,CAClC2F,CAAW,CAAE9G,CAAEoB,OAAO,CACtB,OAAQnB,CAASgH,QAAS,CACzB,IAAK,QAAQ,CACZb,CAAY,CAACnG,CAASsE,MAAV,CAAiB,CAC7B,K,CACD,IAAK,QAAQ,CACZ6B,CAAY,CAACnG,CAASsE,MAAM,CAAE,CAAA,CAAlB,CAAuB,CACnC,K,CACD,IAAK,SAAS,CACb6B,CAAY,CAACnG,CAASiH,SAAS,CAAE,CAAA,CAArB,CAA0B,CACtCd,CAAY,CAACpG,CAAEoB,OAAH,CAAW,CACvB,K,CACD,IAAK,KAAK,CACTkF,CAAW,CAAE/F,CAAQ,CAAE,GAAI,CAAEN,CAASjB,KAAK,CAC3CuH,CAAmB,CAACtG,CAAS,CAAE,UAAU,CAAE,CAAA,CAAxB,CAA6B,CAChDsG,CAAmB,CAACtG,CAAS,CAAE,OAAZ,CAdK,CAgB1B6G,CAAW,CAAE7I,CAAS,CACtBE,CAAE0G,MAAM,CAAC,IAAI,CAAEC,SAAP,CArByB,CAwBlC,IAAI3F,EAAGgI,EAAUb,EAAYM,EAAeE,CAAU,CAEtD,GAAI,OAAO7H,CAAO,GAAImB,EAmBrB,GAlBAwG,CAAc,CAAE,CAAC3H,CAAD,CAAQO,OAAO,CAACiB,CAAD,CAAW,CAC1C0G,CAAS,CAAE3I,CAAQ,CAACS,CAAD,CAAS,CAAE,EAAG,CAAE,GAAG,CAClCd,CAAJ,EAEKgJ,CAAS,EAAG3G,CAAO,GAAI,E,GAK1BwG,CAAS5I,KAAM,CAAEF,CAAQ,CAACC,CAAD,CAAI,CAE7BqD,CAAQ,CAACG,CAAS,CAAE1C,CAAM,CAAEkI,CAAQ,CAAEH,CAAS,CAAE1E,CAAS,CAAE9B,CAAM,CAAEoG,CAAa,CAAErG,CAA3E,EATV,CAaCiB,CAAQ,CAACG,CAAS,CAAE1C,CAAM,CAAEkI,CAAQ,CAAElJ,CAAS,CAAEqE,CAAS,CAAE9B,CAAM,CAAEoG,CAAa,CAAErG,CAA3E,C,CAGL4G,EAGH,IAAKhI,EAAE,GAAGF,CAAV,CACCqH,CAAW,CAAE/F,CAAQ,CAAE,GAAI,CAAEpB,CAAC,CAC9BoH,CAAmB,CAACtH,CAAM,CAAEE,CAAC,CAAEmD,CAAZ,CACpB,CACC,KACD8D,CAAY,CAACnH,CAAM,CAAEqD,CAAT,CA3EmE,CAiUlF8E,SAASA,EAAa,CAAC7G,CAAD,CAAkC,CACvD,OAAOA,CAAO8G,QAAQ,CAAC,GAAD,CAAM,CAAE,CAAE,EAAG9G,CAAO8G,QAAQ,CAAC,GAAD,CAAM,CAAE,CADH,CAv0BxD,GAAI,CAACrJ,EACJ,KAAM,qCAAqC,CAE5C,GAAI,CAAAA,CAACsJ,YAAa,CAIlB,IACCC,EAASvJ,CAACwJ,MAAO,CAChBxJ,CAACwJ,MACD,EAAG,CACF,OAAO,CAJU,cAIK,CACtB,GAAG,CAAE,CAAA,CAFH,EAIJC,EAAOF,CAAMhE,KACbmE,GAAgB1J,CAAC2J,MAAMC,SACvBpC,EAAS,CAAA,CAAEA,QACXhH,EAAWR,CAAC6F,SACZ8C,GAAW3I,CAAC6J,SACZzH,EAAY,SACZ0H,EAAWC,SACX1C,GAAkB,OAClB7C,EAAoBiF,CAAIO,SAAU,CAAEP,CAAIO,SAAU,EAAG,iBACrDhH,EAAiByG,CAAIQ,QAAS,CAAER,CAAIQ,QAAS,EAAG,cAChDnI,EAAkB2H,CAAIS,QAAS,CAAET,CAAIS,QAAS,EAAG,CAAA,EACjDtD,GAAapC,CAAkB,CAAE,WACjCjD,EAAcvB,CAACmK,YACfpF,GAAgB,EAChB1E,GAAe,EACfqH,GAAoB,EACpBzD,GAAWjE,CAACoK,SACZC,GAAS,CAAA,CAAE,CAQZZ,CAAIa,QAAS,CAAEC,QAAQ,CAAA,CAAG,CACzB,IAAItC,EAAOnB,SAAS,CACpB,OAAO,QAAQ,CAAA,CAAG,CAIjB,IAHA,IAAI0D,EAAKlE,EACRmE,EAAO,CAAA,EACPtJ,EAAI8G,CAAI7G,OACT,CAAOD,CAAC,EAAR,CAAA,CACCqJ,CAAI,CAAEvC,CAAK,CAAA9G,CAAC,EAAD,C,CACXmF,CAAI,CAAE2B,CAAK,CAAA9G,CAAA,C,CACPmF,C,GACHmE,CAAK,CAAEA,CAAIjJ,OAAO,CAACD,CAAW,CAAC+E,CAAD,CAAM,CAAEA,CAAG,CAACkE,CAAG,CAAEA,CAAN,CAAW,CAAElE,CAApC,EAEpB,CACA,OAAOmE,CAXU,CAFO,CAezB,CAgiBDzK,CAACsJ,WAAY,CAAEhJ,CAAW,CAC1BA,CAAW2F,MAAO,CAAEyE,QAAQ,CAACnI,CAAO,CAAEtB,CAAM,CAAEwB,CAAS,CAAED,CAA7B,CAAqC,CAChE,GAAIA,CAAO,EAAGjB,CAAW,CAACiB,CAAD,CACxB,CAAEA,CAAM,CAACD,CAAO,CAAEtB,CAAM,CAAEwB,CAAlB,CACR,CAAE,CAAA,EAFH,OAICxB,CAAO,CAAEM,CAAW,CAACN,CAAD,CACnB,CAAEA,CAAM0J,IAAK,EAAG1J,CAAMQ,KAAK,CAACgB,CAAU,CAAA,CAAA,CAAX,CAC3B,CAAExB,CAAM,CACF,OAAOA,CAAO,GAAImB,CAAU,EAAGnB,CARyB,CAUhE,CAEDX,CAAWsK,OAAQ,CAAElK,CAAgB,CACrCJ,CAAWyG,MAAO,CAAEtG,CAAe,CACnCT,CAAC6K,QAAS,CAAEvK,CAAWuK,QAAS,CAAErH,CAAQ,CAC1CxD,CAACsE,UAAW,CAAEhE,CAAWgE,UAAW,CAAE0D,EAAU,CAChD1H,CAAWwK,OAAQ,CAAExH,CAAa,CAElC5C,CAAgBqK,UAAW,CAAE,CAC5B,KAAK,CAAE,IAAI,CAEX,UAAU,CAAE7C,CAAW,CACvB,YAAY,CAAEC,EAAa,CAE3B,IAAI,CAAE5H,QAAQ,CAAA,CAAG,CAChB,OAAO,IAAII,MADK,CAEhB,CAED,WAAW,CAAEqK,QAAQ,CAAChK,CAAI,CAAE2B,CAAK,CAAEsI,CAAd,CAAyB,CAC7C,IAAIC,EAAKC,EAAM9E,EACd+E,EAAO,KACPnK,EAASmK,CAAIzK,MAAM,CAGpB,GADAK,CAAK,CAAEA,CAAK,EAAG,EAAE,CACbC,EACH,GAAIT,CAAQ,CAACQ,CAAD,EAGX,IAAAkK,CAAI,CAAElK,CAAII,OAAV,CACO8J,CAAG,EADV,CAAA,CAECC,CAAK,CAAEnK,CAAK,CAAAkK,CAAA,CAAI,CAChBE,CAAIJ,YAAY,CAACG,CAAIE,KAAK,CAAEF,CAAIxI,MAAM,CAAEsI,CAAU,GAAIhL,CAAU,EAAGgL,CAAnD,CACjB,CACC,KAAK,GAAI,EAAG,CAAEjK,CAAK,GAAIA,EAExB,IAAKkK,EAAI,GAAGlK,CAAZ,CACCoK,CAAIJ,YAAY,CAACE,CAAG,CAAElK,CAAK,CAAAkK,CAAA,CAAI,CAAEvI,CAAjB,CACjB,CACC,KAAK,GAAI3B,CAAK,GAAI2H,GAAU,CAE7B,IAAAtC,CAAM,CAAErF,CAAIsG,MAAM,CAAC,GAAD,CAAlB,CACOrG,CAAO,EAAGoF,CAAKjF,OAAQ,CAAE,CADhC,CAAA,CAECH,CAAO,CAAEA,CAAO,CAAAoF,CAAKa,MAAM,CAAA,CAAX,CACjB,CACAjG,CAAO,EAAGmK,CAAIE,aAAa,CAACrK,CAAM,CAAEoF,CAAM,CAAA,CAAA,CAAE,CAAE1D,CAAK,CAAEsI,CAA1B,CANE,CAS/B,OAAOG,CA7BsC,CA8B7C,CAED,cAAc,CAAEG,QAAQ,CAACvK,CAAD,CAAO,CAE9B,OADA,IAAIgK,YAAY,CAAChK,CAAI,CAAEqJ,EAAP,CAAc,CACvB,IAFuB,CAG9B,CAED,YAAY,CAAEiB,QAAQ,CAACE,CAAI,CAAExK,CAAI,CAAE2B,CAAK,CAAEsI,CAApB,CAA+B,CACpD,IAAIQ,EAAQC,EAAQC,EACnBC,EAAW5K,CAAK,CAAEwK,CAAK,CAAAxK,CAAA,CAAM,CAAEwK,CAAI,CAEhCjK,CAAW,CAACqK,CAAD,C,EACVA,CAAQjB,I,GAEXe,CAAO,CAAEE,CAAQ,CACjBH,CAAO,CAAEG,CAAQjB,IAAK,GAAI,CAAA,CAAK,CAAEiB,CAAS,CAAEA,CAAQjB,IAAI,CACxDiB,CAAS,CAAEA,CAAQnK,KAAK,CAAC+J,CAAD,EAAM,EAI5BI,CAAS,GAAIjJ,CAAM,EAAGsI,CAAU,EAAGW,CAAS,EAAGjJ,E,GAE9C,CAAC,CAACiJ,EAAS,WAAWC,IAArB,CAA2B,EAAGD,CAAS,CAAEjJ,CAAM,EAAGiJ,CAAS,CAAEjJ,E,GAC7D8I,CAAJ,EACCA,CAAMhK,KAAK,CAAC+J,CAAI,CAAE7I,CAAP,CAAa,CACxBA,CAAM,CAAE+I,CAAMjK,KAAK,CAAC+J,CAAD,EAFpB,EAGWG,CAAW,CAAEhJ,CAAM,GAAI0H,GAA3B,EACN,OAAOmB,CAAK,CAAAxK,CAAA,CAAK,CACjB2B,CAAM,CAAE1C,EAFF,CAGIe,C,GACVwK,CAAK,CAAAxK,CAAA,CAAM,CAAE2B,E,CAEd,IAAImJ,SAAS,CAACN,CAAI,CAAE,CAAC,MAAM,CAAE,KAAK,CAAE,IAAI,CAAExK,CAAI,CAAE,KAAK,CAAE2B,CAAK,CAAE,QAAQ,CAAEiJ,CAAQ,CAAE,MAAM,CAAED,CAAtE,CAAP,EAzBqC,CA4BpD,CAED,QAAQ,CAAEG,QAAQ,CAAC1I,CAAM,CAAEnB,CAAT,CAAoB,CACrCjC,CAAC,CAACoD,CAAD,CAAQ2I,eAAe,CAACvH,CAAiB,CAAEvC,CAApB,CADa,CA7EV,CAgF5B,CAEDxB,CAAesK,UAAW,CAAE,CAC3B,KAAK,CAAE,IAAI,CAEX,UAAU,CAAE7C,CAAW,CACvB,YAAY,CAAEC,EAAa,CAE3B,IAAI,CAAE5H,QAAQ,CAAA,CAAG,CAChB,OAAO,IAAII,MADK,CAEhB,CAED,MAAM,CAAEqL,QAAQ,CAACC,CAAK,CAAE1L,CAAR,CAAc,CAC7B,IAAII,EAAQ,IAAIA,MAAM,CAetB,OAdImG,SAAS1F,OAAQ,GAAI,C,GACxBb,CAAK,CAAE0L,CAAK,CACZA,CAAM,CAAEtL,CAAKS,QAAO,CAErB6K,CAAM,CAAEnC,CAAQ,CAACmC,CAAD,CAAO,CACnBA,CAAM,CAAE,EAAG,EAAGA,CAAM,EAAGtL,CAAKS,O,GAC/Bb,CAAK,CAAEC,CAAQ,CAACD,CAAD,CAAO,CAAEA,CAAK,CAAE,CAACA,CAAD,CAAM,CAIjCA,CAAIa,O,EACP,IAAI8K,QAAQ,CAACD,CAAK,CAAE1L,CAAR,EAAa,CAGpB,IAhBsB,CAiB7B,CAED,OAAO,CAAE2L,QAAQ,CAACD,CAAK,CAAE1L,CAAR,CAAc,CAC9B,IAAII,EAAQ,IAAIA,OACfwL,EAAYxL,CAAKS,OAAO,CACzBoG,CAAMX,MAAM,CAAClG,CAAK,CAAE,CAACsL,CAAK,CAAE,CAAR,CAAUzK,OAAO,CAACjB,CAAD,CAAzB,CAAgC,CAC5C,IAAIuL,SAAS,CAAC,CAAC,MAAM,CAAE,QAAQ,CAAE,KAAK,CAAEG,CAAK,CAAE,KAAK,CAAE1L,CAAxC,CAA6C,CAAE4L,CAAhD,CAJiB,CAK9B,CAED,MAAM,CAAE9B,QAAQ,CAAC4B,CAAK,CAAEG,CAAR,CAAqB,CACpC,IAAI7F,EACH5F,EAAQ,IAAIA,MAAM,CAenB,OAbIsL,CAAM,GAAIhM,C,GACbgM,CAAM,CAAEtL,CAAKS,OAAQ,CAAE,EAAC,CAGzB6K,CAAM,CAAEnC,CAAQ,CAACmC,CAAD,CAAO,CACvBG,CAAY,CAAEA,CAAY,CAAEtC,CAAQ,CAACsC,CAAD,CAAc,CAAEA,CAAY,GAAI,CAAE,CAAE,CAAE,CAAE,CAAC,CACzEA,CAAY,CAAE,EAAG,EAAGH,CAAM,CAAE,E,GAC/B1F,CAAM,CAAE5F,CAAKkI,MAAM,CAACoD,CAAK,CAAEA,CAAM,CAAEG,CAAhB,CAA4B,CAC/CA,CAAY,CAAE7F,CAAKnF,OAAO,CACtBgL,C,EACH,IAAIC,QAAQ,CAACJ,CAAK,CAAEG,CAAW,CAAE7F,CAArB,EAA2B,CAGlC,IAjB6B,CAkBpC,CAED,OAAO,CAAE8F,QAAQ,CAACJ,CAAK,CAAEG,CAAW,CAAE7F,CAArB,CAA4B,CAC5C,IAAI5F,EAAQ,IAAIA,OACfwL,EAAYxL,CAAKS,OAAO,CAEzBT,CAAK6G,OAAO,CAACyE,CAAK,CAAEG,CAAR,CAAoB,CAChC,IAAIN,SAAS,CAAC,CAAC,MAAM,CAAE,QAAQ,CAAE,KAAK,CAAEG,CAAK,CAAE,KAAK,CAAE1F,CAAxC,CAA8C,CAAE4F,CAAjD,CAL+B,CAM5C,CAED,IAAI,CAAEG,QAAQ,CAACC,CAAQ,CAAEC,CAAQ,CAAEC,CAArB,CAAgC,CAK7C,GAJAA,CAAU,CAAEA,CAAU,CAAE3C,CAAQ,CAAC2C,CAAD,CAAY,CAAEA,CAAU,GAAI,CAAE,CAAE,CAAE,CAAE,CAAC,CACrEF,CAAS,CAAEzC,CAAQ,CAACyC,CAAD,CAAU,CAC7BC,CAAS,CAAE1C,CAAQ,CAAC0C,CAAD,CAAU,CAEzBC,CAAU,CAAE,CAAE,EAAGF,CAAS,CAAE,EAAG,EAAGC,CAAS,CAAE,EAAG,EAAGD,CAAS,GAAIC,EAAU,CAC7E,IAAIjG,EAAQ,IAAI5F,MAAMkI,MAAM,CAAC0D,CAAQ,CAAEA,CAAS,CAAEE,CAAtB,CAAgC,CAC5DA,CAAU,CAAElG,CAAKnF,OAAO,CACpBqL,C,EACH,IAAIC,MAAM,CAACH,CAAQ,CAAEC,CAAQ,CAAEC,CAAS,CAAElG,CAAhC,CAJkE,CAO9E,OAAO,IAZsC,CAa7C,CAED,KAAK,CAAEmG,QAAQ,CAACH,CAAQ,CAAEC,CAAQ,CAAEC,CAAS,CAAElG,CAAhC,CAAuC,CACrD,IAAI5F,EAAQ,IAAIA,OACfwL,EAAYxL,CAAKS,OAAO,CACzBT,CAAK6G,OAAO,CAAC+E,CAAQ,CAAEE,CAAX,CAAqB,CACjC9L,CAAK6G,OAAOX,MAAM,CAAClG,CAAK,CAAE,CAAC6L,CAAQ,CAAE,CAAX,CAAahL,OAAO,CAAC+E,CAAD,CAA5B,CAAoC,CACtD,IAAIuF,SAAS,CAAC,CAAC,MAAM,CAAE,MAAM,CAAE,QAAQ,CAAES,CAAQ,CAAE,KAAK,CAAEC,CAAQ,CAAE,KAAK,CAAEjG,CAA7D,CAAmE,CAAE4F,CAAtE,CALwC,CAMrD,CAED,OAAO,CAAEQ,QAAQ,CAACC,CAAD,CAAW,CAC3B,IAAI1D,EAAW,IAAIvI,MAAMkI,MAAM,CAAA,CAAE,CAEjC,OADA,IAAIgE,SAAS,CAAC3D,CAAQ,CAAE0D,CAAX,CAAoB,CAC1B,IAHoB,CAI3B,CAED,QAAQ,CAAEC,QAAQ,CAAC3D,CAAQ,CAAE0D,CAAX,CAAqB,CACtC,IAAIjM,EAAQ,IAAIA,OACfwL,EAAYxL,CAAKS,OAAO,CAEzBoG,CAAMX,MAAM,CAAClG,CAAK,CAAE,CAAC,CAAC,CAAEA,CAAKS,OAAT,CAAiBI,OAAO,CAACoL,CAAD,CAAhC,CAA2C,CACvD,IAAId,SAAS,CAAC,CAAC,MAAM,CAAE,SAAS,CAAE,QAAQ,CAAE5C,CAA9B,CAAuC,CAAEiD,CAA1C,CALyB,CAMtC,CAED,QAAQ,CAAEL,QAAQ,CAAC7J,CAAS,CAAEkK,CAAZ,CAAuB,CACxC,IAAIxL,EAAQ,IAAIA,OACfS,EAAST,CAAKS,QACd0L,EAAQ9M,CAAC,CAAC,CAACW,CAAD,CAAD,CAAS,CAEnBmM,CAAKf,eAAe,CAAC/I,CAAc,CAAEf,CAAjB,CAA2B,CAC3Cb,CAAO,GAAI+K,C,EACdW,CAAKf,eAAe,CAACvH,CAAiB,CAAE,CAAC,MAAM,CAAE,KAAK,CAAE,IAAI,CAAE,QAAQ,CAAE,KAAK,CAAEpD,CAAM,CAAE,QAAQ,CAAE+K,CAAzD,CAApB,CAPmB,CArGd,CA+G3B,CAEDzC,EAAc,CAAAlF,CAAA,CAAmB,CAAEkF,EAAc,CAAA1G,CAAA,CAAgB,CAAE,CAIlE,MAAM,CAAEqH,QAAS,CAAC0C,CAAD,CAAY,CAC5B,IAAInL,EAAYoL,EAAOzI,EAAQpD,EAAGZ,EACjCwD,EAASgJ,CAASxM,KAAK,CACxB,GAAKwD,CAAQ,EAAG,CAACA,CAAMzB,IAAK,CAAE,CAAA,C,CAAMyB,CAAO,CAAEA,CAAM5D,GAAnC,C,GAEXyB,CAAW,CAAEE,CAAgB,CAAAiC,CAAM3D,KAAN,GAAc,CAG9C,IADAmE,CAAO,CAAEvE,CAACW,MAAM,CAAC,IAAD,CAAM4D,OAAQ,CAAAwI,CAAShK,KAAT,CAAe,CAC7C5B,CAAE,CAAEoD,CAAMnD,OAAV,CACOD,CAAC,EAAG,EAAG,CAAC6L,CADf,CAAA,CAECA,CAAM,CAAE,CAACzM,CAAK,CAAEgE,CAAO,CAAApD,CAAA,CAAEZ,KAAjB,CAAwB,EAAGA,CAAIJ,GAAGC,KAAM,GAAI2D,CAAM3D,KAE3D,CACK4M,C,GAEJ,OAAOpL,CAAW,CAAA5B,CAACO,KAAK,CAAC,IAAI,CAAE,MAAP,CAAN,CAAqB,CACvCoB,EAAgB,CAACC,CAAU,CAAEmC,CAAM3D,KAAnB,EAX6B,CALpB,CAJqC,CAyBlE,CAMDmJ,CAAM0D,IAAK,CAAEC,QAAQ,CAACC,CAAD,CAAS,CAC7BC,SAASA,CAAM,CAACC,CAAM,CAAEC,CAAO,CAAElK,CAAlB,CAA0B,CACxC,IAAImK,EACHN,EAAM,IAAI,CACP,IAAIO,I,EACP,IAAIC,MAAM,CAAA,CAAE,CAET,OAAOJ,CAAO,GAAIjL,C,GACrB6K,CAAGO,IAAK,CAAEH,CAAM,CAChBJ,CAAGS,IAAK,CAAEtK,CAAO,EAAG6J,CAAGS,IAAK,EAAG,CAAA,CAAE,CACjCT,CAAGK,QAAS,CAAEA,CAAQ,EAAGL,CAAGK,QAAQ,CACpCL,CAAGU,OAAO,CAAA,CAAE,CAEZR,CAAMS,OAAQ,EAAGtN,CAAW,CAAC2M,CAAGO,IAAJ,CAAS3K,WAAW,CAACoK,CAAGY,IAAK,CAAEC,QAAQ,CAAC9L,CAAE,CAAEC,CAAL,CAAgB,CAC7EsL,C,GACJA,CAAS,CAAE,CAAA,CAAI,CACfJ,CAAMS,OAAO,CAACX,CAAG,CAAEjL,CAAE,CAAEC,CAAV,CAAoB,CACjCsL,CAAS,CAAEtN,EAJsE,CAMlF,CAAEgN,CAAGc,OAN0C,CAMlC,CACdZ,CAAMa,OAAQ,EAAG1N,CAAW,CAAC2M,CAAGS,IAAJ,CAAS7K,WAAW,CAACoK,CAAGgB,IAAK,CAAEC,QAAQ,CAAClM,CAAE,CAAEC,CAAL,CAAgB,CAC7EsL,C,GACJA,CAAS,CAAE,CAAA,CAAI,CACfJ,CAAMa,OAAO,CAACf,CAAG,CAAEjL,CAAE,CAAEC,CAAV,CAAoB,CACjCsL,CAAS,CAAEtN,EAJsE,CAMlF,CAAEgN,CAAGkB,OAN0C,EAnBT,CA+DzC,OAlCI5M,CAAW,CAAC4L,CAAD,C,GAEdA,CAAO,CAAE,CACR,MAAM,CAAEA,CADA,EAER,CAGEA,CAAMiB,Q,GACTjB,CAAO,CAAEnN,CAACqO,OAAO,CAAC,CAAA,CAAE,CAAElB,CAAMiB,QAAQ,CAAEjB,CAArB,EAA4B,CAG9CA,CAAMF,IAAK,CAAEqB,QAAQ,CAACjB,CAAM,CAAEC,CAAO,CAAElK,CAAlB,CAA0B,CAC9C,OAAO,IAAIgK,CAAM,CAACC,CAAM,CAAEC,CAAO,CAAElK,CAAlB,CAD6B,CAE9C,CAED,CAACgK,CAAMrC,UAAW,CAAE,CACnB,MAAM,CAAEoC,CAAMY,OAAQ,EAAG3E,EAAa,CACtC,MAAM,CAAE+D,CAAMgB,OAAQ,EAAG/E,EAAa,CACtC,MAAM,CAAEuE,QAAQ,CAACL,CAAD,CAAU,CACzB,IAAIL,EAAM,IAAI,CACd3M,CAAW,CAAC2M,CAAGS,IAAJ,CAASf,QAAQ,CAACQ,CAAMoB,OAAO,CAACtB,CAAGO,IAAI,CAAEP,CAAGK,QAAS,CAAEA,CAAQ,EAAGL,CAAGK,QAAtC,CAAd,CAFH,CAGzB,CACD,KAAK,CAAEG,QAAQ,CAAA,CAAG,CACjB,IAAIR,EAAM,IAAI,CACVA,CAAGO,I,GACNP,CAAGY,IAAK,EAAGvN,CAAW,CAAC2M,CAAGO,IAAJ,CAASgB,aAAa,CAACvB,CAAGY,IAAI,CAAEZ,CAAGc,OAAb,CAAqB,CACjEd,CAAGgB,IAAK,EAAG3N,CAAW,CAAC2M,CAAGS,IAAJ,CAASc,aAAa,CAACvB,CAAGgB,IAAI,CAAEhB,CAAGkB,OAAb,CAAqB,CACjElB,CAAGO,IAAK,CAAEvN,EALM,CAOjB,CACD,GAAG,CAAEmN,CAAM,CACX,IAAI,CAAED,CAhBa,CAApB,CAiBEsB,YAAa,CAAErB,CAAM,CAEhBD,CAhEsB,CAx0BZ,CARa,EAm5B9B,CAAC,IAAI,CAAE,IAAIuB,OAAX,CAAmB", "sources":["jquery.observable.js"], -"names":["global","$","undefined","$observable","data","$isArray","ArrayObservable","ObjectObservable","_data","wrapArray","resolvePathObjects","paths","root","path","object","nextObj","l","length","out","i","$isFunction","concat","call","push","removeCbBindings","cbBindings","cbBindingsId","cb","found","cbBindingsStore","onObservableChange","ev","eventArgs","off","allPath","filter","parentObs","oldIsOb","isOb","oldValue","value","ctx","observeAll","allowArray","noArray","type","arrayChangeStr","array","prop","OBJECT","_path","target","parents","observe_apply","ns","$observe","observeOnOff","namespace","pathStr","isArrayBinding","j","evData","obIdExpando","$hasData","boundObOrArr","initialNs","unobserve","events","propertyChangeStr","el","_cId","callback","replace","all","inArray","on","observeObjKey","onUpdatedExpression","exprOb","_ob","contextCb","origRoot","origRt","obj","len","bindArray","arr","unbind","isArray","relPath","prevObj","prevAllPath","_fltr","cbId","observeCbKey","p","skip","parts","dep","items","depth","innerCb","initNsArr","initNsArrLen","topLevel","observeStr","Array","apply","arguments","lastArg","pop","shift","match","rNotWhite","split","join","splice","_jsvOb","slice","nodeType","depends","$unobserve","args","$observeAll","$unobserveAll","observeArray","unobs","newParentObs","newAllPath","filterAndObserveAll","nestedArray","newObject","$expando","wrappedCb","oldParentObs","change","oldItems","isObject","shallowFilter","indexOf","observable","$views","views","$sub","sub","$eventSpecial","event","special","expando","PARSEINT","parseInt","propChng","arrChng","_cbBnds","isFunction","hasData","remove","getDeps","$sub.getDeps","arg","deps","$observable._fltr","set","Object","observe","_apply","prototype","setProperty","nonStrict","key","pair","self","name","_setProperty","removeProperty","leaf","setter","getter","removeProp","property","Date","_trigger","triggerHandler","insert","index","_insert","oldLength","numToRemove","_remove","move","oldIndex","newIndex","numToMove","_move","refresh","newItems","_refresh","$data","handleObj","map","$views.map","mapDef","newMap","source","options","changing","src","unmap","tgt","update","obsSrc","obs","map.obs","srcFlt","obsTgt","obt","map.obt","tgtFlt","baseMap","extend","mapDef.map","getTgt","unobserveAll","constructor","jQuery"] +"names":["global","$","undefined","getCbKey","cb","_cId","observeCbKey","$observable","data","$isArray","ArrayObservable","ObjectObservable","_data","wrapArray","resolvePathObjects","paths","root","path","object","nextObj","l","length","out","i","$isFunction","concat","call","push","removeCbBindings","cbBindings","cbBindingsId","cbBindingsStore","onObservableChange","ev","eventArgs","isOb","val","objectStr","allowArray","off","allPath","filter","parentObs","oldValue","value","ctx","observeAll","noArray","type","arrayChangeStr","array","prop","_path","target","parents","observe_apply","ns","$observe","innerObserve","observeOnOff","namespace","pathStr","isArrayBinding","j","evData","obIdExpando","$hasData","boundObOrArr","prntObs","allPth","initialNs","unobserve","events","propertyChangeStr","el","callback","replace","all","inArray","on","observeObjKey","getInnerCb","exprOb","origRt","ob","contextCb","exprOb.cb","obj","sub","sb","newObj","bindArray","arr","unbind","isArray","relPath","prevObj","prevAllPath","_fltr","cbId","p","skip","parts","dep","items","depth","innerCb","initNsArr","initNsArrLen","observeStr","apply","arguments","Array","lastArg","pop","shift","_inId","match","rNotWhite","split","join","splice","_jsv","observeInnerCbKey","bnd","prm","origRoot","nodeType","depends","$unobserve","args","$observeAll","$unobserveAll","observeArray","unobs","newAllPath","filterAndObserveAll","nestedArray","newObject","newParentObs","$expando","nextParentObs","slice","updatedTgt","unshift","wrappedCb","change","oldItems","isObject","shallowFilter","indexOf","observable","$views","views","$sub","$eventSpecial","event","special","expando","PARSEINT","parseInt","propChng","arrChng","_cbBnds","isFunction","hasData","remove","getDeps","$sub.getDeps","arg","deps","$observable._fltr","set","Object","observe","_apply","prototype","setProperty","nonStrict","key","pair","self","name","_setProperty","removeProperty","leaf","setter","getter","removeProp","property","Date","_trigger","triggerHandler","insert","index","_insert","oldLength","numToRemove","_remove","move","oldIndex","newIndex","numToMove","_move","refresh","newItems","_refresh","$data","handleObj","found","map","$views.map","mapDef","newMap","source","options","changing","src","unmap","tgt","update","obsSrc","obs","map.obs","srcFlt","obsTgt","obt","map.obt","tgtFlt","baseMap","extend","mapDef.map","getTgt","unobserveAll","constructor","jQuery"] } diff --git a/jquery.views.js b/jquery.views.js index 106b441..a2d0c14 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: 60 (Beta Candidate) */ +informal pre V1.0 commit counter: 61 (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) @@ -47,7 +47,8 @@ informal pre V1.0 commit counter: 60 (Beta Candidate) */ CHECKBOX = "checkbox", RADIO = "radio", NONE = "none", - sTRUE = "true", + SCRIPT = "SCRIPT", + TRUE = "true", closeScript = '">', openScript = '', openScript = 'Name: Sir compFirst. Width: 40 - $.observable(person1).setProperty({firstName: "newFirst", lastName: "newLast"}); - $.observable(settings).setProperty({ title: "Sir", width: 40}); - $.observable(person1).setProperty({fullName: "compFirst compLast"}); - after = $("#result div *")[1].outerHTML; - // ............................... Assert ................................. - equal(before + "|" + after, - isIE8 ? 'Name: Mr Jo. Width: 30|Name: Sir compFirst. Width: 40' : 'Name: Mr Jo. Width: 30|Name: Sir compFirst. Width: 40', - 'Data link with: {^{fnTagEl/}} rendering , updates when dependant object paths change'); - // ----------------------------------------------------------------------- + function Root(a) { + this._a = a; + this.a = getsetA; + } - // ................................ Reset ................................ - $("#result").empty(); - person1._firstName = "Jo"; // reset Prop - person1.lastName = "One"; // reset Prop - settings.title = "Mr"; // reset Prop - settings.width = 30; // reset Prop + function A(b) { + this._b = b; + this.b = getsetB; + } - // =============================== Arrange =============================== - $.templates('
{^{fnTagElNoInit firstName ~settings.width/}}
') - .link("#result", person1, {settings: settings}); + var o1 = new Root(new A('one')), + o2 = new Root(new A('two')), - // ................................ Act .................................. - before = $("#result div span").html(); // The innerHTML will be ""
  • Name: Mr Jo. Width: 30
  • " - $.observable(person1).setProperty({firstName: "newFirst", lastName: "newLast"}); - $.observable(settings).setProperty({ title: "Sir", width: 40}); - $.observable(person1).setProperty({fullName: "compFirst compLast"}); - after = $("#result div span").html(); + tmpl = $.templates(''); - // ............................... Assert ................................. - equal(before + "|" + after, - 'Name: Mr Jo. Width: 30|Name: Sir compFirst. Width: 40', - 'Data link with {^{fnTagElNoInit}} rendering , updates when dependant object paths change'); - // ----------------------------------------------------------------------- + $("#result").html("
    "); - // ................................ Reset ................................ - $("#result").empty(); - person1._firstName = "Jo"; // reset Prop - person1.lastName = "One"; // reset Prop - settings.title = "Mr"; // reset Prop - settings.width = 30; // reset Prop + tmpl.link('#one', o1); + tmpl.link('#two', o2); - // =============================== Arrange =============================== + // ................................ Act .................................. + var res = "", + input1 = $("#one input"), + input2 = $("#two input"), + span1 = $("#one span"), + span2 = $("#two span"); + + function getResult() { + res += input1.val() + " " + span1.text() + " " + input2.val() + " " + span2.text() + "|"; + } - $.templates('
      {^{fnTagElCnt/}}
    ') - .link("#result", person1, {settings: settings}); + getResult(); - // ................................ Act .................................. - before = $("#result ul li").html(); // The innerHTML will be ""
  • Name: Mr Jo. Width: 30
  • " - $.observable(person1).setProperty({firstName: "newFirst", lastName: "newLast"}); - $.observable(settings).setProperty({ title: "Sir", width: 40}); - $.observable(person1).setProperty({fullName: "compFirst compLast"}); - after = $("#result ul li").html(); + input1.val('onechange').change(); - // ............................... Assert ................................. - equal(before + "|" + after, - 'Name: Mr Jo. Width: 30|Name: Sir compFirst. Width: 40', - 'Data link with {^{fnTagElCnt}} rendering
  • , updates when dependant object paths change'); - // ----------------------------------------------------------------------- + getResult(); - // ................................ Reset ................................ - $("#result").empty(); - person1._firstName = "Jo"; // reset Prop - person1.lastName = "One"; // reset Prop - settings.title = "Mr"; // reset Prop - settings.width = 30; // reset Prop + input2.val('twochange').change(); - // =============================== Arrange =============================== + getResult(); - $.templates('
      {^{fnTagElCntNoInit firstName ~settings.width/}}
    ') - .link("#result", person1, {settings: settings}); + $.observable(o1.a()).setProperty('b', 'oneupdate'); - // ................................ Act .................................. - before = $("#result ul li").html(); // The innerHTML will be ""
  • Name: Mr Jo. Width: 30
  • " - $.observable(person1).setProperty({firstName: "newFirst", lastName: "newLast"}); - $.observable(settings).setProperty({ title: "Sir", width: 40}); - $.observable(person1).setProperty({fullName: "compFirst compLast"}); - after = $("#result ul li").html(); + getResult(); - // ............................... Assert ................................. - equal(before + "|" + after, - 'Name: Mr Jo. Width: 30|Name: Sir compFirst. Width: 40', - 'Data link with {^{fnTagElCntNoInit}} rendering
  • , updates when dependant object paths change'); - // ----------------------------------------------------------------------- + $.observable(o2.a()).setProperty('b', 'twoupdate'); - // ................................ Reset ................................ - $("#result").empty(); - person1._firstName = "Jo"; // reset Prop - person1.lastName = "One"; // reset Prop - settings.title = "Mr"; // reset Prop - settings.width = 30; // reset Prop + getResult(); - // =============================== Arrange =============================== + // ............................... Assert ................................. + equal(res, + "one one two two|" + + "onechange onechange two two|" + + "onechange onechange twochange twochange|" + + "oneupdate oneupdate twochange twochange|" + + "oneupdate oneupdate twoupdate twoupdate|", + 'Two-way bindings with chained computed observables remain independent when same template links to multiple target elements'); + + // ................................ Reset ................................ + $("#result").empty(); + })(); + + (function() { + // =============================== Arrange =============================== + var people = [ + { + name: "n0", + _address: { + street: "s0" + }, + ob: function() { return this; }, + address: function() { + return this._address; + } + }, + { + name: "n1", + _address: { + street: "s1" + }, + ob: function() { return this; }, + address: function() { + return this._address; + } + } + ]; - $.templates('{^{tmplTagWithProps #data ~settings.reverse theTitle=~settings.title ~street=home.address.street/}}') - .link("#result", person1, {settings: settings}); + var tmpl = $.templates( + "
    " + + " {^{:ob().name}}" + + " {^{:ob().address().street}}" + + "
    "); - // ................................ Act .................................. - before = $("#result").text(); - $.observable(person1).setProperty({firstName: "newFirst", lastName: "newLast", "home.address.street": "newStreet"}); - $.observable(settings).setProperty({ title: "Sir", width: 40, reverse: false}); - $.observable(person1).setProperty({fullName: "compFirst compLast"}); - after = $("#result").text(); + tmpl.link("#result", people); - // ............................... Assert ................................. - equal(before + "|" + after, 'Name: Mr Jo. Width: 30. Value: true. Prop theTitle: Mr. Prop ~street: StreetOne|Name: Sir compFirst. Width: 40. Value: false. Prop theTitle: Sir. Prop ~street: newStreet', - 'Data link with: {^{tmplTagWithProps ~some.path foo=~other.path ~bar=another.path/}} updates when dependant object paths change'); - // ----------------------------------------------------------------------- + // ................................ Act .................................. + var res = "", + nameInput0 = $("#result .person0 input.name"), + nameSpan0 = $("#result .person0 span.name"), + streetInput0 = $("#result .person0 input.street"), + streetSpan0 = $("#result .person0 span.street"), + nameInput1 = $("#result .person1 input.name"), + nameSpan1 = $("#result .person1 span.name"), + streetInput1 = $("#result .person1 input.street"), + streetSpan1 = $("#result .person1 span.street"); + + function getResult() { + res += nameInput0.val() + " " + nameSpan0.text() + " " + streetInput0.val() + " " + streetSpan0.text() + " " + nameInput1.val() + " " + nameSpan1.text() + " " + streetInput1.val() + " " + streetSpan1.text() + "|"; + } - // ................................ Reset ................................ - $("#result").empty(); - person1._firstName = "Jo"; // reset Prop - person1.lastName = "One"; // reset Prop - settings.title = "Mr"; // reset Prop - settings.width = 30; // reset Prop - settings.reverse = true; // reset Prop - address1.street = "StreetOne"; // reset Prop + getResult(); - // =============================== Arrange =============================== + nameInput0.val('n0new').change(); - $.templates('{{tmplTagWithProps #data ~settings.reverse theTitle=~settings.title ~street=home.address.street/}}') - .link("#result", person1, {settings: settings}); + getResult(); - // ................................ Act .................................. - before = $("#result").text(); - $.observable(person1).setProperty({firstName: "newFirst", lastName: "newLast", "home.address.street": "newStreet"}); - $.observable(settings).setProperty({ title: "Sir", width: 40, reverse: false}); - $.observable(person1).setProperty({fullName: "compFirst compLast"}); - after = $("#result").text(); + nameInput1.val('n1new').change(); - // ............................... Assert ................................. - ok(before === 'Name: Mr Jo. Width: 30. Value: true. Prop theTitle: Mr. Prop ~street: StreetOne' && before === after && !$._data(person1).events && !$._data(settings).events, - 'Data link with: {{tmplTagWithProps ~some.path foo=~other.path ~bar=another.path/}} does nothing'); - // ----------------------------------------------------------------------- + getResult(); - // ................................ Reset ................................ - $("#result").empty(); - person1._firstName = "Jo"; // reset Prop - person1.lastName = "One"; // reset Prop - settings.title = "Mr"; // reset Prop - settings.width = 30; // reset Prop - settings.reverse = true; // reset Prop - address1.street = "StreetOne"; // reset Prop + streetInput0.val('s0new').change(); - // =============================== Arrange =============================== + getResult(); - $.templates('{^{fnTagWithProps #data ~settings.reverse theTitle=~settings.title ~street=home.address.street/}}') - .link("#result", person1, {settings: settings}); + streetInput1.val('s1new').change(); - // ................................ Act .................................. - before = $("#result").text(); - $.observable(person1).setProperty({firstName: "newFirst", lastName: "newLast", "home.address.street": "newStreet"}); - $.observable(settings).setProperty({ title: "Sir", width: 40, reverse: false}); - $.observable(person1).setProperty({fullName: "compFirst compLast"}); - after = $("#result").text(); + getResult(); - // ............................... Assert ................................. - equal(before + "|" + after, 'Name: Mr Jo. Width: 30. Value: true. Prop theTitle: Mr. Prop ~street: StreetOne|Name: Sir compFirst. Width: 40. Value: false. Prop theTitle: Sir. Prop ~street: newStreet', - 'Data link with: {^{fnTagWithProps ~some.path foo=~other.path ~bar=another.path/}} updates when dependant object paths change'); - // ----------------------------------------------------------------------- + $.observable(people[0]).setProperty('name', 'n0update'); - // ................................ Reset ................................ - $("#result").empty(); - person1._firstName = "Jo"; // reset Prop - person1.lastName = "One"; // reset Prop - settings.title = "Mr"; // reset Prop - settings.width = 30; // reset Prop - settings.reverse = true; // reset Prop - address1.street = "StreetOne"; // reset Prop + getResult(); - // =============================== Arrange =============================== + $.observable(people[0].address()).setProperty('street', 's0update'); - $.templates('{{fnTagWithProps #data ~settings.reverse theTitle=~settings.title ~street=home.address.street/}}') - .link("#result", person1, {settings: settings}); + getResult(); - // ................................ Act .................................. - before = $("#result").text(); - $.observable(person1).setProperty({firstName: "newFirst", lastName: "newLast", "home.address.street": "newStreet"}); - $.observable(settings).setProperty({ title: "Sir", width: 40, reverse: false}); - $.observable(person1).setProperty({fullName: "compFirst compLast"}); - after = $("#result").text(); + $.observable(people[1]).setProperty('name', 'n1update'); - // ............................... Assert ................................. - ok(before === 'Name: Mr Jo. Width: 30. Value: true. Prop theTitle: Mr. Prop ~street: StreetOne' && before === after && !$._data(person1).events && !$._data(settings).events, - 'Data link with: {{fnTagWithProps ~some.path foo=~other.path ~bar=another.path/}} does nothing'); - // ----------------------------------------------------------------------- + getResult(); - // ................................ Reset ................................ - $("#result").empty(); - person1._firstName = "Jo"; // reset Prop - person1.lastName = "One"; // reset Prop - settings.title = "Mr"; // reset Prop - settings.width = 30; // reset Prop - settings.reverse = true; // reset Prop - address1.street = "StreetOne"; // reset Prop + $.observable(people[1].address()).setProperty('street', 's1update'); - // =============================== Arrange =============================== - $.views.tags({ - myTag: { - template: "{{:~tag.tagCtx.args[0]}}", - attr: "html" - } - }); + getResult(); - $.templates("{^{myTag foo(\"w\\x\'y\").b/}}
    ") - .link("#result", { - foo: function(val) { - return {b: val}; + $.observable(people).remove(1); + + $.observable(people).insert({ + name: "n1inserted", + _address: { + street: "s1inserted", + address: function() { + return this; + } + }, + ob: function() { return this; }, + address: function() { + return this._address; } }); - // ............................... Assert ................................. - equal($("#result span")[0].outerHTML, isIE8 ? "w\\x\'y" : "w\\x\'y", - "{^{myTag foo(\"w\\x\'y\").b/}} - correct compilation and output of quotes and backslash, with object returned in path (so nested compilation)"); - equal($("#result span")[1].outerHTML, isIE8 ? "w\\x" : "w\\x", - "
    - correct compilation and output of quotes and backslash, with object returned in path (so nested compilation)"); -}); + nameInput1 = $("#result .person1 input.name"); + nameSpan1 = $("#result .person1 span.name"); + streetInput1 = $("#result .person1 input.street"); + streetSpan1 = $("#result .person1 span.street"); -test("{^{for}}", function() { + getResult(); - // =============================== Arrange =============================== + // ............................... Assert ................................. + equal(res, + "n0 n0 s0 s0 n1 n1 s1 s1|" + + "n0new n0new s0 s0 n1 n1 s1 s1|" + + "n0new n0new s0 s0 n1new n1new s1 s1|" + + "n0new n0new s0new s0new n1new n1new s1 s1|" + + "n0new n0new s0new s0new n1new n1new s1new s1new|" + + "n0update n0update s0new s0new n1new n1new s1new s1new|" + + "n0update n0update s0update s0update n1new n1new s1new s1new|" + + "n0update n0update s0update s0update n1update n1update s1new s1new|" + + "n0update n0update s0update s0update n1update n1update s1update s1update|" + + "n0update n0update s0update s0update n1inserted n1inserted s1inserted s1inserted|", + 'Two-way bindings with chained computed observables remain independent when same template links different elements of an array'); + + // ................................ Reset ................................ + $("#result").empty(); + })(); + + (function() { + // =============================== Arrange =============================== + var people = [ + { + name: "n0", + _address: { + street: "s0" + }, + ob: function() { return this; }, + address: function() { + return this._address; + } + }, + { + name: "n1", + _address: { + street: "s1" + }, + ob: function() { return this; }, + address: function() { + return this._address; + } + } + ]; - model.things = [{ thing: "box" }]; // reset Prop - $.templates('{^{for things}}{{:thing}}{{/for}}') - .link("#result", model); + var tmpl = $.templates( + "{^{for people}}
    " + + " {^{:ob().name}}" + + " {^{:ob().address().street}}" + + "
    {{/for}}"); - // ................................ Act .................................. - before = $("#result").text(); - $.observable(model.things).insert(0, { thing: "tree" }); - after = $("#result").text(); + tmpl.link("#result", { people: people }); - // ............................... Assert ................................. - equal(before + "|" + after, 'box|treebox', - '{^{for things}} binds to array changes on leaf array'); + // ................................ Act .................................. + var res = "", + nameInput0 = $("#result .person0 input.name"), + nameSpan0 = $("#result .person0 span.name"), + streetInput0 = $("#result .person0 input.street"), + streetSpan0 = $("#result .person0 span.street"), + nameInput1 = $("#result .person1 input.name"), + nameSpan1 = $("#result .person1 span.name"), + streetInput1 = $("#result .person1 input.street"), + streetSpan1 = $("#result .person1 span.street"); + + function getResult() { + res += nameInput0.val() + " " + nameSpan0.text() + " " + streetInput0.val() + " " + streetSpan0.text() + " " + nameInput1.val() + " " + nameSpan1.text() + " " + streetInput1.val() + " " + streetSpan1.text() + "|"; + } - // ................................ Act .................................. - $.observable(model).setProperty({ things: [{ thing: "triangle" }, { thing: "circle" }] }); - after = $("#result").text(); + getResult(); - // ............................... Assert ................................. - equal(after, 'trianglecircle', - '{^{for things}} binds to property change on path'); + nameInput0.val('n0new').change(); - // ................................ Act .................................. - $.observable(model).setProperty({ things: { thing: "square" } }); - after = $("#result").text(); + getResult(); - // ............................... Assert ................................. - equal(after, 'square', - '{^{for things}} binds to property change on path - swapping from array to singleton object'); - // ----------------------------------------------------------------------- + nameInput1.val('n1new').change(); - // ................................ Act .................................. - $.observable(model).setProperty({ things: [{ thing: "triangle2" }, { thing: "circle2" }] }); - after = $("#result").text(); + getResult(); - // ............................... Assert ................................. - equal(after, 'triangle2circle2', - '{^{for things}} binds to property change on path - swapping from singleton back to array'); - // ----------------------------------------------------------------------- + streetInput0.val('s0new').change(); - // ................................ Act .................................. - $.observable(model.things).insert([{ thing: "oblong" }, { thing: "pentagon" }]); - after = $("#result").text(); + getResult(); - // ............................... Assert ................................. - equal(after, 'triangle2circle2oblongpentagon', - '{^{for things}} binds to array change on array after swapping from singleton back to array'); - // ----------------------------------------------------------------------- + streetInput1.val('s1new').change(); - // ................................ Reset ................................ - $("#result").empty(); - model.things = []; // reset Prop + getResult(); - // =============================== Arrange =============================== + $.observable(people[0]).setProperty('name', 'n0update'); - var things1 = [{ thing: "box" }], - things2 = [{ thing: "triangle" }, { thing: "circle" }], - square = { thing: "square" }; + getResult(); - model.things = things1; // reset Prop + $.observable(people[0].address()).setProperty('street', 's0update'); - $.templates('{^{for things}}{{:thing}}{{/for}}') - .link("#result", model); + getResult(); - // ................................ Act .................................. - before = $("#result").text(); - $.observable(things1).insert(0, { thing: "tree" }); - after = $("#result").text(); + $.observable(people[1]).setProperty('name', 'n1update'); - // ............................... Assert ................................. - equal(before + "|" + after, 'box|treebox', - '{^{for things}} binds to array changes on leaf array'); + getResult(); - // ................................ Act .................................. - $.observable(model).setProperty({ things: things2 }); - after = $("#result").text(); + $.observable(people[1].address()).setProperty('street', 's1update'); - // ............................... Assert ................................. - equal(after, 'trianglecircle', - '{^{for things}} binds to property change on path'); + getResult(); - // ................................ Act .................................. - $.observable(model).setProperty({ things: square }); - after = $("#result").text(); + $.observable(people).remove(1); - // ............................... Assert ................................. - equal(after, 'square', - '{^{for things}} binds to property change on path - swapping from array to singleton object'); - // ----------------------------------------------------------------------- + $.observable(people).insert({ + name: "n1inserted", + _address: { + street: "s1inserted" + }, + ob: function() { return this; }, + address: function() { + return this._address; + } + }); - // ................................ Act .................................. - $.observable(model).setProperty({ things: things2 }); - after = $("#result").text(); + nameInput1 = $("#result .person1 input.name"); + nameSpan1 = $("#result .person1 span.name"); + streetInput1 = $("#result .person1 input.street"); + streetSpan1 = $("#result .person1 span.street"); - // ............................... Assert ................................. - equal(after, 'trianglecircle', - '{^{for things}} binds to property change on path - swapping from singleton back to previous array'); - // ----------------------------------------------------------------------- + getResult(); - // ................................ Act .................................. - $.observable(things2).insert([{ thing: "oblong" }, { thing: "pentagon" }]); - after = $("#result").text(); + // ............................... Assert ................................. + equal(res, + "n0 n0 s0 s0 n1 n1 s1 s1|" + + "n0new n0new s0 s0 n1 n1 s1 s1|" + + "n0new n0new s0 s0 n1new n1new s1 s1|" + + "n0new n0new s0new s0new n1new n1new s1 s1|" + + "n0new n0new s0new s0new n1new n1new s1new s1new|" + + "n0update n0update s0new s0new n1new n1new s1new s1new|" + + "n0update n0update s0update s0update n1new n1new s1new s1new|" + + "n0update n0update s0update s0update n1update n1update s1new s1new|" + + "n0update n0update s0update s0update n1update n1update s1update s1update|" + + "n0update n0update s0update s0update n1inserted n1inserted s1inserted s1inserted|", + 'Two-way bindings with chained computed observables remain independent when same {{for}} block links different elements of an array'); + + // ................................ Reset ................................ + $("#result").empty(); + })(); + + (function() { + // =============================== Arrange =============================== + var people = [ + { + name: "n0", + _address: { + street: "s0" + }, + ob: function() { return this; }, + address: function() { + return this._address; + } + }, + { + name: "n1", + _address: { + street: "s1" + }, + ob: function() { return this; }, + address: function() { + return this._address; + } + } + ]; - // ............................... Assert ................................. - equal(after, 'trianglecircleoblongpentagon', - '{^{for things}} binds to array change on array after swapping from singleton back to array'); - // ----------------------------------------------------------------------- + var tmpl = $.templates( + "
    " + + " {^{:ob().name}}" + + " {^{:ob().address().street}}" + + "
    "); - // ................................ Reset ................................ - $("#result").empty(); - model.things = []; // reset Prop + tmpl.link("#result", people); - // =============================== Arrange =============================== + // ................................ Act .................................. + var res = "", + nameInput0 = $("#result .person0 input.name"), + nameSpan0 = $("#result .person0 span.name"), + streetInput0 = $("#result .person0 input.street"), + streetSpan0 = $("#result .person0 span.street"), + nameInput1 = $("#result .person1 input.name"), + nameSpan1 = $("#result .person1 span.name"), + streetInput1 = $("#result .person1 input.street"), + streetSpan1 = $("#result .person1 span.street"); + + function getResult() { + res += nameInput0.val() + " " + nameSpan0.text() + " " + streetInput0.val() + " " + streetSpan0.text() + " " + nameInput1.val() + " " + nameSpan1.text() + " " + streetInput1.val() + " " + streetSpan1.text() + "|"; + } - things1 = [{ thing: "box" }], - things2 = [{ thing: "triangle" }, { thing: "circle" }]; - square = { thing: "square" }; + getResult(); - model.things = things1; // reset Prop + nameInput0.val('n0new').change(); - $.templates('
      {^{for things}}
    • {{:thing}}
    • {{/for}}
    ') - .link("#result", model); + getResult(); - // ................................ Act .................................. - before = $("#result").text(); - $.observable(things1).insert(0, { thing: "tree" }); - after = $("#result").text(); + nameInput1.val('n1new').change(); - // ............................... Assert ................................. - equal(before + "|" + after, 'box|treebox', - '{^{for things}} in element content binds to array changes on leaf array'); + getResult(); - // ................................ Act .................................. - $.observable(model).setProperty({ things: things2 }); - after = $("#result").text(); + streetInput0.val('s0new').change(); - // ............................... Assert ................................. - equal(after, 'trianglecircle', - '{^{for things}} binds to property change on path'); + getResult(); - // ................................ Act .................................. - $.observable(model).setProperty({ things: square }); - after = $("#result").text(); + streetInput1.val('s1new').change(); - // ............................... Assert ................................. - equal(after, 'square', - '{^{for things}} binds to property change on path - swapping from array to singleton object'); - // ----------------------------------------------------------------------- + getResult(); - // ................................ Act .................................. - $.observable(model).setProperty({ things: things2 }); - after = $("#result").text(); + // ............................... Assert ................................. + equal(res, + "n0 n0 s0 s0 n1 n1 s1 s1|" + + "n0new n0 n0new n0new n1 n1 s1 s1|" + + "n0new n0 n0new n0new n1new n1 n1new n1new|" + + "s0new s0new s0new n0new n1new n1 n1new n1new|" + + "s0new s0new s0new n0new s1new s1new s1new n1new|" + , + 'Two-way bindings with chained computed observables using linkTo remain independent when same template links different elements of an array'); + + // ................................ Reset ................................ + $("#result").empty(); + })(); + + (function() { + // =============================== Arrange =============================== + function ob() { + return this.alt ? this._obB : this._obA; + } + ob.depends = "alt"; - // ............................... Assert ................................. - equal(after, 'trianglecircle', - '{^{for things}} binds to property change on path - swapping from singleton back to previous array'); - // ----------------------------------------------------------------------- + function change() { + $.observable(this._address).setProperty("street", this._address.street + "+"); + } - // ................................ Act .................................. - $.observable(things2).insert([{ thing: "oblong" }, { thing: "pentagon" }]); - after = $("#result").text(); + function switchAlt() { + $.observable(this).setProperty("alt", !this.alt); + } - // ............................... Assert ................................. - equal(after, 'trianglecircleoblongpentagon', - '{^{for things}} binds to array change on array after swapping from singleton back to array'); - // ----------------------------------------------------------------------- + var people = [ + { + alt: false, + ob: ob, + switchAlt: switchAlt, + _obA: { + change: change, + _address: { + street: "A0" + }, + address: function() { + return this._address; + } + }, + _obB: { + change: change, + _address: { + street: "B0" + }, + address: function() { + return this._address; + } + } + }, + { + alt: false, + ob: ob, + switchAlt: switchAlt, + _obA: { + change: change, + _address: { + street: "A1" + }, + address: function() { + return this._address; + } + }, + _obB: { + change: change, + _address: { + street: "B1" + }, + address: function() { + return this._address; + } + } + } + ]; - // ................................ Reset ................................ - $("#result").empty(); - model.things = []; // reset Prop + var tmpl = $.templates( + "
    " + + "" + + "" + + "" + + "" + + " " + + " " + + "" + + "" + + "" + + "
    "); + + tmpl.link("#result", people); - // =============================== Arrange =============================== + // ................................ Act .................................. + var res = "", + toStreet1_0 = $("#result .person0 input.toStreet1"), + toStreet2_0 = $("#result .person0 input.toStreet2"), + toStreetA_0 = $("#result .person0 input.toStreetA"), + toStreetB_0 = $("#result .person0 input.toStreetB"), + spanStreet1_0 = $("#result .person0 span.street1"), + spanStreet2_0 = $("#result .person0 span.street2"), + street1_0 = $("#result .person0 input.street1"), + street2_0 = $("#result .person0 input.street2"), + streetA_0 = $("#result .person0 input.streetA"), + streetB_0 = $("#result .person0 input.streetB"), + + toStreet1_1 = $("#result .person1 input.toStreet1"), + toStreet2_1 = $("#result .person1 input.toStreet2"), + toStreetA_1 = $("#result .person1 input.toStreetA"), + toStreetB_1 = $("#result .person1 input.toStreetB"), + spanStreet1_1 = $("#result .person1 span.street1"), + spanStreet2_1 = $("#result .person1 span.street2"), + street1_1 = $("#result .person1 input.street1"), + street2_1 = $("#result .person1 input.street2"), + streetA_1 = $("#result .person1 input.streetA"), + streetB_1 = $("#result .person1 input.streetB"); + + function getResult(name) { + res += name + ":" + spanStreet1_0.text() + " " + spanStreet2_0.text() + " " + street1_0.val() + " " + street2_0.val() + " " + streetA_0.val() + " " + streetB_0.val() + + " " + spanStreet1_1.text() + " " + spanStreet2_1.text() + " " + street1_1.val() + " " + street2_1.val() + " " + streetA_1.val() + " " + streetB_1.val() + "|"; + } - model.things = [{thing: "box"}, {thing: "table"}]; // reset Prop + getResult(1); - $.templates('{^{:length}} {^{for #data}}{{:thing}}{{/for}}') - .link("#result", model.things, null, true); + people[0]._obA.change(); + people[0]._obB.change(); + getResult(2); - // ................................ Act .................................. - before = $("#result").text(); - $.observable(model.things).insert(0, {thing: "tree"}); - after = $("#result").text(); + people[0].switchAlt(); + getResult(3); - // ............................... Assert ................................. - equal(before + "|" + after, isIE8 ? "2 boxtable|3tree boxtable" : "2 boxtable|3 treeboxtable", - '{^{for #data}} when #data is an array binds to array changes on #data'); + people[0]._obA.change(); + people[0]._obB.change(); + getResult(4); - // ................................ Reset ................................ - $("#result").empty(); - model.things = []; // reset Prop + people[0].switchAlt(); + getResult(5); - // =============================== Arrange =============================== + people[1].ob().change(); + getResult(6); - model.things = [{thing: "box"}, {thing: "table"}]; // reset Prop + people[1].switchAlt(); + getResult(7); - $.templates('{^{:length}} {^{for}}{{:thing}}{{/for}}') - .link("#result", model.things, null, true); + people[1].ob().change(); + getResult(8); - // ................................ Act .................................. - before = $("#result").text(); - $.observable(model.things).insert(0, {thing: "tree"}); - after = $("#result").text(); + toStreet1_0.val("new1").change(); + getResult(9); - // ............................... Assert ................................. - equal(before + "|" + after, isIE8 ? "2 boxtable|3tree boxtable" : "2 boxtable|3 treeboxtable", - '{^{for}} when #data is an array binds to array changes on #data'); + toStreet2_0.val("new2").change(); + getResult(10); - // ................................ Reset ................................ - $("#result").empty(); - model.things = []; // reset Prop + toStreetA_0.val("new3").change(); + getResult(11); - // =============================== Arrange =============================== + toStreetB_0.val("new4").change(); + getResult(12); - model.things = [{thing: "box"}, {thing: "table"}]; // reset Prop + toStreet1_1.val("new5").change(); + getResult(13); - $.templates('{{include things}}{^{:length}} {^{for}}{{:thing}}{{/for}}{{/include}}') - .link("#result", model); + toStreet2_1.val("new6").change(); + getResult(14); - // ................................ Act .................................. - before = $("#result").text(); - $.observable(model.things).insert(0, {thing: "tree"}); - after = $("#result").text(); + toStreetA_1.val("new7").change(); + getResult(15); - // ............................... Assert ................................. - equal(before + "|" + after, isIE8 ? "2 boxtable|3tree boxtable" : "2 boxtable|3 treeboxtable", - '{{include things}} moves context to things array, and {^{for}} then iterates and binds to array'); + toStreetB_1.val("new8").change(); + getResult(16); - // ................................ Reset ................................ - $("#result").empty(); - model.things = []; // reset Prop + // ............................... Assert ................................. + equal(res, + "1:A0 A0 A0 A0 A0 B0 A1 A1 A1 A1 A1 B1|" + + "2:A0+ A0+ A0+ A0+ A0+ B0+ A1 A1 A1 A1 A1 B1|" + + "3:B0+ B0+ B0+ B0+ A0+ B0+ A1 A1 A1 A1 A1 B1|" + + "4:B0++ B0++ B0++ B0++ A0++ B0++ A1 A1 A1 A1 A1 B1|" + + "5:A0++ A0++ A0++ A0++ A0++ B0++ A1 A1 A1 A1 A1 B1|" + + "6:A0++ A0++ A0++ A0++ A0++ B0++ A1+ A1+ A1+ A1+ A1+ B1|" + + "7:A0++ A0++ A0++ A0++ A0++ B0++ B1 B1 B1 B1 A1+ B1|" + + "8:A0++ A0++ A0++ A0++ A0++ B0++ B1+ B1+ B1+ B1+ A1+ B1+|" + + "9:new1 new1 new1 new1 new1 B0++ B1+ B1+ B1+ B1+ A1+ B1+|" + + "10:new2 new2 new2 new2 new2 B0++ B1+ B1+ B1+ B1+ A1+ B1+|" + + "11:new3 new3 new3 new3 new3 B0++ B1+ B1+ B1+ B1+ A1+ B1+|" + + "12:new3 new3 new3 new3 new3 new4 B1+ B1+ B1+ B1+ A1+ B1+|" + + "13:new3 new3 new3 new3 new3 new4 new5 new5 new5 new5 A1+ new5|" + + "14:new3 new3 new3 new3 new3 new4 new6 new6 new6 new6 A1+ new6|" + + "15:new3 new3 new3 new3 new3 new4 new6 new6 new6 new6 new7 new6|" + + "16:new3 new3 new3 new3 new3 new4 new8 new8 new8 new8 new7 new8|", + 'Two-way bindings with chained computed observables with or without linkTo remain independent when multiple bindings in same tag block use same path expression'); + + // ................................ Reset ................................ + $("#result").empty(); + })(); - // =============================== Arrange =============================== +}); - model.things = [{thing: "box"}]; // reset Prop - $.templates('{^{for things}}{{:thing}}{{else}}None{{/for}}') - .link("#result", model); +test("Chained computed observables in template expressions", function() { - // ................................ Act .................................. - before = $("#result").text(); - $.observable(model.things).insert(0, {thing: "tree"}); - after = $("#result").text(); + (function() { + // =============================== Arrange =============================== + function ob() { + return helpers.alt ? this._obB : this._obA; + } - // ............................... Assert ................................. - equal(before + "|" + after, 'box|treebox', - '{^{for things}}{{else}}{{/for}} binds to array changes on leaf array'); + ob.depends = "~helpers.alt"; - // ................................ Act .................................. - before = $("#result").text(); - $.observable(model.things).remove(0, 2); - after = $("#result").text(); + ob.set = function(val) { + if (helpers.alt) { + this._obB = val; + } else { + this._obA = val; + } + } - // ............................... Assert ................................. - equal(before + "|" + after, 'treebox|None', - '{^{for things}}{{else}}{{/for}} renders {{else}} block when array is emptied'); + function address() { + return this._address; + } - // ................................ Act .................................. - $.observable(model).setProperty({things:[{thing: "triangle"}, {thing: "circle"}]}); - after = $("#result").text(); + function setAddress(val) { + this._address = val; + } - // ............................... Assert ................................. - equal(after, 'trianglecircle', - '{^{for things}}{{else}}{{/for}} binds to property change on path'); + address.set = setAddress; - // ................................ Act .................................. - $.observable(model).setProperty({things:{thing: "square"}}); - after = $("#result").text(); + var res, resCount, helpers, ch, person, person2, data; - // ............................... Assert ................................. - equal(after, 'square', - '{^{for things}}{{else}}{{/for}} binds to property change on path - swapping from array to singleton object'); + function setData() { + res = ""; + resCount = 1; + ch = 0; - // ................................ Act .................................. - $.observable(model).removeProperty("things"); - after = $("#result").text(); + helpers = {alt: false}; - // ............................... Assert ................................. - equal(after, 'None', - '{^{for things}}{{else}}{{/for}} binds to removeProperty change on path - and renders {{else}} block'); - // ----------------------------------------------------------------------- + person = { + ob: ob, + _obA: { + home: { + _address: { + street: "A" + }, + _address2: { + street: "A2" + }, + address: address + } + }, + _obB: { + home: { + _address: { + street: "B" + }, + _address2: { + street: "B2" + }, + address: address + } + } + }; + person2 = { + ob: ob, + _obA: { + home: { + _address: { + street: "xA" + }, + _address2: { + street: "xA2" + }, + address: address + } + }, + _obB: { + home: { + _address: { + street: "xB" + }, + _address2: { + street: "xB2" + }, + address: address + } + } + }; + data = {person: person}; + } - // ................................ Reset ................................ - $("#result").empty(); - model.things = []; // reset Prop + function swapPerson() { + $.observable(data).setProperty("person", data.person === person ? person2 : person); + } - // =============================== Arrange =============================== + function changeAlt() { + $.observable(helpers).setProperty("alt", !helpers.alt); + } - // ................................ Act .................................. - $.templates("testTmpl", '{{if ~things.length}}{{for ~things}}{{:thing}}{{/for}}{{/if}}'); - $.templates('{^{for things ~things=things tmpl="testTmpl"/}}
    top
    ') - .link("#result", model); + function changeOb() { + $.observable(data.person).setProperty("ob", { + home: { + _address: { + street: data.person.ob().home._address.street + ch++ + }, + _address2: { + street: data.person.ob().home._address2.street + ch + }, + address: address + } + }); + } - before = $("#result td").text(); - $.observable(model.things).insert(0, {thing: "tree"}); - after = $("#result").text(); + function changeHome() { + $.observable(data.person.ob()).setProperty("home", { + _address: { + street: data.person.ob().home._address.street + "$" + }, + _address2: { + street: data.person.ob().home._address2.street + "$" + }, + address: address + }); + } - // ............................... Assert ................................. - equal(before + "|" + after, 'top|toptree', - 'Complex template, with empty placeholder for tbody after thead, and subsequent data-linked insertion of tbody'); - // ----------------------------------------------------------------------- + function changeAddress() { + $.observable(data.person.ob().home).setProperty("address", {street: data.person.ob().home.address().street + "+"}); + } - // ................................ Act .................................. - $.view("#result", true).refresh(); - result = "" + (after === $("#result").text()); - $.view("#result", true).views._2.refresh(); - result += " " + (after === $("#result").text()); - $.view("#result", true).views._2.views[0].refresh(); - result += " " + (after === $("#result").text()); - $.view("#result", true).views._2.views[0].views._2.refresh(); - result += " " + (after === $("#result").text()); - $.view("#result", true).views._2.views[0].views._2.views._2.refresh(); - result += " " + (after === $("#result").text()); - $.view("#result", true).views._2.views[0].views._2.views._2.views[0].refresh(); - result += " " + (after === $("#result").text()); + function changeStreet() { + $.observable(data.person.ob().home.address()).setProperty("street", data.person.ob().home.address().street + ">"); + } - // ............................... Assert ................................. - equal(result, 'true true true true true true', - 'view refresh at all levels correctly maintains content'); - // ----------------------------------------------------------------------- + function getResult(name) { + res += (name||resCount++) + ": " + $("#result").text() + " |"; + } - // ................................ Reset ................................ - $("#result").empty(); - model.things = []; // reset Prop + var tmpl = $.templates(""); - // =============================== Arrange =============================== + setData(); - // ................................ Act .................................. - $.templates("testTmpl", '{{if ~things.length}}
    {{for ~things}}{{:thing}}{{/for}}
    {{/if}}'); - $.templates('
    top{^{for things ~things=things tmpl="testTmpl"/}}
    ') - .link("#result", model); + tmpl.link("#result", data, {helpers: helpers}); - before = $("#result div").text(); - $.observable(model.things).insert(0, {thing: "tree"}); - after = $("#result").text(); + // ................................ Act .................................. + getResult("None"); - // ............................... Assert ................................. - equal(before + "|" + after, 'top|toptree', - 'Complex template, with empty placeholder for span, and subsequent data-linked insertion of in div'); - // ----------------------------------------------------------------------- + changeStreet(); + getResult("Street"); - // ................................ Act .................................. - $.view("#result", true).refresh(); - result = "" + (after === $("#result").text()); - $.view("#result", true).views._2.refresh(); - result += " " + (after === $("#result").text()); - $.view("#result", true).views._2.views[0].refresh(); - result += " " + (after === $("#result").text()); - $.view("#result", true).views._2.views[0].views._2.refresh(); - result += " " + (after === $("#result").text()); - $.view("#result", true).views._2.views[0].views._2.views._2.refresh(); - result += " " + (after === $("#result").text()); - $.view("#result", true).views._2.views[0].views._2.views._2.views[0].refresh(); - result += " " + (after === $("#result").text()); + changeAddress(); + getResult("Address"); - // ............................... Assert ................................. - equal(result, 'true true true true true true', - 'view refresh at all levels correctly maintains content'); - // ----------------------------------------------------------------------- + changeHome(); + getResult("Home"); - // ................................ Reset ................................ - $("#result").empty(); - model.things = []; // reset Prop + changeOb(); + getResult("Ob"); - // =============================== Arrange =============================== - // ................................ Act .................................. - $.templates('{^{for things}}{^{if expanded}}{{/if}}{{/for}}
    {{:thing}}
    ') - .link("#result", model); + changeAlt(); + getResult("Alt"); - $.observable(model.things).insert(0, [{thing: "tree", expanded: false}]); - result = $._data(model.things[0]).events.propertyChange.length; - $.view("#result", true).views._1.views[0].refresh(); - result += "|" + $._data(model.things[0]).events.propertyChange.length; - $("#result").empty(); - result += "|" + $._data(model.things[0]).events; + changeHome(); + getResult("Home"); - // ............................... Assert ................................. - equal(result, '1|1|undefined', - 'Refreshing a view containing a tag which is bound to dependant data, and has no _prv node, removes the original binding and replaces it with a new one'); + changeAddress(); + getResult("Address"); - // ................................ Reset ................................ - $("#result").empty(); - model.things = []; // reset Prop + changeStreet(); + getResult("Street"); - // =============================== Arrange =============================== + swapPerson(); + getResult("Person"); - // ................................ Act .................................. - $.templates('
    {^{for things}}{^{if expanded}}{{:thing}}{{/if}}{{/for}}
    ') - .link("#result", model); + changeHome(); + getResult("Home"); - $.observable(model.things).insert(0, [{thing: "tree", expanded: false}]); - result = $._data(model.things[0]).events.propertyChange.length; - $.view("#result", true).views._1.views[0].refresh(); - result += "|" + $._data(model.things[0]).events.propertyChange.length; - $("#result").empty(); - result += "|" + $._data(model.things[0]).events; + changeStreet(); + getResult("Street"); - // ............................... Assert ................................. - equal(result, '1|1|undefined', - 'Refreshing a view containing a tag which is bound to dependant data, and has no _prv node, removes the original binding and replaces it with a new one'); + changeAddress(); + getResult("Address"); - // ................................ Reset ................................ - $("#result").empty(); - model.things = []; // reset Prop + changeOb(); + getResult("Ob"); - // =============================== Arrange =============================== + changeHome(); + getResult("Home"); - // ................................ Act .................................. - $.templates('
    {{if true}}{^{:things.length||""}}{{/if}}
    ') - .link("#result", model); + changeStreet(); + getResult("Street"); - before = $("#result div *").length; - $.view("#result div", true).refresh(); - after = $("#result div *").length; - // ............................... Assert ................................. - equal(after, before, - 'Refreshing a view containing non-elOnly content, with a data-bound tag with no rendered content removes the original script node markers for the tag and replace with the new ones'); + changeAddress(); + getResult("Address"); - // ................................ Reset ................................ - $("#result").empty(); - model.things = []; // reset Prop + // ............................... Assert ................................. + equal(res, "None: A |Street: A> |Address: A>+ |Home: A>+$ |Ob: A>+$0 |Alt: B |Home: B$ |Address: B$+ |Street: B$+> |Person: xB |" + + "Home: xB$ |Street: xB$> |Address: xB$>+ |Ob: xB$>+1 |Home: xB$>+1$ |Street: xB$>+1$> |Address: xB$>+1$>+ |", + "Deep path with chained observables, binding to full depth: person^ob().home.address().street"); - // =============================== Arrange =============================== + // ................................ Reset ................................ + $("#result").empty(); + setData(); - // ................................ Act .................................. - $.templates("testTmpl", '{^{if expanded}}{{:thing}}{{/if}}'); - $.templates('{^{for things tmpl="testTmpl"/}}
    ') - .link("#result", model); + // =============================== Arrange =============================== + tmpl = $.templates(""); - result = $("#result td").text(); - $.observable(model.things).insert(0, [{thing: "tree", expanded: false}, {thing: "bush", expanded: true}]); - result += "|" + $("#result td").text(); - $.observable(model.things[0]).setProperty("expanded", true); - $.observable(model.things[1]).setProperty("expanded", false); - result += "|" + $("#result td").text(); + tmpl.link("#result", data, {helpers: helpers}); - // ............................... Assert ................................. - equal(result, '|bush|tree', - 'Changing dependant data on bindings with deferred correctly triggers refreshTag and refreshes content with updated data binding'); + // ................................ Act .................................. - // ................................ Act .................................. - $.view("#result tr").parent.refresh(); - result = $("#result td").text(); - $.view("#result tr").parent.parent.views[1].refresh(); - result += "|" + $("#result td").text(); + getResult("None"); - // ............................... Assert ................................. - equal(result, 'tree|tree', - 'view refresh with deferred correctly refreshes content'); + changeStreet(); + getResult("Street"); - // ................................ Act .................................. - $.observable(model.things[1]).setProperty("expanded", true); - result = $("#result td").text(); + changeAddress(); + getResult("Address"); - $.observable(model.things[0]).setProperty("expanded", false); - result += "|" + $("#result td").text(); + changeHome(); + getResult("Home"); - // ............................... Assert ................................. - equal(result, 'treebush|bush', - 'Changing dependant data on bindings with deferred, after view refresh correctly triggers refreshTag and refreshes content with updated data binding'); - // ----------------------------------------------------------------------- + changeOb(); + getResult("Ob"); - // ................................ Reset ................................ - $("#result").empty(); - model.things = []; // reset Prop + changeAlt(); + getResult("Alt"); - // =============================== Arrange =============================== + changeHome(); + getResult("Home"); - // ................................ Act .................................. - $.templates("testTmpl", '{^{if expanded}}{{:thing}}{{/if}}'); - $.templates('{^{for things tmpl="testTmpl"/}}
    ') - .link("#result", model); + changeAddress(); + getResult("Address"); - result = $("#result td").text(); - $.observable(model.things).insert(0, [{thing: "tree", expanded: false}, {thing: "bush", expanded: true}]); - result += "|" + $("#result").text(); - $.observable(model.things[0]).setProperty("expanded", true); - $.observable(model.things[1]).setProperty("expanded", false); - result += "|" + $("#result").text(); + changeStreet(); + getResult("Street"); - // ............................... Assert ................................. - equal(result, '|bush|tree', - 'Changing dependant data on bindings with deferred correctly triggers refreshTag and refreshes content with updated data binding'); + swapPerson(); + getResult("Person"); - // ................................ Act .................................. - $.view("#result tr").refresh(); - result = $("#result").text(); - $.view("#result tr").parent.views[1].refresh(); - result += "|" + $("#result").text(); + changeStreet(); + getResult("Street"); - // ............................... Assert ................................. - equal(result, 'tree|tree', - 'view refresh with deferred correctly refreshes content'); + changeAddress(); + getResult("Address"); - // ................................ Act .................................. - $.observable(model.things[1]).setProperty("expanded", true); - result = $("#result").text(); - $.observable(model.things[0]).setProperty("expanded", false); - result += "|" + $("#result").text(); + changeHome(); + getResult("Home"); - // ............................... Assert ................................. - equal(result, 'treebush|bush', - 'Changing dependant data on bindings with deferred, after view refresh correctly triggers refreshTag and refreshes content with updated data binding'); - // ----------------------------------------------------------------------- + changeOb(); + getResult("Ob"); - // ................................ Reset ................................ - $("#result").empty(); - model.things = []; // reset Prop + changeAlt(); + getResult("Alt"); - // =============================== Arrange =============================== + // ............................... Assert ................................. + equal(res, "None: A |Street: A> |Address: A>+ |Home: A>+$ |Ob: A>+$0 |Alt: B |Home: B$ |Address: B$+ |Street: B$+> |Person: B$+> |Street: B$+> |Address: B$+> |Home: B$+> |Ob: B$+> |Alt: xA |", + "Deep path with chained observables, binding to full depth - 1: person.ob()^home.address().street"); - $.templates("
      {{for}}
    • Name: {{:firstName()}}. Width: {{:~settings.width}}
    • {{/for}}
    ") - .link("#result", person1, {settings: settings}); + // ................................ Reset ................................ + $("#result").empty(); + setData(); - // ................................ Act .................................. - before = $("#result ul li").html(); // The innerHTML will be Name: Sir compFirst. Width: 40 - person1.fullName.set.call(person1, "compFirst compLast"); - settings.title = "Sir"; - settings.width = 40; - $.view("li").refresh(); - after = $("#result ul li").html(); + // =============================== Arrange =============================== + tmpl = $.templates(""); - // ............................... Assert ................................. - equal(before + "|" + after, - 'Name: Mr Jo. Width: 30|Name: Sir compFirst. Width: 40', - 'Calling view("li").refresh() for a view in element-only content (elCnt true) updates correctly: "
      {{for}}
    • ...
    • {{/for}}
    "'); - // ----------------------------------------------------------------------- + tmpl.link("#result", data, {helpers: helpers}); - // ................................ Reset ................................ - $("#result").empty(); - person1._firstName = "Jo"; // reset Prop - person1.lastName = "One"; // reset Prop - settings.title = "Mr"; // reset Prop - settings.width = 30; // reset Prop + // ................................ Act .................................. - // =============================== Arrange =============================== + getResult("None"); - var data = {}; + changeStreet(); + getResult("Street"); - $.templates("
      {^{for items}}
    • insertBefore
    • {{/for}}
    • next
    ") - .link("#result", data); + changeAddress(); + getResult("Address"); - // ................................ Act .................................. - before = $("#result ul").text(); // The innerHTML will be Name: Sir compFirst. Width: 40 - $.observable(data).setProperty("items", []); - var deferredString = $("#result ul li")[0]._df || ""; - $.observable(data.items).insert("X"); - after = $("#result ul").text(); + changeHome(); + getResult("Home"); - // ............................... Assert ................................. - equal(before + "|" + deferredString + "|" + after, - (isIE8 ? 'next||insertBeforenext' - : ' next||insertBefore next'), - 'Inserting content before a next sibling element in element-only context does not set ._df, and subsequent insertion is correctly placed before the next sibling.'); - // ----------------------------------------------------------------------- + changeOb(); + getResult("Ob"); - // ................................ Reset ................................ - $("#result").empty(); + changeStreet(); + getResult("Street"); - // =============================== Arrange =============================== + changeAddress(); + getResult("Address"); - $.templates('{^{for things}}
    #index: #view.index: {{:thing}} Nested:{{for true}}{{for true}} #get(\'item\').index: #parent.parent.index:|{{/for}}{{/for}}
    {{/for}}') - .link("#result", model); + changeHome(); + getResult("Home"); - // ................................ Act .................................. - $.observable(model.things).insert(0, {thing: "tree"}); - $.observable(model.things).insert(0, {thing: "bush"}); + changeAlt(); + getResult("Alt"); - // ............................... Assert ................................. - equal($("#result").text(), "#index:0 #view.index:0 bush Nested: #get('item').index:0 #parent.parent.index:0|#index:1 #view.index:1 tree Nested: #get('item').index:1 #parent.parent.index:1|", - 'Data-link to "#index" and "#get(\'item\').index" work correctly'); - // ----------------------------------------------------------------------- + // ............................... Assert ................................. + equal(res, "None: A |Street: A> |Address: A>+ |Home: A>+$ |Ob: A>+$ |Street: A>+$ |Address: A>+$ |Home: A>+$ |Alt: A>+$ |", + "Deep path with chained observables, binding to leaf depth + 2: person.ob().home^address().street"); - // ................................ Reset ................................ - $("#result").empty(); - model.things = []; // reset Prop + // ................................ Reset ................................ + $("#result").empty(); + setData(); - // =============================== Arrange =============================== - $.templates('
      {^{for things}}xxx{{/for}}
    ') - .link("#result", model); + // =============================== Arrange =============================== + tmpl = $.templates(""); - // ................................ Act .................................. - $("#result div").empty(); + tmpl.link("#result", data, {helpers: helpers}); - // ............................... Assert ................................. + // ................................ Act .................................. - ok(viewsAndBindings().split(" ").length === 3 // We removed view inside div, but still have the view for the outer template. - && !$._data(model.things).events, - '$(container).empty removes listeners for empty tags in element-only content (_df="#n_/n_")'); - // ----------------------------------------------------------------------- + getResult("None"); - // =============================== Arrange =============================== - data = { - list: [], - q: true - }; + changeStreet(); + getResult("Street"); - $.templates('
      {^{if q}}{^{for list}}
    • {{:#data}}
    • {{/for}}{{/if}}
    ') - .link("#result", data); + changeAddress(); + getResult("Address"); - // ................................ Act .................................. - $.observable(data).setProperty("q", false); - $.observable(data).setProperty("q", true); - $.observable(data.list).insert("added"); + changeHome(); + getResult("Home"); - // ............................... Assert ................................. - ok(viewsAndBindings().split(" ").length === 9 // We removed view inside div, but still have the view for the outer template. - && $._data(data.list).events.arrayChange.length === 1 - && $("#result ul").text() === "added", - 'In element-only content, updateContent calls disposeTokens on _df inner bindings'); + changeStreet(); + getResult("Street"); - // ................................ Reset ................................ - $("#result").empty(); - // ----------------------------------------------------------------------- -}); + changeAddress(); + getResult("Address"); -test("{^{if}}...{{else}}...{{/if}}", function() { + changeAlt(); + getResult("Alt"); - // =============================== Arrange =============================== - var data = {one: true, two: false, three: true}, - boundIfElseTmpl = $.templates( - '{^{if one pane=0}}' - + '{^{if two pane=0}}' - + '{^{if three pane=0}}ONE TWO THREE {{else}}ONE TWO notThree {{/if}}' - + '{{else}}ONE notTwo {^{if three}}THREE {{/if}}{^{if !three}}notThree {{/if}}{{/if}}' - + '{{else three pane=1}}' - + '{^{if two pane=0}}notOne TWO THREE{{else}}notOne notTwo THREE {{/if}}' - + '{{else}}' - + '{^{if two pane=0}}notOne TWO notThree {{else}}notOne TWO notThree {{/if}}' - + '{{/if}}'); + // ............................... Assert ................................. + equal(res, "None: A |Street: A> |Address: A>+ |Home: A>+ |Street: A>+ |Address: A>+ |Alt: A>+ |", + "Deep path with chained observables, binding to leaf depth plus 1: person.ob().home.address()^street"); - // ................................ Act .................................. - boundIfElseTmpl.link("#result", data); + // ................................ Reset ................................ + $("#result").empty(); + setData(); - // ............................... Assert ................................. - after = $("#result").text(); - equal(after, boundIfElseTmpl.render(data), - 'Bound if and else with link render the same as unbound, when using the JsRender render() method'); + // =============================== Arrange =============================== + tmpl = $.templates(""); - // ............................... Assert ................................. - equal(after, "ONE notTwo THREE ", - 'Bound if and else render correct blocks based on boolean expressions'); + tmpl.link("#result", data, {helpers: helpers}); - // ................................ Act .................................. - $.observable(data).setProperty({one: false, two: false, three: true}); - after = $("#result").text(); + // ................................ Act .................................. - // ............................... Assert ................................. - equal(after, isIE8 ? "notOne notTwo THREE " : "notOne notTwo THREE ", - 'Bound if and else render correct blocks based on boolean expressions'); + getResult("None"); - // ................................ Act .................................. - $.observable(data).setProperty({one: false, two: true, three: false}); - after = $("#result").text(); + changeStreet(); + getResult("Street"); - // ............................... Assert ................................. - equal(after, isIE8 ? "notOne TWO notThree " : "notOne TWO notThree ", - 'Bound if and else render correct blocks based on boolean expressions'); + changeAddress(); + getResult("Address"); - // ................................ Reset ................................ - $("#result").empty(); + changeStreet(); + getResult("Street"); - // =============================== Arrange =============================== - data = {expanded: true}; - var deepIfTmpl = $.templates( - '' - + '{^{if expanded}}' - + '' - + '{{/if}}' - + '' - + '
    DeepContent
    afterDeep
    '); + changeAlt(); + getResult("Alt"); - // ................................ Act .................................. - deepIfTmpl.link("#result", data); + // ............................... Assert ................................. + equal(res, "None: A |Street: A> |Address: A> |Street: A> |Alt: A> |", + "Deep path with chained observables, binding to leaf only: person.ob().home.address().street"); - $.observable(data).setProperty("expanded", false); - $.observable(data).setProperty("expanded", true); + })(); - // ............................... Assert ................................. - after = $("#result").text(); - var deferredString = $("#result tr")[0]._df; // "/226_/322^" - // With deep version, the tokens for the {^{if}} binding had to be deferred - we test the format: - deferredString = /\/\d+\_\/\d+\^/.test(deferredString); - equal(deferredString && after, 'DeepContentafterDeep', - 'With deep bound {^{if}} tag, there is deferred binding and binding behaves correctly after removing and inserting'); + (function() { + // =============================== Arrange =============================== + function ob() { + return this._ob; + } - // ................................ Act .................................. - $.observable(data).setProperty("expanded", false); + ob.set = function(val) { + this._ob = val; + } - // ............................... Assert ................................. - after = $("#result").text(); - deferredString = $("#result tr")[0]._df; // "#322^/322^" - // With deep version, the tokens for the {^{if}} binding had to be deferred - we test the format: - deferredString = /#(\d+\^)\/\1/.test(deferredString); + var res, resCount, helpers, ch, person, person2, data; - equal(deferredString && after, 'afterDeep', - 'With deep bound {^{if}} tag, there is deferred binding and binding behaves correctly after further remove'); + function setData() { + res = ""; + resCount = 1; + ch = 0; - // =============================== Arrange =============================== - var shallowIfTmpl = $.templates( - '' - + '{^{if expanded}}' - + '' - + '{{/if}}' - + '' - + '
    ShallowContent
    afterShallow
    '); + person = { + ob: { + street: "A", + type: "T" + } + }; + person2 = { + ob: { + street: "A2", + type: "T2" + } + }; + data = {person: person}; + } - // ................................ Act .................................. - shallowIfTmpl.link("#result", data); + function swapPerson() { + $.observable(data).setProperty("person", data.person === person ? person2 : person); + } - $.observable(data).setProperty("expanded", false); - $.observable(data).setProperty("expanded", true); + function changeStreet() { + $.observable(data.person.ob).setProperty("street", data.person.ob.street + ">"); + } - // ............................... Assert ................................. - after = $("#result").text(); - deferredString = $("#result tr")[0]._df; // "" - // With shallow version, no deferred binding - equal(!deferredString && after, 'ShallowContentafterShallow', - 'With shallow bound {^{if}} tag, there is no deferred binding, and binding behaves correctly after removing and inserting'); + function changeType() { + $.observable(data.person.ob).setProperty("type", data.person.ob.type + "$"); + } - // ................................ Act .................................. - $.observable(data).setProperty("expanded", false); + function getResult(name) { + res += (name||resCount++) + ": " + $("#result").text() + " |"; + } - // ............................... Assert ................................. - after = $("#result").text(); - deferredString = $("#result tr")[0]._df; // "" - // With shallow version, no deferred binding + var tmpl = $.templates(""); - equal(!deferredString && after, 'afterShallow', - 'With shallow bound {^{if}} tag, there is no deferred binding and binding behaves correctly after further remove'); + setData(); -}); + tmpl.link("#result", data); -test("{^{props}} basic", function() { - // =============================== Arrange =============================== - var root = { - objA: {propA1: "valA1a"}, - objB: {propB1: "valB1a"} - }; + // ................................ Act .................................. + getResult("None"); - $.templates('{^{props objA}}{^{:key}}:{^{:prop}},{{/props}}') - .link("#result", root); + changeStreet(); + getResult("Street"); - // ................................ Act .................................. - before = $("#result").text(); - $.observable(root.objA).setProperty({propA1: "valA1b"}); - after = $("#result").text(); + changeType(); + getResult("Type"); - // ............................... Assert ................................. - equal(before + "|" + after, 'propA1:valA1a,|propA1:valA1b,', - '{^{props}} - set existing property'); + swapPerson(); + getResult("Person"); - // ................................ Act .................................. - before = $("#result").text(); - $.observable(root.objA).setProperty({propA1: "valA1c",propA2: "valA2a"}); - after = $("#result").text(); + changeType(); + getResult("Type"); - // ............................... Assert ................................. - equal(before + "|" + after, 'propA1:valA1b,|propA1:valA1c,propA2:valA2a,', - '{^{props}} - set new property'); + changeStreet(); + getResult("Street"); - // ................................ Act .................................. - before = $("#result").text(); - $.observable(root.objA).setProperty({propA1: "",propA2: null}); - after = $("#result").text(); - // ............................... Assert ................................. - equal(before + "|" + after, 'propA1:valA1c,propA2:valA2a,|propA1:,propA2:,', - '{^{props}} - set property to empty string or null'); + // ............................... Assert ................................. + equal(res, "None: TA |Street: TA> |Type: T$A> |Person: T2A2 |Type: T2$A2 |Street: T2$A2> |", + "Adjacent deep paths in expression: person^ob.type + person^ob.street"); - // ................................ Act .................................. - before = $("#result").text(); - $.observable(root.objA).setProperty({propA1: null}); - after = $("#result").text(); + // ................................ Reset ................................ + $("#result").empty(); - // ............................... Assert ................................. - equal(before + "|" + after, 'propA1:,propA2:,|propA1:,propA2:,', - '{^{props}} - all properties null'); + })(); - // ................................ Act .................................. - before = $("#result").text(); - $.observable(root.objA).removeProperty("propA1").removeProperty("propA2"); - after = $("#result").text(); + (function() { + // =============================== Arrange =============================== + function ob() { + return this._ob; + } - // ............................... Assert ................................. - equal(before + "|" + after, 'propA1:,propA2:,|', - '{^{props}} - all properties removed'); + ob.set = function(val) { + this._ob = val; + } - // ................................ Act .................................. - before = $("#result").text(); - $.observable(root.objA).setProperty({propA1: "valA1b"}); - after = $("#result").text(); + var res, resCount, helpers, ch, person, person2, data; - // ............................... Assert ................................. - equal(before + "|" + after, "|propA1:valA1b,", - '{^{props}} - set property where there were none'); + function setData() { + res = ""; + resCount = 1; + ch = 0; - // ................................ Act .................................. - before = $("#result").text(); - $.observable(root).setProperty({ objA: {}}); - after = $("#result").text(); + person = { + ob: ob, + _ob: { + street: "A", + type: "T" + } + }; + person2 = { + ob: ob, + _ob: { + street: "A2", + type: "T2" + } + }; + data = {person: person}; + } - // ............................... Assert ................................. - equal(before + "|" + after, "propA1:valA1b,|", - '{^{props}} - set whole object to empty object'); + function swapPerson() { + $.observable(data).setProperty("person", data.person === person ? person2 : person); + } - // ................................ Act .................................. - before = $("#result").text(); - $.observable(root).setProperty({ objA: {propX: "XX"}}); - after = $("#result").text(); + function changeOb() { + $.observable(data.person).setProperty("ob", { + street: data.person._ob.street + "+", + type: data.person._ob.type + "+" + }); + } - // ............................... Assert ................................. - equal(before + "|" + after, "|propX:XX,", - '{^{props}} - set whole object to different object'); + function changeStreet() { + $.observable(data.person.ob()).setProperty("street", data.person.ob().street + ">"); + } - //................................ Reset ................................ - $("#result").empty(); + function changeType() { + $.observable(data.person.ob()).setProperty("type", data.person.ob().type + "$"); + } - // ............................... Assert ................................. - equal(JSON.stringify($.views.sub._cbBnds), "{}", - "{^{props}} dataMap bindings all removed when tag disposed (content removed from DOM)"); -}); + function getResult(name) { + res += (name||resCount++) + ": " + $("#result").text() + " |"; + } -test("{^{props}} modifying content, through arrayChange/propertyChange on target array", function() { - // =============================== Arrange =============================== + var tmpl = $.templates(""); - var root = { - objA: { propA1: "valA1a" } - }; + setData(); - $.templates( - '{^{props objA}}' - + '{^{:key}}:{^{:prop}},' - + '' - + ',' - + ',' - + '' - + '' - + '{{/props}}') + tmpl.link("#result", data); - .link("#result", root, { - add: function(ev, eventArgs) { - var view = eventArgs.view, - arr = view.get("array").data; - $.observable(arr).insert({key: "addkey", prop: "addprop"}); - }, - remove: function(ev, eventArgs) { - var view = eventArgs.view, - arr = view.get("array").data, - index = view.index; - $.observable(arr).remove(index); - }, - change: function(ev, eventArgs) { - var view = eventArgs.view, - item = view.data; - $.observable(item).setProperty({ key: "changed", prop: "changedValue" }); - } - }); + // ................................ Act .................................. + getResult("None"); - // ................................ Act .................................. - before = $("#result").text(); - $(".addProp").click(); - after = $("#result").text(); + changeStreet(); + getResult("Street"); - // ............................... Assert ................................. - equal(before + "|" + after, "propA1:valA1a,removeadd,change,|propA1:valA1a,removeadd,change,addkey:addprop,removeadd,change,", - '{^{props}} - add properties to props target array'); + changeType(); + getResult("Type"); - // ................................ Act .................................. - before = $("#result").text(); - $(".removeProp:first()").click(); - after = $("#result").text(); + changeOb(); + getResult("Ob"); - // ............................... Assert ................................. - equal(before + "|" + after, "propA1:valA1a,removeadd,change,addkey:addprop,removeadd,change,|addkey:addprop,removeadd,change,", - '{^{props}} - remove properties from props target array'); + changeType(); + getResult("Type"); - // ................................ Act .................................. - before = $("#result").text(); - $(".changeProp").click(); - after = $("#result").text(); + changeStreet(); + getResult("Street"); - // ............................... Assert ................................. - equal(before + "|" + after, "addkey:addprop,removeadd,change,|changed:changedValue,removeadd,change,", - '{^{props}} - change value of key and prop in props target array'); - // ................................ Act .................................. - before = $("#result").text(); - $(".changePropInput").val("newValue").change(); - after = $("#result").text(); + // ............................... Assert ................................. + equal(res, "None: AT |Street: A>T |Type: A>T$ |Ob: A>+T$+ |Type: A>+T$+$ |Street: A>+>T$+$ |", + "Adjacent terms in expression with paths with observables: person^ob().street + person^ob().type"); - // ............................... Assert ................................. - equal(before + "|" + after + "|" + JSON.stringify(root.objA), "changed:changedValue,removeadd,change,|changed:newValue,removeadd,change,|{\"changed\":\"newValue\"}", - '{^{props}} - change value of input bound to prop in props target array'); + // ................................ Reset ................................ + $("#result").empty(); - // ................................ Act .................................. - before = $("#result").text(); - $(".changeKeyInput").val("newKey").change(); - after = $("#result").text(); + })(); - // ............................... Assert ................................. - equal(before + "|" + after + "|" + JSON.stringify(root.objA), "changed:newValue,removeadd,change,|newKey:newValue,removeadd,change,|{\"newKey\":\"newValue\"}", - '{^{props}} - change value of input bound to key in props target array'); + (function() { + // =============================== Arrange =============================== + function ob(alt, a, b) { + var ob = alt ? this._obB : this._obA; + if (a) { + ob.t = [a,b,a+b+2]; + } + return ob; + } - // ................................ Reset ................................ + ob.set = function(val) { + if (helpers.alt) { + this._obB = val; + } else { + this._obA = val; + } + } - before = "" + $._data(root).events.propertyChange.length + "-" + $._data(root.objA).events.propertyChange.length; - $("#result").empty(); - after = "" + ($._data(root).events === undefined) + "-" + ($._data(root.objA).events === undefined) + " -" + JSON.stringify($.views.sub._cbBnds); + function address(c, d) { + if (c) { + this._address.t = c + d; + } + return this._address; + } - // ............................... Assert ................................. - equal(before + "|" + after, "1-1|true-true -{}", - '{^{props}} dataMap bindings all removed when tag disposed (content removed from DOM)'); -}); + function setAddress(val) { + this._address = val; + } -test("{^{props}}..{{else}} ...", function() { - // =============================== Arrange =============================== + address.set = setAddress; + + var res, resCount, helpers, ch, person, person2, data; + + function setData() { + res = ""; + resCount = 1; + ch = 0; + + helpers = { + index: 1, + a: 2, + b: 1, + c: 3, + d: 4, + alt: false + }; + + person = { + ob: ob, + _obA: { + home: { + _address: { + street: "A" + }, + _address2: { + street: "A2" + }, + address: address + } + }, + _obB: { + home: { + _address: { + street: "B" + }, + _address2: { + street: "B2" + }, + address: address + } + } + }; + person2 = { + ob: ob, + _obA: { + home: { + _address: { + street: "xA" + }, + _address2: { + street: "xA2" + }, + address: address + } + }, + _obB: { + home: { + _address: { + street: "xB" + }, + _address2: { + street: "xB2" + }, + address: address + } + } + }; + data = {person: person}; + } - var root = { - objA: { propA1: "valA1" }, - objB: { propB1: "valb1", propB2: "valb2" } - }; + function swapPerson() { + $.observable(data).setProperty("person", data.person === person ? person2 : person); + } - $.templates('{^{props objA}}{^{:key}}:{^{:prop}},' - + ',' - + '{{else objB}}{^{:key}}:{^{:prop}},' - + ',' - + '{{else}}' - + 'NONE' - + '{{/props}}') + function changeAlt() { + $.observable(helpers).setProperty("alt", !helpers.alt); + } - .link("#result", root, { - remove: function(ev, eventArgs) { - var view = eventArgs.view, - arr = view.get("array").data, - index = view.index; - $.observable(arr).remove(index); - } - }); + function changeA() { + $.observable(helpers).setProperty("a", helpers.a + 1); + } - // ................................ Act .................................. - before = $("#result").text(); - $(".removePropA").click(); - after = $("#result").text(); + function changeD() { + $.observable(helpers).setProperty("d", helpers.d + 1); + } - // ............................... Assert ................................. - equal(before + "|" + after, "propA1:valA1,remove,|propB1:valb1,remove,propB2:valb2,remove,", - '{^{props}} - remove properties from objA target array - switches to {{else objB}}'); + function changeIndex(val) { + $.observable(helpers).setProperty("index", val); + } - // ................................ Act .................................. - before = $("#result").text(); - $(".removePropB").click(); - after = $("#result").text(); + function changeOb() { + $.observable(data.person).setProperty("ob", { + home: { + _address: { + street: data.person.ob(helpers.alt).home._address.street + ch++ + }, + _address2: { + street: data.person.ob(helpers.alt).home._address2.street + ch + }, + address: address + } + }); + } - // ............................... Assert ................................. - equal(before + "|" + after, "propB1:valb1,remove,propB2:valb2,remove,|NONE", - '{^{props}} - remove properties from objB target array - switches to {{else}}'); + function changeHome() { + $.observable(data.person.ob(helpers.alt)).setProperty("home", { + _address: { + street: data.person.ob(helpers.alt).home._address.street + "$" + }, + _address2: { + street: data.person.ob(helpers.alt).home._address2.street + "$" + }, + address: address + }); + } - // ................................ Reset ................................ - $("#result").empty(); + function changeAddress() { + $.observable(data.person.ob(helpers.alt).home).setProperty("address", {street: data.person.ob(helpers.alt).home.address().street + "+"}); + } - // ............................... Assert ................................. - equal(JSON.stringify($.views.sub._cbBnds), "{}", - "{^{props}} dataMap bindings all removed when tag disposed (content removed from DOM)"); -}); + function changeStreet() { + $.observable(data.person.ob(helpers.alt).home.address()).setProperty("street", data.person.ob(helpers.alt).home.address().street + ">"); + } -test('data-link="{on ...', function() { + function getResult(name) { + res += (name||resCount++) + ": " + $("#result").text() + " |"; + } - // =============================== Arrange =============================== + var tmpl = $.templates(""); - function swap(ev, eventArgs) { - $.observable(this).setProperty("type", this.type === "shape" ? "line" : "shape"); - } - var thing = { - type: "shape", - swap: swap - }; + setData(); - $.templates('
    {^{:type}}
    ') - .link("#result", thing); + tmpl.link("#result", data, {helpers: helpers}); - // ................................ Act .................................. - before = $("#result").text(); - $("#result div").click(); - after = $("#result").text(); + // ................................ Act .................................. + getResult("None"); - // ............................... Assert ................................. - equal(before + "|" + after, - "shape|line", - '{on swap} calls swap method on click, with "this" pointer context on data object'); - // ----------------------------------------------------------------------- + changeStreet(); + getResult("Street"); - // ................................ Reset ................................ - $("#result").empty(); - thing.type = "shape"; + changeAddress(); + getResult("Address"); - // =============================== Arrange =============================== + changeHome(); + getResult("Home"); - $.templates('
    {^{:type}}
    ') - .link("#result", thing, {swap: swap}); + changeD(); + getResult("D"); - // ................................ Act .................................. - before = $("#result").text(); - $("#result div").click(); - after = $("#result").text(); + changeIndex(3); + getResult("Index3"); - // ............................... Assert ................................. - equal(before + "|" + after, - "shape|line", - '{on ~swap} calls swap helper method on click, with "this" pointer context defaulting to current data object'); - // ----------------------------------------------------------------------- + changeA(); + getResult("A"); - // ................................ Reset ................................ - $("#result").empty(); - thing.type = "shape"; + changeIndex(2); + getResult("Index2"); - // =============================== Arrange =============================== + changeOb(); + getResult("Ob"); - $.templates('
    {^{:type}} {^{:check}}
    ') - .link("#result", thing, {util: - { - swap: function(ev, eventArgs) { - $.observable(this.data).setProperty({ - type: this.data.type === "shape" ? "line" : "shape", - check: this.data === eventArgs.view.data - }); - }, - data: thing - }}); + changeAlt(); + getResult("Alt"); - // ................................ Act .................................. - before = $("#result").text(); - $("#result div").click(); - after = $("#result").text(); + changeHome(); + getResult("Home"); - // ............................... Assert ................................. - equal(before + "|" + after, - isIE8 ? "shape |linetrue " - : "shape |line true", - '{on ~util.swap} calls util.swap helper method on click, with ~util as this pointer'); - // ----------------------------------------------------------------------- + changeAddress(); + getResult("Address"); - // ................................ Reset ................................ - $("#result").empty(); - thing.type = "shape"; - delete thing.check; + changeStreet(); + getResult("Street"); - // =============================== Arrange =============================== + swapPerson(); + getResult("Person"); - $.templates('
    {^{:type}}
    ') - .link("#result", thing, {util: - { - swap: swap - }}); + changeHome(); + getResult("Home"); - // ................................ Act .................................. - before = $("#result").text(); - $("#result div").click(); - after = $("#result").text(); + changeStreet(); + getResult("Street"); - // ............................... Assert ................................. - equal(before + "|" + after, - "shape|line", - '{on ~util.swap context=#data} calls util.swap helper method on click, with current data object as this pointer'); - // ----------------------------------------------------------------------- + changeAddress(); + getResult("Address"); - // ................................ Reset ................................ - $("#result").empty(); - thing.type = "shape"; + changeOb(); + getResult("Ob"); - // =============================== Arrange =============================== + changeHome(); + getResult("Home"); - $.templates('
    {^{:type}} {^{:check}}
    ') - .link("#result", thing, {util: - { - swap: function(ev, eventArgs) { - $.observable(this.data).setProperty({ - type: this.data.type === "shape" ? "line" : "shape", - check: this.data === eventArgs.view.data - }); - }, - data: thing, - swapCtx: { - data: thing - } - }}); + changeIndex(1); + getResult("Index1"); - // ................................ Act .................................. - before = $("#result").text(); - $("#result div").click(); - after = $("#result").text(); + changeA(); + getResult("A"); - // ............................... Assert ................................. - equal(before + "|" + after, - isIE8 ? "shape |linetrue " - : "shape |line true", - '{on ~util.swap context=~util.swapCtx} calls util.swap helper method on click, with util.swapCtx as this pointer'); - // ----------------------------------------------------------------------- + changeD(); + getResult("D"); - // ................................ Reset ................................ - $("#result").empty(); - thing.type = "shape"; - delete thing.check; + changeStreet(); + getResult("Street"); - // =============================== Arrange =============================== + changeAddress(); + getResult("Address"); - $.templates('
    {^{:type}} {^{:check}}
    ') - .link("#result", thing, {util: - { - swap: function(ev, eventArgs) { - $.observable(ev.data).setProperty({ - type: ev.data.type === "shape" ? "line" : "shape", - check: ev.data === eventArgs.view.data - }); - }, - data: thing, - swapCtx: { - data: thing + // ............................... Assert ................................. + equal(res, "None: 46A |Street: 46A> |Address: 46A>+ |Home: 46A>+$ |D: 52A>+$ |Index3: 58A>+$ |A: 60A>+$ |Index2: 50A>+$ |Ob: 50A>+$0 |Alt: 50B |Home: 50B$ |Address: 50B$+ |Street: 50B$+> |Person: 50xB |Home: 50xB$ |Street: 50xB$> |Address: 50xB$>+ |Ob: 50xB$>+1 |Home: 50xB$>+1$ |Index1: 54xB$>+1$ |A: 56xB$>+1$ |D: 62xB$>+1$ |Street: 62xB$>+1$> |Address: 62xB$>+1$>+ |", + "Complex expression with multiple adjacent paths, with nested () and [] paren expressions, chained observables, arithmetic expressions etc."); + + // ................................ Reset ................................ + $("#result").empty(); + + })(); + + (function() { + // =============================== Arrange =============================== + function getResult(name) { + res += (name||resCount++) + ": " + $("#result").text() + " |"; + } + + var res = "", + data = { + type: 't', + sectionTypes: { + t: { + types: [22, 33] + }, + n: { + types: [66, 77] + } + } + }, + helpers = { + mode: "m" + }, + tmpl = $.templates({ + markup: "{^{section 'A' ~mode ~sectionTypes=~root.sectionTypes[type].types/}}", + tags: { + section: function(setting, mode) { + return setting + mode + this.tagCtx.ctx.sectionTypes[0]; + } } - }}); + }); - // ................................ Act .................................. - before = $("#result").text(); - $("#result div").click(); - after = $("#result").text(); + // ................................ Act .................................. + tmpl.link("#result", data, helpers); - // ............................... Assert ................................. - equal(before + "|" + after, - isIE8 ? "shape |linetrue " - : "shape |line true", - '{on ~util.swap data=#data} calls util.swap helper method on click, and passes current data #data as ev.data'); - // ----------------------------------------------------------------------- + getResult("None"); - // ................................ Reset ................................ - $("#result").empty(); - thing.type = "shape"; - delete thing.check; + $.observable(data).setProperty("type", "n") - // =============================== Arrange =============================== + getResult("type"); - $.templates('
    {^{:type}}
    ') - .link("#result", thing); + // ............................... Assert ................................. + equal(res, "None: Am22 |type: Am22 |", + "Complex unbound expression with [] paren expressions etc."); - // ................................ Act .................................. - before = $("#result").text(); - $("#result div").mouseup(); - after = $("#result").text(); - $("#result div").mousedown(); - after += $("#result").text(); - $("#result div").blur(); - after += $("#result").text(); + // ................................ Reset ................................ + $("#result").empty(); - // ............................... Assert ................................. - equal(before + "|" + after, - "shape|lineshapeline", - "{on 'mouseup mousedown blur' swap} calls util method on mouseup, mousedown and blur"); - // ----------------------------------------------------------------------- + })(); - // ................................ Reset ................................ - $("#result").empty(); - thing.type = "shape"; - delete thing.check; + (function() { + // =============================== Arrange =============================== + function getResult(name) { + res += (name||resCount++) + ": " + $("#result").text() + " |"; + } - // =============================== Arrange =============================== - var res = "1: "; - $.templates( - "
    " - + "" - + "" - + "
    ") - .link("#result", { - unbind: function(ev, eventArgs) { - res += "unbind "; - eventArgs.linkCtx.tag.onDispose(); + + var res = "", + data = { + type: 't', + sectionTypes: { + t: { + types: [22, 33] + }, + n: { + types: [66, 77] + } + } }, - refresh: function(ev, eventArgs) { - res += "refresh "; - eventArgs.linkCtx.tag.refresh(); + helpers = { + mode: "m" }, - test: function() { - res += "test "; - } - }); - // ................................ Act .................................. - var events = $._data($("#divForOn")[0]).events, - eventBindings = "before: " + events.keydown.length + events.keyup.length + events.mouseup.length + events.mousedown.length; + tmpl = $.templates({ + markup: "{^{section 'A' ~mode ^~sectionTypes=~root.sectionTypes[type].types/}}", + tags: { + section: function(setting, mode) { + return setting + mode + this.tagCtx.ctx.sectionTypes[0]; + } + } + }); - $("#divForOn #inputB").mouseup(); + // ................................ Act .................................. + tmpl.link("#result", data, helpers); - res += "2: "; - $("#divForOn .inputA").mouseup(); + getResult("None"); - res += "3: "; - $("#divForOn #inputB").keyup(); + $.observable(data).setProperty("type", "n") - res += "4: "; - $("#divForOn #inputB").keyup(); + getResult("type"); - res += "5: "; - $("#divForOn .inputA").keydown(); + // ............................... Assert ................................. + equal(res, "None: Am22 |type: Am66 |", + "Complex bound expression with [] paren expressions etc."); - res += "6: "; - $("#divForOn .inputA").keyup(); + // ................................ Reset ................................ + $("#result").empty(); - res += "7: "; - $("#divForOn #inputB").mouseup(); + })(); - res += "8: "; - $("#divForOn #inputB").mousedown(); + (function() { + // =============================== Arrange =============================== + function ob(alt, a, b) { + var ob = alt ? this._obB : this._obA; + if (a) { + ob.t = [a,b,a+b+2]; + } + return ob; + } - eventBindings += " | after: " + events.keydown + events.keyup + events.mouseup.length + events.mousedown.length; - // ............................... Assert ................................. - equal(res, - "1: test 2: test 3: unbind 4: 5: unbind 6: 7: test 8: refresh ", - "multiple {on events selector method} bindings on container attach events on delegated elements. Also, tag.onDispose on tag instances removes specific handlers for corresponding elements/selectors"); + ob.set = function(val) { + if (helpers.alt) { + this._obB = val; + } else { + this._obA = val; + } + } - // ............................... Assert ................................. - equal(eventBindings, - "before: 1211 | after: undefinedundefined11", - "onDispose removes specific delegated events"); + function address(c, d) { + if (c) { + this._address.t = c + d; + } + return this._address; + } - // ................................ Act .................................. - res = "1: "; - $("#divForOn").html(""); + function setAddress(val) { + this._address = val; + } - $("#divForOn #newlyAdded").mouseup(); + address.set = setAddress; + + var res, resCount, helpers, ch, person, person2, data; + + function setData() { + res = ""; + resCount = 1; + ch = 0; + + helpers = { + index: 1, + a: 2, + b: 1, + c: 3, + d: 4, + alt: false + }; + + person = { + ob: ob, + _obA: { + home: { + _address: { + street: "A" + }, + _address2: { + street: "A2" + }, + address: address + } + }, + _obB: { + home: { + _address: { + street: "B" + }, + _address2: { + street: "B2" + }, + address: address + } + } + }; + person2 = { + ob: ob, + _obA: { + home: { + _address: { + street: "xA" + }, + _address2: { + street: "xA2" + }, + address: address + } + }, + _obB: { + home: { + _address: { + street: "xB" + }, + _address2: { + street: "xB2" + }, + address: address + } + } + }; + data = {person: person}; + } - res += "2: "; - $("#divForOn #newlyAdded").keyup(); + function swapPerson() { + $.observable(data).setProperty("person", data.person === person ? person2 : person); + } - // ............................... Assert ................................. - equal(res, - "1: test 2: ", - "delegated {on events selector method} binding allows additional elements added to content to bind correctly"); + function changeAlt() { + $.observable(helpers).setProperty("alt", !helpers.alt); + } - // ................................ Act .................................. - $("#result").empty(); - eventBindings = "" + events.keydown + events.keyup + events.mouseup + JSON.stringify([$.views.sub._cbBnds, _jsv.bindings]); + function changeA() { + $.observable(helpers).setProperty("a", helpers.a + 1); + } - // ............................... Assert ................................. - equal(eventBindings, - "undefinedundefinedundefined[{},{}]", - "Removing the element removes all associated attached {on } handlers"); + function changeD() { + $.observable(helpers).setProperty("d", helpers.d + 1); + } - // =============================== Arrange =============================== - var tmpl = $.templates("
    \ - \ - \ -
    "), + function changeIndex(val) { + $.observable(helpers).setProperty("index", val); + } - data = { - name: "Jo", - role: "Advisor", - option: { - allow: true - }, - thisIsTheMethod: function(role, text, isFoo, compile, amount, root, ev, eventArgs) { - if (compile) { - compile.call(root, role, text, isFoo, amount, ev.data.allow, eventArgs.linkCtx.tag.tagCtx.args[2]); + function changeOb() { + $.observable(data.person).setProperty("ob", { + home: { + _address: { + street: data.person.ob(helpers.alt).home._address.street + ch++ + }, + _address2: { + street: data.person.ob(helpers.alt).home._address2.street + ch + }, + address: address } - }, - process: function(role, text, isFoo, amount, allow, extraParam) { - $.observable(this).setProperty("res", this.res + role + text + isFoo + amount + " allow:" + allow + " extraParam: " + extraParam + "|"); - }, - res: "" - }; + }); + } - tmpl.link("#result", data); + function changeHome() { + $.observable(data.person.ob(helpers.alt)).setProperty("home", { + _address: { + street: data.person.ob(helpers.alt).home._address.street + "$" + }, + _address2: { + street: data.person.ob(helpers.alt).home._address2.street + "$" + }, + address: address + }); + } - // ................................ Act .................................. - $("#doIt").click(); - data.option.allow = false; - $.observable(data).setProperty("role", "Follower"); - $("#doIt").click(); + function changeAddress() { + $.observable(data.person.ob(helpers.alt).home).setProperty("address", {street: data.person.ob(helpers.alt).home.address().street + "+"}); + } - // ............................... Assert ................................. - equal(data.res, "Advisorheytrue33 allow:true extraParam: 754|Followerheytrue33 allow:false extraParam: 754|", - "{on 'click' selector otherParams... method params...} : supports passing params to method, of any type, as well as setting data and context for the function call"); + function changeStreet() { + $.observable(data.person.ob(helpers.alt).home.address()).setProperty("street", data.person.ob(helpers.alt).home.address().street + ">"); + } - // =============================== Arrange =============================== - res = "1: "; - $("#result").html("
    " - + "" - + "" - + "
    "); + function getResult(name) { + res += (name||resCount++) + ": " + $("#result").text() + " |"; + } - $.link(true, "#result", { - unbind: function(ev, eventArgs) { - res += "unbind "; - eventArgs.linkCtx.tag.onDispose(); - }, - refresh: function(ev, eventArgs) { - res += "refresh "; - eventArgs.linkCtx.tag.refresh(); - }, - test: function() { - res += "test "; - } - }); + var tmpl = $.templates({ + markup: "{^{mytag ^myprop=((person^ob(~helpers.alt).home.address(~helpers.c, ~helpers.d).t*3) + person^ob(~helpers.alt, ~helpers.a, ~helpers.b).t[(~helpers.index + 1) - 2])*2 + person^ob(~helpers.alt).home.address().street}}{{/mytag}}", + tags: { + mytag: function() { + return this.tagCtx.props.myprop; + } + } + }); - // ................................ Act .................................. - events = $._data($("#divForOn")[0]).events; - eventBindings = "before: " + events.keydown.length + events.keyup.length + events.mouseup.length + events.mousedown.length; + setData(); - $("#divForOn #inputB").mouseup(); + tmpl.link("#result", data, {helpers: helpers}); - res += "2: "; - $("#divForOn .inputA").mouseup(); + // ................................ Act .................................. + getResult("None"); - res += "3: "; - $("#divForOn #inputB").keyup(); + changeStreet(); + getResult("Street"); - res += "4: "; - $("#divForOn #inputB").keyup(); + changeAddress(); + getResult("Address"); - res += "5: "; - $("#divForOn .inputA").keydown(); + changeHome(); + getResult("Home"); - res += "6: "; - $("#divForOn .inputA").keyup(); + changeD(); + getResult("D"); - res += "7: "; - $("#divForOn #inputB").mouseup(); + changeIndex(3); + getResult("Index3"); - res += "8: "; - $("#divForOn #inputB").mousedown(); + changeA(); + getResult("A"); - eventBindings += " | after: " + events.keydown + events.keyup + events.mouseup.length + events.mousedown.length; - // ............................... Assert ................................. - equal(res, - "1: test 2: test 3: unbind 4: 5: unbind 6: 7: test 8: refresh ", - "Top-level {on }: multiple {on events selector method} top-level bindings on container attach events on delegated elements. Also, tag.onDispose on tag instances removes specific handlers for corresponding elements/selectors"); + changeIndex(2); + getResult("Index2"); - // ............................... Assert ................................. - equal(eventBindings, - "before: 1211 | after: undefinedundefined11", - "Top-level {on }: onDispose removes specific delegated events"); + changeOb(); + getResult("Ob"); - // ................................ Act .................................. - res = "1: "; - $("#divForOn").html(""); + changeAlt(); + getResult("Alt"); - $("#divForOn #newlyAdded").mouseup(); + changeHome(); + getResult("Home"); - res += "2: "; - $("#divForOn #newlyAdded").keyup(); + changeAddress(); + getResult("Address"); - // ............................... Assert ................................. - equal(res, - "1: test 2: ", - "Top-level {on }: delegated {on events selector method} binding allows additional elements added to content to bind correctly"); + changeStreet(); + getResult("Street"); - // ................................ Act .................................. - $("#result").empty(); - eventBindings = "" + events.keydown + events.keyup + events.mouseup + JSON.stringify([$.views.sub._cbBnds, _jsv.bindings]); + swapPerson(); + getResult("Person"); - // ............................... Assert ................................. - equal(eventBindings, - "undefinedundefinedundefined[{},{}]", - "Top-level {on }: Removing the element removes all associated attached {on } handlers"); + changeHome(); + getResult("Home"); - // =============================== Arrange =============================== - $("#result").html("
    \ - \ - \ -
    "); + changeStreet(); + getResult("Street"); - data = { - name: "Jo", - role: "Advisor", - option: { - allow: true - }, - thisIsTheMethod: function(role, text, isFoo, compile, amount, root, ev, eventArgs) { - if (compile) { - compile.call(root, role, text, isFoo, amount, ev.data.allow, eventArgs.linkCtx.tag.tagCtx.args[2]); - } - }, - process: function(role, text, isFoo, amount, allow, extraParam) { - $.observable(this).setProperty("res", this.res + role + text + isFoo + amount + " allow:" + allow + " extraParam: " + extraParam + "|"); - }, - res: "" - }; + changeAddress(); + getResult("Address"); - $.link(true, "#result", data); + changeOb(); + getResult("Ob"); - // ................................ Act .................................. - $("#doIt").click(); - data.option.allow = false; - $.observable(data).setProperty("role", "Follower"); - $("#doIt").click(); + changeHome(); + getResult("Home"); - // ............................... Assert ................................. - equal(data.res, "Advisorheytrue33 allow:true extraParam: 754|Followerheytrue33 allow:false extraParam: 754|", - "Top-level {on 'click' selector method params...} : supports passing params to method, of any type, as well as setting data and context for the function call"); + changeIndex(1); + getResult("Index1"); - // =============================== Arrange =============================== - res = "1: "; - data = { - unbind: function(ev, eventArgs) { - res += "unbind "; - eventArgs.linkCtx.tag.onDispose(); - }, - refresh: function(ev, eventArgs) { - res += "refresh "; - eventArgs.linkCtx.tag.refresh(); - }, - test: function() { - res += "test "; - } - }; - $("#result").html("
    oldcontent
    "); + changeA(); + getResult("A"); - $.link(true, "#linkTgt", data); + changeD(); + getResult("D"); - events = $._data($("#linkTgt")[0]).events, + changeStreet(); + getResult("Street"); - // ................................ Act .................................. - $("#linkTgt").mousedown(); + changeAddress(); + getResult("Address"); - res += "2: "; - $("#linkTgt").mouseup(); + // ............................... Assert ................................. + equal(res, "None: 46A |Street: 46A> |Address: 46A>+ |Home: 46A>+$ |D: 52A>+$ |Index3: 58A>+$ |A: 60A>+$ |Index2: 50A>+$ |Ob: 50A>+$0 |Alt: 50B |Home: 50B$ |Address: 50B$+ |Street: 50B$+> |Person: 50xB |Home: 50xB$ |Street: 50xB$> |Address: 50xB$>+ |Ob: 50xB$>+1 |Home: 50xB$>+1$ |Index1: 54xB$>+1$ |A: 56xB$>+1$ |D: 62xB$>+1$ |Street: 62xB$>+1$> |Address: 62xB$>+1$>+ |", + "Complex expression on bound tag property, with multiple adjacent paths, with nested () and [] paren expressions, chained observables, arithmetic expressions etc."); - res += "3: "; - $("#linkTgt").click(); + // ................................ Reset ................................ + $("#result").empty(); - res += "4: "; - $("#linkTgt").mousedown(); + })(); - res += "5: "; - $("#linkTgt").mouseup(); + (function() { + // =============================== Arrange =============================== + function setData() { + res = "", - res += "6: "; - $("#linkTgt").click(); + theB = { + a: "a" + }; - // ............................... Assert ................................. - equal(res, - "1: test 2: test 3: refresh 4: test 5: test 6: refresh ", - '$.link(true, "#linkTgt", data): top-level linking to element (not container) links correctly, including \'{on }\' bindings'); + theC = { + _b: theB, + b: function() { + return this._b; + } + }; - // ............................... Assert ................................. - eventBindings = "" + events.mouseup.length + events.mousedown.length + events.click.length; + theD = { + _c: theC, + c: function() { + return this._c; + } + }; - equal(eventBindings, - "111", - '$.link(true, "#linkTgt", data): top-level linking to element (not container) adds {on } binding handlers correctly - including calling refresh() on {on } tag'); + theE = { + _d: theD, + d: function() { + return this._d; + } + }; - // ................................ Act .................................. - $.unlink(true, "#linkTgt"); + theF = { + _e: theE, + e: function() { + return this._e; + } + }; + + theC.b.set = function(val) { + this._b = val; + }; + + theD.c.set = function(val) { + this._c = val; + }; + + theE.d.set = function(val) { + this._d = val; + }; + + theF.e.set = function(val) { + this._e = val; + }; + + data = { + f: theF, + e: theE, + d: theD, + c: theC, + b: theB + } + } - // ............................... Assert ................................. - eventBindings = "" + events.mouseup + events.mousedown + events.click + JSON.stringify([$.views.sub._cbBnds, _jsv.bindings]); + function getResult(name) { + res += (name||resCount++) + ": " + $("#result").text() + " |"; + } - equal(eventBindings, - "undefinedundefinedundefined[{},{}]", - '$.unlink(true, "#linkTgt"): directly on top-level data-linked element (not through container) removes all \'{on }\' handlers'); -}); + var theB, theC, theD, theE, theF, data, res, + tmpl = $.templates('{^{:f.e().d().c().b().a /}} {^{:f.e().d().c().b()^a /}} {^{:f.e().d().c()^b().a /}} {^{:f.e().d()^c().b().a /}} {^{:f.e()^d().c().b().a /}} {^{:f^e().d().c().b().a /}}'); -test('data-link="{tag...} and {^{tag}} in same template"', function() { + setData(); - // =============================== Arrange =============================== + tmpl.link("#result", data); - $.templates('{^{tmplTag/}}-{^{:lastName}} -') - .link("#result", person1); + getResult("None"); - // ................................ Act .................................. - before = $("#result").text() + $("#result input").val(); - $.observable(person1).setProperty({firstName: "newFirst", lastName: "newLast"}); - $.observable(settings).setProperty({ title: "Sir", width: 40}); - after = $("#result").text() + $("#result input").val(); + // ................................ Act .................................. + $.observable(theB).setProperty("a", "A"); - // ............................... Assert ................................. - equal(before + "|" + after, - 'Name: Mr Jo. Width: 30-One Name: Mr Jo. Width: 30-OneOne|Name: Sir newFirst. Width: 40-newLast Name: Sir newFirst. Width: 40-newLastnewLast' -, - 'Data link using: {^{tmplTag/}} {^{:lastName}} '); - // ----------------------------------------------------------------------- + getResult("Change a"); - // ................................ Reset ................................ - $("#result").empty(); - person1._firstName = "Jo"; // reset Prop - person1.lastName = "One"; // reset Prop - settings.title = "Mr"; // reset Prop - settings.width = 30; // reset Prop + $.observable(theC).setProperty("b", {a: "B"}); - // =============================== Arrange =============================== + getResult("Change b"); - $.templates('prop:{^{:lastName}} computed:{^{:fullName()}} Tag:{^{tmplTag/}}') - .link("#result", person1); + $.observable(theD).setProperty("c", { + _b: {a: "C"}, + b: function() { + return this._b; + } + }); - // ................................ Act .................................. - before = $("#result").text() + $("#last").val() + $("#full").val(); - $.observable(person1).setProperty({firstName: "newFirst", lastName: "newLast"}); - $.observable(settings).setProperty({ title: "Sir", width: 40}); - $.observable(person1).setProperty({fullName: "compFirst compLast"}); - after = $("#result").text() + $("#last").val() + $("#full").val(); + getResult("Change c"); - // ............................... Assert ................................. - equal(before + "|" + after, - 'prop:OneOne computed:Mr Jo OneMr Jo One Tag:Name: Mr Jo. Width: 30Name: Mr Jo. Width: 30OneMr Jo One|prop:compLastcompLast computed:Sir compFirst compLastSir compFirst compLast Tag:Name: Sir compFirst. Width: 40Name: Sir compFirst. Width: 40compLastSir compFirst compLast', + $.observable(theE).setProperty("d", { + _c: { + _b: {a: "D"}, + b: function() { + return this._b; + } + }, + c: function() { + return this._c; + } + }); + + getResult("Change d"); + + $.observable(theF).setProperty("e", { + _d: { + _c: { + _b: {a: "E"}, + b: function() { + return this._b; + } + }, + c: function() { + return this._c; + } + }, + d: function() { + return this._d; + } + }); + + getResult("Change e"); + + // ............................... Assert ................................. + equal(res, isIE8 + ? "None: a a a a a a |Change a: AAAAAA |Change b: ABBBBB |Change c: ABCCCC |Change d: ABCDDD |Change e: ABCDEE |" + : "None: a a a a a a |Change a: A A A A A A |Change b: A B B B B B |Change c: A B C C C C |Change d: A B C D D D |Change e: A B C D E E |", + "{{: ...}} expressions with deeply chained computed observables"); + // =============================== Arrange =============================== + + function setData2() { + res = "", + + theB = { + a1: { + a: "a" + } + }; + + theC = { + b1: { + _b: theB, + b: function() { + return this._b; + } + } + }; + + theD = { + c1: { + _c: theC, + c: function() { + return this._c; + } + } + }; + + theE = { + d1: { + _d: theD, + d: function() { + return this._d; + } + } + }; + + theF = { + e1: { + _e: theE, + e: function() { + return this._e; + } + } + }; + + theC.b1.b.set = function(val) { + this._b = val; + }; + + theD.c1.c.set = function(val) { + this._c = val; + }; + + theE.d1.d.set = function(val) { + this._d = val; + }; + + theF.e1.e.set = function(val) { + this._e = val; + }; + } + + tmpl = $.templates('{^{:e1.e().d1.d().c1.c().b1.b().a1.a}} {^{:e1.e().d1.d().c1.c().b1.b().a1^a}} {^{:e1.e().d1.d().c1.c().b1.b()^a1.a}} {^{:e1.e().d1.d().c1.c().b1^b().a1.a}} {^{:e1.e().d1.d().c1.c()^b1.b().a1.a}} {^{:e1.e().d1.d().c1^c().b1.b().a1.a}} {^{:e1.e().d1.d()^c1.c().b1.b().a1.a}} {^{:e1.e().d1^d().c1.c().b1.b().a1.a}} {^{:e1.e()^d1.d().c1.c().b1.b().a1.a}} {^{:e1^e().d1.d().c1.c().b1.b().a1.a}}'); + + setData2(); + + tmpl.link("#result", theF); + + getResult("None"); + + // ................................ Act .................................. + + $.observable(theB.a1).setProperty("a", "A"); + + getResult("Change a"); + + $.observable(theB).setProperty("a1", { + a: "A1" + }); + + getResult("Change a1"); + + $.observable(theC.b1).setProperty("b", { + a1: { + a: "B" + } + }); + + getResult("Change b"); + + $.observable(theC).setProperty("b1", { + _b: { + a1: { + a: "B1" + } + }, + b: function() { + return this._b; + } + }); + + getResult("Change b1"); + + $.observable(theD.c1).setProperty("c", { + b1: { + _b: { + a1: { + a: "C" + } + }, + b: function() { + return this._b; + } + } + }); + + getResult("Change c"); + + $.observable(theD).setProperty("c1", { + _c: { + b1: { + _b: { + a1: { + a: "C1" + } + }, + b: function() { + return this._b; + } + } + }, + c: function() { + return this._c; + } + }); + + getResult("Change c1"); + + $.observable(theE.d1).setProperty("d", { + c1: { + _c: { + b1: { + _b: { + a1: { + a: "D" + } + }, + b: function() { + return this._b; + } + } + }, + c: function() { + return this._c; + } + } + }); + + getResult("Change d"); + + $.observable(theE).setProperty("d1", { + _d: { + c1: { + _c: { + b1: { + _b: { + a1: { + a: "D1" + } + }, + b: function() { + return this._b; + } + } + }, + c: function() { + return this._c; + } + } + }, + d: function() { + return this._d; + } + }); + + getResult("Change d1"); + + $.observable(theF.e1).setProperty("e", { + d1: { + _d: { + c1: { + _c: { + b1: { + _b: { + a1: { + a: "E" + } + }, + b: function() { + return this._b; + } + } + }, + c: function() { + return this._c; + } + } + }, + d: function() { + return this._d; + } + } + }); + + getResult("Change e"); + + $.observable(theF).setProperty("e1", { + _e: { + d1: { + _d: { + c1: { + _c: { + b1: { + _b: { + a1: { + a: "E1" + } + }, + b: function() { + return this._b; + } + } + }, + c: function() { + return this._c; + } + } + }, + d: function() { + return this._d; + } + } + }, + e: function() { + return this._e; + } + }); + + getResult("Change e1"); + + // ............................... Assert ................................. + equal(res, isIE8 + ? "None: a a a a a a a a a a |Change a: AAAAAAAAAA |Change a1: AA1A1A1A1A1A1A1A1A1 |Change b: AA1BBBBBBBB |Change b1: AA1BB1B1B1B1B1B1B1 |Change c: AA1BB1CCCCCC |Change c1: AA1BB1CC1C1C1C1C1 |Change d: AA1BB1CC1DDDD |Change d1: AA1BB1CC1DD1D1D1 |Change e: AA1BB1CC1DD1EE |Change e1: AA1BB1CC1DD1EE1 |" + : "None: a a a a a a a a a a |Change a: A A A A A A A A A A |Change a1: A A1 A1 A1 A1 A1 A1 A1 A1 A1 |Change b: A A1 B B B B B B B B |Change b1: A A1 B B1 B1 B1 B1 B1 B1 B1 |Change c: A A1 B B1 C C C C C C |Change c1: A A1 B B1 C C1 C1 C1 C1 C1 |Change d: A A1 B B1 C C1 D D D D |Change d1: A A1 B B1 C C1 D D1 D1 D1 |Change e: A A1 B B1 C C1 D D1 E E |Change e1: A A1 B B1 C C1 D D1 E E1 |", + "{{: ...}} expressions with deeply chained computed observables (variant)"); + + // =============================== Arrange =============================== + tmpl = $.templates('{^{for f.e().d().c().b()}}{^{:a}}{{/for}} {^{for f.e().d().c()^b()}}{^{:a}}{{/for}} {^{for f.e().d()^c().b()}}{^{:a}}{{/for}} {^{for f.e()^d().c().b()}}{^{:a}}{{/for}} {^{for f^e().d().c().b()}}{^{:a}}{{/for}}'); + + setData(); + + tmpl.link("#result", data); + + getResult("None"); + + // ................................ Act .................................. + + $.observable(theB).setProperty("a", "A"); + + getResult("Change a"); + + $.observable(theC).setProperty("b", {a: "B"}); + + getResult("Change b"); + + $.observable(theD).setProperty("c", { + _b: {a: "C"}, + b: function() { + return this._b; + } + }); + + getResult("Change c"); + + $.observable(theE).setProperty("d", { + _c: { + _b: {a: "D"}, + b: function() { + return this._b; + } + }, + c: function() { + return this._c; + } + }); + + getResult("Change d"); + + $.observable(theF).setProperty("e", { + _d: { + _c: { + _b: {a: "E"}, + b: function() { + return this._b; + } + }, + c: function() { + return this._c; + } + }, + d: function() { + return this._d; + } + }); + + getResult("Change e"); + + // ............................... Assert ................................. + equal(res, isIE8 + ? "None: a a a a a |Change a: AAAAA |Change b: BBBBB |Change c: BCCCC |Change d: BCDDD |Change e: BCDEE |" + : "None: a a a a a |Change a: A A A A A |Change b: B B B B B |Change c: B C C C C |Change d: B C D D D |Change e: B C D E E |", + "{{for ...}} expressions with deeply chained computed observables"); + + // =============================== Arrange =============================== + tmpl = $.templates('{^{:f.e().d().c().b().a + " " + e.d().c().b().a + " " + d.c().b().a + " " + c.b().a + " " + b.a}} | {^{:f.e()^d().c().b().a + " " + e.d()^c().b().a + " " + d.c()^b().a + " " + c.b()^a + " " + b.a}}'); + + setData(); + + tmpl.link("#result", data); + + getResult("None"); + + // ................................ Act .................................. + + $.observable(theB).setProperty("a", "A"); + + getResult("Change a"); + + $.observable(theC).setProperty("b", {a: "B"}); + + getResult("Change b"); + + $.observable(theD).setProperty("c", { + _b: {a: "C"}, + b: function() { + return this._b; + } + }); + + getResult("Change c"); + + $.observable(theE).setProperty("d", { + _c: { + _b: {a: "D"}, + b: function() { + return this._b; + } + }, + c: function() { + return this._c; + } + }); + + getResult("Change d"); + + $.observable(theF).setProperty("e", { + _d: { + _c: { + _b: {a: "E"}, + b: function() { + return this._b; + } + }, + c: function() { + return this._c; + } + }, + d: function() { + return this._d; + } + }); + + getResult("Change e"); + + // ............................... Assert ................................. + equal(res, isIE8 + ? "None: a a a a a | a a a a a |Change a: A A A A A |A A A A A |Change b: A A A A A |B B B B A |Change c: A A A A A |C C C B A |Change d: A A A A A |D D C B A |Change e: A A A A A |E D C B A |" + : "None: a a a a a | a a a a a |Change a: A A A A A | A A A A A |Change b: A A A A A | B B B B A |Change c: A A A A A | C C C B A |Change d: A A A A A | D D C B A |Change e: A A A A A | E D C B A |", + "Sibling {{: ...}} expressions with deeply chained computed observables"); + + // ................................ Reset ................................ + $("#result").empty(); + + })(); +}); + +module("API - data-bound tags"); + +test("{^{:expression}}", function() { + + // =============================== Arrange =============================== + + $.templates('prop:{^{:lastName}}') + .link("#result", person1); + + // ................................ Act .................................. + before = $("#result").text(); + $.observable(person1).setProperty("lastName", "newLast"); + after = $("#result").text(); + + // ............................... Assert ................................. + equal(before + "|" + after, + 'prop:One|prop:newLast', + 'Data link using: {^{:lastName}}'); + // ----------------------------------------------------------------------- + + // =============================== Arrange =============================== + + // ................................ Act .................................. + + $("#result").empty(); + + // ............................... Assert ................................. + + ok(!viewsAndBindings() && !$._data(person1).events, + "$(container).empty removes both views and current listeners from that content"); + // ----------------------------------------------------------------------- + + // ................................ Reset ................................ + person1.lastName = "One"; // reset Prop + + // =============================== Arrange =============================== + + $.templates('prop:{^{:wasUndefined}}') + .link("#result", person1); + + // ................................ Act .................................. + before = $("#result").text(); + $.observable(person1).setProperty("wasUndefined", "newLast"); + after = $("#result").text(); + + // ............................... Assert ................................. + equal(before + "|" + after, + 'prop:|prop:newLast', + 'Data link using: {^{:wasUndefined}} - renders to empty string when undefined, and still binds correctly for subsequent modifications'); + // ----------------------------------------------------------------------- + + // ................................ Reset ................................ + $("#result").empty(); + person1.lastName = "One"; // reset Prop + + // =============================== Arrange =============================== + + var tmpl = $.templates("{^{:#data.person1.home.address.street}}{^{:person1.home^address.street}}"); + $.link(tmpl, "#result", model); + + // ................................ Act .................................. + before = $("#result").text(); + $.observable(address1).setProperty("street", "newStreetOne"); + $.observable(person1).setProperty("home", home2); // Deep change + after = $("#result").text(); + + // ............................... Assert ................................. + equal(before + "|" + after, + "StreetOneStreetOne|newStreetOneStreetTwo", + '#data.person1.home.address.street binds only to the leaf, but person1.home^address.street does deep binding'); + + // ................................ Reset ................................ + $("#result").empty(); + address1.street = "StreetOne"; + person1.home = home1; + + // =============================== Arrange =============================== + + var count = 0, + data = { + last: "Smith", + other: "Other", + a: function() { return this; } + }; + + $.views.tags({ + textbox: { + onAfterLink: function() { + // Find input in contents, if not already found + this.linkedElem = this.linkedElem || this.contents("input"); + }, + onUpdate: function() { + // No need to re-render whole tag, when content updates. + return false; + }, + template: "{^{:~tag.tagCtx.props.label}}
    " + } + }); + + tmpl = $.templates( + '' + + '' + + + '' + + '' + + + '{^{:convert=~upper last a().other}}' + + '{^{:a().other last convert=~upper}}' + + + '{^{textbox a().other last trigger=true convert=~upper convertBack=~lower/}}' + + '{^{textbox convert=~upper convertBack=~lower last a().other trigger=true/}}'); + + $.link(tmpl, "#result", data, { + upper: function(val) { return val.toUpperCase(); }, + lower: function(val) { return val.toLowerCase(); } + }); + + // ................................ Act .................................. + before = $("#result").text(); + $("#result input").each(function() { + before += this.value; + }); + $.observable(data).setProperty("last", "newLast"); + $.observable(data).setProperty("other", "newOther"); + $("#result input").each(function() { + this.value += count++; + $(this).change(); + }); + after = $("#result").text(); + $("#result input").each(function() { + after += this.value; + }); + + // ............................... Assert ................................. + equal(before + "|" + after, + "SMITHOTHEROTHERSMITHOtherSmithOTHERSMITH|NEWLAST135NEWOTHER024NEWOTHER024NEWLAST135newother024newlast135NEWOTHER024NEWLAST135", + 'Binding correctly to and from first argument, even with multiple args and props and with objects in paths, and with converters'); + + // ................................ Reset ................................ + $("#result").empty(); + + // =============================== Arrange =============================== + + var ob = { text: "aBc" }; + $.link("~upper(~ob.text) + ~lower(~ob.text)", "#result", undefined, { + upper: function(val) { return val.toUpperCase(); }, + lower: function(val) { return val.toLowerCase(); }, + ob: ob + }); + + // ................................ Act .................................. + before = $("#result").text(); + $.observable(ob).setProperty("text", "DeF"); + after = $("#result").text(); + + // ............................... Assert ................................. + equal(before + "|" + after, + "ABCabc|DEFdef", + '$.link(expression, selector, undefined, helpers) - without passing data, data-links correctly to helpers'); + + // ................................ Reset ................................ + $("#result").empty(); + $.unlink(true, "#result"); + + // =============================== Arrange =============================== + + ob = { text: "aBc" }; + $.views.helpers({ + upper: function(val) { return val.toUpperCase(); }, + lower: function(val) { return val.toLowerCase(); }, + ob: ob + }); + $.link("~upper(~ob.text) + ~lower(~ob.text)", "#result"); + + // ................................ Act .................................. + before = $("#result").text(); + $.observable(ob).setProperty("text", "DeF"); + after = $("#result").text(); + + // ............................... Assert ................................. + equal(before + "|" + after, + "ABCabc|DEFdef", + '$.link(expression, selector) - without passing data, data-links correctly to helpers'); + + // ................................ Reset ................................ + $.unlink(true, "#result"); + $("#result").empty(); + + // =============================== Arrange =============================== + + tmpl = $.templates("{^{:~upper(~ob.text)}}{^{:~lower(~ob.text)}}"); + $.views.helpers.ob = ob = { text: "aBc" }; + $.link(tmpl, "#result"); + + // ................................ Act .................................. + before = $("#result").text(); + $.observable(ob).setProperty("text", "DeF"); + after = $("#result").text(); + + // ............................... Assert ................................. + equal(before + "|" + after, + "ABCabc|DEFdef", + '$.link(template, selector) - without passing data, data-links correctly to helpers'); + + // ................................ Reset ................................ + $("#result").empty(); + + // =============================== Arrange =============================== + + $.views.helpers.ob = ob = { text: "aBc" }; + tmpl.link("#result"); + + // ................................ Act .................................. + before = $("#result").text(); + $.observable(ob).setProperty("text", "DeF"); + after = $("#result").text(); + + // ............................... Assert ................................. + equal(before + "|" + after, + "ABCabc|DEFdef", + 'template.link(selector) - without passing data, data-links correctly to helpers'); + + // ................................ Reset ................................ + $("#result").empty(); + + // =============================== Arrange =============================== + + $.views.helpers.ob = ob = { text: "aBc" }; + $("#result").link(tmpl); + + // ................................ Act .................................. + before = $("#result").text(); + $.observable(ob).setProperty("text", "DeF"); + after = $("#result").text(); + + // ............................... Assert ................................. + equal(before + "|" + after, + "ABCabc|DEFdef", + '$(selector).link(template) - without passing data, data-links correctly to helpers'); + + // ................................ Reset ................................ + $("#result").empty(); + + // =============================== Arrange =============================== + + $.views.helpers.ob = ob = { text: "aBc" }; + $("#result").link("~upper(~ob.text) + ~lower(~ob.text)"); + + // ................................ Act .................................. + before = $("#result").text(); + $.observable(ob).setProperty("text", "DeF"); + after = $("#result").text(); + + // ............................... Assert ................................. + equal(before + "|" + after, + "ABCabc|DEFdef", + '$(selector).link(expression) - without passing data, data-links correctly to helpers'); + + // ................................ Reset ................................ + $.unlink(true, "#result"); + $("#result").empty(); + +}); + +test("{^{>expression}}", function() { + + // =============================== Arrange =============================== + + $.templates('prop:{^{>lastName + "
    "}}') + .link("#result", person1); + + // ................................ Act .................................. + before = $("#result").text(); + $.observable(person1).setProperty("lastName", "newLast"); + after = $("#result").text(); + + // ............................... Assert ................................. + equal(before + "|" + after, + 'prop:One
    |prop:newLast
    ', + 'Data link using: {^{:lastName}}'); + // ----------------------------------------------------------------------- + + // =============================== Arrange =============================== + + // ................................ Act .................................. + + $("#result").empty(); + + // ............................... Assert ................................. + + ok(!viewsAndBindings() && !$._data(person1).events, + "$(container).empty removes both views and current listeners from that content"); + // ----------------------------------------------------------------------- + + // ................................ Reset ................................ + person1.lastName = "One"; // reset Prop + + // =============================== Arrange =============================== + + var tmpl = $.templates("{^{>#data.person1.home.address.street}}{^{>person1.home^address.street}}"); + $.link(tmpl, "#result", model); + + // ................................ Act .................................. + before = $("#result").text(); + $.observable(address1).setProperty("street", "newStreetOne"); + $.observable(person1).setProperty("home", home2); // Deep change + after = $("#result").text(); + + // ............................... Assert ................................. + equal(before + "|" + after, + "StreetOneStreetOne|newStreetOneStreetTwo", + '#data.person1.home.address.street binds only to the leaf, but person1.home^address.street does deep binding'); + + // ................................ Reset ................................ + $("#result").empty(); + address1.street = "StreetOne"; + person1.home = home1; + +}); + +test("{^{tag}}", function() { + + // =============================== Arrange =============================== + $.views.tags({ + norendernotemplate: {}, + voidrender: function() { }, + emptyrender: function() { return ""; }, + emptytemplate: { + template: "" + }, + templatereturnsempty: { + template: "{{:a}}" + } + }); + + // ............................... Assert ................................. + $.templates("a{{norendernotemplate/}}b{^{norendernotemplate/}}c{{norendernotemplate}}{{/norendernotemplate}}d{^{norendernotemplate}}{{/norendernotemplate}}e").link("#result", 1); + equal($("#result").text(), "abcde", + "non-rendering tag (no template, no render function) renders empty string"); + + $.templates("a{{voidrender/}}b{^{voidrender/}}c{{voidrender}}{{/voidrender}}d{^{voidrender}}{{/voidrender}}e").link("#result", 1); + equal($("#result").text(), "abcde", + "non-rendering tag (no template, no return from render function) renders empty string"); + + $.templates("a{{emptyrender/}}b{^{emptyrender/}}c{{emptyrender}}{{/emptyrender}}d{^{emptyrender}}{{/emptyrender}}e").link("#result", 1); + equal($("#result").text(), "abcde", + "non-rendering tag (no template, empty string returned from render function) renders empty string", 1); + + $.templates("a{{emptytemplate/}}b{^{emptytemplate/}}c{{emptytemplate}}{{/emptytemplate}}d{^{emptytemplate}}{{/emptytemplate}}e").link("#result", 1); + equal($("#result").text(), "abcde", + "non-rendering tag (template has no content, no render function) renders empty string"); + + $.templates("a{{templatereturnsempty/}}b{^{templatereturnsempty/}}c{{templatereturnsempty}}{{/templatereturnsempty}}d{^{templatereturnsempty}}{{/templatereturnsempty}}e").link("#result", 1); + equal($("#result").text(), "abcde", + "non-rendering tag (template returns empty string, no render function) renders empty string"); + + // =============================== Arrange =============================== + + $.templates('{^{tmplTag/}}') + .link("#result", person1, { settings: settings }); + + // ................................ Act .................................. + before = $("#result").text(); + $.observable(person1).setProperty({ firstName: "newFirst", lastName: "newLast" }); + $.observable(settings).setProperty({ title: "Sir", width: 40 }); + $.observable(person1).setProperty({ fullName: "compFirst compLast" }); + after = $("#result").text(); + + // ............................... Assert ................................. + equal(before + "|" + after, 'Name: Mr Jo. Width: 30|Name: Sir compFirst. Width: 40', + 'Data link with: {^{tmplTag/}} updates when dependant object paths change'); + // ----------------------------------------------------------------------- + + // ................................ Reset ................................ + $("#result").empty(); + person1._firstName = "Jo"; // reset Prop + person1.lastName = "One"; // reset Prop + settings.title = "Mr"; // reset Prop + settings.width = 30; // reset Prop + + // =============================== Arrange =============================== + + $.templates('{{tmplTag/}}') + .link("#result", person1, { settings: settings }); + + // ................................ Act .................................. + before = $("#result").text(); + $.observable(person1).setProperty({ firstName: "newFirst", lastName: "newLast" }); + $.observable(settings).setProperty({ title: "Sir", width: 40 }); + $.observable(person1).setProperty({ fullName: "compFirst compLast" }); + after = $("#result").text(); + + // ............................... Assert ................................. + ok(before === 'Name: Mr Jo. Width: 30' && before === after && !$._data(person1).events && !$._data(settings).events, + 'Data link with: {{tmplTag/}} does nothing'); + // ----------------------------------------------------------------------- + + // ................................ Reset ................................ + $("#result").empty(); + person1._firstName = "Jo"; // reset Prop + person1.lastName = "One"; // reset Prop + settings.title = "Mr"; // reset Prop + settings.width = 30; // reset Prop + + // =============================== Arrange =============================== + + $.templates('{^{fnTag/}}') + .link("#result", person1, { settings: settings }); + + // ................................ Act .................................. + before = $("#result").text(); + $.observable(person1).setProperty({ firstName: "newFirst", lastName: "newLast" }); + $.observable(settings).setProperty({ title: "Sir", width: 40 }); + $.observable(person1).setProperty({ fullName: "compFirst compLast" }); + after = $("#result").text(); + + // ............................... Assert ................................. + equal(before + "|" + after, 'Name: Mr Jo. Width: 30|Name: Sir compFirst. Width: 40', + 'Data link with: {^{fnTag/}} updates when dependant object paths change'); + // ----------------------------------------------------------------------- + + // ................................ Reset ................................ + $("#result").empty(); + person1._firstName = "Jo"; // reset Prop + person1.lastName = "One"; // reset Prop + settings.title = "Mr"; // reset Prop + settings.width = 30; // reset Prop + + // =============================== Arrange =============================== + + $.templates('{{fnTag/}}') + .link("#result", person1, { settings: settings }); + + // ................................ Act .................................. + before = $("#result").text(); + $.observable(person1).setProperty({ firstName: "newFirst", lastName: "newLast" }); + $.observable(settings).setProperty({ title: "Sir", width: 40 }); + $.observable(person1).setProperty({ fullName: "compFirst compLast" }); + after = $("#result").text(); + + // ............................... Assert ................................. + ok(before === 'Name: Mr Jo. Width: 30' && before === after && !$._data(person1).events && !$._data(settings).events, + 'Data link with: {{fnTag/}} does nothing'); + // ----------------------------------------------------------------------- + + // ................................ Reset ................................ + $("#result").empty(); + person1._firstName = "Jo"; // reset Prop + person1.lastName = "One"; // reset Prop + settings.title = "Mr"; // reset Prop + settings.width = 30; // reset Prop + + // =============================== Arrange =============================== + + $.templates('
    {^{fnTagEl/}}
    ') + .link("#result", person1, { settings: settings }); + + // ................................ Act .................................. + before = $("#result div *")[1].outerHTML; // The innerHTML will be Name: Sir compFirst. Width: 40 + $.observable(person1).setProperty({ firstName: "newFirst", lastName: "newLast" }); + $.observable(settings).setProperty({ title: "Sir", width: 40 }); + $.observable(person1).setProperty({ fullName: "compFirst compLast" }); + after = $("#result div *")[1].outerHTML; + // ............................... Assert ................................. + equal(before + "|" + after, + isIE8 ? 'Name: Mr Jo. Width: 30|Name: Sir compFirst. Width: 40' : 'Name: Mr Jo. Width: 30|Name: Sir compFirst. Width: 40', + 'Data link with: {^{fnTagEl/}} rendering , updates when dependant object paths change'); + // ----------------------------------------------------------------------- + + // ................................ Reset ................................ + $("#result").empty(); + person1._firstName = "Jo"; // reset Prop + person1.lastName = "One"; // reset Prop + settings.title = "Mr"; // reset Prop + settings.width = 30; // reset Prop + + // =============================== Arrange =============================== + $.templates('
    {^{fnTagElNoInit firstName ~settings.width/}}
    ') + .link("#result", person1, { settings: settings }); + + // ................................ Act .................................. + before = $("#result div span").html(); // The innerHTML will be ""
  • Name: Mr Jo. Width: 30
  • " + $.observable(person1).setProperty({ firstName: "newFirst", lastName: "newLast" }); + $.observable(settings).setProperty({ title: "Sir", width: 40 }); + $.observable(person1).setProperty({ fullName: "compFirst compLast" }); + after = $("#result div span").html(); + + // ............................... Assert ................................. + equal(before + "|" + after, + 'Name: Mr Jo. Width: 30|Name: Sir compFirst. Width: 40', + 'Data link with {^{fnTagElNoInit}} rendering , updates when dependant object paths change'); + // ----------------------------------------------------------------------- + + // ................................ Reset ................................ + $("#result").empty(); + person1._firstName = "Jo"; // reset Prop + person1.lastName = "One"; // reset Prop + settings.title = "Mr"; // reset Prop + settings.width = 30; // reset Prop + + // =============================== Arrange =============================== + + $.templates('
      {^{fnTagElCnt/}}
    ') + .link("#result", person1, { settings: settings }); + + // ................................ Act .................................. + before = $("#result ul li").html(); // The innerHTML will be ""
  • Name: Mr Jo. Width: 30
  • " + $.observable(person1).setProperty({ firstName: "newFirst", lastName: "newLast" }); + $.observable(settings).setProperty({ title: "Sir", width: 40 }); + $.observable(person1).setProperty({ fullName: "compFirst compLast" }); + after = $("#result ul li").html(); + + // ............................... Assert ................................. + equal(before + "|" + after, + 'Name: Mr Jo. Width: 30|Name: Sir compFirst. Width: 40', + 'Data link with {^{fnTagElCnt}} rendering
  • , updates when dependant object paths change'); + // ----------------------------------------------------------------------- + + // ................................ Reset ................................ + $("#result").empty(); + person1._firstName = "Jo"; // reset Prop + person1.lastName = "One"; // reset Prop + settings.title = "Mr"; // reset Prop + settings.width = 30; // reset Prop + + // =============================== Arrange =============================== + + $.templates('
      {^{fnTagElCntNoInit firstName ~settings.width/}}
    ') + .link("#result", person1, { settings: settings }); + + // ................................ Act .................................. + before = $("#result ul li").html(); // The innerHTML will be ""
  • Name: Mr Jo. Width: 30
  • " + $.observable(person1).setProperty({ firstName: "newFirst", lastName: "newLast" }); + $.observable(settings).setProperty({ title: "Sir", width: 40 }); + $.observable(person1).setProperty({ fullName: "compFirst compLast" }); + after = $("#result ul li").html(); + + // ............................... Assert ................................. + equal(before + "|" + after, + 'Name: Mr Jo. Width: 30|Name: Sir compFirst. Width: 40', + 'Data link with {^{fnTagElCntNoInit}} rendering
  • , updates when dependant object paths change'); + // ----------------------------------------------------------------------- + + // ................................ Reset ................................ + $("#result").empty(); + person1._firstName = "Jo"; // reset Prop + person1.lastName = "One"; // reset Prop + settings.title = "Mr"; // reset Prop + settings.width = 30; // reset Prop + + // =============================== Arrange =============================== + + $.templates('{^{tmplTagWithProps #data ~settings.reverse theTitle=~settings.title ~street=home.address.street/}}') + .link("#result", person1, { settings: settings }); + + // ................................ Act .................................. + before = $("#result").text(); + $.observable(person1).setProperty({ firstName: "newFirst", lastName: "newLast", "home.address.street": "newStreet" }); + $.observable(settings).setProperty({ title: "Sir", width: 40, reverse: false }); + $.observable(person1).setProperty({ fullName: "compFirst compLast" }); + after = $("#result").text(); + + // ............................... Assert ................................. + equal(before + "|" + after, 'Name: Mr Jo. Width: 30. Value: true. Prop theTitle: Mr. Prop ~street: StreetOne|Name: Sir compFirst. Width: 40. Value: false. Prop theTitle: Sir. Prop ~street: newStreet', + 'Data link with: {^{tmplTagWithProps ~some.path foo=~other.path ~bar=another.path/}} updates when dependant object paths change'); + // ----------------------------------------------------------------------- + + // ................................ Reset ................................ + $("#result").empty(); + person1._firstName = "Jo"; // reset Prop + person1.lastName = "One"; // reset Prop + settings.title = "Mr"; // reset Prop + settings.width = 30; // reset Prop + settings.reverse = true; // reset Prop + address1.street = "StreetOne"; // reset Prop + + // =============================== Arrange =============================== + + $.templates('{{tmplTagWithProps #data ~settings.reverse theTitle=~settings.title ~street=home.address.street/}}') + .link("#result", person1, { settings: settings }); + + // ................................ Act .................................. + before = $("#result").text(); + $.observable(person1).setProperty({ firstName: "newFirst", lastName: "newLast", "home.address.street": "newStreet" }); + $.observable(settings).setProperty({ title: "Sir", width: 40, reverse: false }); + $.observable(person1).setProperty({ fullName: "compFirst compLast" }); + after = $("#result").text(); + + // ............................... Assert ................................. + ok(before === 'Name: Mr Jo. Width: 30. Value: true. Prop theTitle: Mr. Prop ~street: StreetOne' && before === after && !$._data(person1).events && !$._data(settings).events, + 'Data link with: {{tmplTagWithProps ~some.path foo=~other.path ~bar=another.path/}} does nothing'); + // ----------------------------------------------------------------------- + + // ................................ Reset ................................ + $("#result").empty(); + person1._firstName = "Jo"; // reset Prop + person1.lastName = "One"; // reset Prop + settings.title = "Mr"; // reset Prop + settings.width = 30; // reset Prop + settings.reverse = true; // reset Prop + address1.street = "StreetOne"; // reset Prop + + // =============================== Arrange =============================== + + $.templates('{^{fnTagWithProps #data ~settings.reverse theTitle=~settings.title ~street=home.address.street/}}') + .link("#result", person1, { settings: settings }); + + // ................................ Act .................................. + before = $("#result").text(); + $.observable(person1).setProperty({ firstName: "newFirst", lastName: "newLast", "home.address.street": "newStreet" }); + $.observable(settings).setProperty({ title: "Sir", width: 40, reverse: false }); + $.observable(person1).setProperty({ fullName: "compFirst compLast" }); + after = $("#result").text(); + + // ............................... Assert ................................. + equal(before + "|" + after, 'Name: Mr Jo. Width: 30. Value: true. Prop theTitle: Mr. Prop ~street: StreetOne|Name: Sir compFirst. Width: 40. Value: false. Prop theTitle: Sir. Prop ~street: newStreet', + 'Data link with: {^{fnTagWithProps ~some.path foo=~other.path ~bar=another.path/}} updates when dependant object paths change'); + // ----------------------------------------------------------------------- + + // ................................ Reset ................................ + $("#result").empty(); + person1._firstName = "Jo"; // reset Prop + person1.lastName = "One"; // reset Prop + settings.title = "Mr"; // reset Prop + settings.width = 30; // reset Prop + settings.reverse = true; // reset Prop + address1.street = "StreetOne"; // reset Prop + + // =============================== Arrange =============================== + + $.templates('{{fnTagWithProps #data ~settings.reverse theTitle=~settings.title ~street=home.address.street/}}') + .link("#result", person1, { settings: settings }); + + // ................................ Act .................................. + before = $("#result").text(); + $.observable(person1).setProperty({ firstName: "newFirst", lastName: "newLast", "home.address.street": "newStreet" }); + $.observable(settings).setProperty({ title: "Sir", width: 40, reverse: false }); + $.observable(person1).setProperty({ fullName: "compFirst compLast" }); + after = $("#result").text(); + + // ............................... Assert ................................. + ok(before === 'Name: Mr Jo. Width: 30. Value: true. Prop theTitle: Mr. Prop ~street: StreetOne' && before === after && !$._data(person1).events && !$._data(settings).events, + 'Data link with: {{fnTagWithProps ~some.path foo=~other.path ~bar=another.path/}} does nothing'); + // ----------------------------------------------------------------------- + + // ................................ Reset ................................ + $("#result").empty(); + person1._firstName = "Jo"; // reset Prop + person1.lastName = "One"; // reset Prop + settings.title = "Mr"; // reset Prop + settings.width = 30; // reset Prop + settings.reverse = true; // reset Prop + address1.street = "StreetOne"; // reset Prop + + // =============================== Arrange =============================== + $.views.tags({ + myTag: { + template: "{{:~tag.tagCtx.args[0]}}", + attr: "html" + } + }); + + $.templates("{^{myTag foo(\"w\\x\'y\").b/}}
    ") + .link("#result", { + foo: function(val) { + return { b: val }; + } + }); + + // ............................... Assert ................................. + equal($("#result span")[0].outerHTML, isIE8 ? "w\\x\'y" : "w\\x\'y", + "{^{myTag foo(\"w\\x\'y\").b/}} - correct compilation and output of quotes and backslash, with object returned in path (so nested compilation)"); + equal($("#result span")[1].outerHTML, isIE8 ? "w\\x" : "w\\x", + "
    - correct compilation and output of quotes and backslash, with object returned in path (so nested compilation)"); + + // ................................ Reset ................................ + $("#result").empty(); + result = ""; +}); + +test("{^{for}}", function() { + + // =============================== Arrange =============================== + + model.things = [{ thing: "box" }]; // reset Prop + $.templates('{^{for things}}{{:thing}}{{/for}}') + .link("#result", model); + + // ................................ Act .................................. + before = $("#result").text(); + $.observable(model.things).insert(0, { thing: "tree" }); + after = $("#result").text(); + + // ............................... Assert ................................. + equal(before + "|" + after, 'box|treebox', + '{^{for things}} binds to array changes on leaf array'); + + // ................................ Act .................................. + $.observable(model).setProperty({ things: [{ thing: "triangle" }, { thing: "circle" }] }); + after = $("#result").text(); + + // ............................... Assert ................................. + equal(after, 'trianglecircle', + '{^{for things}} binds to property change on path'); + + // ................................ Act .................................. + $.observable(model).setProperty({ things: { thing: "square" } }); + after = $("#result").text(); + + // ............................... Assert ................................. + equal(after, 'square', + '{^{for things}} binds to property change on path - swapping from array to singleton object'); + // ----------------------------------------------------------------------- + + // ................................ Act .................................. + $.observable(model).setProperty({ things: [{ thing: "triangle2" }, { thing: "circle2" }] }); + after = $("#result").text(); + + // ............................... Assert ................................. + equal(after, 'triangle2circle2', + '{^{for things}} binds to property change on path - swapping from singleton back to array'); + // ----------------------------------------------------------------------- + + // ................................ Act .................................. + $.observable(model.things).insert([{ thing: "oblong" }, { thing: "pentagon" }]); + after = $("#result").text(); + + // ............................... Assert ................................. + equal(after, 'triangle2circle2oblongpentagon', + '{^{for things}} binds to array change on array after swapping from singleton back to array'); + // ----------------------------------------------------------------------- + + // ................................ Reset ................................ + $("#result").empty(); + model.things = []; // reset Prop + + // =============================== Arrange =============================== + + var things1 = [{ thing: "box" }], + things2 = [{ thing: "triangle" }, { thing: "circle" }], + square = { thing: "square" }; + + model.things = things1; // reset Prop + + $.templates('{^{for things}}{{:thing}}{{/for}}') + .link("#result", model); + + // ................................ Act .................................. + before = $("#result").text(); + $.observable(things1).insert(0, { thing: "tree" }); + after = $("#result").text(); + + // ............................... Assert ................................. + equal(before + "|" + after, 'box|treebox', + '{^{for things}} binds to array changes on leaf array'); + + // ................................ Act .................................. + $.observable(model).setProperty({ things: things2 }); + after = $("#result").text(); + + // ............................... Assert ................................. + equal(after, 'trianglecircle', + '{^{for things}} binds to property change on path'); + + // ................................ Act .................................. + $.observable(model).setProperty({ things: square }); + after = $("#result").text(); + + // ............................... Assert ................................. + equal(after, 'square', + '{^{for things}} binds to property change on path - swapping from array to singleton object'); + // ----------------------------------------------------------------------- + + // ................................ Act .................................. + $.observable(model).setProperty({ things: things2 }); + after = $("#result").text(); + + // ............................... Assert ................................. + equal(after, 'trianglecircle', + '{^{for things}} binds to property change on path - swapping from singleton back to previous array'); + // ----------------------------------------------------------------------- + + // ................................ Act .................................. + $.observable(things2).insert([{ thing: "oblong" }, { thing: "pentagon" }]); + after = $("#result").text(); + + // ............................... Assert ................................. + equal(after, 'trianglecircleoblongpentagon', + '{^{for things}} binds to array change on array after swapping from singleton back to array'); + // ----------------------------------------------------------------------- + + // ................................ Reset ................................ + $("#result").empty(); + model.things = []; // reset Prop + + // =============================== Arrange =============================== + + things1 = [{ thing: "box" }], + things2 = [{ thing: "triangle" }, { thing: "circle" }]; + square = { thing: "square" }; + + model.things = things1; // reset Prop + + $.templates('
      {^{for things}}
    • {{:thing}}
    • {{/for}}
    ') + .link("#result", model); + + // ................................ Act .................................. + before = $("#result").text(); + $.observable(things1).insert(0, { thing: "tree" }); + after = $("#result").text(); + + // ............................... Assert ................................. + equal(before + "|" + after, 'box|treebox', + '{^{for things}} in element content binds to array changes on leaf array'); + + // ................................ Act .................................. + $.observable(model).setProperty({ things: things2 }); + after = $("#result").text(); + + // ............................... Assert ................................. + equal(after, 'trianglecircle', + '{^{for things}} binds to property change on path'); + + // ................................ Act .................................. + $.observable(model).setProperty({ things: square }); + after = $("#result").text(); + + // ............................... Assert ................................. + equal(after, 'square', + '{^{for things}} binds to property change on path - swapping from array to singleton object'); + // ----------------------------------------------------------------------- + + // ................................ Act .................................. + $.observable(model).setProperty({ things: things2 }); + after = $("#result").text(); + + // ............................... Assert ................................. + equal(after, 'trianglecircle', + '{^{for things}} binds to property change on path - swapping from singleton back to previous array'); + // ----------------------------------------------------------------------- + + // ................................ Act .................................. + $.observable(things2).insert([{ thing: "oblong" }, { thing: "pentagon" }]); + after = $("#result").text(); + + // ............................... Assert ................................. + equal(after, 'trianglecircleoblongpentagon', + '{^{for things}} binds to array change on array after swapping from singleton back to array'); + // ----------------------------------------------------------------------- + + // ................................ Reset ................................ + $("#result").empty(); + model.things = []; // reset Prop + + // =============================== Arrange =============================== + + model.things = [{ thing: "box" }, { thing: "table" }]; // reset Prop + + $.templates('{^{:length}} {^{for #data}}{{:thing}}{{/for}}') + .link("#result", model.things, null, true); + + // ................................ Act .................................. + before = $("#result").text(); + $.observable(model.things).insert(0, { thing: "tree" }); + after = $("#result").text(); + + // ............................... Assert ................................. + equal(before + "|" + after, isIE8 ? "2 boxtable|3tree boxtable" : "2 boxtable|3 treeboxtable", + '{^{for #data}} when #data is an array binds to array changes on #data'); + + // ................................ Reset ................................ + $("#result").empty(); + model.things = []; // reset Prop + + // =============================== Arrange =============================== + + model.things = [{ thing: "box" }, { thing: "table" }]; // reset Prop + + $.templates('{^{:length}} {^{for}}{{:thing}}{{/for}}') + .link("#result", model.things, null, true); + + // ................................ Act .................................. + before = $("#result").text(); + $.observable(model.things).insert(0, { thing: "tree" }); + after = $("#result").text(); + + // ............................... Assert ................................. + equal(before + "|" + after, isIE8 ? "2 boxtable|3tree boxtable" : "2 boxtable|3 treeboxtable", + '{^{for}} when #data is an array binds to array changes on #data'); + + // ................................ Reset ................................ + $("#result").empty(); + model.things = []; // reset Prop + + // =============================== Arrange =============================== + + model.things = [{ thing: "box" }, { thing: "table" }]; // reset Prop + + $.templates('{{include things}}{^{:length}} {^{for}}{{:thing}}{{/for}}{{/include}}') + .link("#result", model); + + // ................................ Act .................................. + before = $("#result").text(); + $.observable(model.things).insert(0, { thing: "tree" }); + after = $("#result").text(); + + // ............................... Assert ................................. + equal(before + "|" + after, isIE8 ? "2 boxtable|3tree boxtable" : "2 boxtable|3 treeboxtable", + '{{include things}} moves context to things array, and {^{for}} then iterates and binds to array'); + + // ................................ Reset ................................ + $("#result").empty(); + model.things = []; // reset Prop + + // =============================== Arrange =============================== + + model.things = [{ thing: "box" }]; // reset Prop + $.templates('{^{for things}}{{:thing}}{{else}}None{{/for}}') + .link("#result", model); + + // ................................ Act .................................. + before = $("#result").text(); + $.observable(model.things).insert(0, { thing: "tree" }); + after = $("#result").text(); + + // ............................... Assert ................................. + equal(before + "|" + after, 'box|treebox', + '{^{for things}}{{else}}{{/for}} binds to array changes on leaf array'); + + // ................................ Act .................................. + before = $("#result").text(); + $.observable(model.things).remove(0, 2); + after = $("#result").text(); + + // ............................... Assert ................................. + equal(before + "|" + after, 'treebox|None', + '{^{for things}}{{else}}{{/for}} renders {{else}} block when array is emptied'); + + // ................................ Act .................................. + $.observable(model).setProperty({ things: [{ thing: "triangle" }, { thing: "circle" }] }); + after = $("#result").text(); + + // ............................... Assert ................................. + equal(after, 'trianglecircle', + '{^{for things}}{{else}}{{/for}} binds to property change on path'); + + // ................................ Act .................................. + $.observable(model).setProperty({ things: { thing: "square" } }); + after = $("#result").text(); + + // ............................... Assert ................................. + equal(after, 'square', + '{^{for things}}{{else}}{{/for}} binds to property change on path - swapping from array to singleton object'); + + // ................................ Act .................................. + $.observable(model).removeProperty("things"); + after = $("#result").text(); + + // ............................... Assert ................................. + equal(after, 'None', + '{^{for things}}{{else}}{{/for}} binds to removeProperty change on path - and renders {{else}} block'); + // ----------------------------------------------------------------------- + + // ................................ Reset ................................ + $("#result").empty(); + model.things = []; // reset Prop + + // =============================== Arrange =============================== + + // ................................ Act .................................. + $.templates("testTmpl", '{{if ~things.length}}{{for ~things}}{{:thing}}{{/for}}{{/if}}'); + $.templates('{^{for things ~things=things tmpl="testTmpl"/}}
    top
    ') + .link("#result", model); + + before = $("#result td").text(); + $.observable(model.things).insert(0, { thing: "tree" }); + after = $("#result").text(); + + // ............................... Assert ................................. + equal(before + "|" + after, 'top|toptree', + 'Complex template, with empty placeholder for tbody after thead, and subsequent data-linked insertion of tbody'); + // ----------------------------------------------------------------------- + + // ................................ Act .................................. + $.view("#result", true).refresh(); + result = "" + (after === $("#result").text()); + $.view("#result", true).views._2.refresh(); + result += " " + (after === $("#result").text()); + $.view("#result", true).views._2.views[0].refresh(); + result += " " + (after === $("#result").text()); + $.view("#result", true).views._2.views[0].views._2.refresh(); + result += " " + (after === $("#result").text()); + $.view("#result", true).views._2.views[0].views._2.views._2.refresh(); + result += " " + (after === $("#result").text()); + $.view("#result", true).views._2.views[0].views._2.views._2.views[0].refresh(); + result += " " + (after === $("#result").text()); + + // ............................... Assert ................................. + equal(result, 'true true true true true true', + 'view refresh at all levels correctly maintains content'); + // ----------------------------------------------------------------------- + + // ................................ Reset ................................ + $("#result").empty(); + model.things = []; // reset Prop + + // =============================== Arrange =============================== + + // ................................ Act .................................. + $.templates("testTmpl", '{{if ~things.length}}
    {{for ~things}}{{:thing}}{{/for}}
    {{/if}}'); + $.templates('
    top{^{for things ~things=things tmpl="testTmpl"/}}
    ') + .link("#result", model); + + before = $("#result div").text(); + $.observable(model.things).insert(0, { thing: "tree" }); + after = $("#result").text(); + + // ............................... Assert ................................. + equal(before + "|" + after, 'top|toptree', + 'Complex template, with empty placeholder for span, and subsequent data-linked insertion of in div'); + // ----------------------------------------------------------------------- + + // ................................ Act .................................. + $.view("#result", true).refresh(); + result = "" + (after === $("#result").text()); + $.view("#result", true).views._2.refresh(); + result += " " + (after === $("#result").text()); + $.view("#result", true).views._2.views[0].refresh(); + result += " " + (after === $("#result").text()); + $.view("#result", true).views._2.views[0].views._2.refresh(); + result += " " + (after === $("#result").text()); + $.view("#result", true).views._2.views[0].views._2.views._2.refresh(); + result += " " + (after === $("#result").text()); + $.view("#result", true).views._2.views[0].views._2.views._2.views[0].refresh(); + result += " " + (after === $("#result").text()); + + // ............................... Assert ................................. + equal(result, 'true true true true true true', + 'view refresh at all levels correctly maintains content'); + // ----------------------------------------------------------------------- + + // ................................ Reset ................................ + $("#result").empty(); + model.things = []; // reset Prop + + // =============================== Arrange =============================== + // ................................ Act .................................. + $.templates('{^{for things}}{^{if expanded}}{{/if}}{{/for}}
    {{:thing}}
    ') + .link("#result", model); + + $.observable(model.things).insert(0, [{ thing: "tree", expanded: false }]); + result = $._data(model.things[0]).events.propertyChange.length; + $.view("#result", true).views._1.views[0].refresh(); + result += "|" + $._data(model.things[0]).events.propertyChange.length; + $("#result").empty(); + result += "|" + $._data(model.things[0]).events; + + // ............................... Assert ................................. + equal(result, '1|1|undefined', + 'Refreshing a view containing a tag which is bound to dependant data, and has no _prv node, removes the original binding and replaces it with a new one'); + + // ................................ Reset ................................ + $("#result").empty(); + model.things = []; // reset Prop + + // =============================== Arrange =============================== + + // ................................ Act .................................. + $.templates('
    {^{for things}}{^{if expanded}}{{:thing}}{{/if}}{{/for}}
    ') + .link("#result", model); + + $.observable(model.things).insert(0, [{ thing: "tree", expanded: false }]); + result = $._data(model.things[0]).events.propertyChange.length; + $.view("#result", true).views._1.views[0].refresh(); + result += "|" + $._data(model.things[0]).events.propertyChange.length; + $("#result").empty(); + result += "|" + $._data(model.things[0]).events; + + // ............................... Assert ................................. + equal(result, '1|1|undefined', + 'Refreshing a view containing a tag which is bound to dependant data, and has no _prv node, removes the original binding and replaces it with a new one'); + + // ................................ Reset ................................ + $("#result").empty(); + model.things = []; // reset Prop + + // =============================== Arrange =============================== + + // ................................ Act .................................. + $.templates('
    {{if true}}{^{:things.length||""}}{{/if}}
    ') + .link("#result", model); + + before = $("#result div *").length; + $.view("#result div", true).refresh(); + after = $("#result div *").length; + // ............................... Assert ................................. + equal(after, before, + 'Refreshing a view containing non-elOnly content, with a data-bound tag with no rendered content removes the original script node markers for the tag and replace with the new ones'); + + // ................................ Reset ................................ + $("#result").empty(); + model.things = []; // reset Prop + + // =============================== Arrange =============================== + + // ................................ Act .................................. + $.templates("testTmpl", '{^{if expanded}}{{:thing}}{{/if}}'); + $.templates('{^{for things tmpl="testTmpl"/}}
    ') + .link("#result", model); + + result = $("#result td").text(); + $.observable(model.things).insert(0, [{ thing: "tree", expanded: false }, { thing: "bush", expanded: true }]); + result += "|" + $("#result td").text(); + $.observable(model.things[0]).setProperty("expanded", true); + $.observable(model.things[1]).setProperty("expanded", false); + result += "|" + $("#result td").text(); + + // ............................... Assert ................................. + equal(result, '|bush|tree', + 'Changing dependant data on bindings with deferred correctly triggers refreshTag and refreshes content with updated data binding'); + + // ................................ Act .................................. + $.view("#result tr").parent.refresh(); + result = $("#result td").text(); + $.view("#result tr").parent.parent.views[1].refresh(); + result += "|" + $("#result td").text(); + + // ............................... Assert ................................. + equal(result, 'tree|tree', + 'view refresh with deferred correctly refreshes content'); + + // ................................ Act .................................. + $.observable(model.things[1]).setProperty("expanded", true); + result = $("#result td").text(); + + $.observable(model.things[0]).setProperty("expanded", false); + result += "|" + $("#result td").text(); + + // ............................... Assert ................................. + equal(result, 'treebush|bush', + 'Changing dependant data on bindings with deferred, after view refresh correctly triggers refreshTag and refreshes content with updated data binding'); + // ----------------------------------------------------------------------- + + // ................................ Reset ................................ + $("#result").empty(); + model.things = []; // reset Prop + + // =============================== Arrange =============================== + + // ................................ Act .................................. + $.templates("testTmpl", '{^{if expanded}}{{:thing}}{{/if}}'); + $.templates('{^{for things tmpl="testTmpl"/}}
    ') + .link("#result", model); + + result = $("#result td").text(); + $.observable(model.things).insert(0, [{ thing: "tree", expanded: false }, { thing: "bush", expanded: true }]); + result += "|" + $("#result").text(); + $.observable(model.things[0]).setProperty("expanded", true); + $.observable(model.things[1]).setProperty("expanded", false); + result += "|" + $("#result").text(); + + // ............................... Assert ................................. + equal(result, '|bush|tree', + 'Changing dependant data on bindings with deferred correctly triggers refreshTag and refreshes content with updated data binding'); + + // ................................ Act .................................. + $.view("#result tr").refresh(); + result = $("#result").text(); + $.view("#result tr").parent.views[1].refresh(); + result += "|" + $("#result").text(); + + // ............................... Assert ................................. + equal(result, 'tree|tree', + 'view refresh with deferred correctly refreshes content'); + + // ................................ Act .................................. + $.observable(model.things[1]).setProperty("expanded", true); + result = $("#result").text(); + $.observable(model.things[0]).setProperty("expanded", false); + result += "|" + $("#result").text(); + + // ............................... Assert ................................. + equal(result, 'treebush|bush', + 'Changing dependant data on bindings with deferred, after view refresh correctly triggers refreshTag and refreshes content with updated data binding'); + // ----------------------------------------------------------------------- + + // ................................ Reset ................................ + $("#result").empty(); + model.things = []; // reset Prop + + // =============================== Arrange =============================== + + $.templates("
      {{for}}
    • Name: {{:firstName()}}. Width: {{:~settings.width}}
    • {{/for}}
    ") + .link("#result", person1, { settings: settings }); + + // ................................ Act .................................. + before = $("#result ul li").html(); // The innerHTML will be Name: Sir compFirst. Width: 40 + person1.fullName.set.call(person1, "compFirst compLast"); + settings.title = "Sir"; + settings.width = 40; + $.view("li").refresh(); + after = $("#result ul li").html(); + + // ............................... Assert ................................. + equal(before + "|" + after, + 'Name: Mr Jo. Width: 30|Name: Sir compFirst. Width: 40', + 'Calling view("li").refresh() for a view in element-only content (elCnt true) updates correctly: "
      {{for}}
    • ...
    • {{/for}}
    "'); + // ----------------------------------------------------------------------- + + // ................................ Reset ................................ + $("#result").empty(); + person1._firstName = "Jo"; // reset Prop + person1.lastName = "One"; // reset Prop + settings.title = "Mr"; // reset Prop + settings.width = 30; // reset Prop + + // =============================== Arrange =============================== + + var data = {}; + + $.templates("
      {^{for items}}
    • insertBefore
    • {{/for}}
    • next
    ") + .link("#result", data); + + // ................................ Act .................................. + before = $("#result ul").text(); // The innerHTML will be Name: Sir compFirst. Width: 40 + $.observable(data).setProperty("items", []); + var deferredString = $("#result ul li")[0]._df || ""; + $.observable(data.items).insert("X"); + after = $("#result ul").text(); + + // ............................... Assert ................................. + equal(before + "|" + deferredString + "|" + after, + (isIE8 ? 'next||insertBeforenext' + : ' next||insertBefore next'), + 'Inserting content before a next sibling element in element-only context does not set ._df, and subsequent insertion is correctly placed before the next sibling.'); + // ----------------------------------------------------------------------- + + // ................................ Reset ................................ + $("#result").empty(); + + // =============================== Arrange =============================== + + $.templates('{^{for things}}
    #index: #view.index: {{:thing}} Nested:{{for true}}{{for true}} #get(\'item\').index: #parent.parent.index:|{{/for}}{{/for}}
    {{/for}}') + .link("#result", model); + + // ................................ Act .................................. + $.observable(model.things).insert(0, { thing: "tree" }); + $.observable(model.things).insert(0, { thing: "bush" }); + + // ............................... Assert ................................. + equal($("#result").text(), "#index:0 #view.index:0 bush Nested: #get('item').index:0 #parent.parent.index:0|#index:1 #view.index:1 tree Nested: #get('item').index:1 #parent.parent.index:1|", + 'Data-link to "#index" and "#get(\'item\').index" work correctly'); + // ----------------------------------------------------------------------- + + // ................................ Reset ................................ + $("#result").empty(); + model.things = []; // reset Prop + + // =============================== Arrange =============================== + + $.templates('{^{for things}}
    {{:thing}} Nested:{{for}}{{for}}{{/for}}{{/for}}
    |{{/for}}') + .link("#result", model); + + // ................................ Act .................................. + $.observable(model.things).insert(0, { thing: "tree" }); + $.observable(model.things).insert(0, { thing: "bush" }); + + // ............................... Assert ................................. + equal($("#result").text(), "bush Nested:0|tree Nested:1|", + 'Data-link to "#getIndex()" works correctly'); + // ----------------------------------------------------------------------- + + // ................................ Reset ................................ + $("#result").empty(); + model.things = []; // reset Prop + + // =============================== Arrange =============================== + $.templates('
      {^{for things}}xxx{{/for}}
    ') + .link("#result", model); + + // ................................ Act .................................. + $("#result div").empty(); + + // ............................... Assert ................................. + + ok(viewsAndBindings().split(" ").length === 3 // We removed view inside div, but still have the view for the outer template. + && !$._data(model.things).events, + '$(container).empty removes listeners for empty tags in element-only content (_df="#n_/n_")'); + // ----------------------------------------------------------------------- + + // =============================== Arrange =============================== + data = { + list: [], + q: true + }; + + $.templates('
      {^{if q}}{^{for list}}
    • {{:#data}}
    • {{/for}}{{/if}}
    ') + .link("#result", data); + + // ................................ Act .................................. + $.observable(data).setProperty("q", false); + $.observable(data).setProperty("q", true); + $.observable(data.list).insert("added"); + + // ............................... Assert ................................. + ok(viewsAndBindings().split(" ").length === 9 // We removed view inside div, but still have the view for the outer template. + && $._data(data.list).events.arrayChange.length === 1 + && $("#result ul").text() === "added", + 'In element-only content, updateContent calls disposeTokens on _df inner bindings'); + + // ................................ Reset ................................ + $("#result").empty(); + // ----------------------------------------------------------------------- +}); + +test("{^{if}}...{{else}}...{{/if}}", function() { + + // =============================== Arrange =============================== + var data = { one: true, two: false, three: true }, + boundIfElseTmpl = $.templates( + '{^{if one pane=0}}' + + '{^{if two pane=0}}' + + '{^{if three pane=0}}ONE TWO THREE {{else}}ONE TWO notThree {{/if}}' + + '{{else}}ONE notTwo {^{if three}}THREE {{/if}}{^{if !three}}notThree {{/if}}{{/if}}' + + '{{else three pane=1}}' + + '{^{if two pane=0}}notOne TWO THREE{{else}}notOne notTwo THREE {{/if}}' + + '{{else}}' + + '{^{if two pane=0}}notOne TWO notThree {{else}}notOne TWO notThree {{/if}}' + + '{{/if}}'); + + // ................................ Act .................................. + boundIfElseTmpl.link("#result", data); + + // ............................... Assert ................................. + after = $("#result").text(); + equal(after, boundIfElseTmpl.render(data), + 'Bound if and else with link render the same as unbound, when using the JsRender render() method'); + + // ............................... Assert ................................. + equal(after, "ONE notTwo THREE ", + 'Bound if and else render correct blocks based on boolean expressions'); + + // ................................ Act .................................. + $.observable(data).setProperty({ one: false, two: false, three: true }); + after = $("#result").text(); + + // ............................... Assert ................................. + equal(after, isIE8 ? "notOne notTwo THREE " : "notOne notTwo THREE ", + 'Bound if and else render correct blocks based on boolean expressions'); + + // ................................ Act .................................. + $.observable(data).setProperty({ one: false, two: true, three: false }); + after = $("#result").text(); + + // ............................... Assert ................................. + equal(after, isIE8 ? "notOne TWO notThree " : "notOne TWO notThree ", + 'Bound if and else render correct blocks based on boolean expressions'); + + // ................................ Reset ................................ + $("#result").empty(); + + // =============================== Arrange =============================== + data = { expanded: true }; + var deepIfTmpl = $.templates( + '' + + '{^{if expanded}}' + + '' + + '{{/if}}' + + '' + + '
    DeepContent
    afterDeep
    '); + + // ................................ Act .................................. + deepIfTmpl.link("#result", data); + + $.observable(data).setProperty("expanded", false); + $.observable(data).setProperty("expanded", true); + + // ............................... Assert ................................. + after = $("#result").text(); + var deferredString = $("#result tr")[0]._df; // "/226_/322^" + // With deep version, the tokens for the {^{if}} binding had to be deferred - we test the format: + deferredString = /\/\d+\_\/\d+\^/.test(deferredString); + + equal(deferredString && after, 'DeepContentafterDeep', + 'With deep bound {^{if}} tag, there is deferred binding and binding behaves correctly after removing and inserting'); + + // ................................ Act .................................. + $.observable(data).setProperty("expanded", false); + + // ............................... Assert ................................. + after = $("#result").text(); + deferredString = $("#result tr")[0]._df; // "#322^/322^" + // With deep version, the tokens for the {^{if}} binding had to be deferred - we test the format: + deferredString = /#(\d+\^)\/\1/.test(deferredString); + + equal(deferredString && after, 'afterDeep', + 'With deep bound {^{if}} tag, there is deferred binding and binding behaves correctly after further remove'); + + // =============================== Arrange =============================== + var shallowIfTmpl = $.templates( + '' + + '{^{if expanded}}' + + '' + + '{{/if}}' + + '' + + '
    ShallowContent
    afterShallow
    '); + + // ................................ Act .................................. + shallowIfTmpl.link("#result", data); + + $.observable(data).setProperty("expanded", false); + $.observable(data).setProperty("expanded", true); + + // ............................... Assert ................................. + after = $("#result").text(); + deferredString = $("#result tr")[0]._df; // "" + // With shallow version, no deferred binding + equal(!deferredString && after, 'ShallowContentafterShallow', + 'With shallow bound {^{if}} tag, there is no deferred binding, and binding behaves correctly after removing and inserting'); + + // ................................ Act .................................. + $.observable(data).setProperty("expanded", false); + + // ............................... Assert ................................. + after = $("#result").text(); + deferredString = $("#result tr")[0]._df; // "" + // With shallow version, no deferred binding + + equal(!deferredString && after, 'afterShallow', + 'With shallow bound {^{if}} tag, there is no deferred binding and binding behaves correctly after further remove'); + + // ................................ Reset ................................ + $("#result").empty(); + result = ""; +}); + +test("{^{props}} basic", function() { + + // =============================== Arrange =============================== + var root = { + objA: { propA1: "valA1a" }, + objB: { propB1: "valB1a" } + }; + + $.templates('{^{props objA}}{^{:key}}:{^{:prop}},{{/props}}') + .link("#result", root); + + // ................................ Act .................................. + before = $("#result").text(); + $.observable(root.objA).setProperty({ propA1: "valA1b" }); + after = $("#result").text(); + + // ............................... Assert ................................. + equal(before + "|" + after, 'propA1:valA1a,|propA1:valA1b,', + '{^{props}} - set existing property'); + + // ................................ Act .................................. + before = $("#result").text(); + $.observable(root.objA).setProperty({ propA1: "valA1c", propA2: "valA2a" }); + after = $("#result").text(); + + // ............................... Assert ................................. + equal(before + "|" + after, 'propA1:valA1b,|propA1:valA1c,propA2:valA2a,', + '{^{props}} - set new property'); + + // ................................ Act .................................. + before = $("#result").text(); + $.observable(root.objA).setProperty({ propA1: "", propA2: null }); + after = $("#result").text(); + + // ............................... Assert ................................. + equal(before + "|" + after, 'propA1:valA1c,propA2:valA2a,|propA1:,propA2:,', + '{^{props}} - set property to empty string or null'); + + // ................................ Act .................................. + before = $("#result").text(); + $.observable(root.objA).setProperty({ propA1: null }); + after = $("#result").text(); + + // ............................... Assert ................................. + equal(before + "|" + after, 'propA1:,propA2:,|propA1:,propA2:,', + '{^{props}} - all properties null'); + + // ................................ Act .................................. + before = $("#result").text(); + $.observable(root.objA).removeProperty("propA1").removeProperty("propA2"); + after = $("#result").text(); + + // ............................... Assert ................................. + equal(before + "|" + after, 'propA1:,propA2:,|', + '{^{props}} - all properties removed'); + + // ................................ Act .................................. + before = $("#result").text(); + $.observable(root.objA).setProperty({ propA1: "valA1b" }); + after = $("#result").text(); + + // ............................... Assert ................................. + equal(before + "|" + after, "|propA1:valA1b,", + '{^{props}} - set property where there were none'); + + // ................................ Act .................................. + before = $("#result").text(); + $.observable(root).setProperty({ objA: {} }); + after = $("#result").text(); + + // ............................... Assert ................................. + equal(before + "|" + after, "propA1:valA1b,|", + '{^{props}} - set whole object to empty object'); + + // ................................ Act .................................. + before = $("#result").text(); + $.observable(root).setProperty({ objA: { propX: "XX" } }); + after = $("#result").text(); + + // ............................... Assert ................................. + equal(before + "|" + after, "|propX:XX,", + '{^{props}} - set whole object to different object'); + + //................................ Reset ................................ + $("#result").empty(); + + // ............................... Assert ................................. + equal(JSON.stringify($.views.sub._cbBnds), "{}", + "{^{props}} dataMap bindings all removed when tag disposed (content removed from DOM)"); +}); + +test("{^{props}} modifying content, through arrayChange/propertyChange on target array", function() { + + // =============================== Arrange =============================== + + var root = { + objA: { propA1: "valA1a" } + }; + + $.templates( + '{^{props objA}}' + + '{^{:key}}:{^{:prop}},' + + '' + + ',' + + ',' + + '' + + '' + + '{{/props}}') + + .link("#result", root, { + add: function(ev, eventArgs) { + var view = eventArgs.view, + arr = view.get("array").data; + $.observable(arr).insert({ key: "addkey", prop: "addprop" }); + }, + remove: function(ev, eventArgs) { + var view = eventArgs.view, + arr = view.get("array").data, + index = view.index; + $.observable(arr).remove(index); + }, + change: function(ev, eventArgs) { + var view = eventArgs.view, + item = view.data; + $.observable(item).setProperty({ key: "changed", prop: "changedValue" }); + } + }); + + // ................................ Act .................................. + before = $("#result").text(); + $(".addProp").click(); + after = $("#result").text(); + + // ............................... Assert ................................. + equal(before + "|" + after, "propA1:valA1a,removeadd,change,|propA1:valA1a,removeadd,change,addkey:addprop,removeadd,change,", + '{^{props}} - add properties to props target array'); + + // ................................ Act .................................. + before = $("#result").text(); + $(".removeProp:first()").click(); + after = $("#result").text(); + + // ............................... Assert ................................. + equal(before + "|" + after, "propA1:valA1a,removeadd,change,addkey:addprop,removeadd,change,|addkey:addprop,removeadd,change,", + '{^{props}} - remove properties from props target array'); + + // ................................ Act .................................. + before = $("#result").text(); + $(".changeProp").click(); + after = $("#result").text(); + + // ............................... Assert ................................. + equal(before + "|" + after, "addkey:addprop,removeadd,change,|changed:changedValue,removeadd,change,", + '{^{props}} - change value of key and prop in props target array'); + + // ................................ Act .................................. + before = $("#result").text(); + $(".changePropInput").val("newValue").change(); + after = $("#result").text(); + + // ............................... Assert ................................. + equal(before + "|" + after + "|" + JSON.stringify(root.objA), "changed:changedValue,removeadd,change,|changed:newValue,removeadd,change,|{\"changed\":\"newValue\"}", + '{^{props}} - change value of input bound to prop in props target array'); + + // ................................ Act .................................. + before = $("#result").text(); + $(".changeKeyInput").val("newKey").change(); + after = $("#result").text(); + + // ............................... Assert ................................. + equal(before + "|" + after + "|" + JSON.stringify(root.objA), "changed:newValue,removeadd,change,|newKey:newValue,removeadd,change,|{\"newKey\":\"newValue\"}", + '{^{props}} - change value of input bound to key in props target array'); + + // ................................ Reset ................................ + + before = "" + $._data(root).events.propertyChange.length + "-" + $._data(root.objA).events.propertyChange.length; + $("#result").empty(); + after = "" + ($._data(root).events === undefined) + "-" + ($._data(root.objA).events === undefined) + " -" + JSON.stringify($.views.sub._cbBnds); + + // ............................... Assert ................................. + equal(before + "|" + after, "1-1|true-true -{}", + '{^{props}} dataMap bindings all removed when tag disposed (content removed from DOM)'); +}); + +test("{^{props}}..{{else}} ...", function() { + + // =============================== Arrange =============================== + + var root = { + objA: { propA1: "valA1" }, + objB: { propB1: "valb1", propB2: "valb2" } + }; + + $.templates('{^{props objA}}{^{:key}}:{^{:prop}},' + + ',' + + '{{else objB}}{^{:key}}:{^{:prop}},' + + ',' + + '{{else}}' + + 'NONE' + + '{{/props}}') + + .link("#result", root, { + remove: function(ev, eventArgs) { + var view = eventArgs.view, + arr = view.get("array").data, + index = view.index; + $.observable(arr).remove(index); + } + }); + + // ................................ Act .................................. + before = $("#result").text(); + $(".removePropA").click(); + after = $("#result").text(); + + // ............................... Assert ................................. + equal(before + "|" + after, "propA1:valA1,remove,|propB1:valb1,remove,propB2:valb2,remove,", + '{^{props}} - remove properties from objA target array - switches to {{else objB}}'); + + // ................................ Act .................................. + before = $("#result").text(); + $(".removePropB").click(); + after = $("#result").text(); + + // ............................... Assert ................................. + equal(before + "|" + after, "propB1:valb1,remove,propB2:valb2,remove,|NONE", + '{^{props}} - remove properties from objB target array - switches to {{else}}'); + + // ................................ Reset ................................ + $("#result").empty(); + + // ............................... Assert ................................. + equal(JSON.stringify($.views.sub._cbBnds), "{}", + "{^{props}} dataMap bindings all removed when tag disposed (content removed from DOM)"); +}); + +test('data-link="{on ...', function() { + + // =============================== Arrange =============================== + + function swap(ev, eventArgs) { + $.observable(this).setProperty("type", this.type === "shape" ? "line" : "shape"); + } + var thing = { + type: "shape", + swap: swap + }; + + $.templates('
    {^{:type}}
    ') + .link("#result", thing); + + // ................................ Act .................................. + before = $("#result").text(); + $("#result div").click(); + after = $("#result").text(); + + // ............................... Assert ................................. + equal(before + "|" + after, + "shape|line", + '{on swap} calls swap method on click, with "this" pointer context on data object'); + // ----------------------------------------------------------------------- + + // ................................ Reset ................................ + $("#result").empty(); + thing.type = "shape"; + + // =============================== Arrange =============================== + + $.templates('
    {^{:type}}
    ') + .link("#result", thing, { swap: swap }); + + // ................................ Act .................................. + before = $("#result").text(); + $("#result div").click(); + after = $("#result").text(); + + // ............................... Assert ................................. + equal(before + "|" + after, + "shape|line", + '{on ~swap} calls swap helper method on click, with "this" pointer context defaulting to current data object'); + // ----------------------------------------------------------------------- + + // ................................ Reset ................................ + $("#result").empty(); + thing.type = "shape"; + + // =============================== Arrange =============================== + + $.templates('
    {^{:type}} {^{:check}}
    ') + .link("#result", thing, { + util: + { + swap: function(ev, eventArgs) { + $.observable(this.data).setProperty({ + type: this.data.type === "shape" ? "line" : "shape", + check: this.data === eventArgs.view.data + }); + }, + data: thing + } + }); + + // ................................ Act .................................. + before = $("#result").text(); + $("#result div").click(); + after = $("#result").text(); + + // ............................... Assert ................................. + equal(before + "|" + after, + isIE8 ? "shape |linetrue " + : "shape |line true", + '{on ~util.swap} calls util.swap helper method on click, with ~util as this pointer'); + // ----------------------------------------------------------------------- + + // ................................ Reset ................................ + $("#result").empty(); + thing.type = "shape"; + delete thing.check; + + // =============================== Arrange =============================== + + $.templates('
    {^{:type}}
    ') + .link("#result", thing, { + util: + { + swap: swap + } + }); + + // ................................ Act .................................. + before = $("#result").text(); + $("#result div").click(); + after = $("#result").text(); + + // ............................... Assert ................................. + equal(before + "|" + after, + "shape|line", + '{on ~util.swap context=#data} calls util.swap helper method on click, with current data object as this pointer'); + // ----------------------------------------------------------------------- + + // ................................ Reset ................................ + $("#result").empty(); + thing.type = "shape"; + + // =============================== Arrange =============================== + + $.templates('
    {^{:type}} {^{:check}}
    ') + .link("#result", thing, { + util: + { + swap: function(ev, eventArgs) { + $.observable(this.data).setProperty({ + type: this.data.type === "shape" ? "line" : "shape", + check: this.data === eventArgs.view.data + }); + }, + data: thing, + swapCtx: { + data: thing + } + } + }); + + // ................................ Act .................................. + before = $("#result").text(); + $("#result div").click(); + after = $("#result").text(); + + // ............................... Assert ................................. + equal(before + "|" + after, + isIE8 ? "shape |linetrue " + : "shape |line true", + '{on ~util.swap context=~util.swapCtx} calls util.swap helper method on click, with util.swapCtx as this pointer'); + // ----------------------------------------------------------------------- + + // ................................ Reset ................................ + $("#result").empty(); + thing.type = "shape"; + delete thing.check; + + // =============================== Arrange =============================== + + $.templates('
    {^{:type}} {^{:check}}
    ') + .link("#result", thing, { + util: + { + swap: function(ev, eventArgs) { + $.observable(ev.data).setProperty({ + type: ev.data.type === "shape" ? "line" : "shape", + check: ev.data === eventArgs.view.data + }); + }, + data: thing, + swapCtx: { + data: thing + } + } + }); + + // ................................ Act .................................. + before = $("#result").text(); + $("#result div").click(); + after = $("#result").text(); + + // ............................... Assert ................................. + equal(before + "|" + after, + isIE8 ? "shape |linetrue " + : "shape |line true", + '{on ~util.swap data=#data} calls util.swap helper method on click, and passes current data #data as ev.data'); + // ----------------------------------------------------------------------- + + // ................................ Reset ................................ + $("#result").empty(); + thing.type = "shape"; + delete thing.check; + + // =============================== Arrange =============================== + + $.templates('
    {^{:type}}
    ') + .link("#result", thing); + + // ................................ Act .................................. + before = $("#result").text(); + $("#result div").mouseup(); + after = $("#result").text(); + $("#result div").mousedown(); + after += $("#result").text(); + $("#result div").blur(); + after += $("#result").text(); + + // ............................... Assert ................................. + equal(before + "|" + after, + "shape|lineshapeline", + "{on 'mouseup mousedown blur' swap} calls util method on mouseup, mousedown and blur"); + // ----------------------------------------------------------------------- + + // ................................ Reset ................................ + $("#result").empty(); + thing.type = "shape"; + delete thing.check; + + // =============================== Arrange =============================== + var res = "1: "; + $.templates( + "
    " + + "" + + "" + + "
    ") + .link("#result", { + unbind: function(ev, eventArgs) { + res += "unbind "; + eventArgs.linkCtx.tag.onDispose(); + }, + refresh: function(ev, eventArgs) { + res += "refresh "; + eventArgs.linkCtx.tag.refresh(); + }, + test: function() { + res += "test "; + } + }); + + // ................................ Act .................................. + var events = $._data($("#divForOn")[0]).events, + eventBindings = "before: " + events.keydown.length + events.keyup.length + events.mouseup.length + events.mousedown.length; + + $("#divForOn #inputB").mouseup(); + + res += "2: "; + $("#divForOn .inputA").mouseup(); + + res += "3: "; + $("#divForOn #inputB").keyup(); + + res += "4: "; + $("#divForOn #inputB").keyup(); + + res += "5: "; + $("#divForOn .inputA").keydown(); + + res += "6: "; + $("#divForOn .inputA").keyup(); + + res += "7: "; + $("#divForOn #inputB").mouseup(); + + res += "8: "; + $("#divForOn #inputB").mousedown(); + + eventBindings += " | after: " + events.keydown + events.keyup + events.mouseup.length + events.mousedown.length; + // ............................... Assert ................................. + equal(res, + "1: test 2: test 3: unbind 4: 5: unbind 6: 7: test 8: refresh ", + "multiple {on events selector method} bindings on container attach events on delegated elements. Also, tag.onDispose on tag instances removes specific handlers for corresponding elements/selectors"); + + // ............................... Assert ................................. + equal(eventBindings, + "before: 1211 | after: undefinedundefined11", + "onDispose removes specific delegated events"); + + // ................................ Act .................................. + res = "1: "; + $("#divForOn").html(""); + + $("#divForOn #newlyAdded").mouseup(); + + res += "2: "; + $("#divForOn #newlyAdded").keyup(); + + // ............................... Assert ................................. + equal(res, + "1: test 2: ", + "delegated {on events selector method} binding allows additional elements added to content to bind correctly"); + + // ................................ Act .................................. + $("#result").empty(); + eventBindings = "" + events.keydown + events.keyup + events.mouseup + JSON.stringify([$.views.sub._cbBnds, _jsv.bindings]); + + // ............................... Assert ................................. + equal(eventBindings, + "undefinedundefinedundefined[{},{}]", + "Removing the element removes all associated attached {on } handlers"); + + // =============================== Arrange =============================== + var tmpl = $.templates("
    \ + \ + \ +
    "), + + data = { + name: "Jo", + role: "Advisor", + option: { + allow: true + }, + thisIsTheMethod: function(role, text, isFoo, compile, amount, root, ev, eventArgs) { + if (compile) { + compile.call(root, role, text, isFoo, amount, ev.data.allow, eventArgs.linkCtx.tag.tagCtx.args[2]); + } + }, + process: function(role, text, isFoo, amount, allow, extraParam) { + $.observable(this).setProperty("res", this.res + role + text + isFoo + amount + " allow:" + allow + " extraParam: " + extraParam + "|"); + }, + res: "" + }; + + tmpl.link("#result", data); + + // ................................ Act .................................. + $("#doIt").click(); + data.option.allow = false; + $.observable(data).setProperty("role", "Follower"); + $("#doIt").click(); + + // ............................... Assert ................................. + equal(data.res, "Advisorheytrue33 allow:true extraParam: 754|Followerheytrue33 allow:false extraParam: 754|", + "{on 'click' selector otherParams... method params...} : supports passing params to method, of any type, as well as setting data and context for the function call"); + + // =============================== Arrange =============================== + res = "1: "; + $("#result").html("
    " + + "" + + "" + + "
    "); + + $.link(true, "#result", { + unbind: function(ev, eventArgs) { + res += "unbind "; + eventArgs.linkCtx.tag.onDispose(); + }, + refresh: function(ev, eventArgs) { + res += "refresh "; + eventArgs.linkCtx.tag.refresh(); + }, + test: function() { + res += "test "; + } + }); + + // ................................ Act .................................. + events = $._data($("#divForOn")[0]).events; + eventBindings = "before: " + events.keydown.length + events.keyup.length + events.mouseup.length + events.mousedown.length; + + $("#divForOn #inputB").mouseup(); + + res += "2: "; + $("#divForOn .inputA").mouseup(); + + res += "3: "; + $("#divForOn #inputB").keyup(); + + res += "4: "; + $("#divForOn #inputB").keyup(); + + res += "5: "; + $("#divForOn .inputA").keydown(); + + res += "6: "; + $("#divForOn .inputA").keyup(); + + res += "7: "; + $("#divForOn #inputB").mouseup(); + + res += "8: "; + $("#divForOn #inputB").mousedown(); + + eventBindings += " | after: " + events.keydown + events.keyup + events.mouseup.length + events.mousedown.length; + // ............................... Assert ................................. + equal(res, + "1: test 2: test 3: unbind 4: 5: unbind 6: 7: test 8: refresh ", + "Top-level {on }: multiple {on events selector method} top-level bindings on container attach events on delegated elements. Also, tag.onDispose on tag instances removes specific handlers for corresponding elements/selectors"); + + // ............................... Assert ................................. + equal(eventBindings, + "before: 1211 | after: undefinedundefined11", + "Top-level {on }: onDispose removes specific delegated events"); + + // ................................ Act .................................. + res = "1: "; + $("#divForOn").html(""); + + $("#divForOn #newlyAdded").mouseup(); + + res += "2: "; + $("#divForOn #newlyAdded").keyup(); + + // ............................... Assert ................................. + equal(res, + "1: test 2: ", + "Top-level {on }: delegated {on events selector method} binding allows additional elements added to content to bind correctly"); + + // ................................ Act .................................. + $("#result").empty(); + eventBindings = "" + events.keydown + events.keyup + events.mouseup + JSON.stringify([$.views.sub._cbBnds, _jsv.bindings]); + + // ............................... Assert ................................. + equal(eventBindings, + "undefinedundefinedundefined[{},{}]", + "Top-level {on }: Removing the element removes all associated attached {on } handlers"); + + // =============================== Arrange =============================== + $("#result").html("
    \ + \ + \ +
    "); + + data = { + name: "Jo", + role: "Advisor", + option: { + allow: true + }, + thisIsTheMethod: function(role, text, isFoo, compile, amount, root, ev, eventArgs) { + if (compile) { + compile.call(root, role, text, isFoo, amount, ev.data.allow, eventArgs.linkCtx.tag.tagCtx.args[2]); + } + }, + process: function(role, text, isFoo, amount, allow, extraParam) { + $.observable(this).setProperty("res", this.res + role + text + isFoo + amount + " allow:" + allow + " extraParam: " + extraParam + "|"); + }, + res: "" + }; + + $.link(true, "#result", data); + + // ................................ Act .................................. + $("#doIt").click(); + data.option.allow = false; + $.observable(data).setProperty("role", "Follower"); + $("#doIt").click(); + + // ............................... Assert ................................. + equal(data.res, "Advisorheytrue33 allow:true extraParam: 754|Followerheytrue33 allow:false extraParam: 754|", + "Top-level {on 'click' selector method params...} : supports passing params to method, of any type, as well as setting data and context for the function call"); + + // =============================== Arrange =============================== + res = "1: "; + data = { + unbind: function(ev, eventArgs) { + res += "unbind "; + eventArgs.linkCtx.tag.onDispose(); + }, + refresh: function(ev, eventArgs) { + res += "refresh "; + eventArgs.linkCtx.tag.refresh(); + }, + test: function() { + res += "test "; + } + }; + $("#result").html("
    oldcontent
    "); + + $.link(true, "#linkTgt", data); + + events = $._data($("#linkTgt")[0]).events, + + // ................................ Act .................................. + $("#linkTgt").mousedown(); + + res += "2: "; + $("#linkTgt").mouseup(); + + res += "3: "; + $("#linkTgt").click(); + + res += "4: "; + $("#linkTgt").mousedown(); + + res += "5: "; + $("#linkTgt").mouseup(); + + res += "6: "; + $("#linkTgt").click(); + + // ............................... Assert ................................. + equal(res, + "1: test 2: test 3: refresh 4: test 5: test 6: refresh ", + '$.link(true, "#linkTgt", data): top-level linking to element (not container) links correctly, including \'{on }\' bindings'); + + // ............................... Assert ................................. + eventBindings = "" + events.mouseup.length + events.mousedown.length + events.click.length; + + equal(eventBindings, + "111", + '$.link(true, "#linkTgt", data): top-level linking to element (not container) adds {on } binding handlers correctly - including calling refresh() on {on } tag'); + + // ................................ Act .................................. + $.unlink(true, "#linkTgt"); + + // ............................... Assert ................................. + eventBindings = "" + events.mouseup + events.mousedown + events.click + JSON.stringify([$.views.sub._cbBnds, _jsv.bindings]); + + equal(eventBindings, + "undefinedundefinedundefined[{},{}]", + '$.unlink(true, "#linkTgt"): directly on top-level data-linked element (not through container) removes all \'{on }\' handlers'); +}); + +test('data-link="{tag...} and {^{tag}} in same template"', function() { + + // =============================== Arrange =============================== + + $.templates('{^{tmplTag/}}-{^{:lastName}} -') + .link("#result", person1); + + // ................................ Act .................................. + before = $("#result").text() + $("#result input").val(); + $.observable(person1).setProperty({ firstName: "newFirst", lastName: "newLast" }); + $.observable(settings).setProperty({ title: "Sir", width: 40 }); + after = $("#result").text() + $("#result input").val(); + + // ............................... Assert ................................. + equal(before + "|" + after, + 'Name: Mr Jo. Width: 30-One Name: Mr Jo. Width: 30-OneOne|Name: Sir newFirst. Width: 40-newLast Name: Sir newFirst. Width: 40-newLastnewLast' +, + 'Data link using: {^{tmplTag/}} {^{:lastName}} '); + // ----------------------------------------------------------------------- + + // ................................ Reset ................................ + $("#result").empty(); + person1._firstName = "Jo"; // reset Prop + person1.lastName = "One"; // reset Prop + settings.title = "Mr"; // reset Prop + settings.width = 30; // reset Prop + + // =============================== Arrange =============================== + + $.templates('prop:{^{:lastName}} computed:{^{:fullName()}} Tag:{^{tmplTag/}}') + .link("#result", person1); + + // ................................ Act .................................. + before = $("#result").text() + $("#last").val() + $("#full").val(); + $.observable(person1).setProperty({ firstName: "newFirst", lastName: "newLast" }); + $.observable(settings).setProperty({ title: "Sir", width: 40 }); + $.observable(person1).setProperty({ fullName: "compFirst compLast" }); + after = $("#result").text() + $("#last").val() + $("#full").val(); + + // ............................... Assert ................................. + equal(before + "|" + after, + 'prop:OneOne computed:Mr Jo OneMr Jo One Tag:Name: Mr Jo. Width: 30Name: Mr Jo. Width: 30OneMr Jo One|prop:compLastcompLast computed:Sir compFirst compLastSir compFirst compLast Tag:Name: Sir compFirst. Width: 40Name: Sir compFirst. Width: 40compLastSir compFirst compLast', 'Data link using: {^{:lastName}} {^{:fullName()}} {^{tmplTag/}} '); // ----------------------------------------------------------------------- @@ -5401,9 +8359,9 @@ test('data-link="{tag...} and {^{tag}} in same template"', function() { // ................................ Act .................................. before = $("#result").text() + $("#last").val() + $("#full").val(); - $.observable(person1).setProperty({firstName: "newFirst", lastName: "newLast"}); - $.observable(settings).setProperty({ title: "Sir", width: 40}); - $.observable(person1).setProperty({fullName: "compFirst compLast"}); + $.observable(person1).setProperty({ firstName: "newFirst", lastName: "newLast" }); + $.observable(settings).setProperty({ title: "Sir", width: 40 }); + $.observable(person1).setProperty({ fullName: "compFirst compLast" }); after = $("#result").text() + $("#last").val() + $("#full").val(); // ............................... Assert ................................. @@ -5445,9 +8403,9 @@ test('data-link="{tag...} and {^{tag}} in same template"', function() { // ................................ Act .................................. before = $("#result").text() + $("#last").val() + $("#full").val(); - $.observable(person1).setProperty({firstName: "newFirst", lastName: "newLast"}); - $.observable(settings).setProperty({ title: "Sir", width: 40}); - $.observable(person1).setProperty({fullName: "compFirst compLast"}); + $.observable(person1).setProperty({ firstName: "newFirst", lastName: "newLast" }); + $.observable(settings).setProperty({ title: "Sir", width: 40 }); + $.observable(person1).setProperty({ fullName: "compFirst compLast" }); after = $("#result").text() + $("#last").val() + $("#full").val(); // ............................... Assert ................................. @@ -5477,9 +8435,9 @@ test('data-link="{tag...} and {^{tag}} in same template"', function() { // ................................ Act .................................. before = $("#result").text() + $("#last").val() + $("#full").val(); - $.observable(person1).setProperty({firstName: "newFirst", lastName: "newLast"}); - $.observable(settings).setProperty({ title: "Sir", width: 40}); - $.observable(person1).setProperty({fullName: "compFirst compLast"}); + $.observable(person1).setProperty({ firstName: "newFirst", lastName: "newLast" }); + $.observable(settings).setProperty({ title: "Sir", width: 40 }); + $.observable(person1).setProperty({ fullName: "compFirst compLast" }); after = $("#result").text() + $("#last").val() + $("#full").val(); // ............................... Assert ................................. @@ -5500,11 +8458,12 @@ test('data-link="{tag...} and {^{tag}} in same template"', function() { settings.title = "Mr"; // reset Prop settings.width = 30; // reset Prop -// TODO ADDITIONAL TESTS: -// 1: link(null, data) to link whole document + // TODO ADDITIONAL TESTS: + // 1: link(null, data) to link whole document }); test("Fallbacks for missing or undefined paths: using {^{:some.path onError = 'fallback'}}, etc.", function() { + // =============================== Arrange =============================== $.views.tags({ @@ -5518,8 +8477,8 @@ test("Fallbacks for missing or undefined paths: using {^{:some.path onError = 'f } }); - var initial = {a: { b: null }}, - updated = {c: { val: 'leaf' }}; + var initial = { a: { b: null } }, + updated = { c: { val: 'leaf' } }; $.templates( "{^{:a.b^c.val onError=~error + 'A '}} " @@ -5533,7 +8492,7 @@ test("Fallbacks for missing or undefined paths: using {^{:some.path onError = 'f + " " + " " + " ") - .link("#result", initial, {error: "err:"}); + .link("#result", initial, { error: "err:" }); // ................................ Act .................................. before = "" + $._data(initial.a).events.propertyChange.length + " " + !$._data(updated).events + " " + !$._data(updated.c).events + "|" @@ -5542,9 +8501,9 @@ test("Fallbacks for missing or undefined paths: using {^{:some.path onError = 'f after = $("#result").text() + "|"; $.observable(initial.a.b.c).setProperty('val', "leaf2"); after += $("#result").text() + "|"; - $.observable(initial.a.b).setProperty('c', {val: "leaf3"}); + $.observable(initial.a.b).setProperty('c', { val: "leaf3" }); after += $("#result").text() + "|"; - $.observable(initial.a).setProperty('b', {c: {val: "leaf4"}}); + $.observable(initial.a).setProperty('b', { c: { val: "leaf4" } }); after += $("#result").text() + "|"; after += "" + $._data(initial.a).events.propertyChange.length + " " + $._data(initial.a.b).events.propertyChange.length + " " + $._data(initial.a.b.c).events.propertyChange.length + " " + !$._data(updated).events + " " + !$._data(updated.c).events + "|" @@ -5616,12 +8575,12 @@ test("Fallbacks for missing or undefined paths: using {^{:some.path onError = 'f $.templates( "{^{:value() onError='error1'}} {^{:title}} " - + "{^{:value().value() onError='error2'}} {^{:value().title onError='error2b'}} " - + "{^{:value().value().value() onError='error3'}} {^{:value().value().title onError='error3b'}} " - + "{^{:value().value().value().value() onError='error4'}} {^{:value().value().value().title onError='error4b'}} " + + "{^{:value()^value() onError='error2'}} {^{:value()^title onError='error2b'}} " + + "{^{:value()^value().value() onError='error3'}} {^{:value()^value().title onError='error3b'}} " + + "{^{:value()^value().value().value() onError='error4'}} {^{:value()^value().value().title onError='error4b'}} " ).link("#result", initial); // ................................ Act .................................. -var B,C,D,a,b,c,d,e; + var B, C, D, a, b, c, d, e; before = $("#result").text() + "|"; $.observable(initial).setProperty('value', B = new Item("string2", "B")); @@ -5632,7 +8591,7 @@ var B,C,D,a,b,c,d,e; after += $("#result").text() + "|"; $.observable(initial).removeProperty('value'); after += $("#result").text() + "|"; - $.observable(initial).setProperty('value', a = new Item( b = new Item( c = new Item( d = new Item( e = new Item("string4", "e"), "d"), "c"), "b"), "a")); + $.observable(initial).setProperty('value', a = new Item(b = new Item(c = new Item(d = new Item(e = new Item("string4", "e"), "d"), "c"), "b"), "a")); after += $("#result").text() + "|"; equal(before + after, @@ -5682,13 +8641,14 @@ test('Bound tag properties and contextual properties', function() { typeTemplates: { shape: "Shape: {^{:form}}\n", line: "Line: {^{:form}} {^{:thickness}}\n" - }} + } + } ); // ................................ Act .................................. before = $("#result").text(); - $.observable(things[0]).setProperty({type: "line", thickness: 5}); - $.observable(things[1]).setProperty({type: "shape"}); + $.observable(things[0]).setProperty({ type: "line", thickness: 5 }); + $.observable(things[1]).setProperty({ type: "shape" }); $.observable(things[1]).removeProperty("thickness"); after = $("#result").text(); @@ -5721,8 +8681,8 @@ test('Bound tag properties and contextual properties', function() { // ................................ Act .................................. before = $("#result").text(); - $.observable(things[0]).setProperty({type: "line", thickness: 5}); - $.observable(things[1]).setProperty({type: "shape"}); + $.observable(things[0]).setProperty({ type: "line", thickness: 5 }); + $.observable(things[1]).setProperty({ type: "shape" }); $.observable(things[1]).removeProperty("thickness"); after = $("#result").text(); @@ -5765,8 +8725,8 @@ test('Bound tag properties and contextual properties', function() { // ................................ Act .................................. before = $("#result").text(); - $.observable(things[0]).setProperty({type: "line", thickness: 5}); - $.observable(things[1]).setProperty({type: "shape"}); + $.observable(things[0]).setProperty({ type: "line", thickness: 5 }); + $.observable(things[1]).setProperty({ type: "shape" }); $.observable(things[1]).removeProperty("thickness"); after = $("#result").text(); @@ -5787,12 +8747,12 @@ test('Bound tag properties and contextual properties', function() { }); var model = { - lead: "Jim", - people: [ - {name: "Bob"}, - {name: "Jim"} - ] - }; + lead: "Jim", + people: [ + { name: "Bob" }, + { name: "Jim" } + ] + }; // ............................... Act ................................. $.templates("
    ").link("#result", model); @@ -5800,8 +8760,8 @@ test('Bound tag properties and contextual properties', function() { result = $("#result").text(); $.observable(model.people).insert({ - name: "newName" - }); + name: "newName" + }); $.observable(model).setProperty("lead", "newName"); @@ -5809,7 +8769,7 @@ test('Bound tag properties and contextual properties', function() { // ............................... Assert ................................. equal(result, (isIE8 ? "Bob lead:Jim - Jim lead:Jim - |Bob lead:newName - Jim lead:newName -newName lead:newName - " - :"Bob lead:Jim - Jim lead:Jim - |Bob lead:newName - Jim lead:newName - newName lead:newName - "), + : "Bob lead:Jim - Jim lead:Jim - |Bob lead:newName - Jim lead:newName - newName lead:newName - "), "data-link allows passing in new contextual variables to template: data-link=\"{for people ~team=#data tmpl=..."); // ................................ Reset ................................ @@ -5882,6 +8842,7 @@ test("PropertyChange: setProperty()", 4, function() { module("API - ArrayChange"); test("JsObservable: insert()", function() { + // =============================== Arrange =============================== var things = ["1", "2"]; @@ -5922,7 +8883,7 @@ test("JsObservable: insert()", function() { $.observable(things).insert("a"); // ............................... Assert ................................. - equal(things.join(" "), "1 2 a" , + equal(things.join(" "), "1 2 a", 'insert("a") appends'); // ----------------------------------------------------------------------- @@ -6105,6 +9066,7 @@ test("JsObservable: insert()", function() { }); test("JsObservable: remove()", function() { + // =============================== Arrange =============================== var things = ["1", "2"]; @@ -6145,7 +9107,7 @@ test("JsObservable: remove()", function() { $.observable(things).remove(); // ............................... Assert ................................. - equal(things.join(" "), "1" , + equal(things.join(" "), "1", 'remove() removes from end'); // ----------------------------------------------------------------------- @@ -6251,6 +9213,7 @@ test("JsObservable: remove()", function() { }); test("JsObservable: move()", function() { + // =============================== Arrange =============================== var things = ["1", "2", "3", "4"]; @@ -6562,6 +9525,7 @@ test("JsObservable: move()", function() { }); test("JsViews ArrayChange: insert()", function() { + // =============================== Arrange =============================== $.views.tags({ liTag: function() { @@ -6569,15 +9533,15 @@ test("JsViews ArrayChange: insert()", function() { } }); - model.things = [{thing: "Orig"}]; // reset Prop + model.things = [{ thing: "Orig" }]; // reset Prop $.templates('
      {^{liTag/}}{^{for things}}
    • {{:thing}}
    • {^{liTag/}}{{/for}}
    • |after
    ') .link("#result", model); // ................................ Act .................................. - $.observable(model.things).insert(0, {thing: "First"}); - $.observable(model.things).insert(1, {thing: "Last"}); - $.observable(model.things).insert(1, {thing: "Middle"}); + $.observable(model.things).insert(0, { thing: "First" }); + $.observable(model.things).insert(1, { thing: "Last" }); + $.observable(model.things).insert(1, { thing: "Middle" }); // ............................... Assert ................................. equal($("#result").text(), "TagFirstTagMiddleTagLastTagOrigTag|after", @@ -6595,15 +9559,15 @@ test("JsViews ArrayChange: insert()", function() { } }); - model.things = [{thing: "Orig"}]; // reset Prop + model.things = [{ thing: "Orig" }]; // reset Prop $.templates('
    {^{spanTag/}}{^{for things}}{{:thing}}{^{spanTag/}}{{/for}}|after
    ') .link("#result", model); // ................................ Act .................................. - $.observable(model.things).insert(0, {thing: "First"}); - $.observable(model.things).insert(1, {thing: "Last"}); - $.observable(model.things).insert(1, {thing: "Middle"}); + $.observable(model.things).insert(0, { thing: "First" }); + $.observable(model.things).insert(1, { thing: "Last" }); + $.observable(model.things).insert(1, { thing: "Middle" }); // ............................... Assert ................................. equal($("#result").text(), "TagFirstTagMiddleTagLastTagOrigTag|after", @@ -6612,13 +9576,13 @@ test("JsViews ArrayChange: insert()", function() { // ................................ Reset ................................ $("#result").empty(); - model.things = [{thing: "Orig"}]; // reset Prop + model.things = [{ thing: "Orig" }]; // reset Prop // =============================== Arrange =============================== $.templates('{^{for things}}{{/for}}
    {{:thing}}
    ') .link("#result", model); // ................................ Act .................................. - $.observable(model.things).insert(0, {thing: "First"}); + $.observable(model.things).insert(0, { thing: "First" }); $.observable(model.things).remove(0); // ............................... Assert ................................. @@ -6647,6 +9611,7 @@ test("JsViews ArrayChange: refresh()", function() { module("API - $.observe()"); test("observe/unobserve alternative signatures", function() { + reset(); // =============================== Arrange =============================== // ................................ Act .................................. @@ -6681,7 +9646,7 @@ test("observe/unobserve alternative signatures", function() { // ................................ Act .................................. $.observe(person1.home.address, "street", "ZIP", myListener); - $.observable(person1.home.address).setProperty({street: "newValue", ZIP: "newZip"}); + $.observable(person1.home.address).setProperty({ street: "newValue", ZIP: "newZip" }); // ............................... Assert ................................. equal(result, "calls: 1, ev.data: prop: street, eventArgs: oldValue: StreetOne value: newValue, eventArgs.path: street|" @@ -6698,7 +9663,7 @@ test("observe/unobserve alternative signatures", function() { // ................................ Act .................................. $.unobserve(person1.home.address, "street", "ZIP", myListener); - $.observable(person1.home.address).setProperty({street: "newValue", ZIP: "newZip"}); + $.observable(person1.home.address).setProperty({ street: "newValue", ZIP: "newZip" }); // ............................... Assert ................................. equal(result, "", @@ -6711,7 +9676,7 @@ test("observe/unobserve alternative signatures", function() { reset(); // =============================== Arrange =============================== - var person = {last: " L"}; + var person = { last: " L" }; function onch(ev, eventArgs) { } @@ -6740,7 +9705,7 @@ test("observe/unobserve alternative signatures", function() { "observe/unobserve API calls in different orders: all bindings removed when unobserve called"); // =============================== Arrange =============================== - person = {first: "F", last: " L"}; + person = { first: "F", last: " L" }; // ................................ Act .................................. $.observe(person, "last", onch); @@ -6757,7 +9722,7 @@ test("observe/unobserve alternative signatures", function() { "observe/unobserve API calls in different orders (version 2): all bindings removed when unobserve called"); // =============================== Arrange =============================== - person = {first: "F", last: " L"}; + person = { first: "F", last: " L" }; // ................................ Act .................................. $.observe(person, "last", onch); @@ -6781,7 +9746,7 @@ test("observe/unobserve alternative signatures", function() { address: { street: "1st Ave" }, - phones: [{number: "111 111 1111"}, {number:"222 222 2222"}] + phones: [{ number: "111 111 1111" }, { number: "222 222 2222" }] }; // ................................ Act .................................. @@ -6789,7 +9754,7 @@ test("observe/unobserve alternative signatures", function() { $.unobserve(person, "name", myListener); // ............................... Assert ................................. - equal(JSON.stringify([$.views.sub._cbBnds, $._data(person).events]),"[{},null]", + equal(JSON.stringify([$.views.sub._cbBnds, $._data(person).events]), "[{},null]", "unobserve with path and handler works"); // ................................ Act .................................. @@ -6797,7 +9762,7 @@ test("observe/unobserve alternative signatures", function() { $.unobserve(person); // ............................... Assert ................................. - equal(JSON.stringify([$.views.sub._cbBnds, $._data(person).events]),"[{},null]", + equal(JSON.stringify([$.views.sub._cbBnds, $._data(person).events]), "[{},null]", "unobserve without path and handler works"); // ................................ Act .................................. @@ -6805,7 +9770,7 @@ test("observe/unobserve alternative signatures", function() { $.unobserve(person, "name", "address^street", "phones", myListener); // ............................... Assert ................................. - equal(JSON.stringify([$.views.sub._cbBnds, $._data(person).events, $._data(person.address).events, $._data(person.phones).events]),"[{},null,null,null]", + equal(JSON.stringify([$.views.sub._cbBnds, $._data(person).events, $._data(person.address).events, $._data(person.phones).events]), "[{},null,null,null]", "unobserve with multiple paths and handler works"); // ................................ Act .................................. @@ -6813,7 +9778,7 @@ test("observe/unobserve alternative signatures", function() { $.unobserve(person.phones); // ............................... Assert ................................. - equal(JSON.stringify([$.views.sub._cbBnds, $._data(person.phones).events]),"[{},null]", + equal(JSON.stringify([$.views.sub._cbBnds, $._data(person.phones).events]), "[{},null]", "unobserve for array works"); // ................................ Act .................................. @@ -6821,12 +9786,13 @@ test("observe/unobserve alternative signatures", function() { $.unobserve(person, "*", person.address, "*"); // ............................... Assert ................................. - equal(JSON.stringify([$.views.sub._cbBnds, $._data(person).events]),"[{},null]", + equal(JSON.stringify([$.views.sub._cbBnds, $._data(person).events]), "[{},null]", "unobserve with deep paths using '*' works"); }); test("observe/unobserve using namespaces", function() { - var thing = {val: "initVal"}; + + var thing = { val: "initVal" }; // =============================== Arrange =============================== // ................................ Act .................................. $.observe("my.nmspace", thing, "val", myListener); @@ -6986,7 +9952,7 @@ test("observe/unobserve using namespaces", function() { address: { street: "1st Ave" }, - phones: [{number: "111 111 1111"}, {number:"222 222 2222"}] + phones: [{ number: "111 111 1111" }, { number: "222 222 2222" }] }; // ................................ Act .................................. @@ -6994,7 +9960,7 @@ test("observe/unobserve using namespaces", function() { $.unobserve("ns", person, "name", myListener); // ............................... Assert ................................. - equal(JSON.stringify([$.views.sub._cbBnds, $._data(person).events]),"[{},null]", + equal(JSON.stringify([$.views.sub._cbBnds, $._data(person).events]), "[{},null]", "unobserve using namespaces, with path and handler works"); // ................................ Act .................................. @@ -7002,7 +9968,7 @@ test("observe/unobserve using namespaces", function() { $.unobserve("ns", person); // ............................... Assert ................................. - equal(JSON.stringify([$.views.sub._cbBnds, $._data(person).events]),"[{},null]", + equal(JSON.stringify([$.views.sub._cbBnds, $._data(person).events]), "[{},null]", "unobserve using namespaces, without path and handler works"); // ................................ Act .................................. @@ -7010,7 +9976,7 @@ test("observe/unobserve using namespaces", function() { $.unobserve("ns", person, "name", "address^street", "phones", myListener); // ............................... Assert ................................. - equal(JSON.stringify([$.views.sub._cbBnds, $._data(person).events, $._data(person.address).events, $._data(person.phones).events]),"[{},null,null,null]", + equal(JSON.stringify([$.views.sub._cbBnds, $._data(person).events, $._data(person.address).events, $._data(person.phones).events]), "[{},null,null,null]", "unobserve using namespaces, with multiple paths and handler works"); // ................................ Act .................................. @@ -7018,7 +9984,7 @@ test("observe/unobserve using namespaces", function() { $.unobserve("ns", person.phones); // ............................... Assert ................................. - equal(JSON.stringify([$.views.sub._cbBnds, $._data(person.phones).events]),"[{},null]", + equal(JSON.stringify([$.views.sub._cbBnds, $._data(person.phones).events]), "[{},null]", "unobserve using namespaces, for array works"); // ................................ Act .................................. @@ -7026,18 +9992,19 @@ test("observe/unobserve using namespaces", function() { $.unobserve("ns", person, "*", person.address, "*"); // ............................... Assert ................................. - equal(JSON.stringify([$.views.sub._cbBnds, $._data(person).events]),"[{},null]", + equal(JSON.stringify([$.views.sub._cbBnds, $._data(person).events]), "[{},null]", "unobserve using namespaces, with deep paths using '*' works"); }); -test("paths", function() { +test("Paths", function() { // =============================== Arrange =============================== + var originalAddress = person1.home.address; // ................................ Act .................................. $.observe(person1, "home^address.street", person1.home.address, "ZIP", myListener); - $.observable(person1.home.address).setProperty({street: "newValue", ZIP: "newZip"}); + $.observable(person1.home.address).setProperty({ street: "newValue", ZIP: "newZip" }); // ............................... Assert ................................. ok(result === "calls: 1, ev.data: prop: street, eventArgs: oldValue: StreetOne value: newValue, eventArgs.path: street|" @@ -7049,7 +10016,7 @@ test("paths", function() { reset(); // ................................ Act .................................. - $.observable(person1).setProperty({home: home2}); // Swap object higher in path + $.observable(person1).setProperty({ home: home2 }); // Swap object higher in path // ............................... Assert ................................. equal("" + (lastEventArgs.oldValue === home1) + (lastEventArgs.value === home2) + result, "truetruecalls: 1, ev.data: prop: home, path: address^street," @@ -7061,7 +10028,7 @@ test("paths", function() { reset(); // ................................ Act .................................. - $.observable(address1).setProperty({street: "newValue2", ZIP: "newZip2"}); + $.observable(address1).setProperty({ street: "newValue2", ZIP: "newZip2" }); // ............................... Assert ................................. equal(result, "calls: 1, ev.data: prop: ZIP, eventArgs: oldValue: newZip value: newZip2, eventArgs.path: ZIP|", @@ -7069,11 +10036,11 @@ test("paths", function() { // ----------------------------------------------------------------------- // ................................ Reset ................................ - $.observable(address1).setProperty({street: "Street1", ZIP: "111"}); // reset Prop + $.observable(address1).setProperty({ street: "Street1", ZIP: "111" }); // reset Prop reset(); // ................................ Act .................................. - $.observable(address2).setProperty({street: "newValue2", ZIP: "newZip2"}); + $.observable(address2).setProperty({ street: "newValue2", ZIP: "newZip2" }); // ............................... Assert ................................. equal(result, "calls: 1, ev.data: prop: street, eventArgs: oldValue: StreetTwo value: newValue2, eventArgs.path: street|", @@ -7085,8 +10052,8 @@ test("paths", function() { $.observable(person1).setProperty("home", home1); // Set object higher up to different object reset(); - $.observable(address2).setProperty({street: "newValue2", ZIP: "newZip2"}); - $.observable(address1).setProperty({street: "newValue3", ZIP: "newZip3"}); + $.observable(address2).setProperty({ street: "newValue2", ZIP: "newZip2" }); + $.observable(address1).setProperty({ street: "newValue3", ZIP: "newZip3" }); // ............................... Assert ................................. @@ -7100,12 +10067,12 @@ test("paths", function() { // ................................ Act .................................. $.observable(person1).setProperty("home", home2); // Set object higher up to different object - $.observable(home1).setProperty("address", {street: "ignoreThisStreet", ZIP: "ignoreZip"}); - $.observable(home2).setProperty("address", {street: "address3Street", ZIP: "address3Zip"}); + $.observable(home1).setProperty("address", { street: "ignoreThisStreet", ZIP: "ignoreZip" }); + $.observable(home2).setProperty("address", { street: "address3Street", ZIP: "address3Zip" }); // ............................... Assert ................................. equal(result, - "calls: 1, ev.data: prop: home, path: address^street, eventArgs: oldValue: {\"address\":{\"street\":\"newValue3\",\"ZIP\":\"newZip3\"}} value: {\"address\":{\"street\":\"newValue2\",\"ZIP\":\"newZip2\"}}, eventArgs.path: home|" + "calls: 1, ev.data: prop: home, path: address^street, eventArgs: oldValue: {\"address\":{\"street\":\"newValue3\",\"ZIP\":\"newZip3\"}} value: {\"address\":{\"street\":\"newValue2\",\"ZIP\":\"newZip2\"}}, eventArgs.path: home|" + "calls: 2, ev.data: prop: address, path: street, eventArgs: oldValue: {\"street\":\"newValue2\",\"ZIP\":\"newZip2\"} value: {\"street\":\"address3Street\",\"ZIP\":\"address3Zip\"}, eventArgs.path: address|", "$.observe(object, 'home.address.street', object2, 'ZIP', cb) after swapping higher up on deep path, is listening to intermediate paths on new object - 'i.e. 'address'"); // ----------------------------------------------------------------------- @@ -7114,7 +10081,7 @@ test("paths", function() { reset(); // ................................ Act .................................. - $.unobserve(person1, "home^address.street", person1.home.address, "ZIP", myListener); + $.unobserve(person1, "home^address.street", originalAddress, "ZIP", myListener); // ............................... Assert ................................. ok(!$._data(person1).events && !$._data(person1.home.address).events, @@ -7125,7 +10092,7 @@ test("paths", function() { reset(); $.observe(person1, "home^address.street", person1.home.address, "ZIP", "ZIP", "foo", myListener); - $.observe(person1.home.address, "street", function(){}); + $.observe(person1.home.address, "street", function() { }); // ............................... Assert ................................. equal("" + $._data(person1.home.address).events.propertyChange.length + " " @@ -7173,7 +10140,7 @@ test("paths", function() { // ----------------------------------------------------------------------- // ................................ Act .................................. - $.observable(person1.home.address).setProperty({street: "newValue4", ZIP: "newZip4"}); + $.observable(person1.home.address).setProperty({ street: "newValue4", ZIP: "newZip4" }); // ............................... Assert ................................. @@ -7195,12 +10162,12 @@ test("paths", function() { // =============================== Arrange =============================== // ................................ Act .................................. $.observe(person1, "work^address.street", myListener); - $.observable(person1).setProperty({work: home2}); - $.observable(address2).setProperty({street: "newAddress2"}); + $.observable(person1).setProperty({ work: home2 }); + $.observable(address2).setProperty({ street: "newAddress2" }); // ............................... Assert ................................. equal(result, - "calls: 1, ev.data: prop: work, path: address^street, eventArgs: oldValue: undefined value: {\"address\":{\"street\":\"StreetTwo\",\"ZIP\":\"newZip2\"}}, eventArgs.path: work|" + "calls: 1, ev.data: prop: work, path: address^street, eventArgs: oldValue: undefined value: {\"address\":{\"street\":\"StreetTwo\",\"ZIP\":\"newZip2\"}}, eventArgs.path: work|" + "calls: 2, ev.data: prop: street, eventArgs: oldValue: StreetTwo value: newAddress2, eventArgs.path: street|", 'observing a deep path into missing properties, followed by $.observable(...).setProperty calls which supply the missing object property then modify subobjects deeper down the path lead to the correct callback events'); // ----------------------------------------------------------------------- @@ -7230,8 +10197,8 @@ test("paths", function() { // ----------------------------------------------------------------------- // ................................ Act .................................. - $.observable(person1).setProperty({firstName: "newFirst", lastName: "newLast"}); - $.observable(settings).setProperty({title: "Sir"}); + $.observable(person1).setProperty({ firstName: "newFirst", lastName: "newLast" }); + $.observable(settings).setProperty({ title: "Sir" }); // ............................... Assert ................................. equal(result, "calls: 1, ev.data: prop: firstName, eventArgs: oldValue: Mr Jo value: Mr newFirst, eventArgs.path: firstName|" @@ -7268,7 +10235,7 @@ test("paths", function() { // ----------------------------------------------------------------------- // ................................ Act .................................. - $.observable(person1).setProperty({firstName: "newFirst", lastName: "newLast"}); + $.observable(person1).setProperty({ firstName: "newFirst", lastName: "newLast" }); // ............................... Assert ................................. @@ -7306,7 +10273,7 @@ test("paths", function() { // ----------------------------------------------------------------------- // ................................ Act .................................. - $.observable(person1).setProperty({firstName: "newFirst", lastName: "newLast"}); + $.observable(person1).setProperty({ firstName: "newFirst", lastName: "newLast" }); // ............................... Assert ................................. equal(result, "calls: 1, ev.data: prop: firstName, eventArgs: oldValue: Sir Jo value: Sir newFirst, eventArgs.path: firstName|" @@ -7381,7 +10348,7 @@ test("paths", function() { handlersCount = $._data(model).events.propertyChange.length + " " + $._data(model.person1).events.propertyChange.length + " " + $._data(model.person1.home.address).events.propertyChange.length; - $.observe(model, "person1^fullName", function() {}); + $.observe(model, "person1^fullName", function() { }); handlersCount += "|" + $._data(model).events.propertyChange.length + " " + $._data(model.person1).events.propertyChange.length + " " + $._data(model.person1.home.address).events.propertyChange.length; @@ -7433,12 +10400,15 @@ test("paths", function() { settings.onFoo = "Jo"; person1.lastName = "One"; reset(); + $("#result").empty(); + result = ""; // =============================== Teardown =============================== }); test("observe context helper", function() { + // =============================== Arrange =============================== var str = "aa", main = { @@ -7451,15 +10421,15 @@ test("observe context helper", function() { function observeCtxHelper(val, currentRoot) { if (val) { if (val.charAt(0) === "%") { - return [obj, val.slice(1), currentRoot||{}]; + return [obj, val.slice(1), currentRoot || {}]; } } } // ................................ Act .................................. $.observe(main, "title", "%name", myListener, observeCtxHelper); - $.observable(main).setProperty({title: "newTitle"}); - $.observable(obj).setProperty({name: "newName"}); + $.observable(main).setProperty({ title: "newTitle" }); + $.observable(obj).setProperty({ name: "newName" }); // ............................... Assert ................................. equal(result, "calls: 1, ev.data: prop: title, eventArgs: oldValue: foo value: newTitle, eventArgs.path: title|" @@ -7475,14 +10445,16 @@ test("observe context helper", function() { obj.name = "One"; // ............................... Assert ................................. - ok(handlersCount===1 && !$._data(obj).events, + ok(handlersCount === 1 && !$._data(obj).events, "$.unobserve(object, path, cb, observeCtxHelper) uses observeCtxHelper correctly to substitute objects and paths and unobserve from mapped "); reset(); + $.unobserve(main, "title", "%name", myListener, observeCtxHelper); + // =============================== Arrange =============================== // ................................ Act .................................. $.observe(str, myListener); - $.observable(obj).setProperty({name: "newName"}); + $.observable(obj).setProperty({ name: "newName" }); // ............................... Assert ................................. equal(result, "", @@ -7495,7 +10467,7 @@ test("observe context helper", function() { // =============================== Arrange =============================== // ................................ Act .................................. $.observe(str, myListener, observeCtxHelper); - $.observable(obj).setProperty({name: "newName"}); + $.observable(obj).setProperty({ name: "newName" }); // ............................... Assert ................................. equal(result, "", @@ -7508,7 +10480,7 @@ test("observe context helper", function() { // =============================== Arrange =============================== // ................................ Act .................................. $.observe(null, "%name", myListener, observeCtxHelper); - $.observable(obj).setProperty({name: "newName"}); + $.observable(obj).setProperty({ name: "newName" }); // ............................... Assert ................................. equal(result, "calls: 1, ev.data: prop: name, eventArgs: oldValue: One value: newName, eventArgs.path: name|", @@ -7520,7 +10492,7 @@ test("observe context helper", function() { $.unobserve(null, "%name", myListener, observeCtxHelper); // ............................... Assert ................................. - ok(handlersCount===1 && !$._data(obj).events, + ok(handlersCount === 1 && !$._data(obj).events, "$.unobserve(null, path, cb, observeCtxHelper) uses observeCtxHelper correctly to substitute objects and paths and unobserve from mapped "); reset(); @@ -7530,7 +10502,7 @@ test("observe context helper", function() { // =============================== Arrange =============================== // ................................ Act .................................. $.observe(undefined, "%name", myListener, observeCtxHelper); - $.observable(obj).setProperty({name: "newName"}); + $.observable(obj).setProperty({ name: "newName" }); // ............................... Assert ................................. equal(result, "calls: 1, ev.data: prop: name, eventArgs: oldValue: One value: newName, eventArgs.path: name|", @@ -7542,7 +10514,7 @@ test("observe context helper", function() { $.unobserve(undefined, "%name", myListener, observeCtxHelper); // ............................... Assert ................................. - ok(handlersCount===1 && !$._data(obj).events, + ok(handlersCount === 1 && !$._data(obj).events, "$.unobserve(undefined, path, cb, observeCtxHelper) uses observeCtxHelper correctly to substitute objects and paths and unobserve from mapped "); reset(); @@ -7552,7 +10524,7 @@ test("observe context helper", function() { // =============================== Arrange =============================== // ................................ Act .................................. $.observe(undefined, str, myListener, observeCtxHelper); - $.observable(obj).setProperty({name: "newName"}); + $.observable(obj).setProperty({ name: "newName" }); // ............................... Assert ................................. equal(result, "", @@ -7576,662 +10548,332 @@ test("observe context helper", function() { $.observe(true, str, myListener, observeCtxHelper); $.observe(2, str, myListener, observeCtxHelper); - $.observable(obj).setProperty({name: "newName"}); + $.observable(obj).setProperty({ name: "newName" }); // ............................... Assert ................................. equal(result, "", "$.observe(foo, path, ...): When first parameter foo is not a string or object, it is skipped"); - // ................................ Reset ................................ - obj.name = "One"; - - // =============================== Arrange =============================== - // ................................ Act .................................. - $.observe(true, "%name", myListener, observeCtxHelper); - $.observable(obj).setProperty({name: "newName"}); - - // ............................... Assert ................................. - equal(result, "calls: 1, ev.data: prop: name, eventArgs: oldValue: One value: newName, eventArgs.path: name|", - "$.observe(foo, path, ...): When first parameter foo is not a string or object, it is skipped"); - - // ................................ Act .................................. - handlersCount = $._data(obj).events.propertyChange.length; - $.unobserve(true, "%name", myListener, observeCtxHelper); - - // ............................... Assert ................................. - ok(handlersCount===1 && !$._data(obj).events, - "$.unobserve(foo, path, ...): When first parameter foo is not a string or object, it is skipped"); - reset(); - - // ................................ Reset ................................ - obj.name = "One"; - - // =============================== Arrange =============================== - // ................................ Act .................................. - $.observe(false, "%name", myListener, observeCtxHelper); - $.observable(obj).setProperty({name: "newName"}); - - // ............................... Assert ................................. - equal(result, "calls: 1, ev.data: prop: name, eventArgs: oldValue: One value: newName, eventArgs.path: name|", - "$.observe(foo, path, ...): When first parameter foo is not a string or object, it is skipped"); - - // ................................ Act .................................. - handlersCount = $._data(obj).events.propertyChange.length; - $.unobserve(false, "%name", myListener, observeCtxHelper); - - // ............................... Assert ................................. - ok(handlersCount===1 && !$._data(obj).events, - "$.unobserve(foo, path, ...): When first parameter foo is not a string or object, it is skipped"); - reset(); - - // ................................ Reset ................................ - obj.name = "One"; - - //TODO test case for observe(domElement, "a.b.c", callback) should not bind at all -}); - -test("array", function() { -// For V1 >>>>> $.observe(person, "**", changeHandler); // all props and arraychange on all array-type props - over complete object graph of person. -// For V1 >>>>> $.observe(person.phones, "**", changeHandler); // all props and arraychange on all array-type props over complete object graph of each phone -// =============================== Arrange =============================== - // Using an the same event handler for arrayChange and propertyChange - - var myArray = [1,2]; - - // ................................ Act .................................. - $.observe(myArray, myListener); - - $.observable(myArray).insert(10); - - // ............................... Assert ................................. - equal(result + $._data(myArray).events.arrayChange.length + " " + !$._data(myArray).events.propertyChange, - "regularCallbackCalls: 1, eventArgs: change: insert|1 true", - "$.observe(myArray, myListener) listens just to array change on the array"); - - // ................................ Act .................................. - reset(); - $.unobserve(myArray, myListener); - - $.observable(myArray).insert(11); - - // ............................... Assert ................................. - equal(result + !$._data(myArray).events + " " + !$._data(myArray).events, "true true", - "$.unobserve(myArray, cbWithoutArrayCallback) removes the arraychange handler"); - - // ................................ Act .................................. - reset(); - $.observe(myArray, "length", myListener); - - $.observable(myArray).insert(14); - - // ............................... Assert ................................. - equal(result + $._data(myArray).events.arrayChange.length + " " + $._data(myArray).events.propertyChange.length, - "regularCallbackCalls: 1, eventArgs: change: insert|" - + "calls: 2, ev.data: prop: length, eventArgs: oldValue: 4 value: 5, eventArgs.path: length|1 1", - '$.observe(myArray, "length", myListener) listens to array change on the array and to length propertyChange on the array'); - - // ................................ Act .................................. - reset(); - $.unobserve(myArray, "length", myListener); - - $.observable(myArray).insert(15); - - // ............................... Assert ................................. - equal(result + !$._data(myArray).events + " " + !$._data(myArray).events, "true true", - '$.unobserve(myArray, "length", cbWithoutArrayCallback) removes the arraychange handler and the propertychange handler'); - - // =============================== Arrange =============================== - reset(); - -var initialArray = [1,2], - altArray = [4,3,2,1], - obj = {name:{first:"n", arr: initialArray}}; - - $.observe(obj, "name.arr", myListener); - - // ................................ Act .................................. - $.observable(initialArray).insert(10); - - // ............................... Assert ................................. - equal(result + $._data(initialArray).events.arrayChange.length + " " + !$._data(initialArray).events.propertyChange, - "regularCallbackCalls: 1, eventArgs: change: insert|1 true", - '$.observe(object, "a.b.myArray", cbWithoutArrayCallback) listens just to array change on the array'); - - // ................................ Act .................................. - reset(); - $.observable(obj).setProperty("name.arr", altArray); - - // ............................... Assert ................................. - equal(result, "calls: 1, ev.data: prop: arr, eventArgs: oldValue: [1,2,10] value: [4,3,2,1], eventArgs.path: arr|", - '$.observe(object, "a.b.myArray", cbWithoutArrayCallback) listens to property change for swapping the array property'); - - // ............................... Assert ................................. - reset(); - equal(!$._data(initialArray).events + " " + $._data(altArray).events.arrayChange.length, "true 1", - '$.observable(obj).setProperty("name.arr", newArray) removes the arrayChange handler on previous array, and adds arrayChange to new array'); - - // ................................ Act .................................. - $.observable(obj.name.arr).insert(11); - - // ............................... Assert ................................. - equal(result, "regularCallbackCalls: 1, eventArgs: change: insert|", - '$.observe(object, "a.b.myArray", cbWithoutArrayCallback) listens to array changes on leaf array property (regular callback)'); - - // ................................ Act .................................. - handlersCount = $._data(obj.name).events.propertyChange.length + $._data(obj.name.arr).events.arrayChange.length; - $.unobserve(obj, "name.arr", myListener); - - // ............................... Assert ................................. - ok(handlersCount === 2 && !$._data(obj.name).events && !$._data(obj.name.arr).events, - '$.unobserve(object, "a.b.myArray") removes both arrayChange and propertyChange event handlers'); - // ----------------------------------------------------------------------- - reset(); - $.observe(obj, "name.arr", "name.arr.length", myListener); - - $.observable(obj.name.arr).insert(16); - - // ............................... Assert ................................. - equal(result + $._data(obj.name.arr).events.arrayChange.length + " " + $._data(obj.name.arr).events.propertyChange.length, - "regularCallbackCalls: 1, eventArgs: change: insert|" - + "calls: 2, ev.data: prop: length, eventArgs: oldValue: 5 value: 6, eventArgs.path: length|1 1", - '$.observe(object, "a.b.array", "a.b.array.length", myListener) listens to array change on the array and to length propertyChange on the array'); - - // ................................ Act .................................. - reset(); - $.unobserve(obj, "name.arr", "name.arr.length", myListener); - - $.observable(obj.name.arr).insert(17); - - // ............................... Assert ................................. - equal(result + !$._data(obj.name.arr).events, "true", - '$.unobserve(object, "a.b.array", "a.b.array.length", cbWithoutArrayCallback) removes the arraychange handler and the propertychange handler'); - - // ................................ Act .................................. - reset(); - $.observe(obj, "name.*", myListener); - - $.observable(obj.name.arr).insert(18); - - $.observable(obj.name).setProperty({ - first: "1st", - notThereBefore: "2nd", - arr: initialArray - }); - $.observable(obj.name.arr).insert(19); - - // ............................... Assert ................................. - equal(result + $._data(obj.name.arr).events.arrayChange.length + " " + $._data(obj.name).events.propertyChange.length + " " + !$._data(altArray).events, - "regularCallbackCalls: 1, eventArgs: change: insert|" - + "calls: 2, ev.data: prop: *, eventArgs: oldValue: n value: 1st, eventArgs.path: first|" - + "calls: 3, ev.data: prop: *, eventArgs: oldValue: undefined value: 2nd, eventArgs.path: notThereBefore|" - + "calls: 4, ev.data: prop: *, eventArgs: oldValue: [4,3,2,1,11,16,17,18] value: [1,2,10], eventArgs.path: arr|" - + "regularCallbackCalls: 5, eventArgs: change: insert|1 1 true", - '$.observe(object, "a.b.*", myListener) listens to all propertyChange events on object.a.b and to array change on any array properties of object.a.b'); - - // ................................ Act .................................. - reset(); - $.unobserve(obj, "name.*", myListener); - - $.observable(obj.name.arr).insert(17); - - // ............................... Assert ................................. - equal(result + !$._data(obj.name.arr).events, "true", - '$.unobserve(object, "a.b.*", cbWithoutArrayCallback) removes the propertychange handler and any arraychange handlers'); - - // =============================== Arrange =============================== - // Using an array event handler - obj = {name:{first:"n", arr: initialArray}}; - var newArray1 = [1, 1], - newArray2 = [2, 2], - newArray3 = [3, 3]; - - reset(); - $.observe(obj, "name.*", myListener); - - $.observable(obj.name.arr).insert(18); - - $.observable(obj.name).setProperty({ - first: newArray1, - arrayNotThereBefore: newArray2, - arr: newArray3 - }); - $.observable(obj.name.first).insert(10); - $.observable(obj.name.arrayNotThereBefore).insert(11); - $.observable(obj.name.arr).insert(12); - - // ............................... Assert ................................. - equal(result + !$._data(initialArray).events + " " + $._data(obj.name).events.propertyChange.length + " " - + $._data(newArray1).events.arrayChange.length + " " + $._data(newArray2).events.arrayChange.length + " " + $._data(newArray3).events.arrayChange.length, - "regularCallbackCalls: 1, eventArgs: change: insert|" - + "calls: 2, ev.data: prop: *, eventArgs: oldValue: n value: [1,1], eventArgs.path: first|" - + "calls: 3, ev.data: prop: *, eventArgs: oldValue: undefined value: [2,2], eventArgs.path: arrayNotThereBefore|" - + "calls: 4, ev.data: prop: *, eventArgs: oldValue: [1,2,10,19,17,18] value: [3,3], eventArgs.path: arr|" - + "regularCallbackCalls: 5, eventArgs: change: insert|" - + "regularCallbackCalls: 6, eventArgs: change: insert|" - + "regularCallbackCalls: 7, eventArgs: change: insert|" - + "true 1 1 1 1", - '$.observe(object, "a.b.*", myListener) listens to array change on any array properties of object.a.b whether intially present, or added subsequently'); - - // ................................ Act .................................. - reset(); - $.unobserve(obj, "name.*", myListener); - - $.observable(obj.name.arr).insert(17); - - // ............................... Assert ................................. - equal(result + !$._data(obj.name.arr).events + " " + !$._data(newArray1).events + " " + !$._data(newArray1).events + " " + !$._data(newArray1).events, "true true true true", - '$.unobserve(object, "a.b.*", cbWithoutArrayCallback) removes the propertychange handler and any arraychange handlers'); - - // =============================== Arrange =============================== - // Using an array event handler - obj = {name:{first:"n", arr: [1,2]}}; - - myListener.array = function(ev,eventArgs) { - result += "arrayListenerCalls: " + calls - + ", eventArgs: change: " + eventArgs.change + "|"; - }; - - $.observe(obj, "name.arr", myListener); - - // ................................ Act .................................. - reset(); - $.observable(obj).setProperty("name.arr", [4,3,2,1]); - - // ............................... Assert ................................. - equal(result, "calls: 1, ev.data: prop: arr, eventArgs: oldValue: [1,2] value: [4,3,2,1], eventArgs.path: arr|", - '$.observe(object, "a.b.myArray", cbWithArrayCallback) listens to property change for swapping the array property'); + // ................................ Reset ................................ + obj.name = "One"; + // =============================== Arrange =============================== // ................................ Act .................................. - reset(); - $.observable(obj.name.arr).insert(12); + $.observe(true, "%name", myListener, observeCtxHelper); + $.observable(obj).setProperty({ name: "newName" }); // ............................... Assert ................................. - equal(result, "arrayListenerCalls: 0, eventArgs: change: insert|", - '$.observe(object, "a.b.myArray", cbWithArrayCallback) listens to array changes on leaf array property (array callback handler)'); + equal(result, "calls: 1, ev.data: prop: name, eventArgs: oldValue: One value: newName, eventArgs.path: name|", + "$.observe(foo, path, ...): When first parameter foo is not a string or object, it is skipped"); // ................................ Act .................................. - handlersCount = $._data(obj.name.arr).events.arrayChange.length; - $.unobserve(obj, "name.arr", myListener); + handlersCount = $._data(obj).events.propertyChange.length; + $.unobserve(true, "%name", myListener, observeCtxHelper); // ............................... Assert ................................. - ok(handlersCount === 1 && !$._data(obj.name.arr).events, - '$.unobserve(object, "a.b.myArray") removes arrayChange event handler'); - // ----------------------------------------------------------------------- + ok(handlersCount === 1 && !$._data(obj).events, + "$.unobserve(foo, path, ...): When first parameter foo is not a string or object, it is skipped"); + reset(); - // =============================== Arrange =============================== - $.observe(obj.name.arr, myListener); + // ................................ Reset ................................ + obj.name = "One"; + // =============================== Arrange =============================== // ................................ Act .................................. - reset(); - $.observable(obj.name.arr).insert(13); + $.observe(false, "%name", myListener, observeCtxHelper); + $.observable(obj).setProperty({ name: "newName" }); // ............................... Assert ................................. - equal(result, "arrayListenerCalls: 0, eventArgs: change: insert|", - '$.observe(myArray, cbWithArrayCallback) listens to array changes (array callback handler)'); + equal(result, "calls: 1, ev.data: prop: name, eventArgs: oldValue: One value: newName, eventArgs.path: name|", + "$.observe(foo, path, ...): When first parameter foo is not a string or object, it is skipped"); + // ................................ Act .................................. - handlersCount = $._data(obj.name.arr).events.arrayChange.length; - $.unobserve(obj.name.arr, myListener); + handlersCount = $._data(obj).events.propertyChange.length; + $.unobserve(false, "%name", myListener, observeCtxHelper); // ............................... Assert ................................. - ok(handlersCount === 1 && !$._data(obj.name.arr).events, - '$.unobserve(myArray) removes arrayChange event handler'); - // ----------------------------------------------------------------------- - - // =============================== Arrange =============================== - var people = [1,2]; - function onch() {} - function onch2() {} + ok(handlersCount === 1 && !$._data(obj).events, + "$.unobserve(foo, path, ...): When first parameter foo is not a string or object, it is skipped"); + reset(); - // ................................ Act .................................. - $.observe(people, "length", onch); - $.observe(people, "length", onch2); - $.observe(people, "length2", onch); - $.templates("{^{for people}}{{/for}} {^{:people}}").link("#result", {people: people}); - $.observe(people, "length2", onch2); - $.unobserve(people, "length2", onch); - $.unobserve(people, "length2", onch2); - $("#result").empty(); - $.unobserve(people, "length", onch); - $.unobserve(people, "length", onch2); + // ................................ Reset ................................ + obj.name = "One"; - // ............................... Assert ................................. - equal(JSON.stringify([$.views.sub._cbBnds, _jsv.bindings, $._data(people).events]), "[{},{},null]", - "observe/unobserve array - API calls in different orders: all bindings removed when content removed from DOM and unobserve called"); + //TODO test case for observe(domElement, "a.b.c", callback) should not bind at all }); -test("computed observables in paths", function() { +test("Array", function() { + + // For V1 >>>>> $.observe(person, "**", changeHandler); // all props and arraychange on all array-type props - over complete object graph of person. + // For V1 >>>>> $.observe(person.phones, "**", changeHandler); // all props and arraychange on all array-type props over complete object graph of each phone // =============================== Arrange =============================== - var app = { items: [ - { - name: "one", - row: "1", - expanded: false - }, - { - name: "two", - row: "2", - expanded: false - }, - { - name: "three", - row: "3", - expanded: false - } - ]}; + // Using an the same event handler for arrayChange and propertyChange -function testTemplate(message, template) { - $.templates(template) - .link("#result", app, { - getItems: function(exp) { - return exp ? ["a","b"]: []; - } - }); + var myArray = [1, 2]; // ................................ Act .................................. - var ret = $("#result").text() + "|"; - $.view("#result .groupdata").refresh(); - - $.observable(app.items[0]).setProperty("expanded", true); - ret += $("#result").text() + "|"; + $.observe(myArray, myListener); - $.observable(app.items[0]).setProperty("expanded", false); - ret += $("#result").text() + "|"; + $.observable(myArray).insert(10); - $.observable(app.items[1]).setProperty("expanded", true); - ret += $("#result").text() + "|"; + // ............................... Assert ................................. + equal(result + $._data(myArray).events.arrayChange.length + " " + !$._data(myArray).events.propertyChange, + "regularCallbackCalls: 1, eventArgs: change: insert|1 true", + "$.observe(myArray, myListener) listens just to array change on the array"); - $.observable(app.items[1]).setProperty("expanded", false); - ret += $("#result").text() + "|"; + // ................................ Act .................................. + reset(); + $.unobserve(myArray, myListener); - $.observable(app.items).insert(0, { - name: "added", - row: "+", - expanded: false - }); - ret += $("#result").text() + "|"; - $.observable(app.items).remove(0); - ret += $("#result").text() + "|"; - $("#result").empty(); + $.observable(myArray).insert(11); // ............................... Assert ................................. - equal(ret, "onetwothree|one1a1btwothree|onetwothree|onetwo2a2bthree|onetwothree|addedonetwothree|onetwothree|" - , "Interplay of view and tag refresh in deep content: " + message); -} + equal(result + !$._data(myArray).events + " " + !$._data(myArray).events, "true true", + "$.unobserve(myArray, cbWithoutArrayCallback) removes the arraychange handler"); - // ............................... Assert ................................. - testTemplate("div", - "{^{for items}}" - + "{{:name}}" - + "{^{for ~getItems(expanded) ~row=row}}" - + "
    {{:~row}}{{:#data}}
    " - + "{{/for}}" - + "{{/for}}"); + // ................................ Act .................................. + reset(); + $.observe(myArray, "length", myListener); - // ............................... Assert ................................. - testTemplate("deep div", - "{^{for items}}" - + "{{:name}}" - + "
    {^{for ~getItems(expanded) ~row=row}}" - + "" - + "{{:~row}}{{:#data}}" - + "{{/for}}
    " - + "{{/for}}"); + $.observable(myArray).insert(14); // ............................... Assert ................................. - testTemplate("deep div2", - "{^{for items}}" - + "{{:name}}" - + "
    {^{for ~getItems(expanded) ~row=row}}" - + "" - + "{{:~row}}{{:#data}}" - + "{{/for}}
    " - + "{{/for}}"); + equal(result + $._data(myArray).events.arrayChange.length + " " + $._data(myArray).events.propertyChange.length, + "regularCallbackCalls: 1, eventArgs: change: insert|" + + "calls: 2, ev.data: prop: length, eventArgs: oldValue: 4 value: 5, eventArgs.path: length|1 1", + '$.observe(myArray, "length", myListener) listens to array change on the array and to length propertyChange on the array'); - // ............................... Assert ................................. - testTemplate("li", - "
      {^{for items}}" - + "
    • {{:name}}
    • " - + "
      • {^{for ~getItems(expanded) ~row=row ~item=#data}}" - + "
      • " - + "
      • {{:~row}}{{:#data}}
      • " - + "{{/for}}
    • " - + "{{/for}}
    "); + // ................................ Act .................................. + reset(); + $.unobserve(myArray, "length", myListener); - // ............................... Assert ................................. - testTemplate("table", - "{^{for items}}" - + "" - + "{^{for ~getItems(expanded) ~row=row}}" - + "" - + "" - + "" - + "{{/for}}" - + " {{/for}}
    {{:name}}
    {{:~row}}{{:#data}}
    "); + $.observable(myArray).insert(15); // ............................... Assert ................................. - testTemplate("deep table", - "{^{for items}}" - + "" - + "" - + "{^{for ~getItems(expanded) ~row=row}}" - + "" - + "" - + "" - + "{{/for}}" - + "" - + " {{/for}}
    {{:name}}
    {{:~row}}{{:#data}}
    "); + equal(result + !$._data(myArray).events + " " + !$._data(myArray).events, "true true", + '$.unobserve(myArray, "length", cbWithoutArrayCallback) removes the arraychange handler and the propertychange handler'); // =============================== Arrange =============================== - var ret = "", - people1 = [{address:{street: "1 first street"}}], - people2 = [{address:{street: "1 second street"}},{address:{street: "2 second street"}}], - data1 = {value: "data1", people:people1}, - data2 = {value: "data2", people:people2}; - app = { - alt:false, - index: 1, - getPeople: getPeople, - getData: getData, - options: { - getWidth: function() { - return "33"; - } - } - }; + reset(); - function getPeople(type) { - return this.alt ? people2 : people1; - } + var initialArray = [1, 2], + altArray = [4, 3, 2, 1], + obj = { name: { first: "n", arr: initialArray } }; - getPeople.depends = function() { - return [app, "alt"]; - }; + $.observe(obj, "name.arr", myListener); - function getData(type) { - return this.alt ? data2 : data1; - } + // ................................ Act .................................. + $.observable(initialArray).insert(10); - getData.depends = function() { - return [app, "alt"]; - }; + // ............................... Assert ................................. + equal(result + $._data(initialArray).events.arrayChange.length + " " + !$._data(initialArray).events.propertyChange, + "regularCallbackCalls: 1, eventArgs: change: insert|1 true", + '$.observe(object, "a.b.myArray", cbWithoutArrayCallback) listens just to array change on the array'); // ................................ Act .................................. - $.templates("{^{for (getPeople()[index]||{}).address}}{^{:street}}{{/for}}").link("#result", app); + reset(); + $.observable(obj).setProperty("name.arr", altArray); - ret = "|" + $("#result").text(); + // ............................... Assert ................................. + equal(result, "calls: 1, ev.data: prop: arr, eventArgs: oldValue: [1,2,10] value: [4,3,2,1], eventArgs.path: arr|", + '$.observe(object, "a.b.myArray", cbWithoutArrayCallback) listens to property change for swapping the array property'); - // ................................ Act .................................. - $.observable(app).setProperty("index", 0); - ret += "|" + $("#result").text(); + // ............................... Assert ................................. + reset(); + equal(!$._data(initialArray).events + " " + $._data(altArray).events.arrayChange.length, "true 1", + '$.observable(obj).setProperty("name.arr", newArray) removes the arrayChange handler on previous array, and adds arrayChange to new array'); // ................................ Act .................................. - $.observable(people1[0].address).setProperty("street", "1 first streetB"); - ret += "|" + $("#result").text(); + $.observable(obj.name.arr).insert(11); - // ................................ Act .................................. - $.observable(people1[0]).setProperty("address", {street: "1 first swappedstreet"}); - ret += "|" + $("#result").text(); + // ............................... Assert ................................. + equal(result, "regularCallbackCalls: 1, eventArgs: change: insert|", + '$.observe(object, "a.b.myArray", cbWithoutArrayCallback) listens to array changes on leaf array property (regular callback)'); // ................................ Act .................................. - $.observable(app).setProperty("alt", true); - ret += "|" + $("#result").text(); + handlersCount = $._data(obj.name).events.propertyChange.length + $._data(obj.name.arr).events.arrayChange.length; + $.unobserve(obj, "name.arr", myListener); - // ................................ Act .................................. - $.observable(app).setProperty("index", 1); - ret += "|" + $("#result").text(); + // ............................... Assert ................................. + ok(handlersCount === 2 && !$._data(obj.name).events && !$._data(obj.name.arr).events, + '$.unobserve(object, "a.b.myArray") removes both arrayChange and propertyChange event handlers'); + // ----------------------------------------------------------------------- + reset(); + $.observe(obj, "name.arr", "name.arr.length", myListener); + + $.observable(obj.name.arr).insert(16); + + // ............................... Assert ................................. + equal(result + $._data(obj.name.arr).events.arrayChange.length + " " + $._data(obj.name.arr).events.propertyChange.length, + "regularCallbackCalls: 1, eventArgs: change: insert|" + + "calls: 2, ev.data: prop: length, eventArgs: oldValue: 5 value: 6, eventArgs.path: length|1 1", + '$.observe(object, "a.b.array", "a.b.array.length", myListener) listens to array change on the array and to length propertyChange on the array'); // ................................ Act .................................. - $.observable(people2[1]).setProperty("address", {street: "2 second swappedstreet"}); - ret += "|" + $("#result").text(); + reset(); + $.unobserve(obj, "name.arr", "name.arr.length", myListener); + + $.observable(obj.name.arr).insert(17); + + // ............................... Assert ................................. + equal(result + !$._data(obj.name.arr).events, "true", + '$.unobserve(object, "a.b.array", "a.b.array.length", cbWithoutArrayCallback) removes the arraychange handler and the propertychange handler'); // ................................ Act .................................. - $.observable(people2[1].address).setProperty("street", "2 second swappedstreetB"); - ret += "|" + $("#result").text(); + reset(); + $.observe(obj, "name.*", myListener); - // ................................ Assert .................................. - equal(ret, "||1 first street|1 first streetB|1 first swappedstreet|1 second street|2 second street|2 second swappedstreet|2 second swappedstreetB", - "deep paths with computed observables bind correctly to rest of path after computed returns new array"); - $("#result").empty(); + $.observable(obj.name.arr).insert(18); - app.alt = false; - app.index = 0; - people1 = [{address:{street: "1 first street"}}]; - people2 = [{address:{street: "1 second street"}},{address:{street: "2 second street"}}]; - data1 = {value: "data1", people:people1}; - data2 = {value: "data2", people:people2}; + $.observable(obj.name).setProperty({ + first: "1st", + notThereBefore: "2nd", + arr: initialArray + }); + $.observable(obj.name.arr).insert(19); + + // ............................... Assert ................................. + equal(result + $._data(obj.name.arr).events.arrayChange.length + " " + $._data(obj.name).events.propertyChange.length + " " + !$._data(altArray).events, + "regularCallbackCalls: 1, eventArgs: change: insert|" + + "calls: 2, ev.data: prop: *, eventArgs: oldValue: n value: 1st, eventArgs.path: first|" + + "calls: 3, ev.data: prop: *, eventArgs: oldValue: undefined value: 2nd, eventArgs.path: notThereBefore|" + + "calls: 4, ev.data: prop: *, eventArgs: oldValue: [4,3,2,1,11,16,17,18] value: [1,2,10], eventArgs.path: arr|" + + "regularCallbackCalls: 5, eventArgs: change: insert|1 1 true", + '$.observe(object, "a.b.*", myListener) listens to all propertyChange events on object.a.b and to array change on any array properties of object.a.b'); // ................................ Act .................................. - $.templates("{^{:(getData().people[index]).address^street}}").link("#result", app); + reset(); + $.unobserve(obj, "name.*", myListener); - ret = $("#result").text(); + $.observable(obj.name.arr).insert(17); - // ................................ Act .................................. - $.observable(people1[0].address).setProperty("street", "1 first streetB"); - ret += "|" + $("#result").text(); + // ............................... Assert ................................. + equal(result + !$._data(obj.name.arr).events, "true", + '$.unobserve(object, "a.b.*", cbWithoutArrayCallback) removes the propertychange handler and any arraychange handlers'); - // ................................ Act .................................. - $.observable(people1[0]).setProperty("address", {street: "1 first swappedstreet"}); - ret += "|" + $("#result").text(); + // =============================== Arrange =============================== + // Using an array event handler + obj = { name: { first: "n", arr: initialArray } }; + var newArray1 = [1, 1], + newArray2 = [2, 2], + newArray3 = [3, 3]; - // ................................ Act .................................. - $.observable(app).setProperty("alt", true); - ret += "|" + $("#result").text(); + reset(); + $.observe(obj, "name.*", myListener); - // ................................ Assert .................................. - equal(ret, "1 first street|1 first streetB|1 first swappedstreet|1 second street", - "deep paths with computed observables bind correctly to rest of path after computed returns new object"); - $("#result").empty(); + $.observable(obj.name.arr).insert(18); - //TODO add support for binding to [expression] accessors in deep paths, including [index] accessors for arrays, as above - //$.observable(app).setProperty("index", 1); - //ret += "|" + $("#result").text(); + $.observable(obj.name).setProperty({ + first: newArray1, + arrayNotThereBefore: newArray2, + arr: newArray3 + }); + $.observable(obj.name.first).insert(10); + $.observable(obj.name.arrayNotThereBefore).insert(11); + $.observable(obj.name.arr).insert(12); - //$.observable(people2[1]).setProperty("address", {street: "2 second swappedstreet"}); - //ret += "|" + $("#result").text(); + // ............................... Assert ................................. + equal(result + !$._data(initialArray).events + " " + $._data(obj.name).events.propertyChange.length + " " + + $._data(newArray1).events.arrayChange.length + " " + $._data(newArray2).events.arrayChange.length + " " + $._data(newArray3).events.arrayChange.length, + "regularCallbackCalls: 1, eventArgs: change: insert|" + + "calls: 2, ev.data: prop: *, eventArgs: oldValue: n value: [1,1], eventArgs.path: first|" + + "calls: 3, ev.data: prop: *, eventArgs: oldValue: undefined value: [2,2], eventArgs.path: arrayNotThereBefore|" + + "calls: 4, ev.data: prop: *, eventArgs: oldValue: [1,2,10,19,17,18] value: [3,3], eventArgs.path: arr|" + + "regularCallbackCalls: 5, eventArgs: change: insert|" + + "regularCallbackCalls: 6, eventArgs: change: insert|" + + "regularCallbackCalls: 7, eventArgs: change: insert|" + + "true 1 1 1 1", + '$.observe(object, "a.b.*", myListener) listens to array change on any array properties of object.a.b whether intially present, or added subsequently'); - //$.observable(people2[1].address).setProperty("street", "2 second swappedstreetB"); - //ret += "|" + $("#result").text(); + // ................................ Act .................................. + reset(); + $.unobserve(obj, "name.*", myListener); - // TODO allow the following to work by declaring getPeople as depending on collection change of app.alt ? people2 : people; - //$.observable(people2).insert(1, {address:{street: "99 new street"}}) - //ret += "|" + $("#result").text(); + $.observable(obj.name.arr).insert(17); + + // ............................... Assert ................................. + equal(result + !$._data(obj.name.arr).events + " " + !$._data(newArray1).events + " " + !$._data(newArray1).events + " " + !$._data(newArray1).events, "true true true true", + '$.unobserve(object, "a.b.*", cbWithoutArrayCallback) removes the propertychange handler and any arraychange handlers'); // =============================== Arrange =============================== - function getValue(a) { - return this.value + a; - } - function switchAlt() { - $.observable(app).setProperty("alt", !app.alt); - } + // Using an array event handler + obj = { name: { first: "n", arr: [1, 2] } }; - app.alt = false; - app.index = 0; - people1 = [{address:{street: "1 first street"}}]; - people2 = [{address:{street: "1 second street"}},{address:{street: "2 second street"}}]; - data1 = {value: "val1", people:people1, getValue:getValue}, - data2 = {value: "val2", people:people2, getValue:getValue}, + myListener.array = function(ev, eventArgs) { + result += "arrayListenerCalls: " + calls + + ", eventArgs: change: " + eventArgs.change + "|"; + }; - // ................................ Act .................................. - $.templates("{^{:getData().getValue(22)}}").link("#result", app); + $.observe(obj, "name.arr", myListener); // ................................ Act .................................. - $.observable(app).setProperty("alt", true); - ret = "|" + $("#result").text(); - switchAlt(); - ret += "--" + $("#result").text(); - $("#result").empty(); + reset(); + $.observable(obj).setProperty("name.arr", [4, 3, 2, 1]); - // ................................ Act .................................. - $.templates("{^{for (getPeople())}}{^{:address.street}}{{/for}}").link("#result", app); - ret += "|" + $("#result").text(); - switchAlt(); - ret += "--" + $("#result").text(); - $("#result").empty(); + // ............................... Assert ................................. + equal(result, "calls: 1, ev.data: prop: arr, eventArgs: oldValue: [1,2] value: [4,3,2,1], eventArgs.path: arr|", + '$.observe(object, "a.b.myArray", cbWithArrayCallback) listens to property change for swapping the array property'); // ................................ Act .................................. - $.templates("{^{for getPeople()}}{^{:address.street}}{{/for}}").link("#result", app); - ret += "|" + $("#result").text(); - switchAlt(); - ret += "--" + $("#result").text(); - $("#result").empty(); + reset(); + $.observable(obj.name.arr).insert(12); - // ................................ Act .................................. - $.templates("{^{:(getData().getValue(22))}}").link("#result", app); - ret += "|" + $("#result").text(); - switchAlt(); - ret += "--" + $("#result").text(); - $("#result").empty(); + // ............................... Assert ................................. + equal(result, "arrayListenerCalls: 0, eventArgs: change: insert|", + '$.observe(object, "a.b.myArray", cbWithArrayCallback) listens to array changes on leaf array property (array callback handler)'); // ................................ Act .................................. - $.templates("{^{:getData().getValue((getData().getValue(22)))}}").link("#result", app); - ret += "|" + $("#result").text(); - switchAlt(); - ret += "--" + $("#result").text(); - $("#result").empty(); + handlersCount = $._data(obj.name.arr).events.arrayChange.length; + $.unobserve(obj, "name.arr", myListener); - // ................................ Act .................................. - $.templates("{^{:getData(getPeople(getData(alt || 2).getValue()).length).value}}").link("#result", app); - ret += "|" + $("#result").text(); - switchAlt(); - ret += "--" + $("#result").text(); - $("#result").empty(); + // ............................... Assert ................................. + ok(handlersCount === 1 && !$._data(obj.name.arr).events, + '$.unobserve(object, "a.b.myArray") removes arrayChange event handler'); + // ----------------------------------------------------------------------- - // ................................ Act .................................. - $.templates("{^{for (getPeople()[index]||{}).address}}{^{:street}}{{/for}}").link("#result", app); - ret += "|" + $("#result").text(); - switchAlt(); - ret += "--" + $("#result").text(); - $("#result").empty(); + // =============================== Arrange =============================== + $.observe(obj.name.arr, myListener); // ................................ Act .................................. - $.templates("{^{:(((getData()).people[0]).address^street)}}").link("#result", app); - ret += "|" + $("#result").text(); - switchAlt(); - ret += "--" + $("#result").text(); - $("#result").empty(); + reset(); + $.observable(obj.name.arr).insert(13); + // ............................... Assert ................................. + equal(result, "arrayListenerCalls: 0, eventArgs: change: insert|", + '$.observe(myArray, cbWithArrayCallback) listens to array changes (array callback handler)'); // ................................ Act .................................. - $.templates("{^{:'b'+((getData().value) + ('a'+getData().value)) + getData().getValue(55)}}").link("#result", app); - ret += "|" + $("#result").text(); - switchAlt(); - ret += "--" + $("#result").text(); - $("#result").empty(); + handlersCount = $._data(obj.name.arr).events.arrayChange.length; + $.unobserve(obj.name.arr, myListener); + + // ............................... Assert ................................. + ok(handlersCount === 1 && !$._data(obj.name.arr).events, + '$.unobserve(myArray) removes arrayChange event handler'); + // ----------------------------------------------------------------------- + + // =============================== Arrange =============================== + var people = [1, 2]; + function onch() { } + function onch2() { } // ................................ Act .................................. - $.templates("{^{:'a' + getData().value}}").link("#result", app); - ret += "|" + $("#result").text(); - switchAlt(); - ret += "--" + $("#result").text(); + $.observe(people, "length", onch); + $.observe(people, "length", onch2); + $.observe(people, "length2", onch); + $.templates("{^{for people}}{{/for}} {^{:people}}").link("#result", { people: people }); + $.observe(people, "length2", onch2); + $.unobserve(people, "length2", onch); + $.unobserve(people, "length2", onch2); $("#result").empty(); + $.unobserve(people, "length", onch); + $.unobserve(people, "length", onch2); - // ................................ Assert .................................. - equal(ret, "|val222--val122|1 first street--1 second street2 second street|1 second street2 second street--1 first street" - + "|val122--val222|val2val222--val1val122|val1--val2|1 second street--1 first street" - + "|1 first street--1 second street|bval2aval2val255--bval1aval1val155|aval1--aval2", - "deep paths with computed observables bind correctly to rest of path after computed returns new object or array, including complex expressions, wrapped in parens etc."); - }); + // ............................... Assert ................................. + equal(JSON.stringify([$.views.sub._cbBnds, _jsv.bindings, $._data(people).events]), "[{},{},null]", + "observe/unobserve array - API calls in different orders: all bindings removed when content removed from DOM and unobserve called"); +}); test("MVVM", function() { + reset(); // =============================== Arrange =============================== function Person(name, address, phones) { @@ -8302,7 +10944,7 @@ test("MVVM", function() { message = '', ret = '', input, - getResult = function(sep) {ret += (sep || "|") + input.val() + "/" + $("#result").text();}; + getResult = function(sep) { ret += (sep || "|") + input.val() + "/" + $("#result").text(); }; // ................................ Act .................................. $.templates('{^{:address()^street()}}').link("#result", person); @@ -8330,7 +10972,7 @@ test("MVVM", function() { // ................................ Act .................................. ret = ""; - $.templates('{^{:address().street()}}').link("#result", person); + $.templates('{^{:address()^street()}}').link("#result", person); input = $("#result input"); getResult(); @@ -8351,11 +10993,11 @@ test("MVVM", function() { + "- same as if there was a '^' separator"); // =============================== Arrange =============================== - person = new Person("pete", new Address("1st Ave"), [new Phone({number: "phone1"}), new Phone({number:"phone2"})]); + person = new Person("pete", new Address("1st Ave"), [new Phone({ number: "phone1" }), new Phone({ number: "phone2" })]); // ................................ Act .................................. ret = ""; - $.templates('{^{:address()^street()}}').link("#result", person); + $.templates('{^{:address()^street()}}').link("#result", person); var observeAllHandler = function(ev, eventArgs) { message += JSON.stringify(eventArgs) + "\n"; @@ -8407,7 +11049,7 @@ test("MVVM", function() { $("#result").empty(); - var eventsAfterEmptyTemplateContainer = !$._data(person).events + var eventsAfterEmptyTemplateContainer = !$._data(person).events + " " + !$._data(person.address()).events + "|"; // ............................... Assert ................................. @@ -8423,40 +11065,40 @@ test("MVVM", function() { // =============================== Arrange =============================== - getResult = function(sep){ret += (sep || "|") + $("#result").text();}; + getResult = function(sep) { ret += (sep || "|") + $("#result").text(); }; - person = new Person("pete", new Address("1st Ave"), [new Phone({number: "phone1"}), new Phone({number: "phone2"})]); + person = new Person("pete", new Address("1st Ave"), [new Phone({ number: "phone1" }), new Phone({ number: "phone2" })]); // ................................ Act .................................. ret = ""; $.templates('{^{for phones()}}{^{:number()}},{{/for}}').link("#result", person); getResult("\nInit>>"); - $.observable(person.phones()).insert(new Phone({number: "insertedPhone"})); + $.observable(person.phones()).insert(new Phone({ number: "insertedPhone" })); getResult("insert:"); $.observable(person.phones()).remove(0); getResult("remove:"); - $.observable(person.phones()).refresh([new Phone({number: "replacedPhone1"}), new Phone({number: "replacedPhone2"})]); + $.observable(person.phones()).refresh([new Phone({ number: "replacedPhone1" }), new Phone({ number: "replacedPhone2" })]); getResult("refresh:"); - $.observable(person.phones()).insert(1, [new Phone({number: "insertedPhone3a"}), new Phone({number: "insertedPhone3b"})]); + $.observable(person.phones()).insert(1, [new Phone({ number: "insertedPhone3a" }), new Phone({ number: "insertedPhone3b" })]); getResult("insert:"); - $.observable(person.phones()).move(1,3,2); + $.observable(person.phones()).move(1, 3, 2); getResult(" move:"); - $.observable(person).setProperty("phones", [new Phone({number: "replacedPhone1"})]); + $.observable(person).setProperty("phones", [new Phone({ number: "replacedPhone1" })]); getResult("\nSet>>"); - $.observable(person.phones()).insert(new Phone({number: "insertedPhoneX"})); + $.observable(person.phones()).insert(new Phone({ number: "insertedPhoneX" })); getResult("insert:"); $.observable(person.phones()).remove(0); getResult("remove:"); - $.observable(person.phones()).refresh([new Phone({number: "replacedPhoneX1"}), new Phone({number: "replacedPhoneX2"})]); + $.observable(person.phones()).refresh([new Phone({ number: "replacedPhoneX1" }), new Phone({ number: "replacedPhoneX2" })]); getResult("refresh:"); - $.observable(person.phones()).insert(1, [new Phone({number: "insertedPhoneX3a"}), new Phone({number: "insertedPhoneX3b"})]); + $.observable(person.phones()).insert(1, [new Phone({ number: "insertedPhoneX3a" }), new Phone({ number: "insertedPhoneX3b" })]); getResult("insert:"); - $.observable(person.phones()).move(1,3,2); + $.observable(person.phones()).move(1, 3, 2); getResult("move:"); $.observable(person).setProperty("phones", []); getResult("\nsetEmpty>>"); - $.observable(person.phones()).insert(new Phone({number: "insertedPhoneY"})); + $.observable(person.phones()).insert(new Phone({ number: "insertedPhoneY" })); getResult("insert:"); $("#result").empty(); @@ -8468,7 +11110,7 @@ test("MVVM", function() { "Array operations with getters allow complete functionality, and track the modified tree at all times"); // =============================== Arrange =============================== - person = new Person("pete", new Address("1st Ave"), [new Phone({number: "phone1"}), new Phone({number:"phone2"})]); + person = new Person("pete", new Address("1st Ave"), [new Phone({ number: "phone1" }), new Phone({ number: "phone2" })]); // ................................ Act .................................. ret = ""; @@ -8484,19 +11126,19 @@ test("MVVM", function() { + " " + $._data(person.phones()).events.arrayChange.length + " " + $._data(person.phones()[0]).events.propertyChange.length + "|"; - $.observable(person.phones()).insert(new Phone({number: "insertedPhone"})); + $.observable(person.phones()).insert(new Phone({ number: "insertedPhone" })); $.observable(person.phones()).remove(0); - $.observable(person.phones()).refresh([new Phone({number: "replacedPhone1"}), new Phone({number: "replacedPhone2"})]); - $.observable(person.phones()).insert(1, [new Phone({number: "insertedPhone3a"}), new Phone({number: "insertedPhone3b"})]); - $.observable(person.phones()).move(1,3,2); - $.observable(person).setProperty("phones", [new Phone({number: "replacedPhone1"})]); - $.observable(person.phones()).insert(new Phone({number: "insertedPhoneX"})); + $.observable(person.phones()).refresh([new Phone({ number: "replacedPhone1" }), new Phone({ number: "replacedPhone2" })]); + $.observable(person.phones()).insert(1, [new Phone({ number: "insertedPhone3a" }), new Phone({ number: "insertedPhone3b" })]); + $.observable(person.phones()).move(1, 3, 2); + $.observable(person).setProperty("phones", [new Phone({ number: "replacedPhone1" })]); + $.observable(person.phones()).insert(new Phone({ number: "insertedPhoneX" })); $.observable(person.phones()).remove(0); - $.observable(person.phones()).refresh([new Phone({number: "replacedPhoneX1"}), new Phone({number: "replacedPhoneX2"})]); - $.observable(person.phones()).insert(1, [new Phone({number: "insertedPhoneX3a"}), new Phone({number: "insertedPhoneX3b"})]); - $.observable(person.phones()).move(1,3,2); + $.observable(person.phones()).refresh([new Phone({ number: "replacedPhoneX1" }), new Phone({ number: "replacedPhoneX2" })]); + $.observable(person.phones()).insert(1, [new Phone({ number: "insertedPhoneX3a" }), new Phone({ number: "insertedPhoneX3b" })]); + $.observable(person.phones()).move(1, 3, 2); $.observable(person).setProperty("phones", []); - $.observable(person.phones()).insert(new Phone({number: "insertedPhoneY"})); + $.observable(person.phones()).insert(new Phone({ number: "insertedPhoneY" })); $.observable(person.phones()[0]).setProperty("number", "newNumber"); eventsCountAfterChanges = $._data(person).events.propertyChange.length @@ -8536,7 +11178,7 @@ test("MVVM", function() { + " " + !$._data(person.phones()).events + " " + $._data(person.phones()[0]).events.propertyChange.length + "|"; - $.observable(person.phones()).insert(new Phone({number: "insertedPhoneZ"})); + $.observable(person.phones()).insert(new Phone({ number: "insertedPhoneZ" })); $.observable(person.phones()[0]).setProperty("number", "newNumberZ"); $("#result").empty(); @@ -8559,6 +11201,7 @@ test("MVVM", function() { }); test("observeAll", function() { + reset(); // =============================== Arrange =============================== @@ -8578,10 +11221,10 @@ test("observeAll", function() { }, "person1.home.address.street": "upper St", "person1.home.address.ZIP": "33333", - things: [{thing: "tree"}] + things: [{ thing: "tree" }] }); - $.observable(model.things).insert({thing: "bush"}); - $.observable(model.things).refresh([model.things[1],model.things[0],model.things[1]]); + $.observable(model.things).insert({ thing: "bush" }); + $.observable(model.things).refresh([model.things[1], model.things[0], model.things[1]]); $.observable(model.things[2]).setProperty("thing", model.things[2].thing + "+"); // ............................... Assert ................................. @@ -8614,7 +11257,7 @@ test("observeAll", function() { equal(listeners, "1 1 1 1 1 1 1", 'Calling observeAll more than once does not add extra event bindings'); // ................................ Act .................................. - function cb2(ev, eventArgs) {} + function cb2(ev, eventArgs) { } $.observable(model).observeAll(cb2); @@ -8629,7 +11272,7 @@ test("observeAll", function() { equal(listeners, "2 2 2 2 2 2 2", 'Calling observeAll with a different callback adds one binding for the new callback on each object or array'); -// ................................ Act .................................. + // ................................ Act .................................. $.observable(model).unobserveAll(changeHandler); // ............................... Assert ................................. @@ -8673,7 +11316,7 @@ test("observeAll", function() { "true true true true true true true", 'unobserveAll() with no callback removes all bindings from the tree'); -// ................................ Act .................................. + // ................................ Act .................................. $.observable(model.things).observeAll(changeHandler); // ............................... Assert ................................. @@ -8696,6 +11339,7 @@ test("observeAll", function() { }); test("observeAll/unobserveAll using namespaces", function() { + reset(); // =============================== Arrange =============================== @@ -8715,10 +11359,10 @@ test("observeAll/unobserveAll using namespaces", function() { }, "person1.home.address.street": "upper St", "person1.home.address.ZIP": "33333", - things: [{thing: "tree"}] + things: [{ thing: "tree" }] }); - $.observable(model.things).insert({thing: "bush"}); - $.observable(model.things).refresh([model.things[1],model.things[0],model.things[1]]); + $.observable(model.things).insert({ thing: "bush" }); + $.observable(model.things).refresh([model.things[1], model.things[0], model.things[1]]); $.observable(model.things[2]).setProperty("thing", model.things[2].thing + "+"); // ............................... Assert ................................. @@ -8751,7 +11395,7 @@ test("observeAll/unobserveAll using namespaces", function() { equal(listeners, "1 1 1 1 1 1 1", 'Calling observeAll with namespace more than once does not add extra event bindings'); // ................................ Act .................................. - function cb2(ev, eventArgs) {} + function cb2(ev, eventArgs) { } $.observable(model).observeAll("my.nmspace", cb2); @@ -8908,24 +11552,25 @@ test("observeAll/unobserveAll using namespaces", function() { }); module("API - Settings"); -test("settings, error handlers, onError", function() { +test("Settings, error handlers, onError", function() { + // ................................ Act .................................. - $.views.settings.delimiters("@%","%@"); + $.views.settings.delimiters("@%", "%@"); var result = $.templates("A_@%if true%@yes@%/if%@_B").render(); - $.views.settings.delimiters("{{","}}"); + $.views.settings.delimiters("{{", "}}"); result += "|" + $.templates("A_{{if true}}YES{{/if}}_B").render(); // ............................... Assert ................................. equal(result, "A_yes_B|A_YES_B", "Custom delimiters with render()"); // ................................ Act .................................. - var app = {choose: true, name: "Jo"}; - $.views.settings.delimiters("_^","^_", "*"); + var app = { choose: true, name: "Jo" }; + $.views.settings.delimiters("_^", "^_", "*"); $.templates('_*^if choose^_
    _^else^_no
    _^/if^_').link("#result", app); result = $("#result").text(); - $.observable(app).setProperty({choose: false, name: "other"}); + $.observable(app).setProperty({ choose: false, name: "other" }); result += "|" + $("#result").text(); - $.views.settings.delimiters("{{","}}", "^"); + $.views.settings.delimiters("{{", "}}", "^"); $.templates('{^{if choose}}
    {{else}}NO
    {{/if}}').link("#result", app); result += "|" + $("#result").text(); @@ -8933,7 +11578,7 @@ test("settings, error handlers, onError", function() { equal(result, "Jo|noother2|NOother2", "Custom delimiters with link()"); // =============================== Arrange =============================== - app = {choose: true, name: "Jo", onerr:"invalid'Jo'"}; + app = { choose: true, name: "Jo", onerr: "invalid'Jo'" }; result = ""; var oldOnError = $.views.settings.onError; @@ -8990,6 +11635,7 @@ test("settings, error handlers, onError", function() { equal(result, "myErrFn for myErrFn for myErrFn for ", "onError handler in tags and in data-link expression, with override onError()"); // ................................ Reset .................................. + $("#result").empty(); $.views.settings({ onError: oldOnError }); @@ -8997,7 +11643,7 @@ test("settings, error handlers, onError", function() { module("API - Declarations"); -test("template encapsulation", function() { +test("Template encapsulation", function() { // =============================== Arrange =============================== $.templates({ @@ -9029,10 +11675,13 @@ test("template encapsulation", function() { }); // ................................ Act .................................. - $.link.myTmpl7("#result", {people: people, first: false}); + $.link.myTmpl7("#result", { people: people, first: false }); // ............................... Assert ................................. equal($("#result").text(), "NoisFooTwoOne", "Can access tag and helper resources from a nested context (i.e. inside {{if}} block)"); + + // ............................... Reset ................................. + $("#result").empty(); }); module("API - Views"); @@ -9180,6 +11829,9 @@ test("$.view() in regular content", function() { // ............................... Assert ................................. ok(itemView.index === 0, 'If elem is a container for a rendered array rendering nothing, and the array is not empty, $.view(elem, true, "item") returns the item view (even though the container element is empty)'); + + // ............................... Reset ................................. + $("#result").empty(); }); test("view.get() and view.getIndex() in regular content", function() { @@ -9210,6 +11862,8 @@ test("view.get() and view.getIndex() in regular content", function() { // ............................... Assert ................................. ok($.view("#1").getIndex() === 0 && $.view("#1", "item").index === 0 && $.view("#2").getIndex() === 1 && $.view("#2", "item").index === 1, '$.view(elem).getIndex() gets index of nearest item view'); + // ............................... Reset ................................. + $("#result").empty(); }); test("$.view() in element-only content", function() { @@ -9372,6 +12026,9 @@ test("$.view() in element-only content", function() { // ............................... Assert ................................. ok(itemView.index === 0, 'If elem is a container for a rendered array rendering nothing, and the array is not empty, $.view(elem, true, "item") returns the item view (even though the container element is empty)'); + + // ............................... Reset ................................. + $("#result").empty(); }); test("view.get() and view.getIndex() in element-only content", function() { @@ -9407,11 +12064,14 @@ test("view.get() and view.getIndex() in element-only content", function() { ok($.view("#sp1").getIndex() === 0 && $.view("#sp1", "item").index === 0 && $.view("#sp2").getIndex() === 1 && $.view("#sp2", "item").index === 1, '$.view(elem).getIndex() gets index of nearest item view, up through both elCnt and regular content views'); + // ............................... Reset ................................. + $("#result").empty(); }); module("API - Tag Controls"); test("view.childTags() and tag.childTags()", function() { + // =============================== Arrange =============================== $.link.boundTmplHierarchy("#result", topData); @@ -9501,6 +12161,8 @@ test("view.childTags() and tag.childTags()", function() { ok(tags.length === 2 && tags[0].tagName === "myWrap2" && tags[1].tagName === "myWrap2", 'tag.childTags(true) returns descendant tags, and skips any unbound tags'); + // ............................... Reset ................................. + $("#result").empty(); }); test("view.childTags() in element-only content", function() { @@ -9611,15 +12273,17 @@ test("view.childTags() in element-only content", function() { 'tag.childTags(true, "myTagName") returns descendant tags of chosen name, and skips any unbound tags'); // =============================== Arrange =============================== - $.templates("{^{for row}}{^{mySimpleWrap/}}{{/for}}
    ").link("#result", {row: {}}); + $.templates("{^{for row}}{^{mySimpleWrap/}}{{/for}}
    ").link("#result", { row: {} }); // ................................ Act .................................. var tag = $("#result tr").view().childTags()[0]; - // ............................... Assert ................................. + // ............................... Assert ................................. ok(tag.tagName === "mySimpleWrap", 'childTags() correctly finds tag which has no output and renders within element contet, inside another tag also in element content'); + // ............................... Reset ................................. + $("#result").empty(); }); test("view.childTags() in element-only content, using data-link", function() { @@ -9662,6 +12326,9 @@ test("view.childTags() in element-only content, using data-link", function() { // ............................... Assert ................................. ok(tags.length === 0, 'In element-only content, view.childTags(true, "myTagName") returns all tags of the given name within the view - in document order'); + + // ............................... Reset ................................. + $("#result").empty(); }); //TODO add tests for tag.refresh() @@ -9716,12 +12383,14 @@ test("Modifying content, initializing widgets/tag controls, using data-link", fu // ............................... Assert ................................. equal($("#result div").html(), " init before after", 'A data-linked tag control which does not render allows setting of content on the data-linked element during init, onBeforeLink and onAfterLink'); -//TODO: Add tests for attaching jQuery UI widgets or similar to tag controls, using data-link and {^{myTag}} inline data binding. + // ............................... Reset ................................. + $("#result").empty(); + //TODO: Add tests for attaching jQuery UI widgets or similar to tag controls, using data-link and {^{myTag}} inline data binding. }); test('two-way bound tag controls', function() { // =============================== Arrange =============================== - var person = {name: "Jo"}; + var person = { name: "Jo" }; cancelChange = false; noRenderOnUpdate = true; @@ -9745,7 +12414,7 @@ test('two-way bound tag controls', function() { // ................................ Act .................................. before = tag.value + linkedEl.value; - $.observable(person).setProperty({name: "newName"}); + $.observable(person).setProperty({ name: "newName" }); after = tag.value + linkedEl.value; // ............................... Assert ................................. @@ -9762,7 +12431,7 @@ test('two-way bound tag controls', function() { noRenderOnUpdate = false; before = tag.value + linkedEl.value; - $.observable(person).setProperty({name: "newName2"}); + $.observable(person).setProperty({ name: "newName2" }); after = tag.value + linkedEl.value; // ............................... Assert ................................. @@ -9780,7 +12449,7 @@ test('two-way bound tag controls', function() { renders = true; before = tag.value + linkedEl.value; - $.observable(person).setProperty({name: "newName3"}); + $.observable(person).setProperty({ name: "newName3" }); after = tag.value + linkedEl.value; // ............................... Assert ................................. @@ -9888,7 +12557,7 @@ test('two-way bound tag controls', function() { 'Data link using: - (tag.convert setting) - initial linking: converts the value on the target input'); // ................................ Act .................................. - $.observable(person).setProperty({name: "ANewName"}); + $.observable(person).setProperty({ name: "ANewName" }); // ............................... Assert ................................. equal(linkedEl.value + "|" + tag.value, @@ -9915,12 +12584,12 @@ test('two-way bound tag controls', function() { // =============================== Arrange =============================== var lower = function(val) { - return val.toLowerCase(); - }, + return val.toLowerCase(); + }, upper = function(val) { return val.toUpperCase(); }, - options = {cvt: upper}; + options = { cvt: upper }; // ................................ Act .................................. $.templates({ @@ -9930,7 +12599,7 @@ test('two-way bound tag controls', function() { return val.toUpperCase(); } } - }).link("#result", person, {options: options}); + }).link("#result", person, { options: options }); tag = $("#result").view(true).childTags("twoWayTag")[0]; linkedEl = $("#linkedEl")[0]; @@ -9941,8 +12610,8 @@ test('two-way bound tag controls', function() { 'Data link using: - converter specified by data-linked convert property'); // ................................ Act .................................. - $.observable(options).setProperty({cvt: lower}); - $.observable(person).setProperty({name: "ANewName"}); + $.observable(options).setProperty({ cvt: lower }); + $.observable(person).setProperty({ name: "ANewName" }); // ............................... Assert ................................. equal(linkedEl.value + "|" + tag.value, @@ -9950,7 +12619,7 @@ test('two-way bound tag controls', function() { 'Data link using: - data-linked swapping of converter from one function to another'); // ................................ Act .................................. - $.observable(options).setProperty({cvt: "myupper"}); + $.observable(options).setProperty({ cvt: "myupper" }); // ............................... Assert ................................. equal(linkedEl.value + "|" + tag.value, @@ -9982,7 +12651,7 @@ test('two-way bound tag controls', function() { // ................................ Act .................................. before = tag.value + linkedEl.value; - $.observable(person).setProperty({name: "newName"}); + $.observable(person).setProperty({ name: "newName" }); after = tag.value + linkedEl.value; // ............................... Assert ................................. @@ -9999,7 +12668,7 @@ test('two-way bound tag controls', function() { noRenderOnUpdate = false; before = tag.value + linkedEl.value; - $.observable(person).setProperty({name: "newName2"}); + $.observable(person).setProperty({ name: "newName2" }); // ............................... Assert ................................. equal(eventData + !!linkedEl.parentNode, "onUpdate render onBeforeLink onAfterLink false", @@ -10018,7 +12687,7 @@ test('two-way bound tag controls', function() { renders = true; before = tag.value + linkedEl.value; - $.observable(person).setProperty({name: "newName3"}); + $.observable(person).setProperty({ name: "newName3" }); after = tag.value + linkedEl.value; // ............................... Assert ................................. @@ -10128,7 +12797,7 @@ test('two-way bound tag controls', function() { 'Data link using: {^{twoWayTag name convert=\'myupper\'}} - (tag.convert setting) - initial linking: converts the value on the target input'); // ................................ Act .................................. - $.observable(person).setProperty({name: "ANewName"}); + $.observable(person).setProperty({ name: "ANewName" }); // ............................... Assert ................................. equal(linkedEl.value + "|" + tag.value, @@ -10171,7 +12840,7 @@ test('two-way bound tag controls', function() { linkedEl = tag.linkedElem[0]; before = tag.value + linkedEl.value; - $.observable(person).setProperty({name: "newName"}); + $.observable(person).setProperty({ name: "newName" }); after = tag.value + tag.linkedElem[0].value; // ............................... Assert ................................. @@ -10189,7 +12858,7 @@ test('two-way bound tag controls', function() { linkedEl = tag.linkedElem[0]; before = tag.value + linkedEl.value; - $.observable(person).setProperty({name: "newName2"}); + $.observable(person).setProperty({ name: "newName2" }); after = tag.value + tag.linkedElem[0].value; // ............................... Assert ................................. @@ -10208,7 +12877,7 @@ test('two-way bound tag controls', function() { linkedEl = tag.linkedElem[0]; before = tag.value + linkedEl.value; - $.observable(person).setProperty({name: "newName3"}); + $.observable(person).setProperty({ name: "newName3" }); after = tag.value + tag.linkedElem[0].value; // ............................... Assert ................................. @@ -10313,7 +12982,7 @@ test('two-way bound tag controls', function() { 'Data link using: {^{twoWayTag name convert="myupper"/}} - (tag.convert setting) - initial linking: converts the value on the target input'); // ................................ Act .................................. - $.observable(person).setProperty({name: "ANewName"}); + $.observable(person).setProperty({ name: "ANewName" }); // ............................... Assert ................................. equal(tag.linkedElem[0].value + "|" + tag.value, @@ -10342,7 +13011,7 @@ test('two-way bound tag controls', function() { tag = $("#result").view(true).childTags("twoWayTag")[0]; - $.observable(person).setProperty({name: "FirstName"}); + $.observable(person).setProperty({ name: "FirstName" }); handlers += "|" + events.mouseup.length + events.keydown.length; @@ -10398,7 +13067,7 @@ test('two-way bound tag controls', function() { events = $._data(linkedElem).events; handlers = "|" + events.mouseup.length + events.keydown.length; - $.observable(person).setProperty({name: "FirstName"}); + $.observable(person).setProperty({ name: "FirstName" }); handlers += "|" + events.mouseup.length + events.keydown.length; @@ -10454,7 +13123,7 @@ test('two-way bound tag controls', function() { events = $._data(linkedElem).events; handlers = "|" + events.mouseup.length + events.keydown.length; - $.observable(person).setProperty({name: "First Name"}); + $.observable(person).setProperty({ name: "First Name" }); handlers += "|" + events.mouseup.length + events.keydown.length; @@ -10510,7 +13179,7 @@ test('two-way bound tag controls', function() { events = $._data(linkedElem).events; handlers = "|" + events.mouseup.length + events.keydown.length; - $.observable(person).setProperty({name: "FirstName"}); + $.observable(person).setProperty({ name: "FirstName" }); handlers += "|" + events.mouseup.length + events.keydown.length; @@ -10555,11 +13224,11 @@ test('two-way bound tag controls', function() { 'Top-level data link using: : handlers are removed by $.unlink(true, container)'); }); -QUnit.asyncTest( "trigger=true - after keydown: ", function() { +QUnit.asyncTest("trigger=true - after keydown: ", function() { // =============================== Arrange =============================== var res = "", - person = {name: "Jo"}; + person = { name: "Jo" }; $.templates({ markup: '' @@ -10570,7 +13239,7 @@ QUnit.asyncTest( "trigger=true - after keydown: ", function() { var events = $._data(linkedElem).events, handlers = "|" + events.keydown.length; - $.observable(person).setProperty({name: "FirstName"}); + $.observable(person).setProperty({ name: "FirstName" }); events = $._data(linkedElem).events; handlers += "|" + events.keydown.length; @@ -10612,7 +13281,7 @@ QUnit.asyncTest("trigger=true - after keydown: {^{twoWayTag}}", function() { // =============================== Arrange =============================== var before = "", - person = {name: "Jo"}; + person = { name: "Jo" }; $.templates({ markup: '{^{twoWayTag name convert="myupper" convertBack=~lower trigger=true/}}', @@ -10667,7 +13336,7 @@ QUnit.asyncTest("trigger=true - after keydown: {^{textbox}}", function() { // =============================== Arrange =============================== var before = "", - person = {name: "Jo"}; + person = { name: "Jo" }; $.views.tags({ textbox: { @@ -10736,7 +13405,7 @@ QUnit.asyncTest("trigger=true - after keydown: {^{contentEditable}}", function() // =============================== Arrange =============================== var before = "", - person = {name: "Jo Smith"}; + person = { name: "Jo Smith" }; $.views.tags({ contentEditable: { @@ -10810,47 +13479,50 @@ QUnit.asyncTest("trigger=true - after keydown: {^{contentEditable}}", function() QUnit.start(); }, 0); + }); test('linkTo for {:source linkTo=target:} or {twoWayTag source linkTo=target}', function() { // =============================== Arrange =============================== - var before, after, person = {name: "Jo", name2: "Jo2"}, + var before, after, person = { name: "Jo", name2: "Jo2" }, cancelChange = false, eventData = ""; - $.views.tags({ twoWayTag: { - init: function(tagCtx, linkCtx) { - eventData += "init "; - if (this._.inline && !tagCtx.content) { - this.template = ""; - } - }, - render: function(val) { - eventData += "render "; - }, - onBeforeLink: function(tagCtx, linkCtx) { - eventData += "onBeforeLink "; - }, - onAfterLink: function(tagCtx, linkCtx) { - eventData += "onAfterLink "; - this.value = tagCtx.args[0]; - this.linkedElem = this.linkedElem || (this._.inline ? this.contents("input,div") : $(linkCtx.elem)); - }, - onUpdate: function(ev, eventArgs, tagCtxs) { - eventData += "onUpdate "; - return false; - }, - onBeforeChange: function(ev, eventArgs) { - eventData += "onBeforeChange "; - if (!cancelChange) { - this.value = eventArgs.value; + $.views.tags({ + twoWayTag: { + init: function(tagCtx, linkCtx) { + eventData += "init "; + if (this._.inline && !tagCtx.content) { + this.template = ""; + } + }, + render: function(val) { + eventData += "render "; + }, + onBeforeLink: function(tagCtx, linkCtx) { + eventData += "onBeforeLink "; + }, + onAfterLink: function(tagCtx, linkCtx) { + eventData += "onAfterLink "; + this.value = tagCtx.args[0]; + this.linkedElem = this.linkedElem || (this._.inline ? this.contents("input,div") : $(linkCtx.elem)); + }, + onUpdate: function(ev, eventArgs, tagCtxs) { + eventData += "onUpdate "; + return false; + }, + onBeforeChange: function(ev, eventArgs) { + eventData += "onBeforeChange "; + if (!cancelChange) { + this.value = eventArgs.value; + } + return !cancelChange; + }, + onDispose: function() { + eventData += "onDispose "; } - return !cancelChange; - }, - onDispose: function() { - eventData += "onDispose "; } - } }); + }); // ELEMENT-BASED DATA-LINKED TAGS ON INPUT - WITH linkTo EXPRESSION $.templates('') @@ -10861,7 +13533,7 @@ test('linkTo for {:source linkTo=target:} or {twoWayTag source linkTo=target}', // ................................ Act .................................. before = linkedEl.value; - $.observable(person).setProperty({name: "newName"}); + $.observable(person).setProperty({ name: "newName" }); after = linkedEl.value; // ............................... Assert ................................. @@ -10887,7 +13559,47 @@ test('linkTo for {:source linkTo=target:} or {twoWayTag source linkTo=target}', $(linkedEl).change(); after = "name:" + person.name + " name2:" + person.name2; + // =============================== Arrange =============================== + + person.name = "Jo"; + person.name2 = "Jo2"; + + $.templates('') + .link("#result", person); + + var linkedEl = $("#linkedEl")[0]; + + // ................................ Act .................................. + before = "value: " + linkedEl.value + " name:" + person.name + " name2:" + person.name2; + linkedEl.value = "3rdNewVal"; + $(linkedEl).change(); + after = "name:" + person.name + " name2:" + person.name2; + + // ............................... Assert ................................. + equal(before + "|" + after, + "value: initialValue name:Jo name2:Jo2|name:Jo name2:3rdNewVal", + 'Data link using: - Initializes to provided string, and binds updated value of linkedElem back to "linkTo" target'); + + // =============================== Arrange =============================== + person.name = "Jo"; + person.name2 = "Jo2"; + + $.templates('') + .link("#result", person); + + var linkedEl = $("#linkedEl")[0]; + + // ................................ Act .................................. + before = "value: " + linkedEl.value + " name:" + person.name + " name2:" + person.name2; + linkedEl.value = "4thNewVal"; + $(linkedEl).change(); + after = "name:" + person.name + " name2:" + person.name2; + + // ............................... Assert ................................. + equal(before + "|" + after, + "value: [object Object] name:Jo name2:Jo2|name:Jo name2:4thNewVal", + 'Data link using: - Initializes to current data item, and binds updated value of linkedElem back to "linkTo" target'); // =============================== Arrange =============================== @@ -10911,7 +13623,7 @@ test('linkTo for {:source linkTo=target:} or {twoWayTag source linkTo=target}', 'Data link using: - (tag.convert setting) - initial linking: converts the value on the target input'); // ................................ Act .................................. - $.observable(person).setProperty({name: "ANewName"}); + $.observable(person).setProperty({ name: "ANewName" }); // ............................... Assert ................................. equal(linkedEl.value + "|" + tag.value, @@ -11065,7 +13777,7 @@ test('linkTo for {:source linkTo=target:} or {twoWayTag source linkTo=target}', // ................................ Act .................................. before = tag.value + linkedEl.value; - $.observable(person).setProperty({name: "newName"}); + $.observable(person).setProperty({ name: "newName" }); after = tag.value + linkedEl.value; // ............................... Assert ................................. @@ -11139,7 +13851,7 @@ test('linkTo for {:source linkTo=target:} or {twoWayTag source linkTo=target}', 'Data link using: {^{twoWayTag name linkTo=name2 convert="myupper"}} - (tag.convert setting) - initial linking: converts the value on the target input'); // ................................ Act .................................. - $.observable(person).setProperty({name: "ANewName"}); + $.observable(person).setProperty({ name: "ANewName" }); // ............................... Assert ................................. equal(linkedEl.value + "|" + tag.value, @@ -11155,13 +13867,16 @@ test('linkTo for {:source linkTo=target:} or {twoWayTag source linkTo=target}', equal("name:" + person.name + " name2:" + person.name2 + " value:" + tag.value, "name:ANewName name2:changethename value:changethename", 'Data link using: {^{twoWayTag name linkTo=name2 convertBack=~lower}} - (tag.convertBack setting) on element change: converts the data, and sets on "linkTo" target data'); + + // ............................... Reset ................................. + $("#result").empty(); }); -test("tag control events", function() { +test("Tag control events", function() { // =============================== Arrange =============================== var eventData = ""; - model.things = [{thing: "box"}]; // reset Prop + model.things = [{ thing: "box" }]; // reset Prop // ................................ Act .................................. $.templates({ @@ -11224,7 +13939,7 @@ test("tag control events", function() { equal($("#result").text() + "|" + eventData, "Two 1 special| init render getType before after update render getType before after", '{^{myWidget/}} - Events fire in order during update: update, render, onBeforeLink and onAfterLink'); // ................................ Act .................................. - $.observable(model.things).insert(0, {thing: "tree"}); + $.observable(model.things).insert(0, { thing: "tree" }); // ............................... Assert ................................. equal($("#result").text() + "|" + eventData, "Two 1 special| init render getType before after update render getType before after onArrayChange", '{^{myWidget/}} - Events fire in order during update: update, render, onBeforeLink and onAfterLink'); @@ -11263,7 +13978,7 @@ test("tag control events", function() { // ............................... Assert ................................. equal($("#result").text() + "|" + eventData, "| init before after", '{^{myNoRenderWidget/}} - A data-linked tag control which does not render fires init, onBeforeLink and onAfterLink'); -//TODO: Add tests for attaching jQuery UI widgets or similar to tag controls, using data-link and {^{myTag}} inline data binding. + //TODO: Add tests for attaching jQuery UI widgets or similar to tag controls, using data-link and {^{myTag}} inline data binding. }); })();