Skip to content

Commit

Permalink
Merge pull request #1170 from sveltejs/gh-1100-onchange
Browse files Browse the repository at this point in the history
Deep component store bindings
  • Loading branch information
Rich-Harris authored Feb 11, 2018
2 parents 173792f + dfff295 commit fd69ada
Show file tree
Hide file tree
Showing 8 changed files with 149 additions and 52 deletions.
90 changes: 38 additions & 52 deletions src/generators/nodes/Component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) : '';

Expand All @@ -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`
Expand All @@ -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`
Expand All @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<input bind:value>
42 changes: 42 additions & 0 deletions test/runtime/samples/store-component-binding-deep/_config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Store } from '../../../../store.js';

const store = new Store({
name: {
value: 'world'
}
});

export default {
store,

html: `
<h1>Hello world!</h1>
<input>
`,

test(assert, component, target, window) {
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);

assert.equal(store.get('name').value, 'everybody');
assert.htmlEqual(target.innerHTML, `
<h1>Hello everybody!</h1>
<input>
`);

assert.deepEqual(changeRecord, [
{
state: { name: { value: 'everybody' } },
changes: { name: true }
}
]);
}
};
10 changes: 10 additions & 0 deletions test/runtime/samples/store-component-binding-deep/main.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<h1>Hello {{$name.value}}!</h1>
<TextInput bind:value=$name.value/>

<script>
import TextInput from './TextInput.html';

export default {
components: { TextInput }
};
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<input bind:value>
30 changes: 30 additions & 0 deletions test/runtime/samples/store-component-binding-each/_config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Store } from '../../../../store.js';

const store = new Store({
a: ['foo', 'bar', 'baz']
});

export default {
store,

html: `
<input><input><input>
<p>foo, bar, baz</p>
`,

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, `
<input><input><input>
<p>blah, bar, baz</p>
`);

component.destroy();
},
};
15 changes: 15 additions & 0 deletions test/runtime/samples/store-component-binding-each/main.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{{#each $a as x}}
<Widget bind:value='x'/>
{{/each}}

<p>{{$a.join(', ')}}</p>

<script>
import Widget from './Widget.html';

export default {
components: {
Widget
}
};
</script>
12 changes: 12 additions & 0 deletions test/runtime/samples/store-component-binding/_config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -24,5 +29,12 @@ export default {
<h1>Hello everybody!</h1>
<input>
`);

assert.deepEqual(changeRecord, [
{
state: { name: 'everybody' },
changes: { name: true }
}
]);
}
};

0 comments on commit fd69ada

Please sign in to comment.