Skip to content

Commit 444ef60

Browse files
committed
Embedded method tree conversion
1 parent 257bf31 commit 444ef60

File tree

9 files changed

+423
-0
lines changed

9 files changed

+423
-0
lines changed
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/* eslint-disable jsx-a11y/no-static-element-interactions */
2+
/* eslint-disable jsx-a11y/click-events-have-key-events */
3+
import React, { useEffect, useState } from 'react';
4+
import PropTypes from 'prop-types';
5+
import { Modal, Button, ModalBody } from 'carbon-components-react';
6+
import MiqTree from '../MiqTreeView';
7+
8+
/** Component to render a tree and to select an embedded method. */
9+
const AeInlineMethod = ({ type }) => {
10+
const [data, setData] = useState({
11+
isModalOpen: false,
12+
selectedNode: undefined,
13+
list: [],
14+
});
15+
16+
/** Function to show/hide the modal. */
17+
const showModal = (status) => {
18+
setData({
19+
...data,
20+
isModalOpen: status,
21+
});
22+
};
23+
24+
/** Function to render the Add method button. */
25+
const renderAddButton = () => (
26+
<Button
27+
id="add-method"
28+
kind="primary"
29+
title={__('Add Method')}
30+
onClick={() => showModal(true)}
31+
size="sm"
32+
>
33+
{__('Add method')}
34+
</Button>
35+
);
36+
37+
console.log(data);
38+
39+
const renderList = () => (data.list.map((item) => (
40+
<div key={item.key}>
41+
<div>{item.fqname}</div>
42+
</div>
43+
)));
44+
45+
return (
46+
<div>
47+
{renderAddButton()}
48+
{renderList()}
49+
<Modal
50+
primaryButtonDisabled={data.selectedNode === undefined}
51+
size="lg"
52+
modalHeading={__('Select item')}
53+
open={data.isModalOpen}
54+
primaryButtonText={__('OK')}
55+
secondaryButtonText={__('Cancel')}
56+
onRequestClose={() => showModal(false)}
57+
onRequestSubmit={() => {
58+
console.log('on onRequestSubmit');
59+
setData({
60+
...data,
61+
list: data.list.push(data.selectedNode),
62+
});
63+
showModal(false);
64+
}}
65+
onSecondarySubmit={() => {
66+
console.log('on onSecondarySubmit');
67+
showModal(false);
68+
}}
69+
>
70+
<ModalBody>
71+
{
72+
data.isModalOpen
73+
&& (
74+
<MiqTree
75+
type={type}
76+
onNodeSelect={(item) => {
77+
setData({
78+
...data,
79+
selectedNode: item,
80+
});
81+
}}
82+
/>
83+
)
84+
}
85+
</ModalBody>
86+
</Modal>
87+
88+
</div>
89+
);
90+
};
91+
92+
export default AeInlineMethod;
93+
94+
AeInlineMethod.propTypes = {
95+
type: PropTypes.string.isRequired,
96+
};
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/* eslint-disable jsx-a11y/no-static-element-interactions */
2+
/* eslint-disable jsx-a11y/click-events-have-key-events */
3+
/* eslint-disable import/no-cycle */
4+
import React from 'react';
5+
import PropTypes from 'prop-types';
6+
import MiqTreeNode from './MiqTreeNode';
7+
8+
/** A component to render the parent node of the tree. */
9+
const MiqTreeChildNode = ({
10+
node, onSelect, selectedNode, selectKey,
11+
}) => (
12+
<div className="tree-row child-tree intend-right" key={node.key}>
13+
{
14+
node.nodes.map((item) => (
15+
<MiqTreeNode
16+
key={item.key}
17+
node={item}
18+
selectedNode={selectedNode}
19+
selectKey={selectKey}
20+
onSelect={(childItem) => onSelect(childItem)}
21+
/>
22+
))
23+
}
24+
</div>
25+
);
26+
27+
export default MiqTreeChildNode;
28+
29+
MiqTreeChildNode.propTypes = {
30+
node: PropTypes.shape({
31+
key: PropTypes.string,
32+
nodes: PropTypes.arrayOf(PropTypes.any),
33+
state: PropTypes.shape({
34+
expanded: PropTypes.bool,
35+
}),
36+
}).isRequired,
37+
selectKey: PropTypes.string.isRequired,
38+
onSelect: PropTypes.func.isRequired,
39+
selectedNode: PropTypes.shape({
40+
key: PropTypes.string,
41+
}),
42+
};
43+
44+
MiqTreeChildNode.defaultProps = {
45+
selectedNode: undefined,
46+
};
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/* eslint-disable import/no-cycle */
2+
import React from 'react';
3+
import PropTypes from 'prop-types';
4+
import MiqTreeParentNode from './MiqTreeParentNode';
5+
import MiqTreeChildNode from './MiqTreeChildNode';
6+
7+
/** A Recursive Functional component to render the Tree and its child nodes. */
8+
const MiqTreeNode = ({
9+
node, selectedNode, selectKey, onSelect,
10+
}) => {
11+
const isSelected = (selectedNode && selectedNode.key === node.key) || false;
12+
13+
return (
14+
<div key={node.key}>
15+
<MiqTreeParentNode
16+
node={node}
17+
isSelected={isSelected}
18+
selectKey={selectKey}
19+
onSelect={(parentItem) => onSelect(parentItem)}
20+
/>
21+
{
22+
node.state.expanded && node.nodes && node.nodes.length > 0 && (
23+
<MiqTreeChildNode
24+
node={node}
25+
onSelect={(childItem) => onSelect(childItem)}
26+
selectedNode={selectedNode}
27+
selectKey={selectKey}
28+
/>
29+
)
30+
}
31+
</div>
32+
);
33+
};
34+
35+
export default MiqTreeNode;
36+
37+
MiqTreeNode.propTypes = {
38+
node: PropTypes.shape({
39+
key: PropTypes.string,
40+
nodes: PropTypes.arrayOf(PropTypes.any),
41+
state: PropTypes.shape({
42+
expanded: PropTypes.bool,
43+
}),
44+
}).isRequired,
45+
selectKey: PropTypes.string.isRequired,
46+
onSelect: PropTypes.func.isRequired,
47+
selectedNode: PropTypes.shape({
48+
key: PropTypes.string,
49+
}),
50+
};
51+
52+
MiqTreeNode.defaultProps = {
53+
selectedNode: undefined,
54+
};
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/* eslint-disable jsx-a11y/no-static-element-interactions */
2+
/* eslint-disable jsx-a11y/click-events-have-key-events */
3+
import React from 'react';
4+
import PropTypes from 'prop-types';
5+
import classNames from 'classnames';
6+
import { CaretRight16, CaretDown16, CheckmarkFilled16 } from '@carbon/icons-react';
7+
import { selectableItem } from './helper';
8+
9+
/** A component to render the parent node of the tree. */
10+
const MiqTreeParentNode = ({
11+
node, selectKey, isSelected, onSelect,
12+
}) => {
13+
/** Function to render the down and right caret. */
14+
const renderCaret = (item) => {
15+
if (!item) {
16+
return undefined;
17+
}
18+
if (selectableItem(item, selectKey) || !item.lazyLoad) {
19+
return undefined;
20+
}
21+
return item.state.expanded ? <CaretDown16 className="tree-caret" /> : <CaretRight16 className="tree-caret" />;
22+
};
23+
24+
return (
25+
<div className={classNames('tree-row parent-tree', isSelected && 'selected-node')} onClick={() => onSelect(node)}>
26+
{renderCaret(node)}
27+
<div className="tree-icon"><i className={node.icon} /></div>
28+
<div className="tree-text">{node.text}</div>
29+
{isSelected && <CheckmarkFilled16 className="selected-node-check" />}
30+
</div>
31+
);
32+
};
33+
34+
export default MiqTreeParentNode;
35+
36+
MiqTreeParentNode.propTypes = {
37+
node: PropTypes.shape({
38+
icon: PropTypes.string,
39+
text: PropTypes.string,
40+
key: PropTypes.string,
41+
nodes: PropTypes.arrayOf(PropTypes.any),
42+
state: PropTypes.shape({
43+
expanded: PropTypes.bool,
44+
}),
45+
}).isRequired,
46+
selectKey: PropTypes.string.isRequired,
47+
isSelected: PropTypes.bool.isRequired,
48+
onSelect: PropTypes.func.isRequired,
49+
};
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
export const TREE_CONFIG = {
2+
aeInlineMethod: {
3+
url: '/tree/automate_inline_methods',
4+
selectKey: 'aem',
5+
},
6+
};
7+
8+
/** Function to find the selected item from the tree data. */
9+
export const findNodeByKey = (array, keyToFind) => {
10+
const flattenedArray = array.flatMap((item) => [item, ...(item.nodes || [])]);
11+
const foundNode = flattenedArray.find((item) => item.key === keyToFind);
12+
13+
return foundNode || flattenedArray
14+
.filter((item) => item.nodes && item.nodes.length > 0)
15+
.map((item) => findNodeByKey(item.nodes, keyToFind))
16+
.find(Boolean);
17+
};
18+
19+
export const selectableItem = (child, selectKey) => child.key.split('-')[0] === selectKey;
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import React, { useEffect, useState } from 'react';
2+
import PropTypes from 'prop-types';
3+
import { Loading } from 'carbon-components-react';
4+
import MiqTreeNode from './MiqTreeNode';
5+
import { TREE_CONFIG, selectableItem, findNodeByKey } from './helper';
6+
import './style.scss';
7+
8+
const MiqTree = ({ type, onNodeSelect }) => {
9+
const treeType = TREE_CONFIG[type];
10+
11+
const [data, setData] = useState({
12+
list: undefined,
13+
isLoading: true,
14+
selectedNode: undefined,
15+
});
16+
17+
/** Function to update the data. */
18+
const updateData = (others) => {
19+
setData({
20+
...data,
21+
...others,
22+
});
23+
};
24+
25+
/** A request is made to fetch the initial data during component load. */
26+
useEffect(() => {
27+
http.get(treeType.url)
28+
.then((response) => updateData({ list: response, isLoading: false }));
29+
}, []);
30+
31+
useEffect(() => {
32+
onNodeSelect(data.selectedNode);
33+
}, [data.selectedNode]);
34+
35+
/** Function to select a node from tree. This triggers the useEffect. */
36+
const selectNode = (node) => {
37+
const selectedNode = (data.selectedNode && data.selectedNode.key === node.key) ? undefined : node;
38+
updateData({ selectedNode });
39+
};
40+
41+
/** Function to handle show the children of a node
42+
* if child nodes are available, just expand the tree.
43+
* else, request an API to fetch the child nodes and update the results.
44+
*/
45+
const expandTree = (item, node) => {
46+
if (item.nodes && item.nodes.length > 0) {
47+
item.state.expanded = true;
48+
updateData({ list: [...data.list] });
49+
} else {
50+
http.get(`${treeType.url}?id=${node.key}`)
51+
.then((response) => {
52+
item.nodes = response;
53+
item.state.expanded = true;
54+
updateData({ list: [...data.list] });
55+
});
56+
}
57+
};
58+
59+
/** Function to collapse the tree to hide the children */
60+
const collapseTree = (item) => {
61+
item.state.expanded = false;
62+
updateData({ list: [...data.list] });
63+
};
64+
65+
/** Function to expand/collapse the tree. */
66+
const toggleTree = (node) => {
67+
const item = findNodeByKey(data.list, node.key);
68+
if (item) {
69+
if (node.state.expanded) {
70+
collapseTree(item);
71+
} else {
72+
expandTree(item, node);
73+
}
74+
}
75+
};
76+
77+
/** Function to handle the click events of tree node. */
78+
const loadSelectedNode = (node) => (selectableItem(node, treeType.selectKey)
79+
? selectNode(node)
80+
: toggleTree(node));
81+
82+
/** Function to render the tree contents. */
83+
const renderTree = (list) => (list && list.map((child) => (
84+
<MiqTreeNode
85+
key={child.key}
86+
node={child}
87+
selectedNode={data.selectedNode}
88+
selectKey={treeType.selectKey}
89+
onSelect={(node) => loadSelectedNode(node)}
90+
/>
91+
)));
92+
93+
/** Function to render the modal contents. */
94+
const renderTreeContent = () => ((data.list && data.list.length > 0) ? renderTree(data.list) : undefined);
95+
96+
return (
97+
<div>
98+
{
99+
data.isLoading
100+
? <Loading active small withOverlay={false} className="loading" />
101+
: renderTreeContent()
102+
}
103+
</div>
104+
);
105+
};
106+
107+
export default MiqTree;
108+
109+
MiqTree.propTypes = {
110+
type: PropTypes.string.isRequired,
111+
onNodeSelect: PropTypes.func.isRequired,
112+
};

0 commit comments

Comments
 (0)