Hello,
I have a TelerikTreeView with about 2000 items bound to an ObservableCollection (Data). I use SelectedItems, SelectedItemsChanged, SelectionMode multitple and @bind-ExpandedItems.
When my users manually expand the nodes of the treeview, everything is fine and the operation of the treeview works smoothly. Loading UI components based on user selection takes less than 50ms.
As soon as I set the "ExpandedItems" collection from code (according to your treeview demo), the whole treeview operation becomes painfully slow with waiting times of 2 to 4 SECONDS for selection and expansion. Already expanding from code takes several seconds.
As soon as I reset the ExpandedItems from code and collapse the complete tree, the treeview is usable again without delays.
If the application expands TreeView items programmatically, and then the user tries to select multiple items, an exception will occur.
The issue is a regression that occurred in version 3.0.0. A possible workaround is to Rebind()
the TreeView with a small delay after programmatic item expansion.
<TelerikTreeView @ref="@TreeViewRef"
Data="@FlatData"
@bind-ExpandedItems="@ExpandedItems"
SelectionMode="@TreeViewSelectionMode.Multiple"
SelectedItems="@SelectedItems"
SelectedItemsChanged="@((IEnumerable<object> items) => SelectedItemsHandler(items))" />
<TelerikButton OnClick="@ExpandAll">Expand All</TelerikButton>
<TelerikButton OnClick="@CollapseAll">Collapse All</TelerikButton>
@code {
public TelerikTreeView TreeViewRef { get; set; }
public IEnumerable<TreeItem> FlatData { get; set; }
public IEnumerable<object> SelectedItems { get; set; } = new List<object>();
public IEnumerable<object> ExpandedItems { get; set; } = new List<object>();
async Task ExpandAll()
{
ExpandedItems = FlatData.Where(x => x.HasChildren == true);
await Task.Delay(1);
TreeViewRef.Rebind();
}
void CollapseAll()
{
ExpandedItems = new List<object>();
SelectedItems = new List<object>();
}
void SelectedItemsHandler(IEnumerable<object> items)
{
SelectedItems = items;
}
protected override async void OnInitialized()
{
FlatData = LoadFlat();
}
int TreeLevels { get; set; } = 3;
int ItemsPerLevel { get; set; } = 3;
int IdCounter { get; set; } = 1;
List<TreeItem> LoadFlat()
{
List<TreeItem> items = new List<TreeItem>();
PopulateTreeItems(items, null, 1);
return items;
}
void PopulateTreeItems(List<TreeItem> items, int? parentId, int level)
{
for (int i = 1; i <= ItemsPerLevel; i++)
{
var itemId = IdCounter++;
items.Add(new TreeItem()
{
Id = itemId,
Text = $"Level {level} Item {i} Id {itemId}",
ParentId = parentId,
HasChildren = level < TreeLevels
});
if (level < TreeLevels)
{
PopulateTreeItems(items, itemId, level + 1);
}
}
}
public class TreeItem
{
public int Id { get; set; }
public string Text { get; set; }
public int? ParentId { get; set; }
public bool HasChildren { get; set; }
}
}
Per WAI-ARIA 1.2, the "aria-level" attribute, if present, "is an integer greater than or equal to 1".
However, Blazor TreeView uses 0-indexing for the attribute which means that the root item has an invalid (and ignored) aria-level property. This is problematic because the browser will infer the level from the DOM nesting for the root item, but then use aria-level for all other sub-trees, leading to inconsistent levels (in the demo, the level goes 2 > 1 > 2).
Checking a checkbox of a on demand-loaded child then collapsing and reopening its parent makes the checkbox disappear. However, it is still checked in the CheckedItems collection, but just not in the UI. See this REPL example. Steps...
1. Expand a top level item
2. Check its child checkbox
3. Collapse the top level item
4. Expand it again
Result: checkbox gone (in the UI)
After upgrade to 3.0, when expanding a checked tree node, the child items are not checked. Also, if the child items are checked, but the parent is collapsed and expanded again, the checked children are unchecked.
This can be replicated on the online demo.
The TreeView has CheckBoxMode="@TreeViewCheckBoxMode.Multiple" and CheckParents="true". Only some checkboxes are checked and there are parent checkboxes in indeterminate state.
When I try to clear all checked checkboxes, the indeterminate checkboxes are not cleared and maintain their state.
ADMIT EDIT:
Initially, this bug report was about unchecking all child items. However, it appears that the opposite behavior exists too - if all children are checked programmatically, the parent checkbox will show as indeterminate. In this case, check the parent explicitly as well.
Stack trace:
crit: Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]
Unhandled exception rendering component: Unknown edit type: 0
Error: Unknown edit type: 0
at e.applyEdits (https://localhost:44363/_framework/blazor.webassembly.js:1:15008)
at e.updateComponent (https://localhost:44363/_framework/blazor.webassembly.js:1:12880)
at Object.t.renderBatch (https://localhost:44363/_framework/blazor.webassembly.js:1:1704)
at Object.window.Blazor._internal.renderBatch (https://localhost:44363/_framework/blazor.webassembly.js:1:34784)
at _mono_wasm_invoke_js_unmarshalled (https://localhost:44363/_framework/wasm/dotnet.3.2.0.js:1:172099)
at wasm_invoke_iiiiii (<anonymous>:wasm-function[3160]:0x9b33d)
at icall_trampoline_dispatch (<anonymous>:wasm-function[5777]:0xfe711)
at mono_wasm_interp_to_native_trampoline (<anonymous>:wasm-function[4607]:0xca81d)
at ves_pinvoke_method (<anonymous>:wasm-function[3209]:0x9cd40)
at interp_exec_method (<anonymous>:wasm-function[1120]:0x2598d)
Microsoft.JSInterop.JSException: Unknown edit type: 0
Error: Unknown edit type: 0
at e.applyEdits (https://localhost:44363/_framework/blazor.webassembly.js:1:15008)
at e.updateComponent (https://localhost:44363/_framework/blazor.webassembly.js:1:12880)
at Object.t.renderBatch (https://localhost:44363/_framework/blazor.webassembly.js:1:1704)
at Object.window.Blazor._internal.renderBatch (https://localhost:44363/_framework/blazor.webassembly.js:1:34784)
at _mono_wasm_invoke_js_unmarshalled (https://localhost:44363/_framework/wasm/dotnet.3.2.0.js:1:172099)
at wasm_invoke_iiiiii (<anonymous>:wasm-function[3160]:0x9b33d)
at icall_trampoline_dispatch (<anonymous>:wasm-function[5777]:0xfe711)
at mono_wasm_interp_to_native_trampoline (<anonymous>:wasm-function[4607]:0xca81d)
at ves_pinvoke_method (<anonymous>:wasm-function[3209]:0x9cd40)
at interp_exec_method (<anonymous>:wasm-function[1120]:0x2598d)
at Microsoft.JSInterop.WebAssembly.WebAssemblyJSRuntime.InvokeUnmarshalled[T0,T1,T2,TResult] (System.String identifier, T0 arg0, T1 arg1, T2 arg2) <0x3ae01e8 + 0x00046> in <filename unknown>:0
at Microsoft.JSInterop.WebAssembly.WebAssemblyJSRuntime.InvokeUnmarshalled[T0,T1,TResult] (System.String identifier, T0 arg0, T1 arg1) <0x3ae0108 + 0x00014> in <filename unknown>:0
at Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer.UpdateDisplayAsync (Microsoft.AspNetCore.Components.RenderTree.RenderBatch& batch) <0x3ae0010 + 0x0001e> in <filename unknown>:0
at Microsoft.AspNetCore.Components.RenderTree.Renderer.ProcessRenderQueue () <0x387e448 + 0x000f2> in <filename unknown>:0
If i collapse/expand any item of treeview then each item of collapsed/expanded branch will be rendered twice.
Clicking in or away from the treeview re-renders all nodes too.
The issue manifests both when
Happens in 2.14.1 first.
*** Thread created by admin on customer behalf ***
Hello,
I am trying to fill the TreeView using an async method. The first level is loaded an expanded. Subsequent levels are not. Or so it seams. They are loaded, but when expanding a node it expands and immediately collapses. Expanding a second time reveals the nodes loaded earlier.
I included a demo project. Open http://localhost:{port}/tree.
Best regards,
Marcel Gelijk
Repro plus workaround (already in):
<button @onclick="@SwitchDataSource">switch to other data source</button>
<TelerikTreeView Data="@FlatData">
<TreeViewBindings>
<TreeViewBinding IdField="Id" ParentIdField="ParentIdValue" ExpandedField="Expanded" TextField="Text"
HasChildrenField="HasChildren" IconField="Icon">
<ItemTemplate>
@{
TreeItem currProduct = context as TreeItem;
@(currProduct.Text)
}
</ItemTemplate>
</TreeViewBinding>
</TreeViewBindings>
</TelerikTreeView>
@code {
public class TreeItem
{
public int Id { get; set; }
public string Text { get; set; }
public int? ParentIdValue { get; set; }
public bool HasChildren { get; set; }
public string Icon { get; set; }
public bool Expanded { get; set; }
}
async void SwitchDataSource()
{
//workaround - remove it to see the actual error
foreach (TreeItem item in FlatData)
{
item.Expanded = false;
}
StateHasChanged();
await Task.Delay(300);//awaits the animation that will hide the nodes we just collapsed so their elements get properly disposed
//change data
LoadSecondDataSource();
//update UI
StateHasChanged();
}
public IEnumerable<TreeItem> FlatData { get; set; }
protected override void OnInitialized()
{
LoadFlatData();
}
private void LoadFlatData()
{
List<TreeItem> items = new List<TreeItem>();
items.Add(new TreeItem()
{
Id = 1,
Text = "Project",
ParentIdValue = null,
HasChildren = true,
Icon = "folder",
Expanded = true
});
items.Add(new TreeItem()
{
Id = 2,
Text = "Design",
ParentIdValue = 1,
HasChildren = true,
Icon = "brush",
Expanded = true
});
items.Add(new TreeItem()
{
Id = 3,
Text = "Implementation",
ParentIdValue = 1,
HasChildren = true,
Icon = "folder",
Expanded = true
});
items.Add(new TreeItem()
{
Id = 4,
Text = "site.psd",
ParentIdValue = 2,
HasChildren = false,
Icon = "psd",
Expanded = true
});
items.Add(new TreeItem()
{
Id = 5,
Text = "index.js",
ParentIdValue = 3,
HasChildren = false,
Icon = "js"
});
items.Add(new TreeItem()
{
Id = 6,
Text = "index.html",
ParentIdValue = 3,
HasChildren = false,
Icon = "html"
});
items.Add(new TreeItem()
{
Id = 7,
Text = "styles.css",
ParentIdValue = 3,
HasChildren = false,
Icon = "css"
});
FlatData = items;
}
void LoadSecondDataSource()
{
List<TreeItem> items = new List<TreeItem>();
items.Add(new TreeItem()
{
Id = 1,
Text = "1",
ParentIdValue = null,
HasChildren = true
});
items.Add(new TreeItem()
{
Id = 2,
Text = "1 1",
ParentIdValue = 1,
HasChildren = true
});
//if you add this, there is no error because the levels match
//if the new data source does not have the same number of (maybe expanded) levels
//you will get an error while disposing those levels
//items.Add(new TreeItem()
//{
// Id = 3,
// Text = "1 1 1",
// ParentIdValue = 2
//});
FlatData = items;
}
}
@
using
Telerik.Blazor.Components.TreeView
<button
class
=
"btn btn-primary"
@onclick=
"@(() => ChangeTreeData(false))"
>Change tree data</button>
<TelerikTreeView Data=
"@FlatData"
>
<TelerikTreeViewBindings>
<TelerikTreeViewBinding ParentIdField=
"Parent"
ExpandedField=
"IsExpanded"
></TelerikTreeViewBinding>
</TelerikTreeViewBindings>
</TelerikTreeView>
@code {
public
List<TreeItem> FlatData {
get
;
set
; } =
new
List<TreeItem>();
public
class
TreeItem
//most fields use the default names and will bind automatically in this example
{
public
int
Id {
get
;
set
; }
public
string
Text {
get
;
set
; }
public
int
? Parent {
get
;
set
; }
//this is a non-default field name
public
bool
HasChildren {
get
;
set
; }
public
bool
IsExpanded {
get
;
set
; }
//this is a non-default field name
}
protected
override
void
OnInit()
{
ChangeTreeData(
true
);
}
int
currIndex {
get
;
set
; } = 0;
void
ChangeTreeData(
bool
expandItems)
{
currIndex++;
FlatData.Clear();
List<TreeItem> data =
new
List<TreeItem>();
data.Add(
new
TreeItem { Id = currIndex, HasChildren =
true
, IsExpanded = expandItems, Parent =
null
, Text = $
"root {currIndex}"
});
data.Add(
new
TreeItem { Id = currIndex + 1, HasChildren =
false
, IsExpanded = expandItems, Parent = currIndex, Text = $
"root {currIndex}: child one"
});
FlatData.AddRange(data);
}
}
When using load-on-demand, you may not know how much data there is and how many levels deep it will go. You need the OnExpand event to fire for each node so you can call your services and retrieve data.
Unfortunately, it does not fire for the second level of nodes, so you cannot load data for them.