Skip to content

Commit e0917fd

Browse files
committedJun 11, 2017
allow components to have computed member expressions for bindings (fixes #624)
1 parent 5c53f5b commit e0917fd

File tree

11 files changed

+100
-40
lines changed

11 files changed

+100
-40
lines changed
 

‎src/generators/dom/visitors/Component/Binding.ts

+6-9
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { DomGenerator } from '../../index';
55
import Block from '../../Block';
66
import { Node } from '../../../../interfaces';
77
import { State } from '../../interfaces';
8+
import getObject from '../../../../utils/getObject';
89

910
export default function visitBinding(
1011
generator: DomGenerator,
@@ -14,16 +15,11 @@ export default function visitBinding(
1415
attribute,
1516
local
1617
) {
17-
const { name } = flattenReference(attribute.value);
18+
const { name } = getObject(attribute.value);
1819
const { snippet, contexts, dependencies } = block.contextualise(
1920
attribute.value
2021
);
2122

22-
if (dependencies.length > 1)
23-
throw new Error(
24-
'An unexpected situation arose. Please raise an issue at https://github.com/sveltejs/svelte/issues — thanks!'
25-
);
26-
2723
contexts.forEach(context => {
2824
if (!~local.allUsedContexts.indexOf(context))
2925
local.allUsedContexts.push(context);
@@ -38,8 +34,9 @@ export default function visitBinding(
3834
obj = block.listNames.get(name);
3935
prop = block.indexNames.get(name);
4036
} else if (attribute.value.type === 'MemberExpression') {
41-
prop = `'[✂${attribute.value.property.start}-${attribute.value.property
42-
.end}✂]'`;
37+
prop = `[✂${attribute.value.property.start}-${attribute.value.property
38+
.end}✂]`;
39+
if (!attribute.value.computed) prop = `'${prop}'`;
4340
obj = `[✂${attribute.value.object.start}-${attribute.value.object.end}✂]`;
4441
} else {
4542
obj = 'state';
@@ -85,7 +82,7 @@ export default function visitBinding(
8582
local.update.addBlock(deindent`
8683
if ( !${updating} && ${dependencies
8784
.map(dependency => `'${dependency}' in changed`)
88-
.join('||')} ) {
85+
.join(' || ')} ) {
8986
${updating} = true;
9087
${local.name}._set({ ${attribute.name}: ${snippet} });
9188
${updating} = false;

‎src/generators/dom/visitors/Element/Binding.ts

+2-15
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,7 @@ import { DomGenerator } from '../../index';
66
import Block from '../../Block';
77
import { Node } from '../../../../interfaces';
88
import { State } from '../../interfaces';
9-
10-
function getObject(node) {
11-
// TODO validation should ensure this is an Identifier or a MemberExpression
12-
while (node.type === 'MemberExpression') node = node.object;
13-
return node;
14-
}
9+
import getObject from '../../../../utils/getObject';
1510

1611
export default function visitBinding(
1712
generator: DomGenerator,
@@ -21,15 +16,7 @@ export default function visitBinding(
2116
attribute: Node
2217
) {
2318
const { name } = getObject(attribute.value);
24-
const { snippet, contexts } = block.contextualise(attribute.value);
25-
const dependencies = block.contextDependencies.has(name)
26-
? block.contextDependencies.get(name)
27-
: [name];
28-
29-
if (dependencies.length > 1)
30-
throw new Error(
31-
'An unexpected situation arose. Please raise an issue at https://github.com/sveltejs/svelte/issues — thanks!'
32-
);
19+
const { snippet, contexts, dependencies } = block.contextualise(attribute.value);
3320

3421
contexts.forEach(context => {
3522
if (!~state.allUsedContexts.indexOf(context))

‎src/generators/dom/visitors/shared/binding/getSetter.ts

+3-9
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import deindent from '../../../../../utils/deindent';
2+
import getTailSnippet from '../../../../../utils/getTailSnippet';
3+
import { Node } from '../../../../../interfaces';
24

35
export default function getSetter({
46
block,
@@ -40,15 +42,7 @@ export default function getSetter({
4042
return `${block.component}._set({ ${name}: ${value} });`;
4143
}
4244

43-
function getTailSnippet(node) {
44-
const end = node.end;
45-
while (node.type === 'MemberExpression') node = node.object;
46-
const start = node.end;
47-
48-
return `[✂${start}-${end}✂]`;
49-
}
50-
51-
function isComputed(node) {
45+
function isComputed(node: Node) {
5246
while (node.type === 'MemberExpression') {
5347
if (node.computed) return true;
5448
node = node.object;

‎src/generators/server-side-rendering/Block.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import deindent from '../../utils/deindent';
22
import flattenReference from '../../utils/flattenReference';
33
import { SsrGenerator } from './index';
44
import { Node } from '../../interfaces';
5+
import getObject from '../../utils/getObject';
56

67
interface BlockOptions {
78
// TODO
@@ -25,13 +26,13 @@ export default class Block {
2526
this.conditions.map(c => `(${c})`)
2627
);
2728

28-
const { keypath } = flattenReference(binding.value);
29+
const { name: prop } = getObject(binding.value);
2930

3031
this.generator.bindings.push(deindent`
3132
if ( ${conditions.join('&&')} ) {
3233
tmp = ${name}.data();
33-
if ( '${keypath}' in tmp ) {
34-
state.${binding.name} = tmp.${keypath};
34+
if ( '${prop}' in tmp ) {
35+
state.${binding.name} = tmp.${prop};
3536
settled = false;
3637
}
3738
}

‎src/generators/server-side-rendering/visitors/Component.ts

+9-3
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import visit from '../visit';
33
import { SsrGenerator } from '../index';
44
import Block from '../Block';
55
import { Node } from '../../../interfaces';
6+
import getObject from '../../../utils/getObject';
7+
import getTailSnippet from '../../../utils/getTailSnippet';
68

79
export default function visitComponent(
810
generator: SsrGenerator,
@@ -52,9 +54,13 @@ export default function visitComponent(
5254
})
5355
.concat(
5456
bindings.map(binding => {
55-
const { name, keypath } = flattenReference(binding.value);
56-
const value = block.contexts.has(name) ? keypath : `state.${keypath}`;
57-
return `${binding.name}: ${value}`;
57+
const { name } = getObject(binding.value);
58+
const tail = binding.value.type === 'MemberExpression'
59+
? getTailSnippet(binding.value)
60+
: '';
61+
62+
const keypath = block.contexts.has(name) ? `${name}${tail}` : `state.${name}${tail}`;
63+
return `${binding.name}: ${keypath}`;
5864
})
5965
)
6066
.join(', ');

‎src/utils/deindent.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
const start = /\n(\t+)/;
22

3-
export default function deindent(strings: string[], ...values: any[]) {
3+
export default function deindent(strings: TemplateStringsArray, ...values: any[]) {
44
const indentation = start.exec(strings[0])[1];
55
const pattern = new RegExp(`^${indentation}`, 'gm');
66

‎src/utils/getObject.ts

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

‎src/utils/getTailSnippet.ts

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { Node } from '../interfaces';
2+
3+
export default function getTailSnippet(node: Node) {
4+
const end = node.end;
5+
while (node.type === 'MemberExpression') node = node.object;
6+
const start = node.end;
7+
8+
return `[✂${start}-${end}✂]`;
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<label>
2+
{{field}} <input bind:value>
3+
</label>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
export default {
2+
html: `
3+
<label>firstname <input></label>
4+
<label>lastname <input></label>
5+
`,
6+
7+
test ( assert, component, target, window ) {
8+
const input = new window.Event( 'input' );
9+
const inputs = target.querySelectorAll( 'input' );
10+
11+
inputs[0].value = 'Ada';
12+
inputs[0].dispatchEvent(input);
13+
assert.deepEqual(component.get('values'), {
14+
firstname: 'Ada',
15+
lastname: ''
16+
});
17+
18+
inputs[1].value = 'Lovelace';
19+
inputs[1].dispatchEvent(input);
20+
assert.deepEqual(component.get('values'), {
21+
firstname: 'Ada',
22+
lastname: 'Lovelace'
23+
});
24+
25+
component.set({
26+
values: {
27+
firstname: 'Grace',
28+
lastname: 'Hopper'
29+
}
30+
});
31+
assert.equal(inputs[0].value, 'Grace');
32+
assert.equal(inputs[1].value, 'Hopper');
33+
}
34+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{{#each fields as field}}
2+
<Nested :field bind:value='values[field]'/>
3+
{{/each}}
4+
5+
<script>
6+
import Nested from './Nested.html';
7+
8+
export default {
9+
data: function () {
10+
return {
11+
fields: ['firstname', 'lastname'],
12+
values: {
13+
firstname: '',
14+
lastname: ''
15+
}
16+
};
17+
},
18+
components: {
19+
Nested
20+
}
21+
};
22+
</script>

0 commit comments

Comments
 (0)
Please sign in to comment.