Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce ItemComparer parameter to DropDownBase, for IEqualityComparer support #1854

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@
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 @@

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>
Loading