From 99fbb69c74989f9a82934e3e097eec9cc5e71e8a Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 4 Feb 2018 10:51:39 -0500 Subject: [PATCH] component store bindings - fixes #1100 --- src/generators/nodes/Component.ts | 67 +++++++++++++------ .../store-component-binding/TextInput.html | 1 + .../store-component-binding/_config.js | 28 ++++++++ .../samples/store-component-binding/main.html | 10 +++ 4 files changed, 87 insertions(+), 19 deletions(-) create mode 100644 test/runtime/samples/store-component-binding/TextInput.html create mode 100644 test/runtime/samples/store-component-binding/_config.js create mode 100644 test/runtime/samples/store-component-binding/main.html diff --git a/src/generators/nodes/Component.ts b/src/generators/nodes/Component.ts index 1b9b1bb8b66b..493302c3d58d 100644 --- a/src/generators/nodes/Component.ts +++ b/src/generators/nodes/Component.ts @@ -140,40 +140,47 @@ export default class Component extends Node { const setParentFromChildOnChange = new CodeBuilder(); const setParentFromChildOnInit = new CodeBuilder(); + const setStoreFromChildOnChange = new CodeBuilder(); + const setStoreFromChildOnInit = new CodeBuilder(); + bindings.forEach((binding: Binding) => { - let setParentFromChild; + 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); }); - const { name: key } = getObject(binding.value); + let setFromChild; - if (block.contexts.has(key)) { + if (!isStoreProp && block.contexts.has(key)) { const prop = binding.dependencies[0]; const computed = isComputed(binding.value); const tail = binding.value.type === 'MemberExpression' ? getTailSnippet(binding.value) : ''; - setParentFromChild = deindent` + setFromChild = deindent` var list = ${name_context}.${block.listNames.get(key)}; var index = ${name_context}.${block.indexNames.get(key)}; list[index]${tail} = childState.${binding.name}; ${binding.dependencies - .map((prop: string) => `newState.${prop} = state.${prop};`) + .map((prop: string) => `${newState}.${prop} = state.${prop};`) .join('\n')} `; } else if (binding.value.type === 'MemberExpression') { - setParentFromChild = deindent` + setFromChild = deindent` ${binding.snippet} = childState.${binding.name}; - ${binding.dependencies.map((prop: string) => `newState.${prop} = state.${prop};`).join('\n')} + ${binding.dependencies.map((prop: string) => `${newState}.${prop} = state.${prop};`).join('\n')} `; } else { - setParentFromChild = `newState.${binding.value.name} = childState.${binding.name};`; + setFromChild = `${newState}.${key} = childState.${binding.name};`; } statements.push(deindent` @@ -183,14 +190,14 @@ export default class Component extends Node { }` ); - setParentFromChildOnChange.addConditional( + (isStoreProp ? setStoreFromChildOnChange : setParentFromChildOnChange).addConditional( `!${name_updating}.${binding.name} && changed.${binding.name}`, - setParentFromChild + setFromChild ); - setParentFromChildOnInit.addConditional( + (isStoreProp ? setStoreFromChildOnInit : setParentFromChildOnInit).addConditional( `!${name_updating}.${binding.name}`, - setParentFromChild + setFromChild ); // TODO could binding.dependencies.length ever be 0? @@ -206,23 +213,45 @@ export default class Component extends Node { componentInitProperties.push(`data: ${name_initial_data}`); + const initialisers = [ + 'state = #component.get()', + !setParentFromChildOnChange.isEmpty() && 'newState = {}', + !setStoreFromChildOnChange.isEmpty() && 'newStoreState = {}', + ].filter(Boolean).join(', '); + componentInitProperties.push(deindent` _bind: function(changed, childState) { - var state = #component.get(), newState = {}; - ${setParentFromChildOnChange} - ${name_updating} = @assign({}, changed); - #component._set(newState); + 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); + `} ${name_updating} = {}; } `); + // TODO can `!childState` ever be true? beforecreate = deindent` #component.root._beforecreate.push(function() { - var state = #component.get(), childState = ${name}.get(), newState = {}; + var childState = ${name}.get(), ${initialisers}; if (!childState) return; ${setParentFromChildOnInit} - ${name_updating} = { ${bindings.map((binding: Binding) => `${binding.name}: true`).join(', ')} }; - #component._set(newState); + ${!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} = {}; }); `; diff --git a/test/runtime/samples/store-component-binding/TextInput.html b/test/runtime/samples/store-component-binding/TextInput.html new file mode 100644 index 000000000000..f24d608cd586 --- /dev/null +++ b/test/runtime/samples/store-component-binding/TextInput.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/runtime/samples/store-component-binding/_config.js b/test/runtime/samples/store-component-binding/_config.js new file mode 100644 index 000000000000..aefc4ec652ac --- /dev/null +++ b/test/runtime/samples/store-component-binding/_config.js @@ -0,0 +1,28 @@ +import { Store } from '../../../../store.js'; + +const store = new Store({ + name: 'world' +}); + +export default { + store, + + html: ` +

Hello world!

+ + `, + + test(assert, component, target, window) { + const input = target.querySelector('input'); + const event = new window.Event('input'); + + input.value = 'everybody'; + input.dispatchEvent(event); + + assert.equal(store.get('name'), 'everybody'); + assert.htmlEqual(target.innerHTML, ` +

Hello everybody!

+ + `); + } +}; \ No newline at end of file diff --git a/test/runtime/samples/store-component-binding/main.html b/test/runtime/samples/store-component-binding/main.html new file mode 100644 index 000000000000..aaebf609d243 --- /dev/null +++ b/test/runtime/samples/store-component-binding/main.html @@ -0,0 +1,10 @@ +

Hello {{$name}}!

+ + + \ No newline at end of file