CodeBeamOrg / CodeBeam.MudBlazor.Extensions

Useful third party extension components for MudBlazor, from the contributors.
https://mudextensions.codebeam.org/
MIT License
369 stars 62 forks source link

MudSelectExtended update parent component from child and persist selected Values #200

Open MartinSoka opened 1 year ago

MartinSoka commented 1 year ago

Hi community, I have a chipset in my parent component and a MudSelectExtended in child component. Both are updated properly, but when navigating away to a different page and navigating back to the page MudSelectExtended doesn't show the values as selected, it seems like it threats them as different objects. I was experimenting with SelectedValues prop, binding and ended up using the ref keyword with updating the values on eventcallback to parent.

I have read about overriding GetHashCode and Equals for custom reference types here: https://github.com/MudBlazor/MudBlazor/discussions/3532 however i wasn't able to get it working, so i was not sure if this is a good lead.

I have attached a simplified repo here: https://github.com/MartinSoka/MudBlazor_Repro/

I'm a blazor beginner so apologies in advance if i do something horribly wrong and many thanks for help!

parent

<MudChipSet MultiSelection="true" AllClosable="true" OnClose="UpdateItems">
    @foreach (var selecteItem in Model.SeletedItems)
    {
        <MudChip Text="@selecteItem.Title"></MudChip>
    }
</MudChipSet>

<MySearchBox AllItems="@AllItems" SelectedItems="@Model.SeletedItems" SelectedItemsChanged="@((IEnumerable<Item> selectedValues) => Model.SeletedItems = selectedValues)" />

@code {
    public List<Item> AllItems { get; set; } = new List<Item> { new Item { Title = "someTitle" }, new Item { Title = "otherTitle" } };

    public void UpdateItems(MudChip chip)
    {
        Model.SeletedItems = Model.SeletedItems.Where(l => l.Title != chip.Text).ToList();
        StateHasChanged();
    }

    public static class Model
    {
        public static IEnumerable<Item> SeletedItems { get; set; } = new HashSet<Item>();
    }

}

child-MySearchBox

<MudSelectExtended
    @ref="mudSelect"
    Text=""
    Placeholder="This text shoudln't change"
    ToStringFunc="@(i=> ItemDisplay(i, AllItems ))"
    MultiSelection="true"
    ItemCollection="@AllItems"
    SelectedValuesChanged="@((IEnumerable<Item> value) => HandleSelectionChanged())"
    AdornmentIcon="@Icons.Material.Filled.Search">

    @foreach (var item in AllItems)
    {
        <MudSelectItemExtended Value="item">@item.Title</MudSelectItemExtended>
    }
</MudSelectExtended>

@code {

    [Parameter] public List<Item> AllItems{ get; set; }

    [Parameter] public IEnumerable<Item> SelectedItems { get; set; }

    [Parameter] public EventCallback<IEnumerable<Item>> SelectedItemsChanged { get; set; }

    private MudSelectExtended<Item> mudSelect;

    private string ItemDisplay(Item item, IEnumerable<Item> items)
    {
        var i = items.FirstOrDefault(i => i.Title == item.Title);

        return i == null ? "!Not Found!" : $"{i.Title}";
    }

    private async Task HandleSelectionChanged()
    {
        await SelectedItemsChanged.InvokeAsync(mudSelect.SelectedValues);
    }
}
MartinSoka commented 1 year ago

I was able to get it working when i use @bind-SelectedValues and no child component. However once i want to extract this into a separate component and use the @ref keyword together with the EventCallback, the items are not displayed as selected in the MudSelectExtended.

This is the edited code:

 private async Task HandleSelectionChanged()
    {
        HashSet<Item> uniqueObjects = new HashSet<Item>(
    SelectedItems .Concat(mudSelect.SelectedValues),
    new TypeEqualityComparer());

        await SelectedItemsChanged.InvokeAsync(uniqueObjects);
    }

    public class TypeEqualityComparer : IEqualityComparer<Item>
    {
        public bool Equals(Item x, Item y)
        {
            return x.Id == y.Id;
        }

        public int GetHashCode(Item obj)
        {
            return obj.Id.GetHashCode();
        }
    }