// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Web; namespace AntDesign { public partial class Tree : AntDomComponentBase { #region fields /// /// All of the node /// internal List> _allNodes = new List>(); /// /// All the checked nodes /// private ConcurrentDictionary> _checkedNodes = new ConcurrentDictionary>(); #endregion fields #region Tree /// /// Shows an expansion icon before the node /// [Parameter] public bool ShowExpand { get; set; } = true; /// /// Shows a connecting line /// [Parameter] public bool ShowLine { get => _showLine; set { _showLine = value; if (!_hasSetShowLeafIcon) { ShowLeafIcon = _showLine; } } } /// /// show treeNode icon icon /// [Parameter] public bool ShowIcon { get; set; } /// /// Whether treeNode fill remaining horizontal space /// [Parameter] public bool BlockNode { get; set; } /// /// Whether the node allows drag and drop /// [Parameter] public bool Draggable { get; set; } /// /// The tree is disabled /// [Parameter] public bool Disabled { get; set; } /// /// Displays the cotyledon icon /// [Parameter] public bool ShowLeafIcon { get => _showLeafIcon; set { _showLeafIcon = value; _hasSetShowLeafIcon = true; } } private bool _hasSetShowLeafIcon; /// /// Specific the Icon type of switcher /// [Parameter] public string SwitcherIcon { get; set; } public bool Directory { get; set; } private void SetClassMapper() { ClassMapper .Add("ant-tree") .If("ant-tree-show-line", () => ShowLine) .If("ant-tree-icon-hide", () => ShowIcon) .If("ant-tree-block-node", () => BlockNode) .If("ant-tree-directory", () => Directory) .If("draggable-tree", () => Draggable) .If("ant-tree-unselectable", () => !Selectable) .If("ant-tree-rtl", () => RTL); } #endregion Tree #region Node [Parameter] public RenderFragment Nodes { get; set; } /// /// tree childnodes /// Add values when the node is initialized /// internal List> ChildNodes { get; set; } = new List>(); /// /// Add a node /// /// internal void AddNode(TreeNode treeNode) { treeNode.NodeIndex = ChildNodes.Count; ChildNodes.Add(treeNode); } #endregion Node #region Selected /// /// Whether can be selected /// [Parameter] public bool Selectable { get; set; } = true; /// /// Allows selecting multiple treeNodes /// [Parameter] public bool Multiple { get; set; } [Parameter] public string[] DefaultSelectedKeys { get; set; } /// /// The selected tree node /// internal Dictionary> SelectedNodesDictionary { get; set; } = new Dictionary>(); internal List SelectedTitles => SelectedNodesDictionary.Select(x => x.Value.Title).ToList(); /// /// Add the selected node /// /// internal void SelectedNodeAdd(TreeNode treeNode) { if (SelectedNodesDictionary.ContainsKey(treeNode.NodeId) == false) SelectedNodesDictionary.Add(treeNode.NodeId, treeNode); if (OnSelect.HasDelegate) { OnSelect.InvokeAsync(new TreeEventArgs(this, treeNode)); } } /// /// remove the selected node /// /// internal void SelectedNodeRemove(TreeNode treeNode) { if (SelectedNodesDictionary.ContainsKey(treeNode.NodeId) == true) SelectedNodesDictionary.Remove(treeNode.NodeId); } /// /// Deselect all selections /// public void DeselectAll() { foreach (var item in SelectedNodesDictionary.Select(x => x.Value).ToList()) { item.SetSelected(false); } } /// /// @bind-SelectedKey /// [Parameter] public string SelectedKey { get; set; } /// /// /// [Parameter] public EventCallback SelectedKeyChanged { get; set; } /// /// @bind-SelectedNode /// [Parameter] public TreeNode SelectedNode { get; set; } [Parameter] public EventCallback> SelectedNodeChanged { get; set; } /// /// @bing-SelectedData /// [Parameter] public TItem SelectedData { get; set; } [Parameter] public EventCallback SelectedDataChanged { get; set; } /// /// /// [Parameter] public string[] SelectedKeys { get; set; } [Parameter] public EventCallback SelectedKeysChanged { get; set; } /// /// The collection of selected nodes /// [Parameter] public TreeNode[] SelectedNodes { get; set; } /// /// The selected data set /// [Parameter] public TItem[] SelectedDatas { get; set; } /// /// Update binding data /// internal void UpdateBindData() { if (SelectedNodesDictionary.Count == 0) { SelectedKey = null; SelectedNode = null; SelectedData = default(TItem); SelectedKeys = Array.Empty(); SelectedNodes = Array.Empty>(); SelectedDatas = Array.Empty(); } else { var selectedFirst = SelectedNodesDictionary.FirstOrDefault(); SelectedKey = selectedFirst.Value?.Key; SelectedNode = selectedFirst.Value; SelectedData = selectedFirst.Value.DataItem; SelectedKeys = SelectedNodesDictionary.Select(x => x.Value.Key).ToArray(); SelectedNodes = SelectedNodesDictionary.Select(x => x.Value).ToArray(); SelectedDatas = SelectedNodesDictionary.Select(x => x.Value.DataItem).ToArray(); } if (SelectedKeyChanged.HasDelegate) SelectedKeyChanged.InvokeAsync(SelectedKey); if (SelectedNodeChanged.HasDelegate) SelectedNodeChanged.InvokeAsync(SelectedNode); if (SelectedDataChanged.HasDelegate) SelectedDataChanged.InvokeAsync(SelectedData); if (SelectedKeysChanged.HasDelegate) SelectedKeysChanged.InvokeAsync(SelectedKeys); } #endregion Selected #region Checkable /// /// Add a Checkbox before the node /// [Parameter] public bool Checkable { get; set; } /// /// Check treeNode precisely; parent treeNode and children treeNodes are not associated /// [Parameter] public bool CheckStrictly { get; set; } /// /// Checked keys /// [Parameter] public string[] CheckedKeys { get; set; } = Array.Empty(); /// /// @bind-CheckedKeys /// [Parameter] public EventCallback CheckedKeysChanged { get; set; } /// /// Dechecked all selected items /// public void CheckedAll() { foreach (var item in ChildNodes) { item.SetChecked(true); } } // Decheck all of the checked nodes public void DecheckedAll() { foreach (var item in ChildNodes) { item.SetChecked(false); } } /// /// Specifies the keys of the default checked treeNodes /// [Parameter] public string[] DefaultCheckedKeys { get; set; } /// /// Disable node Checkbox /// public string[] DisableCheckKeys { get; set; } /// /// Adds or removes a checkbox node /// /// internal void AddOrRemoveCheckNode(TreeNode treeNode) { if (treeNode.Checked) _checkedNodes.TryAdd(treeNode.NodeId, treeNode); else _checkedNodes.TryRemove(treeNode.NodeId, out TreeNode _); CheckedKeys = _checkedNodes.Select(x => x.Value.Key).ToArray(); if (CheckedKeysChanged.HasDelegate) CheckedKeysChanged.InvokeAsync(CheckedKeys); } #endregion Checkable #region Search private string _searchValue; private bool _showLeafIcon; private bool _showLine; /// /// search value /// [Parameter] public string SearchValue { get => _searchValue; set { _searchValue = value; SearchNodes(); } } [Parameter] public Func, bool> SearchExpression { get; set; } /// /// Search for matching text styles /// [Parameter] public string MatchedStyle { get; set; } = ""; [Parameter] public string MatchedClass { get; set; } private void SearchNodes() { var allList = _allNodes.ToList(); List> searchDatas = null, exceptList = null; if (SearchExpression != null) { searchDatas = allList.Where(x => SearchExpression(x)).ToList(); } else if (!string.IsNullOrEmpty(_searchValue)) { searchDatas = allList.Where(x => x.Title.Contains(_searchValue)).ToList(); } if (searchDatas != null && searchDatas.Any()) exceptList = allList.Except(searchDatas).ToList(); if (exceptList != null || searchDatas != null) { exceptList?.ForEach(m => { m.Expand(false); m.Matched = false; }); searchDatas?.ForEach(node => { node.OpenPropagation(); node.Matched = true; }); } else { allList.ForEach(m => { m.Expand(false); m.Matched = false; }); } } #endregion Search #region DataBind /// /// /// [Parameter] public IEnumerable DataSource { get; set; } /// /// Specifies a method that returns the text of the node. /// [Parameter] public Func, string> TitleExpression { get; set; } /// /// Specifies a method that returns the key of the node. /// [Parameter] public Func, string> KeyExpression { get; set; } /// /// Specifies a method to return the node icon. /// [Parameter] public Func, string> IconExpression { get; set; } /// /// Specifies a method that returns whether the expression is a leaf node. /// [Parameter] public Func, bool> IsLeafExpression { get; set; } /// /// Specifies a method to return a child node /// [Parameter] public Func, IList> ChildrenExpression { get; set; } /// /// Specifies a method to return a disabled node /// [Parameter] public Func, bool> DisabledExpression { get; set; } #endregion DataBind #region Event /// /// Lazy load callbacks /// /// You must use async and the return type is Task, otherwise you may experience load lag and display problems [Parameter] public EventCallback> OnNodeLoadDelayAsync { get; set; } /// /// Click the tree node callback /// [Parameter] public EventCallback> OnClick { get; set; } /// /// Double-click the node callback /// [Parameter] public EventCallback> OnDblClick { get; set; } /// /// Right-click tree node callback /// [Parameter] public EventCallback> OnContextMenu { get; set; } /// /// checked the tree node callback /// [Parameter] public EventCallback> OnCheck { get; set; } [Parameter] public EventCallback> OnSelect { get; set; } /// /// Click the expansion tree node icon to call back /// [Parameter] public EventCallback> OnExpandChanged { get; set; } #endregion Event #region Template /// /// The indentation template /// [Parameter] public RenderFragment> IndentTemplate { get; set; } /// /// Customize the header template /// [Parameter] public RenderFragment> TitleTemplate { get; set; } /// /// Customize the icon templates /// [Parameter] public RenderFragment> TitleIconTemplate { get; set; } /// /// Customize toggle icon templates /// [Parameter] public RenderFragment> SwitcherIconTemplate { get; set; } #endregion Template #region DragDrop /// /// 当前拖拽项 /// internal TreeNode DragItem { get; set; } /// /// Called when the drag and drop begins /// [Parameter] public EventCallback> OnDragStart { get; set; } /// /// Called when drag and drop into a releasable target /// [Parameter] public EventCallback> OnDragEnter { get; set; } /// /// Called when drag and drop away from a releasable target /// [Parameter] public EventCallback> OnDragLeave { get; set; } /// /// Triggered when drag-and-drop drops succeed /// [Parameter] public EventCallback> OnDrop { get; set; } /// /// Drag-and-drop end callback /// /// this callback method must be set [Parameter] public EventCallback> OnDragEnd { get; set; } #endregion DragDrop protected override void OnInitialized() { SetClassMapper(); base.OnInitialized(); } /// /// Find Node /// /// Predicate /// Recursive Find /// public TreeNode FindFirstOrDefaultNode(Func, bool> predicate, bool recursive = true) { foreach (var child in ChildNodes) { if (predicate != null && predicate.Invoke(child)) { return child; } if (recursive) { var find = child.FindFirstOrDefaultNode(predicate, recursive); if (find != null) { return find; } } } return null; } #region Expand /// /// All tree nodes are expanded by default /// [Parameter] public bool DefaultExpandAll { get; set; } /// /// The parent node is expanded by default /// [Parameter] public bool DefaultExpandParent { get; set; } /// /// Expand the specified tree node by default /// [Parameter] public string[] DefaultExpandedKeys { get; set; } /// /// (Controlled) expands the specified tree node /// [Parameter] public string[] ExpandedKeys { get; set; } [Parameter] public EventCallback ExpandedKeysChanged { get; set; } [Parameter] public EventCallback<(string[] ExpandedKeys, TreeNode Node, bool Expanded)> OnExpand { get; set; } [Parameter] public bool AutoExpandParent { get; set; } /// /// Expand all nodes /// public void ExpandAll() { this.ChildNodes.ForEach(node => Switch(node, true)); } /// /// Collapse all nodes /// public void CollapseAll() { this.ChildNodes.ForEach(node => Switch(node, false)); } /// /// 节点展开关闭 /// /// /// private void Switch(TreeNode node, bool expanded) { node.Expand(expanded); node.ChildNodes.ForEach(n => Switch(n, expanded)); } internal async Task OnNodeExpand(TreeNode node, bool expanded, MouseEventArgs args) { var expandedKeys = _allNodes.Select(x => x.Key).ToArray(); if (OnNodeLoadDelayAsync.HasDelegate && expanded == true) { node.SetLoading(true); await OnNodeLoadDelayAsync.InvokeAsync(new TreeEventArgs(this, node, args)); node.SetLoading(false); } if (OnExpandChanged.HasDelegate) { await OnExpandChanged.InvokeAsync(new TreeEventArgs(this, node, args)); } if (ExpandedKeysChanged.HasDelegate) { await ExpandedKeysChanged.InvokeAsync(expandedKeys); } if (OnExpand.HasDelegate) { await OnExpand.InvokeAsync((expandedKeys, node, expanded)); } if (AutoExpandParent && expanded) { node.ParentNode?.Expand(true); } } #endregion Expand } }