mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-25 18:55:31 -06:00
Initial version of the new tree implementation.
This is the first version of our Tree implementation. At this point is a very simple tree without no abstractions and with code that eventually is not very performant, but this is only the first iteration and we are trying to follow the 'Last Responsible Moment Principle' [1]. Implemention details: - Creation of PGBrowser.treeMenu - Initial version of the Tree Adaptor 'pgadmin/static/js/tree/tree.js' - TreeFake test double that can replace the Tree for testing purposes - Tests, As an interesting asside because Fake’s need to behave like the real object you will noticed that there are tests for this type of double and they the same as of the real object. [1] https://medium.com/@aidanjcasey/guiding-principles-for-an-evolutionary-software-architecture-b6dc2cb24680 Patched by: Victoria && Joao Reviewed by: Khushboo & Ashesh
This commit is contained in:
committed by
Ashesh Vashi
parent
a34b3f27d4
commit
bc4d16eb83
212
web/pgadmin/static/js/tree/tree.js
Normal file
212
web/pgadmin/static/js/tree/tree.js
Normal file
@@ -0,0 +1,212 @@
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2018, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
export class TreeNode {
|
||||
constructor(id, data, domNode, parent) {
|
||||
this.id = id;
|
||||
this.data = data;
|
||||
this.setParent(parent);
|
||||
this.children = [];
|
||||
this.domNode = domNode;
|
||||
}
|
||||
|
||||
hasParent() {
|
||||
return this.parentNode !== null && this.parentNode !== undefined;
|
||||
}
|
||||
|
||||
parent() {
|
||||
return this.parentNode;
|
||||
}
|
||||
|
||||
setParent(parent) {
|
||||
this.parentNode = parent;
|
||||
this.path = this.id;
|
||||
if (parent !== null && parent !== undefined && parent.path !== undefined) {
|
||||
this.path = parent.path + '.' + this.id;
|
||||
}
|
||||
}
|
||||
|
||||
getData() {
|
||||
if (this.data === undefined) {
|
||||
return undefined;
|
||||
} else if (this.data === null) {
|
||||
return null;
|
||||
}
|
||||
return Object.assign({}, this.data);
|
||||
}
|
||||
|
||||
getHtmlIdentifier() {
|
||||
return this.domNode;
|
||||
}
|
||||
|
||||
reload(tree) {
|
||||
this.unload(tree);
|
||||
tree.aciTreeApi.setInode(this.domNode);
|
||||
tree.aciTreeApi.deselect(this.domNode);
|
||||
setTimeout(() => {
|
||||
tree.selectNode(this.domNode);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
unload(tree) {
|
||||
this.children = [];
|
||||
tree.aciTreeApi.unload(this.domNode);
|
||||
}
|
||||
|
||||
anyParent(condition) {
|
||||
let node = this;
|
||||
|
||||
while (node.hasParent()) {
|
||||
node = node.parent();
|
||||
if (condition(node)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a condition returns true if the current node
|
||||
* or any of the parent nodes condition result is true
|
||||
*/
|
||||
anyFamilyMember(condition) {
|
||||
if(condition(this)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return this.anyParent(condition);
|
||||
}
|
||||
}
|
||||
|
||||
export class Tree {
|
||||
constructor() {
|
||||
this.rootNode = new TreeNode(undefined, {});
|
||||
this.aciTreeApi = undefined;
|
||||
}
|
||||
|
||||
addNewNode(id, data, domNode, parentPath) {
|
||||
const parent = this.findNode(parentPath);
|
||||
return this.createOrUpdateNode(id, data, parent, domNode);
|
||||
}
|
||||
|
||||
findNode(path) {
|
||||
if (path === null || path === undefined || path.length === 0) {
|
||||
return this.rootNode;
|
||||
}
|
||||
return findInTree(this.rootNode, path.join('.'));
|
||||
}
|
||||
|
||||
findNodeByDomElement(domElement) {
|
||||
const path = this.translateTreeNodeIdFromACITree(domElement);
|
||||
if(!path || !path[0]) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return this.findNode(path);
|
||||
}
|
||||
|
||||
selected() {
|
||||
return this.aciTreeApi.selected();
|
||||
}
|
||||
|
||||
selectNode(aciTreeIdentifier) {
|
||||
this.aciTreeApi.select(aciTreeIdentifier);
|
||||
}
|
||||
|
||||
createOrUpdateNode(id, data, parent, domNode) {
|
||||
let oldNodePath = [id];
|
||||
if(parent !== null && parent !== undefined) {
|
||||
oldNodePath = [parent.path, id];
|
||||
}
|
||||
const oldNode = this.findNode(oldNodePath);
|
||||
if (oldNode !== null) {
|
||||
oldNode.data = Object.assign({}, data);
|
||||
return oldNode;
|
||||
}
|
||||
|
||||
const node = new TreeNode(id, data, domNode, parent);
|
||||
if (parent === this.rootNode) {
|
||||
node.parentNode = null;
|
||||
}
|
||||
parent.children.push(node);
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given the JQuery object that contains the ACI Tree
|
||||
* this method is responsible for registering this tree class
|
||||
* to listen to all the events that happen in the ACI Tree.
|
||||
*
|
||||
* At this point in time the only event that we care about is
|
||||
* the addition of a new node.
|
||||
* The function will create a new tree node to store the information
|
||||
* that exist in the ACI for it.
|
||||
*/
|
||||
register($treeJQuery) {
|
||||
$treeJQuery.on('acitree', function (event, api, item, eventName) {
|
||||
if (api.isItem(item)) {
|
||||
if (eventName === 'added') {
|
||||
const id = api.getId(item);
|
||||
const data = api.itemData(item);
|
||||
const parentId = this.translateTreeNodeIdFromACITree(api.parent(item));
|
||||
this.addNewNode(id, data, item, parentId);
|
||||
}
|
||||
}
|
||||
}.bind(this));
|
||||
this.aciTreeApi = $treeJQuery.aciTree('api');
|
||||
}
|
||||
|
||||
/**
|
||||
* As the name hints this functions works as a layer in between ACI and
|
||||
* the adaptor. Given a ACITree JQuery node find the location of it in the
|
||||
* Tree and then returns and array with the path to to the Tree Node in
|
||||
* question
|
||||
*
|
||||
* This is not optimized and will always go through the full tree
|
||||
*/
|
||||
translateTreeNodeIdFromACITree(aciTreeNode) {
|
||||
let currentTreeNode = aciTreeNode;
|
||||
let path = [];
|
||||
while (currentTreeNode !== null && currentTreeNode !== undefined && currentTreeNode.length > 0) {
|
||||
path.unshift(this.aciTreeApi.getId(currentTreeNode));
|
||||
if (this.aciTreeApi.hasParent(currentTreeNode)) {
|
||||
currentTreeNode = this.aciTreeApi.parent(currentTreeNode);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an initial node and a path, it will navigate through
|
||||
* the new tree to find the node that matches the path
|
||||
*/
|
||||
function findInTree(rootNode, path) {
|
||||
if (path === null) {
|
||||
return rootNode;
|
||||
}
|
||||
|
||||
return (function findInNode(currentNode) {
|
||||
for (let i = 0, length = currentNode.children.length; i < length; i++) {
|
||||
const calculatedNode = findInNode(currentNode.children[i]);
|
||||
if (calculatedNode !== null) {
|
||||
return calculatedNode;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentNode.path === path) {
|
||||
return currentNode;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
})(rootNode);
|
||||
}
|
||||
Reference in New Issue
Block a user