Skip to content

Commit cf1a80a

Browse files
committed
component two-way bindings
1 parent 22751e9 commit cf1a80a

File tree

10 files changed

+101
-35
lines changed

10 files changed

+101
-35
lines changed

compiler/generate/index.js

+7-7
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import flattenReference from './utils/flattenReference.js';
77
import visitors from './visitors/index.js';
88
import processCss from './css/process.js';
99

10-
export default function generate ( parsed, source, options = {} ) {
10+
export default function generate ( parsed, source, options ) {
1111
const renderers = [];
1212

1313
const generator = {
@@ -255,12 +255,10 @@ export default function generate ( parsed, source, options = {} ) {
255255

256256
setStatements.push( deindent`
257257
dispatchObservers( observers.immediate, newState, oldState );
258-
mainFragment.update( state );
258+
if ( mainFragment ) mainFragment.update( state );
259259
dispatchObservers( observers.deferred, newState, oldState );
260260
` );
261261

262-
const constructorName = options.name || 'SvelteComponent';
263-
264262
const topLevelStatements = [];
265263

266264
if ( parsed.js ) {
@@ -277,6 +275,8 @@ export default function generate ( parsed, source, options = {} ) {
277275

278276
topLevelStatements.push( ...renderers.reverse() );
279277

278+
const constructorName = options.name || 'SvelteComponent';
279+
280280
topLevelStatements.push( deindent`
281281
export default function ${constructorName} ( options ) {
282282
var component = this;${generator.usesRefs ? `\nthis.refs = {}` : ``}
@@ -351,16 +351,16 @@ export default function generate ( parsed, source, options = {} ) {
351351
};
352352
353353
this.teardown = function teardown () {
354+
this.fire( 'teardown' );${templateProperties.onteardown ? `\ntemplate.onteardown.call( this );` : ``}
355+
354356
mainFragment.teardown();
355357
mainFragment = null;
356358
357359
state = {};
358-
359-
this.fire( 'teardown' );${templateProperties.onteardown ? `\ntemplate.onteardown.call( this );` : ``}
360360
};
361361
362362
${parsed.css ? `if ( !addedCss ) addCss();` : ''}
363-
let mainFragment = renderMainFragment( this, options.target );
363+
var mainFragment = renderMainFragment( this, options.target );
364364
this.set( ${templateProperties.data ? `Object.assign( template.data(), options.data )` : `options.data`} );
365365
366366
${templateProperties.onrender ? `template.onrender.call( this );` : ``}

compiler/generate/visitors/Element.js

+2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ export default {
1010
const local = {
1111
name,
1212
namespace: name === 'svg' ? 'http://www.w3.org/2000/svg' : generator.current.namespace,
13+
isComponent,
14+
1315
allUsedContexts: new Set(),
1416

1517
init: [],

compiler/generate/visitors/attributes/addComponentAttributes.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import createBinding from './binding/index.js';
12
import deindent from '../../utils/deindent.js';
23

34
export default function addComponentAttributes ( generator, node, local ) {
@@ -85,7 +86,7 @@ export default function addComponentAttributes ( generator, node, local ) {
8586
}
8687

8788
else if ( attribute.type === 'Binding' ) {
88-
throw new Error( 'TODO component bindings' );
89+
createBinding( node, attribute, generator.current, local );
8990
}
9091

9192
else if ( attribute.type === 'Ref' ) {

compiler/generate/visitors/attributes/addElementAttributes.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ export default function addElementAttributes ( generator, node, local ) {
149149
}
150150

151151
else if ( attribute.type === 'Binding' ) {
152-
createBinding( node, local.name, attribute, generator.current, local.init, local.update, local.teardown, local.allUsedContexts );
152+
createBinding( node, attribute, generator.current, local );
153153
}
154154

155155
else if ( attribute.type === 'Ref' ) {

compiler/generate/visitors/attributes/binding/index.js

+36-19
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@ import deindent from '../../../utils/deindent.js';
22
import isReference from '../../../utils/isReference.js';
33
import flattenReference from '../../../utils/flattenReference.js';
44

5-
export default function createBinding ( node, name, attribute, current, initStatements, updateStatements, teardownStatements, allUsedContexts ) {
5+
export default function createBinding ( node, attribute, current, local ) {
66
const parts = attribute.value.split( '.' );
77

88
const deep = parts.length > 1;
99
const contextual = parts[0] in current.contexts;
10-
if ( contextual ) allUsedContexts.add( parts[0] );
10+
if ( contextual ) local.allUsedContexts.add( parts[0] );
1111

12-
const handler = current.counter( `${name}ChangeHandler` );
12+
const handler = current.counter( `${local.name}ChangeHandler` );
1313
let setter;
1414

1515
let eventName = 'change';
@@ -54,26 +54,43 @@ export default function createBinding ( node, name, attribute, current, initStat
5454
component.set({ ${parts[0]}: ${parts[0]} });
5555
`;
5656
} else {
57-
setter = `component.set({ ${attribute.value}: ${name}.${attribute.name} });`;
57+
const value = local.isComponent ? `value` : `${local.name}.${attribute.name}`;
58+
setter = `component.set({ ${attribute.value}: ${value} });`;
5859
}
5960

60-
initStatements.push( deindent`
61-
var ${name}_updating = false;
61+
if ( local.isComponent ) {
62+
local.init.push( deindent`
63+
var ${local.name}_updating = false;
6264
63-
function ${handler} () {
64-
${name}_updating = true;
65-
${setter}
66-
${name}_updating = false;
67-
}
65+
${local.name}.observe( '${attribute.name}', function ( value ) {
66+
${local.name}_updating = true;
67+
${setter}
68+
${local.name}_updating = false;
69+
});
70+
` );
71+
72+
local.update.push( deindent`
73+
if ( !${local.name}_updating ) ${local.name}.set({ ${attribute.name}: ${contextual ? attribute.value : `root.${attribute.value}`} });
74+
` );
75+
} else {
76+
local.init.push( deindent`
77+
var ${local.name}_updating = false;
6878
69-
${name}.addEventListener( '${eventName}', ${handler}, false );
70-
` );
79+
function ${handler} () {
80+
${local.name}_updating = true;
81+
${setter}
82+
${local.name}_updating = false;
83+
}
84+
85+
${local.name}.addEventListener( '${eventName}', ${handler}, false );
86+
` );
7187

72-
updateStatements.push( deindent`
73-
if ( !${name}_updating ) ${name}.${attribute.name} = ${contextual ? attribute.value : `root.${attribute.value}`}
74-
` );
88+
local.update.push( deindent`
89+
if ( !${local.name}_updating ) ${local.name}.${attribute.name} = ${contextual ? attribute.value : `root.${attribute.value}`}
90+
` );
7591

76-
teardownStatements.push( deindent`
77-
${name}.removeEventListener( '${eventName}', ${handler}, false );
78-
` );
92+
local.teardown.push( deindent`
93+
${local.name}.removeEventListener( '${eventName}', ${handler}, false );
94+
` );
95+
}
7996
}

compiler/index.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import parse from './parse/index.js';
22
import generate from './generate/index.js';
33

4-
export function compile ( template ) {
5-
const parsed = parse( template );
4+
export function compile ( template, options = {} ) {
5+
const parsed = parse( template, options );
66
// TODO validate template
7-
const generated = generate( parsed, template );
7+
const generated = generate( parsed, template, options );
88

99
return generated;
1010
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<button on:click='set({ count: count + 1 })'>+1</button>
2+
3+
<script>
4+
export default {
5+
data: () => ({
6+
count: 0
7+
})
8+
};
9+
</script>
+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
export default {
2+
html: `
3+
<button>+1</button>
4+
<p>count: 0</p>
5+
`,
6+
7+
test ( assert, component, target, window ) {
8+
const click = new window.MouseEvent( 'click' );
9+
const button = target.querySelector( 'button' );
10+
11+
button.dispatchEvent( click );
12+
13+
assert.equal( component.get( 'x' ), 1 );
14+
assert.htmlEqual( target.innerHTML, `
15+
<button>+1</button>
16+
<p>count: 1</p>
17+
` );
18+
19+
button.dispatchEvent( click );
20+
21+
assert.equal( component.get( 'x' ), 2 );
22+
assert.htmlEqual( target.innerHTML, `
23+
<button>+1</button>
24+
<p>count: 2</p>
25+
` );
26+
}
27+
};
+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<Counter bind:count='x'/>
2+
<p>count: {{x}}</p>
3+
4+
<script>
5+
import Counter from './Counter.html';
6+
7+
export default {
8+
components: {
9+
Counter
10+
}
11+
};
12+
</script>

test/test.js

+2-4
Original file line numberDiff line numberDiff line change
@@ -71,12 +71,12 @@ describe( 'svelte', () => {
7171
}
7272

7373
if ( child.nodeType === 3 ) {
74-
child.data = child.data.replace( /\s{2,}/, ' ' );
74+
child.data = child.data.replace( /\s{2,}/, '\n' );
7575

7676
// text
7777
if ( previous && previous.nodeType === 3 ) {
7878
previous.data += child.data;
79-
previous.data = previous.data.replace( /\s{2,}/, ' ' );
79+
previous.data = previous.data.replace( /\s{2,}/, '\n' );
8080

8181
node.removeChild( child );
8282
}
@@ -113,8 +113,6 @@ describe( 'svelte', () => {
113113

114114
assert.deepEqual( actual, expected, message );
115115
};
116-
117-
assert.htmlEqual( ' <p> foo</p> ', '<p>foo</p>' );
118116
});
119117
});
120118

0 commit comments

Comments
 (0)