two-way binding with <select multiple> (#313)

pull/330/head
Rich-Harris 8 years ago
parent 3ae25bd48d
commit 806cefe556

@ -79,12 +79,10 @@ export default {
} }
// special case bound <option> without a value attribute // 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 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'; const statement = `${name}.__value = ${name}.textContent;`;
// TODO do this in init for static values... have to do it in `leave`, because they don't exist yet local.update.addLine( statement );
local.update.addLine( node.initialUpdate = statement;
`${name}.__value = ${name}.textContent`
);
} }
generator.current.builders.init.addBlock( local.init ); generator.current.builders.init.addBlock( local.init );

@ -45,13 +45,17 @@ export default function createBinding ( generator, node, attribute, current, loc
eventName = 'input'; eventName = 'input';
} }
const isMultipleSelect = node.name === 'select' && node.attributes.find( attr => attr.name.toLowerCase() === 'multiple' ); // TODO ensure that this is a static attribute
let value; let value;
if ( local.isComponent ) { if ( local.isComponent ) {
value = 'value'; value = 'value';
} else if ( node.name === 'select' ) { } else if ( node.name === 'select' ) {
// TODO <select multiple> can use select.selectedOptions if ( isMultipleSelect ) {
value = 'selectedOption && selectedOption.__value'; value = `[].map.call( ${local.name}.selectedOptions, function ( option ) { return option.__value; })`;
} else {
value = 'selectedOption && selectedOption.__value';
}
} else { } else {
value = `${local.name}.${attribute.name}`; value = `${local.name}.${attribute.name}`;
} }
@ -101,7 +105,7 @@ export default function createBinding ( generator, node, attribute, current, loc
} }
// special case // special case
if ( node.name === 'select' ) { if ( node.name === 'select' && !isMultipleSelect ) {
setter = `var selectedOption = ${local.name}.selectedOptions[0] || ${local.name}.options[0];\n` + setter; setter = `var selectedOption = ${local.name}.selectedOptions[0] || ${local.name}.options[0];\n` + setter;
} }
@ -130,19 +134,26 @@ export default function createBinding ( generator, node, attribute, current, loc
let updateElement; let updateElement;
if ( node.name === 'select' ) { if ( node.name === 'select' ) {
// TODO select multiple
const value = generator.current.getUniqueName( 'value' ); const value = generator.current.getUniqueName( 'value' );
const i = generator.current.getUniqueName( 'i' ); const i = generator.current.getUniqueName( 'i' );
const option = generator.current.getUniqueName( 'option' ); const option = generator.current.getUniqueName( 'option' );
const ifStatement = isMultipleSelect ?
deindent`
${option}.selected = ~${value}.indexOf( ${option}.__value );` :
deindent`
if ( ${option}.__value === ${value} ) {
${option}.selected = true;
break;
}`;
updateElement = deindent` updateElement = deindent`
var ${value} = ${contextual ? attribute.value : `root.${attribute.value}`}; var ${value} = ${contextual ? attribute.value : `root.${attribute.value}`};
console.log( 'value', ${value} );
for ( var ${i} = 0; ${i} < ${local.name}.options.length; ${i} += 1 ) { for ( var ${i} = 0; ${i} < ${local.name}.options.length; ${i} += 1 ) {
var ${option} = ${local.name}.options[${i}]; var ${option} = ${local.name}.options[${i}];
if ( ${option}.__value === ${value} ) {
${option}.selected = true; ${ifStatement}
break;
}
} }
`; `;
} else { } else {
@ -164,7 +175,9 @@ export default function createBinding ( generator, node, attribute, current, loc
node.initialUpdate = updateElement; node.initialUpdate = updateElement;
local.update.addLine( local.update.addLine(
`if ( !${local.name}_updating ) ${updateElement}` `if ( !${local.name}_updating ) {
${updateElement}
}`
); );
generator.current.builders.teardown.addLine( deindent` generator.current.builders.teardown.addLine( deindent`

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

@ -0,0 +1,7 @@
<select multiple bind:value='selected'>
<option>one</option>
<option>two</option>
<option>three</option>
</select>
<p>selected: {{selected.join( ', ' )}}</p>
Loading…
Cancel
Save