Skip to content

Commit

Permalink
Merge pull request #2082 from canjs/each-index
Browse files Browse the repository at this point in the history
Makes %index work when a list has observables and makes ../ skip noContext scopes right.
  • Loading branch information
daffl committed Nov 30, 2015
2 parents eca344a + 3ab934c commit ea80088
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 40 deletions.
78 changes: 42 additions & 36 deletions view/scope/scope.js
Expand Up @@ -11,7 +11,7 @@ steal(
'can/list',
'can/view',
'can/compute', function (can, makeComputeData) {

/**
* @add can.view.Scope
*/
Expand All @@ -26,13 +26,13 @@ steal(
// - notContext - This can't be looked within using `./` and `../`. It will be skipped. This is
// for virtual contexts like those used by `%index`.
this._meta = meta || {};

// A cache that can be used to store computes used to look up within this scope.
// For example if someone creates a compute to lookup `name`, another compute does not
// need to be created.
this.__cache = {};
}

/**
* @static
*/
Expand All @@ -44,7 +44,7 @@ steal(
// ## Scope.Refs
// A special type of `can.Map` used for the references scope.
Refs: can.Map.extend({shortName: "ReferenceMap"},{}),

// ## Scope.refsScope
// A scope with a references scope in it and no parent.
refsScope: function(){
Expand All @@ -55,7 +55,7 @@ steal(
* @prototype
*/
can.simpleExtend(Scope.prototype,{

// ## Scope.prototype.add
// Creates a new scope and sets the current scope to be the parent.
// ```
Expand All @@ -72,7 +72,7 @@ steal(
return this;
}
},

// ## Scope.prototype.read
// Reads from the scope chain and returns the first non-`undefined` value.
// `read` deals mostly with setting up "context based" keys to start reading
Expand All @@ -92,7 +92,7 @@ steal(
if(attr === "%root") {
return { value: this.getRoot() };
}

// Identify context based keys. Context based keys try to
// specify a particular context a key should be within.
var isInCurrentContext = attr.substr(0, 2) === './',
Expand All @@ -103,25 +103,31 @@ steal(
isInParentContext ||
isCurrentContext ||
isParentContext;

// `notContext` contexts should be skipped if the key is "context based".
// For example, the context that holds `%index`.
if(isContextBased && this._meta.notContext) {
return this._parent.read(attr, options);
}

// If true, lookup stops after the current context.
var currentScopeOnly;

if(isInCurrentContext) {
// Stop lookup from checking parent scopes.
// Set flag to halt lookup from walking up scope.
currentScopeOnly = true;
attr = attr.substr(2);
}
else if (isInParentContext) {
// Read from the parent context.
return this._parent.read(attr.substr(3), options);
// walk up until we find a parent that can have context.
// the `isContextBased` check above won't catch it when you go from
// `../foo` to `foo` because `foo` isn't context based.
var parent = this._parent;
while(parent._meta.notContext) {
parent = parent._parent;
}
return parent.read(attr.substr(3), options);
}
else if ( isCurrentContext ) {
return {
Expand All @@ -133,7 +139,7 @@ steal(
value: this._parent._context
};
}

// if it's a reference scope, read from there.
var keyReads = can.compute.read.reads(attr);
if(keyReads[0].key.charAt(0) === "*") {
Expand All @@ -153,7 +159,7 @@ steal(
// 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 from `currentObserve`.
Expand All @@ -163,7 +169,7 @@ steal(
setObserveDepth = -1,
currentSetReads,
currentSetObserve,

readOptions = can.simpleExtend({
/* Store found observable, incase we want to set it as the rootObserve. */
foundObservable: function (observe, nameIndex) {
Expand All @@ -188,18 +194,18 @@ steal(
while (currentScope) {
currentContext = currentScope._context;



if ( currentContext !== null &&
// if its a primitive type, keep looking up the scope, since there won't be any properties
// if its a primitive type, keep looking up the scope, since there won't be any properties
(typeof currentContext === "object" || typeof currentContext === "function")
) {
) {

// Prevent computes from temporarily observing the reading of observables.
var getObserves = can.__trapObserves();

var data = can.compute.read(currentContext, keyReads, readOptions);

// Retrieve the observes that were read.
var observes = getObserves();
// If a **value was was found**, return value and location data.
Expand Down Expand Up @@ -238,15 +244,15 @@ steal(
value: undefined
};
},

// ## Scope.prototype.get
// Gets a value from the scope without being observable.
get: can.__notObserve(function (key, options) {

options = can.simpleExtend({
isArgument: true
}, options);

var res = this.read(key, options);
return res.value;
}),
Expand Down Expand Up @@ -282,7 +288,7 @@ steal(
getRoot: function(){
var cur = this,
child = this;

while(cur._parent) {
child = cur;
cur = cur._parent;
Expand All @@ -293,21 +299,21 @@ steal(
}
return cur._context;
},


// ## Scope.prototype.attr
// Gets or sets a value in the scope without being observable.
attr: can.__notObserve(function (key, value, options) {


options = can.simpleExtend({
isArgument: true
}, options);

// Allow setting a value on the context
if(arguments.length === 2) {
var lastIndex = key.lastIndexOf('.'),
// Either get the paren of a key or the current context object with `.`
// Either get the paren of a key or the current context object with `.`
readKey = lastIndex !== -1 ? key.substring(0, lastIndex) : '.',
obj = this.read(readKey, options).value;

Expand All @@ -320,10 +326,10 @@ steal(
} else {
return this.get(key, options);
}

}),



// ## Scope.prototype.computeData
// Finds the first location of the key in the scope and then provides a get-set compute that represents the key's value
Expand Down Expand Up @@ -368,9 +374,9 @@ steal(
}
}
});

can.view.Scope = Scope;

function Options(data, parent, meta){
if (!data.helpers && !data.partials && !data.tags) {
data = {
Expand All @@ -381,8 +387,8 @@ steal(
}
Options.prototype = new Scope();
Options.prototype.constructor = Options;

can.view.Options = Options;

return Scope;
});
12 changes: 8 additions & 4 deletions view/stache/mustache_helpers.js
Expand Up @@ -47,8 +47,9 @@ steal("can/util", "./utils.js","can/view/live",function(can, utils, live){
var cb = function (item, index, parentNodeList) {

return options.fn(options.scope.add({
"%index": index,
"@index": index
}).add(item), options.options, parentNodeList);
},{notContext: true}).add(item), options.options, parentNodeList);

};
live.list(el, items, cb, options.context, el.parentNode, nodeList, function(list, parentNodeList){
Expand All @@ -62,8 +63,9 @@ steal("can/util", "./utils.js","can/view/live",function(can, utils, live){
if ( !! expr && utils.isArrayLike(expr)) {
for (i = 0; i < expr.length; i++) {
result.push(options.fn(options.scope.add({
"%index": i,
"@index": i
})
},{notContext: true})
.add(expr[i])));
}
} else if (utils.isObserveLike(expr)) {
Expand All @@ -73,15 +75,17 @@ steal("can/util", "./utils.js","can/view/live",function(can, utils, live){
for (i = 0; i < keys.length; i++) {
key = keys[i];
result.push(options.fn(options.scope.add({
"%key": key,
"@key": key
})
},{notContext: true})
.add(expr[key])));
}
} else if (expr instanceof Object) {
for (key in expr) {
result.push(options.fn(options.scope.add({
"%key": key,
"@key": key
})
},{notContext: true})
.add(expr[key])));
}

Expand Down
25 changes: 25 additions & 0 deletions view/stache/stache_test.js
Expand Up @@ -4558,6 +4558,31 @@ steal("can-simple-dom", "can/util/vdom/build_fragment","can/view/stache", "can/v

});

test("Rendering live bound indicies with #each, @index and a simple can.List (#2067)", function () {
var list = new can.List([{value:'a'}, {value:'b'}, {value: 'c'}]);
var template = can.stache("<ul>{{#each list}}<li>{{%index}} {{value}}</li>{{/each}}</ul>");

var tpl = template({
list: list
}).firstChild;
//.getElementsByTagName('li');

var lis = tpl.getElementsByTagName('li');
equal(lis.length, 3, "three lis");

equal(innerHTML(lis[0]), '0 a', "first index and value are correct");
equal(innerHTML(lis[1]), '1 b', "second index and value are correct");
equal(innerHTML(lis[2]), '2 c', "third index and value are correct");

});

test("%index content should be skipped by ../ (#1554)", function(){
var list = new can.List(["a","b"]);
var tmpl = can.stache('{{#each items}}<li>{{.././items.indexOf .}}</li>{{/each}}');
var frag = tmpl({items: list});
equal(frag.lastChild.firstChild.nodeValue, "1", "read indexOf");
});

test("rendering style tag (#2035)",function(){
var map = new can.Map({color: 'green'});
var frag = can.stache('<style>body {color: {{color}} }</style>')(map);
Expand Down

0 comments on commit ea80088

Please sign in to comment.