diff --git a/Radzen.Blazor.Tests/DropDownTests.cs b/Radzen.Blazor.Tests/DropDownTests.cs index 74c7e380a00..9fd1d7264e1 100644 --- a/Radzen.Blazor.Tests/DropDownTests.cs +++ b/Radzen.Blazor.Tests/DropDownTests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Threading.Tasks; using AngleSharp.Dom; using Bunit; @@ -114,6 +115,34 @@ public void DropDown_AppliesSelectionStyleForStringValue() Assert.Contains("rz-state-highlight", items[0].ClassList); } + [Fact] + public void DropDown_Respects_ItemEqualityComparer() + { + using var ctx = new TestContext(); + ctx.JSInterop.Mode = JSRuntimeMode.Loose; + + List boundCollection = [new() { Text = "Item 2" }]; + + var component = DropDown(ctx, parameters => { + parameters.Add(p => p.ItemComparer, new DataItemComparer()); + parameters.Add(p => p.Multiple, true); + parameters.Add(p => p.Value, boundCollection); + }); + + var selectedItems = component.FindAll(".rz-state-highlight"); + Assert.Equal(1, selectedItems.Count); + Assert.Equal("Item 2", selectedItems[0].TextContent.Trim()); + + // select Item 1 in list + var items = component.FindAll(".rz-multiselect-item"); + items[0].Click(); + component.Render(); + + selectedItems = component.FindAll(".rz-state-highlight"); + Assert.Equal(2, selectedItems.Count); + Assert.Equal("Item 1", selectedItems[0].TextContent.Trim()); + } + [Fact] public void DropDown_AppliesSelectionStyleWhenMultipleSelectionIsEnabled() { @@ -245,5 +274,34 @@ public void DropDown_AppliesValueTemplateOnMultipleSelectionChips() Assert.Collection(selectedItems, item => Assert.Contains("value: Item 1", item.Text()), item => Assert.Contains("value: Item 2", item.Text())); } + + + class DataItemComparer : IEqualityComparer, IEqualityComparer + { + public bool Equals(DataItem x, DataItem y) + { + if (ReferenceEquals(x, y)) return true; + if (x is null) return false; + if (y is null) return false; + if (x.GetType() != y.GetType()) return false; + return x.Text == y.Text; + } + + public int GetHashCode(DataItem obj) + { + return obj.Text.GetHashCode(); + } + + public bool Equals(object x, object y) + { + return Equals((DataItem)x, (DataItem)y); + } + + public int GetHashCode(object obj) + { + return GetHashCode((DataItem)obj); + + } + } } -} \ No newline at end of file +} diff --git a/Radzen.Blazor/DropDownBase.cs b/Radzen.Blazor/DropDownBase.cs index 5e783b44bcd..fda71ca6ff5 100644 --- a/Radzen.Blazor/DropDownBase.cs +++ b/Radzen.Blazor/DropDownBase.cs @@ -272,7 +272,7 @@ internal object GetKey(object item) /// /// The selected items /// - protected IList selectedItems = new List(); + protected ISet selectedItems = new HashSet(); /// /// The selected item /// @@ -288,10 +288,10 @@ protected virtual async System.Threading.Tasks.Task SelectAll() return; } - if (selectedItems.Count != View.Cast().ToList().Where(i => disabledPropertyGetter != null ? disabledPropertyGetter(i) as bool? != true : true).Count()) + if (selectedItems.Count != View.Cast().ToList().Where(i => disabledPropertyGetter == null || disabledPropertyGetter(i) as bool? != true).Count()) { selectedItems.Clear(); - selectedItems = View.Cast().ToList().Where(i => disabledPropertyGetter != null ? disabledPropertyGetter(i) as bool? != true : true).ToList(); + selectedItems = View.Cast().ToList().Where(i => disabledPropertyGetter == null || disabledPropertyGetter(i) as bool? != true).ToHashSet(ItemComparer); } else { @@ -453,6 +453,8 @@ protected override void OnParametersSet() { disabledPropertyGetter = GetGetter(DisabledProperty, type); } + + selectedItems = new HashSet(ItemComparer); } } @@ -678,7 +680,7 @@ protected virtual async System.Threading.Tasks.Task HandleKeyPress(Microsoft.Asp var itemToSelect = items.ElementAtOrDefault(selectedIndex); await JSRuntime.InvokeAsync("Radzen.setInputValue", search, $"{searchText}".Trim()); - + if (itemToSelect != null) { await OnSelectItem(itemToSelect, true); @@ -990,7 +992,7 @@ internal bool IsSelected(object item) { if (Multiple) { - return selectedItems.IndexOf(item) != -1; + return selectedItems.Contains(item); } else { @@ -1216,18 +1218,14 @@ internal void UpdateSelectedItems(object item) } else { - selectedItems = selectedItems.AsQueryable().Where(DynamicLinqCustomTypeProvider.ParsingConfig, $@"!object.Equals(it.{ValueProperty},@0)", value).ToList(); + selectedItems = selectedItems.AsQueryable().Where(DynamicLinqCustomTypeProvider.ParsingConfig, $@"!object.Equals(it.{ValueProperty},@0)", value).ToHashSet(ItemComparer); } } else { - if (!selectedItems.Any(i => object.Equals(i, item))) + if (!selectedItems.Add(item)) { - selectedItems.Add(item); - } - else - { - selectedItems = selectedItems.Where(i => !object.Equals(i, item)).ToList(); + selectedItems.Remove(item); } } } @@ -1289,7 +1287,7 @@ protected virtual void SelectItemFromValue(object value) } else { - selectedItems = ((IEnumerable)values).Cast().ToList(); + selectedItems = values.Cast().ToHashSet(ItemComparer); } } @@ -1301,6 +1299,11 @@ protected virtual void SelectItemFromValue(object value) } } + /// + /// For lists of objects, an IEqualityComparer to control how selected items are determined + /// + [Parameter] public IEqualityComparer ItemComparer { get; set; } + internal bool IsItemSelectedByValue(object v) { switch (internalValue) diff --git a/Radzen.Blazor/RadzenDropDownDataGrid.razor b/Radzen.Blazor/RadzenDropDownDataGrid.razor index c9806781ba3..e9a4fc8ffea 100644 --- a/Radzen.Blazor/RadzenDropDownDataGrid.razor +++ b/Radzen.Blazor/RadzenDropDownDataGrid.razor @@ -37,7 +37,7 @@ } else if ((selectedItems.Count > 0 || SelectedValue is IEnumerable && !(SelectedValue is string)) && Multiple) { - var itemsToUse = SelectedValue is IEnumerable && !(SelectedValue is string) ? ((IEnumerable)SelectedValue).Cast().ToList() : selectedItems; + var itemsToUse = SelectedValue is IEnumerable && !(SelectedValue is string) ? ((IEnumerable)SelectedValue).Cast().ToHashSet() : selectedItems; @if (itemsToUse.Count < MaxSelectedLabels && Chips) {
diff --git a/Radzen.Blazor/RadzenDropDownDataGrid.razor.cs b/Radzen.Blazor/RadzenDropDownDataGrid.razor.cs index 8f132cce60d..b63a097d451 100644 --- a/Radzen.Blazor/RadzenDropDownDataGrid.razor.cs +++ b/Radzen.Blazor/RadzenDropDownDataGrid.razor.cs @@ -628,10 +628,7 @@ protected override void SelectItemFromValue(object value) { foreach (object v in valueList) { - if (selectedItems.IndexOf(v) == -1) - { - selectedItems.Add(v); - } + selectedItems.Add(v); } } diff --git a/RadzenBlazorDemos/Models/Northwind/Product.cs b/RadzenBlazorDemos/Models/Northwind/Product.cs index 42f00c301b6..88ee58d737e 100644 --- a/RadzenBlazorDemos/Models/Northwind/Product.cs +++ b/RadzenBlazorDemos/Models/Northwind/Product.cs @@ -16,6 +16,34 @@ public int ProductID set; } + public static ProductComparer Comparer { get; } = new(); + public class ProductComparer : IEqualityComparer, IEqualityComparer + { + public bool Equals(Product x, Product y) + { + if (ReferenceEquals(x, y)) return true; + if (x is null) return false; + if (y is null) return false; + if (x.GetType() != y.GetType()) return false; + return x.ProductName == y.ProductName; + } + + public int GetHashCode(Product obj) + { + return (obj.ProductName != null ? obj.ProductName.GetHashCode() : 0); + } + + public bool Equals(object x, object y) + { + return Equals((Product)x, (Product)y); + } + + public int GetHashCode(object obj) + { + return GetHashCode((Product)obj); + } + } + [InverseProperty("Product")] public ICollection OrderDetails { get; set; } diff --git a/RadzenBlazorDemos/Pages/DropDownMultipleItemComparer.razor b/RadzenBlazorDemos/Pages/DropDownMultipleItemComparer.razor new file mode 100644 index 00000000000..0884f88e7ee --- /dev/null +++ b/RadzenBlazorDemos/Pages/DropDownMultipleItemComparer.razor @@ -0,0 +1,27 @@ +@using RadzenBlazorDemos.Models.Northwind + +@inherits DbContextPage + + + + + + +@code { + IEnumerable values = []; + IEnumerable products; + + protected override async Task OnInitializedAsync() + { + await base.OnInitializedAsync(); + + products = dbContext.Products; + values = + [ + new Product { ProductName = "Chai" }, + new Product { ProductName = "Aniseed Syrup" } + ]; + } +} diff --git a/RadzenBlazorDemos/Pages/DropDownMultiplePage.razor b/RadzenBlazorDemos/Pages/DropDownMultiplePage.razor index 40144daa16a..05ed28478fd 100644 --- a/RadzenBlazorDemos/Pages/DropDownMultiplePage.razor +++ b/RadzenBlazorDemos/Pages/DropDownMultiplePage.razor @@ -20,3 +20,10 @@ + + + Specify an Equality Comparer for item selection. Useful when binding directly to an object collection. + + + +