You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1037 lines
35 KiB
1037 lines
35 KiB
2 years ago
|
//Pickle tree component created by Kadir Barış Bozat
|
||
|
|
||
|
class PickleTree {
|
||
|
/**
|
||
|
*
|
||
|
* @param {object} obj as tree object
|
||
|
*/
|
||
|
constructor(obj) {
|
||
|
//target div id
|
||
|
this.target = obj.c_target;
|
||
|
//building area
|
||
|
this.area = "";
|
||
|
//available nodes list
|
||
|
this.nodeList = {};
|
||
|
//row create callback
|
||
|
this.rowCreateCallback = obj.rowCreateCallback;
|
||
|
//draw callback
|
||
|
this.drawCallback = obj.drawCallback;
|
||
|
//switch callback
|
||
|
this.switchCallback = obj.switchCallback;
|
||
|
//drag callback
|
||
|
this.dragCallback = obj.dragCallback;
|
||
|
//drop callback
|
||
|
this.dropCallback = obj.dropCallback;
|
||
|
//order callback
|
||
|
this.orderCallback = obj.orderCallback;
|
||
|
//node removed callback
|
||
|
this.nodeRemove = obj.nodeRemoveCallback;
|
||
|
//tree json data
|
||
|
this.data = obj.c_data;
|
||
|
//build tree
|
||
|
this.build(obj.c_config);
|
||
|
//start events
|
||
|
this.staticEvents();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* this method will contains static events for tree
|
||
|
*/
|
||
|
staticEvents() {
|
||
|
//close menu
|
||
|
this.main_container.addEventListener("click", (e) => {
|
||
|
let elm = e.target;
|
||
|
//close all first
|
||
|
document.querySelectorAll(".ptreemenuCont").forEach((menu) => {
|
||
|
menu.outerHTML = "";
|
||
|
});
|
||
|
if (elm.classList.contains("menuIcon")) {
|
||
|
//menu toggle event for node
|
||
|
setTimeout(() => {
|
||
|
this.getMenu(e.target, this.getNode(elm.id.split("node_")[1]));
|
||
|
}, 10);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
//drag - drop events
|
||
|
if (this.config.drag) {
|
||
|
this.invalid_area = {
|
||
|
container: null,
|
||
|
top: 0,
|
||
|
left: 0,
|
||
|
right: 0,
|
||
|
bottom: 0,
|
||
|
};
|
||
|
|
||
|
//drag start
|
||
|
this.main_container.addEventListener("dragstart", async (e) => {
|
||
|
//give border to container
|
||
|
//container
|
||
|
this.invalid_area.container = document.getElementById(this.target + "node_" + e.target.id.split("node_")[1]);
|
||
|
this.invalid_area.top = this.invalid_area.container.getBoundingClientRect().top;
|
||
|
this.invalid_area.left = this.invalid_area.container.getBoundingClientRect().left;
|
||
|
this.invalid_area.right = this.invalid_area.left + this.invalid_area.container.offsetWidth;
|
||
|
this.invalid_area.bottom = this.invalid_area.top + this.invalid_area.container.offsetHeight;
|
||
|
setTimeout(() => {
|
||
|
this.invalid_area.container.classList.add("valid");
|
||
|
this._lock();
|
||
|
}, 300);
|
||
|
//drag callback
|
||
|
if (this.dragCallback) {
|
||
|
this.dragCallback(this.nodeList[parseInt(e.target.id.split("node_")[1])]);
|
||
|
}
|
||
|
/////// ***** ///////
|
||
|
//draging
|
||
|
//clone element when drag start
|
||
|
const id = e.target.id.split("node_")[1];
|
||
|
this.clone = document.getElementById(this.target +'node_' + id).cloneNode(true);
|
||
|
this.clone.style.position = 'absolute';
|
||
|
this.clone.style.zIndex = 1000;
|
||
|
this.clone.querySelectorAll('div').forEach(el=>el.style.backgroundColor = 'grey');
|
||
|
this.clone.querySelectorAll('li').forEach(el=>el.style.border = 'unset !important');
|
||
|
this.clone.style.width = '50vh';
|
||
|
//this.clone.querySelector('ul').remove();
|
||
|
this.clone.querySelectorAll('.switch').forEach(el => el.remove());
|
||
|
const rul = document.createElement('ul');
|
||
|
rul.appendChild(this.clone);
|
||
|
const rdiv = document.createElement('div');
|
||
|
rdiv.appendChild(rul);
|
||
|
rdiv.classList.add('dragging-element');
|
||
|
this.clone = rdiv;
|
||
|
|
||
|
this.main_container.appendChild(this.clone);
|
||
|
/////// ***** ///////
|
||
|
});
|
||
|
|
||
|
//draging
|
||
|
this.main_container.addEventListener("drag", (e) => {
|
||
|
const bounds = e.currentTarget.getBoundingClientRect();
|
||
|
/////// ***** ///////
|
||
|
this.clone.style.position = "absolute";
|
||
|
this.clone.style.left = `${e.clientX - bounds.left + 30}px`;
|
||
|
this.clone.style.top = `${e.clientY - bounds.top + 30 }px`;
|
||
|
/////// ***** ///////
|
||
|
});
|
||
|
|
||
|
//drag end
|
||
|
this.main_container.addEventListener("dragend", async (e) => {
|
||
|
this.clone.remove();
|
||
|
//console.log('drag end')
|
||
|
//remove border to container
|
||
|
this.invalid_area.container.classList.remove("invalid");
|
||
|
this.invalid_area.container.classList.remove("valid");
|
||
|
//make all elements pointer clean
|
||
|
this._lock(false);
|
||
|
|
||
|
//clear old targets
|
||
|
this.clearDebris();
|
||
|
//get node
|
||
|
|
||
|
const node = this.nodeList[parseInt(e.target.id.split("node_")[1])];
|
||
|
|
||
|
//check is valid
|
||
|
node.old_parent = node.parent;
|
||
|
if (!this.invalid_area.valid) {
|
||
|
node.parent = {
|
||
|
id: 0
|
||
|
};
|
||
|
} else {
|
||
|
console.log('target' , this.drag_target);
|
||
|
const drop = this.getNode(this.drag_target);
|
||
|
if (this.drag_target === parseInt(e.target.id.split("node_")[1]) || this.drag_target === undefined || drop === undefined || drop.parent.value === node.value) {
|
||
|
//this means it dragged to outside
|
||
|
node.parent = {
|
||
|
id: 0
|
||
|
};
|
||
|
} else {
|
||
|
node.parent = drop;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
this.nodeList[node.value] = node.updateNode();
|
||
|
|
||
|
console.log(this.nodeList[node.value]);
|
||
|
|
||
|
|
||
|
//drop callback
|
||
|
if (this.dropCallback) {
|
||
|
this.dropCallback(node);
|
||
|
}
|
||
|
});
|
||
|
//drag location
|
||
|
this.main_container.addEventListener("dragenter", (e) => {
|
||
|
//console.log('drag enter')
|
||
|
this.clearDebris();
|
||
|
try {
|
||
|
//check position is valid
|
||
|
let target = {
|
||
|
left: e.target.getBoundingClientRect().left,
|
||
|
top: e.target.getBoundingClientRect().top,
|
||
|
};
|
||
|
|
||
|
if (target.top > this.invalid_area.top && target.top < this.invalid_area.bottom && target.left > this.invalid_area.left && target.left < this.invalid_area.right) {
|
||
|
this.invalid_area.valid = false;
|
||
|
this.invalid_area.container.classList.add("invalid");
|
||
|
this.invalid_area.container.classList.remove("valid");
|
||
|
} else {
|
||
|
this.invalid_area.valid = true;
|
||
|
this.invalid_area.container.classList.remove("invalid");
|
||
|
this.invalid_area.container.classList.add("valid");
|
||
|
}
|
||
|
|
||
|
if (e.target.classList) {
|
||
|
if (e.target.classList.contains("drop_target")) {
|
||
|
e.target.classList.add("drag_triggered");
|
||
|
//this is for updating node parent to current
|
||
|
this.drag_target = parseInt(e.target.id.split("node_")[1]);
|
||
|
}
|
||
|
}
|
||
|
} catch (e) {
|
||
|
//console.log('dragging have exception..');
|
||
|
this.drag_target = undefined;
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//#region Helper Methods
|
||
|
/**
|
||
|
*
|
||
|
*/
|
||
|
async destroy() {
|
||
|
//remove all menus
|
||
|
document.querySelectorAll(".ptreemenuCont").forEach((menu) => {
|
||
|
menu.outerHTML = "";
|
||
|
});
|
||
|
//remove all items
|
||
|
document.getElementById(this.target).innerHTML = "";
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* this method will lock elements when dragging
|
||
|
*/
|
||
|
async _lock(type = true) {
|
||
|
const elms = document.querySelectorAll(".drop_target");
|
||
|
for (let i = 0; i < elms.length; i++) {
|
||
|
if (type) {
|
||
|
elms[i].classList.add("disabled");
|
||
|
} else {
|
||
|
elms[i].classList.remove("disabled");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
*
|
||
|
* @param {string} message for log messages
|
||
|
*/
|
||
|
log(message) {
|
||
|
if (this.config.logMode) {
|
||
|
console.log(message);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Building main details
|
||
|
*/
|
||
|
build(c_config) {
|
||
|
//set default config
|
||
|
this.config = {
|
||
|
key: new Date().getTime(),
|
||
|
//logs are open or close
|
||
|
logMode: false,
|
||
|
//switch mode
|
||
|
switchMode: false,
|
||
|
hasLink: false,
|
||
|
//family mode
|
||
|
//for child
|
||
|
autoChild: true,
|
||
|
//for parent
|
||
|
autoParent: true,
|
||
|
//fold icon
|
||
|
foldedIcon: "fa fa-plus",
|
||
|
//unfold icon
|
||
|
unFoldedIcon: "fa fa-minus",
|
||
|
//menu icon
|
||
|
menuIcon: ["fa", "fa-list-ul"],
|
||
|
//link icon
|
||
|
linkIcon: "fa fa-list-ul",
|
||
|
//start status is collapsed or not
|
||
|
foldedStatus: false,
|
||
|
//drag
|
||
|
drag: false,
|
||
|
//order
|
||
|
order: false,
|
||
|
// context menu position
|
||
|
contextPos: 'after'
|
||
|
};
|
||
|
//check config here!!
|
||
|
for (let key in this.config) {
|
||
|
if (c_config[key] !== undefined) {
|
||
|
this.config[key] = c_config[key];
|
||
|
}
|
||
|
}
|
||
|
//check if key is exist somewhere in document
|
||
|
if (document.getElementById(this.config.key + "_div_pickletree") !== null) {
|
||
|
this.config.key = new Date().getTime() + 10;
|
||
|
}
|
||
|
//referance for some events
|
||
|
this.main_container = document.getElementById(this.target);
|
||
|
this.main_container.classList.add('ptree');
|
||
|
this.main_container.innerHTML = '<div id="' + this.config.key + '_div_pickletree"><ul id="' + this.config.key + '_tree_picklemain"></ul></div>';
|
||
|
//console.log(this.main_container.getElementById(this.config.key+'_tree_picklemain'));
|
||
|
|
||
|
this.area = document.getElementById(this.config.key + "_tree_picklemain");
|
||
|
this.log("tree build started..");
|
||
|
this.drawData();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
*
|
||
|
* @param {integer} id node id for finding node
|
||
|
*/
|
||
|
getNode(id) {
|
||
|
this.log("node returned..");
|
||
|
//return node
|
||
|
return this.nodeList[id];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* set child nodes for parent node
|
||
|
* @param {object} node
|
||
|
*/
|
||
|
setChildNodes(node) {
|
||
|
//update node parent
|
||
|
for (let key in this.nodeList) {
|
||
|
if (this.nodeList[key].id === node.parent.id) {
|
||
|
this.nodeList[key].childs.push(node.id);
|
||
|
//show icon for childs
|
||
|
const ic = document.getElementById("i_" + this.nodeList[key].id);
|
||
|
if (ic !== null) ic.style.display = "";
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* this method will return switched nodes
|
||
|
*/
|
||
|
getSelected() {
|
||
|
let nodes = [];
|
||
|
//get all checked nodes
|
||
|
for (let key in this.nodeList) {
|
||
|
if (this.nodeList[key].checkStatus) nodes.push(this.nodeList[key]);
|
||
|
}
|
||
|
return nodes;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* this method will reset switched nodes
|
||
|
*/
|
||
|
resetSelected(type = false) {
|
||
|
//get all checked nodes
|
||
|
for (let key in this.nodeList) {
|
||
|
this.nodeList[key].checkStatus = type;
|
||
|
this.checkNode(this.nodeList[key]);
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
//#endregion
|
||
|
|
||
|
//#region drag - drop events helpers
|
||
|
/**
|
||
|
* this method will clean entered areas after drag events
|
||
|
*/
|
||
|
clearDebris() {
|
||
|
//first clean all entered areas
|
||
|
let elms = document.querySelectorAll(".drag_triggered");
|
||
|
for (let i = 0; i < elms.length; i++) {
|
||
|
elms[i].classList.remove("drag_triggered");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//#endregion
|
||
|
|
||
|
//#region Node Events
|
||
|
/**
|
||
|
* this method will order element
|
||
|
* @param {event} e
|
||
|
*/
|
||
|
orderNode(e) {
|
||
|
const isBefore = e.target.dataset.target == 1;
|
||
|
const main = e.target.parentNode.parentNode.parentNode;
|
||
|
const target = isBefore ? main.previousElementSibling : main.nextElementSibling;
|
||
|
//get nodes
|
||
|
if (target !== null) {
|
||
|
|
||
|
//replace data
|
||
|
const targetNode = this.getNode(target.id.split("_").at(-1));
|
||
|
const mainNode = this.getNode(main.id.split("_").at(-1));
|
||
|
const currentOrder = mainNode.order;
|
||
|
const targetOrder = targetNode.order === mainNode.order ? (isBefore ? targetNode.order - 1 : targetNode.order + 1) : targetNode.order;
|
||
|
//change order data
|
||
|
targetNode.order = currentOrder;
|
||
|
mainNode.order = targetOrder;
|
||
|
|
||
|
target.dataset.order = 'order_'+currentOrder;
|
||
|
main.dataset.order = 'order_'+targetOrder;
|
||
|
|
||
|
//replace element
|
||
|
main.parentNode.replaceChild(main, target);
|
||
|
main.parentNode.insertBefore(target, isBefore ? main.nextSibling : main);
|
||
|
}
|
||
|
if (typeof this.orderCallback == "function") this.orderCallback(main, target);
|
||
|
}
|
||
|
/**
|
||
|
* get child nodes list of node
|
||
|
* @param {object} node
|
||
|
*/
|
||
|
getChilds(node) {
|
||
|
let list = [];
|
||
|
for (let key in this.nodeList) {
|
||
|
if (node.childs.includes(this.nodeList[key].id)) {
|
||
|
list.push(this.nodeList[key]);
|
||
|
}
|
||
|
}
|
||
|
this.log("node childs returned..");
|
||
|
return list;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* toggle open or close node childs
|
||
|
* @param {object} node
|
||
|
*/
|
||
|
toggleNode(node) {
|
||
|
if (node.childs.length > 0) {
|
||
|
let ie = document.getElementById("i_" + node.id);
|
||
|
let ule = document.getElementById("c_" + node.id);
|
||
|
if (node.foldedStatus === false) {
|
||
|
//change icon
|
||
|
ie.classList.remove("fa-minus");
|
||
|
ie.classList.add("fa-plus");
|
||
|
//hide element
|
||
|
//ule.style.display = "none";
|
||
|
ule.classList.remove("active");
|
||
|
ule.classList.add("not-active");
|
||
|
} else {
|
||
|
//change icon
|
||
|
ie.classList.remove("fa-plus");
|
||
|
ie.classList.add("fa-minus");
|
||
|
//show element
|
||
|
//ule.style.display = "";
|
||
|
ule.classList.remove("not-active");
|
||
|
ule.classList.add("active");
|
||
|
}
|
||
|
node.foldedStatus = !node.foldedStatus;
|
||
|
//change node status
|
||
|
for (let key in this.nodeList) {
|
||
|
if (this.nodeList[key].id === node.id) {
|
||
|
this.nodeList[key].foldedStatus = node.foldedStatus;
|
||
|
}
|
||
|
}
|
||
|
this.log("node toggled..");
|
||
|
} else {
|
||
|
this.log("node not has childs...!");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* remove node from dom
|
||
|
* @param {object} node
|
||
|
*/
|
||
|
deleteNode(node) {
|
||
|
//remove node from old parent's child data !!!!
|
||
|
|
||
|
let elm = document.getElementById(node.id);
|
||
|
let childs = node.getChilds();
|
||
|
if (childs.length > 0) {
|
||
|
for (let i = 0; i < childs.length; i++) {
|
||
|
this.deleteNode(childs[i]);
|
||
|
}
|
||
|
}
|
||
|
//remove node from container
|
||
|
//delete this.nodeList[node.value];
|
||
|
|
||
|
if (elm !== null) elm.parentNode.removeChild(elm);
|
||
|
this.log("node removed..(" + node.id + ")");
|
||
|
if (this.nodeRemove !== undefined) this.nodeRemove(node);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* this method will check node and its family.
|
||
|
* @param {object} node
|
||
|
*/
|
||
|
checkNode(node) {
|
||
|
//console.log(node);
|
||
|
//then if is checked and folded unfold and open childs
|
||
|
let clength = node.childs.length;
|
||
|
if (node.checkStatus && clength > 0) {
|
||
|
//make element looks like is folded
|
||
|
node.foldedStatus = true;
|
||
|
this.toggleNode(node);
|
||
|
}
|
||
|
//trigger callback if exists
|
||
|
if (typeof this.switchCallback == "function") this.switchCallback(node);
|
||
|
//check html element if family mode is open
|
||
|
document.getElementById("ck_" + node.id).checked = node.checkStatus;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* this method will check node childs and his parents if not checked.
|
||
|
* @param {object} node
|
||
|
*/
|
||
|
checkNodeFamily(node) {
|
||
|
let status = node.checkStatus;
|
||
|
let parentCheck = async (node) => {
|
||
|
//first check if has parent
|
||
|
if (node.parent.id !== 0) {
|
||
|
//get parent node
|
||
|
node = node.parent;
|
||
|
let trans = () => {
|
||
|
//change parent node status
|
||
|
node.checkStatus = status;
|
||
|
//check parent node
|
||
|
this.checkNode(node);
|
||
|
//then restart process
|
||
|
parentCheck(node);
|
||
|
};
|
||
|
//decide for uncheck
|
||
|
if (!status) {
|
||
|
//if all childs is unchecked or child count is equal to 1
|
||
|
let valid = true;
|
||
|
let childs = node.getChilds();
|
||
|
for (let i = 0; i < childs.length; i++) {
|
||
|
if (childs[i].checkStatus) {
|
||
|
valid = false;
|
||
|
}
|
||
|
}
|
||
|
if (valid) trans();
|
||
|
} else {
|
||
|
trans();
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
let childCheck = async (node) => {
|
||
|
//first check main node
|
||
|
this.checkNode(node);
|
||
|
//then check childs if exist
|
||
|
if (node.childs.length > 0) {
|
||
|
//foreach child
|
||
|
for (let i = 0; i < node.childs.length; i++) {
|
||
|
let c_node = this.getNode(node.childs[i].split("node_")[1]);
|
||
|
c_node.checkStatus = status;
|
||
|
//restart process
|
||
|
childCheck(c_node);
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
if (this.config.autoChild) childCheck(node);
|
||
|
if (this.config.autoParent) parentCheck(node);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* this method will unfold all parents of node
|
||
|
* @param {object} node
|
||
|
*/
|
||
|
async showFamily(node) {
|
||
|
//check if has parent
|
||
|
if (node.parent.id !== 0) {
|
||
|
//then make node status closed
|
||
|
node.parent.foldedStatus = true;
|
||
|
//after send parent node for toggle
|
||
|
this.toggleNode(node.parent);
|
||
|
//make recursive for another parents
|
||
|
this.showFamily(node.parent);
|
||
|
}
|
||
|
}
|
||
|
//#endregion
|
||
|
|
||
|
//#region Node Creator
|
||
|
|
||
|
/**
|
||
|
* creating node
|
||
|
* @param {object} obj
|
||
|
*/
|
||
|
createNode(obj) {
|
||
|
const id = Date.now();
|
||
|
const node = {
|
||
|
//node value
|
||
|
value: id,
|
||
|
//node id
|
||
|
id: this.target + "node_" + id,
|
||
|
//node title
|
||
|
title: "untitled " + id,
|
||
|
//node html elements
|
||
|
elements: [],
|
||
|
//order number
|
||
|
order: null,
|
||
|
//node parent element
|
||
|
parent: {
|
||
|
id: 0
|
||
|
},
|
||
|
// child element ids
|
||
|
childs: [],
|
||
|
//addional info
|
||
|
addional: {},
|
||
|
//childs status (child list opened or not)
|
||
|
foldedStatus: this.config.foldedStatus,
|
||
|
//check status for node
|
||
|
checkStatus: false,
|
||
|
//this method will return child nodes
|
||
|
getChilds: () => this.getChilds(node),
|
||
|
//this method will remove node from dom
|
||
|
deleteNode: () => this.deleteNode(node),
|
||
|
//this method will update node
|
||
|
updateNode: () => this.updateNode(node),
|
||
|
//this method will toggle node
|
||
|
toggleNode: () => this.toggleNode(node),
|
||
|
//this method will show node location
|
||
|
showFamily: () => this.showFamily(node),
|
||
|
//check node
|
||
|
toggleCheck: (status) => {
|
||
|
node.checkStatus = status;
|
||
|
this.checkNode(node);
|
||
|
},
|
||
|
//scroll to node
|
||
|
scroll:() => document.getElementById(node.id).scrollIntoView(),
|
||
|
//find child nodes from text
|
||
|
find :(text) => {
|
||
|
const nodes = [];
|
||
|
document.getElementById(node.id).querySelectorAll('li').forEach(el=>{
|
||
|
if(el.innerHTML.includes(text)){
|
||
|
nodes.push(this.getNode(el.id.split("node_")[1]))
|
||
|
}
|
||
|
});
|
||
|
return nodes;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
//check setted values here!!
|
||
|
for (let key in obj) {
|
||
|
if (obj[key] !== undefined) node[key.split("_")[1]] = obj[key];
|
||
|
if (key === "n_id") node["id"] = this.target + "node_" + obj["n_id"];
|
||
|
}
|
||
|
|
||
|
if (node.order === null) node.order = 0;
|
||
|
|
||
|
//node is added to container
|
||
|
this.nodeList[obj["n_id"]] = node;
|
||
|
//node is drawed
|
||
|
this.drawNode(node);
|
||
|
//logged
|
||
|
this.log("Node is created (" + node.id + ")");
|
||
|
//node is returned
|
||
|
return node;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* this method will update node
|
||
|
* !! id is recommended
|
||
|
*/
|
||
|
updateNode(node) {
|
||
|
//first remove old node
|
||
|
//console.log(this.getNode(node.id.split('_')[1]))
|
||
|
this.getNode(node.id.split("node_")[1]).deleteNode();
|
||
|
//clear old parent's childs if old parent info is exist
|
||
|
if (node.old_parent !== undefined && node.old_parent.id !== 0) {
|
||
|
this.nodeList[node.old_parent.value].childs = this.nodeList[node.old_parent.value].childs.filter((x) => {
|
||
|
return x !== node.id;
|
||
|
});
|
||
|
//if child count is 0 then remove minus icon
|
||
|
if (this.nodeList[node.old_parent.value].childs.length === 0) {
|
||
|
document.getElementById("i_" + node.old_parent.id).style.display = "none";
|
||
|
}
|
||
|
}
|
||
|
//draw new node with childs
|
||
|
const set = (data) => {
|
||
|
this.drawNode(data);
|
||
|
let childs = data.getChilds();
|
||
|
if (childs.length > 0) {
|
||
|
for (let i = 0; i < childs.length; i++) {
|
||
|
set(childs[i]);
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
set(node);
|
||
|
|
||
|
//log
|
||
|
this.log("Node is created (" + node.id + ")");
|
||
|
//return node
|
||
|
return node;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
*
|
||
|
* @param {object} node object for creating html element
|
||
|
*/
|
||
|
drawNode(node) {
|
||
|
let icon = this.config.unFoldedIcon;
|
||
|
let style = "";
|
||
|
let defaultClass = "active";
|
||
|
|
||
|
if (node.foldedStatus) {
|
||
|
icon = this.config.foldedIcon;
|
||
|
style = "none";
|
||
|
defaultClass = "not-active";
|
||
|
}
|
||
|
//#region elements
|
||
|
|
||
|
//node li item
|
||
|
let li_item = document.createElement("li");
|
||
|
//node a item
|
||
|
let a_item = document.createElement("a");
|
||
|
//node i item
|
||
|
let i_item = document.createElement("i");
|
||
|
//node ul item
|
||
|
let ul_item = document.createElement("ul");
|
||
|
//node group item
|
||
|
let div_item = document.createElement("div");
|
||
|
|
||
|
//make node ordarable
|
||
|
if (this.config.order) {
|
||
|
const o_div = document.createElement("div");
|
||
|
o_div.id = "order_" + node.id;
|
||
|
//create buttons
|
||
|
const up_i = document.createElement("i");
|
||
|
const dw_i = document.createElement("i");
|
||
|
|
||
|
o_div.classList.add("ptree_order_div");
|
||
|
|
||
|
up_i.classList.add("fa", "fa-arrow-up");
|
||
|
up_i.dataset.target = "1";
|
||
|
dw_i.classList.add("fa", "fa-arrow-down");
|
||
|
dw_i.dataset.target = "0";
|
||
|
|
||
|
o_div.appendChild(up_i);
|
||
|
o_div.appendChild(dw_i);
|
||
|
//ordering event
|
||
|
o_div.onclick = (e) => (e.target.tagName == "I" ? this.orderNode(e) : false);
|
||
|
|
||
|
div_item.appendChild(o_div);
|
||
|
}
|
||
|
|
||
|
//make node dragable
|
||
|
if (this.config.drag) {
|
||
|
//add drag button to start
|
||
|
const a_ditem = document.createElement("a");
|
||
|
const i_ditem = document.createElement("i");
|
||
|
//set icon drag button
|
||
|
i_ditem.classList.add("fa");
|
||
|
i_ditem.classList.add("fa-bars");
|
||
|
a_ditem.classList.add("drag-handler");
|
||
|
|
||
|
a_ditem.id = "a_dr_" + node.id;
|
||
|
a_ditem.appendChild(i_ditem);
|
||
|
a_ditem.href = "javascript:;";
|
||
|
a_ditem.setAttribute("dragable", true);
|
||
|
a_ditem.setAttribute("drag-title", node.title);
|
||
|
//icon added to div
|
||
|
div_item.appendChild(a_ditem);
|
||
|
div_item.classList.add("drop_target");
|
||
|
}
|
||
|
|
||
|
//set i item id
|
||
|
i_item.id = "i_" + node.id;
|
||
|
//set i item style
|
||
|
i_item.style.color = "black";
|
||
|
//set i item icon
|
||
|
icon = icon.split(" ");
|
||
|
for (let i = 0; i < icon.length; i++) {
|
||
|
i_item.classList.add(icon[i]);
|
||
|
}
|
||
|
i_item.style.display = "none";
|
||
|
|
||
|
//set ul item id
|
||
|
ul_item.id = "c_" + node.id;
|
||
|
|
||
|
//set ul item style
|
||
|
//ul_item.style.display = style;
|
||
|
|
||
|
//set ul item class
|
||
|
ul_item.classList.add(defaultClass);
|
||
|
|
||
|
//set a item id
|
||
|
a_item.id = "a_toggle_" + node.id;
|
||
|
//set i tag to a item
|
||
|
a_item.appendChild(i_item);
|
||
|
//set a item href
|
||
|
a_item.href = "javascript:;";
|
||
|
//set a_item title
|
||
|
a_item.innerHTML += " " + node.title;
|
||
|
|
||
|
a_item.onclick = (e) => this.toggleNode(node);
|
||
|
|
||
|
//set li item id
|
||
|
li_item.id = node.id;
|
||
|
li_item.dataset.order = "order_" + node.order;
|
||
|
|
||
|
div_item.id = "div_g_" + node.id;
|
||
|
//set a tag to div item
|
||
|
div_item.appendChild(a_item);
|
||
|
|
||
|
|
||
|
//set switch to li item if user is wanted
|
||
|
if (this.config.switchMode) {
|
||
|
const sw_item = document.createElement("label");
|
||
|
const ck_item = document.createElement("input");
|
||
|
const spn_item = document.createElement("span");
|
||
|
spn_item.classList.add("slider");
|
||
|
spn_item.classList.add("round");
|
||
|
ck_item.type = "checkbox";
|
||
|
sw_item.classList.add("switch");
|
||
|
|
||
|
sw_item.appendChild(ck_item);
|
||
|
sw_item.appendChild(spn_item);
|
||
|
|
||
|
//id definitions
|
||
|
ck_item.id = "ck_" + node.id;
|
||
|
sw_item.id = "sw_" + node.id;
|
||
|
|
||
|
ck_item.value = node.value;
|
||
|
//if item created as checked
|
||
|
ck_item.checked = node.checkStatus;
|
||
|
|
||
|
ck_item.onclick = (e) => {
|
||
|
node.checkStatus = e.target.checked;
|
||
|
if (this.config.autoChild || this.config.autoParent) {
|
||
|
this.checkNodeFamily(node);
|
||
|
}else{
|
||
|
this.checkNode(node);
|
||
|
}
|
||
|
|
||
|
};
|
||
|
|
||
|
//switch is added to li element
|
||
|
div_item.appendChild(sw_item);
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
//if node has extra elements
|
||
|
if (node.elements.length > 0) {
|
||
|
//add menu button to end
|
||
|
let a_item = document.createElement("a");
|
||
|
let i_item = document.createElement("i");
|
||
|
//set icon for menu
|
||
|
|
||
|
for (let i = 0; i < this.config.menuIcon.length; i++) {
|
||
|
i_item.classList.add(this.config.menuIcon[i]);
|
||
|
}
|
||
|
|
||
|
a_item.id = "a_me_" + node.id;
|
||
|
a_item.appendChild(i_item);
|
||
|
// a_item.href = "javascript:;";
|
||
|
a_item.classList.add("menuIcon");
|
||
|
//icon added to div
|
||
|
div_item.appendChild(a_item);
|
||
|
}
|
||
|
|
||
|
li_item.appendChild(div_item);
|
||
|
//set ul tag to li item
|
||
|
li_item.appendChild(ul_item);
|
||
|
|
||
|
//#endregion
|
||
|
|
||
|
//if is main node
|
||
|
//check if element is exist for preventing copy elements
|
||
|
if (node.parent.id === 0) {
|
||
|
//put item to area
|
||
|
this.area.appendChild(li_item);
|
||
|
} else {
|
||
|
//if has parent set to parents childs
|
||
|
this.setChildNodes(node);
|
||
|
//then put item
|
||
|
const cont = document.getElementById("c_" + node.parent.id);
|
||
|
if (cont !== null) cont.appendChild(li_item);
|
||
|
}
|
||
|
|
||
|
//node.element = li_item;
|
||
|
|
||
|
//set node events
|
||
|
this.setNodeEvents(node, div_item);
|
||
|
|
||
|
//draw callback method
|
||
|
if (typeof this.rowCreateCallback == "function") this.rowCreateCallback(node);
|
||
|
}
|
||
|
|
||
|
setNodeEvents(node, parent) {
|
||
|
//order event for node
|
||
|
if (this.config.order) {
|
||
|
document.getElementById('order_' + node.id).addEventListener('click', e => {
|
||
|
if (e.target.tagName == 'I') {
|
||
|
const isBefore = e.target.dataset.target == 1;
|
||
|
const main = e.target.parentNode.parentNode.parentNode;
|
||
|
const target = isBefore ? main.previousElementSibling : main.nextElementSibling;
|
||
|
//get nodes
|
||
|
if (target !== null) {
|
||
|
//replace data
|
||
|
const targetNode = this.getNode(target.id.split("_").at(-1));
|
||
|
const mainNode = this.getNode(main.id.split("_").at(-1));
|
||
|
//console.log( mainNode.order,targetNode.order);
|
||
|
const currentOrder = mainNode.order;
|
||
|
const targetOrder = targetNode.order === mainNode.order ? (isBefore ? targetNode.order - 1 : targetNode.order + 1) : targetNode.order;
|
||
|
//change order data
|
||
|
targetNode.order = currentOrder;
|
||
|
mainNode.order = targetOrder;
|
||
|
//console.log( mainNode.order,targetNode.order);
|
||
|
//replace element
|
||
|
main.parentNode.replaceChild(main, target);
|
||
|
main.parentNode.insertBefore(target, isBefore ? main.nextSibling : main);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* this method will draw multiple data
|
||
|
*/
|
||
|
drawData() {
|
||
|
//start loading
|
||
|
|
||
|
//if data is exist
|
||
|
if (this.data.length > 0) {
|
||
|
//first reshape data
|
||
|
let order = (list, p = {
|
||
|
n_id: 0,
|
||
|
Child: []
|
||
|
}, tree = []) => {
|
||
|
let childrens = list.filter((y) => y.n_parentid === p.n_id);
|
||
|
if (childrens.length > 0) {
|
||
|
// order items by order_num param if exist
|
||
|
childrens.sort((a, b) => parseFloat(a.n_order_num === undefined ? 0 : a.n_order_num) - parseFloat(b.n_order_num === undefined ? 0 : b.n_order_num));
|
||
|
if (p.n_id === 0) {
|
||
|
tree = childrens;
|
||
|
} else {
|
||
|
p.Child = childrens;
|
||
|
}
|
||
|
for (let i = 0; i < childrens.length; i++) {
|
||
|
order(list, childrens[i]);
|
||
|
}
|
||
|
}
|
||
|
return tree;
|
||
|
};
|
||
|
|
||
|
//then create nodes
|
||
|
let set = (list) => {
|
||
|
for (let i = 0; i < list.length; i++) {
|
||
|
this.createNode({
|
||
|
n_data: list[i],
|
||
|
n_addional: list[i].n_addional,
|
||
|
n_value: list[i].n_id,
|
||
|
n_title: list[i].n_title,
|
||
|
n_id: list[i].n_id,
|
||
|
n_elements: list[i].n_elements,
|
||
|
n_parent: this.getNode(list[i].n_parentid),
|
||
|
n_checkStatus: list[i].n_checkStatus === undefined ? false : list[i].n_checkStatus,
|
||
|
n_order: list[i].n_order_num,
|
||
|
});
|
||
|
if (list[i].Child) {
|
||
|
set(list[i].Child);
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
//start chain
|
||
|
set(order(this.data));
|
||
|
}
|
||
|
|
||
|
//start drawcallback
|
||
|
if (this.drawCallback !== undefined) this.drawCallback();
|
||
|
//end loading
|
||
|
}
|
||
|
|
||
|
//#endregion
|
||
|
|
||
|
//#region Menu
|
||
|
// brings up the context menu
|
||
|
getMenu(element, node) {
|
||
|
//get element location
|
||
|
const rect = element.getBoundingClientRect();
|
||
|
const origin = {
|
||
|
node: node,
|
||
|
left: rect.x,
|
||
|
top: rect.y + rect.height,
|
||
|
};
|
||
|
//draw menu
|
||
|
this.drawMenu(origin);
|
||
|
}
|
||
|
|
||
|
drawMenu(obj) {
|
||
|
//check if menu already exist
|
||
|
if (document.getElementById("div_menu_" + obj.node.id) === null) {
|
||
|
//create menu div
|
||
|
const menu_item = document.createElement("div");
|
||
|
//add to body
|
||
|
document.body.appendChild(menu_item);
|
||
|
menu_item.id = "div_menu_" + obj.node.id;
|
||
|
menu_item.classList.add("ptreemenuCont");
|
||
|
|
||
|
//for each menu item
|
||
|
let span_item;
|
||
|
let icon;
|
||
|
for (let i = 0; i < obj.node.elements.length; i++) {
|
||
|
span_item = document.createElement("span");
|
||
|
span_item.setAttribute("data-node", obj.node.id);
|
||
|
icon = obj.node.elements[i].icon.trim().length > 0 ? '<i class="' + obj.node.elements[i].icon.trim() + '"></i>' : "";
|
||
|
span_item.innerHTML = icon + " " + obj.node.elements[i].title.trim();
|
||
|
|
||
|
menu_item.appendChild(span_item);
|
||
|
|
||
|
//then add click event
|
||
|
span_item.addEventListener("click", (e) => {
|
||
|
obj.node.elements[i].onClick(this.getNode(e.target.getAttribute("data-node").split("node_")[1]));
|
||
|
//remove menu after click
|
||
|
menu_item.outerHTML = "";
|
||
|
});
|
||
|
}
|
||
|
|
||
|
switch (this.config.contextPos) {
|
||
|
case 'above':
|
||
|
menu_item.offsetPosition = -menu_item.offsetHeight;
|
||
|
break;
|
||
|
case 'after':
|
||
|
menu_item.offsetPosition = menu_item.offsetWidth;
|
||
|
break;
|
||
|
case 'below':
|
||
|
menu_item.offsetPosition = menu_item.offsetHeight;
|
||
|
break;
|
||
|
case 'before':
|
||
|
menu_item.offsetPosition = -menu_item.offsetWidth;
|
||
|
break;
|
||
|
default:
|
||
|
}
|
||
|
|
||
|
//calculate location
|
||
|
if (screen.width - obj.left < menu_item.offsetWidth) {
|
||
|
menu_item.style.left = obj.left - menu_item.offsetWidth + "px";
|
||
|
} else {
|
||
|
menu_item.style.left = obj.left + menu_item.offsetPosition + "px";
|
||
|
}
|
||
|
menu_item.style.top = obj.top + "px";
|
||
|
|
||
|
// listen mouse out
|
||
|
menu_item.onmouseleave = (e) => {
|
||
|
menu_item.remove();
|
||
|
};
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
collapseAll() {
|
||
|
for( let key in this.nodeList) {
|
||
|
this.getNode(key).foldedStatus = true;
|
||
|
this.updateNode(this.getNode(key))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
uncheckAll() {
|
||
|
this.getSelected().forEach(n => {
|
||
|
n.checkStatus = false;
|
||
|
this.checkNode(n)
|
||
|
});
|
||
|
}
|
||
|
//#endregion
|
||
|
}
|