Skip to content

Commit

Permalink
Merge pull request #617 from bholbrook/feature/HxMultiSelect-filterin…
Browse files Browse the repository at this point in the history
…g [HxMultiSelect] filtering and select all
  • Loading branch information
hakenr authored Oct 22, 2023
2 parents 9416a03 + 3ab65e2 commit 9c6f117
Show file tree
Hide file tree
Showing 22 changed files with 1,275 additions and 154 deletions.
81 changes: 59 additions & 22 deletions BlazorAppTest/Pages/HxMultiSelectTest.razor
Original file line number Diff line number Diff line change
@@ -1,38 +1,75 @@
@page "/HxMultiSelectTest"
@using System.Globalization

<h1>HxCheckboxList</h1>
<h1>HxMultiSelect</h1>

<HxSwitch Text="Enabled" @bind-Value="@enabled" />

<EditForm Model="@model">
<HxMultiSelect TItem="CultureInfo" TValue="string" Label="Cultures" EmptyText="-- choose here --" TextSelector="@(item => item.EnglishName)" ValueSelector="@(item => item.EnglishName)" Data="@data" @bind-Value="@model.CultureInfos" NullDataText="Loading languages..." Enabled="@enabled" InputSize="InputSize.Small" />

<HxMultiSelect TItem="CultureInfo" TValue="string" Label="Cultures" EmptyText="-- choose here --" TextSelector="@(item => item.EnglishName)" ValueSelector="@(item => item.EnglishName)" Data="@data" @bind-Value="@model.CultureInfos" NullDataText="Loading languages..." Enabled="@enabled" />
<HxMultiSelect TItem="CultureInfo" TValue="string" Label="Cultures" EmptyText="-- choose here --" TextSelector="@(item => item.EnglishName)" ValueSelector="@(item => item.EnglishName)" Data="@data" @bind-Value="@model.CultureInfos" NullDataText="Loading languages..." Enabled="@enabled" InputSize="InputSize.Large" />

<HxMultiSelect TItem="CultureInfo" TValue="string" Label="Cultures" EmptyText="-- choose here --" TextSelector="@(item => item.EnglishName)" ValueSelector="@(item => item.EnglishName)" Data="@data" @bind-Value="@model.CultureInfos" NullDataText="Loading languages..." Enabled="@enabled" InputSize="InputSize.Large" />

<!-- Multi-select with default filtering and select all -->
<HxMultiSelect TItem="CultureInfo"
TValue="string"
Label="Cultures with default filtering and select all enabled"
EmptyText="-- choose here --"
TextSelector="@(item => item.EnglishName)"
ValueSelector="@(item => item.EnglishName)"
Data="@data"
@bind-Value="@model.CultureInfos"
NullDataText="Loading languages..."
AllowFiltering="true"
AllowSelectAll="true"
Enabled="@enabled" />

<!-- Multi-select with custom filtering and select all -->
<HxMultiSelect TItem="CultureInfo"
TValue="string"
Label="Cultures with custom filtering and select all enabled"
EmptyText="-- choose here --"
TextSelector="@(item => item.EnglishName)"
ValueSelector="@(item => item.EnglishName)"
Data="@data"
@bind-Value="@model.CultureInfos"
NullDataText="Loading languages..."
AllowFiltering="true"
AllowSelectAll="true"
SelectAllText="Select all cultures"
ClearFilterOnHide="false"
Enabled="@enabled"
InputSize="InputSize.Small">
<FilterEmptyResultTemplate>
<span class="p-2">Couldn't find any matching cultures</span>
</FilterEmptyResultTemplate>
</HxMultiSelect>
</EditForm>

<p>Selected values: @String.Join(", ", model.CultureInfos ?? Enumerable.Empty<string>())</p>

@code
{
private bool enabled = true;
private Model model = new Model();
private List<CultureInfo> data;

protected override async Task OnParametersSetAsync()
{
await base.OnParametersSetAsync();
await Task.Delay(3000);

data = CultureInfo.GetCultures(CultureTypes.SpecificCultures)
.OrderBy(item => item.EnglishName)
.Take(100)
.OrderByDescending(i => i.ToString()) // sorting test
.ToList();
}

private class Model
{
public List<string> CultureInfos { get; set; }
}
private bool enabled = true;
private Model model = new Model();
private List<CultureInfo> data;

protected override async Task OnParametersSetAsync()
{
await base.OnParametersSetAsync();
await Task.Delay(3000);

data = CultureInfo.GetCultures(CultureTypes.SpecificCultures)
.OrderBy(item => item.EnglishName)
.Take(100)
.OrderByDescending(i => i.ToString()) // sorting test
.ToList();
}

private class Model
{
public List<string> CultureInfos { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using System.Threading;

namespace Havit.Blazor.Components.Web.Bootstrap.Documentation.DemoData;
namespace Havit.Blazor.Components.Web.Bootstrap.Documentation.DemoData;

public class DemoDataService : IDemoDataService
{
Expand Down Expand Up @@ -542,6 +540,15 @@ public IEnumerable<EmployeeDto> GetAllEmployees()
return employees.ToList();
}

public async Task<IEnumerable<EmployeeDto>> GetAllEmployeesAsync(CancellationToken cancellationToken = default)
{
logger.LogInformation("DemoDataService.GetAllEmployeesAsync() called.");

await Task.Delay(150, cancellationToken); // simulate server call

return employees.ToList();
}

public IQueryable<EmployeeDto> GetEmployeesAsQueryable()
{
logger.LogInformation("DemoDataService.GetEmployeesAsQueryable() called.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
public interface IDemoDataService
{
IEnumerable<EmployeeDto> GetAllEmployees();
Task<IEnumerable<EmployeeDto>> GetAllEmployeesAsync(CancellationToken cancellationToken = default);
IQueryable<EmployeeDto> GetEmployeesAsQueryable();

Task<IEnumerable<EmployeeDto>> GetEmployeesDataFragmentAsync(int startIndex, int? count, CancellationToken cancellationToken = default);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
@code {
private Task<GridDataProviderResult<CultureInfo>> GetGridData(GridDataProviderRequest<CultureInfo> request)
{
// you usualy pass the data-request to your API/DataLayer and it returns just the few requested items (+ total count)
// you usually pass the data-request to your API/DataLayer and it returns just the few requested items (+ total count)
var cultureInfos = CultureInfo.GetCultures(CultureTypes.AllCultures).ToList();
cultureInfos.Sort(request.Sorting.ToGenericPropertyComparer()); // Just a demo. NEVER use in production code!
Expand Down
Original file line number Diff line number Diff line change
@@ -1,31 +1,23 @@
<HxMultiSelect Label="Band members"
TItem="Person"
TValue="string"
Data="@people"
@bind-Value="@selectedPersonsInitials"
@inject IDemoDataService DemoDataService

<HxMultiSelect Label="Employees"
TItem="EmployeeDto"
TValue="int"
Data="@employees"
@bind-Value="selectedEmployeeIds"
TextSelector="@(p => p.Name)"
ValueSelector="@(p => p.Initials)"
EmptyText="-select values-"
NullDataText="Loading band members..." />
ValueSelector="@(p => p.Id)"
NullDataText="Loading employees..."
EmptyText="-select values-" />

<p class="mt-3">Selected initials: @String.Join(' ', selectedPersonsInitials)</p>
<p class="mt-3">Selected employees (IDs): @String.Join(", ", selectedEmployeeIds.Select(e => e.ToString()))</p>

@code {
private List<Person> people;
private List<string> selectedPersonsInitials { get; set; } = new();

protected override async Task OnInitializedAsync()
{
await Task.Delay(1000); // simulates slow server API call
people = new List<Person>
{
new Person("Starr Ringo", "RS"),
new Person("Lennon John", "JL"),
new Person("McCartney Paul", "PMC"),
new Person("Harrison George", "GH")
};
}
private IEnumerable<EmployeeDto> employees;
private List<int> selectedEmployeeIds = new();

private record Person(string Name, string Initials);
protected override async Task OnInitializedAsync()
{
employees = await DemoDataService.GetAllEmployeesAsync();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
@inject IDemoDataService DemoDataService

<HxMultiSelect Label="Employees"
TItem="EmployeeDto"
TValue="int"
Data="@employees"
@bind-Value="selectedEmployeeIds"
TextSelector="@(p => p.Name)"
ValueSelector="@(p => p.Id)"
NullDataText="Loading employees..."
AllowFiltering="true"
ClearFilterOnHide="false"
FilterPredicate="IsRecordIncluded">
<FilterEmptyResultTemplate>
<HxIcon Icon="BootstrapIcon.EmojiFrown" /> No employees found
</FilterEmptyResultTemplate>
</HxMultiSelect>

<p class="mt-3">Selected employees (IDs): @String.Join(", ", selectedEmployeeIds.Select(e => e.ToString()))</p>

@code {
private IEnumerable<EmployeeDto> employees;
private List<int> selectedEmployeeIds = new();

protected override async Task OnInitializedAsync()
{
employees = await DemoDataService.GetAllEmployeesAsync();
}

private bool IsRecordIncluded(EmployeeDto employee, string filterQuery)
{
return employee.Name.Contains(filterQuery, StringComparison.OrdinalIgnoreCase)
|| employee.Email.Contains(filterQuery, StringComparison.OrdinalIgnoreCase);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
@inject IDemoDataService DemoDataService

<HxMultiSelect Label="Employees"
TItem="EmployeeDto"
TValue="int"
Data="@employees"
@bind-Value="selectedEmployeeIds"
TextSelector="@(p => p.Name)"
ValueSelector="@(p => p.Id)"
NullDataText="Loading employees..."
AllowFiltering="true"
FilterEmptyResultText="No employees found"/>

<p class="mt-3">Selected employees (IDs): @String.Join(", ", selectedEmployeeIds.Select(e => e.ToString()))</p>

@code {
private IEnumerable<EmployeeDto> employees;
private List<int> selectedEmployeeIds = new();

protected override async Task OnInitializedAsync()
{
employees = await DemoDataService.GetAllEmployeesAsync();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
@inject IDemoDataService DemoDataService

<HxMultiSelect Label="Employees"
TItem="EmployeeDto"
TValue="int"
Data="@employees"
@bind-Value="selectedEmployeeIds"
TextSelector="@(p => p.Name)"
ValueSelector="@(p => p.Id)"
NullDataText="Loading employees..."
AllowSelectAll="true"
SelectAllText="Select all"/>

<p class="mt-3">Selected employees (IDs): @String.Join(", ", selectedEmployeeIds.Select(e => e.ToString()))</p>

@code {
private IEnumerable<EmployeeDto> employees;
private List<int> selectedEmployeeIds = new();

protected override async Task OnInitializedAsync()
{
employees = await DemoDataService.GetAllEmployeesAsync();
}
}
Original file line number Diff line number Diff line change
@@ -1,21 +1,46 @@
@attribute [Route("/components/" + nameof(HxMultiSelect<object, object>))]

<ComponentApiDoc Type="typeof(HxMultiSelect<TValue, TItem>)">
<MainContent>
<MainContent>

<DocHeading Title="Basic usage" />
<Demo Type="typeof(HxMultiSelect_Demo_BasicUsage)" Tabs="false" />
<DocHeading Title="Basic usage" />
<Demo Type="typeof(HxMultiSelect_Demo_BasicUsage)" />

</MainContent>
<CssVariables>
<DocHeading Title="Filtering" />
<p>
You can enable filtering by setting <code>AllowFiltering="true"</code>.<br />
You can use the <code>FilterEmptyResultTemplate</code> to customize the message displayed when no items are found.
</p>
<Demo Type="typeof(HxMultiSelect_Demo_Filtering)" />

<ComponentApiDocCssVariable Name="--hx-multi-select-dropdown-menu-height" Default="300px">
Height of the dropdown menu.
</ComponentApiDocCssVariable>
<DocHeading Title="Custom filtering" Level="3" />
<p>
You can customize filtering by setting <code>FilterPredicate</code> to a delegate that returns <code>bool</code> and accepts two parameters: <code>TItem</code> and <code>string</code> (filter).<br />
You can use the <code>FilterEmptyResultTemplate</code> to customize the content displayed when no items are found.
</p>
<Demo Type="typeof(HxMultiSelect_Demo_CustomFiltering)" />

<ComponentApiDocCssVariable Name="--hx-multi-select-background-color" Default="var(--bs-body-bg)">
Height of the dropdown menu.
</ComponentApiDocCssVariable>
<DocHeading Title="Select all" />
<p>
You can enable the "-select all-" option by setting <code>AllowSelectAll="true"</code>.<br />
You can customize the text of the "-select all-" option by setting <code>SelectAllText</code>.
</p>
<Demo Type="typeof(HxMultiSelect_Demo_SelectAll)" />

</CssVariables>
</MainContent>
<CssVariables>

<ComponentApiDocCssVariable Name="--hx-multi-select-dropdown-menu-height" Default="300px">
Height of the dropdown menu.
</ComponentApiDocCssVariable>

<ComponentApiDocCssVariable Name="--hx-multi-select-background-color" Default="var(--bs-body-bg)">
Background color of the dropdown menu.
</ComponentApiDocCssVariable>

<ComponentApiDocCssVariable Name="--hx-multi-select-filter-input-icon-opacity" Default=".25">
Opacity of the filter input icon.
</ComponentApiDocCssVariable>

</CssVariables>
</ComponentApiDoc>
Loading

0 comments on commit 9c6f117

Please sign in to comment.