Skip to content

Commit f799cae

Browse files
authored
Merge pull request #330 from sveltejs/gh-313
two-way binding with <select multiple>
2 parents 3ae25bd + 806cefe commit f799cae

File tree

4 files changed

+101
-15
lines changed

4 files changed

+101
-15
lines changed

src/generators/dom/visitors/Element.js

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -79,12 +79,10 @@ export default {
7979
}
8080

8181
// special case – bound <option> without a value attribute
82-
if ( node.name === 'option' && !node.attributes.find( attribute => attribute.type === 'Attribute' && attribute.name === 'value' ) ) { // TODO check it's bound
83-
// const dynamic = node.children.length > 1 || node.children[0].type !== 'Text';
84-
// TODO do this in init for static values... have to do it in `leave`, because they don't exist yet
85-
local.update.addLine(
86-
`${name}.__value = ${name}.textContent`
87-
);
82+
if ( node.name === 'option' && !node.attributes.find( attribute => attribute.type === 'Attribute' && attribute.name === 'value' ) ) { // TODO check it's bound
83+
const statement = `${name}.__value = ${name}.textContent;`;
84+
local.update.addLine( statement );
85+
node.initialUpdate = statement;
8886
}
8987

9088
generator.current.builders.init.addBlock( local.init );

src/generators/dom/visitors/attributes/binding/index.js

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,17 @@ export default function createBinding ( generator, node, attribute, current, loc
4545
eventName = 'input';
4646
}
4747

48+
const isMultipleSelect = node.name === 'select' && node.attributes.find( attr => attr.name.toLowerCase() === 'multiple' ); // TODO ensure that this is a static attribute
4849
let value;
4950

5051
if ( local.isComponent ) {
5152
value = 'value';
5253
} else if ( node.name === 'select' ) {
53-
// TODO <select multiple> – can use select.selectedOptions
54-
value = 'selectedOption && selectedOption.__value';
54+
if ( isMultipleSelect ) {
55+
value = `[].map.call( ${local.name}.selectedOptions, function ( option ) { return option.__value; })`;
56+
} else {
57+
value = 'selectedOption && selectedOption.__value';
58+
}
5559
} else {
5660
value = `${local.name}.${attribute.name}`;
5761
}
@@ -101,7 +105,7 @@ export default function createBinding ( generator, node, attribute, current, loc
101105
}
102106

103107
// special case
104-
if ( node.name === 'select' ) {
108+
if ( node.name === 'select' && !isMultipleSelect ) {
105109
setter = `var selectedOption = ${local.name}.selectedOptions[0] || ${local.name}.options[0];\n` + setter;
106110
}
107111

@@ -130,19 +134,26 @@ export default function createBinding ( generator, node, attribute, current, loc
130134
let updateElement;
131135

132136
if ( node.name === 'select' ) {
133-
// TODO select multiple
134137
const value = generator.current.getUniqueName( 'value' );
135138
const i = generator.current.getUniqueName( 'i' );
136139
const option = generator.current.getUniqueName( 'option' );
137140

141+
const ifStatement = isMultipleSelect ?
142+
deindent`
143+
${option}.selected = ~${value}.indexOf( ${option}.__value );` :
144+
deindent`
145+
if ( ${option}.__value === ${value} ) {
146+
${option}.selected = true;
147+
break;
148+
}`;
149+
138150
updateElement = deindent`
139151
var ${value} = ${contextual ? attribute.value : `root.${attribute.value}`};
152+
console.log( 'value', ${value} );
140153
for ( var ${i} = 0; ${i} < ${local.name}.options.length; ${i} += 1 ) {
141154
var ${option} = ${local.name}.options[${i}];
142-
if ( ${option}.__value === ${value} ) {
143-
${option}.selected = true;
144-
break;
145-
}
155+
156+
${ifStatement}
146157
}
147158
`;
148159
} else {
@@ -164,7 +175,9 @@ export default function createBinding ( generator, node, attribute, current, loc
164175
node.initialUpdate = updateElement;
165176

166177
local.update.addLine(
167-
`if ( !${local.name}_updating ) ${updateElement}`
178+
`if ( !${local.name}_updating ) {
179+
${updateElement}
180+
}`
168181
);
169182

170183
generator.current.builders.teardown.addLine( deindent`
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
export default {
2+
skip: true, // selectedOptions doesn't work in JSDOM???
3+
4+
data: {
5+
selected: [ 'two', 'three' ]
6+
},
7+
8+
html: `
9+
<select>
10+
<option>one</option>
11+
<option>two</option>
12+
<option>three</option>
13+
</select>
14+
15+
<p>selected: two, three</p>
16+
`,
17+
18+
test ( assert, component, target, window ) {
19+
const select = target.querySelector( 'select' );
20+
const options = [ ...target.querySelectorAll( 'option' ) ];
21+
22+
const change = new window.Event( 'change' );
23+
24+
options[1].selected = false;
25+
select.dispatchEvent( change );
26+
27+
assert.deepEqual( component.get( 'selected' ), [ 'three' ] );
28+
assert.htmlEqual( target.innerHTML, `
29+
<select>
30+
<option>one</option>
31+
<option>two</option>
32+
<option>three</option>
33+
</select>
34+
35+
<p>selected: three</p>
36+
` );
37+
38+
options[0].selected = true;
39+
select.dispatchEvent( change );
40+
41+
assert.deepEqual( component.get( 'selected' ), [ 'one', 'three' ] );
42+
assert.htmlEqual( target.innerHTML, `
43+
<select>
44+
<option>one</option>
45+
<option>two</option>
46+
<option>three</option>
47+
</select>
48+
49+
<p>selected: one, three</p>
50+
` );
51+
52+
component.set({ selected: [ 'one', 'two' ] });
53+
54+
assert.ok( options[0].selected );
55+
assert.ok( options[1].selected );
56+
assert.ok( !options[2].selected );
57+
58+
assert.htmlEqual( target.innerHTML, `
59+
<select>
60+
<option>one</option>
61+
<option>two</option>
62+
<option>three</option>
63+
</select>
64+
65+
<p>selected: one, two</p>
66+
` );
67+
}
68+
};
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<select multiple bind:value='selected'>
2+
<option>one</option>
3+
<option>two</option>
4+
<option>three</option>
5+
</select>
6+
7+
<p>selected: {{selected.join( ', ' )}}</p>

0 commit comments

Comments
 (0)