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

Grid: Select all or Unselect all items programmatically #988

Merged
merged 2 commits into from
Dec 25, 2024
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<div class="mb-3">
<Button Type="ButtonType.Button" Color="ButtonColor.Primary" Size="ButtonSize.Small" @onclick="SelectAllEmployees">Select All</Button>
<Button Type="ButtonType.Button" Color="ButtonColor.Primary" Size="ButtonSize.Small" @onclick="UnselectAllEmployees">Unselect All</Button>
</div>

<Grid @ref="gridRef" TItem="Employee1"
Class="table table-hover table-bordered"
DataProvider="EmployeesDataProvider"
AllowFiltering="true"
AllowSelection="true"
SelectionMode="GridSelectionMode.Multiple"
SelectedItemsChanged="OnSelectedItemsChanged"
Responsive="true">

<GridColumns>
<GridColumn TItem="Employee1" HeaderText="Id" PropertyName="Id">
@context.Id
</GridColumn>
<GridColumn TItem="Employee1" HeaderText="Employee Name" PropertyName="Name">
@context.Name
</GridColumn>
<GridColumn TItem="Employee1" HeaderText="Designation" PropertyName="Designation">
@context.Designation
</GridColumn>
<GridColumn TItem="Employee1" HeaderText="DOJ" PropertyName="DOJ">
@context.DOJ
</GridColumn>
<GridColumn TItem="Employee1" HeaderText="Active" PropertyName="IsActive">
@context.IsActive
</GridColumn>
</GridColumns>

</Grid>

<div class="mt-3">
Selected Items Count: @selectedEmployees.Count
</div>

<div class="mt-2">
Selected Employees:
<ul>
@foreach (var emp in selectedEmployees)
{
<li>@emp.Name</li>
}
</ul>
</div>

@code {
private Grid<Employee1> gridRef;
private IEnumerable<Employee1> employees = default!;

private HashSet<Employee1> selectedEmployees = new();

private async Task<GridDataProviderResult<Employee1>> EmployeesDataProvider(GridDataProviderRequest<Employee1> request)
{
Console.WriteLine("EmployeesDataProvider called...");

if (employees is null) // pull employees only one time for client-side filtering, sorting, and paging
employees = GetEmployees(); // call a service or an API to pull the employees

return await Task.FromResult(request.ApplyTo(employees));
}

private IEnumerable<Employee1> GetEmployees()
{
return new List<Employee1>
{
new Employee1 { Id = 107, Name = "Alice", Designation = "AI Engineer", DOJ = new DateOnly(1998, 11, 17), IsActive = true },
new Employee1 { Id = 103, Name = "Bob", Designation = "Senior DevOps Engineer", DOJ = new DateOnly(1985, 1, 5), IsActive = true },
new Employee1 { Id = 106, Name = "John", Designation = "Data Engineer", DOJ = new DateOnly(1995, 4, 17), IsActive = true },
new Employee1 { Id = 104, Name = "Pop", Designation = "Associate Architect", DOJ = new DateOnly(1985, 6, 8), IsActive = false },
new Employee1 { Id = 105, Name = "Ronald", Designation = "Senior Data Engineer", DOJ = new DateOnly(1991, 8, 23), IsActive = true },
new Employee1 { Id = 102, Name = "Line", Designation = "Architect", DOJ = new DateOnly(1977, 1, 12), IsActive = true },
new Employee1 { Id = 101, Name = "Daniel", Designation = "Architect", DOJ = new DateOnly(1977, 1, 12), IsActive = true },
new Employee1 { Id = 108, Name = "Zayne", Designation = "Data Analyst", DOJ = new DateOnly(1991, 1, 1), IsActive = true },
new Employee1 { Id = 109, Name = "Isha", Designation = "App Maker", DOJ = new DateOnly(1996, 7, 1), IsActive = true },
};
}

private Task OnSelectedItemsChanged(HashSet<Employee1> employees)
{
selectedEmployees = employees is not null && employees.Any() ? employees : new();
return Task.CompletedTask;
}

private Task SelectAllEmployees() => gridRef.SelectAllItemsAsync();

private Task UnselectAllEmployees() => gridRef.UnSelectAllItemsAsync();
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,21 @@
<Demo Type="typeof(Grid_Demo_01_Selection)" Tabs="true" />
</Section>

<Section Size="HeadingSize.H2" Name="Multiple Selection" PageUrl="@pageUrl" Link="multiple-selection">
<Section Size="HeadingSize.H2" Name="Multiple selection" PageUrl="@pageUrl" Link="multiple-selection">
<div class="mb-3">
To select multiple rows, set <code>SelectionMode="GridSelectionMode.Multiple"</code>.
</div>
<Demo Type="typeof(Grid_Demo_02_Multiple_Selection)" Tabs="true" />
<Demo Type="typeof(Grid_Demo_02_A_Multiple_Selection)" Tabs="true" />
<Callout Color="CalloutColor.Danger" Heading="Note">
<p>Selected items are removed from the selection if they are not rendered after paging, sorting, filtering, etc.</p>
</Callout>
</Section>

<Section Size="HeadingSize.H2" Name="Select all or unselect all programmatically" PageUrl="@pageUrl" Link="select-all-or-unselect-all-programmatically">
<div class="mb-3">
<b>Select</b> or <b>unselect</b> all the items programatically by calling the <code>SelectAllItemsAsync()</code> and <code>UnSelectAllItemsAsync()</code> methods. Also, set <code>SelectionMode="GridSelectionMode.Multiple"</code>.
</div>
<Demo Type="typeof(Grid_Demo_02_B_Multiple_Selection_Programmatically)" Tabs="true" />
<Callout Color="CalloutColor.Danger" Heading="Note">
<p>Selected items are removed from the selection if they are not rendered after paging, sorting, filtering, etc.</p>
</Callout>
Expand Down
88 changes: 57 additions & 31 deletions blazorbootstrap/Components/Grid/Grid.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ public partial class Grid<TItem> : BlazorBootstrapComponentBase

private List<GridColumn<TItem>> columns = new();

private GridDetailView<TItem>? detailView;

public GridEmptyDataTemplate<TItem>? emptyDataTemplate;

/// <summary>
/// Current grid state (filters, paging, sorting).
/// </summary>
Expand All @@ -21,18 +25,14 @@ public partial class Grid<TItem> : BlazorBootstrapComponentBase

private RenderFragment? headerSelectionTemplate;

private GridDetailView<TItem>? detailView;

public GridEmptyDataTemplate<TItem>? emptyDataTemplate;

public GridLoadingTemplate<TItem>? loadingTemplate;

private bool isFirstRenderComplete = false;

private List<TItem>? items = null;

private object? lastAssignedDataOrDataProvider;

public GridLoadingTemplate<TItem>? loadingTemplate;

private int pageSize;

private bool requestInProgress = false;
Expand Down Expand Up @@ -69,7 +69,8 @@ protected override void OnInitialized()

protected override Task OnParametersSetAsync()
{
if ((Data is null && DataProvider is null) || (Data is not null && DataProvider is not null)) throw new ArgumentException($"Grid requires either {nameof(Data)} or {nameof(DataProvider)}, but not both or neither.");
if ((Data is null && DataProvider is null) || (Data is not null && DataProvider is not null))
throw new ArgumentException($"Grid requires either {nameof(Data)} or {nameof(DataProvider)}, but not both or neither.");

if (AllowPaging && PageSize < 0)
throw new ArgumentException($"{nameof(PageSize)} must be greater than zero.");
Expand Down Expand Up @@ -123,6 +124,10 @@ protected override Task OnParametersSetAsync()
/// </summary>
public async ValueTask ResetPageNumber() => await ResetPageNumberAsync(true);

public Task SelectAllItemsAsync() => SelectAllItemsInternalAsync(true);

public Task UnSelectAllItemsAsync() => SelectAllItemsInternalAsync(false);

internal void AddColumn(GridColumn<TItem> column) => columns.Add(column);

internal async Task FilterChangedAsync()
Expand Down Expand Up @@ -158,13 +163,13 @@ internal async Task RefreshDataAsync(bool firstRender = false, CancellationToken
await LoadGridSettingsAsync();

var request = new GridDataProviderRequest<TItem>
{
PageNumber = AllowPaging ? gridCurrentState.PageIndex : 0,
PageSize = AllowPaging ? pageSize : 0,
Sorting = AllowSorting ? gridCurrentState.Sorting ?? GetDefaultSorting()! : null!,
Filters = AllowFiltering ? GetFilters()! : null!,
CancellationToken = cancellationToken
};
{
PageNumber = AllowPaging ? gridCurrentState.PageIndex : 0,
PageSize = AllowPaging ? pageSize : 0,
Sorting = AllowSorting ? gridCurrentState.Sorting ?? GetDefaultSorting()! : null!,
Filters = AllowFiltering ? GetFilters()! : null!,
CancellationToken = cancellationToken
};

GridDataProviderResult<TItem> result = default!;

Expand Down Expand Up @@ -205,6 +210,12 @@ internal async ValueTask ResetPageNumberAsync(bool refreshGrid = false)
await RefreshDataAsync(false);
}

internal void SetGridDetailView(GridDetailView<TItem> detailView) => this.detailView = detailView;

internal void SetGridEmptyDataTemplate(GridEmptyDataTemplate<TItem> emptyDataTemplate) => this.emptyDataTemplate = emptyDataTemplate;

internal void SetGridLoadingTemplate(GridLoadingTemplate<TItem> loadingTemplate) => this.loadingTemplate = loadingTemplate;

internal async Task SortingChangedAsync(GridColumn<TItem> column)
{
if (columns == null || !columns.Any())
Expand Down Expand Up @@ -380,13 +391,8 @@ private async Task LoadGridSettingsAsync()

private async Task OnHeaderCheckboxChanged(ChangeEventArgs args)
{
allItemsSelected = bool.TryParse(args?.Value?.ToString(), out var checkboxState) && checkboxState;
selectedItems = allItemsSelected ? new HashSet<TItem>(items!) : new HashSet<TItem>();
SelectedItemsCount = selectedItems.Count;
await CheckOrUnCheckAll();

if (SelectedItemsChanged.HasDelegate)
await SelectedItemsChanged.InvokeAsync(selectedItems);
var headerCheckboxState = bool.TryParse(args?.Value?.ToString(), out var checkboxState) && checkboxState;
await SelectAllItemsInternalAsync(headerCheckboxState);
}

private async Task OnPageChangedAsync(int newPageNumber)
Expand Down Expand Up @@ -495,13 +501,27 @@ private Task SaveGridSettingsAsync()
return GridSettingsChanged.InvokeAsync(settings);
}

private async Task SetCheckboxStateAsync(string id, CheckboxState checkboxState) => await JSRuntime.InvokeVoidAsync("window.blazorBootstrap.grid.setSelectAllCheckboxState", id, (int)checkboxState);
private async Task SelectAllItemsInternalAsync(bool selectAll)
{
if(SelectionMode != GridSelectionMode.Multiple)
return;

internal void SetGridDetailView(GridDetailView<TItem> detailView) => this.detailView = detailView;
allItemsSelected = selectAll;
selectedItems = allItemsSelected ? new HashSet<TItem>(items!) : new HashSet<TItem>();
SelectedItemsCount = allItemsSelected ? selectedItems.Count : 0;

internal void SetGridEmptyDataTemplate(GridEmptyDataTemplate<TItem> emptyDataTemplate) => this.emptyDataTemplate = emptyDataTemplate;
if (allItemsSelected)
await SetCheckboxStateAsync(headerCheckboxId, CheckboxState.Checked);
else
await SetCheckboxStateAsync(headerCheckboxId, CheckboxState.Unchecked);

internal void SetGridLoadingTemplate(GridLoadingTemplate<TItem> loadingTemplate) => this.loadingTemplate = loadingTemplate;
await CheckOrUnCheckAll();

if (SelectedItemsChanged.HasDelegate)
await SelectedItemsChanged.InvokeAsync(selectedItems);
}

private async Task SetCheckboxStateAsync(string id, CheckboxState checkboxState) => await JSRuntime.InvokeVoidAsync("window.blazorBootstrap.grid.setSelectAllCheckboxState", id, (int)checkboxState);

/// <summary>
/// Set filters.
Expand Down Expand Up @@ -534,9 +554,11 @@ private void SetFilters(IEnumerable<FilterItem> filterItems)
#region Properties, Indexers

protected override string? ClassNames =>
BuildClassNames(Class,
BuildClassNames(
Class,
("bb-table", true),
(BootstrapClass.TableSticky, FixedHeader));
(BootstrapClass.TableSticky, FixedHeader)
);

/// <summary>
/// Gets or sets the grid delete.
Expand Down Expand Up @@ -705,8 +727,10 @@ private void SetFilters(IEnumerable<FilterItem> filterItems)
public string? GridContainerClass { get; set; }

private string? GridContainerClassNames =>
BuildClassNames(GridContainerClass,
(BootstrapClass.TableResponsive, Responsive));
BuildClassNames(
GridContainerClass,
(BootstrapClass.TableResponsive, Responsive)
);

/// <summary>
/// Gets or sets the grid container css style.
Expand All @@ -715,8 +739,10 @@ private void SetFilters(IEnumerable<FilterItem> filterItems)
public string? GridContainerStyle { get; set; }

private string? GridContainerStyleNames =>
BuildStyleNames(GridContainerStyle,
($"height:{Height.ToString(CultureInfo.InvariantCulture)}{Unit.ToCssString()}", FixedHeader));
BuildStyleNames(
GridContainerStyle,
($"height:{Height.ToString(CultureInfo.InvariantCulture)}{Unit.ToCssString()}", FixedHeader)
);

/// <summary>
/// This event is fired when the grid state is changed.
Expand Down
Loading