Apply selection to options even within markup block or optgroup. Fixes #5616 #10648

This commit is contained in:
Steve Sanderson 2019-06-27 14:48:57 +01:00
parent fd9ad7f8c5
commit 4df198dfaa
5 changed files with 67 additions and 14 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

@ -238,6 +238,17 @@ export class BrowserRenderer {
break; break;
} }
} }
// We handle setting 'value' on a <select> in two different ways:
// [1] When inserting a corresponding <option>, in case you're dynamically adding options
// [2] After we finish inserting the <select>, in case the descendant options are being
// 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;
delete newDomElementRaw[selectValuePropname];
}
} }
private insertComponent(batch: RenderBatch, parent: LogicalElement, childIndex: number, frame: RenderTreeFrame) { private insertComponent(batch: RenderBatch, parent: LogicalElement, childIndex: number, frame: RenderTreeFrame) {
@ -317,7 +328,8 @@ export class BrowserRenderer {
// <select> is special, in that anything we write to .value will be lost if there // <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 // isn't yet a matching <option>. To maintain the expected behavior no matter the
// element insertion/update order, preserve the desired value separately so // element insertion/update order, preserve the desired value separately so
// we can recover it when inserting any matching <option>. // we can recover it when inserting any matching <option> or after inserting an
// entire markup block of descendants.
element[selectValuePropname] = value; element[selectValuePropname] = value;
} }
return true; return true;
@ -330,10 +342,11 @@ export class BrowserRenderer {
element.removeAttribute('value'); element.removeAttribute('value');
} }
// See above for why we have this special handling for <select>/<option> // See above for why we have this special handling for <select>/<option>
const parentElement = element.parentElement; // Note that this is only one of the two cases where we set the value on a <select>
if (parentElement && (selectValuePropname in parentElement) && parentElement[selectValuePropname] === value) { const selectElem = this.findClosestAncestorSelectElement(element);
this.tryApplyValueProperty(batch, parentElement, attributeFrame); if (selectElem && (selectValuePropname in selectElem) && selectElem[selectValuePropname] === value) {
delete parentElement[selectValuePropname]; this.tryApplyValueProperty(batch, selectElem, attributeFrame);
delete selectElem[selectValuePropname];
} }
return true; return true;
} }
@ -353,6 +366,18 @@ export class BrowserRenderer {
} }
} }
private findClosestAncestorSelectElement(element: Element | null) {
while (element) {
if (element instanceof HTMLSelectElement) {
return element;
} else {
element = element.parentElement;
}
}
return null;
}
private insertFrameRange(batch: RenderBatch, componentId: number, parent: LogicalElement, childIndex: number, frames: ArrayValues<RenderTreeFrame>, startIndex: number, endIndexExcl: number): number { private insertFrameRange(batch: RenderBatch, componentId: number, parent: LogicalElement, childIndex: number, frames: ArrayValues<RenderTreeFrame>, startIndex: number, endIndexExcl: number): number {
const origChildIndex = childIndex; const origChildIndex = childIndex;
for (let index = startIndex; index < endIndexExcl; index++) { for (let index = startIndex; index < endIndexExcl; index++) {

View File

@ -214,6 +214,19 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
Assert.Equal("Fourth choice", target.SelectedOption.Text); Assert.Equal("Fourth choice", target.SelectedOption.Text);
} }
[Fact]
public void CanBindSelectToMarkup()
{
var target = new SelectElement(Browser.FindElement(By.Id("select-markup-box")));
var boundValue = Browser.FindElement(By.Id("select-markup-box-value"));
Assert.Equal("Second choice", target.SelectedOption.Text);
Assert.Equal("Second", boundValue.Text);
// Modify target; verify value is updated
target.SelectByText("Third choice");
Browser.Equal("Third", () => boundValue.Text);
}
[Fact] [Fact]
public void CanBindTextboxInt() public void CanBindTextboxInt()
{ {

View File

@ -145,18 +145,32 @@
<h2>Select</h2> <h2>Select</h2>
<p> <p>
<select id="select-box" @bind="selectValue"> <select id="select-box" @bind="selectValue">
<option value=@SelectableValue.First>First choice</option> <optgroup label="Some choices"> <!-- Show it also works with optgroup -->
<option value=@SelectableValue.Second>Second choice</option> <option value=@SelectableValue.First>First choice</option>
<option value=@SelectableValue.Third>Third choice</option> <option value=@SelectableValue.Second>Second choice</option>
@if (includeFourthOption) <option value=@SelectableValue.Third>Third choice</option>
{ @if (includeFourthOption)
<option value=@SelectableValue.Fourth>Fourth choice</option> {
} <option value=@SelectableValue.Fourth>Fourth choice</option>
}
</optgroup>
</select> </select>
<span id="select-box-value">@selectValue</span> <span id="select-box-value">@selectValue</span>
<button id="select-box-add-option" @onclick="AddAndSelectNewSelectOption">Add and select new item</button> <button id="select-box-add-option" @onclick="AddAndSelectNewSelectOption">Add and select new item</button>
</p> </p>
<h2>Select (markup block)</h2>
<p>
<select id="select-markup-box" @bind="selectMarkupValue">
<optgroup label="Some choices">
<option value="First">First choice</option>
<option value="Second">Second choice</option>
<option value="Third">Third choice</option>
</optgroup>
</select>
<span id="select-markup-box-value">@selectMarkupValue</span>
</p>
@code { @code {
string textboxInitiallyBlankValue = null; string textboxInitiallyBlankValue = null;
string textboxInitiallyPopulatedValue = "Hello"; string textboxInitiallyPopulatedValue = "Hello";
@ -187,6 +201,7 @@ Guid textboxGenericGuidValue = Guid.Empty;
bool includeFourthOption = false; bool includeFourthOption = false;
enum SelectableValue { First, Second, Third, Fourth } enum SelectableValue { First, Second, Third, Fourth }
SelectableValue selectValue = SelectableValue.Second; SelectableValue selectValue = SelectableValue.Second;
SelectableValue selectMarkupValue = SelectableValue.Second;
void AddAndSelectNewSelectOption() void AddAndSelectNewSelectOption()
{ {