dotnet/aspnetcore

Support binding to <select multiple>, getting/setting the selection as an array

dlr1 opened this issue ยท 13 comments

dlr1 commented

With the following code

 <select name="cars" multiple onchange="@SelectionChanged">
        <option value="volvo">Volvo</option>
        <option value="saab">Saab</option>
        <option value="opel">Opel</option>
        <option value="audi">Audi</option>
    </select>
 void SelectionChanged(UIChangeEventArgs e)
        {
            Console.WriteLine(e.Value);
        }

Holding down the ctrl key, if you select 'volvo' first and then any other value, e.value is always 'volvo'. It basically returns the top selected item in the list.

I have checked #805 and it looks that in simple binding scenario it doesn't work either. The question is: what data type should we use to bind to "select multiple" and what should be returned in e.Value? Array of strings or simple string (multiple values with separators)?

Great point to raise. We don't have any specific API in place for this scenario.

I guess a reasonable design would be binding to a string[], but that's not yet implemented.

We can add it to the backlog.

Just to be clear, Blazor doesn't currently support selects that are defined as multi select? If it doesn't, is there a simple way around it?

@pjmagee There is a workaround. I used a javascript interop call to get the list of selected items from the list. To set the initial selections, you can set the <select value=@selections> where selections is a List<string>.

   <select ref="sel" multiple="multiple" onchange=@SelectionChanged value=@selections>
         @foreach (string s in MyStringList)
         {
            <option value="@s">@s</option>
         }
     </select>

@functions {

[Parameter]
private List<string> FilteredStringList { get; set; } = new List<string>();
private List<string> selections = new List<string>();  

public async Task<List<string>> GetAllSelections()
{
    return (await JSRuntime.Current.InvokeAsync<List<string>>("getSelectedValues", sel)).ToList();
}

 private async Task SelectionChanged(UIChangeEventArgs evt)
 {
    List<string> localSelections = await GetAllSelections();
    // do something with localSelections.  or wait for some other
    //  event like a button and call GetAllSelections() to get the list later.
  }
}

in the html page include the getSelectedValues function. The function below works, but
I know there are faster ways of getting the list.

  window.getSelectedValues = function(sel) {
    var results = [];
    var i;
    for (i = 0; i < sel.options.length; i++) {
        if (sel.options[i].selected) {
            results[results.length] = sel.options[i].value;
        }
    }
    return results;
};

Great point to raise. We don't have any specific API in place for this scenario.

I guess a reasonable design would be binding to a string[], but that's not yet implemented.

We can add it to the backlog.

Hi Steve. I cannot find any new information on this, could you confirm if this is still in the backlog or if it has been implemented? Thanks.

It is still in the backlog. Currently we're focused on shipping Blazor WebAssembly, during which we're not making any changes to the core Blazor components programming model. The next phase during which we'll do programming model enhancements is for .NET 5, which we'll be working on between May and November.

acpt commented

Still not working ?
Well, I'm trying to use it and its not working as intended...

Any Updates?

I can't seem to find it on the project board (https://github.com/dotnet/aspnetcore/projects/12).

is it still planned for the .NET5 release?

No, sorry, this was never planned for .NET 5 (I only said that was the earliest it could be done). We packed in absolutely as much as we could, but this didnโ€™t make the cut. I know that will seem disappointing if this one was particularly important to you.

For now if you need this youโ€™d need to implement it manually. Sorry!

Itโ€™s on the backlog that weโ€™ll review for .NET 6.

Thanks for contacting us.
We're moving this issue to the Next sprint planning milestone for future evaluation / consideration. We will evaluate the request when we are planning the work for the next milestone. To learn more about what to expect next and how this issue will be handled you can read more about our triage process here.

Okay, so it would have been nice if this had been working already, but what @mikewodarczyk wrote doesn't work anymore. What works for me currently is:

@functions {

    private async Task OnChangeCallback(ChangeEventArgs eventArgs)
    {
        var selection = await GetAllSelections(_elementReference);
        Model.MyListProperty = selection;
    }

    private ElementReference _elementReference;

    public async Task<List<string>> GetAllSelections(ElementReference elementReference)
    {
        return  (await JSRuntime.InvokeAsync<List<string>>("getSelectedValues", elementReference)).ToList();
    }
}

And then as HTML:

<select @ref="_elementReference" value="@Model.MyListProperty" multiple="multiple" @onchange="OnChangeCallback">
    @foreach (var option in SomeService.Options)
    {
        <option value="@option">@option</option>
    }
</select>

JS stays the same as mikewodarczyk wrote, but let's at least use Typescript ;)

function getSelectedValues(sel: HTMLSelectElement)
{
    const results = [];

    for (let i = 0; i < sel.options.length; i++) {
        if (sel.options[i].selected) {
            results[results.length] = sel.options[i].value;
        }
    }
    return results;
};

Closing because this was done in #33950.