Skip to content

Commit 8dc17b7

Browse files
committed
allow arbitrary expressions in each block keys - fixes #703
1 parent dbab1a8 commit 8dc17b7

File tree

6 files changed

+71
-30
lines changed

6 files changed

+71
-30
lines changed

src/compile/nodes/EachBlock.ts

+13-7
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export default class EachBlock extends Node {
1616
iterations: string;
1717
index: string;
1818
context: string;
19-
key: string;
19+
key: Expression;
2020
scope: TemplateScope;
2121
destructuredContexts: string[];
2222

@@ -29,7 +29,10 @@ export default class EachBlock extends Node {
2929
this.expression = new Expression(compiler, this, scope, info.expression);
3030
this.context = info.context;
3131
this.index = info.index;
32-
this.key = info.key;
32+
33+
this.key = info.key
34+
? new Expression(compiler, this, scope, info.key)
35+
: null;
3336

3437
this.scope = scope.child();
3538

@@ -262,7 +265,7 @@ export default class EachBlock extends Node {
262265
mountOrIntro,
263266
}
264267
) {
265-
const key = block.getUniqueName('key');
268+
const get_key = block.getUniqueName('get_key');
266269
const blocks = block.getUniqueName(`${each}_blocks`);
267270
const lookup = block.getUniqueName(`${each}_lookup`);
268271

@@ -282,11 +285,14 @@ export default class EachBlock extends Node {
282285
}
283286

284287
block.builders.init.addBlock(deindent`
288+
const ${get_key} = ctx => ${this.key.snippet};
289+
285290
for (var #i = 0; #i < ${each_block_value}.${length}; #i += 1) {
286-
var ${key} = ${each_block_value}[#i].${this.key};
287-
${blocks}[#i] = ${lookup}[${key}] = ${create_each_block}(#component, ${key}, @assign(@assign({}, ctx), {
291+
let child_ctx = @assign(@assign({}, ctx), {
288292
${this.contextProps.join(',\n')}
289-
}));
293+
});
294+
let key = ${get_key}(child_ctx);
295+
${blocks}[#i] = ${lookup}[key] = ${create_each_block}(#component, key, child_ctx);
290296
}
291297
`);
292298

@@ -313,7 +319,7 @@ export default class EachBlock extends Node {
313319
block.builders.update.addBlock(deindent`
314320
var ${each_block_value} = ${snippet};
315321
316-
${blocks} = @updateKeyedEach(${blocks}, #component, changed, "${this.key}", ${dynamic ? '1' : '0'}, ${each_block_value}, ${lookup}, ${updateMountNode}, ${String(this.block.hasOutroMethod)}, ${create_each_block}, "${mountOrIntro}", ${anchor}, function(#i) {
322+
${blocks} = @updateKeyedEach(${blocks}, #component, changed, ${get_key}, ${dynamic ? '1' : '0'}, ${each_block_value}, ${lookup}, ${updateMountNode}, ${String(this.block.hasOutroMethod)}, ${create_each_block}, "${mountOrIntro}", ${anchor}, function(#i) {
317323
return @assign(@assign({}, ctx), {
318324
${this.contextProps.join(',\n')}
319325
});

src/parse/state/mustache.ts

+1-17
Original file line numberDiff line numberDiff line change
@@ -299,23 +299,7 @@ export default function mustache(parser: Parser) {
299299
if (parser.eat('(')) {
300300
parser.allowWhitespace();
301301

302-
const expression = readExpression(parser);
303-
304-
// TODO eventually, we should accept any expression, and turn
305-
// it into a function. For now, assume that every expression
306-
// follows the `foo.id` pattern, and equates to `@id`
307-
if (
308-
expression.type !== 'MemberExpression' ||
309-
expression.property.computed ||
310-
expression.property.type !== 'Identifier'
311-
) {
312-
parser.error({
313-
code: `invalid-key`,
314-
message: 'invalid key'
315-
}, expression.start);
316-
}
317-
318-
block.key = expression.property.name;
302+
block.key = readExpression(parser);
319303
parser.allowWhitespace();
320304
parser.eat(')', true);
321305
parser.allowWhitespace();

src/shared/keyed-each.js

+5-4
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export function outroAndDestroyBlock(block, lookup) {
1010
});
1111
}
1212

13-
export function updateKeyedEach(old_blocks, component, changed, key_prop, dynamic, list, lookup, node, has_outro, create_each_block, intro_method, next, get_context) {
13+
export function updateKeyedEach(old_blocks, component, changed, get_key, dynamic, list, lookup, node, has_outro, create_each_block, intro_method, next, get_context) {
1414
var o = old_blocks.length;
1515
var n = list.length;
1616

@@ -24,14 +24,15 @@ export function updateKeyedEach(old_blocks, component, changed, key_prop, dynami
2424

2525
var i = n;
2626
while (i--) {
27-
var key = list[i][key_prop];
27+
var ctx = get_context(i);
28+
var key = get_key(ctx);
2829
var block = lookup[key];
2930

3031
if (!block) {
31-
block = create_each_block(component, key, get_context(i));
32+
block = create_each_block(component, key, ctx);
3233
block.c();
3334
} else if (dynamic) {
34-
block.p(changed, get_context(i));
35+
block.p(changed, ctx);
3536
}
3637

3738
new_blocks[i] = new_lookup[key] = block;

test/parser/samples/each-block-keyed/output.json

+18-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
{
2-
"hash": "1x6az5m",
32
"html": {
43
"start": 0,
54
"end": 54,
@@ -38,7 +37,24 @@
3837
}
3938
],
4039
"context": "todo",
41-
"key": "id"
40+
"key": {
41+
"type": "MemberExpression",
42+
"start": 22,
43+
"end": 29,
44+
"object": {
45+
"type": "Identifier",
46+
"start": 22,
47+
"end": 26,
48+
"name": "todo"
49+
},
50+
"property": {
51+
"type": "Identifier",
52+
"start": 27,
53+
"end": 29,
54+
"name": "id"
55+
},
56+
"computed": false
57+
}
4258
}
4359
]
4460
},
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
export default {
2+
data: {
3+
words: ['foo', 'bar', 'baz']
4+
},
5+
6+
html: `
7+
<p>foo</p>
8+
<p>bar</p>
9+
<p>baz</p>
10+
`,
11+
12+
test(assert, component, target) {
13+
const [p1, p2, p3] = target.querySelectorAll('p');
14+
15+
component.set({
16+
words: ['foo', 'baz'],
17+
});
18+
19+
assert.htmlEqual(target.innerHTML, `
20+
<p>foo</p>
21+
<p>baz</p>
22+
`);
23+
24+
const [p4, p5] = target.querySelectorAll('p');
25+
26+
assert.ok(!target.contains(p2), '<p> element should be removed');
27+
28+
assert.equal(p1, p4, 'first <p> element should be retained');
29+
assert.equal(p3, p5, 'last <p> element should be retained');
30+
},
31+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{#each words as word (word)}
2+
<p>{word}</p>
3+
{/each}

0 commit comments

Comments
 (0)