Skip to content

Commit f75d23d

Browse files
ef
1 parent 8c32947 commit f75d23d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+1546
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<Project Sdk="Microsoft.NET.Sdk.Razor">
2+
<PropertyGroup>
3+
<TargetFramework>net6.0</TargetFramework>
4+
</PropertyGroup>
5+
<ItemGroup>
6+
<SupportedPlatform Include="browser" />
7+
</ItemGroup>
8+
<ItemGroup>
9+
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="5.0.7" />
10+
</ItemGroup>
11+
</Project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
@using Microsoft.AspNetCore.Components.Web
2+
@using Microsoft.JSInterop
3+
4+
@implements IAsyncDisposable
5+
@inject IJSRuntime JSRuntime
6+
7+
<div @ref="treeListElement"></div>
8+
9+
@code {
10+
private ElementReference treeListElement;
11+
private DotNetObjectReference<TreeList> dotNetHelper;
12+
private IJSObjectReference treeListModule;
13+
private IJSObjectReference treeList;
14+
15+
[Parameter]
16+
public Func<string, Task<IEnumerable<object>>> GetDataAsync { get; set; }
17+
[Parameter]
18+
public string[] FieldNames { get; set; }
19+
[Parameter]
20+
public Func<object, string, string> GetFieldDisplayText { get; set; }
21+
[Parameter]
22+
public Func<object, string> GetKey { get; set; }
23+
[Parameter]
24+
public Func<object, bool> HasChildren { get; set; }
25+
[Parameter]
26+
public EventCallback<string> RowClick { get; set; }
27+
[Parameter]
28+
public EventCallback<string[]> SelectionChanged { get; set; }
29+
30+
protected override void OnParametersSet()
31+
{
32+
base.OnParametersSet();
33+
List<string> missingParameters = new List<string>();
34+
if (GetDataAsync is null) missingParameters.Add(nameof(GetDataAsync));
35+
if (FieldNames is null) missingParameters.Add(nameof(FieldNames));
36+
if (GetFieldDisplayText is null) missingParameters.Add(nameof(GetFieldDisplayText));
37+
if (GetKey is null) missingParameters.Add(nameof(GetKey));
38+
if (HasChildren is null) missingParameters.Add(nameof(HasChildren));
39+
if (missingParameters.Count > 0)
40+
{
41+
throw new ArgumentException($"Please declare the following parameter(s): {string.Join(',', missingParameters)}");
42+
}
43+
}
44+
protected override async Task OnAfterRenderAsync(bool firstRender)
45+
{
46+
if (firstRender)
47+
{
48+
dotNetHelper = DotNetObjectReference.Create(this);
49+
treeListModule = await JSRuntime.InvokeAsync<IJSObjectReference>("import", "./_content/BlazorComponents/treeListModule.js");
50+
treeList = await treeListModule.InvokeAsync<IJSObjectReference>("addTreeListToElement", treeListElement, FieldNames, dotNetHelper);
51+
}
52+
}
53+
protected override bool ShouldRender() => false;
54+
public async Task Refresh() => await treeListModule.InvokeVoidAsync("refresh", treeListElement);
55+
[JSInvokable]
56+
public async Task<List<Dictionary<string, object>>> OnGetDataAsync(string parentKey)
57+
{
58+
List<Dictionary<string, object>> dictionaries = new List<Dictionary<string, object>>();
59+
var data = await GetDataAsync(parentKey);
60+
foreach (var item in data)
61+
{
62+
var dictionary = FieldNames.ToDictionary(field => field, field => (object)GetFieldDisplayText(item, field));
63+
dictionary.Add("__parentKey", parentKey);
64+
dictionary.Add("__key", GetKey(item));
65+
dictionary.Add("__hasChildren", HasChildren(item));
66+
dictionaries.Add(dictionary);
67+
}
68+
return dictionaries;
69+
}
70+
[JSInvokable]
71+
public async Task OnRowClick(string key) => await RowClick.InvokeAsync(key);
72+
[JSInvokable]
73+
public async Task OnSelectionChanged(string[] keys) => await SelectionChanged.InvokeAsync(keys);
74+
async ValueTask IAsyncDisposable.DisposeAsync()
75+
{
76+
try
77+
{
78+
if (treeListModule is not null) await treeListModule.InvokeVoidAsync("dispose", treeListElement);
79+
}
80+
catch (Exception ex) when (ex.GetType().Name == "JSDisconnectedException")
81+
{
82+
//https://github.com/dotnet/aspnetcore/issues/33336#issuecomment-862425579
83+
}
84+
85+
if (treeList is not null) await treeList.DisposeAsync();
86+
if (treeListModule is not null) await treeListModule.DisposeAsync();
87+
dotNetHelper?.Dispose();
88+
}
89+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
export function addTreeListToElement(element, fieldNames, dotNetHelper) {
2+
const keyExpression = "__key";
3+
const parentKeyExpression = "__parentKey";
4+
const hasChildrenExpression = "__hasChildren";
5+
var treeList = $(element).dxTreeList({
6+
keyExpr: keyExpression,
7+
rootValue: null,
8+
parentIdExpr: parentKeyExpression,
9+
hasItemsExpr: hasChildrenExpression,
10+
columns: fieldNames,
11+
dataSource: {
12+
key: keyExpression,
13+
load: function (options) {
14+
var parentKeys = null;
15+
if (options.parentIds) {
16+
parentKeys = options.parentIds[0];
17+
}
18+
return dotNetHelper.invokeMethodAsync('OnGetDataAsync', parentKeys);
19+
}
20+
},
21+
remoteOperations: {
22+
filtering: true
23+
},
24+
selection: {
25+
mode: "multiple"
26+
},
27+
onRowClick: function (e) {
28+
if (!e.event.target.parentElement.classList.contains("dx-treelist-expanded") && !e.event.target.parentElement.classList.contains("dx-treelist-collapsed")) {
29+
dotNetHelper.invokeMethodAsync('OnRowClick', e.key);
30+
}
31+
},
32+
onSelectionChanged: function (e) {
33+
dotNetHelper.invokeMethodAsync('OnSelectionChanged', e.selectedRowKeys);
34+
},
35+
columnAutoWidth: true,
36+
wordWrapEnabled: true,
37+
showRowLines: true,
38+
showBorders: true
39+
}).dxTreeList('instance');
40+
return treeList;
41+
}
42+
export function refresh(element) {
43+
$(element).dxTreeList('instance').option("expandedRowKeys",[]);
44+
$(element).dxTreeList('instance').refresh();
45+
}
46+
export function dispose(element) {
47+
if (element) {
48+
$(element).dxTreeList('dispose');
49+
}
50+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<Router AppAssembly="@typeof(Program).Assembly" AdditionalAssemblies="new[] { typeof(DevExpress.ExpressApp.Blazor.BlazorApplication).Assembly }">
2+
<Found Context="routeData">
3+
<RouteView RouteData="@routeData" />
4+
</Found>
5+
<NotFound>
6+
<LayoutView>
7+
<PageTitle>Not found</PageTitle>
8+
<p role="alert">Sorry, there's nothing at this address.</p>
9+
</LayoutView>
10+
</NotFound>
11+
</Router>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
using DevExpress.ExpressApp;
2+
using DevExpress.ExpressApp.ApplicationBuilder;
3+
using DevExpress.ExpressApp.Blazor;
4+
using DevExpress.ExpressApp.SystemModule;
5+
using TreeListDemoEF.Module.BusinessObjects;
6+
using Microsoft.EntityFrameworkCore;
7+
using DevExpress.ExpressApp.EFCore;
8+
9+
namespace TreeListDemoEF.Blazor.Server;
10+
11+
public class TreeListDemoEFBlazorApplication : BlazorApplication {
12+
public TreeListDemoEFBlazorApplication() {
13+
ApplicationName = "TreeListDemoEF";
14+
CheckCompatibilityType = DevExpress.ExpressApp.CheckCompatibilityType.DatabaseSchema;
15+
DatabaseVersionMismatch += TreeListDemoEFBlazorApplication_DatabaseVersionMismatch;
16+
}
17+
protected override void OnSetupStarted() {
18+
base.OnSetupStarted();
19+
#if DEBUG
20+
if(System.Diagnostics.Debugger.IsAttached && CheckCompatibilityType == CheckCompatibilityType.DatabaseSchema) {
21+
DatabaseUpdateMode = DatabaseUpdateMode.UpdateDatabaseAlways;
22+
}
23+
#endif
24+
}
25+
private void TreeListDemoEFBlazorApplication_DatabaseVersionMismatch(object sender, DatabaseVersionMismatchEventArgs e) {
26+
#if EASYTEST
27+
e.Updater.Update();
28+
e.Handled = true;
29+
#else
30+
if(System.Diagnostics.Debugger.IsAttached) {
31+
e.Updater.Update();
32+
e.Handled = true;
33+
}
34+
else {
35+
string message = "The application cannot connect to the specified database, " +
36+
"because the database doesn't exist, its version is older " +
37+
"than that of the application or its schema does not match " +
38+
"the ORM data model structure. To avoid this error, use one " +
39+
"of the solutions from the https://www.devexpress.com/kb=T367835 KB Article.";
40+
41+
if(e.CompatibilityError != null && e.CompatibilityError.Exception != null) {
42+
message += "\r\n\r\nInner exception: " + e.CompatibilityError.Exception.Message;
43+
}
44+
throw new InvalidOperationException(message);
45+
}
46+
#endif
47+
}
48+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using System.ComponentModel;
2+
using DevExpress.ExpressApp;
3+
using DevExpress.ExpressApp.DC;
4+
using DevExpress.ExpressApp.Model;
5+
using DevExpress.ExpressApp.Editors;
6+
using DevExpress.ExpressApp.Actions;
7+
using DevExpress.ExpressApp.Updating;
8+
using DevExpress.ExpressApp.Model.Core;
9+
using DevExpress.ExpressApp.Model.DomainLogics;
10+
using DevExpress.ExpressApp.Model.NodeGenerators;
11+
using DevExpress.Persistent.BaseImpl.EF;
12+
using DevExpress.ExpressApp.Utils;
13+
using TreeListDemoEF.Blazor.Server.Editors.TreeListEditor;
14+
15+
namespace TreeListDemoEF.Blazor.Server;
16+
17+
[ToolboxItemFilter("Xaf.Platform.Blazor")]
18+
// For more typical usage scenarios, be sure to check out https://docs.devexpress.com/eXpressAppFramework/DevExpress.ExpressApp.ModuleBase.
19+
public sealed class TreeListDemoEFBlazorModule : ModuleBase {
20+
public TreeListDemoEFBlazorModule() {
21+
DataAccessModeHelper.RegisterEditorSupportedModes(typeof(TreeListEditor), new[] { CollectionSourceDataAccessMode.Client });
22+
}
23+
public override IEnumerable<ModuleUpdater> GetModuleUpdaters(IObjectSpace objectSpace, Version versionFromDB) {
24+
return ModuleUpdater.EmptyModuleUpdaters;
25+
}
26+
public override void Setup(XafApplication application) {
27+
base.Setup(application);
28+
}
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
Folder Description
2+
3+
The "Controllers" project folder is intended for storing Blazor-specific Controller classes
4+
that can change the default XAF application flow and add new features.
5+
6+
7+
Relevant Documentation
8+
9+
Controllers and Actions
10+
https://docs.devexpress.com/eXpressAppFramework/112623
11+
12+
Implement Custom Controllers
13+
https://docs.devexpress.com/eXpressAppFramework/112621
14+
15+
Define the Scope of Controllers and Actions
16+
https://docs.devexpress.com/eXpressAppFramework/113103
17+
18+
Ways to Show a View
19+
https://docs.devexpress.com/eXpressAppFramework/112803
20+
21+
Ways to Implement Business Logic
22+
https://docs.devexpress.com/eXpressAppFramework/113710
23+
24+
Debugging, Unit and Functional Testing
25+
https://docs.devexpress.com/eXpressAppFramework/112572
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
Folder Description
2+
3+
This project folder is intended for storing custom Blazor List Editors,
4+
Property Editors and View Items.
5+
6+
7+
Relevant Documentation
8+
9+
Using a Custom Control that is not Integrated by Default
10+
https://docs.devexpress.com/eXpressAppFramework/113610
11+
12+
Ways to Access UI Elements and Their Controls
13+
https://docs.devexpress.com/eXpressAppFramework/120092
14+
15+
Views
16+
https://docs.devexpress.com/eXpressAppFramework/112611
17+
18+
List Editors
19+
https://docs.devexpress.com/eXpressAppFramework/113189
20+
21+
View Items
22+
https://docs.devexpress.com/eXpressAppFramework/112612
23+
24+
Debugging, Unit and Functional Testing
25+
https://docs.devexpress.com/eXpressAppFramework/112572
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
using DevExpress.ExpressApp;
2+
using DevExpress.ExpressApp.Blazor;
3+
using DevExpress.ExpressApp.Blazor.Components;
4+
using DevExpress.ExpressApp.Editors;
5+
using DevExpress.ExpressApp.Model;
6+
using DevExpress.Persistent.Base.General;
7+
using Microsoft.AspNetCore.Components;
8+
using System.Collections;
9+
using System.ComponentModel;
10+
11+
namespace TreeListDemoEF.Blazor.Server.Editors.TreeListEditor {
12+
[ListEditor(typeof(ITreeNode))]
13+
public class TreeListEditor : ListEditor, IComplexListEditor {
14+
public class TreeListHolder : IComponentContentHolder {
15+
private RenderFragment componentContent;
16+
public TreeListHolder(TreeListModel componentModel) {
17+
ComponentModel = componentModel ?? throw new ArgumentNullException(nameof(componentModel));
18+
}
19+
private RenderFragment CreateComponent() => ComponentModelObserver.Create(ComponentModel, TreeListRenderer.Create(ComponentModel));
20+
public TreeListModel ComponentModel { get; }
21+
RenderFragment IComponentContentHolder.ComponentContent => componentContent ??= CreateComponent();
22+
}
23+
private ITreeNode[] selectedObjects = Array.Empty<ITreeNode>();
24+
private IObjectSpace objectSpace;
25+
private IEnumerable<object> data;
26+
public TreeListEditor(IModelListView model) : base(model) { }
27+
void IComplexListEditor.Setup(CollectionSourceBase collectionSource, XafApplication application) {
28+
objectSpace = collectionSource.ObjectSpace;
29+
}
30+
protected override object CreateControlsCore() => new TreeListHolder(new TreeListModel());
31+
protected override void AssignDataSourceToControl(object dataSource) {
32+
if (Control is TreeListHolder holder) {
33+
if (data is IBindingList bindingList) {
34+
bindingList.ListChanged -= BindingList_ListChanged;
35+
}
36+
data = (dataSource as IEnumerable)?.Cast<ITreeNode>();
37+
if (dataSource is IBindingList newBindingList) {
38+
newBindingList.ListChanged += BindingList_ListChanged;
39+
}
40+
}
41+
}
42+
protected override void OnControlsCreated() {
43+
if (Control is TreeListHolder holder) {
44+
holder.ComponentModel.GetDataAsync = GetDataAsync;
45+
holder.ComponentModel.FieldNames = new string[] { nameof(ITreeNode.Name) };
46+
holder.ComponentModel.GetFieldDisplayText = GetFieldDisplayText;
47+
holder.ComponentModel.GetKey = GetKey;
48+
holder.ComponentModel.HasChildren = HasChildren;
49+
holder.ComponentModel.RowClick += ComponentModel_RowClick;
50+
holder.ComponentModel.SelectionChanged += ComponentModel_SelectionChanged;
51+
}
52+
base.OnControlsCreated();
53+
}
54+
55+
private Task<IEnumerable<object>> GetDataAsync(string parentKey) {
56+
bool IsRoot(object obj) {
57+
return obj is ITreeNode node && (node.Parent == null || !data.Contains(node.Parent));
58+
}
59+
if (parentKey is null) {
60+
IEnumerable<object> rootData = data?.Where(n => IsRoot(n)) ?? Array.Empty<object>();
61+
return Task.FromResult(rootData);
62+
}
63+
ITreeNode parent = GetNode(parentKey);
64+
return Task.FromResult(parent.Children.Cast<object>());
65+
}
66+
private string GetFieldDisplayText(object item, string field) => ObjectTypeInfo.FindMember(field).GetValue(item)?.ToString();
67+
private string GetKey(object item) => objectSpace.GetObjectHandle(item);
68+
private ITreeNode GetNode(string key) => (ITreeNode)objectSpace.GetObjectByHandle(key);
69+
private bool HasChildren(object item) => ((ITreeNode)item).Children.Count > 0;
70+
71+
public override void BreakLinksToControls() {
72+
if (Control is TreeListHolder holder) {
73+
holder.ComponentModel.RowClick -= ComponentModel_RowClick;
74+
holder.ComponentModel.SelectionChanged -= ComponentModel_SelectionChanged;
75+
}
76+
AssignDataSourceToControl(null);
77+
base.BreakLinksToControls();
78+
}
79+
public override void Refresh() {
80+
if (Control is TreeListHolder holder) {
81+
holder.ComponentModel.Refresh();
82+
}
83+
}
84+
private void BindingList_ListChanged(object sender, ListChangedEventArgs e) {
85+
Refresh();
86+
}
87+
private void ComponentModel_RowClick(object sender, TreeListRowClickEventArgs e) {
88+
selectedObjects = new ITreeNode[] { GetNode(e.Key) };
89+
OnSelectionChanged();
90+
OnProcessSelectedItem();
91+
}
92+
private void ComponentModel_SelectionChanged(object sender, TreeListSelectionChangedEventArgs e) {
93+
var items = e.Keys.Select(key => GetNode(key)).ToArray();
94+
selectedObjects = items;
95+
OnSelectionChanged();
96+
}
97+
public override SelectionType SelectionType => SelectionType.Full;
98+
public override IList GetSelectedObjects() => selectedObjects;
99+
}
100+
}

0 commit comments

Comments
 (0)