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

[HxMultiSelect] Select all synchronisation #639

Merged
merged 7 commits into from
Nov 1, 2023
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
Expand Up @@ -101,7 +101,7 @@
var item = filteredItems[i];
TValue value = SelectorHelpers.GetValue<TItem, TValue>(ValueSelector, item);

bool itemSelected = DoSelectedValuesContainValue(value);
bool itemSelected = currentSelectedValues.Contains(value);

<li>
<button type="button" class="dropdown-item" role="option" @onclick="async () => await HandleItemSelectionChangedAsync(!itemSelected, item)">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,6 @@ public partial class HxMultiSelectInternal<TValue, TItem> : IAsyncDisposable

[Parameter] public bool ClearFilterOnHide { get; set; }

[Parameter] public EventCallback<string> OnShown { get; set; }

[Parameter] public EventCallback<string> OnHidden { get; set; }

[Parameter] public RenderFragment FilterEmptyResultTemplate { get; set; }

[Parameter] public string FilterEmptyResultText { get; set; }
Expand Down Expand Up @@ -78,12 +74,20 @@ public partial class HxMultiSelectInternal<TValue, TItem> : IAsyncDisposable
private string filterText = string.Empty;
private bool selectAllChecked;
private bool disposed;
private List<TValue> currentSelectedValues;

public HxMultiSelectInternal()
{
dotnetObjectReference = DotNetObjectReference.Create(this);
}

protected override void OnParametersSet()
{
currentSelectedValues = this.SelectedValues ?? new();

SynchronizeSelectAllCheckbox();
}

/// <inheritdoc cref="ComponentBase.OnAfterRenderAsync(bool)" />
protected override async Task OnAfterRenderAsync(bool firstRender)
{
Expand Down Expand Up @@ -111,14 +115,19 @@ private async Task HandleItemSelectionChangedAsync(bool newChecked, TItem item)
if (newChecked)
{
args.ItemsSelected.Add(item);

currentSelectedValues = new List<TValue>(currentSelectedValues);
currentSelectedValues.Add(SelectorHelpers.GetValue<TItem, TValue>(ValueSelector, item));
}
else
{
args.ItemsDeselected.Add(item);

currentSelectedValues = new List<TValue>(currentSelectedValues);
currentSelectedValues.Remove(SelectorHelpers.GetValue<TItem, TValue>(ValueSelector, item));
}

// When a single item is clicked we always want to uncheck select all
selectAllChecked = false;
SynchronizeSelectAllCheckbox();

await OnItemsSelectionChanged.InvokeAsync(args);
}
Expand All @@ -131,45 +140,65 @@ private async Task HandleSelectAllClickedAsync()
// If all items are already selected then they should be deselected, otherwise only records that aren't selected should be
if (selectAllChecked)
{
selectAllChecked = false;

foreach (var item in filteredItems)
{
args.ItemsDeselected.Add(item);
}

currentSelectedValues = new List<TValue>();
}
else
{
selectAllChecked = true;

var newCurrentSelectedValues = new List<TValue>(currentSelectedValues);
foreach (var item in filteredItems)
{
var value = SelectorHelpers.GetValue<TItem, TValue>(ValueSelector, item);
var itemSelected = DoSelectedValuesContainValue(value);

// If the item is already selected we don't need to reselect it
if (!itemSelected)
if (!newCurrentSelectedValues.Contains(SelectorHelpers.GetValue<TItem, TValue>(ValueSelector, item)))
{
args.ItemsSelected.Add(item);

newCurrentSelectedValues.Add(SelectorHelpers.GetValue<TItem, TValue>(ValueSelector, item));
}
}
currentSelectedValues = newCurrentSelectedValues;
}

selectAllChecked = !selectAllChecked;

await OnItemsSelectionChanged.InvokeAsync(args);
}

private void HandleClearIconClick()
{
filterText = string.Empty;
}

private bool DoSelectedValuesContainValue(TValue value)
{
return SelectedValues?.Contains(value) ?? false;
SynchronizeSelectAllCheckbox();
}

private void HandleFilterInputChanged(ChangeEventArgs e)
{
filterText = e.Value?.ToString() ?? string.Empty;
selectAllChecked = false;

SynchronizeSelectAllCheckbox();
}

private void SynchronizeSelectAllCheckbox()
{
if (AllowSelectAll)
{
if (currentSelectedValues.Any())
{
// If every item in the filtered list is contained in the selected values then select all should be checked
var filteredItems = GetFilteredItems();
selectAllChecked = filteredItems.All(item => currentSelectedValues.Contains(SelectorHelpers.GetValue<TItem, TValue>(ValueSelector, item)));
}
else
{
selectAllChecked = false;
}
}
}

private List<TItem> GetFilteredItems()
Expand All @@ -190,22 +219,12 @@ bool DefaultFilterPredicate(TItem item, string filter)

private string GetSelectAllText()
{
if (SelectAllText is not null)
{
return SelectAllText;
}

return StringLocalizer["SelectAllDefaultText"];
return SelectAllText ?? StringLocalizer["SelectAllDefaultText"];
}

private string GetFilterEmptyResultText()
{
if (FilterEmptyResultText is not null)
{
return FilterEmptyResultText;
}

return StringLocalizer["FilterEmptyResultDefaultText"];
return FilterEmptyResultText ?? StringLocalizer["FilterEmptyResultDefaultText"];
}

public async ValueTask FocusAsync()
Expand Down