allow components to have computed member expressions for bindings (fixes #624)

pull/627/head
Rich Harris 8 years ago
parent 5c53f5b6a2
commit e0917fd874

@ -5,6 +5,7 @@ import { DomGenerator } from '../../index';
import Block from '../../Block';
import { Node } from '../../../../interfaces';
import { State } from '../../interfaces';
import getObject from '../../../../utils/getObject';
export default function visitBinding(
generator: DomGenerator,
@ -14,16 +15,11 @@ export default function visitBinding(
attribute,
local
) {
const { name } = flattenReference(attribute.value);
const { name } = getObject(attribute.value);
const { snippet, contexts, dependencies } = block.contextualise(
attribute.value
);
if (dependencies.length > 1)
throw new Error(
'An unexpected situation arose. Please raise an issue at https://github.com/sveltejs/svelte/issues — thanks!'
);
contexts.forEach(context => {
if (!~local.allUsedContexts.indexOf(context))
local.allUsedContexts.push(context);
@ -38,8 +34,9 @@ export default function visitBinding(
obj = block.listNames.get(name);
prop = block.indexNames.get(name);
} else if (attribute.value.type === 'MemberExpression') {
prop = `'[✂${attribute.value.property.start}-${attribute.value.property
.end}]'`;
prop = `[✂${attribute.value.property.start}-${attribute.value.property
.end}]`;
if (!attribute.value.computed) prop = `'${prop}'`;
obj = `[✂${attribute.value.object.start}-${attribute.value.object.end}✂]`;
} else {
obj = 'state';
@ -85,7 +82,7 @@ export default function visitBinding(
local.update.addBlock(deindent`
if ( !${updating} && ${dependencies
.map(dependency => `'${dependency}' in changed`)
.join('||')} ) {
.join(' || ')} ) {
${updating} = true;
${local.name}._set({ ${attribute.name}: ${snippet} });
${updating} = false;

@ -6,12 +6,7 @@ import { DomGenerator } from '../../index';
import Block from '../../Block';
import { Node } from '../../../../interfaces';
import { State } from '../../interfaces';
function getObject(node) {
// TODO validation should ensure this is an Identifier or a MemberExpression
while (node.type === 'MemberExpression') node = node.object;
return node;
}
import getObject from '../../../../utils/getObject';
export default function visitBinding(
generator: DomGenerator,
@ -21,15 +16,7 @@ export default function visitBinding(
attribute: Node
) {
const { name } = getObject(attribute.value);
const { snippet, contexts } = block.contextualise(attribute.value);
const dependencies = block.contextDependencies.has(name)
? block.contextDependencies.get(name)
: [name];
if (dependencies.length > 1)
throw new Error(
'An unexpected situation arose. Please raise an issue at https://github.com/sveltejs/svelte/issues — thanks!'
);
const { snippet, contexts, dependencies } = block.contextualise(attribute.value);
contexts.forEach(context => {
if (!~state.allUsedContexts.indexOf(context))

@ -1,4 +1,6 @@
import deindent from '../../../../../utils/deindent';
import getTailSnippet from '../../../../../utils/getTailSnippet';
import { Node } from '../../../../../interfaces';
export default function getSetter({
block,
@ -40,15 +42,7 @@ export default function getSetter({
return `${block.component}._set({ ${name}: ${value} });`;
}
function getTailSnippet(node) {
const end = node.end;
while (node.type === 'MemberExpression') node = node.object;
const start = node.end;
return `[✂${start}-${end}✂]`;
}
function isComputed(node) {
function isComputed(node: Node) {
while (node.type === 'MemberExpression') {
if (node.computed) return true;
node = node.object;

@ -2,6 +2,7 @@ import deindent from '../../utils/deindent';
import flattenReference from '../../utils/flattenReference';
import { SsrGenerator } from './index';
import { Node } from '../../interfaces';
import getObject from '../../utils/getObject';
interface BlockOptions {
// TODO
@ -25,13 +26,13 @@ export default class Block {
this.conditions.map(c => `(${c})`)
);
const { keypath } = flattenReference(binding.value);
const { name: prop } = getObject(binding.value);
this.generator.bindings.push(deindent`
if ( ${conditions.join('&&')} ) {
tmp = ${name}.data();
if ( '${keypath}' in tmp ) {
state.${binding.name} = tmp.${keypath};
if ( '${prop}' in tmp ) {
state.${binding.name} = tmp.${prop};
settled = false;
}
}

@ -3,6 +3,8 @@ import visit from '../visit';
import { SsrGenerator } from '../index';
import Block from '../Block';
import { Node } from '../../../interfaces';
import getObject from '../../../utils/getObject';
import getTailSnippet from '../../../utils/getTailSnippet';
export default function visitComponent(
generator: SsrGenerator,
@ -52,9 +54,13 @@ export default function visitComponent(
})
.concat(
bindings.map(binding => {
const { name, keypath } = flattenReference(binding.value);
const value = block.contexts.has(name) ? keypath : `state.${keypath}`;
return `${binding.name}: ${value}`;
const { name } = getObject(binding.value);
const tail = binding.value.type === 'MemberExpression'
? getTailSnippet(binding.value)
: '';
const keypath = block.contexts.has(name) ? `${name}${tail}` : `state.${name}${tail}`;
return `${binding.name}: ${keypath}`;
})
)
.join(', ');

@ -1,6 +1,6 @@
const start = /\n(\t+)/;
export default function deindent(strings: string[], ...values: any[]) {
export default function deindent(strings: TemplateStringsArray, ...values: any[]) {
const indentation = start.exec(strings[0])[1];
const pattern = new RegExp(`^${indentation}`, 'gm');

@ -0,0 +1,7 @@
import { Node } from '../interfaces';
export default function getObject(node: Node) {
// TODO validation should ensure this is an Identifier or a MemberExpression
while (node.type === 'MemberExpression') node = node.object;
return node;
}

@ -0,0 +1,9 @@
import { Node } from '../interfaces';
export default function getTailSnippet(node: Node) {
const end = node.end;
while (node.type === 'MemberExpression') node = node.object;
const start = node.end;
return `[✂${start}-${end}✂]`;
}

@ -0,0 +1,3 @@
<label>
{{field}} <input bind:value>
</label>

@ -0,0 +1,34 @@
export default {
html: `
<label>firstname <input></label>
<label>lastname <input></label>
`,
test ( assert, component, target, window ) {
const input = new window.Event( 'input' );
const inputs = target.querySelectorAll( 'input' );
inputs[0].value = 'Ada';
inputs[0].dispatchEvent(input);
assert.deepEqual(component.get('values'), {
firstname: 'Ada',
lastname: ''
});
inputs[1].value = 'Lovelace';
inputs[1].dispatchEvent(input);
assert.deepEqual(component.get('values'), {
firstname: 'Ada',
lastname: 'Lovelace'
});
component.set({
values: {
firstname: 'Grace',
lastname: 'Hopper'
}
});
assert.equal(inputs[0].value, 'Grace');
assert.equal(inputs[1].value, 'Hopper');
}
};

@ -0,0 +1,22 @@
{{#each fields as field}}
<Nested :field bind:value='values[field]'/>
{{/each}}
<script>
import Nested from './Nested.html';
export default {
data: function () {
return {
fields: ['firstname', 'lastname'],
values: {
firstname: '',
lastname: ''
}
};
},
components: {
Nested
}
};
</script>
Loading…
Cancel
Save