diff --git a/src/generators/nodes/Component.ts b/src/generators/nodes/Component.ts index 493302c3d58d..584444da6552 100644 --- a/src/generators/nodes/Component.ts +++ b/src/generators/nodes/Component.ts @@ -137,27 +137,21 @@ export default class Component extends Node { block.addVariable(name_updating, '{}'); statements.push(`var ${name_initial_data} = ${initialPropString};`); - const setParentFromChildOnChange = new CodeBuilder(); - const setParentFromChildOnInit = new CodeBuilder(); + let hasLocalBindings = false; + let hasStoreBindings = false; - const setStoreFromChildOnChange = new CodeBuilder(); - const setStoreFromChildOnInit = new CodeBuilder(); + const builder = new CodeBuilder(); bindings.forEach((binding: Binding) => { let { name: key } = getObject(binding.value); - const isStoreProp = generator.options.store && key[0] === '$'; - if (isStoreProp) key = key.slice(1); - const newState = isStoreProp ? 'newStoreState' : 'newState'; - binding.contexts.forEach(context => { allContexts.add(context); }); let setFromChild; - if (!isStoreProp && block.contexts.has(key)) { - const prop = binding.dependencies[0]; + if (block.contexts.has(key)) { const computed = isComputed(binding.value); const tail = binding.value.type === 'MemberExpression' ? getTailSnippet(binding.value) : ''; @@ -167,20 +161,38 @@ export default class Component extends Node { list[index]${tail} = childState.${binding.name}; ${binding.dependencies - .map((prop: string) => `${newState}.${prop} = state.${prop};`) - .join('\n')} - `; - } + .map((name: string) => { + const isStoreProp = generator.options.store && name[0] === '$'; + const prop = isStoreProp ? name.slice(1) : name; + const newState = isStoreProp ? 'newStoreState' : 'newState'; - else if (binding.value.type === 'MemberExpression') { - setFromChild = deindent` - ${binding.snippet} = childState.${binding.name}; - ${binding.dependencies.map((prop: string) => `${newState}.${prop} = state.${prop};`).join('\n')} + if (isStoreProp) hasStoreBindings = true; + else hasLocalBindings = true; + + return `${newState}.${prop} = state.${name};`; + }) + .join('\n')} `; } else { - setFromChild = `${newState}.${key} = childState.${binding.name};`; + const isStoreProp = generator.options.store && key[0] === '$'; + const prop = isStoreProp ? key.slice(1) : key; + const newState = isStoreProp ? 'newStoreState' : 'newState'; + + if (isStoreProp) hasStoreBindings = true; + else hasLocalBindings = true; + + if (binding.value.type === 'MemberExpression') { + setFromChild = deindent` + ${binding.snippet} = childState.${binding.name}; + ${newState}.${prop} = state.${key}; + `; + } + + else { + setFromChild = `${newState}.${prop} = childState.${binding.name};`; + } } statements.push(deindent` @@ -190,16 +202,11 @@ export default class Component extends Node { }` ); - (isStoreProp ? setStoreFromChildOnChange : setParentFromChildOnChange).addConditional( + builder.addConditional( `!${name_updating}.${binding.name} && changed.${binding.name}`, setFromChild ); - (isStoreProp ? setStoreFromChildOnInit : setParentFromChildOnInit).addConditional( - `!${name_updating}.${binding.name}`, - setFromChild - ); - // TODO could binding.dependencies.length ever be 0? if (binding.dependencies.length) { updates.push(deindent` @@ -215,44 +222,23 @@ export default class Component extends Node { const initialisers = [ 'state = #component.get()', - !setParentFromChildOnChange.isEmpty() && 'newState = {}', - !setStoreFromChildOnChange.isEmpty() && 'newStoreState = {}', + hasLocalBindings && 'newState = {}', + hasStoreBindings && 'newStoreState = {}', ].filter(Boolean).join(', '); componentInitProperties.push(deindent` _bind: function(changed, childState) { var ${initialisers}; - ${!setStoreFromChildOnChange.isEmpty() && deindent` - ${setStoreFromChildOnChange} - ${name_updating} = @assign({}, changed); - #component.store.set(newStoreState); - `} - ${!setParentFromChildOnChange.isEmpty() && deindent` - ${setParentFromChildOnChange} - ${name_updating} = @assign({}, changed); - #component._set(newState); - `} + ${builder} + ${hasStoreBindings && `#component.store.set(newStoreState);`} + ${hasLocalBindings && `#component._set(newState);`} ${name_updating} = {}; } `); - // TODO can `!childState` ever be true? beforecreate = deindent` #component.root._beforecreate.push(function() { - var childState = ${name}.get(), ${initialisers}; - if (!childState) return; - ${setParentFromChildOnInit} - ${!setStoreFromChildOnInit.isEmpty() && deindent` - ${setStoreFromChildOnInit} - ${name_updating} = { ${bindings.map((binding: Binding) => `${binding.name}: true`).join(', ')} }; - #component.store.set(newStoreState); - `} - ${!setParentFromChildOnInit.isEmpty() && deindent` - ${setParentFromChildOnInit} - ${name_updating} = { ${bindings.map((binding: Binding) => `${binding.name}: true`).join(', ')} }; - #component._set(newState); - `} - ${name_updating} = {}; + ${name}._bind({ ${bindings.map(b => `${b.name}: 1`).join(', ')} }, ${name}.get()); }); `; } else if (initialProps.length) { diff --git a/test/runtime/samples/store-component-binding-deep/TextInput.html b/test/runtime/samples/store-component-binding-deep/TextInput.html new file mode 100644 index 000000000000..f24d608cd586 --- /dev/null +++ b/test/runtime/samples/store-component-binding-deep/TextInput.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/runtime/samples/store-component-binding-deep/_config.js b/test/runtime/samples/store-component-binding-deep/_config.js new file mode 100644 index 000000000000..66ae5ac4ae22 --- /dev/null +++ b/test/runtime/samples/store-component-binding-deep/_config.js @@ -0,0 +1,42 @@ +import { Store } from '../../../../store.js'; + +const store = new Store({ + name: { + value: 'world' + } +}); + +export default { + store, + + html: ` +
foo, bar, baz
+ `, + + test(assert, component, target, window) { + const event = new window.MouseEvent('input'); + const inputs = target.querySelectorAll('input'); + + inputs[0].value = 'blah'; + inputs[0].dispatchEvent(event); + + assert.deepEqual(store.get('a'), ['blah', 'bar', 'baz']); + assert.htmlEqual(target.innerHTML, ` + +blah, bar, baz
+ `); + + component.destroy(); + }, +}; diff --git a/test/runtime/samples/store-component-binding-each/main.html b/test/runtime/samples/store-component-binding-each/main.html new file mode 100644 index 000000000000..e150c57af881 --- /dev/null +++ b/test/runtime/samples/store-component-binding-each/main.html @@ -0,0 +1,15 @@ +{{#each $a as x}} +{{$a.join(', ')}}
+ + diff --git a/test/runtime/samples/store-component-binding/_config.js b/test/runtime/samples/store-component-binding/_config.js index aefc4ec652ac..1d2beab7a778 100644 --- a/test/runtime/samples/store-component-binding/_config.js +++ b/test/runtime/samples/store-component-binding/_config.js @@ -16,6 +16,11 @@ export default { const input = target.querySelector('input'); const event = new window.Event('input'); + const changeRecord = []; + store.onchange((state, changes) => { + changeRecord.push({ state, changes }); + }); + input.value = 'everybody'; input.dispatchEvent(event); @@ -24,5 +29,12 @@ export default {