Fix binding <select> to a null value (#23221)

This commit is contained in:
Steve Sanderson 2020-06-22 17:27:02 +01:00 committed by GitHub
parent adbedd2cfb
commit 36be16b640
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 25 additions and 7 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -255,8 +255,8 @@ export class BrowserRenderer {
// added as an opaque markup block rather than individually
// Right here we implement [2]
if (newDomElementRaw instanceof HTMLSelectElement && selectValuePropname in newDomElementRaw) {
const selectValue = newDomElementRaw[selectValuePropname];
newDomElementRaw.value = selectValue;
const selectValue: string | null = newDomElementRaw[selectValuePropname];
setSelectElementValue(newDomElementRaw, selectValue);
}
}
@ -357,16 +357,20 @@ export class BrowserRenderer {
case 'SELECT':
case 'TEXTAREA': {
const value = attributeFrame ? frameReader.attributeValue(attributeFrame) : null;
(element as any).value = value;
if (element.tagName === 'SELECT') {
if (element instanceof HTMLSelectElement) {
setSelectElementValue(element, value);
// <select> is special, in that anything we write to .value will be lost if there
// isn't yet a matching <option>. To maintain the expected behavior no matter the
// element insertion/update order, preserve the desired value separately so
// we can recover it when inserting any matching <option> or after inserting an
// entire markup block of descendants.
element[selectValuePropname] = value;
} else {
(element as any).value = value;
}
return true;
}
case 'OPTION': {
@ -519,3 +523,15 @@ function stripOnPrefix(attributeName: string) {
throw new Error(`Attribute should be an event name, but doesn't start with 'on'. Value: '${attributeName}'`);
}
function setSelectElementValue(element: HTMLSelectElement, value: string | null) {
// There's no sensible way to represent a select option with value 'null', because
// (1) HTML attributes can't have null values - the closest equivalent is absence of the attribute
// (2) When picking an <option> with no 'value' attribute, the browser treats the value as being the
// *text content* on that <option> element. Trying to suppress that default behavior would involve
// a long chain of special-case hacks, as well as being breaking vs 3.x.
// So, the most plausible 'null' equivalent is an empty string. It's unfortunate that people can't
// write <option value=@someNullVariable>, and that we can never distinguish between null and empty
// string in a bound <select>, but that's a limit in the representational power of HTML.
element.value = value || '';
}

View File

@ -219,6 +219,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
// https://github.com/dotnet/aspnetcore/issues/17735
target.SelectByText("Empty value");
Browser.Equal(string.Empty, () => boundValue.Text);
Browser.Equal("Empty value", () => target.SelectedOption.Text);
}
[Fact]
@ -237,6 +238,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
// https://github.com/dotnet/aspnetcore/issues/17735
target.SelectByText("Empty value");
Browser.Equal(string.Empty, () => boundValue.Text);
Browser.Equal("Empty value", () => target.SelectedOption.Text);
}
[Fact]

View File

@ -241,7 +241,7 @@
<select id="select-box" @bind="selectValue">
<optgroup label="Some choices">
<!-- Show it also works with optgroup -->
<option value="">Empty value</option>
<option value=@string.Empty>Empty value</option>
<option value=@SelectableValue.First>First choice</option>
<option value=@SelectableValue.Second>Second choice</option>
<option value=@SelectableValue.Third>Third choice</option>