From b767227a2fc3b70e5b3a375f38dc4e8556fee060 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 29 Apr 2018 17:28:51 -0400 Subject: [PATCH] bind to destructured properties --- src/compile/dom/Block.ts | 10 ++-- src/compile/nodes/Binding.ts | 5 +- src/compile/nodes/Component.ts | 5 +- src/compile/nodes/EachBlock.ts | 47 +++++++++---------- src/compile/nodes/Fragment.ts | 3 +- src/parse/read/context.ts | 2 +- .../_config.js | 39 +++++++++++++++ .../main.html | 5 ++ 8 files changed, 76 insertions(+), 40 deletions(-) create mode 100644 test/runtime/samples/each-block-destructured-object-binding/_config.js create mode 100644 test/runtime/samples/each-block-destructured-object-binding/main.html diff --git a/src/compile/dom/Block.ts b/src/compile/dom/Block.ts index f41892ccc9..11f34268b3 100644 --- a/src/compile/dom/Block.ts +++ b/src/compile/dom/Block.ts @@ -9,8 +9,7 @@ export interface BlockOptions { compiler?: Compiler; comment?: string; key?: string; - indexNames?: Map; - listNames?: Map; + bindings?: Map; dependencies?: Set; } @@ -23,8 +22,8 @@ export default class Block { first: string; dependencies: Set; - indexNames: Map; - listNames: Map; + + bindings: Map; builders: { init: CodeBuilder; @@ -62,8 +61,7 @@ export default class Block { this.dependencies = new Set(); - this.indexNames = options.indexNames; - this.listNames = options.listNames; + this.bindings = options.bindings; this.builders = { init: new CodeBuilder(), diff --git a/src/compile/nodes/Binding.ts b/src/compile/nodes/Binding.ts index 3c56da0f43..c282cf1a9d 100644 --- a/src/compile/nodes/Binding.ts +++ b/src/compile/nodes/Binding.ts @@ -195,14 +195,13 @@ function getEventHandler( ? getTailSnippet(binding.value.node) : ''; - const list = `ctx.${block.listNames.get(name)}`; - const index = `ctx.${block.indexNames.get(name)}`; + const head = block.bindings.get(name); return { usesContext: true, usesState: true, usesStore: storeDependencies.length > 0, - mutation: `${list}[${index}]${tail} = ${value};`, + mutation: `${head}${tail} = ${value};`, props: dependencies.map(prop => `${prop}: ctx.${prop}`), storeProps: storeDependencies.map(prop => `${prop}: $.${prop}`) }; diff --git a/src/compile/nodes/Component.ts b/src/compile/nodes/Component.ts index d40466a052..1a872fe6ce 100644 --- a/src/compile/nodes/Component.ts +++ b/src/compile/nodes/Component.ts @@ -239,12 +239,11 @@ export default class Component extends Node { const computed = isComputed(binding.value.node); const tail = binding.value.node.type === 'MemberExpression' ? getTailSnippet(binding.value.node) : ''; - const list = block.listNames.get(key); - const index = block.indexNames.get(key); + const head = block.bindings.get(key); const lhs = binding.value.node.type === 'MemberExpression' ? binding.value.snippet - : `ctx.${list}[ctx.${index}]${tail} = childState.${binding.name}`; + : `${head}${tail} = childState.${binding.name}`; setFromChild = deindent` ${lhs} = childState.${binding.name}; diff --git a/src/compile/nodes/EachBlock.ts b/src/compile/nodes/EachBlock.ts index 3bf6f08789..6d771dbf87 100644 --- a/src/compile/nodes/EachBlock.ts +++ b/src/compile/nodes/EachBlock.ts @@ -76,15 +76,16 @@ export default class EachBlock extends Node { name: this.compiler.getUniqueName('create_each_block'), key: this.key, - indexNames: new Map(block.indexNames), - listNames: new Map(block.listNames) + bindings: new Map(block.bindings) }); - const listName = this.compiler.getUniqueName('each_value'); + this.each_block_value = this.compiler.getUniqueName('each_value'); + const indexName = this.index || this.compiler.getUniqueName(`${this.context}_index`); - this.block.indexNames.set(this.context, indexName); - this.block.listNames.set(this.context, listName); + this.contexts.forEach(prop => { + this.block.bindings.set(prop.key.name, `ctx.${this.each_block_value}[ctx.${indexName}]${prop.tail}`); + }); if (this.index) { this.block.getUniqueName(this.index); // this prevents name collisions (#1254) @@ -94,7 +95,7 @@ export default class EachBlock extends Node { // TODO only add these if necessary this.contextProps.push( - `${listName}: list`, + `${this.each_block_value}: list`, `${indexName}: i` ); @@ -131,7 +132,6 @@ export default class EachBlock extends Node { const each = this.var; const create_each_block = this.block.name; - const each_block_value = this.block.listNames.get(this.context); const iterations = this.iterations; const needsAnchor = this.next ? !this.next.isDomNode() : !parentNode || !this.parent.isDomNode(); @@ -150,7 +150,6 @@ export default class EachBlock extends Node { const vars = { each, create_each_block, - each_block_value, length, iterations, anchor, @@ -159,7 +158,7 @@ export default class EachBlock extends Node { const { snippet } = this.expression; - block.builders.init.addLine(`var ${each_block_value} = ${snippet};`); + block.builders.init.addLine(`var ${this.each_block_value} = ${snippet};`); this.compiler.target.blocks.push(deindent` function ${this.get_each_context}(ctx, list, i) { @@ -191,7 +190,7 @@ export default class EachBlock extends Node { // TODO neaten this up... will end up with an empty line in the block block.builders.init.addBlock(deindent` - if (!${each_block_value}.${length}) { + if (!${this.each_block_value}.${length}) { ${each_block_else} = ${this.else.block.name}(#component, ctx); ${each_block_else}.c(); } @@ -207,9 +206,9 @@ export default class EachBlock extends Node { if (this.else.block.hasUpdateMethod) { block.builders.update.addBlock(deindent` - if (!${each_block_value}.${length} && ${each_block_else}) { + if (!${this.each_block_value}.${length} && ${each_block_else}) { ${each_block_else}.p(changed, ctx); - } else if (!${each_block_value}.${length}) { + } else if (!${this.each_block_value}.${length}) { ${each_block_else} = ${this.else.block.name}(#component, ctx); ${each_block_else}.c(); ${each_block_else}.${mountOrIntro}(${initialMountNode}, ${anchor}); @@ -221,7 +220,7 @@ export default class EachBlock extends Node { `); } else { block.builders.update.addBlock(deindent` - if (${each_block_value}.${length}) { + if (${this.each_block_value}.${length}) { if (${each_block_else}) { ${each_block_else}.u(); ${each_block_else}.d(); @@ -263,7 +262,6 @@ export default class EachBlock extends Node { { each, create_each_block, - each_block_value, length, anchor, mountOrIntro, @@ -291,8 +289,8 @@ export default class EachBlock extends Node { block.builders.init.addBlock(deindent` const ${get_key} = ctx => ${this.key.snippet}; - for (var #i = 0; #i < ${each_block_value}.${length}; #i += 1) { - let child_ctx = ${this.get_each_context}(ctx, ${each_block_value}, #i); + for (var #i = 0; #i < ${this.each_block_value}.${length}; #i += 1) { + let child_ctx = ${this.get_each_context}(ctx, ${this.each_block_value}, #i); let key = ${get_key}(child_ctx); ${blocks}[#i] = ${lookup}[key] = ${create_each_block}(#component, key, child_ctx); } @@ -319,9 +317,9 @@ export default class EachBlock extends Node { const dynamic = this.block.hasUpdateMethod; block.builders.update.addBlock(deindent` - var ${each_block_value} = ${snippet}; + var ${this.each_block_value} = ${snippet}; - ${blocks} = @updateKeyedEach(${blocks}, #component, changed, ${get_key}, ${dynamic ? '1' : '0'}, ctx, ${each_block_value}, ${lookup}, ${updateMountNode}, ${String(this.block.hasOutroMethod)}, ${create_each_block}, "${mountOrIntro}", ${anchor}, ${this.get_each_context}); + ${blocks} = @updateKeyedEach(${blocks}, #component, changed, ${get_key}, ${dynamic ? '1' : '0'}, ctx, ${this.each_block_value}, ${lookup}, ${updateMountNode}, ${String(this.block.hasOutroMethod)}, ${create_each_block}, "${mountOrIntro}", ${anchor}, ${this.get_each_context}); `); if (!parentNode) { @@ -342,7 +340,6 @@ export default class EachBlock extends Node { snippet: string, { create_each_block, - each_block_value, length, iterations, anchor, @@ -352,8 +349,8 @@ export default class EachBlock extends Node { block.builders.init.addBlock(deindent` var ${iterations} = []; - for (var #i = 0; #i < ${each_block_value}.${length}; #i += 1) { - ${iterations}[#i] = ${create_each_block}(#component, ${this.get_each_context}(ctx, ${each_block_value}, #i)); + for (var #i = 0; #i < ${this.each_block_value}.${length}; #i += 1) { + ${iterations}[#i] = ${create_each_block}(#component, ${this.get_each_context}(ctx, ${this.each_block_value}, #i)); } `); @@ -441,15 +438,15 @@ export default class EachBlock extends Node { ${iterations}[#i].u(); ${iterations}[#i].d(); } - ${iterations}.length = ${each_block_value}.${length}; + ${iterations}.length = ${this.each_block_value}.${length}; `; block.builders.update.addBlock(deindent` if (${condition}) { - ${each_block_value} = ${snippet}; + ${this.each_block_value} = ${snippet}; - for (var #i = ${start}; #i < ${each_block_value}.${length}; #i += 1) { - const child_ctx = ${this.get_each_context}(ctx, ${each_block_value}, #i); + for (var #i = ${start}; #i < ${this.each_block_value}.${length}; #i += 1) { + const child_ctx = ${this.get_each_context}(ctx, ${this.each_block_value}, #i); ${forLoopBody} } diff --git a/src/compile/nodes/Fragment.ts b/src/compile/nodes/Fragment.ts index f0de86fa97..e874fe3707 100644 --- a/src/compile/nodes/Fragment.ts +++ b/src/compile/nodes/Fragment.ts @@ -23,8 +23,7 @@ export default class Fragment extends Node { name: '@create_main_fragment', key: null, - indexNames: new Map(), - listNames: new Map(), + bindings: new Map(), dependencies: new Set(), }); diff --git a/src/parse/read/context.ts b/src/parse/read/context.ts index 3ab18695bd..77fd2cbf68 100644 --- a/src/parse/read/context.ts +++ b/src/parse/read/context.ts @@ -77,7 +77,7 @@ export default function readContext(parser: Parser) { parser.allowWhitespace(); const value = parser.eat(':') - ? readContext(parser) + ? (parser.allowWhitespace(), readContext(parser)) : key; const property: Property = { diff --git a/test/runtime/samples/each-block-destructured-object-binding/_config.js b/test/runtime/samples/each-block-destructured-object-binding/_config.js new file mode 100644 index 0000000000..42fba9d8b8 --- /dev/null +++ b/test/runtime/samples/each-block-destructured-object-binding/_config.js @@ -0,0 +1,39 @@ +export default { + data: { + people: [{ name: { first: 'Doctor', last: 'Who' } }], + }, + + html: ` + + +

Doctor Who

+ `, + + test(assert, component, target, window) { + const inputs = target.querySelectorAll('input'); + + inputs[1].value = 'Oz'; + inputs[1].dispatchEvent(new window.Event('input')); + + const { people } = component.get(); + + assert.deepEqual(people, [ + { name: { first: 'Doctor', last: 'Oz' } } + ]); + + assert.htmlEqual(target.innerHTML, ` + + +

Doctor Oz

+ `); + + people[0].name.first = 'Frank'; + component.set({ people }); + + assert.htmlEqual(target.innerHTML, ` + + +

Frank Oz

+ `); + }, +}; diff --git a/test/runtime/samples/each-block-destructured-object-binding/main.html b/test/runtime/samples/each-block-destructured-object-binding/main.html new file mode 100644 index 0000000000..049387bc3d --- /dev/null +++ b/test/runtime/samples/each-block-destructured-object-binding/main.html @@ -0,0 +1,5 @@ +{#each people as { name: { first: f, last: l } } } + + +

{f} {l}

+{/each}