Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,49 +1,48 @@
import { parseStyle } from "@mendix/widget-plugin-platform/preview/parse-style";
import { mapPreviewIconToWebIcon } from "@mendix/widget-plugin-platform/preview/map-icon";
import { GUID } from "mendix";
import { ReactElement } from "react";
import { TreeNodePreviewProps } from "../typings/TreeNodeProps";
import { TreeNode } from "./components/TreeNode";

function renderTextTemplateWithFallback(textTemplateValue: string, placeholder: string): string {
if (textTemplateValue.trim().length === 0) {
return placeholder;
}
return textTemplateValue;
}
// function renderTextTemplateWithFallback(textTemplateValue: string, placeholder: string): string {
// if (textTemplateValue.trim().length === 0) {
// return placeholder;
// }
// return textTemplateValue;
// }

export function preview(props: TreeNodePreviewProps): ReactElement | null {
export function preview(_props: TreeNodePreviewProps): ReactElement | null {
return (
<TreeNode
class={props.className}
style={parseStyle(props.style)}
items={[
{
id: "1" as GUID,
headerContent:
props.headerType === "text" ? (
renderTextTemplateWithFallback(props.headerCaption, "[No header caption configured]")
) : (
<props.headerContent.renderer caption="Place header contents here.">
<div />
</props.headerContent.renderer>
),
bodyContent: (
<props.children.renderer caption="Place other tree nodes here.">
<div />
</props.children.renderer>
)
}
]}
isUserDefinedLeafNode={!props.hasChildren}
startExpanded
showCustomIcon={Boolean(props.expandedIcon) || Boolean(props.collapsedIcon)}
iconPlacement={props.showIcon}
collapsedIcon={mapPreviewIconToWebIcon(props.collapsedIcon)}
expandedIcon={mapPreviewIconToWebIcon(props.expandedIcon)}
animateIcon={false}
animateTreeNodeContent={false}
openNodeOn={"headerClick"}
/>
<div> test </div>
// <TreeNodeComponent
// {...props}
// headerCaption={dynamc
// // class={props.className}
// // style={parseStyle(props.style)}
// items={[
// {
// id: "1" as GUID,
// // headerContent:
// // props.headerType === "text" ? (
// // renderTextTemplateWithFallback(props.headerCaption, "[No header caption configured]")
// // ) : (
// // <props.headerContent.renderer caption="Place header contents here.">
// // <div />
// // </props.headerContent.renderer>
// // ),
// // bodyContent: (
// // <props.children.renderer caption="Place other tree nodes here.">
// // <div />
// // </props.children.renderer>
// // )
// }
// ]}
// // isUserDefinedLeafNode={!props.hasChildren}
// startExpanded
// showCustomIcon={Boolean(props.expandedIcon) || Boolean(props.collapsedIcon)}
// // iconPlacement={props.showIcon}
// collapsedIcon={mapPreviewIconToWebIcon(props.collapsedIcon)}
// expandedIcon={mapPreviewIconToWebIcon(props.expandedIcon)}
// animateIcon={false}
// // animateTreeNodeContent={false}
// openNodeOn={"headerClick"}
// />
);
}
84 changes: 55 additions & 29 deletions packages/pluggableWidgets/tree-node-web/src/TreeNode.tsx
Original file line number Diff line number Diff line change
@@ -1,51 +1,77 @@
import { ReactElement, useEffect, useState } from "react";
import { ObjectItem, ValueStatus } from "mendix";
import { GUID, ObjectItem, Option, ValueStatus } from "mendix";
import { association, equals, literal } from "mendix/filters/builders";
import { ReactElement, useCallback, useEffect, useId, useRef, useState } from "react";
import { TreeNodeContainerProps } from "../typings/TreeNodeProps";
import { TreeNode as TreeNodeComponent, TreeNodeItem } from "./components/TreeNode";
import { TreeNodeRoot } from "./components/TreeNodeRoot";
import { TreeNodeRootContext } from "./components/TreeNodeRootContext";

function mapDataSourceItemToTreeNodeItem(item: ObjectItem, props: TreeNodeContainerProps): TreeNodeItem {
return {
id: item.id,
headerContent:
props.headerType === "text" ? props.headerCaption?.get(item).value : props.headerContent?.get(item),
bodyContent: props.children?.get(item)
};
}
type treeNodeGraph = {
parentObject: ObjectItem | null;
items: ObjectItem[];
};

export function TreeNode(props: TreeNodeContainerProps): ReactElement {
const { datasource } = props;
const rootId = useId();
const parent = useRef<ObjectItem | null>(null);
const [treeNodeItems, setTreeNodeItems] = useState(new Map<Option<GUID> | string, treeNodeGraph>());

const [treeNodeItems, setTreeNodeItems] = useState<TreeNodeItem[] | null>([]);
const filterContent = useCallback(
(item: Option<ObjectItem>) => {
if (props.parentAssociation) {
return equals(association(props.parentAssociation?.id), literal(item));
}
},
[props.parentAssociation]
);

const fetchChildren = useCallback(
(item?: Option<ObjectItem>) => {
parent.current = item || null;
if (props.parentAssociation) {
datasource.setFilter(filterContent(item));
}
},
[filterContent, datasource, props.parentAssociation]
);

useEffect(() => {
// Initial Load of Top Level Items
if (props.parentAssociation) {
fetchChildren(undefined);
}
}, []);

Check warning on line 43 in packages/pluggableWidgets/tree-node-web/src/TreeNode.tsx

View workflow job for this annotation

GitHub Actions / Run code quality check

React Hook useEffect has missing dependencies: 'fetchChildren' and 'props.parentAssociation'. Either include them or remove the dependency array

useEffect(() => {
// only get the items when datasource is actually available
// this is to prevent treenode resetting it's render while datasource is loading.
if (datasource.status === ValueStatus.Available) {
const updatedItems = new Map(treeNodeItems);
if (datasource.items && datasource.items.length) {
setTreeNodeItems(datasource.items.map(item => mapDataSourceItemToTreeNodeItem(item, props)));
updatedItems.set(parent.current?.id || rootId, {
items: datasource.items,
parentObject: parent.current ?? null
});
} else {
setTreeNodeItems([]);
updatedItems.set(parent.current?.id || rootId, { items: [], parentObject: parent.current ?? null });
}
setTreeNodeItems(updatedItems);
}
}, [datasource.status, datasource.items]);

Check warning on line 60 in packages/pluggableWidgets/tree-node-web/src/TreeNode.tsx

View workflow job for this annotation

GitHub Actions / Run code quality check

React Hook useEffect has missing dependencies: 'rootId' and 'treeNodeItems'. Either include them or remove the dependency array
const expandedIcon = props.expandedIcon?.status === ValueStatus.Available ? props.expandedIcon.value : undefined;
const collapsedIcon = props.collapsedIcon?.status === ValueStatus.Available ? props.collapsedIcon.value : undefined;

return (
<TreeNodeComponent
class={props.class}
style={props.style}
items={treeNodeItems}
isUserDefinedLeafNode={!props.hasChildren}
startExpanded={props.startExpanded}
showCustomIcon={Boolean(props.expandedIcon) || Boolean(props.collapsedIcon)}
iconPlacement={props.showIcon}
expandedIcon={expandedIcon}
collapsedIcon={collapsedIcon}
tabIndex={props.tabIndex}
animateIcon={props.animate && props.animateIcon}
animateTreeNodeContent={props.animate}
openNodeOn={props.openNodeOn}
/>
<TreeNodeRootContext.Provider value={{ fetchChildren, treeNodeItems, rootId }}>
<TreeNodeRoot
{...props}
// items={treeNodeItems.get(parent?.id || rootId)?.items || null}
showCustomIcon={Boolean(props.expandedIcon) || Boolean(props.collapsedIcon)}
expandedIcon={expandedIcon}
collapsedIcon={collapsedIcon}
isInfiniteMode={props.parentAssociation !== undefined}
// level={level || 0}
/>
</TreeNodeRootContext.Provider>
);
}
10 changes: 9 additions & 1 deletion packages/pluggableWidgets/tree-node-web/src/TreeNode.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@
<caption>Data source</caption>
<description />
</property>
<property key="parentAssociation" type="association" dataSource="datasource" selectableObjects="datasource" required="false">
<caption>Parent association</caption>
<description>Select the self-referencing association that connects each item to its parent, enabling infinite depth hierarchies.</description>
<associationTypes>
<associationType name="Reference" />
</associationTypes>
</property>
<property key="headerType" type="enumeration" defaultValue="text">
<caption>Header type</caption>
<description />
Expand All @@ -40,9 +47,10 @@
<caption>Header caption</caption>
<description />
</property>
<property key="hasChildren" type="boolean" defaultValue="true">
<property key="hasChildren" type="expression" dataSource="datasource">
<caption>Has children</caption>
<description>Indicate whether the node has children or is an end node. When set to yes, a composable region becomes available to define the child nodes.</description>
<returnType type="Boolean" />
</property>
<property key="startExpanded" type="boolean" defaultValue="false">
<caption>Start expanded</caption>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ import { ShowIconEnum } from "../../typings/TreeNodeProps";
import loadingCircleSvg from "../assets/loading-circle.svg";

import { ChevronIcon, CustomHeaderIcon } from "./Icons";
import { TreeNodeProps, TreeNodeState } from "./TreeNode";
import { TreeNodeComponentProps, TreeNodeState } from "./TreeNodeComponent";

export type IconOptions = Pick<TreeNodeProps, "animateIcon" | "collapsedIcon" | "expandedIcon" | "showCustomIcon">;
export type IconOptions = Pick<
TreeNodeComponentProps,
"animateIcon" | "collapsedIcon" | "expandedIcon" | "showCustomIcon"
>;

export type TreeNodeHeaderIcon = (
treeNodeState: TreeNodeState,
Expand Down
107 changes: 0 additions & 107 deletions packages/pluggableWidgets/tree-node-web/src/components/TreeNode.tsx

This file was deleted.

Loading
Loading