Skip to content

Commit

Permalink
Rewrote TreeView for AntDesign v4
Browse files Browse the repository at this point in the history
  • Loading branch information
JelteMX committed Jul 16, 2020
1 parent e1767a1 commit 60394a1
Show file tree
Hide file tree
Showing 10 changed files with 578 additions and 2,539 deletions.
932 changes: 333 additions & 599 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,10 @@
"webpack-bundle-analyzer": "^3.6.0"
},
"dependencies": {
"@ant-design/icons": "^4.2.1",
"@bem-react/classname": "^1.5.6",
"@jeltemx/mendix-react-widget-utils": "^0.3.7",
"antd": "^3.26.7",
"antd": "^4.4.2",
"array-to-tree": "^3.3.2",
"big.js": "^5.2.2",
"classnames": "^2.2.6",
Expand Down
19 changes: 15 additions & 4 deletions src/TreeView.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import { Component, ReactNode, createElement } from "react";
import { Component, ReactNode, createElement, ReactElement } from "react";
import { findDOMNode } from "react-dom";

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

import "antd/es/tree/style/index.css";
import "antd/es/spin/style/index.css";
import "antd/es/empty/style/index.css";
import "antd/es/input/style/index.css";

import "./ui/TreeView.scss";

import { NodeStore, NodeStoreConstructorOptions } from "./store/index";
import {
IAction,
Expand Down Expand Up @@ -128,6 +134,7 @@ class TreeView extends Component<TreeViewContainerProps> {
iconIsGlyphicon={uiNodeIconIsGlyphicon}
draggable={dragIsDraggable}
onClickHandler={this.clickTypeHandler}
switcherBg={this.props.uiSwitcherBg}
/>
);
}
Expand Down Expand Up @@ -173,6 +180,10 @@ class TreeView extends Component<TreeViewContainerProps> {
isRoot: nodeLoadScenario === "top"
});

if (nodeLoadScenario === "all") {
entryOpts.isLoaded = true;
}

this.store.setEntries(objects, entryOpts);
} else {
this.store.setEntries([], {});
Expand All @@ -181,7 +192,7 @@ class TreeView extends Component<TreeViewContainerProps> {
this.store.setLoading(false);
}

private async _fetchChildren(parentObject: EntryObject): Promise<void> {
private async _fetchChildren(parentObject: EntryObject, expandAfter: string | null = null): Promise<void> {
if (this.props.nodeLoadScenario === "all") {
return;
}
Expand Down Expand Up @@ -234,7 +245,7 @@ class TreeView extends Component<TreeViewContainerProps> {
parent: parentObject.mxObject.getGuid()
});

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

if (titleType === "attribute" && attribute) {
opts.staticTitleMethod = (obj: mendix.lib.MxObject) =>
opts.staticTitleMethod = (obj: mendix.lib.MxObject): ReactElement =>
getStaticTitleFromObject(obj, {
attribute,
titleType,
Expand Down
4 changes: 4 additions & 0 deletions src/TreeView.xml
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,10 @@
<caption>Show tree lines</caption>
<description>These lines will show up in the tree structure to make it better visible</description>
</property>
<property key="uiSwitcherBg" type="string" required="false" defaultValue="">
<caption>Switcher background</caption>
<description>When using tree lines, you might need to set the switcher background to match the background of your tree.</description>
</property>
</propertyGroup>
</propertyGroup>

Expand Down
94 changes: 63 additions & 31 deletions src/components/TreeViewComponent.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
import { Component, ReactNode, createElement, ReactElement } from "react";
import { Component, ReactNode, createElement, MouseEvent } from "react";
import { observer } from "mobx-react";
import { Tree as ArrayTree } from "array-to-tree";
import Tree, { AntTreeNode } from "antd/es/tree";
import Spin from "antd/es/spin";
import Input from "antd/es/input";
import Empty from "antd/es/empty";
import classNames from "classnames";

import { Key } from "antd/es/table/interface";
import { EventDataNode, DataNode } from "antd/es/tree";
import { Tree, Spin, Input, Empty } from "antd";
import { CaretDownFilled } from "@ant-design/icons";

import debounce from "debounce";

const { TreeNode } = Tree;
const { Search } = Input;

import { NodeStore } from "../store/index";
import { TreeObject } from "../store/objects/entry";
import { AntTreeNodeDropEvent, AntTreeNodeExpandedEvent } from "antd/es/tree/Tree";
import { ClickCellType } from "../utils/titlehelper";
import { Alerts } from "./Alerts";
import classNames from "classnames";

export interface TreeViewComponentProps {
store: NodeStore;
Expand All @@ -27,6 +27,7 @@ export interface TreeViewComponentProps {
iconIsGlyphicon: boolean;
onClickHandler: (_obj: mendix.lib.MxObject, _clickType: ClickCellType) => Promise<void>;
className: string;
switcherBg: string;
}

@observer
Expand All @@ -47,6 +48,16 @@ export class TreeViewComponent extends Component<TreeViewComponentProps> {
);
}

componentWillMount(): void {
if (this.props.switcherBg) {
document.documentElement.style.setProperty("--switcher-icon-bg", this.props.switcherBg);
}
}

componentWillUnmount(): void {
document.documentElement.style.removeProperty("--switcher-icon-bg");
}

private renderControl(): ReactNode {
const { store, searchEnabled } = this.props;

Expand Down Expand Up @@ -106,19 +117,19 @@ export class TreeViewComponent extends Component<TreeViewComponentProps> {
expandedKeys={expandedKeys}
showIcon={showIcon}
showLine={showLine}
switcherIcon={showLine ? <CaretDownFilled style={{ backgroundColor: "#F5F8FD" }} /> : undefined}
selectable={false}
draggable={draggable}
onDrop={this.onDrop.bind(this)}
onExpand={this.onExpand.bind(this)}
onClick={this.handleClick("single")}
onDoubleClick={this.handleClick("double")}
>
{this.renderTreeNodes(store.entryTree)}
</Tree>
treeData={this.getTreeNodes(store.entryTree)}
/>
);
}

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

if (item.children && item.children.length > 0) {
const children = this.renderTreeNodes(item.children);
return (
<TreeNode key={item.guid} title={item.title} icon={icon} isLeaf={isLeaf} className={extraClass}>
{children}
</TreeNode>
);
const dataNode: DataNode = {
key: item.guid,
icon,
title: item.title,
isLeaf,
className: extraClass
};

if (item.children && item.children.length > 0 && typeof item.children[0] !== "string") {
const children = this.getTreeNodes(item.children);
dataNode.children = children;
}
return <TreeNode key={item.guid} icon={icon} title={item.title} isLeaf={isLeaf} className={extraClass} />;

return dataNode;
});
}

private onDrop(opts: AntTreeNodeDropEvent): void {
if (!opts.dragNode.props.eventKey) {
private onDrop(info: {
event: React.MouseEvent;
node: EventDataNode;
dragNode: EventDataNode;
dragNodesKeys: Key[];
dropPosition: number;
dropToGap: boolean;
}): void {
if (!info.dragNode.key) {
return;
}
// console.log(`Dragged : ${opts.dragNode.props.eventKey} to ${opts.node.props.eventKey}`);
this.props.store.switchEntryParent(opts.dragNode.props.eventKey, opts.node.props.eventKey);
this.props.store.switchEntryParent(info.dragNode.key as string, info.node.key as string);
}

private handleClick(clickType: ClickCellType): (_evt: unknown, _node: AntTreeNode) => void {
return (_evt: unknown, node: AntTreeNode) => {
if (!node.props.eventKey || !this.props.onClickHandler) {
private handleClick(
clickType: ClickCellType
): (_evt: MouseEvent<Element, globalThis.MouseEvent>, node: EventDataNode) => void {
return (_evt: MouseEvent<Element, globalThis.MouseEvent>, node: EventDataNode): void => {
if (!node.key || !this.props.onClickHandler) {
return;
}
const entryObject = this.props.store.findEntry(node.props.eventKey);
const key = node.key as string;
const entryObject = this.props.store.findEntry(key);
if (entryObject && entryObject.mxObject) {
this.props.onClickHandler(entryObject.mxObject, clickType);
if (this.props.holdSelection) {
Expand All @@ -168,10 +193,17 @@ export class TreeViewComponent extends Component<TreeViewComponentProps> {
};
}

private onExpand(_expandedKeys: string[], info: AntTreeNodeExpandedEvent): void {
private onExpand(
_expandedKeys: React.ReactText[],
info: {
node: EventDataNode;
expanded: boolean;
nativeEvent: globalThis.MouseEvent;
}
): void {
const { expanded, node } = info;
if (node && node.props.eventKey && typeof expanded !== "undefined") {
this.props.store.expandKey(node.props.eventKey, expanded);
if (node && node.key && typeof expanded !== "undefined") {
this.props.store.expandKey(node.key as string, expanded);
}
}
}
22 changes: 16 additions & 6 deletions src/store/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export interface NodeStoreConstructorOptions {
onSelectionChange?: (guids: TreeGuids) => void;
validationMessages: ValidationMessage[];
entryObjectAttributes: EntryObjectAttributes;
childLoader: (parent: EntryObject) => Promise<void>;
childLoader: (parent: EntryObject, expandAfter: string | null) => Promise<void>;
searchHandler?: (_query: string) => Promise<mendix.lib.MxObject[] | null>;
debug: (...args: unknown[]) => void;
}
Expand All @@ -45,7 +45,7 @@ export class NodeStore {
public subscriptionHandler: (guids: TreeGuids) => void;
public onSelectionChangeHandler: (guids: TreeGuids) => void;
public entryObjectAttributes: EntryObjectAttributes;
public childLoader: (parent: EntryObject) => Promise<void> = async () => {};
public childLoader: (parent: EntryObject, expandAfter?: string | null) => Promise<void> = async () => {};
public searchHandler: ((_query: string) => Promise<mendix.lib.MxObject[] | null>) | null;
public debug: (...args: unknown[]) => void;

Expand Down Expand Up @@ -123,7 +123,12 @@ export class NodeStore {

// Entries
@action
setEntries(entryObjects: mendix.lib.MxObject[], opts: EntryObjectExtraOptions, clean = true): void {
setEntries(
entryObjects: mendix.lib.MxObject[],
opts: EntryObjectExtraOptions,
clean = true,
expandAfter: string | null = null
): void {
this.debug("store: setEntries", entryObjects.length, opts, clean);
const entries = entryObjects.map(mxObject => this.createEntryObject(mxObject, this.entryHandler(opts), opts));

Expand Down Expand Up @@ -154,6 +159,10 @@ export class NodeStore {
});
this.entries = cloned;
}

if (expandAfter !== null) {
this.expandKey(expandAfter, true);
}
}

@action
Expand Down Expand Up @@ -244,7 +253,7 @@ export class NodeStore {
}

@action
selectEntry(guid: string) {
selectEntry(guid: string): void {
if (!this.holdSelection) {
return;
}
Expand Down Expand Up @@ -274,9 +283,10 @@ export class NodeStore {
expandKey(guid: string, expanded: boolean): void {
const entryObject = this.findEntry(guid);
if (entryObject) {
entryObject.setExpanded(expanded);
if (expanded && !entryObject.isLoaded) {
this.childLoader(entryObject);
this.childLoader(entryObject, guid);
} else {
entryObject.setExpanded(expanded);
}
}
}
Expand Down
6 changes: 3 additions & 3 deletions src/store/objects/entry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export interface EntryObjectExtraOptions {
classMethod?: ClassMethod;
isRoot?: boolean;
parent?: string;
isLoaded?: boolean;
}

export interface EntryObjectOptions {
Expand Down Expand Up @@ -60,14 +61,13 @@ export class EntryObject {
fixTitle = flow(function*(this: EntryObject) {
if (this._dynamicTitleMethod) {
const title = yield this._dynamicTitleMethod(this._obj);
// @ts-ignore
this._title = title;
}
});

constructor(opts: EntryObjectOptions, attributes: EntryObjectAttributes) {
const { mxObject, changeHandler, extraOpts } = opts;
const { staticTitleMethod, dynamicTitleMethod, classMethod, isRoot, parent } = extraOpts;
const { staticTitleMethod, dynamicTitleMethod, classMethod, isRoot, parent, isLoaded } = extraOpts;
this._obj = mxObject;

this._title = "";
Expand All @@ -77,7 +77,7 @@ export class EntryObject {
this._parent = typeof parent !== "undefined" ? parent : "";
this._children = [];
this._hasChildren = false;
this._isLoaded = false;
this._isLoaded = typeof isLoaded !== "undefined" ? isLoaded : false;
this._isExpanded = false;
this._isRoot = typeof isRoot !== "undefined" ? isRoot : false;
this._dynamicTitleMethod = dynamicTitleMethod || null;
Expand Down
Loading

0 comments on commit 60394a1

Please sign in to comment.