Skip to content

Commit 52f5ea2

Browse files
fix: MudSelectExtended search with ItemCollection and complex types (#606) (#610)
* fix(ListExtended): use ToStringFunc for ItemCollection search on complex types (#606) Search with ItemCollection was ignoring ToStringFunc and falling back to Converter.Convert() for complex types, making search fail for custom display formats. - Use ToStringFunc when available for ItemCollection search - Add integration tests for complex type search - Update docs with complex type example * Simple Docs Changes --------- Co-authored-by: Mehmet Can Karagöz <m.cankaragoz@outlook.com>
1 parent 0e5282e commit 52f5ea2

File tree

7 files changed

+179
-52
lines changed

7 files changed

+179
-52
lines changed

docs/CodeBeam.MudBlazor.Extensions.Docs/Pages/Components/SelectExtended/Examples/SelectExtendedExample3.razor

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,21 +25,21 @@
2525
</MudItem>
2626

2727
<MudItem xs="12" sm="4">
28-
<MudCheckBox @bind-Value="_multiSelection" Label="MultiSelection" Color="Color.Primary" />
29-
<MudCheckBox @bind-Value="_sticky" Label="Sticky" Color="Color.Primary" />
30-
<MudCheckBox @bind-Value="_nested" Label="Nested" Color="Color.Primary" />
31-
<MudCheckBox @bind-Value="_initiallyExpanded" Label="Initially Expanded" Color="Color.Primary" />
28+
<MudCheckBox @bind-Value="_multiSelection" Label="MultiSelection" Color="Color.Secondary" />
29+
<MudCheckBox @bind-Value="_sticky" Label="Sticky" Color="Color.Secondary" />
30+
<MudCheckBox @bind-Value="_nested" Label="Nested" Color="Color.Secondary" />
31+
<MudCheckBox @bind-Value="_initiallyExpanded" Label="Initially Expanded" Color="Color.Secondary" />
3232
<MudDivider />
3333
<MudText Class="ma-4">Selected Value: @_selectedValue</MudText>
3434
<MudText Class="ma-4">Selected Values: @(_selectedValues == null ? null : string.Join(", ", _selectedValues))</MudText>
3535
</MudItem>
3636
</MudGrid>
3737

3838
@code {
39-
bool _multiSelection = false;
40-
bool _sticky = true;
41-
bool _nested = false;
42-
bool _initiallyExpanded = false;
43-
string? _selectedValue;
44-
IEnumerable<string>? _selectedValues;
39+
private bool _multiSelection = false;
40+
private bool _sticky = true;
41+
private bool _nested = false;
42+
private bool _initiallyExpanded = false;
43+
private string? _selectedValue;
44+
private IEnumerable<string>? _selectedValues;
4545
}

docs/CodeBeam.MudBlazor.Extensions.Docs/Pages/Components/SelectExtended/Examples/SelectExtendedExample5.razor

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,15 @@
1010
}
1111
</MudSelectExtended>
1212

13-
<MudSelectExtended MultiSelectionTextFunc="@(_radioGroup?.Value == 1 ? new Func<List<string>, string?>(GetMultiSelectionText2) : null)" MultiSelection="true" ValuePresenter="_valuePresenter" @bind-Value="stringValue" @bind-SelectedValues="stringValues" T="string" Label="US States" AnchorOrigin="Origin.BottomCenter"
13+
<MudSelectExtended MultiSelectionTextFunc="@(_radioGroup?.Value == 1 ? new Func<List<string>, string?>(GetMultiSelectionText2) : null)" MultiSelection="true" ValuePresenter="_valuePresenter" @bind-Value="_stringValue" @bind-SelectedValues="_stringValues" T="string" Label="US States" AnchorOrigin="Origin.BottomCenter"
1414
ChipCloseable="_chipCloseable" ChipSize="_chipSize" ChipVariant="_chipVariant" NoWrap="_nowrap">
1515
@foreach (var state in states)
1616
{
1717
<MudSelectItemExtended T="string" Value="@state" Text="@state" />
1818
}
1919
</MudSelectExtended>
2020

21-
<MudSelectExtended ItemCollection="states" MultiSelectionTextFunc="@(_radioGroup?.Value == 1 ? new Func<List<string>, string?>(GetMultiSelectionText2) : null)" MultiSelection="true" ValuePresenter="_valuePresenter" @bind-Value="stringValue" @bind-SelectedValues="stringValues" T="string" Label="US States" AnchorOrigin="Origin.BottomCenter"
21+
<MudSelectExtended ItemCollection="states" MultiSelectionTextFunc="@(_radioGroup?.Value == 1 ? new Func<List<string>, string?>(GetMultiSelectionText2) : null)" MultiSelection="true" ValuePresenter="_valuePresenter" @bind-Value="_stringValue" @bind-SelectedValues="_stringValues" T="string" Label="US States" AnchorOrigin="Origin.BottomCenter"
2222
ChipCloseable="_chipCloseable" ChipSize="_chipSize" ChipVariant="_chipVariant" NoWrap="_nowrap">
2323
</MudSelectExtended>
2424

@@ -38,42 +38,42 @@
3838

3939
<MudItem xs="12" sm="4">
4040
<MudRadioGroup @ref="_radioGroup" T="int" ValueChanged="GroupOptionChanged">
41-
<MudRadio Value="0" Color="Color.Primary">Standard Text</MudRadio>
42-
<MudRadio Value="1" Color="Color.Primary">Customized Text</MudRadio>
43-
<MudRadio Value="2" Color="Color.Primary">Chip</MudRadio>
41+
<MudRadio Value="0" Color="Color.Secondary">Standard Text</MudRadio>
42+
<MudRadio Value="1" Color="Color.Secondary">Customized Text</MudRadio>
43+
<MudRadio Value="2" Color="Color.Secondary">Chip</MudRadio>
4444
</MudRadioGroup>
4545
<MudGrid Class="mt-3 px-4">
4646
<MudItem xs="6">
4747
<MudText Typo="Typo.subtitle2">Value:</MudText>
4848
<MudText Typo="Typo.subtitle2">"</MudText>
49-
<MudText Typo="Typo.body2" Class="pl-4">@stringValue</MudText>
49+
<MudText Typo="Typo.body2" Class="pl-4">@_stringValue</MudText>
5050
<MudText Typo="Typo.subtitle2">"</MudText>
5151
</MudItem>
5252
<MudItem xs="6">
5353
<MudText Typo="Typo.subtitle2">SelectedValues: HashSet&lt;string&gt;</MudText>
5454
<MudText Typo="Typo.subtitle2">{</MudText>
55-
<MudText Typo="Typo.body2" Class="pl-4">@(string.Join(", ", stringValues.Select(x => $"\"{x}\"")))</MudText>
56-
<MudText Typo="Typo.subtitle2">}</MudText>
57-
</MudItem>
58-
</MudGrid>
59-
<MudSwitchM3 @bind-Value="_chipCloseable" Label="Chip Closeable" Color="Color.Primary" />
60-
<MudSwitchM3 @bind-Value="_nowrap" Label="Nowrap" Color="Color.Primary" />
61-
<MudSelectExtended Class="mt-4" ItemCollection="@(Enum.GetValues<Variant>())" @bind-Value="_chipVariant" Label="Chip Variant" Variant="Variant.Outlined" Margin="Margin.Dense" />
62-
<MudSelectExtended Class="mt-4" ItemCollection="@(Enum.GetValues<Size>())" @bind-Value="_chipSize" Label="Chip Size" Variant="Variant.Outlined" Margin="Margin.Dense" />
63-
</MudItem>
64-
</MudGrid>
55+
<MudText Typo="Typo.body2" Class="pl-4">@(string.Join(", ", _stringValues.Select(x => $"\"{x}\"")))</MudText>
56+
<MudText Typo="Typo.subtitle2">}</MudText>
57+
</MudItem>
58+
</MudGrid>
59+
<MudSwitchM3 @bind-Value="_chipCloseable" Label="Chip Closeable" Color="Color.Secondary" />
60+
<MudSwitchM3 @bind-Value="_nowrap" Label="Nowrap" Color="Color.Secondary" />
61+
<MudSelectExtended Class="mt-4" ItemCollection="@(Enum.GetValues<Variant>())" @bind-Value="_chipVariant" Label="Chip Variant" Variant="Variant.Outlined" Margin="Margin.Dense" />
62+
<MudSelectExtended Class="mt-4" ItemCollection="@(Enum.GetValues<Size>())" @bind-Value="_chipSize" Label="Chip Size" Variant="Variant.Outlined" Margin="Margin.Dense" />
63+
</MudItem>
64+
</MudGrid>
6565

66-
@code {
67-
MudRadioGroup<int>? _radioGroup;
68-
ValuePresenter _valuePresenter = ValuePresenter.Text;
69-
string stringValue { get; set; } = "Nothing selected";
70-
IEnumerable<string> stringValues { get; set; } = new HashSet<string>() { "Alaska", "California" };
71-
int? intValue;
72-
IEnumerable<int?> intValues { get; set; } = new HashSet<int?>() { 2, 3 };
73-
bool _chipCloseable = false;
74-
Variant _chipVariant = Variant.Filled;
75-
Size _chipSize = Size.Small;
76-
bool _nowrap;
66+
@code {
67+
private MudRadioGroup<int>? _radioGroup;
68+
private ValuePresenter _valuePresenter = ValuePresenter.Text;
69+
private string _stringValue = "Nothing selected";
70+
private IEnumerable<string> _stringValues = new HashSet<string>() { "Alaska", "California" };
71+
private int? intValue;
72+
private IEnumerable<int?> intValues { get; set; } = new HashSet<int?>() { 2, 3 };
73+
private bool _chipCloseable = false;
74+
private Variant _chipVariant = Variant.Filled;
75+
private Size _chipSize = Size.Small;
76+
private bool _nowrap;
7777

7878
public class Complex
7979
{

docs/CodeBeam.MudBlazor.Extensions.Docs/Pages/Components/SelectExtended/Examples/SelectExtendedExample6.razor

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,54 @@
1414

1515
<MudItem xs="12" sm="4">
1616
<MudStack>
17-
<MudSwitchM3 @bind-Value="_multiselection" Color="Color.Primary" Label="MultiSelection" />
18-
<MudSwitchM3 @bind-Value="_searchBoxAutoFocus" Color="Color.Primary" Label="AutoFocus (SearchBox)" />
19-
<MudSwitchM3 @bind-Value="_searchBoxClearable" Color="Color.Primary" Label="Clearable (SearchBox)" />
17+
<MudSwitchM3 @bind-Value="_multiselection" Color="Color.Secondary" Label="MultiSelection" />
18+
<MudSwitchM3 @bind-Value="_searchBoxAutoFocus" Color="Color.Secondary" Label="AutoFocus (SearchBox)" />
19+
<MudSwitchM3 @bind-Value="_searchBoxClearable" Color="Color.Secondary" Label="Clearable (SearchBox)" />
2020
</MudStack>
2121
</MudItem>
2222
</MudGrid>
2323

24+
<MudGrid Class="mt-8">
25+
<MudItem xs="12">
26+
<MudText Typo="Typo.h6" Class="mb-4">Complex Type with ToStringFunc and Search</MudText>
27+
</MudItem>
28+
<MudItem xs="12" sm="8" Class="d-flex gap-4">
29+
<MudSelectExtended MultiSelection="true"
30+
ItemCollection="TestHouses"
31+
@bind-Value="_selectedHouse"
32+
SearchBox="true"
33+
T="TestHouse"
34+
Label="Houses (Complex Type)"
35+
ToStringFunc="@((TestHouse house) => $"{house.Number} - {house.Name}")"
36+
AnchorOrigin="Origin.BottomCenter"
37+
Variant="Variant.Outlined"
38+
HelperText="Search by number or name (e.g., '1 -' or 'Test3')"
39+
SearchBoxClearable="true" />
40+
</MudItem>
41+
42+
<MudItem xs="12" sm="4">
43+
<MudPaper Class="pa-4" Elevation="0" Outlined="true">
44+
<MudText Typo="Typo.subtitle2" Class="mb-2"><strong>Selected House:</strong></MudText>
45+
@if (_selectedHouse != null)
46+
{
47+
<MudText Typo="Typo.body2">Number: @_selectedHouse.Number</MudText>
48+
<MudText Typo="Typo.body2">Name: @_selectedHouse.Name</MudText>
49+
<MudText Typo="Typo.body2">Postcode: @_selectedHouse.Postcode</MudText>
50+
}
51+
else
52+
{
53+
<MudText Typo="Typo.body2" Color="Color.Secondary">None selected</MudText>
54+
}
55+
</MudPaper>
56+
</MudItem>
57+
</MudGrid>
58+
2459
@code {
2560
bool _multiselection;
2661
bool _searchBoxAutoFocus;
2762
bool _searchBoxClearable;
2863
private string? _selectedState;
64+
private TestHouse? _selectedHouse;
2965

3066
private string[] _states =
3167
{
@@ -45,6 +81,22 @@
4581
"Washington", "West Virginia", "Wisconsin", "Wyoming",
4682
};
4783

84+
private class TestHouse
85+
{
86+
public int Number { get; set; }
87+
public string? Name { get; set; }
88+
public string? Postcode { get; set; }
89+
}
90+
91+
private List<TestHouse> TestHouses = new()
92+
{
93+
new TestHouse { Number = 1, Name = "Test1", Postcode = "12345" },
94+
new TestHouse { Number = 2, Name = "Test2", Postcode = "54321" },
95+
new TestHouse { Number = 3, Name = "Test3", Postcode = "67890" },
96+
new TestHouse { Number = 4, Name = "Test4", Postcode = "09876" },
97+
new TestHouse { Number = 5, Name = "Test5", Postcode = "11223" },
98+
};
99+
48100
private bool SearchItems(string value, string searchString)
49101
{
50102
if (searchString == "")

docs/CodeBeam.MudBlazor.Extensions.Docs/Pages/Components/SelectExtended/Examples/SelectExtendedExample7.razor

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@
1212

1313
<MudItem xs="12" sm="4">
1414
<MudRadioGroup T="SelectAllPosition" @bind-Value="_selectAllPosition">
15-
<MudRadio Value="SelectAllPosition.BeforeSearchBox" Color="Color.Primary">Before Search (Default)</MudRadio>
16-
<MudRadio Value="SelectAllPosition.NextToSearchBox" Color="Color.Primary">Start of the searchbox in the same line</MudRadio>
17-
<MudRadio Value="SelectAllPosition.AfterSearchBox" Color="Color.Primary">After Search</MudRadio>
15+
<MudRadio Value="SelectAllPosition.BeforeSearchBox" Color="Color.Secondary">Before Search (Default)</MudRadio>
16+
<MudRadio Value="SelectAllPosition.NextToSearchBox" Color="Color.Secondary">Start of the searchbox in the same line</MudRadio>
17+
<MudRadio Value="SelectAllPosition.AfterSearchBox" Color="Color.Secondary">After Search</MudRadio>
1818
</MudRadioGroup>
1919
<MudGrid Class="mt-3 px-4">
2020
<MudItem xs="6">
@@ -33,11 +33,11 @@
3333
</MudItem>
3434
</MudGrid>
3535

36-
@code {
37-
ValuePresenter _valuePresenter = ValuePresenter.Text;
38-
SelectAllPosition _selectAllPosition;
39-
string value { get; set; } = "Nothing selected";
40-
IEnumerable<string> options { get; set; } = new HashSet<string>() { "Alaska", "California" };
36+
@code {
37+
private ValuePresenter _valuePresenter = ValuePresenter.Text;
38+
private SelectAllPosition _selectAllPosition;
39+
private string value { get; set; } = "Nothing selected";
40+
private IEnumerable<string> options { get; set; } = new HashSet<string>() { "Alaska", "California" };
4141

4242
private string[] states =
4343
{

src/CodeBeam.MudBlazor.Extensions/Components/ListExtended/MudListExtended.razor.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -956,7 +956,7 @@ protected internal async Task SearchBoxHandleKeyDownAsync(KeyboardEventArgs obj)
956956
await MudSelectExtended.FocusAsync();
957957
}
958958
break;
959-
case "Escape":
959+
case "Escape":
960960
if (MudSelectExtended != null && MultiSelection == false)
961961
{
962962
await MudSelectExtended.CloseMenu();
@@ -1783,10 +1783,14 @@ protected internal async ValueTask ScrollToMiddleAsync(MudListItemExtended<T?>?
17831783

17841784
if (SearchFunc != null)
17851785
{
1786-
return ItemCollection.Where(x => SearchFunc.Invoke(x, _searchString)).ToList();
1786+
return [.. ItemCollection.Where(x => SearchFunc.Invoke(x, _searchString))];
17871787
}
17881788

1789-
return ItemCollection.Where(x => Converter.Convert(x)?.Contains(_searchString, StringComparison.InvariantCultureIgnoreCase) == true).ToList();
1789+
var stringValue = new Func<T?, string?>(x =>
1790+
ToStringFunc != null ? ToStringFunc(x) : Converter.Convert(x)
1791+
);
1792+
1793+
return [.. ItemCollection.Where(x => stringValue(x)?.Contains(_searchString, StringComparison.InvariantCultureIgnoreCase) == true)];
17901794
}
17911795

17921796
/// <summary>
@@ -1834,7 +1838,7 @@ protected async Task OnDoubleClickHandler(MouseEventArgs? args, T? itemValue)
18341838
///
18351839
/// </summary>
18361840
protected internal MudListItemExtended<T?>? ActiveItem => _lastActivatedItem;
1837-
1841+
18381842
/// <summary>
18391843
///
18401844
/// </summary>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
@namespace MudExtensions.UnitTests.TestComponents
2+
3+
<MudListExtended @ref="List"
4+
SearchBox="true"
5+
ItemCollection="Houses"
6+
MultiSelection="true"
7+
ToStringFunc="@(house => $"{house!.Number} - {house.Name}")">
8+
</MudListExtended>
9+
10+
@code {
11+
public MudListExtended<TestHouse>? List { get; set; }
12+
13+
public List<TestHouse> Houses = new()
14+
{
15+
new TestHouse { Number = 1, Name = "Test1", Postcode = "12345" },
16+
new TestHouse { Number = 2, Name = "Test2", Postcode = "54321" },
17+
new TestHouse { Number = 3, Name = "Test3", Postcode = "67890" },
18+
};
19+
20+
public class TestHouse
21+
{
22+
public int Number { get; set; }
23+
public string? Name { get; set; }
24+
public string? Postcode { get; set; }
25+
}
26+
}

tests/CodeBeam.MudBlazor.Extensions.UnitTests/Components/ListExtendedTests.cs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,5 +363,50 @@ public async Task List_SearchChanged_WithNullItemCollection()
363363
var finalItems = comp.FindAll("div.mud-list-item-extended").Count;
364364
finalItems.Should().Be(9);
365365
}
366+
367+
368+
[Test]
369+
public void ListExtended_Search_With_ItemCollection_Should_Use_ToStringFunc()
370+
{
371+
// Arrange
372+
var comp = Context.Render<ListExtendedItemCollectionSearchTest>();
373+
var list = comp.FindComponent<MudListExtended<ListExtendedItemCollectionSearchTest.TestHouse>>().Instance;
374+
375+
list.SearchBox.Should().BeTrue();
376+
list.ItemCollection.Should().HaveCount(3);
377+
378+
// Act - search for "1 - Test1" using reflection to set _searchString
379+
var searchStringField = typeof(MudListExtended<ListExtendedItemCollectionSearchTest.TestHouse>)
380+
.GetField("_searchString", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
381+
searchStringField?.SetValue(list, "1 - Test1");
382+
383+
var result = list.GetSearchedItems();
384+
385+
// Assert - should find item by ToStringFunc format, not class name
386+
result.Should().NotBeNull();
387+
result.Should().HaveCount(1);
388+
result.First().Number.Should().Be(1);
389+
result.First().Name.Should().Be("Test1");
390+
}
391+
392+
[Test]
393+
public void ListExtended_Search_Should_Find_Items_By_Custom_Display_Format_Not_ClassName()
394+
{
395+
// Arrange
396+
var comp = Context.Render<ListExtendedItemCollectionSearchTest>();
397+
var list = comp.FindComponent<MudListExtended<ListExtendedItemCollectionSearchTest.TestHouse>>().Instance;
398+
399+
// Act - search for "Test2" (part of the formatted display, not the class name)
400+
var searchStringField = typeof(MudListExtended<ListExtendedItemCollectionSearchTest.TestHouse>)
401+
.GetField("_searchString", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
402+
searchStringField?.SetValue(list, "Test2");
403+
404+
var result = list.GetSearchedItems();
405+
406+
// Assert
407+
result.Should().HaveCount(1);
408+
result.First().Name.Should().Be("Test2");
409+
result.First().Number.Should().Be(2);
410+
}
366411
}
367412
}

0 commit comments

Comments
 (0)