437 lines
16 KiB
JavaScript
437 lines
16 KiB
JavaScript
(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;
|
|
};
|
|
|
|
}());
|