blazorcheckboxlistmudblazor

Using MudBlazor, how can I respond to checkbox changes to create a Select All checkbox for a list of checkboxes


I'm trying to create something like the multi-select Select control except the list of checkboxes are always visible. I've gotten pretty close using @bind-Value and @oninput, but the @oninput fires before the checkbox value has changed causing the "Select All" state to be behind by 1 selection. Is there a better way to bind these, or is it possible to just have the Select control stay open?

Last resort is using a MudTable.

https://try.mudblazor.com/snippet/mamoEUPLgPhmeROZ

<MudCheckBox  @bind-Value="@AllSelected" @oninput="OnAllSelectedChanged" Label="Select All"> @AllSelected</MudCheckBox>


@foreach (var site in Sites)
{
    <MudCheckBox @bind-Value="@site.IsSelected" @oninput="CheckboxChanged" Label="@site.Name">@site.IsSelected</MudCheckBox>        
}

<MudTextField Label="Log" Variant="Variant.Filled" AutoGrow @bind-Value="@log"></MudTextField>

@code{ 
    
    public bool? AllSelected = null;
    public List<Site> Sites = new List<Site>();
    public string log = "";

    public class Site
    {
        public int Id;
        public string Name;
        public bool IsSelected;
    }

    protected override async Task OnInitializedAsync()
    {
        Sites.Add(new Site{Id=1,Name="Site1",IsSelected=false});
        Sites.Add(new Site{Id=2,Name="Site2",IsSelected=true});
        Sites.Add(new Site{Id=3,Name="Site3",IsSelected=true});
    }

    private void OnAllSelectedChanged(ChangeEventArgs e)
    {        
        AllSelected = (bool?)e.Value;
        foreach (var site in Sites)
        {
            site.IsSelected = AllSelected.Value;
        }
        
        log += $"All checkbox changed {e.Value}\r\n";
    }

    private void CheckboxChanged(ChangeEventArgs e)
    {        
        log += $"Checkbox changed {e.Value} ";

        if (Sites.All(x => x.IsSelected))
        {
            AllSelected = true;
            log += $"ALL\r\n";
        }
        else if (Sites.All(x => !x.IsSelected))
        {
            AllSelected = false;
            log += $"NONE\r\n";
        }
        else
        {
            AllSelected = null;
            log += $"SOME\r\n";
        }

        StateHasChanged();  //doesn't seem to work for updating AllSelected checkbox
    }
}    

Solution

  • Use the components Value & ValueChanged property instead of @oninput.

    Components are a wrapper around several elements so, whenever you can you should use the exposed properties over native blazor directives as it will lead to unexpected behaviours.

    Wherever, in the docs you see this "Binding TwoWay". It means that it has a {PARAMETER NAME}Changed property that you can use instead of @bind-{PARAMETER NAME}. This is the naming convention for Blazor data binding.

    enter image description here

    <MudCheckBox Value="@AllSelected" ValueChanged="OnAllSelected" Label="Select All" T="bool?">&nbsp;@AllSelected</MudCheckBox>
    
    @foreach (var site in Sites)
    {
        <MudCheckBox Value="@site.IsSelected" ValueChanged="@((bool newCheckboxValue) => CheckboxChanged(newCheckboxValue,site.Id))" Label="@site.Name">&nbsp;@site.IsSelected</MudCheckBox>        
    }
    
    <MudTextField Label="Log" Variant="Variant.Filled" AutoGrow @bind-Value="@log"></MudTextField>
    
    @code{ 
        
        public bool? AllSelected = false;
        public List<Site> Sites = new List<Site>();
        public string log = "";
    
        private void OnAllSelected(bool? allSelected)
        {   if(allSelected.HasValue)
            {
                AllSelected=allSelected;
                foreach (var site in Sites)
                {
                    site.IsSelected = AllSelected.Value;
                }
                log += $"All checkbox changed {AllSelected}\r\n";
            }
            AllSelected = allSelected;
        }
    
        private void CheckboxChanged(bool newCheckboxValue, int siteId)
        {        
            log += $"Checkbox changed for id:{siteId} with value:{newCheckboxValue}\r\n";
            Sites.Where(x=>x.Id == siteId).First().IsSelected=newCheckboxValue;
            if (Sites.All(x => x.IsSelected))
            {
                AllSelected = true;
                log += $"ALL\r\n";
            }else if (Sites.All(x => !x.IsSelected))
            {
                AllSelected = false;
                log += $"NONE\r\n";
            }
            else
            {
                AllSelected = null;
                log += $"SOME\r\n";
            }
            
        }
    
        public class Site
        {
            public int Id;
            public string Name;
            public bool IsSelected;
        }
    
        protected override async Task OnInitializedAsync()
        {
            Sites.Add(new Site{Id=1,Name="Site1",IsSelected=false});
            Sites.Add(new Site{Id=2,Name="Site2",IsSelected=true});
            Sites.Add(new Site{Id=3,Name="Site3",IsSelected=true});
            await base.OnInitializedAsync();
        }
    }
    

    Demo 👉MudBlazor Snippet