Hi @mckaragoz, I'm hoping you're able to help. I'm in the middle of creating, or at least trying to, a custom component as a proof of concept to mirror something that Power BI does with dropdowns.
The component below leverages the MudComboBox and some other bits but attempts to customise it heavily by supporting custom filtering, auto-expanding/collapsing during searching. multiple nesting/groups, the ability to select all within a group, and some other things. The primary issue I'm having right now is there is something weird going on with clicking individual items, it's just collapsing the parent instead of actually selecting the item? .. my hunch is it's something with event propagation, but I tried stopPropagation on the onclick but it doesn't seem to be working. Also another issue is the select all within a group is sometimes flaky, as in it sometimes works, sometimes it doesn't.
I've had to use MudComboBox due to the whole shadow thing with the other selects, but then use MudListItemExtended for the nesting to be able to auto-expand/collapse.
I can't save on the MudExtensions playground to provide a snippet but below is the code to paste in:
@using MudExtensions.Enums;
@using MudExtensions.Services;
<style>
.mud-popover {
max-height: 500px !important;
height: 500px !important;
}
div[id^='comboboxpopover_'] {
max-height: 500px !important;
height: 500px !important;
}
</style>
@* Remove MudContainer once not in Test page and is own component *@
<MudContainer>
<MudComboBox MultiSelection="true"
@key="_selectKey"
SelectedValues="_selectedPractices"
T="PracticeInformation"
ShowCheckbox="true"
Clearable="true"
Label="Practice *"
OnOpen="FocusOnTextInput"
AnchorOrigin="Origin.BottomCenter"
OnClearButtonClick="ClearButtonClicked"
Variant="Variant.Text">
<MudTextFieldExtended T="string"
Variant="Variant.Outlined"
Clearable="true"
@ref="myTextField"
Class="ml-5 mt-5 mr-5 mb-5"
Placeholder="Search..."
AutoFocus="true"
ValueChanged="@(text => FilterItems(text))"
Immediate="true">
<AdornmentEnd>
<MudIcon Icon="@Icons.Material.Filled.Search" Color="@Color.Primary" />
</AdornmentEnd>
</MudTextFieldExtended>
<MudCheckBox Class="ml-2" T=bool Label="Select All" Value="@_selectAll" ValueChanged="@(isChecked => SelectAll(isChecked))" Color="Color.Primary" />
@foreach (var region in groups)
{
@if (region.Value.Any())
{
<MudListItemExtended T="string" Text="@region.Key" Style="font-weight: bold !important" Expanded="@expandedRegions.Contains(region.Key)">
<NestedList>
@foreach (var area in region.Value)
{
@if (area.Value.Any())
{
<div style="margin-left: 20px;">
<MudListItemExtended T="string" Text="@area.Key" Style="font-weight: bold !important" Expanded="@expandedAreas.Contains(area.Key)">
<NestedList>
<MudCheckBox Class="ml-2" T=bool Label="@($"Select All In {area.Key}")" Value="@selectAllAreaFlags[region.Key][area.Key]" ValueChanged="@(isChecked => SelectAllArea(area, (bool)isChecked, region.Key, area.Key))" />
@foreach (var practice in area.Value)
{
<MudComboBoxItem Value="@(practice)" Text="@practice.PracticeName">@(practice.PracticeName)</MudComboBoxItem>
}
</NestedList>
</MudListItemExtended>
</div>
}
}
</NestedList>
</MudListItemExtended>
}
}
</MudComboBox>
</MudContainer>
@code {
/// <summary>
/// Current limitiations:
/// - Need to add the ability to pass back _selectedValues to the caller
/// - Select All in an Area is flaky
/// - Selecting an individual item doesn't work
/// </summary>
// For Testing
private IEnumerable<PracticeInformation> _userPractices;
// Rendering
private int _selectKey = 0;
MudTextFieldExtended<string> myTextField;
// User Input
private List<PracticeInformation> _selectedPractices = new List<PracticeInformation>();
private bool _selectAll = false;
// Component Data
Dictionary<string, Dictionary<string, List<PracticeInformation>>> groups;
Dictionary<string, Dictionary<string, bool>> selectAllAreaFlags;
private HashSet<string> expandedRegions = new HashSet<string>();
private HashSet<string> expandedAreas = new HashSet<string>();
protected override async Task OnInitializedAsync()
{
_userPractices = new List<PracticeInformation>
{
new PracticeInformation{PracticeName = "Practice A", AreaName = "Area 1", RegionName = "Region 1"},
new PracticeInformation{PracticeName = "Practice B", AreaName = "Area 1", RegionName = "Region 1"},
new PracticeInformation{PracticeName = "Practice C", AreaName = "Area 2", RegionName = "Region 1"},
new PracticeInformation{PracticeName = "Practice D", AreaName = "Area 3", RegionName = "Region 2"},
new PracticeInformation{PracticeName = "Practice E", AreaName = "Area 3", RegionName = "Region 2"},
new PracticeInformation{PracticeName = "Practice F", AreaName = "Area 4", RegionName = "Region 3"},
};
SetGroupsToDefaultValues(_userPractices.ToList());
}
protected async Task FocusOnTextInput()
{
await myTextField.InputReference.ElementReference.FocusAsync();
}
private void ClearButtonClicked()
{
_selectedPractices = new List<PracticeInformation>();
SetGroupsToDefaultValues(_userPractices.ToList());
_selectKey++;
StateHasChanged();
}
private void FilterItems(string searchText)
{
expandedRegions.Clear();
expandedAreas.Clear();
if (string.IsNullOrEmpty(searchText))
{
SetGroupsToDefaultValues(_userPractices.ToList());
}
else
{
var filteredGroups = new Dictionary<string, Dictionary<string, List<PracticeInformation>>>();
foreach (var region in groups)
{
if (region.Value.Any(area => area.Value.Any(practice => practice.PracticeName.Contains(searchText))))
{
expandedRegions.Add(region.Key);
foreach (var area in region.Value)
{
if (area.Value.Any(practice => practice.PracticeName.Contains(searchText)))
{
expandedAreas.Add(area.Key);
}
}
}
var filteredAreas = new Dictionary<string, List<PracticeInformation>>();
foreach (var area in region.Value)
{
filteredAreas[area.Key] = area.Value.Where(item => item.PracticeName.Contains(searchText, StringComparison.OrdinalIgnoreCase)).ToList();
}
filteredGroups[region.Key] = filteredAreas;
}
groups = filteredGroups;
}
StateHasChanged();
}
private void SelectAll(bool isChecked)
{
_selectedPractices.Clear();
_selectAll = isChecked;
if (isChecked)
{
foreach (var region in groups)
{
foreach (var area in region.Value)
{
foreach (var practice in area.Value)
{
if (!_selectedPractices.Contains(practice))
{
_selectedPractices.Add(practice);
}
}
}
}
}
_selectKey++;
StateHasChanged();
}
private void SelectAllArea(
KeyValuePair<string, List<PracticeInformation>> area,
bool isChecked,
string regionName,
string areaName)
{
bool selectAllArea = selectAllAreaFlags[regionName][areaName];
selectAllArea = isChecked;
if (isChecked)
{
foreach (var practice in area.Value)
{
if (!_selectedPractices.Contains(practice))
{
_selectedPractices.Add(practice);
}
}
}
else
{
_selectedPractices.RemoveAll(practice => area.Value.Contains(practice));
}
// Create a new list to force the MudComboBox to update
_selectedPractices = new List<PracticeInformation>(_selectedPractices);
selectAllAreaFlags[regionName][areaName] = selectAllArea;
_selectKey++;
StateHasChanged();
}
private void SetGroupsToDefaultValues(List<PracticeInformation> practices)
{
groups = new Dictionary<string, Dictionary<string, List<PracticeInformation>>>();
foreach (var practice in practices)
{
if (!groups.ContainsKey(practice.RegionName))
{
groups[practice.RegionName] = new Dictionary<string, List<PracticeInformation>>();
}
if (!groups[practice.RegionName].ContainsKey(practice.AreaName))
{
groups[practice.RegionName][practice.AreaName] = new List<PracticeInformation>();
}
groups[practice.RegionName][practice.AreaName].Add(practice);
}
var orderedDict = groups
.OrderBy(kv1 => kv1.Key)
.ToDictionary(
kv1 => kv1.Key,
kv1 => kv1.Value
.OrderBy(kv2 => kv2.Key)
.ToDictionary(
kv2 => kv2.Key,
kv2 => kv2.Value.OrderBy(str => str.PracticeName).ToList()
)
);
groups = orderedDict;
selectAllAreaFlags = new Dictionary<string, Dictionary<string, bool>>();
foreach (var practice in practices)
{
if (!selectAllAreaFlags.ContainsKey(practice.RegionName))
{
selectAllAreaFlags[practice.RegionName] = new Dictionary<string, bool>();
}
if (!selectAllAreaFlags[practice.RegionName].ContainsKey(practice.AreaName))
{
selectAllAreaFlags[practice.RegionName][practice.AreaName] = false;
}
}
StateHasChanged();
}
public class PracticeInformation
{
public string PracticeName { get; set; }
public string AreaName { get; set; }
public string RegionName { get; set; }
}
}
Hi @mckaragoz, I'm hoping you're able to help. I'm in the middle of creating, or at least trying to, a custom component as a proof of concept to mirror something that Power BI does with dropdowns.
The component below leverages the MudComboBox and some other bits but attempts to customise it heavily by supporting custom filtering, auto-expanding/collapsing during searching. multiple nesting/groups, the ability to select all within a group, and some other things. The primary issue I'm having right now is there is something weird going on with clicking individual items, it's just collapsing the parent instead of actually selecting the item? .. my hunch is it's something with event propagation, but I tried stopPropagation on the onclick but it doesn't seem to be working. Also another issue is the select all within a group is sometimes flaky, as in it sometimes works, sometimes it doesn't.
I've had to use MudComboBox due to the whole shadow thing with the other selects, but then use MudListItemExtended for the nesting to be able to auto-expand/collapse.
I can't save on the MudExtensions playground to provide a snippet but below is the code to paste in: