Skip to content

Commit 60394a1

Browse files
author
JelteMX
committed
Rewrote TreeView for AntDesign v4
1 parent e1767a1 commit 60394a1

File tree

10 files changed

+578
-2539
lines changed

10 files changed

+578
-2539
lines changed

package-lock.json

Lines changed: 333 additions & 599 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,10 @@
4848
"webpack-bundle-analyzer": "^3.6.0"
4949
},
5050
"dependencies": {
51+
"@ant-design/icons": "^4.2.1",
5152
"@bem-react/classname": "^1.5.6",
5253
"@jeltemx/mendix-react-widget-utils": "^0.3.7",
53-
"antd": "^3.26.7",
54+
"antd": "^4.4.2",
5455
"array-to-tree": "^3.3.2",
5556
"big.js": "^5.2.2",
5657
"classnames": "^2.2.6",

src/TreeView.tsx

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
1-
import { Component, ReactNode, createElement } from "react";
1+
import { Component, ReactNode, createElement, ReactElement } from "react";
22
import { findDOMNode } from "react-dom";
33

44
import { TreeViewComponent } from "./components/TreeViewComponent";
55
import { hot } from "react-hot-loader/root";
66
import { TreeViewContainerProps } from "../typings/TreeViewProps";
77

8+
import "antd/es/tree/style/index.css";
9+
import "antd/es/spin/style/index.css";
10+
import "antd/es/empty/style/index.css";
11+
import "antd/es/input/style/index.css";
12+
813
import "./ui/TreeView.scss";
14+
915
import { NodeStore, NodeStoreConstructorOptions } from "./store/index";
1016
import {
1117
IAction,
@@ -128,6 +134,7 @@ class TreeView extends Component<TreeViewContainerProps> {
128134
iconIsGlyphicon={uiNodeIconIsGlyphicon}
129135
draggable={dragIsDraggable}
130136
onClickHandler={this.clickTypeHandler}
137+
switcherBg={this.props.uiSwitcherBg}
131138
/>
132139
);
133140
}
@@ -173,6 +180,10 @@ class TreeView extends Component<TreeViewContainerProps> {
173180
isRoot: nodeLoadScenario === "top"
174181
});
175182

183+
if (nodeLoadScenario === "all") {
184+
entryOpts.isLoaded = true;
185+
}
186+
176187
this.store.setEntries(objects, entryOpts);
177188
} else {
178189
this.store.setEntries([], {});
@@ -181,7 +192,7 @@ class TreeView extends Component<TreeViewContainerProps> {
181192
this.store.setLoading(false);
182193
}
183194

184-
private async _fetchChildren(parentObject: EntryObject): Promise<void> {
195+
private async _fetchChildren(parentObject: EntryObject, expandAfter: string | null = null): Promise<void> {
185196
if (this.props.nodeLoadScenario === "all") {
186197
return;
187198
}
@@ -234,7 +245,7 @@ class TreeView extends Component<TreeViewContainerProps> {
234245
parent: parentObject.mxObject.getGuid()
235246
});
236247

237-
this.store.setEntries(objects, entryOpts, false);
248+
this.store.setEntries(objects, entryOpts, false, expandAfter);
238249
parentObject.setLoaded(true);
239250
} else {
240251
parentObject.setHasChildren(false);
@@ -251,7 +262,7 @@ class TreeView extends Component<TreeViewContainerProps> {
251262
const nanoflow = this.props.uiNodeTitleNanoflow;
252263

253264
if (titleType === "attribute" && attribute) {
254-
opts.staticTitleMethod = (obj: mendix.lib.MxObject) =>
265+
opts.staticTitleMethod = (obj: mendix.lib.MxObject): ReactElement =>
255266
getStaticTitleFromObject(obj, {
256267
attribute,
257268
titleType,

src/TreeView.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,10 @@
172172
<caption>Show tree lines</caption>
173173
<description>These lines will show up in the tree structure to make it better visible</description>
174174
</property>
175+
<property key="uiSwitcherBg" type="string" required="false" defaultValue="">
176+
<caption>Switcher background</caption>
177+
<description>When using tree lines, you might need to set the switcher background to match the background of your tree.</description>
178+
</property>
175179
</propertyGroup>
176180
</propertyGroup>
177181

src/components/TreeViewComponent.tsx

Lines changed: 63 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
1-
import { Component, ReactNode, createElement, ReactElement } from "react";
1+
import { Component, ReactNode, createElement, MouseEvent } from "react";
22
import { observer } from "mobx-react";
33
import { Tree as ArrayTree } from "array-to-tree";
4-
import Tree, { AntTreeNode } from "antd/es/tree";
5-
import Spin from "antd/es/spin";
6-
import Input from "antd/es/input";
7-
import Empty from "antd/es/empty";
4+
import classNames from "classnames";
5+
6+
import { Key } from "antd/es/table/interface";
7+
import { EventDataNode, DataNode } from "antd/es/tree";
8+
import { Tree, Spin, Input, Empty } from "antd";
9+
import { CaretDownFilled } from "@ant-design/icons";
10+
811
import debounce from "debounce";
912

10-
const { TreeNode } = Tree;
1113
const { Search } = Input;
1214

1315
import { NodeStore } from "../store/index";
1416
import { TreeObject } from "../store/objects/entry";
15-
import { AntTreeNodeDropEvent, AntTreeNodeExpandedEvent } from "antd/es/tree/Tree";
1617
import { ClickCellType } from "../utils/titlehelper";
1718
import { Alerts } from "./Alerts";
18-
import classNames from "classnames";
1919

2020
export interface TreeViewComponentProps {
2121
store: NodeStore;
@@ -27,6 +27,7 @@ export interface TreeViewComponentProps {
2727
iconIsGlyphicon: boolean;
2828
onClickHandler: (_obj: mendix.lib.MxObject, _clickType: ClickCellType) => Promise<void>;
2929
className: string;
30+
switcherBg: string;
3031
}
3132

3233
@observer
@@ -47,6 +48,16 @@ export class TreeViewComponent extends Component<TreeViewComponentProps> {
4748
);
4849
}
4950

51+
componentWillMount(): void {
52+
if (this.props.switcherBg) {
53+
document.documentElement.style.setProperty("--switcher-icon-bg", this.props.switcherBg);
54+
}
55+
}
56+
57+
componentWillUnmount(): void {
58+
document.documentElement.style.removeProperty("--switcher-icon-bg");
59+
}
60+
5061
private renderControl(): ReactNode {
5162
const { store, searchEnabled } = this.props;
5263

@@ -106,19 +117,19 @@ export class TreeViewComponent extends Component<TreeViewComponentProps> {
106117
expandedKeys={expandedKeys}
107118
showIcon={showIcon}
108119
showLine={showLine}
120+
switcherIcon={showLine ? <CaretDownFilled style={{ backgroundColor: "#F5F8FD" }} /> : undefined}
109121
selectable={false}
110122
draggable={draggable}
111123
onDrop={this.onDrop.bind(this)}
112124
onExpand={this.onExpand.bind(this)}
113125
onClick={this.handleClick("single")}
114126
onDoubleClick={this.handleClick("double")}
115-
>
116-
{this.renderTreeNodes(store.entryTree)}
117-
</Tree>
127+
treeData={this.getTreeNodes(store.entryTree)}
128+
/>
118129
);
119130
}
120131

121-
private renderTreeNodes(data: ArrayTree<TreeObject>[]): ReactElement<any>[] {
132+
private getTreeNodes(data: ArrayTree<TreeObject>[]): DataNode[] {
122133
const { iconIsGlyphicon } = this.props;
123134
return data.map(item => {
124135
let icon: ReactNode | boolean = false;
@@ -133,32 +144,46 @@ export class TreeViewComponent extends Component<TreeViewComponentProps> {
133144
icon = <span className={iconIsGlyphicon ? "glyphicon glyphicon-" + item.icon : item.icon} />;
134145
}
135146

136-
if (item.children && item.children.length > 0) {
137-
const children = this.renderTreeNodes(item.children);
138-
return (
139-
<TreeNode key={item.guid} title={item.title} icon={icon} isLeaf={isLeaf} className={extraClass}>
140-
{children}
141-
</TreeNode>
142-
);
147+
const dataNode: DataNode = {
148+
key: item.guid,
149+
icon,
150+
title: item.title,
151+
isLeaf,
152+
className: extraClass
153+
};
154+
155+
if (item.children && item.children.length > 0 && typeof item.children[0] !== "string") {
156+
const children = this.getTreeNodes(item.children);
157+
dataNode.children = children;
143158
}
144-
return <TreeNode key={item.guid} icon={icon} title={item.title} isLeaf={isLeaf} className={extraClass} />;
159+
160+
return dataNode;
145161
});
146162
}
147163

148-
private onDrop(opts: AntTreeNodeDropEvent): void {
149-
if (!opts.dragNode.props.eventKey) {
164+
private onDrop(info: {
165+
event: React.MouseEvent;
166+
node: EventDataNode;
167+
dragNode: EventDataNode;
168+
dragNodesKeys: Key[];
169+
dropPosition: number;
170+
dropToGap: boolean;
171+
}): void {
172+
if (!info.dragNode.key) {
150173
return;
151174
}
152-
// console.log(`Dragged : ${opts.dragNode.props.eventKey} to ${opts.node.props.eventKey}`);
153-
this.props.store.switchEntryParent(opts.dragNode.props.eventKey, opts.node.props.eventKey);
175+
this.props.store.switchEntryParent(info.dragNode.key as string, info.node.key as string);
154176
}
155177

156-
private handleClick(clickType: ClickCellType): (_evt: unknown, _node: AntTreeNode) => void {
157-
return (_evt: unknown, node: AntTreeNode) => {
158-
if (!node.props.eventKey || !this.props.onClickHandler) {
178+
private handleClick(
179+
clickType: ClickCellType
180+
): (_evt: MouseEvent<Element, globalThis.MouseEvent>, node: EventDataNode) => void {
181+
return (_evt: MouseEvent<Element, globalThis.MouseEvent>, node: EventDataNode): void => {
182+
if (!node.key || !this.props.onClickHandler) {
159183
return;
160184
}
161-
const entryObject = this.props.store.findEntry(node.props.eventKey);
185+
const key = node.key as string;
186+
const entryObject = this.props.store.findEntry(key);
162187
if (entryObject && entryObject.mxObject) {
163188
this.props.onClickHandler(entryObject.mxObject, clickType);
164189
if (this.props.holdSelection) {
@@ -168,10 +193,17 @@ export class TreeViewComponent extends Component<TreeViewComponentProps> {
168193
};
169194
}
170195

171-
private onExpand(_expandedKeys: string[], info: AntTreeNodeExpandedEvent): void {
196+
private onExpand(
197+
_expandedKeys: React.ReactText[],
198+
info: {
199+
node: EventDataNode;
200+
expanded: boolean;
201+
nativeEvent: globalThis.MouseEvent;
202+
}
203+
): void {
172204
const { expanded, node } = info;
173-
if (node && node.props.eventKey && typeof expanded !== "undefined") {
174-
this.props.store.expandKey(node.props.eventKey, expanded);
205+
if (node && node.key && typeof expanded !== "undefined") {
206+
this.props.store.expandKey(node.key as string, expanded);
175207
}
176208
}
177209
}

src/store/index.ts

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export interface NodeStoreConstructorOptions {
2121
onSelectionChange?: (guids: TreeGuids) => void;
2222
validationMessages: ValidationMessage[];
2323
entryObjectAttributes: EntryObjectAttributes;
24-
childLoader: (parent: EntryObject) => Promise<void>;
24+
childLoader: (parent: EntryObject, expandAfter: string | null) => Promise<void>;
2525
searchHandler?: (_query: string) => Promise<mendix.lib.MxObject[] | null>;
2626
debug: (...args: unknown[]) => void;
2727
}
@@ -45,7 +45,7 @@ export class NodeStore {
4545
public subscriptionHandler: (guids: TreeGuids) => void;
4646
public onSelectionChangeHandler: (guids: TreeGuids) => void;
4747
public entryObjectAttributes: EntryObjectAttributes;
48-
public childLoader: (parent: EntryObject) => Promise<void> = async () => {};
48+
public childLoader: (parent: EntryObject, expandAfter?: string | null) => Promise<void> = async () => {};
4949
public searchHandler: ((_query: string) => Promise<mendix.lib.MxObject[] | null>) | null;
5050
public debug: (...args: unknown[]) => void;
5151

@@ -123,7 +123,12 @@ export class NodeStore {
123123

124124
// Entries
125125
@action
126-
setEntries(entryObjects: mendix.lib.MxObject[], opts: EntryObjectExtraOptions, clean = true): void {
126+
setEntries(
127+
entryObjects: mendix.lib.MxObject[],
128+
opts: EntryObjectExtraOptions,
129+
clean = true,
130+
expandAfter: string | null = null
131+
): void {
127132
this.debug("store: setEntries", entryObjects.length, opts, clean);
128133
const entries = entryObjects.map(mxObject => this.createEntryObject(mxObject, this.entryHandler(opts), opts));
129134

@@ -154,6 +159,10 @@ export class NodeStore {
154159
});
155160
this.entries = cloned;
156161
}
162+
163+
if (expandAfter !== null) {
164+
this.expandKey(expandAfter, true);
165+
}
157166
}
158167

159168
@action
@@ -244,7 +253,7 @@ export class NodeStore {
244253
}
245254

246255
@action
247-
selectEntry(guid: string) {
256+
selectEntry(guid: string): void {
248257
if (!this.holdSelection) {
249258
return;
250259
}
@@ -274,9 +283,10 @@ export class NodeStore {
274283
expandKey(guid: string, expanded: boolean): void {
275284
const entryObject = this.findEntry(guid);
276285
if (entryObject) {
277-
entryObject.setExpanded(expanded);
278286
if (expanded && !entryObject.isLoaded) {
279-
this.childLoader(entryObject);
287+
this.childLoader(entryObject, guid);
288+
} else {
289+
entryObject.setExpanded(expanded);
280290
}
281291
}
282292
}

src/store/objects/entry.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export interface EntryObjectExtraOptions {
2727
classMethod?: ClassMethod;
2828
isRoot?: boolean;
2929
parent?: string;
30+
isLoaded?: boolean;
3031
}
3132

3233
export interface EntryObjectOptions {
@@ -60,14 +61,13 @@ export class EntryObject {
6061
fixTitle = flow(function*(this: EntryObject) {
6162
if (this._dynamicTitleMethod) {
6263
const title = yield this._dynamicTitleMethod(this._obj);
63-
// @ts-ignore
6464
this._title = title;
6565
}
6666
});
6767

6868
constructor(opts: EntryObjectOptions, attributes: EntryObjectAttributes) {
6969
const { mxObject, changeHandler, extraOpts } = opts;
70-
const { staticTitleMethod, dynamicTitleMethod, classMethod, isRoot, parent } = extraOpts;
70+
const { staticTitleMethod, dynamicTitleMethod, classMethod, isRoot, parent, isLoaded } = extraOpts;
7171
this._obj = mxObject;
7272

7373
this._title = "";
@@ -77,7 +77,7 @@ export class EntryObject {
7777
this._parent = typeof parent !== "undefined" ? parent : "";
7878
this._children = [];
7979
this._hasChildren = false;
80-
this._isLoaded = false;
80+
this._isLoaded = typeof isLoaded !== "undefined" ? isLoaded : false;
8181
this._isExpanded = false;
8282
this._isRoot = typeof isRoot !== "undefined" ? isRoot : false;
8383
this._dynamicTitleMethod = dynamicTitleMethod || null;

0 commit comments

Comments
 (0)