(function () { this.treeDiagram = function (selector, data, options) { let defaults = { itemsWidth: null, fullScreenContainer: false, lineColor: '#333333', customItemStyle: false, toggleGroups: { show: false, customClass: 'toggle-group-button', customStyle: false }, draggable: { show: false, customClass: 'reset-draggable', customStyle: false, resetButtonText: 'Reset drag', insertElement: { position: 'beforeend', element: 'body' } }, zoom: { show: false, default: 1, customClass: 'buttons-zoom-container', customStyle: false, zoomInText: 'Zoom in', zoomOutText: 'Zoom out', insertElement: { position: 'beforeend', element: 'body' } } }; this.settings = (arguments[2] && typeof arguments[2] === 'object') ? extendDefaults(defaults, arguments[2]) : defaults; this.init(selector, data, options); }; /*** Public Methods */ treeDiagram.prototype.init = function (selector, data) { this.container = document.querySelector(selector); this.diagram = this.container.firstElementChild; this.treeBlock = this.diagram.firstElementChild; this.treeItem = this.treeBlock.firstElementChild; this.treeBlock.remove(); this.data = data; const createDone = create.call(this); if (createDone) { if (this.settings.toggleGroups.show) { initToggleButtons.call(this); } initStyles.call(this); insertStyles.call(this); if (this.settings.draggable.show) { initDraggable.call(this); } if (this.settings.zoom.show) { initZoom.call(this); } } }; function create() { this.diagram.insertAdjacentElement("beforeend", createTreeBlock.call(this, this.data)); if (this.data.hasOwnProperty('children')) { recursiveCreate.call(this, this.data['children'], this.data['id']); } return true; } /*** Private Methods */ function createTreeBlock(obj) { const resultHtml = this.treeBlock.cloneNode(true); resultHtml.setAttribute('data-id', obj['id']); for (let key in obj) { if (key !== 'id' && key !== 'children') { const el = resultHtml.querySelector(`[data-field="${key}"]`); if (el) { if (el.tagName === 'IMG') { el.src = obj[key]; } else if (el.tagName === 'A') { if (typeof obj[key] === 'object') { const items = Object.keys(obj[key]); el.innerHTML = obj[key][items[0]]; el.href = obj[key][items[1]]; } else { el.href = obj[key]; } } else { el.innerHTML = obj[key]; } } } } return resultHtml; } function recursiveCreate(array, id) { const treeGroupEl = document.createElement('div'); const treeBlockParent = this.diagram.querySelector(`[data-id="${id}"]`); const treeItem = treeBlockParent.firstElementChild; treeGroupEl.classList = 'tree-diagram-group show'; treeGroupEl.setAttribute('data-group', id); treeItem.classList.add('show-group'); if (array.length > 0) { treeItem.classList.add('has-children'); } if (array.length > 1) { treeItem.classList.add('has-children-more-one'); treeGroupEl.classList.add('multi-line'); } if (this.settings.toggleGroups.show) { initToggleButton.call(this, id); } const parentDiagram = treeBlockParent.appendChild(treeGroupEl); array.forEach(item => { parentDiagram.insertAdjacentElement("beforeend", createTreeBlock.call(this, item)); if (item.hasOwnProperty('children')) { recursiveCreate.call(this, item['children'], item.id); } }); } function initToggleButton(id) { const treeBlockParent = this.diagram.querySelector(`[data-id="${id}"]`); const treeItem = treeBlockParent.firstElementChild; const toggleButton = document.createElement('span'); toggleButton.classList = `${this.settings.toggleGroups.customClass} show`; toggleButton.setAttribute('data-toggle', id); treeItem.appendChild(toggleButton); } function initStyles() { this.style = document.createElement('style'); this.head = document.head || document.getElementsByTagName('head')[0]; const classDiagramContainer = this.container.classList.value; const classDiagram = this.diagram.classList.value; const classTreeBlock = this.treeBlock.classList.value; const classTreeItem = this.treeItem.classList.value; let widthTreeItem = null; if(this.settings.itemsWidth === null){ widthTreeItem = 'auto'; } else { widthTreeItem = this.settings.itemsWidth + 'px'; } console.log(widthTreeItem); this.style.textContent = ` .${classDiagramContainer}{ overflow: hidden; } .${classDiagram} { position: relative; text-align: center; transition: background-color 0.2s ease; } .${classDiagram}.draggable{ background-color: #f2f2f2; } .${classDiagram} > .${classTreeBlock} > .${classTreeItem}::after{ content: none; } .${classTreeItem} { position: relative; display: inline-block; width: ${widthTreeItem}; } .${classTreeItem}::after { content: ''; position: absolute; top: -21px; left: 50%; transform: translateX(-50%); height: 20px; width: 1px; background-color: ${this.settings.lineColor}; } .${classTreeItem}.has-children::before { content: ''; position: absolute; bottom: -41px; height: 40px; left: 50%; transform: translateX(-50%); width: 1px; background-color: ${this.settings.lineColor}; } .${classTreeItem}.show-group::before{ bottom: -61px; height: 60px; } .${classTreeBlock} { display: inline-block; margin-left: 20px; } .${classTreeBlock}:first-child { margin-left: 0; } .tree-diagram-group { display: flex; align-items: flex-start; margin-top: 60px; padding-top: 20px; position: relative; visibility: hidden; opacity: 0; transition: 0.3s all ease; } .tree-diagram-group.show{ visibility: visible; opacity: 1; } .tree-diagram-group.multi-line::before { content: ''; position: absolute; top: 0; height: 1px; background-color: ${this.settings.lineColor}; } `; if (!this.settings.customItemStyle) { const itemStyle = ` .${classTreeItem} { padding: 20px; border-radius: 6px; border: 1px solid #ddd; background-color: #ddd; }`; this.style.insertAdjacentText('beforeend', itemStyle); } if (!this.settings.toggleGroups.customStyle) { const toggleGroupButton = ` .${classTreeItem} .${this.settings.toggleGroups.customClass}{ position: absolute; bottom: -45px; left: 50%; transform: translateX(-50%); width: 30px; height: 30px; border-radius: 50%; background-color: #333; cursor: pointer; } .${classTreeItem} .${this.settings.toggleGroups.customClass}::before{ content: '+'; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); font-size: 25px; line-height: 1; color: #fff; } .${classTreeItem} .${this.settings.toggleGroups.customClass}.show::before{ content: '-'; top: calc(50% - 2px); }`; this.style.insertAdjacentText('beforeend', toggleGroupButton); } this.head.appendChild(this.style); } function insertStyles() { const elementsOfGroup = this.diagram.querySelectorAll('[data-group]'); if (elementsOfGroup) { elementsOfGroup.forEach(item => { const childItem = item.querySelectorAll(`.${this.treeBlock.classList.value}`); const dataGroup = item.getAttribute('data-group'); let widthLeft = this.settings.itemsWidth / 2; let widthRight = this.settings.itemsWidth / 2; if (childItem.length > 0) { widthLeft = (childItem[0].offsetWidth / 2); widthRight = childItem[childItem.length - 1].offsetWidth / 2; } const treeGroupBefore = ` .tree-diagram-group[data-group="${dataGroup}"].multi-line::before{ left: ${widthLeft}px; right: ${widthRight}px; }`; this.style.insertAdjacentText('beforeend', treeGroupBefore); this.head.appendChild(this.style); }); } } function initToggleButtons() { const toggleButtons = this.diagram.querySelectorAll('[data-toggle]'); toggleButtons.forEach(button => { const id = button.getAttribute('data-toggle'); const group = this.diagram.querySelector(`[data-group='${id}']`); button.addEventListener('click', function (event) { group.classList.toggle('show'); button.parentElement.classList.toggle('show-group'); button.classList.toggle('show'); }); }); } function initZoom() { const diagram = this.diagram; const buttonsZoomContainer = document.createElement('div'); const buttonZoomIn = document.createElement('button'); const buttonZoomOut = document.createElement('button'); buttonsZoomContainer.classList = `${this.settings.zoom.customClass}`; buttonZoomIn.classList = 'zoom-in'; buttonZoomOut.classList = 'zoom-out'; buttonZoomIn.textContent = this.settings.zoom.zoomInText; buttonZoomOut.textContent = this.settings.zoom.zoomOutText; buttonsZoomContainer.insertAdjacentElement('beforeend', buttonZoomIn); buttonsZoomContainer.insertAdjacentElement('beforeend', buttonZoomOut); document.querySelector(this.settings.zoom.insertElement.element).insertAdjacentElement(this.settings.zoom.insertElement.position, buttonsZoomContainer); diagram.style.zoom = this.settings.zoom.default; document.body.querySelector(`.${this.settings.zoom.customClass} .zoom-in`).addEventListener('click', function () { diagram.style.zoom = +diagram.style.zoom + 0.12; }); document.body.querySelector(`.${this.settings.zoom.customClass} .zoom-out`).addEventListener('click', function () { diagram.style.zoom = +diagram.style.zoom - 0.12; }); if (!this.settings.zoom.customStyle && this.settings.fullScreenContainer) { const style = ` .${this.settings.zoom.customClass}{ position: fixed; bottom: 20px; right: 20px; z-index: 1001; }`; this.style.insertAdjacentText('beforeend', style); } } function initDraggable() { const diagramContainer = this.container; const diagram = this.diagram; const resetButtonBlock = document.createElement('div'); const resetButton = document.createElement('button'); resetButtonBlock.classList = this.settings.draggable.customClass; resetButton.textContent = this.settings.draggable.resetButtonText; resetButtonBlock.insertAdjacentElement('beforeend', resetButton); document.querySelector(this.settings.draggable.insertElement.element).insertAdjacentElement(this.settings.draggable.insertElement.position, resetButtonBlock); diagram.style.width = diagram.scrollWidth + 'px'; if (this.settings.fullScreenContainer) { diagramContainer.style.width = window.innerWidth + 'px'; diagramContainer.style.height = window.innerHeight + 'px'; diagramContainer.style.overflow = 'hidden'; window.addEventListener('resize', function () { diagramContainer.style.width = window.innerWidth + 'px'; diagramContainer.style.height = window.innerHeight + 'px'; }); } let isDrag = false; let xDrag = 0; let yDrag = 0; function startDrag(event) { isDrag = true; xDrag = event.clientX - diagram.offsetLeft; yDrag = event.clientY - diagram.offsetTop; } function stop_drag() { isDrag = false; diagram.classList.remove('draggable'); } function while_drag(event) { diagramContainer.addEventListener('mouseleave', () => { return stop_drag(); }); if (isDrag) { event.cancelBubble = true; event.returnValue = false; diagram.style.left = (event.clientX - xDrag) + 'px'; diagram.style.top = (event.clientY - yDrag) + 'px'; diagram.classList.add('draggable'); } } document.body.querySelector(`.${this.settings.draggable.customClass} button`).addEventListener('click', function () { diagram.style.left = 0; diagram.style.top = 0; }); diagramContainer.addEventListener('mousedown', startDrag); diagramContainer.addEventListener('mousemove', while_drag); diagramContainer.addEventListener('mouseup', stop_drag); if (!this.settings.draggable.customStyle && this.settings.fullScreenContainer) { const style = ` .${this.settings.draggable.customClass}{ position: fixed; bottom: 50px; right: 20px; z-index: 1001; }`; this.style.insertAdjacentText('beforeend', style); } } function extendDefaults(defaults, properties) { Object.keys(properties).forEach(property => { if (properties.hasOwnProperty(property)) { defaults[property] = properties[property]; } }); return defaults; }; }());