UNPKG

orgchart

Version:

Simple and direct organization chart(tree-like hierarchy) library based on pure DOM.

261 lines (246 loc) 11.1 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>OrgChart Edit Hybrid 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 { padding-bottom: 20px; } .orgchart.edit-state .edge, .orgchart.edit-state .toggleBtn { 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 Hybrid Chart Demo</h1> <p>This demo applies the same add, delete, and reset editing flow to a hybrid horizontal and vertical chart.</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) { chartElement.classList.toggle('edit-state', isEditMode); } }; let oc = new OrgChart({ chartContainer: '#chart-container', 'data' : datasource, 'verticalLevel': 3, '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', 'verticalLevel': 3, '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 rel = nodeVals.length > 1 ? '110' : '100'; oc.addChildren(selectedNode, nodeVals.map((item) => ({ 'name': item, 'relationship': rel, '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>