Blazor: binding to a MultiSelectList (ideally with a checkbox)

11,442

Solution 1

I got this to work with a component that takes the MultiSelectList as a parameter. There may be more elegant ways to achieve this (please do update if you know of a better way).

@using Microsoft.AspNetCore.Components
@using Microsoft.AspNetCore.Mvc.Rendering

<div class="multiselect">
    <div id="checkboxes">
        @foreach (var item in this.Items)
        {
            <div>
                <label for="@item.Value">
                    @if (item.Selected)
                    {
                        <input type="checkbox" id="@item.Value" checked="checked" @onchange="@((e) => CheckboxChanged(e, item.Value))" />
                    }
                    else
                    {
                        <input type="checkbox" id="@item.Value" @onchange="@((e) => CheckboxChanged(e, item.Value))" />
                    }
                    @item.Text
                </label>
            </div>
        }
    </div>
</div>

@code
{
    [Parameter]
    public MultiSelectList Items { get; set; } = null!;

    private void CheckboxChanged(ChangeEventArgs e, string key)
    {
        var i = this.Items.FirstOrDefault(i => i.Value == key);
        if (i != null)
        {
            i.Selected = (bool)e.Value;
        }
    }
}

Solution 2

Checkboxes are a bit different in blazor. Normally you would use the bind-value attribute on an input element as shown below, however, this is not recommended as you will only be able to read the value and NOT update the UI by changing the boolean value via code:

    <input type="checkbox" @bind-value="@item.Selected"/>

Instead, use the @bind syntax for checkboxes, which is much more robust and will work both ways (changing the bound boolean value from code & interacting with the checkbox on the UI). See the syntax below:

    <input type="checkbox" @bind="@item.Selected"/>

The bind attribute will automatically bind your boolean value to the "checked" property of the html element.

Also make sure you are binding to the "Selected" property rather than the "Value" property.

Using the built in bind will prevent the need to manually setup events as you did in your answer. You can also get rid of the if/else block and merge your code into a single code flow since you are now binding to the boolean rather than setting the checked property manually. If you still need to tap into an event to fire off some process(maybe hiding parts of UI on checking a box), I'd suggest using the onclick event and manually passing in the multiselect Item for each line. Here is the final code:

@foreach(var item in list)
{
    <input type="checkbox" @bind="item.Selected" @onclick="(()=>handleClick(item))" />
}
@foreach(var item in list.Where(x=>x.Selected))
{
    <p> Item @item.Text is Selected</p>
}

@code {
    MultiSelectList list = new MultiSelectList(new List<Car> { new Car { Year = 2019, Make = "Honda", Model = "Accord" }, new Car { Make = "Honda", Model = "Civic", Year = 2019 } });

    private void handleClick(SelectListItem item)
    {        
        //Do something crazy
    }

}
Share:
11,442
DrGriff
Author by

DrGriff

Updated on June 14, 2022

Comments

  • DrGriff
    DrGriff almost 2 years

    Experimenting with Blazor (Server, if that makes any difference), and I'm having difficulty getting binding to a MultiSelectList to work....

    Bit of background: I'm dealing with EF Core and have a Many-to-Many relationship, let's say between people and cars. I'm currently loading a page that shows the existing details, and allowing the user to update this page.

    So in my Service, I load my Person entity from the DB, and this includes the details of all the cars they currently own. I also load the list of all the available cars. My Service method then creates a MultiSelectList and adds it to my ViewModel (to be returned to the Razor Page):

    Service method

    vm.CarSelector = new MultiSelectList(
         allCars,
         nameof(Car.CarId), 
         nameof(Car.Name), 
         person.OwnedCars.Select(oc => oc.CarId));
    

    This is fictitious code, but I hope you get the picture. When debugging this (in the Service method) I can see that this MultiSelectList has an entry for every car, and the ones that are already selected are showing as Selected. Great!

    Blazor Razor Page

    So, this is where I come unstuck.... I can't work out how to do the two-way data-binding of a Razor control to this object.

    • I'm trying to use an <InputSelect />, but that might not be the best control to use.
    • ideally (actually, that's more of a "must have"), each option should have CheckBox.
    • I'm wondering whether the use of a MultiSelectList really buys me anything
  • Aaron Hudon
    Aaron Hudon over 3 years
    you can tighten this up and remove the if(item.Selected) conditional. Blazor will not output attributes if their value is false or null e.g. <input type="checkbox" id="@item.Value" checked="@(item.Selected)" @onchange="@((e) => CheckboxChanged(e, item.Value))" />