Search code examples
c#blazorblazor-server-sideasp.net-blazor

Unselect main checkbox on any other checkbox selection change in asp.net blazor


I've written a generic grid component where I've a checkbox in column header which can be selected to check all rows checkboxes and it working fine but there is an issue when I'm unselecting any other checkboxes then the main checkbox should also be unselected and that is not working as expected:

enter image description here

below is my code for the generic grid-

@if (Items == null)
{
    <p><em>Loading...</em></p>
}
else {
    <table class="table">
        <thead>
            <tr>
                <th>
                    <input type="checkbox" id="chkAll" checked="@SelectMain" @onchange="ChangeCheckboxState"/>
                </th>
                @Columns(default(TItem))
            </tr>
        </thead>
        <tbody>
            @foreach (var item in Items) {
            var id = $"chk{counter}";
            <CascadingValue Value="item">
                <tr>
                    <td class="col-checkbox chkColumn quarter">
                       <input type="checkbox" id="@id" checked="@SelectAll" @onchange="ChangeCheckboxSingle"/>
                    </td>
                    @Columns(item)
                </tr>
            </CascadingValue>
            counter++;
        }
        </tbody>
    </table>
}

@code {
    [Parameter]
    public IList<TItem> Items { get; set; }

    [Parameter]
    public RenderFragment<TItem>? Columns { get; set; }
    public bool SelectAll { get; set; } = false;
    public bool SelectMain { get; set; } = false;
    int counter = 1;

    void ChangeCheckboxState(ChangeEventArgs e){
        if (Convert.ToBoolean(e.Value))
            SelectAll = true;
        else
            SelectAll = false;
    }

    void ChangeCheckboxSingle(ChangeEventArgs e){
        SelectMain = false;
        StateHasChanged();
    }
}

and lastly, what is the actual difference between @onselectionchange and @onchange.


Solution

  • Don't worry, I too find binding checkboxes quite annoying in Blazor. In short, your Render DOM gets out of sync with the Browser DOM. Find Shauns detailed explanation about what's happening here.

    The trick is to toggle the set value using await Task.Yield(); between toggles. This allows Blazor to perform it's double render. Change your event handlers to this and it will work;

    Note: your ChangeCheckboxState handler could have been simplified to one line SelectAll = Convert.ToBoolean(e.Value). Below I just simply your event and toggle it using the yield trick.

    async void ChangeCheckboxState(ChangeEventArgs e){
        SelectAll = !Convert.ToBoolean(e.Value);
        await Task.Yield();
        SelectAll = !SelectAll;
        StateHasChanged();
    }
    
    async void ChangeCheckboxSingle(ChangeEventArgs e){
        SelectMain = true;
        await Task.Yield();
        SelectMain = false;
        StateHasChanged();
    }
    

    and lastly, what is the actual difference between @onselectionchange and @onchange.

    The selectionchange event of the Selection API is fired when the text selection within an <input> element is changed. This includes both changes in the selected range of characters, or if the caret moves.

    The change event is fired for <input>, <select>, and <textarea> elements when an alteration to the element's value is committed by the user. Unlike the input event, the change event is not necessarily fired for each alteration to an element's value.

    Basically the onselectionchanged event is only for input type text and it only works on FireFox. It is part of the Selection API, not the HTML Standard. I avoid it for these reasons. Verify this yourself by putting a break point in the following code and type something in the textbox.

    <input @onselectionchange="SelectChanged"/>
    
    async void SelectChanged(EventArgs e)
    {
        
    }