Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
Original file line number Diff line number Diff line change
Expand Up @@ -6405,6 +6405,12 @@
Gets or sets the function used to determine if an option is initially selected.
</summary>
</member>
<member name="P:Microsoft.FluentUI.AspNetCore.Components.ListComponentBase`1.OptionComparer">
<summary>
Gets or sets the function used to determine if an option is already added to the internal list.
The first parameter is the item from the internal list to check and the second parameter is the actual selected item from the menu.
</summary>
</member>
<member name="P:Microsoft.FluentUI.AspNetCore.Components.ListComponentBase`1.Items">
<summary>
Gets or sets the content source of all items to display in this list.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,16 @@
</Description>
</DemoSection>

<DemoSection Title="Different object instances from search results" Component="@typeof(AutocompleteDifferentObjectInstances)">
<Description>
<p>
By default the <code>FluentAutocomplete</code> component compares the search results by instance with it's internal selected items. You can control that behaviour by providing the <code>OptionComparer</code> parameter.

It expects a function that compares the search result item with the selected item and returns <code>true</code> if they are equal. The first parameter of this function represents the item from the internal list of the component and the second parameter is the selected item from the search results.
</p>
</Description>
</DemoSection>

<h2 id="documentation">Documentation</h2>

<ApiDocumentation Component="typeof(FluentAutocomplete<>)" GenericLabel="TOption" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<FluentStack>
<div>
Without <code>OptionComparer</code>:
<FluentAutocomplete TOption="SimplePerson"
Label="Users"
Class="w-100"
Placeholder="Name"
OnOptionsSearch="@OnSearchUserAsync"
OptionText="@(item => $"{item.Firstname} {item.Lastname}" )"
@bind-SelectedOptions="Users1" />
</div>
<div>
With <code>OptionComparer</code>:
<FluentAutocomplete TOption="SimplePerson"
Label="Users"
Class="w-100"
Placeholder="Name"
OnOptionsSearch="@OnSearchUserAsync"
OptionComparer="IsOptionAdded"
OptionText="@(item => $"{item.Firstname} {item.Lastname}" )"
@bind-SelectedOptions="Users2" />
</div>
</FluentStack>
@code {

public IEnumerable<SimplePerson> Users1 { get; set; } = [new SimplePerson { Firstname = "Marvin", Lastname = "Klein", Age = 28 }];
public IEnumerable<SimplePerson> Users2 { get; set; } = [new SimplePerson { Firstname = "Marvin", Lastname = "Klein", Age = 28 }];

private bool IsOptionAdded(SimplePerson listItem, SimplePerson selectedItem)
{
// Compare both objects with each other to determine if they are equal
return listItem.Firstname == selectedItem.Firstname && listItem.Lastname == selectedItem.Lastname;
}

private Task OnSearchUserAsync(OptionsSearchEventArgs<SimplePerson> e)
{
// Simulate new instances for every search. Typically you would retrieve these from a database or an API.
var results = new List<SimplePerson>
{
new SimplePerson { Firstname = "Alice", Lastname = "Wonder", Age = 31 },
new SimplePerson { Firstname = "Marvin", Lastname = "Klein", Age = 28 },
new SimplePerson { Firstname = "Vincent", Lastname = "Baaji", Age = 38 },
};

e.Items = results;

return Task.CompletedTask;
}
}
11 changes: 11 additions & 0 deletions src/Core/Components/List/ListComponentBase.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,12 @@ protected string? InternalValue
/// </summary>
[Parameter]
public virtual Func<TOption, bool>? OptionSelected { get; set; }
/// <summary>
/// Gets or sets the function used to determine if an option is already added to the internal list.
/// The first parameter is the item from the internal list to check and the second parameter is the actual selected item from the menu.
/// </summary>
[Parameter]
public virtual Func<TOption, TOption, bool>? OptionComparer { get; set; }

/// <summary>
/// Gets or sets the content source of all items to display in this list.
Expand Down Expand Up @@ -538,6 +544,11 @@ protected virtual async Task OnSelectedItemChangedHandlerAsync(TOption? item)
RemoveSelectedItem(item);
await RaiseChangedEventsAsync();
}
else if (OptionComparer is not null && _selectedOptions.FirstOrDefault(x => OptionComparer(x, item)) is TOption addedItem)
{
RemoveSelectedItem(addedItem);
await RaiseChangedEventsAsync();
}
else
{
AddSelectedItem(item);
Expand Down
44 changes: 44 additions & 0 deletions tests/Core/List/FluentAutocompleteTests.razor
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,32 @@
cut.Verify();
}

[Fact]
public void FluentAutocomplete_SelectValueFromDifferentObjectInstances()
{
IEnumerable<Customer> SelectedItems = [new Customer(1, "Marvin Klein")];

// Arrange
var cut = Render<FluentAutocomplete<Customer>>(
@<FluentAutocomplete TOption="Customer"
SelectValueOnTab="true"
OptionComparer="IsItemAdded"
@bind-SelectedOptions="@SelectedItems"
OnOptionsSearch="@OnSearchNewInstance" />
);

// Act: click to open -> KeyDow + Enter to select
var input = cut.Find("fluent-text-field");
input.Click();

// Click on the second FluentOption
var marvin = SelectedItems.First(i => i.Id == 1);
cut.Find($"fluent-option[value='{marvin}']").Click();

// Assert (no item selected)
Assert.Empty(SelectedItems);
}

// Send a key code
private async Task PressKeyAsync(IRenderedComponent<FluentAutocomplete<Customer>> cut, KeyCode key, bool popoverOpened = false)
{
Expand All @@ -511,4 +537,22 @@
.OrderBy(i => i.Name);
return Task.CompletedTask;
}

private Task OnSearchNewInstance(OptionsSearchEventArgs<Customer> e)
{
var results = new List<Customer>
{
new Customer(1, "Marvin Klein"),
new Customer(2, "Alice Wonder"),
new Customer(3, "Vincent Baaji")
};

e.Items = results;
return Task.CompletedTask;
}

private bool IsItemAdded(Customer listItem, Customer selectedItem)
{
return listItem.Id == selectedItem.Id;
}
}