Skip to content

Commit

Permalink
Merge pull request #161 from canjs/better-scope-props
Browse files Browse the repository at this point in the history
Adding scope.vm/scope.top, deprecating scope.root, adding scope.getPathsForKey
  • Loading branch information
phillipskevin committed May 15, 2018
2 parents c56760f + 5a52ba2 commit cf4e706
Show file tree
Hide file tree
Showing 2 changed files with 131 additions and 0 deletions.
83 changes: 83 additions & 0 deletions can-view-scope.js
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,80 @@ assign(Scope.prototype, {
return cur._context;
},

// first viewModel scope
getViewModel: function() {
var vmScope = this.getScope(function(scope) {
return scope._meta.viewModel;
});

return vmScope && vmScope._context;
},

// _top_ viewModel scope
getTop: function() {
var top;

this.getScope(function(scope) {
if (scope._meta.viewModel) {
top = scope;
}

// walk entire scope tree
return false;
});

return top && top._context;
},

// ## Scope.prototype.getPathsForKey
// Finds all paths that will return a value for a specific key
getPathsForKey: function getPathsForKey(key) {
var paths = {};

// scope.foo@bar -> bar
var keyParts = key.split(/\.|@/);
var scopeIndex = keyParts.indexOf("scope");

if (scopeIndex > -1) {
keyParts.splice(scopeIndex, 2);
}

var normalizedKey = keyParts.join(".");

// find specific paths (like ../key)
var cur = "";

this.getScope(function(scope) {
// `notContext` and `special` contexts can't be read using `../`
var canBeRead = !scope._meta.special && !scope._meta.notContext;

if (canBeRead && canReflect.hasKey(scope._context, normalizedKey)) {
paths[cur + normalizedKey] = scope._context;
}

cur += "../";

// walk entire scope tree
return false;
});

// check scope.vm.<key>
var vm = this.getViewModel();

if (vm && canReflect.hasKey(vm, normalizedKey)) {
paths["scope.vm." + normalizedKey] = vm;
}

// check scope.top.<key>
var top = this.getTop();

if (top && canReflect.hasKey(top, normalizedKey)) {
paths["scope.top." + normalizedKey] = top;
}

return paths;
},

// ## Scope.prototype.getDataForScopeSet
// Returns an object with data needed by `.set` to figure out what to set,
// and how.
Expand Down Expand Up @@ -604,9 +678,18 @@ defineLazyValue(Scope.prototype, 'templateContext', function() {
});

defineLazyValue(Scope.prototype, 'root', function() {
canLog.warn('`scope.root` is deprecated. Use either `scope.top` or `scope.vm` instead.');
return this.getRoot();
});

defineLazyValue(Scope.prototype, 'vm', function() {
return this.getViewModel();
});

defineLazyValue(Scope.prototype, 'top', function() {
return this.getTop();
});

defineLazyValue(Scope.prototype, 'helpers', function() {
return stacheHelpers;
});
Expand Down
48 changes: 48 additions & 0 deletions test/scope-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1260,3 +1260,51 @@ QUnit.test("debugger is a reserved scope key for calling debugger helper", funct

delete canStacheHelpers["debugger"];
});

QUnit.test("scope.vm and scope.top", function() {
var scope = new Scope({ name: "foo" })
.add({ name: "Kevin" }, { viewModel: true }) // top
.add({ name: "bar" }) // intermediate
.add({ name: "Ryan" }, { viewModel: true }) // vm
.add({ name: "baz" });

QUnit.equal(scope.read("scope.vm.name").value, "Ryan", "scope.first can be used to read from the _first_ context with viewModel: true");
QUnit.equal(scope.read("scope.top.name").value, "Kevin", "scope.top can be used to read from the _top_ context with viewModel: true");
});

testHelpers.dev.devOnlyTest("scope.root deprecation warning", function() {
var teardown = testHelpers.dev.willWarn(/`scope.root` is deprecated/);

var scope = new Scope({ foo: "bar" });
scope.read("scope.root");

QUnit.equal(teardown(), 1, "deprecation warning displayed");
});

QUnit.test("scope.getPathsForKey", function() {
var top = {};
top[canSymbol.for("can.hasKey")] = function(key) {
return key === "name";
};

var vm = { name: "Ryan" };
var nonVm = { name: "Bianca" };
var notContext = { index: 0 };
var special = { myIndex: 0 };

var scope = new Scope(top, null, { viewModel: true })
.add(notContext, { notContext: true })
.add(vm, { viewModel: true })
.add(special, { special: true })
.add(nonVm);

var paths = scope.getPathsForKey("name");

QUnit.deepEqual(paths, {
"scope.vm.name": vm,
"scope.top.name": top,
"name": nonVm,
"../../name": vm,
"../../../../name": top
});
});

0 comments on commit cf4e706

Please sign in to comment.