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;
}
}
// 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) {
@ -317,7 +328,8 @@ export class BrowserRenderer {
// <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>.
// we can recover it when inserting any matching <option> or after inserting an
// entire markup block of descendants.
element[selectValuePropname] = value;
}
return true;
@ -330,10 +342,11 @@ export class BrowserRenderer {
element.removeAttribute('value');
}
// See above for why we have this special handling for <select>/<option>
const parentElement = element.parentElement;
if (parentElement && (selectValuePropname in parentElement) && parentElement[selectValuePropname] === value) {
this.tryApplyValueProperty(batch, parentElement, attributeFrame);
delete parentElement[selectValuePropname];
// Note that this is only one of the two cases where we set the value on a <select>
const selectElem = this.findClosestAncestorSelectElement(element);
if (selectElem && (selectValuePropname in selectElem) && selectElem[selectValuePropname] === value) {
this.tryApplyValueProperty(batch, selectElem, attributeFrame);
delete selectElem[selectValuePropname];
}
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 {
const origChildIndex = childIndex;
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);
}
[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]
public void CanBindTextboxInt()
{

View File

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