|
|
|
|
using System.Globalization;
|
|
|
|
|
using System.Windows.Input;
|
|
|
|
|
using Connected.Annotations;
|
|
|
|
|
using Connected.Utilities;
|
|
|
|
|
using Microsoft.AspNetCore.Components;
|
|
|
|
|
using Microsoft.AspNetCore.Components.Web;
|
|
|
|
|
|
|
|
|
|
namespace Connected.Components;
|
|
|
|
|
|
|
|
|
|
public partial class TreeViewItem<T> : UIComponent
|
|
|
|
|
{
|
|
|
|
|
private string _text;
|
|
|
|
|
private bool _disabled;
|
|
|
|
|
private bool _isChecked, _isSelected, _isServerLoaded;
|
|
|
|
|
private Converter<T> _converter = new DefaultConverter<T>();
|
|
|
|
|
private readonly List<TreeViewItem<T>> _childItems = new();
|
|
|
|
|
|
|
|
|
|
protected string Classname =>
|
|
|
|
|
new CssBuilder("mud-treeview-item")
|
|
|
|
|
.AddClass(Class)
|
|
|
|
|
.Build();
|
|
|
|
|
|
|
|
|
|
protected string ContentClassname =>
|
|
|
|
|
new CssBuilder("mud-treeview-item-content")
|
|
|
|
|
.AddClass("cursor-pointer", TreeRoot?.IsSelectable == true || TreeRoot?.ExpandOnClick == true && HasChild)
|
|
|
|
|
.AddClass($"mud-treeview-item-selected", _isSelected)
|
|
|
|
|
.Build();
|
|
|
|
|
|
|
|
|
|
public string TextClassname =>
|
|
|
|
|
new CssBuilder("mud-treeview-item-label")
|
|
|
|
|
.AddClass(TextClass)
|
|
|
|
|
.Build();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[CascadingParameter] TreeView<T> TreeRoot { get; set; }
|
|
|
|
|
|
|
|
|
|
[CascadingParameter] TreeViewItem<T> Parent { get; set; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Custom checked icon, leave null for default.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Parameter]
|
|
|
|
|
[Category(CategoryTypes.TreeView.Selecting)]
|
|
|
|
|
public string CheckedIcon { get; set; } = Icons.Material.Filled.CheckBox;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Custom unchecked icon, leave null for default.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Parameter]
|
|
|
|
|
[Category(CategoryTypes.TreeView.Selecting)]
|
|
|
|
|
public string UncheckedIcon { get; set; } = Icons.Material.Filled.CheckBoxOutlineBlank;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Value of the treeviewitem. Acts as the displayed text if no text is set.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Parameter]
|
|
|
|
|
[Category(CategoryTypes.TreeView.Data)]
|
|
|
|
|
public T Value { get; set; }
|
|
|
|
|
|
|
|
|
|
[Parameter]
|
|
|
|
|
[Category(CategoryTypes.TreeView.Behavior)]
|
|
|
|
|
public CultureInfo Culture { get; set; } = CultureInfo.CurrentCulture;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// The text to display
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Parameter]
|
|
|
|
|
[Category(CategoryTypes.TreeView.Behavior)]
|
|
|
|
|
public string Text
|
|
|
|
|
{
|
|
|
|
|
get => string.IsNullOrEmpty(_text) ? _converter.Set(Value) : _text;
|
|
|
|
|
set => _text = value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Tyopography for the text.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Parameter]
|
|
|
|
|
[Category(CategoryTypes.TreeView.Appearance)]
|
|
|
|
|
public Typo TextTypo { get; set; } = Typo.body1;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// User class names for the text, separated by space.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Parameter]
|
|
|
|
|
[Category(CategoryTypes.TreeView.Appearance)]
|
|
|
|
|
public string TextClass { get; set; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// The text at the end of the item.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Parameter]
|
|
|
|
|
[Category(CategoryTypes.TreeView.Behavior)]
|
|
|
|
|
public string EndText { get; set; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Tyopography for the endtext.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Parameter]
|
|
|
|
|
[Category(CategoryTypes.TreeView.Appearance)]
|
|
|
|
|
public Typo EndTextTypo { get; set; } = Typo.body1;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// User class names for the endtext, separated by space.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Parameter]
|
|
|
|
|
[Category(CategoryTypes.TreeView.Appearance)]
|
|
|
|
|
public string EndTextClass { get; set; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// If true, treeviewitem will be disabled.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Parameter]
|
|
|
|
|
[Category(CategoryTypes.TreeView.Behavior)]
|
|
|
|
|
public bool Disabled
|
|
|
|
|
{
|
|
|
|
|
get => _disabled || (TreeRoot?.Disabled ?? false);
|
|
|
|
|
set => _disabled = value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Child content of component used to create sub levels.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Parameter]
|
|
|
|
|
[Category(CategoryTypes.TreeView.Data)]
|
|
|
|
|
public RenderFragment ChildContent { get; set; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Content of the item, if used completly replaced the default rendering.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Parameter]
|
|
|
|
|
[Category(CategoryTypes.TreeView.Behavior)]
|
|
|
|
|
public RenderFragment Content { get; set; }
|
|
|
|
|
|
|
|
|
|
[Parameter]
|
|
|
|
|
[Category(CategoryTypes.TreeView.Data)]
|
|
|
|
|
public HashSet<T> Items { get; set; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Command executed when the user clicks on the CommitEdit Button.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Parameter]
|
|
|
|
|
[Category(CategoryTypes.TreeView.ClickAction)]
|
|
|
|
|
public ICommand Command { get; set; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Expand or collapse treeview item when it has children. Two-way bindable. Note: if you directly set this to
|
|
|
|
|
/// true or false (instead of using two-way binding) it will force the item's expansion state.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Parameter]
|
|
|
|
|
[Category(CategoryTypes.TreeView.Expanding)]
|
|
|
|
|
public bool Expanded { get; set; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Called whenever expanded changed.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Parameter] public EventCallback<bool> ExpandedChanged { get; set; }
|
|
|
|
|
|
|
|
|
|
[Parameter]
|
|
|
|
|
[Category(CategoryTypes.TreeView.Selecting)]
|
|
|
|
|
public bool Activated
|
|
|
|
|
{
|
|
|
|
|
get => _isSelected;
|
|
|
|
|
set
|
|
|
|
|
{
|
|
|
|
|
_ = TreeRoot?.UpdateSelected(this, value);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Parameter]
|
|
|
|
|
[Category(CategoryTypes.TreeView.Selecting)]
|
|
|
|
|
public bool Selected
|
|
|
|
|
{
|
|
|
|
|
get => _isChecked;
|
|
|
|
|
set
|
|
|
|
|
{
|
|
|
|
|
if (_isChecked == value)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
_isChecked = value;
|
|
|
|
|
TreeRoot?.UpdateSelectedItems();
|
|
|
|
|
SelectedChanged.InvokeAsync(_isChecked);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Icon placed before the text if set.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Parameter]
|
|
|
|
|
[Category(CategoryTypes.TreeView.Behavior)]
|
|
|
|
|
public string Icon { get; set; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// The color of the icon. It supports the theme colors.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Parameter]
|
|
|
|
|
[Category(CategoryTypes.TreeView.Appearance)]
|
|
|
|
|
public ThemeColor IconColor { get; set; } = ThemeColor.Default;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Icon placed after the text if set.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Parameter]
|
|
|
|
|
[Category(CategoryTypes.TreeView.Behavior)]
|
|
|
|
|
public string EndIcon { get; set; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// The color of the icon. It supports the theme colors.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Parameter]
|
|
|
|
|
[Category(CategoryTypes.TreeView.Appearance)]
|
|
|
|
|
public ThemeColor EndIconColor { get; set; } = ThemeColor.Default;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// The expand/collapse icon.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Parameter]
|
|
|
|
|
[Category(CategoryTypes.TreeView.Expanding)]
|
|
|
|
|
public string ExpandedIcon { get; set; } = Icons.Material.Filled.ChevronRight;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// The color of the expand/collapse button. It supports the theme colors.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Parameter]
|
|
|
|
|
[Category(CategoryTypes.TreeView.Expanding)]
|
|
|
|
|
public ThemeColor ExpandedIconColor { get; set; } = ThemeColor.Default;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// The loading icon.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Parameter]
|
|
|
|
|
[Category(CategoryTypes.TreeView.Appearance)]
|
|
|
|
|
public string LoadingIcon { get; set; } = Icons.Material.Filled.Loop;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// The color of the loading. It supports the theme colors.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Parameter]
|
|
|
|
|
[Category(CategoryTypes.TreeView.Appearance)]
|
|
|
|
|
public ThemeColor LoadingIconColor { get; set; } = ThemeColor.Default;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Called whenever the activated value changed.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Parameter] public EventCallback<bool> ActivatedChanged { get; set; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Called whenever the selected value changed.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Parameter] public EventCallback<bool> SelectedChanged { get; set; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Tree item click event.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Parameter] public EventCallback<MouseEventArgs> OnClick { get; set; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Tree item double click event.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Parameter] public EventCallback<MouseEventArgs> OnDoubleClick { get; set; }
|
|
|
|
|
|
|
|
|
|
public bool Loading { get; set; }
|
|
|
|
|
|
|
|
|
|
bool HasChild => ChildContent != null ||
|
|
|
|
|
(TreeRoot != null && Items != null && Items.Count != 0) ||
|
|
|
|
|
(TreeRoot?.ServerData != null && !_isServerLoaded && (Items == null || Items.Count == 0));
|
|
|
|
|
|
|
|
|
|
protected bool IsChecked
|
|
|
|
|
{
|
|
|
|
|
get => Selected;
|
|
|
|
|
set { _ = SelectItem(value, this); }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected internal bool ArrowExpanded
|
|
|
|
|
{
|
|
|
|
|
get => Expanded;
|
|
|
|
|
set
|
|
|
|
|
{
|
|
|
|
|
if (value == Expanded)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
Expanded = value;
|
|
|
|
|
ExpandedChanged.InvokeAsync(value);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected override void OnInitialized()
|
|
|
|
|
{
|
|
|
|
|
if (Parent != null)
|
|
|
|
|
{
|
|
|
|
|
Parent.AddChild(this);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
TreeRoot?.AddChild(this);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
|
|
|
|
{
|
|
|
|
|
if (firstRender && _isSelected)
|
|
|
|
|
{
|
|
|
|
|
await TreeRoot.UpdateSelected(this, _isSelected);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await base.OnAfterRenderAsync(firstRender);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected async Task OnItemClicked(MouseEventArgs ev)
|
|
|
|
|
{
|
|
|
|
|
if (TreeRoot?.IsSelectable ?? false)
|
|
|
|
|
{
|
|
|
|
|
await TreeRoot.UpdateSelected(this, !_isSelected);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (HasChild && (TreeRoot?.ExpandOnClick ?? false))
|
|
|
|
|
{
|
|
|
|
|
Expanded = !Expanded;
|
|
|
|
|
TryInvokeServerLoadFunc();
|
|
|
|
|
await ExpandedChanged.InvokeAsync(Expanded);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await OnClick.InvokeAsync(ev);
|
|
|
|
|
if (Command?.CanExecute(Value) ?? false)
|
|
|
|
|
{
|
|
|
|
|
Command.Execute(Value);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected async Task OnItemDoubleClicked(MouseEventArgs ev)
|
|
|
|
|
{
|
|
|
|
|
if (TreeRoot?.IsSelectable ?? false)
|
|
|
|
|
{
|
|
|
|
|
await TreeRoot.UpdateSelected(this, !_isSelected);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (HasChild && (TreeRoot?.ExpandOnDoubleClick ?? false))
|
|
|
|
|
{
|
|
|
|
|
Expanded = !Expanded;
|
|
|
|
|
TryInvokeServerLoadFunc();
|
|
|
|
|
await ExpandedChanged.InvokeAsync(Expanded);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await OnDoubleClick.InvokeAsync(ev);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected internal Task OnItemExpanded(bool expanded)
|
|
|
|
|
{
|
|
|
|
|
if (Expanded == expanded)
|
|
|
|
|
return Task.CompletedTask;
|
|
|
|
|
|
|
|
|
|
Expanded = expanded;
|
|
|
|
|
TryInvokeServerLoadFunc();
|
|
|
|
|
return ExpandedChanged.InvokeAsync(expanded);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal Task Select(bool value)
|
|
|
|
|
{
|
|
|
|
|
if (_isSelected == value)
|
|
|
|
|
return Task.CompletedTask;
|
|
|
|
|
|
|
|
|
|
_isSelected = value;
|
|
|
|
|
|
|
|
|
|
StateHasChanged();
|
|
|
|
|
|
|
|
|
|
return ActivatedChanged.InvokeAsync(_isSelected);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal async Task SelectItem(bool value, TreeViewItem<T> source = null)
|
|
|
|
|
{
|
|
|
|
|
if (value == _isChecked)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
_isChecked = value;
|
|
|
|
|
_childItems.ForEach(async c => await c.SelectItem(value, source));
|
|
|
|
|
|
|
|
|
|
StateHasChanged();
|
|
|
|
|
|
|
|
|
|
await SelectedChanged.InvokeAsync(_isChecked);
|
|
|
|
|
|
|
|
|
|
if (source == this)
|
|
|
|
|
{
|
|
|
|
|
if (TreeRoot != null)
|
|
|
|
|
{
|
|
|
|
|
await TreeRoot.UpdateSelectedItems();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void AddChild(TreeViewItem<T> item) => _childItems.Add(item);
|
|
|
|
|
|
|
|
|
|
internal IEnumerable<TreeViewItem<T>> GetSelectedItems()
|
|
|
|
|
{
|
|
|
|
|
if (_isChecked)
|
|
|
|
|
yield return this;
|
|
|
|
|
|
|
|
|
|
foreach (var treeItem in _childItems)
|
|
|
|
|
{
|
|
|
|
|
foreach (var selected in treeItem.GetSelectedItems())
|
|
|
|
|
{
|
|
|
|
|
yield return selected;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal async void TryInvokeServerLoadFunc()
|
|
|
|
|
{
|
|
|
|
|
if (Expanded && (Items == null || Items.Count == 0) && TreeRoot?.ServerData != null)
|
|
|
|
|
{
|
|
|
|
|
Loading = true;
|
|
|
|
|
StateHasChanged();
|
|
|
|
|
|
|
|
|
|
Items = await TreeRoot.ServerData(Value);
|
|
|
|
|
|
|
|
|
|
Loading = false;
|
|
|
|
|
_isServerLoaded = true;
|
|
|
|
|
|
|
|
|
|
StateHasChanged();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|