diff --git a/compute/get_value_and_bind.js b/compute/get_value_and_bind.js index 394d57f3030..3a8fcec4c29 100644 --- a/compute/get_value_and_bind.js +++ b/compute/get_value_and_bind.js @@ -95,7 +95,7 @@ steal("can/util", function(){ can.__clearObserved = can.__clearReading = function () { if (observedStack.length) { var ret = observedStack[observedStack.length-1]; - observedStack[observedStack.length-1] = {observed: {}}; + observedStack[observedStack.length-1] = {names: "", observed: {}}; return ret; } }; @@ -108,7 +108,9 @@ steal("can/util", function(){ can.__addObserved = can.__addReading = function(o){ if (observedStack.length) { - can.simpleExtend(observedStack[observedStack.length-1], o); + var last = observedStack[observedStack.length-1]; + can.simpleExtend(last.observed, o.observed); + last.names += o.names; } }; diff --git a/test/pluginified/2.0.5.test.js b/test/pluginified/2.0.5.test.js index 02222067056..a5b1852b783 100644 --- a/test/pluginified/2.0.5.test.js +++ b/test/pluginified/2.0.5.test.js @@ -13618,30 +13618,7 @@ var __m78 = (function () { ok(fooInfo.parent === top, "we pick the current if we have no leads"); })*/ - test('use highest default observe in stack unless you\'ve found your way in something that does exist', function () { - var bottom = new can.Map({ - name: { - first: 'Justin' - } - }); - var middle = new can.Map({ - name: { - first: 'Brian' - } - }); - var top = new can.Map({ - title: 'top' - }); - var cur = new can.view.Scope(bottom) - .add(middle) - .add(top); - var lastNameInfo = cur.read('name.last', {}); - ok(lastNameInfo.rootObserve === middle, 'pick the default observe with the highest depth'); - deepEqual(lastNameInfo.reads, [ - 'name', - 'last' - ], 'pick the default observe with the highest depth'); - }); + /* test("use observe like objects, e.g. can.route, within scope properly", function() { var expected = "video" var cur = new can.view.Scope({}).add(can.route); diff --git a/view/callbacks/callbacks.js b/view/callbacks/callbacks.js index e234b79038e..abbe6788828 100644 --- a/view/callbacks/callbacks.js +++ b/view/callbacks/callbacks.js @@ -73,9 +73,9 @@ steal("can/util", "can/view",function(can){ res; if(tagCallback) { - var reads = can.__clearReading(); + var reads = can.__clearObserved(); res = tagCallback(el, tagData); - can.__setReading(reads); + can.__setObserved(reads); } else { res = scope; } diff --git a/view/scope/compute_data.js b/view/scope/compute_data.js index c2c215f121d..200a9d93fbb 100644 --- a/view/scope/compute_data.js +++ b/view/scope/compute_data.js @@ -38,12 +38,14 @@ steal("can/util","can/compute","can/compute/get_value_and_bind.js",function(can, }; var scopeReader = function(scope, key, options, computeData, newVal){ if (arguments.length > 4) { - if(computeData.root.isComputed) { - computeData.root(newVal); + var root = computeData.root || computeData.setRoot; + + if(root.isComputed) { + root(newVal); } else if(computeData.reads.length) { var last = computeData.reads.length - 1; - var obj = computeData.reads.length ? can.compute.read(computeData.root, computeData.reads.slice(0, last)).value - : computeData.root; + var obj = computeData.reads.length ? can.compute.read(root, computeData.reads.slice(0, last)).value + : root; can.compute.set(obj, computeData.reads[last], newVal, options); } // **Compute getter** @@ -64,6 +66,7 @@ steal("can/util","can/compute","can/compute/get_value_and_bind.js",function(can, computeData.initialValue = data.value; computeData.reads = data.reads; computeData.root = data.rootObserve; + computeData.setRoot = data.setRoot; return data.value; } }; diff --git a/view/scope/scope.js b/view/scope/scope.js index 319538d8573..1e927d9928c 100644 --- a/view/scope/scope.js +++ b/view/scope/scope.js @@ -174,32 +174,19 @@ steal( context, // The current scope. scope = this, - // While we are looking for a value, we track the most likely place this value will be found. - // This is so if there is no me.name.first, we setup a listener on me.name. - // The most likely candidate is the one with the most "read matches" "lowest" in the - // context chain. - // By "read matches", we mean the most number of values along the key. - // By "lowest" in the context chain, we mean the closest to the current context. - // We track the starting position of the likely place with `defaultObserve`. - defaultObserve, - // Tracks how to read from the defaultObserve. - defaultReads = [], - // Tracks the highest found number of "read matches". - defaultPropertyDepth = -1, - // `scope.read` is designed to be called within a compute, but - // for performance reasons only listens to observables within one context. - // This is to say, if you have me.name in the current context, but me.name.first and - // we are looking for me.name.first, we don't setup bindings on me.name and me.name.first. - // To make this happen, we clear readings if they do not find a value. But, - // if that path turns out to be the default read, we need to restore them. This - // variable remembers those reads so they can be restored. - defaultComputeReadings, - // Tracks the default's scope. - defaultScope, + + // If no value can be found, this is a list of of every observed + // object and property name to observe. + undefinedObserves = [], // Tracks the first found observe. currentObserve, // Tracks the reads to get the value for a scope. - currentReads; + currentReads, + + // Tracks the most likely observable to use as a setter. + setObserveDepth = -1, + currentSetReads, + currentSetObserve; // Goes through each scope context provided until it finds the key (attr). Once the key is found // then it's value is returned along with an observe, the current scope and reads. @@ -218,16 +205,11 @@ steal( currentObserve = observe; currentReads = names.slice(nameIndex); }, - // Called when we were unable to find a value. earlyExit: function (parentValue, nameIndex) { - /* If this has more matching values */ - if (nameIndex > defaultPropertyDepth) { - defaultObserve = currentObserve; - defaultReads = currentReads; - defaultPropertyDepth = nameIndex; - defaultScope = scope; - /* Clear and save readings so next attempt does not use these readings */ - defaultComputeReadings = can.__clearReading(); + if (nameIndex > setObserveDepth) { + currentSetObserve = currentObserve; + currentSetReads = currentReads; + setObserveDepth = nameIndex; } }, // Execute anonymous functions found along the way @@ -241,10 +223,12 @@ steal( value: data.value, reads: currentReads }; + } else { + // save all old readings before we try the next scope + undefinedObserves.push( can.__clearObserved() ); } } - // Prevent prior readings and then move up to the next scope. - can.__clearReading(); + if(!stopLookup) { // Move up to the next scope. scope = scope._parent; @@ -253,24 +237,21 @@ steal( } } - // **Key was not found**, return undefined for the value. Unless an observable was - // found in the process of searching for the key, then return the most likely observable along with it's - // scope and reads. - - if (defaultObserve) { - can.__setReading(defaultComputeReadings); - return { - scope: defaultScope, - rootObserve: defaultObserve, - reads: defaultReads, - value: undefined - }; - } else { - return { - names: names, - value: undefined - }; + // **Key was not found**, return undefined for the value. + // Make sure we listen to everything we checked for when the value becomes defined. + // Once it becomes defined, we won't have to listen to so many things. + var len = undefinedObserves.length; + if (len) { + for(var i = 0; i < len; i++) { + can.__addObserved(undefinedObserves[i]); + } } + return { + setRoot: currentSetObserve, + reads: currentSetReads, + value: undefined + }; + } }); diff --git a/view/scope/scope_test.js b/view/scope/scope_test.js index ebc1e6dbeb5..cdc6cb8d896 100644 --- a/view/scope/scope_test.js +++ b/view/scope/scope_test.js @@ -1,26 +1,21 @@ steal("can/view/scope", "can/route", "can/test", "steal-qunit", function () { QUnit.module('can/view/scope'); - /* test("basics",function(){ - - var items = { people: [{name: "Justin"},[{name: "Brian"}]], count: 1000 }; - - var itemsScope = new can.view.Scope(items), - arrayScope = new can.view.Scope(itemsScope.attr('people'), itemsScope), - firstItem = new can.view.Scope( arrayScope.attr('0'), arrayScope ); - - var nameInfo = firstItem.get('name'); - equal(nameInfo.name, "name"); - equal(nameInfo.scope, firstItem); - equal(nameInfo.value,"Justin"); - equal(nameInfo.parent, items.people[0]); + + test("basics",function(){ - var countInfo = firstItem.get('count'); - equal( countInfo.name, "count" ); - equal( countInfo.scope, itemsScope ); - equal(countInfo.value,1000); - equal(countInfo.parent, items); + var items = new can.Map({ people: [{name: "Justin"},[{name: "Brian"}]], count: 1000 }); + + var itemsScope = new can.view.Scope(items), + arrayScope = new can.view.Scope(itemsScope.attr('people'), itemsScope), + firstItem = new can.view.Scope( arrayScope.attr('0'), arrayScope ); + + var nameInfo = firstItem.read('name'); + deepEqual(nameInfo.reads, ["name"]); + equal(nameInfo.scope, firstItem); + equal(nameInfo.value,"Justin"); + equal(nameInfo.rootObserve, items.people[0]); - });*/ + }); /* * REMOVE test("adding items",function(){ @@ -111,54 +106,7 @@ steal("can/view/scope", "can/route", "can/test", "steal-qunit", function () { equal(cur.attr('..'), row, 'got row'); equal(cur.attr('../first'), 'Justin', 'got row'); }); - /* test("use highest default observe in stack", function(){ - var bottom = new can.Map({ - name: "bottom" - }); - var top = new can.Map({ - name: "top" - }); - - var base = new can.view.Scope( bottom ), - cur = base.add(top); - - var fooInfo = cur.get("foo"); - ok(fooInfo.parent === top, "we pick the current if we have no leads"); - })*/ - test('use highest default observe in stack unless you\'ve found your way in something that does exist', function () { - var bottom = new can.Map({ - name: { - first: 'Justin' - } - }); - var middle = new can.Map({ - name: { - first: 'Brian' - } - }); - var top = new can.Map({ - title: 'top' - }); - var cur = new can.view.Scope(bottom) - .add(middle) - .add(top); - var lastNameInfo = cur.read('name.last', {}); - ok(lastNameInfo.rootObserve === middle, 'pick the default observe with the highest depth'); - deepEqual(lastNameInfo.reads, [ - 'name', - 'last' - ], 'pick the default observe with the highest depth'); - }); - /* test("use observe like objects, e.g. can.route, within scope properly", function() { - var expected = "video" - var cur = new can.view.Scope({}).add(can.route); - can.route.attr('type', expected); - var type = cur.get('type'); - - equal(type.value, expected); - equal(type.parent, can.route); - })*/ test('nested properties with compute', function () { var me = new can.Map({ name: { @@ -425,5 +373,25 @@ steal("can/view/scope", "can/route", "can/test", "steal-qunit", function () { equal(compute(), 5, "updated compute value"); equal( computeData.compute(), 5, "the compute has the right value"); }); + + test("computesData can find update when initially undefined parent scope becomes defined (#579)", function(){ + expect(2); + + var map = new can.Map(); + var scope = new can.view.Scope(map); + var top = scope.add(new can.Map()); + + var computeData = top.computeData("value",{}); + + equal( computeData.compute(), undefined, "initially undefined"); + + computeData.compute.bind("change", function(ev, newVal){ + equal(newVal, "first"); + }); + + map.attr("value","first"); + + + }); }); diff --git a/view/stache/mustache_core.js b/view/stache/mustache_core.js index 20015c6022f..b8c7858b355 100644 --- a/view/stache/mustache_core.js +++ b/view/stache/mustache_core.js @@ -498,9 +498,9 @@ steal("can/util", // we hide any observables read in the function by saving any observables that // have been read and then setting them back which overwrites any `can.__observe` calls // performed in value. - var old = can.__clearReading(); + var old = can.__clearObserved(); value(this); - can.__setReading(old); + can.__setObserved(old); } // If the compute has observable dependencies, setup live binding.