orgchart
Version:
Simple and direct organization chart(tree-like hierarchy) library based on pure DOM.
270 lines (256 loc) • 10.7 kB
HTML
<html lang="en">
<head>
<meta charset="utf-8">
<title>OrgChart Edit Chart Demo</title>
<link rel="icon" href="img/logo.png">
<link rel="stylesheet" href="css/orgchart.css">
<link rel="stylesheet" href="css/style.css">
<style type="text/css">
#chart-container { height: 500px; }
.orgchart.edit-state .edge { display: none; }
.orgchart .node .title { height: 30px; line-height: 30px; }
#edit-panel { margin: 0.5rem; padding: 0.5rem; border: 1px solid #aaa; }
#edit-panel .btn-inputs { width: 2rem; padding: 0; font-size: 1.5rem; }
#edit-panel .btn-inputs { vertical-align: sub; }
#edit-panel.edit-parent-node .btn-inputs { display: none; }
#edit-panel.edit-state>:not(#chart-state-panel) { display: none; }
#edit-panel label { font-weight: bold; }
#edit-panel.edit-parent-node .selected-node-group { display: none; }
#chart-state-panel, #selected-node, #btn-remove-input { margin-right: 1rem; }
#edit-panel button, #edit-panel input { padding: 0.5rem 1rem; font-size: 1rem; }
#edit-panel.edit-parent-node button:not(#btn-add-nodes) { display: none; }
#new-nodelist {
display: inline-block;
list-style:none;
margin-top: -0.5rem;
padding: 0;
vertical-align: text-top;
}
#new-nodelist>* { padding-bottom: 4px; }
.radio-panel input[type='radio'] { height: 1.25rem; width: 1.25rem; vertical-align: text-bottom; }
#btn-add-nodes { margin-left: 20px; }
</style>
</head>
<body>
<section class="demo-notes">
<h1>Edit Chart Demo</h1>
<p>This demo uses the edit panel to add parent, child, and sibling nodes, delete nodes, and reset the chart state.</p>
</section>
<div id="chart-container"></div>
<div id="edit-panel">
<span id="chart-state-panel" class="radio-panel">
<input type="radio" name="chart-state" id="rd-view" value="view"><label for="rd-view">View</label>
<input type="radio" name="chart-state" id="rd-edit" value="edit" checked="true"><label for="rd-edit">Edit</label>
</span>
<label class="selected-node-group">selected node:</label>
<input type="text" id="selected-node" class="selected-node-group">
<label>new node:</label>
<ul id="new-nodelist">
<li><input type="text" class="new-node"></li>
</ul>
<button class="btn-inputs" id="btn-add-input">+</button>
<button class="btn-inputs" id="btn-remove-input">-</button>
<span id="node-type-panel" class="radio-panel">
<input type="radio" name="node-type" id="rd-parent" value="parent"><label for="rd-parent">Parent(root)</label>
<input type="radio" name="node-type" id="rd-child" value="children"><label for="rd-child">Child</label>
<input type="radio" name="node-type" id="rd-sibling" value="siblings"><label for="rd-sibling">Sibling</label>
</span>
<button type="button" id="btn-add-nodes">Add</button>
<button type="button" id="btn-delete-nodes">Delete</button>
<button type="button" id="btn-reset">Reset</button>
</div>
<script type="text/javascript" src="js/html2canvas.min.js"></script>
<script type="text/javascript" src="js/orgchart.js"></script>
<script type="text/javascript">
document.addEventListener('DOMContentLoaded', () => {
const chartContainer = document.getElementById('chart-container');
const selectedNodeInput = document.getElementById('selected-node');
const editPanel = document.getElementById('edit-panel');
const newNodeList = document.getElementById('new-nodelist');
const nodeTypePanel = document.getElementById('node-type-panel');
let selectedNode = null;
const datasource = {
'name': 'Ball game',
'children': [
{ 'name': 'Football' },
{ 'name': 'Basketball' },
{ 'name': 'Volleyball' }
]
};
const getId = () => (Date.now() * 1000) + Math.floor(Math.random() * 1001);
const getRootNode = () => chartContainer.querySelector('.orgchart .node');
const getCheckedNodeType = () => document.querySelector('input[name="node-type"]:checked');
const getCheckedChartState = () => document.querySelector('input[name="chart-state"]:checked');
const getNewNodeValues = () => Array.from(newNodeList.querySelectorAll('.new-node'))
.map((input) => input.value.trim())
.filter(Boolean);
const clearSelection = () => {
selectedNode = null;
selectedNodeInput.value = '';
};
const syncChartEditState = () => {
const isEditMode = getCheckedChartState()?.value === 'edit';
const chartElement = chartContainer.querySelector('.orgchart');
if (!chartElement) {
return;
}
chartElement.classList.toggle('edit-state', isEditMode);
};
let oc = new OrgChart({
chartContainer: '#chart-container',
'data' : datasource,
'createNode': function(node) {
node.id = getId();
}
});
chartContainer.addEventListener('click', (event) => {
const clickedNode = event.target.closest('.node');
if (clickedNode && chartContainer.contains(clickedNode)) {
selectedNode = clickedNode;
const titleElement = clickedNode.querySelector('.title');
selectedNodeInput.value = titleElement ? titleElement.textContent.trim() : '';
return;
}
if (event.target.closest('.orgchart')) {
clearSelection();
}
});
document.querySelectorAll('input[name="chart-state"]').forEach((radio) => {
radio.addEventListener('click', () => {
const isEditMode = radio.value === 'edit';
const chartElement = chartContainer.querySelector('.orgchart');
if (chartElement) {
chartElement.classList.toggle('edit-state', isEditMode);
}
editPanel.classList.toggle('edit-state', !isEditMode);
if (isEditMode) {
const orgchart = chartContainer.querySelector('.orgchart');
if (orgchart) {
orgchart.querySelectorAll('.hidden').forEach((element) => element.classList.remove('hidden'));
orgchart.querySelectorAll('.hierarchy').forEach((element) => {
element.classList.remove('isCollapsedDescendant', 'isChildrenCollapsed', 'isSiblingsCollapsed', 'isCollapsedSibling');
});
orgchart.querySelectorAll('.node').forEach((element) => {
element.classList.remove('slide-up', 'slide-down', 'slide-right', 'slide-left');
});
}
} else {
document.getElementById('btn-reset').click();
}
});
});
document.querySelectorAll('input[name="node-type"]').forEach((radio) => {
radio.addEventListener('click', () => {
if (radio.value === 'parent') {
editPanel.classList.add('edit-parent-node');
Array.from(newNodeList.children).slice(1).forEach((item) => item.remove());
} else {
editPanel.classList.remove('edit-parent-node');
}
});
});
document.getElementById('btn-add-input').addEventListener('click', () => {
const item = document.createElement('li');
item.innerHTML = '<input type="text" class="new-node">';
newNodeList.appendChild(item);
});
document.getElementById('btn-remove-input').addEventListener('click', () => {
if (newNodeList.children.length > 1) {
newNodeList.lastElementChild.remove();
}
});
document.getElementById('btn-add-nodes').addEventListener('click', () => {
const nodeVals = getNewNodeValues();
const nodeType = getCheckedNodeType();
const rootNode = getRootNode();
if (!nodeVals.length) {
alert('Please input value for new node');
return;
}
if (!nodeType) {
alert('Please select a node type');
return;
}
if (nodeType.value !== 'parent' && !chartContainer.querySelector('.orgchart')) {
alert('Please creat the root node firstly when you want to build up the orgchart from the scratch');
return;
}
if (nodeType.value !== 'parent' && !selectedNode) {
alert('Please select one node in orgchart');
return;
}
if (nodeType.value === 'parent') {
if (!chartContainer.querySelector('.orgchart')) {
oc = new OrgChart({
chartContainer: '#chart-container',
'data' : { 'name': nodeVals[0] },
'chartClass': 'edit-state',
'exportButton': true,
'exportFilename': 'SportsChart',
'createNode': function(node) {
node.id = getId();
}
});
syncChartEditState();
} else {
oc.addParent(selectedNode || rootNode, { 'name': nodeVals[0], 'id': getId() });
}
return;
}
if (nodeType.value === 'siblings') {
if (selectedNode.id === rootNode.id) {
alert('You are not allowed to directly add sibling nodes to root node');
return;
}
oc.addSiblings(selectedNode, nodeVals.map((item) => ({
'name': item,
'relationship': '110',
'id': getId()
})));
return;
}
const childLevel = selectedNode.nextElementSibling;
if (!childLevel || !childLevel.classList.contains('nodes')) {
const relationship = nodeVals.length > 1 ? '110' : '100';
oc.addChildren(selectedNode, nodeVals.map((item) => ({
'name': item,
'relationship': relationship,
'id': getId()
})));
} else {
oc.addSiblings(childLevel.querySelector('.node'), nodeVals.map((item) => ({
'name': item,
'relationship': '110',
'id': getId()
})));
}
});
document.getElementById('btn-delete-nodes').addEventListener('click', () => {
const rootNode = getRootNode();
if (!selectedNode) {
alert('Please select one node in orgchart');
return;
}
if (rootNode && selectedNode === rootNode && !window.confirm('Are you sure you want to delete the whole chart?')) {
return;
}
oc.removeNodes(selectedNode);
clearSelection();
});
document.getElementById('btn-reset').addEventListener('click', () => {
chartContainer.querySelectorAll('.focused').forEach((element) => element.classList.remove('focused'));
clearSelection();
const inputs = newNodeList.querySelectorAll('input');
if (inputs.length) {
inputs[0].value = '';
}
Array.from(newNodeList.children).slice(1).forEach((item) => item.remove());
nodeTypePanel.querySelectorAll('input').forEach((input) => {
input.checked = false;
});
});
});
</script>
</body>
</html>