Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 9c88b10

Browse files
ddjerqqddjerqqstsrkiDavid-Moreira
authoredOct 22, 2024··
TreeView: Add virtualization support (#5689)
* added virtualization support to treeview * Replaced a regular html anchor tag with a blazorise Anchor component. Added the relevant logs to the current changelog's optimization section. Updated the summary string documentation for the Virtualize Parameter of TreeView * formating * Mention default value * Fix virtualization * Add Virtualization example * Don't pass styling parameters further from root element * Use defaults when Virtualize is enabled * release notes * Change description * Docs notes * testing treeview virtualize with dynamic data * Properly fallback to default Height and Overflow values * Remove extra parenthesis * Fix duplicate nodes --------- Co-authored-by: ddjerqq <570173344+ddjerqq@users.noreply.github.com> Co-authored-by: Mladen Macanovic <mladen.macanovic@gmail.com> Co-authored-by: Mladen Macanovic <mladen.macanovic@blazorise.com> Co-authored-by: David Moreira <david_nogueira_moreira@hotmail.com>
1 parent f279562 commit 9c88b10

File tree

12 files changed

+630
-86
lines changed

12 files changed

+630
-86
lines changed
 

‎Demos/Blazorise.Demo/Pages/Tests/TreeViewPage.razor

+5
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
</Field>
2727
</Column>
2828
<Column ColumnSize="ColumnSize.IsAuto">
29+
<Button Color="Color.Primary" Clicked="AddNode">Add</Button>
2930
<Button Color="Color.Primary" Clicked="ForceReload">Force Reload</Button>
3031
<Button Color="Color.Primary" Clicked="DisableRandomNode">Disable Random</Button>
3132
<Button Color="Color.Primary" Clicked="DisableAll">Disable All</Button>
@@ -41,12 +42,16 @@
4142
<Button Color="Color.Secondary" Clicked="@(()=>selectedNodes = Nodes.Take(3).ToList())">Select multiple nodes</Button>
4243
}
4344
</Column>
45+
<Column ColumnSize="ColumnSize.IsAuto">
46+
<Switch @bind-Checked=virtualize>Virtualize</Switch>
47+
</Column>
4448
</Row>
4549
</CardBody>
4650
<CardBody>
4751
<TreeView @ref="@treeViewRef"
4852
TNode="NodeInfo"
4953
Nodes="Nodes"
54+
Virtualize="@virtualize"
5055
SelectionMode="@selectionMode"
5156
@bind-SelectedNode="selectedNode"
5257
@bind-ExpandedNodes="expandedNodes"

‎Demos/Blazorise.Demo/Pages/Tests/TreeViewPage.razor.cs

+21-8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Collections.ObjectModel;
34
using System.Linq;
45
using System.Security;
56
using System.Threading.Tasks;
@@ -12,30 +13,31 @@ namespace Blazorise.Demo.Pages.Tests;
1213
public partial class TreeViewPage : ComponentBase
1314
{
1415
TreeView<NodeInfo> treeViewRef;
15-
private IList<NodeInfo> expandedNodes = new List<NodeInfo>();
16-
private IList<NodeInfo> selectedNodes = new List<NodeInfo>();
16+
private IList<NodeInfo> expandedNodes = new ObservableCollection<NodeInfo>();
17+
private IList<NodeInfo> selectedNodes = new ObservableCollection<NodeInfo>();
1718
private NodeInfo selectedNode;
1819
private TreeViewSelectionMode selectionMode;
20+
private bool virtualize;
1921

2022
public class NodeInfo
2123
{
2224
public string Text { get; set; }
23-
public IEnumerable<NodeInfo> Children { get; set; }
25+
public ObservableCollection<NodeInfo> Children { get; set; }
2426
public bool Disabled { get; set; }
2527
}
2628

27-
private IEnumerable<NodeInfo> Nodes = new[]
29+
private ObservableCollection<NodeInfo> Nodes = new ObservableCollection<NodeInfo>()
2830
{
2931
new NodeInfo { Text = "NodeInfo 1" },
3032
new NodeInfo
3133
{
3234
Text = "NodeInfo 2",
33-
Children = new []
35+
Children = new ObservableCollection<NodeInfo>()
3436
{
3537
new NodeInfo { Text = "NodeInfo 2.1" },
3638
new NodeInfo
3739
{
38-
Text = "NodeInfo 2.2", Children = new []
40+
Text = "NodeInfo 2.2", Children = new ObservableCollection<NodeInfo>()
3941
{
4042
new NodeInfo { Text = "NodeInfo 2.2.1" },
4143
new NodeInfo { Text = "NodeInfo 2.2.2" },
@@ -51,12 +53,12 @@ public class NodeInfo
5153
new NodeInfo
5254
{
5355
Text = "NodeInfo 4",
54-
Children = new []
56+
Children = new ObservableCollection<NodeInfo>()
5557
{
5658
new NodeInfo { Text = "NodeInfo 4.1" },
5759
new NodeInfo
5860
{
59-
Text = "NodeInfo 4.2", Children = new []
61+
Text = "NodeInfo 4.2", Children = new ObservableCollection<NodeInfo>()
6062
{
6163
new NodeInfo { Text = "NodeInfo 4.2.1" },
6264
new NodeInfo { Text = "NodeInfo 4.2.2" },
@@ -72,6 +74,17 @@ public class NodeInfo
7274
new NodeInfo { Text = "NodeInfo 6" }
7375
};
7476

77+
78+
int count = 0;
79+
private async Task AddNode()
80+
{
81+
count++;
82+
selectedNode.Children ??= new ObservableCollection<NodeInfo>();
83+
selectedNode.Children.Add( new NodeInfo()
84+
{
85+
Text = selectedNode.Text + count,
86+
} );
87+
}
7588
private async Task ForceReload()
7689
{
7790
await treeViewRef.Reload();

‎Documentation/Blazorise.Docs/Models/Snippets.generated.cs

+40
Original file line numberDiff line numberDiff line change
@@ -10970,6 +10970,46 @@ public class Item
1097010970

1097110971
public const string TreeViewResourcesExample = @"<link href=""_content/Blazorise.TreeView/blazorise.treeview.css"" rel=""stylesheet"" />";
1097210972

10973+
public const string TreeViewVirtualizationExample = @"<TreeView Nodes=""Items""
10974+
GetChildNodes=""@(item => item.Children)""
10975+
HasChildNodes=""@(item => item.Children?.Any() == true)""
10976+
@bind-SelectedNode=""selectedNode""
10977+
@bind-ExpandedNodes=""expandedNodes""
10978+
Virtualize>
10979+
<NodeContent>
10980+
<Icon Name=""IconName.Folder"" />
10981+
@context.Text
10982+
</NodeContent>
10983+
</TreeView>
10984+
10985+
@code {
10986+
public class Item
10987+
{
10988+
public string Text { get; set; }
10989+
public IEnumerable<Item> Children { get; set; }
10990+
}
10991+
10992+
protected override void OnInitialized()
10993+
{
10994+
Items = Enumerable.Range( 1, 4 ).Select( rootIndex => new Item
10995+
{
10996+
Text = $""Root Node {rootIndex}"",
10997+
Children = Enumerable.Range( 1, 100 ).Select( childIndex => new Item
10998+
{
10999+
Text = $""Root {rootIndex} - Child {childIndex}"",
11000+
Children = Enumerable.Empty<Item>() // No children for the child nodes in this example
11001+
} )
11002+
} ).ToList();
11003+
11004+
base.OnInitialized();
11005+
}
11006+
11007+
IEnumerable<Item> Items;
11008+
11009+
IList<Item> expandedNodes = new List<Item>();
11010+
Item selectedNode;
11011+
}";
11012+
1097311013
public const string BasicVideoExample = @"<Video Source=""@(""http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4"")"" />";
1097411014

1097511015
public const string DRMVideoExample = @"<Video Source=""@(""https://media.axprod.net/TestVectors/v7-MultiDRM-SingleKey/Manifest_1080p.mpd"")""
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<div class="blazorise-codeblock">
2+
<div class="html"><pre>
3+
<span class="htmlTagDelimiter">&lt;</span><span class="htmlElementName">TreeView</span> <span class="htmlAttributeName">Nodes</span><span class="htmlOperator">=</span><span class="quot">&quot;</span><span class="htmlAttributeValue">Items</span><span class="quot">&quot;</span>
4+
<span class="htmlAttributeName">GetChildNodes</span><span class="htmlOperator">=</span><span class="quot">&quot;</span><span class="htmlAttributeValue"><span class="atSign">&#64;</span>(item =&gt; item.Children)</span><span class="quot">&quot;</span>
5+
<span class="htmlAttributeName">HasChildNodes</span><span class="htmlOperator">=</span><span class="quot">&quot;</span><span class="htmlAttributeValue"><span class="atSign">&#64;</span>(item =&gt; item.Children?.Any() == true)</span><span class="quot">&quot;</span>
6+
<span class="htmlAttributeName"><span class="atSign">&#64;</span>bind-SelectedNode</span><span class="htmlOperator">=</span><span class="quot">&quot;</span><span class="htmlAttributeValue">selectedNode</span><span class="quot">&quot;</span>
7+
<span class="htmlAttributeName"><span class="atSign">&#64;</span>bind-ExpandedNodes</span><span class="htmlOperator">=</span><span class="quot">&quot;</span><span class="htmlAttributeValue">expandedNodes</span><span class="quot">&quot;</span>
8+
<span class="htmlAttributeName">Virtualize</span><span class="htmlTagDelimiter">&gt;</span>
9+
<span class="htmlTagDelimiter">&lt;</span><span class="htmlElementName">NodeContent</span><span class="htmlTagDelimiter">&gt;</span>
10+
<span class="htmlTagDelimiter">&lt;</span><span class="htmlElementName">Icon</span> <span class="htmlAttributeName">Name</span><span class="htmlOperator">=</span><span class="quot">&quot;</span><span class="enum">IconName</span><span class="enumValue">.Folder</span><span class="quot">&quot;</span> <span class="htmlTagDelimiter">/&gt;</span>
11+
<span class="atSign">&#64;</span>context.Text
12+
<span class="htmlTagDelimiter">&lt;/</span><span class="htmlElementName">NodeContent</span><span class="htmlTagDelimiter">&gt;</span>
13+
<span class="htmlTagDelimiter">&lt;/</span><span class="htmlElementName">TreeView</span><span class="htmlTagDelimiter">&gt;</span>
14+
</pre></div>
15+
<div class="csharp"><pre>
16+
<span class="atSign">&#64;</span>code {
17+
<span class="keyword">public</span> <span class="keyword">class</span> Item
18+
{
19+
<span class="keyword">public</span> <span class="keyword">string</span> Text { <span class="keyword">get</span>; <span class="keyword">set</span>; }
20+
<span class="keyword">public</span> IEnumerable&lt;Item&gt; Children { <span class="keyword">get</span>; <span class="keyword">set</span>; }
21+
}
22+
23+
<span class="keyword">protected</span> <span class="keyword">override</span> <span class="keyword">void</span> OnInitialized()
24+
{
25+
Items = Enumerable.Range( <span class="number">1</span>, <span class="number">4</span> ).Select( rootIndex =&gt; <span class="keyword">new</span> Item
26+
{
27+
Text = $<span class="string">&quot;Root Node {rootIndex}&quot;</span>,
28+
Children = Enumerable.Range( <span class="number">1</span>, <span class="number">100</span> ).Select( childIndex =&gt; <span class="keyword">new</span> Item
29+
{
30+
Text = $<span class="string">&quot;Root {rootIndex} - Child {childIndex}&quot;</span>,
31+
Children = Enumerable.Empty&lt;Item&gt;() <span class="comment">// No children for the child nodes in this example</span>
32+
} )
33+
} ).ToList();
34+
35+
<span class="keyword">base</span>.OnInitialized();
36+
}
37+
38+
IEnumerable&lt;Item&gt; Items;
39+
40+
IList&lt;Item&gt; expandedNodes = <span class="keyword">new</span> List&lt;Item&gt;();
41+
Item selectedNode;
42+
}
43+
</pre></div>
44+
</div>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
@namespace Blazorise.Docs.Docs.Examples
2+
3+
<TreeView Nodes="Items"
4+
GetChildNodes="@(item => item.Children)"
5+
HasChildNodes="@(item => item.Children?.Any() == true)"
6+
@bind-SelectedNode="selectedNode"
7+
@bind-ExpandedNodes="expandedNodes"
8+
Virtualize>
9+
<NodeContent>
10+
<Icon Name="IconName.Folder" />
11+
@context.Text
12+
</NodeContent>
13+
</TreeView>
14+
15+
@code {
16+
public class Item
17+
{
18+
public string Text { get; set; }
19+
public IEnumerable<Item> Children { get; set; }
20+
}
21+
22+
protected override void OnInitialized()
23+
{
24+
Items = Enumerable.Range( 1, 4 ).Select( rootIndex => new Item
25+
{
26+
Text = $"Root Node {rootIndex}",
27+
Children = Enumerable.Range( 1, 100 ).Select( childIndex => new Item
28+
{
29+
Text = $"Root {rootIndex} - Child {childIndex}",
30+
Children = Enumerable.Empty<Item>() // No children for the child nodes in this example
31+
} )
32+
} ).ToList();
33+
34+
base.OnInitialized();
35+
}
36+
37+
IEnumerable<Item> Items;
38+
39+
IList<Item> expandedNodes = new List<Item>();
40+
Item selectedNode;
41+
}

‎Documentation/Blazorise.Docs/Pages/Docs/Extensions/TreeView/TreeViewPage.razor

+33
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,36 @@
159159
<DocsPageSectionSource Code="TreeViewContextMenuExample" />
160160
</DocsPageSection>
161161

162+
<DocsPageSection>
163+
<DocsPageSectionHeader Title="Virtualization">
164+
<Paragraph>
165+
This example demonstrates how to use virtualization in a Blazorise <Code Tag>TreeView</Code> component to efficiently render large hierarchical data sets. Virtualization improves performance by only rendering the visible nodes in the viewport, rather than all nodes in the tree. This is particularly useful when dealing with large numbers of nodes, as it reduces the DOM size and enhances responsiveness.
166+
</Paragraph>
167+
<Paragraph>
168+
For virtualization to function correctly, it is essential to specify both the <Strong>Height</Strong> and <Strong>Overflow</Strong> properties
169+
</Paragraph>
170+
<OrderedList>
171+
<OrderedListItem>
172+
<Paragraph>
173+
<Strong>Height</Strong>: Defines the fixed height of the TreeView component. Without a specified height, the tree would expand indefinitely, defeating the purpose of virtualization since all nodes would be rendered at once.
174+
</Paragraph>
175+
</OrderedListItem>
176+
<OrderedListItem>
177+
<Paragraph>
178+
<Strong>Overflow</Strong>: Ensures that the tree's content is scrollable. This scrollable area allows for dynamic loading of nodes as the user scrolls, effectively utilizing virtualization to render only the nodes currently in view.
179+
</Paragraph>
180+
</OrderedListItem>
181+
</OrderedList>
182+
<Paragraph>
183+
By default, when <Code>Virtualize</Code> is enabled, we will define <Strong>Height</Strong> and <Strong>Overflow</Strong> for you, if they are not already explicitly defined.
184+
</Paragraph>
185+
</DocsPageSectionHeader>
186+
<DocsPageSectionContent Outlined FullWidth>
187+
<TreeViewVirtualizationExample />
188+
</DocsPageSectionContent>
189+
<DocsPageSectionSource Code="TreeViewVirtualizationExample" />
190+
</DocsPageSection>
191+
162192
<DocsPageSubtitle>
163193
API
164194
</DocsPageSubtitle>
@@ -192,6 +222,9 @@
192222
<DocsAttributesItem Name="AutoExpandAll" Type="bool" Default="false">
193223
Defines if the treenode should be automatically expanded. Note that it can happen only once when the tree is first loaded.
194224
</DocsAttributesItem>
225+
<DocsAttributesItem Name="Virtualize" Type="bool" Default="false">
226+
Controls if the child nodes, which are currently not expanded, are visible. See <Anchor To="https://learn.microsoft.com/en-us/aspnet/core/blazor/components/virtualization">docs for Virtualization</Anchor>.
227+
</DocsAttributesItem>
195228
<DocsAttributesItem Name="ExpandedNodes" Type="List<TNode>">
196229
List of currently expanded TreeView items (child nodes).
197230
</DocsAttributesItem>

‎Documentation/Blazorise.Docs/Pages/News/2024-10-15-release-notes-170.razor

+8
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,14 @@
196196
After our community has convinced us that these parameters are still useful, we have decided to undeprecate them. We have undeprecated the <Code>CellClass</Code> and <Code>CellStyle</Code> parameters in the <Code>DataGridColumn</Code> component. These parameters allow you to define the class and style for the cell based on the cell item value.
197197
</Paragraph>
198198

199+
<Heading Size="HeadingSize.Is3">
200+
TreeView Virtualization
201+
</Heading>
202+
203+
<Paragraph>
204+
We have added the ability to virtualize the TreeView component. This can be useful when you have a large number of nodes and you want to improve the performance of the TreeView component. The virtualization feature allows you to render only the visible nodes, which can significantly reduce the number of DOM elements and improve the overall performance of the TreeView component.
205+
</Paragraph>
206+
199207
<Heading Size="HeadingSize.Is3">
200208
Optimizations
201209
</Heading>
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,79 @@
1-
@typeparam TNode
1+
@using Microsoft.AspNetCore.Components.Web.Virtualization
2+
@typeparam TNode
23
@inherits BaseComponent
34
<CascadingValue Value="@this" IsFixed>
45
<div class="@ClassNames" style="@StyleNames" @attributes="@Attributes">
5-
@foreach ( var nodeState in NodeStates ?? Enumerable.Empty<TreeViewNodeState<TNode>>() )
6+
@if ( NodeStates is not null )
67
{
7-
<div @key="@nodeState.Key" @oncontextmenu="@((eventArgs)=>OnContextMenuHandler(nodeState, eventArgs))" @oncontextmenu:stopPropagation @oncontextmenu:preventDefault="@ContextMenuPreventDefault">
8-
@if ( nodeState.HasChildren )
8+
@if ( Virtualize )
9+
{
10+
<Virtualize TItem="TreeViewNodeState<TNode>" Context="node" Items="@NodeStates.ToList()">
11+
@nodeFragment( node )
12+
</Virtualize>
13+
}
14+
else
15+
{
16+
@foreach ( var nodeState in NodeStates )
917
{
10-
<span class="b-tree-view-node-icon" @onclick="@(() => ToggleNode(nodeState))">
11-
@if ( nodeState.Expanded )
12-
{
13-
<Icon Name="@CollapseIconName" IconStyle="@CollapseIconStyle" IconSize="@CollapseIconSize" />
14-
}
15-
else
16-
{
17-
<Icon Name="@ExpandIconName" IconStyle="@ExpandIconStyle" IconSize="@ExpandIconSize" />
18-
}
19-
</span>
18+
@nodeFragment( nodeState )
2019
}
21-
22-
<_TreeViewNodeContent TNode="TNode" NodeState="@nodeState"
23-
NodeStyling="@NodeStyling"
24-
SelectionMode="@SelectionMode"
25-
SelectedNodeStyling="@SelectedNodeStyling"
26-
DisabledNodeStyling="@DisabledNodeStyling">
27-
@NodeContent( nodeState.Node )
28-
</_TreeViewNodeContent>
29-
30-
@if ( nodeState.Expanded && nodeState.HasChildren )
31-
{
32-
<_TreeViewNode @ref="@nodeState.ViewRef"
33-
NodeStates="@nodeState.Children"
34-
NodeContent="@NodeContent"
35-
GetChildNodes="@GetChildNodes"
36-
GetChildNodesAsync="@GetChildNodesAsync"
37-
ExpandedNodes="@ExpandedNodes"
38-
ExpandedNodesChanged="@ExpandedNodesChanged"
39-
Expanded="@nodeState.Expanded"
40-
AutoExpandAll="@AutoExpandAll"
41-
HasChildNodes="@HasChildNodes"
42-
IsDisabled="@IsDisabled"
43-
HasChildNodesAsync="@HasChildNodesAsync"
44-
NodeStyling="@NodeStyling"
45-
SelectedNodeStyling="@SelectedNodeStyling"
46-
DisabledNodeStyling="@DisabledNodeStyling"
47-
ExpandIconName="@ExpandIconName"
48-
ExpandIconStyle="@ExpandIconStyle"
49-
CollapseIconName="@CollapseIconName"
50-
CollapseIconStyle="@CollapseIconStyle"
51-
SelectionMode="@SelectionMode"
52-
ContextMenu="@ContextMenu"
53-
ContextMenuPreventDefault="@ContextMenuPreventDefault" />
54-
}
55-
</div>
20+
}
5621
}
5722
</div>
58-
</CascadingValue>
23+
</CascadingValue>
24+
25+
@code {
26+
protected RenderFragment<TreeViewNodeState<TNode>> nodeFragment => nodeState => __builder =>
27+
{
28+
<div @key="@nodeState.Key" @oncontextmenu="@((eventArgs)=>OnContextMenuHandler(nodeState, eventArgs))" @oncontextmenu:stopPropagation @oncontextmenu:preventDefault="@ContextMenuPreventDefault">
29+
@if ( nodeState.HasChildren )
30+
{
31+
<span class="b-tree-view-node-icon" @onclick="@(()=>ToggleNode(nodeState))">
32+
@if ( nodeState.Expanded )
33+
{
34+
<Icon Name="@CollapseIconName" IconStyle="@CollapseIconStyle" IconSize="@CollapseIconSize" />
35+
}
36+
else
37+
{
38+
<Icon Name="@ExpandIconName" IconStyle="@ExpandIconStyle" IconSize="@ExpandIconSize" />
39+
}
40+
</span>
41+
}
42+
43+
<_TreeViewNodeContent TNode="TNode" NodeState="@nodeState"
44+
NodeStyling="@NodeStyling"
45+
SelectionMode="@SelectionMode"
46+
SelectedNodeStyling="@SelectedNodeStyling"
47+
DisabledNodeStyling="@DisabledNodeStyling">
48+
@NodeContent( nodeState.Node )
49+
</_TreeViewNodeContent>
50+
51+
@if ( nodeState.Expanded && nodeState.HasChildren )
52+
{
53+
<_TreeViewNode @ref="@nodeState.ViewRef"
54+
NodeStates="@nodeState.Children"
55+
NodeContent="@NodeContent"
56+
GetChildNodes="@GetChildNodes"
57+
GetChildNodesAsync="@GetChildNodesAsync"
58+
ExpandedNodes="@ExpandedNodes"
59+
ExpandedNodesChanged="@ExpandedNodesChanged"
60+
Expanded="@nodeState.Expanded"
61+
AutoExpandAll="@AutoExpandAll"
62+
HasChildNodes="@HasChildNodes"
63+
IsDisabled="@IsDisabled"
64+
HasChildNodesAsync="@HasChildNodesAsync"
65+
NodeStyling="@NodeStyling"
66+
SelectedNodeStyling="@SelectedNodeStyling"
67+
DisabledNodeStyling="@DisabledNodeStyling"
68+
ExpandIconName="@ExpandIconName"
69+
ExpandIconStyle="@ExpandIconStyle"
70+
CollapseIconName="@CollapseIconName"
71+
CollapseIconStyle="@CollapseIconStyle"
72+
SelectionMode="@SelectionMode"
73+
ContextMenu="@ContextMenu"
74+
ContextMenuPreventDefault="@ContextMenuPreventDefault"
75+
Virtualize="@Virtualize" />
76+
}
77+
</div>
78+
};
79+
}

‎Source/Extensions/Blazorise.TreeView/Internal/_TreeViewNode.razor.cs

+10-3
Original file line numberDiff line numberDiff line change
@@ -132,10 +132,10 @@ private async Task LoadChildNodes( TreeViewNodeState<TNode> nodeState )
132132
? GetChildNodes( nodeState.Node )
133133
: null;
134134

135-
NotifyCollectionChangedEventHandler childrenChangedHandler = ( ( sender, e ) =>
135+
NotifyCollectionChangedEventHandler childrenChangedHandler = ( sender, e ) =>
136136
{
137137
OnChildrenChanged( sender, e, nodeState, childNodes );
138-
} );
138+
};
139139

140140
if ( childNodes is INotifyCollectionChanged observableCollection )
141141
{
@@ -171,7 +171,8 @@ private async void OnChildrenChanged( object sender, NotifyCollectionChangedEven
171171
{
172172
await foreach ( var childNodeState in e.NewItems.ToNodeStates( HasChildNodesAsync, DetermineHasChildNodes, ( node ) => ExpandedNodes?.Contains( node ) == true, DetermineIsDisabled ) )
173173
{
174-
nodeState.Children.Add( childNodeState );
174+
if ( !nodeState.Children.Exists( x => x.Node.IsEqual( childNodeState.Node ) ) )
175+
nodeState.Children.Add( childNodeState );
175176
}
176177
}
177178
else if ( e.Action == NotifyCollectionChangedAction.Remove )
@@ -305,6 +306,12 @@ protected virtual Task OnContextMenuHandler( TreeViewNodeState<TNode> nodeState,
305306
/// </summary>
306307
[Parameter] public bool AutoExpandAll { get; set; }
307308

309+
/// <summary>
310+
/// Controls if the child nodes, which are currently not expanded, are visible.<para></para>
311+
/// This is useful for optimizing large TreeViews. See <see href="https://learn.microsoft.com/en-us/aspnet/core/blazor/components/virtualization">Docs for virtualization</see> for more info.
312+
/// </summary>
313+
[Parameter] public bool Virtualize { get; set; }
314+
308315
/// <summary>
309316
/// Defines the name of the treenode expand icon.
310317
/// </summary>

‎Source/Extensions/Blazorise.TreeView/TreeView.razor

+2-25
Original file line numberDiff line numberDiff line change
@@ -19,38 +19,15 @@
1919
SelectedNodeStyling="@SelectedNodeStyling"
2020
DisabledNodeStyling="@DisabledNodeStyling"
2121
SelectionMode="@SelectionMode"
22-
Class="@Class"
23-
Style="@Style"
24-
Float="@Float"
25-
Clearfix="@Clearfix"
26-
Visibility="@Visibility"
27-
Width="@Width"
28-
Height="@Height"
29-
Margin="@Margin"
30-
Padding="@Padding"
31-
Display="@Display"
32-
Border="@Border"
33-
Flex="@Flex"
34-
Position="@Position"
35-
Overflow="@Overflow"
36-
Casing="@Casing"
37-
TextColor="@TextColor"
38-
TextAlignment="@TextAlignment"
39-
TextTransform="@TextTransform"
40-
TextWeight="@TextWeight"
41-
TextOverflow="@TextOverflow"
42-
VerticalAlignment="@VerticalAlignment"
43-
Background="@Background"
44-
Shadow="@Shadow"
45-
Attributes="@Attributes"
4622
ExpandIconName="@ExpandIconName"
4723
ExpandIconStyle="@ExpandIconStyle"
4824
ExpandIconSize="@ExpandIconSize"
4925
CollapseIconName="@CollapseIconName"
5026
CollapseIconStyle="@CollapseIconStyle"
5127
CollapseIconSize="@CollapseIconSize"
5228
ContextMenu="@NodeContextMenu"
53-
ContextMenuPreventDefault="@NodeContextMenuPreventDefault">
29+
ContextMenuPreventDefault="@NodeContextMenuPreventDefault"
30+
Virtualize="@Virtualize">
5431
</_TreeViewNode>
5532
</div>
5633
</CascadingValue>

‎Source/Extensions/Blazorise.TreeView/TreeView.razor.cs

+27
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ public override async Task SetParametersAsync( ParameterView parameters )
3737
bool nodesChanged = parameters.TryGetValue<IEnumerable<TNode>>( nameof( Nodes ), out var paramNodes ) && !paramNodes.AreEqual( Nodes );
3838
bool selectedNodeChanged = parameters.TryGetValue<TNode>( nameof( SelectedNode ), out var paramSelectedNode ) && !paramSelectedNode.IsEqual( treeViewState.SelectedNode );
3939
bool selectedNodesChanged = parameters.TryGetValue<IList<TNode>>( nameof( SelectedNodes ), out var paramSelectedNodes ) && !paramSelectedNodes.AreEqual( treeViewState.SelectedNodes );
40+
bool virtualizeDefined = parameters.TryGetValue<bool>( nameof( Virtualize ), out var paramVirtualize );
41+
bool virtualizeChanged = virtualizeDefined && !paramVirtualize.IsEqual( Virtualize );
42+
43+
bool heightDefined = parameters.TryGetValue<IFluentSizing>( nameof( Height ), out var paramHeight );
44+
bool overflowDefined = parameters.TryGetValue<IFluentOverflow>( nameof( Overflow ), out var paramOverflow );
4045

4146
if ( selectedNodeChanged )
4247
{
@@ -57,6 +62,22 @@ public override async Task SetParametersAsync( ParameterView parameters )
5762

5863
await base.SetParametersAsync( parameters );
5964

65+
if ( Rendered && virtualizeChanged )
66+
{
67+
if ( !heightDefined && paramVirtualize )
68+
Height = Blazorise.Height.Px( 300 );
69+
else
70+
Height = paramHeight;
71+
72+
if ( !overflowDefined && paramVirtualize )
73+
Overflow = Blazorise.Overflow.Auto;
74+
else
75+
Overflow = paramOverflow;
76+
77+
DirtyClasses();
78+
DirtyStyles();
79+
}
80+
6081
if ( nodesChanged )
6182
{
6283
await Reload();
@@ -341,6 +362,12 @@ public TreeViewSelectionMode SelectionMode
341362
/// </summary>
342363
[Parameter] public bool AutoExpandAll { get; set; }
343364

365+
/// <summary>
366+
/// Controls if the child nodes, which are currently not expanded, are visible.
367+
/// This is useful for optimizing large TreeViews. See <see href="https://learn.microsoft.com/en-us/aspnet/core/blazor/components/virtualization">Docs for virtualization</see> for more info.
368+
/// </summary>
369+
[Parameter] public bool Virtualize { get; set; }
370+
344371
/// <summary>
345372
/// List of currently expanded TreeView items (child nodes).
346373
/// </summary>

‎workload-install.ps1

+328
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,328 @@
1+
#
2+
# Copyright (c) Samsung Electronics. All rights reserved.
3+
# Licensed under the MIT license. See LICENSE file in the project root for full license information.
4+
#
5+
6+
<#
7+
.SYNOPSIS
8+
Installs Tizen workload manifest.
9+
.DESCRIPTION
10+
Installs the WorkloadManifest.json and WorkloadManifest.targets files for Tizen to the dotnet sdk.
11+
.PARAMETER Version
12+
Use specific VERSION
13+
.PARAMETER DotnetInstallDir
14+
Dotnet SDK Location installed
15+
#>
16+
17+
[cmdletbinding()]
18+
param(
19+
[Alias('v')][string]$Version="<latest>",
20+
[Alias('d')][string]$DotnetInstallDir="<auto>",
21+
[Alias('t')][string]$DotnetTargetVersionBand="<auto>",
22+
[Alias('u')][switch]$UpdateAllWorkloads
23+
)
24+
25+
Set-StrictMode -Version Latest
26+
$ErrorActionPreference = "Stop"
27+
$ProgressPreference = "SilentlyContinue"
28+
29+
$ManifestBaseName = "Samsung.NET.Sdk.Tizen.Manifest"
30+
$global:FallbackId = ""
31+
32+
$LatestVersionMap = [ordered]@{
33+
"$ManifestBaseName-6.0.100" = "7.0.101";
34+
"$ManifestBaseName-6.0.200" = "7.0.100-preview.13.6";
35+
"$ManifestBaseName-6.0.300" = "8.0.133";
36+
"$ManifestBaseName-6.0.400" = "8.0.140";
37+
"$ManifestBaseName-7.0.100-preview.6" = "7.0.100-preview.6.14";
38+
"$ManifestBaseName-7.0.100-preview.7" = "7.0.100-preview.7.20";
39+
"$ManifestBaseName-7.0.100-rc.1" = "7.0.100-rc.1.22";
40+
"$ManifestBaseName-7.0.100-rc.2" = "7.0.100-rc.2.24";
41+
"$ManifestBaseName-7.0.100" = "7.0.103";
42+
"$ManifestBaseName-7.0.200" = "7.0.105";
43+
"$ManifestBaseName-7.0.300" = "7.0.120";
44+
"$ManifestBaseName-7.0.400" = "8.0.141";
45+
"$ManifestBaseName-8.0.100-alpha.1" = "7.0.104";
46+
"$ManifestBaseName-8.0.100-preview.2" = "7.0.106";
47+
"$ManifestBaseName-8.0.100-preview.3" = "7.0.107";
48+
"$ManifestBaseName-8.0.100-preview.4" = "7.0.108";
49+
"$ManifestBaseName-8.0.100-preview.5" = "7.0.110";
50+
"$ManifestBaseName-8.0.100-preview.6" = "7.0.121";
51+
"$ManifestBaseName-8.0.100-preview.7" = "7.0.122";
52+
"$ManifestBaseName-8.0.100-rc.1" = "7.0.124";
53+
"$ManifestBaseName-8.0.100-rc.2" = "7.0.125";
54+
"$ManifestBaseName-8.0.100-rtm" = "7.0.127";
55+
"$ManifestBaseName-8.0.100" = "8.0.144";
56+
"$ManifestBaseName-8.0.200" = "8.0.145";
57+
"$ManifestBaseName-8.0.300" = "8.0.149";
58+
"$ManifestBaseName-9.0.100-alpha.1" = "8.0.134";
59+
"$ManifestBaseName-9.0.100-preview.1" = "8.0.135";
60+
"$ManifestBaseName-9.0.100-preview.2" = "8.0.137";
61+
}
62+
63+
function New-TemporaryDirectory {
64+
$parent = [System.IO.Path]::GetTempPath()
65+
$name = [System.IO.Path]::GetRandomFileName()
66+
New-Item -ItemType Directory -Path (Join-Path $parent $name)
67+
}
68+
69+
function Ensure-Directory([string]$TestDir) {
70+
Try {
71+
New-Item -ItemType Directory -Path $TestDir -Force -ErrorAction stop | Out-Null
72+
[io.file]::OpenWrite($(Join-Path -Path $TestDir -ChildPath ".test-write-access")).Close()
73+
Remove-Item -Path $(Join-Path -Path $TestDir -ChildPath ".test-write-access") -Force
74+
}
75+
Catch [System.UnauthorizedAccessException] {
76+
Write-Error "No permission to install. Try run with administrator mode."
77+
}
78+
}
79+
80+
function Get-LatestVersion([string]$Id) {
81+
$attempts=3
82+
$sleepInSeconds=3
83+
do
84+
{
85+
try
86+
{
87+
$Response = Invoke-WebRequest -Uri https://api.nuget.org/v3-flatcontainer/$Id/index.json -UseBasicParsing | ConvertFrom-Json
88+
return $Response.versions | Select-Object -Last 1
89+
}
90+
catch {
91+
Write-Host "Id: $Id"
92+
Write-Host "An exception was caught: $($_.Exception.Message)"
93+
}
94+
95+
$attempts--
96+
if ($attempts -gt 0) { Start-Sleep $sleepInSeconds }
97+
} while ($attempts -gt 0)
98+
99+
if ($LatestVersionMap.Contains($Id))
100+
{
101+
Write-Host "Return cached latest version."
102+
return $LatestVersionMap.$Id
103+
}
104+
else
105+
{
106+
$SubStringId = $Id.Substring(0, $ManifestBaseName.Length + 2);
107+
$MatchingFallbackId = @()
108+
$MatchingFallbackVersion = @()
109+
foreach ($key in $LatestVersionMap.Keys) {
110+
if ($key -like "$SubStringId*") {
111+
$MatchingFallbackId += $key
112+
$MatchingFallbackVersion += $LatestVersionMap[$key]
113+
}
114+
}
115+
if ($MatchingFallbackVersion)
116+
{
117+
$global:FallbackId = $MatchingFallbackId[-1]
118+
$FallbackVersion = $MatchingFallbackVersion[-1]
119+
Write-Host "Return fallback version: $FallbackVersion"
120+
return $FallbackVersion
121+
}
122+
}
123+
124+
Write-Error "Wrong Id: $Id"
125+
}
126+
127+
function Get-Package([string]$Id, [string]$Version, [string]$Destination, [string]$FileExt = "nupkg") {
128+
$OutFileName = "$Id.$Version.$FileExt"
129+
$OutFilePath = Join-Path -Path $Destination -ChildPath $OutFileName
130+
131+
if ($Id -match ".net[0-9]+$") {
132+
$Id = $Id -replace (".net[0-9]+", "")
133+
}
134+
135+
Invoke-WebRequest -Uri "https://www.nuget.org/api/v2/package/$Id/$Version" -OutFile $OutFilePath
136+
137+
return $OutFilePath
138+
}
139+
140+
function Install-Pack([string]$Id, [string]$Version, [string]$Kind) {
141+
$TempZipFile = $(Get-Package -Id $Id -Version $Version -Destination $TempDir -FileExt "zip")
142+
$TempUnzipDir = Join-Path -Path $TempDir -ChildPath "unzipped\$Id"
143+
144+
switch ($Kind) {
145+
"manifest" {
146+
Expand-Archive -Path $TempZipFile -DestinationPath $TempUnzipDir
147+
New-Item -Path $TizenManifestDir -ItemType "directory" -Force | Out-Null
148+
Copy-Item -Path "$TempUnzipDir\data\*" -Destination $TizenManifestDir -Force
149+
}
150+
{($_ -eq "sdk") -or ($_ -eq "framework")} {
151+
Expand-Archive -Path $TempZipFile -DestinationPath $TempUnzipDir
152+
if ( ($kind -eq "sdk") -and ($Id -match ".net[0-9]+$")) {
153+
$Id = $Id -replace (".net[0-9]+", "")
154+
}
155+
$TargetDirectory = $(Join-Path -Path $DotnetInstallDir -ChildPath "packs\$Id\$Version")
156+
New-Item -Path $TargetDirectory -ItemType "directory" -Force | Out-Null
157+
Copy-Item -Path "$TempUnzipDir/*" -Destination $TargetDirectory -Recurse -Force
158+
}
159+
"template" {
160+
$TargetFileName = "$Id.$Version.nupkg".ToLower()
161+
$TargetDirectory = $(Join-Path -Path $DotnetInstallDir -ChildPath "template-packs")
162+
New-Item -Path $TargetDirectory -ItemType "directory" -Force | Out-Null
163+
Copy-Item $TempZipFile -Destination $(Join-Path -Path $TargetDirectory -ChildPath "$TargetFileName") -Force
164+
}
165+
}
166+
}
167+
168+
function Remove-Pack([string]$Id, [string]$Version, [string]$Kind) {
169+
switch ($Kind) {
170+
"manifest" {
171+
Remove-Item -Path $TizenManifestDir -Recurse -Force
172+
}
173+
{($_ -eq "sdk") -or ($_ -eq "framework")} {
174+
$TargetDirectory = $(Join-Path -Path $DotnetInstallDir -ChildPath "packs\$Id\$Version")
175+
Remove-Item -Path $TargetDirectory -Recurse -Force
176+
}
177+
"template" {
178+
$TargetFileName = "$Id.$Version.nupkg".ToLower();
179+
Remove-Item -Path $(Join-Path -Path $DotnetInstallDir -ChildPath "template-packs\$TargetFileName") -Force
180+
}
181+
}
182+
}
183+
184+
function Install-TizenWorkload([string]$DotnetVersion)
185+
{
186+
$VersionSplitSymbol = '.'
187+
$SplitVersion = $DotnetVersion.Split($VersionSplitSymbol)
188+
189+
$CurrentDotnetVersion = [Version]"$($SplitVersion[0]).$($SplitVersion[1])"
190+
$DotnetVersionBand = $SplitVersion[0] + $VersionSplitSymbol + $SplitVersion[1] + $VersionSplitSymbol + $SplitVersion[2][0] + "00"
191+
$ManifestName = "$ManifestBaseName-$DotnetVersionBand"
192+
193+
if ($DotnetTargetVersionBand -eq "<auto>" -or $UpdateAllWorkloads.IsPresent) {
194+
if ($CurrentDotnetVersion -ge "7.0")
195+
{
196+
$IsPreviewVersion = $DotnetVersion.Contains("-preview") -or $DotnetVersion.Contains("-rc") -or $DotnetVersion.Contains("-alpha")
197+
if ($IsPreviewVersion -and ($SplitVersion.Count -ge 4)) {
198+
$DotnetTargetVersionBand = $DotnetVersionBand + $SplitVersion[2].SubString(3) + $VersionSplitSymbol + $($SplitVersion[3])
199+
$ManifestName = "$ManifestBaseName-$DotnetTargetVersionBand"
200+
}
201+
elseif ($DotnetVersion.Contains("-rtm") -and ($SplitVersion.Count -ge 3)) {
202+
$DotnetTargetVersionBand = $DotnetVersionBand + $SplitVersion[2].SubString(3)
203+
$ManifestName = "$ManifestBaseName-$DotnetTargetVersionBand"
204+
}
205+
else {
206+
$DotnetTargetVersionBand = $DotnetVersionBand
207+
}
208+
}
209+
else {
210+
$DotnetTargetVersionBand = $DotnetVersionBand
211+
}
212+
}
213+
214+
# Check latest version of manifest.
215+
if ($Version -eq "<latest>" -or $UpdateAllWorkloads.IsPresent) {
216+
$Version = Get-LatestVersion -Id $ManifestName
217+
}
218+
219+
# Check workload manifest directory.
220+
$ManifestDir = Join-Path -Path $DotnetInstallDir -ChildPath "sdk-manifests" | Join-Path -ChildPath $DotnetTargetVersionBand
221+
$TizenManifestDir = Join-Path -Path $ManifestDir -ChildPath "samsung.net.sdk.tizen"
222+
$TizenManifestFile = Join-Path -Path $TizenManifestDir -ChildPath "WorkloadManifest.json"
223+
224+
# Check and remove already installed old version.
225+
if (Test-Path $TizenManifestFile) {
226+
$ManifestJson = $(Get-Content $TizenManifestFile | ConvertFrom-Json)
227+
$OldVersion = $ManifestJson.version
228+
if ($OldVersion -eq $Version) {
229+
$DotnetWorkloadList = Invoke-Expression "& '$DotnetCommand' workload list | Select-String -Pattern '^tizen'"
230+
if ($DotnetWorkloadList)
231+
{
232+
Write-Host "Tizen Workload $Version version is already installed."
233+
Continue
234+
}
235+
}
236+
237+
Ensure-Directory $ManifestDir
238+
Write-Host "Removing $ManifestName/$OldVersion from $ManifestDir..."
239+
Remove-Pack -Id $ManifestName -Version $OldVersion -Kind "manifest"
240+
$ManifestJson.packs.PSObject.Properties | ForEach-Object {
241+
Write-Host "Removing $($_.Name)/$($_.Value.version)..."
242+
Remove-Pack -Id $_.Name -Version $_.Value.version -Kind $_.Value.kind
243+
}
244+
}
245+
246+
Ensure-Directory $ManifestDir
247+
$TempDir = $(New-TemporaryDirectory)
248+
249+
# Install workload manifest.
250+
Write-Host "Installing $ManifestName/$Version to $ManifestDir..."
251+
if ($global:FallbackId) {
252+
Install-Pack -Id $global:FallbackId -Version $Version -Kind "manifest"
253+
} else {
254+
Install-Pack -Id $ManifestName -Version $Version -Kind "manifest"
255+
}
256+
257+
# Download and install workload packs.
258+
$NewManifestJson = $(Get-Content $TizenManifestFile | ConvertFrom-Json)
259+
$NewManifestJson.packs.PSObject.Properties | ForEach-Object {
260+
Write-Host "Installing $($_.Name)/$($_.Value.version)..."
261+
Install-Pack -Id $_.Name -Version $_.Value.version -Kind $_.Value.kind
262+
}
263+
264+
# Add tizen to the installed workload metadata.
265+
# Featured version band for metadata does NOT include any preview specifier.
266+
# https://github.com/dotnet/sdk/blob/main/documentation/general/workloads/user-local-workloads.md
267+
New-Item -Path $(Join-Path -Path $DotnetInstallDir -ChildPath "metadata\workloads\$DotnetVersionBand\InstalledWorkloads\tizen") -Force | Out-Null
268+
if (Test-Path $(Join-Path -Path $DotnetInstallDir -ChildPath "metadata\workloads\$DotnetVersionBand\InstallerType\msi")) {
269+
New-Item -Path "HKLM:\SOFTWARE\Microsoft\dotnet\InstalledWorkloads\Standalone\x64\$DotnetTargetVersionBand\tizen" -Force | Out-Null
270+
}
271+
272+
# Clean up
273+
Remove-Item -Path $TempDir -Force -Recurse
274+
275+
Write-Host "Done installing Tizen workload $Version"
276+
}
277+
278+
# Check dotnet install directory.
279+
if ($DotnetInstallDir -eq "<auto>") {
280+
if ($Env:DOTNET_ROOT -And $(Test-Path "$Env:DOTNET_ROOT")) {
281+
$DotnetInstallDir = $Env:DOTNET_ROOT
282+
} else {
283+
$DotnetInstallDir = Join-Path -Path $Env:Programfiles -ChildPath "dotnet"
284+
}
285+
}
286+
if (-Not $(Test-Path "$DotnetInstallDir")) {
287+
Write-Error "No installed dotnet '$DotnetInstallDir'."
288+
}
289+
290+
# Check installed dotnet version
291+
$DotnetCommand = "$DotnetInstallDir\dotnet"
292+
if (Get-Command $DotnetCommand -ErrorAction SilentlyContinue)
293+
{
294+
if ($UpdateAllWorkloads.IsPresent)
295+
{
296+
$InstalledDotnetSdks = Invoke-Expression "& '$DotnetCommand' --list-sdks | Select-String -Pattern '^6|^7'" | ForEach-Object {$_ -replace (" \[.*","")}
297+
}
298+
else
299+
{
300+
$InstalledDotnetSdks = Invoke-Expression "& '$DotnetCommand' --version"
301+
}
302+
}
303+
else
304+
{
305+
Write-Error "'$DotnetCommand' occurs an error."
306+
}
307+
308+
if (-Not $InstalledDotnetSdks)
309+
{
310+
Write-Host "`n.NET SDK version 6 or later is required to install Tizen Workload."
311+
}
312+
else
313+
{
314+
foreach ($DotnetSdk in $InstalledDotnetSdks)
315+
{
316+
try {
317+
Write-Host "`nCheck Tizen Workload for sdk $DotnetSdk"
318+
Install-TizenWorkload -DotnetVersion $DotnetSdk
319+
}
320+
catch {
321+
Write-Host "Failed to install Tizen Workload for sdk $DotnetSdk"
322+
Write-Host "$_"
323+
Continue
324+
}
325+
}
326+
}
327+
328+
Write-Host "`nDone"

0 commit comments

Comments
 (0)
Please sign in to comment.