// 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.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Web; namespace AntDesign { public partial class TreeNode : AntDomComponentBase { #region Node /// /// 树控件本身 /// [CascadingParameter(Name = "Tree")] public Tree TreeComponent { get; set; } /// /// 上一级节点 /// [CascadingParameter(Name = "Node")] public TreeNode ParentNode { get; set; } /// /// /// [Parameter] public RenderFragment Nodes { get; set; } internal List> ChildNodes { get; set; } = new List>(); /// /// Whether child nodes exist /// internal bool HasChildNodes => ChildNodes?.Count > 0; /// /// Current Node Level /// public int TreeLevel => (ParentNode?.TreeLevel ?? -1) + 1; /// /// record the index in children nodes list of parent node. /// internal int NodeIndex { get; set; } /// /// Determine if it is the last node in the same level nodes. /// internal bool IsLastNode => NodeIndex == (ParentNode?.ChildNodes.Count ?? TreeComponent?.ChildNodes.Count) - 1; /// /// add node to parent node /// /// internal void AddNode(TreeNode treeNode) { treeNode.NodeIndex = ChildNodes.Count; ChildNodes.Add(treeNode); IsLeaf = false; } /// /// Find a node /// /// Predicate /// Recursive Find /// public TreeNode FindFirstOrDefaultNode(Func, bool> predicate, bool recursive = true) { foreach (var child in ChildNodes) { if (predicate.Invoke(child)) { return child; } if (recursive) { var find = child.FindFirstOrDefaultNode(predicate, recursive); if (find != null) { return find; } } } return null; } /// /// Obtain the parent data set /// /// public List> GetParentNodes() { if (this.ParentNode != null) return this.ParentNode.ChildNodes; else return this.TreeComponent.ChildNodes; } public TreeNode GetPreviousNode() { var parentNodes = GetParentNodes(); var index = parentNodes.IndexOf(this); if (index == 0) return null; else return parentNodes[index - 1]; } public TreeNode GetNextNode() { var parentNodes = GetParentNodes(); var index = parentNodes.IndexOf(this); if (index == parentNodes.Count - 1) return null; else return parentNodes[index + 1]; } #endregion Node #region TreeNode private static long _nextNodeId; internal long NodeId { get; private set; } public TreeNode() { NodeId = Interlocked.Increment(ref _nextNodeId); } private string _key; /// /// Specifies the unique identifier name of the current node。 /// [Parameter] public string Key { get { if (TreeComponent.KeyExpression != null) return TreeComponent.KeyExpression(this); else return _key; } set { _key = value; } } private bool _disabled; /// /// The disabled state is subject to the parent node /// [Parameter] public bool Disabled { get { return _disabled || (ParentNode?.Disabled ?? false); } set { _disabled = value; } } private bool _selected; /// /// Selected or not /// [Parameter] public bool Selected { get => _selected; set { if (_selected == value) return; SetSelected(value); } } /// /// Setting Selection State /// /// public void SetSelected(bool value) { if (Disabled) return; if (!TreeComponent.Selectable && TreeComponent.Checkable) { SetChecked(!Checked); return; } if (_selected == value) return; _selected = value; if (value == true) { if (TreeComponent.Multiple == false) TreeComponent.DeselectAll(); TreeComponent.SelectedNodeAdd(this); } else { TreeComponent.SelectedNodeRemove(this); } StateHasChanged(); } /// /// Whether the load state is asynchronous (affects the display of the expansion icon) /// [Parameter] public bool Loading { get; set; } private bool _dragTarget; /// /// Whether or not to release the target /// internal bool DragTarget { get { return _dragTarget; } set { _dragTarget = value; StateHasChanged(); } } /// /// /// internal bool DragTargetBottom { get; private set; } /// /// Sets the node to release the target location /// /// public void SetTargetBottom(bool value = false) { if (DragTargetBottom == value) return; this.DragTargetBottom = value; StateHasChanged(); } /// /// /// private bool TargetContainer { get; set; } /// /// Sets the drag and drop target node container /// internal void SetParentTargetContainer(bool value = false) { if (this.ParentNode == null) return; if (this.ParentNode.TargetContainer == value) return; this.ParentNode.TargetContainer = value; this.ParentNode.StateHasChanged(); } /// /// Gets the children of the parent node /// /// private List> GetParentChildNodes() { return this.ParentNode?.ChildNodes ?? TreeComponent.ChildNodes; } /// /// Remove the current node /// public void RemoveNode() { GetParentChildNodes().Remove(this); } private void SetTreeNodeClassMapper() { ClassMapper .Add("ant-tree-treenode") .If("ant-tree-treenode-disabled", () => Disabled) .If("ant-tree-treenode-switcher-open", () => SwitcherOpen) .If("ant-tree-treenode-switcher-close", () => SwitcherClose) .If("ant-tree-treenode-checkbox-checked", () => Checked) .If("ant-tree-treenode-checkbox-indeterminate", () => Indeterminate) .If("ant-tree-treenode-selected", () => Selected) .If("ant-tree-treenode-loading", () => Loading) .If("drop-target", () => DragTarget) .If("drag-over-gap-bottom", () => DragTarget && DragTargetBottom) .If("drag-over", () => DragTarget && !DragTargetBottom) .If("drop-container", () => TargetContainer) .If("ant-tree-treenode-leaf-last", () => IsLastNode); } #endregion TreeNode #region Switcher private bool _isLeaf = true; /// /// Whether it is a leaf node /// [Parameter] public bool IsLeaf { get { if (TreeComponent.IsLeafExpression != null) return TreeComponent.IsLeafExpression(this); else return _isLeaf; } set { if (_isLeaf == value) return; _isLeaf = value; StateHasChanged(); } } /// /// Whether it has been expanded /// [Parameter] public bool Expanded { get; set; } /// /// Expand the node /// /// public void Expand(bool expanded) { if (Expanded == expanded) return; Expanded = expanded; } /// /// The real expand state, as long as there is a expaneded node on the path, then all the folds below /// internal bool RealDisplay { get { if (ParentNode == null) return true; if (ParentNode.Expanded == false) return false; return ParentNode.RealDisplay; } } /// /// Nodes switch /// /// /// private async Task OnSwitcherClick(MouseEventArgs args) { this.Expanded = !this.Expanded; await TreeComponent?.OnNodeExpand(this, this.Expanded, args); } internal void SetLoading(bool loading) { this.Loading = loading; } /// /// switcher is opened /// private bool SwitcherOpen => Expanded && !IsLeaf; /// /// switcher is close /// private bool SwitcherClose => !Expanded && !IsLeaf; /// /// expaned parents /// internal void OpenPropagation() { this.Expand(true); if (this.ParentNode != null) this.ParentNode.OpenPropagation(); } #endregion Switcher #region Checkbox /// /// According to check the /// [Parameter] public bool Checked { get; set; } [Parameter] public bool Indeterminate { get; set; } private bool _disableCheckbox; /// /// Disable checkbox /// [Parameter] public bool DisableCheckbox { get { return _disableCheckbox || (TreeComponent?.DisableCheckKeys?.Any(k => k == Key) ?? false); } set { _disableCheckbox = value; } } /// /// Triggered when the selection box is clicked /// private async void OnCheckBoxClick(MouseEventArgs args) { if (DisableCheckbox) return; SetChecked(!Checked); if (TreeComponent.OnCheck.HasDelegate) await TreeComponent.OnCheck.InvokeAsync(new TreeEventArgs(TreeComponent, this, args)); } /// /// Set the checkbox state /// /// public void SetChecked(bool check) { if (!Disabled) { if (TreeComponent.CheckStrictly) { this.Checked = check; } else { SetChildChecked(this, check); if (ParentNode != null) ParentNode.UpdateCheckState(); } } else TreeComponent.AddOrRemoveCheckNode(this); StateHasChanged(); } /// /// Sets the checkbox status of child nodes /// /// /// private void SetChildChecked(TreeNode subnode, bool check) { if (Disabled) return; this.Checked = DisableCheckbox ? false : check; this.Indeterminate = false; TreeComponent.AddOrRemoveCheckNode(this); if (subnode.HasChildNodes) foreach (var child in subnode.ChildNodes) child?.SetChildChecked(child, check); } /// /// Update check status /// /// private void UpdateCheckState(bool? halfChecked = null) { if (halfChecked == true) { //If the child node is indeterminate, the parent node must is indeterminate. this.Checked = false; this.Indeterminate = true; } else if (HasChildNodes == true && !DisableCheckbox) { //Determines the selection status of the current node bool hasChecked = false; bool hasUnchecked = false; foreach (var item in ChildNodes) { if (!item.DisableCheckbox && !item.Disabled) { if (item.Indeterminate) { hasChecked = true; hasUnchecked = true; break; } else if (item.Checked) { hasChecked = true; } else if (!item.Checked) { hasUnchecked = true; } } } if (hasChecked && !hasUnchecked) { this.Checked = true; this.Indeterminate = false; } else if (!hasChecked && hasUnchecked) { this.Checked = false; this.Indeterminate = false; } else if (hasChecked && hasUnchecked) { this.Checked = false; this.Indeterminate = true; } } TreeComponent.AddOrRemoveCheckNode(this); if (ParentNode != null) ParentNode.UpdateCheckState(this.Indeterminate); if (ParentNode == null) StateHasChanged(); } #endregion Checkbox #region Title [Parameter] public bool Draggable { get; set; } private string _icon; /// /// The icon in front of the node /// [Parameter] public string Icon { get { if (TreeComponent.IconExpression != null) return TreeComponent.IconExpression(this); else return _icon; } set { _icon = value; } } [Parameter] public RenderFragment> IconTemplate { get; set; } [Parameter] public string SwitcherIcon { get; set; } [Parameter] public RenderFragment> SwitcherIconTemplate { get; set; } private string _title; [Parameter] public string Title { get { if (TreeComponent.TitleExpression != null) return TreeComponent.TitleExpression(this); else return _title; } set { _title = value; } } [Parameter] public RenderFragment TitleTemplate { get; set; } /// /// title是否包含SearchValue(搜索使用) /// public bool Matched { get; set; } /// /// 子节点存在满足搜索条件,所以夫节点也需要显示 /// internal bool HasChildMatched { get; set; } #endregion Title #region data binding [Parameter] public TItem DataItem { get; set; } private IList ChildDataItems { get { if (TreeComponent.ChildrenExpression != null) return TreeComponent.ChildrenExpression(this) ?? new List(); else return new List(); } } /// /// 获得上级数据集合 /// /// public IList GetParentChildDataItems() { if (this.ParentNode != null) return this.ParentNode.ChildDataItems; else return this.TreeComponent.DataSource.ToList(); } #endregion data binding #region Node data operation /// /// Add child node /// /// public void AddChildNode(TItem dataItem) { ChildDataItems.Add(dataItem); } /// /// Add a node next the node /// /// public void AddNextNode(TItem dataItem) { var parentChildDataItems = GetParentChildDataItems(); var index = parentChildDataItems.IndexOf(this.DataItem); parentChildDataItems.Insert(index + 1, dataItem); AddNodeAndSelect(dataItem); } /// /// Add a node before the node /// /// public void AddPreviousNode(TItem dataItem) { var parentChildDataItems = GetParentChildDataItems(); var index = parentChildDataItems.IndexOf(this.DataItem); parentChildDataItems.Insert(index, dataItem); AddNodeAndSelect(dataItem); } /// /// remove /// public void Remove() { var parentChildDataItems = GetParentChildDataItems(); parentChildDataItems.Remove(this.DataItem); } /// /// The node moves into the child node /// /// target node public void MoveInto(TreeNode treeNode) { if (treeNode == this || this.DataItem.Equals(treeNode.DataItem)) return; var parentChildDataItems = GetParentChildDataItems(); parentChildDataItems.Remove(this.DataItem); treeNode.AddChildNode(this.DataItem); } /// /// Move up the nodes /// public void MoveUp() { var parentChildDataItems = GetParentChildDataItems(); var index = parentChildDataItems.IndexOf(this.DataItem); if (index == 0) return; parentChildDataItems.RemoveAt(index); parentChildDataItems.Insert(index - 1, this.DataItem); } /// /// Move down the node /// public void MoveDown() { var parentChildDataItems = GetParentChildDataItems(); var index = parentChildDataItems.IndexOf(this.DataItem); if (index == parentChildDataItems.Count - 1) return; parentChildDataItems.RemoveAt(index); parentChildDataItems.Insert(index + 1, this.DataItem); } /// /// /// public void Downgrade() { var previousNode = GetPreviousNode(); if (previousNode == null) return; var parentChildDataItems = GetParentChildDataItems(); parentChildDataItems.Remove(this.DataItem); previousNode.AddChildNode(this.DataItem); } /// /// Upgrade nodes /// public void Upgrade() { if (this.ParentNode == null) return; var parentChildDataItems = this.ParentNode.GetParentChildDataItems(); var index = parentChildDataItems.IndexOf(this.ParentNode.DataItem); Remove(); parentChildDataItems.Insert(index + 1, this.DataItem); } private void AddNodeAndSelect(TItem dataItem) { var tn = ChildNodes.FirstOrDefault(treeNode => treeNode.DataItem.Equals(dataItem)); if (tn != null) { this.Expand(true); tn.SetSelected(true); } } /// /// Drag and drop into child nodes /// /// 目标 internal void DragMoveInto(TreeNode treeNode) { if (TreeComponent.DataSource == null || !TreeComponent.DataSource.Any()) return; if (treeNode == this || this.DataItem.Equals(treeNode.DataItem)) return; Remove(); treeNode.AddChildNode(this.DataItem); treeNode.IsLeaf = false; treeNode.Expand(true); } /// /// Drag and drop to the bottom of the target /// /// 目标 internal void DragMoveDown(TreeNode treeNode) { if (TreeComponent.DataSource == null || !TreeComponent.DataSource.Any()) return; if (treeNode == this || this.DataItem.Equals(treeNode.DataItem)) return; Remove(); treeNode.AddNextNode(this.DataItem); } #endregion Node data operation bool _defaultBinding; protected override void OnInitialized() { SetTreeNodeClassMapper(); if (ParentNode != null) ParentNode.AddNode(this); else { TreeComponent.AddNode(this); if (!TreeComponent.DefaultExpandAll && TreeComponent.DefaultExpandParent) Expand(true); } TreeComponent._allNodes.Add(this); if (TreeComponent.DisabledExpression != null) Disabled = TreeComponent.DisabledExpression(this); if (TreeComponent.DefaultExpandAll) Expand(true); else if (TreeComponent.ExpandedKeys != null) { Expand(TreeComponent.ExpandedKeys.Any(k => k == this.Key)); } if (TreeComponent.Selectable && TreeComponent.SelectedKeys != null) { this.Selected = TreeComponent.SelectedKeys.Any(k => k == this.Key); } base.OnInitialized(); } protected override void OnParametersSet() { DefaultBinding(); base.OnParametersSet(); } private void DefaultBinding() { if (!_defaultBinding) { _defaultBinding = true; if (this.Checked) this.SetChecked(true); TreeComponent.DefaultCheckedKeys?.ForEach(k => { var node = TreeComponent._allNodes.FirstOrDefault(x => x.Key == k); if (node != null) node.SetChecked(true); }); TreeComponent.DefaultSelectedKeys?.ForEach(k => { var node = TreeComponent._allNodes.FirstOrDefault(x => x.Key == k); if (node != null) node.SetSelected(true); }); if (!TreeComponent.DefaultExpandAll) { if (this.Expanded) this.OpenPropagation(); TreeComponent.DefaultExpandedKeys?.ForEach(k => { var node = TreeComponent._allNodes.FirstOrDefault(x => x.Key == k); if (node != null) node.OpenPropagation(); }); } } } } }