basic <select> binding

pull/31/head
Rich-Harris 8 years ago
parent 50a02cc344
commit f3d635fe60

@ -281,6 +281,26 @@ export default function generate ( parsed, source, options ) {
const constructorName = options.name || 'SvelteComponent';
const initStatements = [];
if ( parsed.css ) {
initStatements.push( `if ( !addedCss ) addCss();` );
}
if ( generator.hasComplexBindings ) {
initStatements.push( `this.__bindings = [];` );
setStatements.push( `while ( this.__bindings.length ) this.__bindings.pop()();` );
}
initStatements.push( `
var mainFragment = renderMainFragment( this, options.target );
this.set( ${templateProperties.data ? `Object.assign( template.data(), options.data )` : `options.data || {}`} );
` );
if ( templateProperties.onrender ) {
initStatements.push( `template.onrender.call( this );` );
}
topLevelStatements.push( deindent`
export default function ${constructorName} ( options ) {
var component = this;${generator.usesRefs ? `\nthis.refs = {}` : ``}
@ -375,11 +395,7 @@ export default function generate ( parsed, source, options ) {
state = {};
};
${parsed.css ? `if ( !addedCss ) addCss();` : ''}
var mainFragment = renderMainFragment( this, options.target );
this.set( ${templateProperties.data ? `Object.assign( template.data(), options.data )` : `options.data || {}`} );
${templateProperties.onrender ? `template.onrender.call( this );` : ``}
${initStatements.join( '\n\n' )}
}
` );

@ -82,6 +82,13 @@ export default {
local.teardown.push( `${name}.parentNode.removeChild( ${name} );` );
}
// special case bound <option> without a value attribute
if ( node.name === 'option' && !node.attributes.find( attribute => attribute.type === 'Attribute' && attribute.name === 'value' ) ) { // TODO check it's bound
// const dynamic = node.children.length > 1 || node.children[0].type !== 'Text';
// TODO do this in init for static values... have to do it in `leave`, because they don't exist yet
local.update.push( `${name}.__value = ${name}.textContent` );
}
generator.current.initStatements.push( local.init.join( '\n' ) );
if ( local.update.length ) generator.current.updateStatements.push( local.update.join( '\n' ) );
generator.current.teardownStatements.push( local.teardown.join( '\n' ) );

@ -86,7 +86,7 @@ export default function addComponentAttributes ( generator, node, local ) {
}
else if ( attribute.type === 'Binding' ) {
createBinding( node, attribute, generator.current, local );
createBinding( generator, node, attribute, generator.current, local );
}
else if ( attribute.type === 'Ref' ) {

@ -8,11 +8,16 @@ export default function addElementAttributes ( generator, node, local ) {
let metadata = attributeLookup[ attribute.name ];
if ( metadata && metadata.appliesTo && !~metadata.appliesTo.indexOf( node.name ) ) metadata = null;
let dynamic = false;
const isBoundOptionValue = node.name === 'option' && attribute.name === 'value'; // TODO check it's actually bound
const propertyName = isBoundOptionValue ? '__value' : metadata && metadata.propertyName;
if ( attribute.value === true ) {
// attributes without values, e.g. <textarea readonly>
if ( metadata ) {
if ( propertyName ) {
local.init.push( deindent`
${local.name}.${metadata.propertyName} = true;
${local.name}.${propertyName} = true;
` );
} else {
local.init.push( deindent`
@ -35,9 +40,9 @@ export default function addElementAttributes ( generator, node, local ) {
// static attributes
result = JSON.stringify( value.data );
if ( metadata ) {
if ( propertyName ) {
local.init.push( deindent`
${local.name}.${metadata.propertyName} = ${result};
${local.name}.${propertyName} = ${result};
` );
} else {
local.init.push( deindent`
@ -53,13 +58,15 @@ export default function addElementAttributes ( generator, node, local ) {
}
else {
dynamic = true;
// dynamic but potentially non-string attributes
generator.contextualise( value.expression );
result = `[✂${value.expression.start}-${value.expression.end}✂]`;
if ( metadata ) {
if ( propertyName ) {
local.update.push( deindent`
${local.name}.${metadata.propertyName} = ${result};
${local.name}.${propertyName} = ${result};
` );
} else {
local.update.push( deindent`
@ -70,6 +77,8 @@ export default function addElementAttributes ( generator, node, local ) {
}
else {
dynamic = true;
const value = ( attribute.value[0].type === 'Text' ? '' : `"" + ` ) + (
attribute.value.map( chunk => {
if ( chunk.type === 'Text' ) {
@ -83,9 +92,9 @@ export default function addElementAttributes ( generator, node, local ) {
}).join( ' + ' )
);
if ( metadata ) {
if ( propertyName ) {
local.update.push( deindent`
${local.name}.${metadata.propertyName} = ${value};
${local.name}.${propertyName} = ${value};
` );
} else {
local.update.push( deindent`
@ -93,6 +102,10 @@ export default function addElementAttributes ( generator, node, local ) {
` );
}
}
if ( isBoundOptionValue ) {
( dynamic ? local.update : local.init ).push( `${local.name}.value = ${local.name}.__value` );
}
}
else if ( attribute.type === 'EventHandler' ) {
@ -149,7 +162,7 @@ export default function addElementAttributes ( generator, node, local ) {
}
else if ( attribute.type === 'Binding' ) {
createBinding( node, attribute, generator.current, local );
createBinding( generator, node, attribute, generator.current, local );
}
else if ( attribute.type === 'Ref' ) {

@ -2,7 +2,7 @@ import deindent from '../../../utils/deindent.js';
import isReference from '../../../utils/isReference.js';
import flattenReference from '../../../utils/flattenReference.js';
export default function createBinding ( node, attribute, current, local ) {
export default function createBinding ( generator, node, attribute, current, local ) {
const parts = attribute.value.split( '.' );
const deep = parts.length > 1;
@ -21,6 +21,17 @@ export default function createBinding ( node, attribute, current, local ) {
}
}
let value;
if ( local.isComponent ) {
value = 'value';
} else if ( node.name === 'select' ) {
// TODO <select multiple> can use select.selectedOptions
value = 'selectedOption && selectedOption.__value';
} else {
value = `${local.name}.${attribute.name}`;
}
if ( contextual ) {
// find the top-level property that this is a child of
let fragment = current;
@ -43,21 +54,25 @@ export default function createBinding ( node, attribute, current, local ) {
setter = deindent`
var list = this.__svelte.${listName};
var index = this.__svelte.${indexName};
list[index]${parts.slice( 1 ).map( part => `.${part}` ).join( '' )} = this.${attribute.name};
list[index]${parts.slice( 1 ).map( part => `.${part}` ).join( '' )} = ${value};
component.set({ ${prop}: component.get( '${prop}' ) });
`;
} else if ( deep ) {
setter = deindent`
var ${parts[0]} = component.get( '${parts[0]}' );
${parts[0]}.${parts.slice( 1 ).join( '.' )} = this.${attribute.name};
${parts[0]}.${parts.slice( 1 ).join( '.' )} = ${value};
component.set({ ${parts[0]}: ${parts[0]} });
`;
} else {
const value = local.isComponent ? `value` : `${local.name}.${attribute.name}`;
setter = `component.set({ ${attribute.value}: ${value} });`;
}
// special case
if ( node.name === 'select' ) {
setter = `var selectedOption = ${local.name}.selectedOptions[0] || ${local.name}.options[0];\n` + setter;
}
if ( local.isComponent ) {
local.init.push( deindent`
var ${local.name}_updating = false;
@ -93,4 +108,9 @@ export default function createBinding ( node, attribute, current, local ) {
${local.name}.removeEventListener( '${eventName}', ${handler}, false );
` );
}
if ( node.name === 'select' ) {
generator.hasComplexBindings = true;
local.init.push( `component.__bindings.push( ${handler} )` );
}
}

@ -0,0 +1,43 @@
export default {
skip: true, // selectedOptions doesn't work in JSDOM???
html: `
<p>selected: one</p>
<select>
<option>one</option>
<option>two</option>
<option>three</option>
</select>
<p>selected: one</p>
`,
test ( assert, component, target, window ) {
const select = target.querySelector( 'select' );
const options = [ ...target.querySelectorAll( 'option' ) ];
assert.deepEqual( options, select.options );
assert.equal( component.get( 'selected' ), 'one' );
const change = new window.Event( 'change' );
options[1].selected = true;
select.dispatchEvent( change );
assert.equal( component.get( 'selected' ), 'two' );
assert.htmlEqual( target.innerHTML, `
<p>selected: two</p>
<select>
<option>one</option>
<option>two</option>
<option>three</option>
</select>
<p>selected: two</p>
` );
component.set({ selected: 'three' });
}
};

@ -0,0 +1,9 @@
<p>selected: {{selected}}</p>
<select bind:value='selected'>
<option>one</option>
<option>two</option>
<option>three</option>
</select>
<p>selected: {{selected}}</p>

@ -146,7 +146,7 @@ describe( 'svelte', () => {
const config = loadConfig( dir );
( config.solo ? it.only : it )( dir, () => {
( config.skip ? it.skip : config.solo ? it.only : it )( dir, () => {
let compiled;
try {

Loading…
Cancel
Save