Skip to content

Commit

Permalink
Introduce ItemComparer parameter to DropDownBase, for IEqualityCompar…
Browse files Browse the repository at this point in the history
…er support (#1854)

* Introduce ItemComparer parameter to DropDownBase. Use HashSet to track selectedItems.

* include summary for ItemComparer Parameter
  • Loading branch information
pianomanjh authored Dec 16, 2024
1 parent 11a037a commit c9b5a53
Show file tree
Hide file tree
Showing 7 changed files with 139 additions and 19 deletions.
60 changes: 59 additions & 1 deletion Radzen.Blazor.Tests/DropDownTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using AngleSharp.Dom;
using Bunit;
Expand Down Expand Up @@ -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<DataItem> boundCollection = [new() { Text = "Item 2" }];

var component = DropDown<string>(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()
{
Expand Down Expand Up @@ -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<DataItem>, IEqualityComparer<object>
{
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)

Check warning on line 295 in Radzen.Blazor.Tests/DropDownTests.cs

View workflow job for this annotation

GitHub Actions / build

'DropDownTests.DataItemComparer.Equals(object, object)' hides inherited member 'object.Equals(object?, object?)'. Use the new keyword if hiding was intended.
{
return Equals((DataItem)x, (DataItem)y);
}

public int GetHashCode(object obj)
{
return GetHashCode((DataItem)obj);

}
}
}
}
}
29 changes: 16 additions & 13 deletions Radzen.Blazor/DropDownBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ internal object GetKey(object item)
/// <summary>
/// The selected items
/// </summary>
protected IList<object> selectedItems = new List<object>();
protected ISet<object> selectedItems = new HashSet<object>();
/// <summary>
/// The selected item
/// </summary>
Expand All @@ -288,10 +288,10 @@ protected virtual async System.Threading.Tasks.Task SelectAll()
return;
}

if (selectedItems.Count != View.Cast<object>().ToList().Where(i => disabledPropertyGetter != null ? disabledPropertyGetter(i) as bool? != true : true).Count())
if (selectedItems.Count != View.Cast<object>().ToList().Where(i => disabledPropertyGetter == null || disabledPropertyGetter(i) as bool? != true).Count())
{
selectedItems.Clear();
selectedItems = View.Cast<object>().ToList().Where(i => disabledPropertyGetter != null ? disabledPropertyGetter(i) as bool? != true : true).ToList();
selectedItems = View.Cast<object>().ToList().Where(i => disabledPropertyGetter == null || disabledPropertyGetter(i) as bool? != true).ToHashSet(ItemComparer);
}
else
{
Expand Down Expand Up @@ -453,6 +453,8 @@ protected override void OnParametersSet()
{
disabledPropertyGetter = GetGetter(DisabledProperty, type);
}

selectedItems = new HashSet<object>(ItemComparer);
}
}

Expand Down Expand Up @@ -678,7 +680,7 @@ protected virtual async System.Threading.Tasks.Task HandleKeyPress(Microsoft.Asp
var itemToSelect = items.ElementAtOrDefault(selectedIndex);

await JSRuntime.InvokeAsync<string>("Radzen.setInputValue", search, $"{searchText}".Trim());

if (itemToSelect != null)
{
await OnSelectItem(itemToSelect, true);
Expand Down Expand Up @@ -990,7 +992,7 @@ internal bool IsSelected(object item)
{
if (Multiple)
{
return selectedItems.IndexOf(item) != -1;
return selectedItems.Contains(item);
}
else
{
Expand Down Expand Up @@ -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);
}
}
}
Expand Down Expand Up @@ -1289,7 +1287,7 @@ protected virtual void SelectItemFromValue(object value)
}
else
{
selectedItems = ((IEnumerable)values).Cast<object>().ToList();
selectedItems = values.Cast<object>().ToHashSet(ItemComparer);
}

}
Expand All @@ -1301,6 +1299,11 @@ protected virtual void SelectItemFromValue(object value)
}
}

/// <summary>
/// For lists of objects, an IEqualityComparer to control how selected items are determined
/// </summary>
[Parameter] public IEqualityComparer<object> ItemComparer { get; set; }

internal bool IsItemSelectedByValue(object v)
{
switch (internalValue)
Expand Down
2 changes: 1 addition & 1 deletion Radzen.Blazor/RadzenDropDownDataGrid.razor
Original file line number Diff line number Diff line change
Expand Up @@ -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<object>().ToList() : selectedItems;
var itemsToUse = SelectedValue is IEnumerable && !(SelectedValue is string) ? ((IEnumerable)SelectedValue).Cast<object>().ToHashSet() : selectedItems;
@if (itemsToUse.Count < MaxSelectedLabels && Chips)
{
<div class="rz-dropdown-chips-wrapper">
Expand Down
5 changes: 1 addition & 4 deletions Radzen.Blazor/RadzenDropDownDataGrid.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

Expand Down
28 changes: 28 additions & 0 deletions RadzenBlazorDemos/Models/Northwind/Product.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,34 @@ public int ProductID
set;
}

public static ProductComparer Comparer { get; } = new();
public class ProductComparer : IEqualityComparer<Product>, IEqualityComparer<object>
{
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<OrderDetail> OrderDetails { get; set; }
Expand Down
27 changes: 27 additions & 0 deletions RadzenBlazorDemos/Pages/DropDownMultipleItemComparer.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
@using RadzenBlazorDemos.Models.Northwind

@inherits DbContextPage

<RadzenStack Orientation="Orientation.Horizontal" AlignItems="AlignItems.Center" JustifyContent="JustifyContent.Center" Gap="0.5rem" class="rz-p-sm-12">
<RadzenLabel Text="Select Values" Component="DropDownMultiple" />
<RadzenDropDown @bind-Value=@values Data=@products TextProperty="@nameof(Product.ProductName)" Name="DropDownMultiple"
Multiple=true AllowClear=true Placeholder="Select products" Style="width: 100%; max-width: 400px;"
ItemComparer="@Product.Comparer"/>
</RadzenStack>

@code {
IEnumerable<Product> values = [];
IEnumerable<Product> products;

protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();

products = dbContext.Products;
values =
[
new Product { ProductName = "Chai" },
new Product { ProductName = "Aniseed Syrup" }
];
}
}
7 changes: 7 additions & 0 deletions RadzenBlazorDemos/Pages/DropDownMultiplePage.razor
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,10 @@
<RadzenExample ComponentName="DropDown" Example="DropDownMultipleMaxLabels">
<DropDownMultipleMaxLabels />
</RadzenExample>

<RadzenText Anchor="dropdown-multiple#item-comparer" TextStyle="TextStyle.H5" TagName="TagName.H2" class="rz-pt-8 rz-mb-6">
Specify an Equality Comparer for item selection. Useful when binding directly to an object collection.
</RadzenText>
<RadzenExample ComponentName="DropDown" Example="DropDownMultipleItemComparer">
<DropDownMultipleItemComparer />
</RadzenExample>

0 comments on commit c9b5a53

Please sign in to comment.