component two-way bindings

pull/31/head
Rich-Harris 8 years ago
parent 22751e9ba4
commit cf1a80a28f

@ -7,7 +7,7 @@ import flattenReference from './utils/flattenReference.js';
import visitors from './visitors/index.js';
import processCss from './css/process.js';
export default function generate ( parsed, source, options = {} ) {
export default function generate ( parsed, source, options ) {
const renderers = [];
const generator = {
@ -255,12 +255,10 @@ export default function generate ( parsed, source, options = {} ) {
setStatements.push( deindent`
dispatchObservers( observers.immediate, newState, oldState );
mainFragment.update( state );
if ( mainFragment ) mainFragment.update( state );
dispatchObservers( observers.deferred, newState, oldState );
` );
const constructorName = options.name || 'SvelteComponent';
const topLevelStatements = [];
if ( parsed.js ) {
@ -277,6 +275,8 @@ export default function generate ( parsed, source, options = {} ) {
topLevelStatements.push( ...renderers.reverse() );
const constructorName = options.name || 'SvelteComponent';
topLevelStatements.push( deindent`
export default function ${constructorName} ( options ) {
var component = this;${generator.usesRefs ? `\nthis.refs = {}` : ``}
@ -351,16 +351,16 @@ export default function generate ( parsed, source, options = {} ) {
};
this.teardown = function teardown () {
this.fire( 'teardown' );${templateProperties.onteardown ? `\ntemplate.onteardown.call( this );` : ``}
mainFragment.teardown();
mainFragment = null;
state = {};
this.fire( 'teardown' );${templateProperties.onteardown ? `\ntemplate.onteardown.call( this );` : ``}
};
${parsed.css ? `if ( !addedCss ) addCss();` : ''}
let mainFragment = renderMainFragment( this, options.target );
var mainFragment = renderMainFragment( this, options.target );
this.set( ${templateProperties.data ? `Object.assign( template.data(), options.data )` : `options.data`} );
${templateProperties.onrender ? `template.onrender.call( this );` : ``}

@ -10,6 +10,8 @@ export default {
const local = {
name,
namespace: name === 'svg' ? 'http://www.w3.org/2000/svg' : generator.current.namespace,
isComponent,
allUsedContexts: new Set(),
init: [],

@ -1,3 +1,4 @@
import createBinding from './binding/index.js';
import deindent from '../../utils/deindent.js';
export default function addComponentAttributes ( generator, node, local ) {
@ -85,7 +86,7 @@ export default function addComponentAttributes ( generator, node, local ) {
}
else if ( attribute.type === 'Binding' ) {
throw new Error( 'TODO component bindings' );
createBinding( node, attribute, generator.current, local );
}
else if ( attribute.type === 'Ref' ) {

@ -149,7 +149,7 @@ export default function addElementAttributes ( generator, node, local ) {
}
else if ( attribute.type === 'Binding' ) {
createBinding( node, local.name, attribute, generator.current, local.init, local.update, local.teardown, local.allUsedContexts );
createBinding( node, attribute, generator.current, local );
}
else if ( attribute.type === 'Ref' ) {

@ -2,14 +2,14 @@ import deindent from '../../../utils/deindent.js';
import isReference from '../../../utils/isReference.js';
import flattenReference from '../../../utils/flattenReference.js';
export default function createBinding ( node, name, attribute, current, initStatements, updateStatements, teardownStatements, allUsedContexts ) {
export default function createBinding ( node, attribute, current, local ) {
const parts = attribute.value.split( '.' );
const deep = parts.length > 1;
const contextual = parts[0] in current.contexts;
if ( contextual ) allUsedContexts.add( parts[0] );
if ( contextual ) local.allUsedContexts.add( parts[0] );
const handler = current.counter( `${name}ChangeHandler` );
const handler = current.counter( `${local.name}ChangeHandler` );
let setter;
let eventName = 'change';
@ -54,26 +54,43 @@ export default function createBinding ( node, name, attribute, current, initStat
component.set({ ${parts[0]}: ${parts[0]} });
`;
} else {
setter = `component.set({ ${attribute.value}: ${name}.${attribute.name} });`;
const value = local.isComponent ? `value` : `${local.name}.${attribute.name}`;
setter = `component.set({ ${attribute.value}: ${value} });`;
}
initStatements.push( deindent`
var ${name}_updating = false;
if ( local.isComponent ) {
local.init.push( deindent`
var ${local.name}_updating = false;
function ${handler} () {
${name}_updating = true;
${setter}
${name}_updating = false;
}
${local.name}.observe( '${attribute.name}', function ( value ) {
${local.name}_updating = true;
${setter}
${local.name}_updating = false;
});
` );
local.update.push( deindent`
if ( !${local.name}_updating ) ${local.name}.set({ ${attribute.name}: ${contextual ? attribute.value : `root.${attribute.value}`} });
` );
} else {
local.init.push( deindent`
var ${local.name}_updating = false;
${name}.addEventListener( '${eventName}', ${handler}, false );
` );
function ${handler} () {
${local.name}_updating = true;
${setter}
${local.name}_updating = false;
}
${local.name}.addEventListener( '${eventName}', ${handler}, false );
` );
updateStatements.push( deindent`
if ( !${name}_updating ) ${name}.${attribute.name} = ${contextual ? attribute.value : `root.${attribute.value}`}
` );
local.update.push( deindent`
if ( !${local.name}_updating ) ${local.name}.${attribute.name} = ${contextual ? attribute.value : `root.${attribute.value}`}
` );
teardownStatements.push( deindent`
${name}.removeEventListener( '${eventName}', ${handler}, false );
` );
local.teardown.push( deindent`
${local.name}.removeEventListener( '${eventName}', ${handler}, false );
` );
}
}

@ -1,10 +1,10 @@
import parse from './parse/index.js';
import generate from './generate/index.js';
export function compile ( template ) {
const parsed = parse( template );
export function compile ( template, options = {} ) {
const parsed = parse( template, options );
// TODO validate template
const generated = generate( parsed, template );
const generated = generate( parsed, template, options );
return generated;
}

@ -0,0 +1,9 @@
<button on:click='set({ count: count + 1 })'>+1</button>
<script>
export default {
data: () => ({
count: 0
})
};
</script>

@ -0,0 +1,27 @@
export default {
html: `
<button>+1</button>
<p>count: 0</p>
`,
test ( assert, component, target, window ) {
const click = new window.MouseEvent( 'click' );
const button = target.querySelector( 'button' );
button.dispatchEvent( click );
assert.equal( component.get( 'x' ), 1 );
assert.htmlEqual( target.innerHTML, `
<button>+1</button>
<p>count: 1</p>
` );
button.dispatchEvent( click );
assert.equal( component.get( 'x' ), 2 );
assert.htmlEqual( target.innerHTML, `
<button>+1</button>
<p>count: 2</p>
` );
}
};

@ -0,0 +1,12 @@
<Counter bind:count='x'/>
<p>count: {{x}}</p>
<script>
import Counter from './Counter.html';
export default {
components: {
Counter
}
};
</script>

@ -71,12 +71,12 @@ describe( 'svelte', () => {
}
if ( child.nodeType === 3 ) {
child.data = child.data.replace( /\s{2,}/, ' ' );
child.data = child.data.replace( /\s{2,}/, '\n' );
// text
if ( previous && previous.nodeType === 3 ) {
previous.data += child.data;
previous.data = previous.data.replace( /\s{2,}/, ' ' );
previous.data = previous.data.replace( /\s{2,}/, '\n' );
node.removeChild( child );
}
@ -113,8 +113,6 @@ describe( 'svelte', () => {
assert.deepEqual( actual, expected, message );
};
assert.htmlEqual( ' <p> foo</p> ', '<p>foo</p>' );
});
});

Loading…
Cancel
Save