diff --git a/erpnext/public/js/hierarchy_chart/hierarchy_chart_desktop.js b/erpnext/public/js/hierarchy_chart/hierarchy_chart_desktop.js
index fe4d17c210..694c26567a 100644
--- a/erpnext/public/js/hierarchy_chart/hierarchy_chart_desktop.js
+++ b/erpnext/public/js/hierarchy_chart/hierarchy_chart_desktop.js
@@ -36,7 +36,11 @@ erpnext.HierarchyChart = class {
me.nodes[this.id] = this;
me.make_node_element(this);
- me.setup_node_click_action(this);
+
+ if (!me.all_nodes_expanded) {
+ me.setup_node_click_action(this);
+ }
+
me.setup_edit_node_action(this);
}
};
@@ -60,8 +64,9 @@ erpnext.HierarchyChart = class {
show() {
frappe.breadcrumbs.add('HR');
- let me = this;
+ this.setup_actions();
if ($(`[data-fieldname="company"]`).length) return;
+ let me = this;
let company = this.page.add_field({
fieldtype: 'Link',
@@ -79,20 +84,9 @@ erpnext.HierarchyChart = class {
// svg for connectors
me.make_svg_markers();
-
- if (me.$hierarchy)
- me.$hierarchy.remove();
-
- // setup hierarchy
- me.$hierarchy = $(
- `
`);
-
- me.page.main.append(me.$hierarchy);
+ me.setup_hierarchy()
me.render_root_nodes();
+ me.all_nodes_expanded = false;
}
}
});
@@ -101,6 +95,42 @@ erpnext.HierarchyChart = class {
$(`[data-fieldname="company"]`).trigger('change');
}
+ setup_actions() {
+ let me = this;
+ this.page.add_inner_button(__('Expand All'), function() {
+ me.load_children(me.root_node, true);
+ me.all_nodes_expanded = true;
+
+ me.page.remove_inner_button(__('Expand All'));
+ me.page.add_inner_button(__('Collapse All'), function() {
+ me.setup_hierarchy();
+ me.render_root_nodes();
+ me.all_nodes_expanded = false;
+
+ me.page.remove_inner_button(__('Collapse All'));
+ me.setup_actions();
+ });
+ });
+ }
+
+ setup_hierarchy() {
+ if (this.$hierarchy)
+ this.$hierarchy.remove();
+
+ $(`#connectors`).empty();
+
+ // setup hierarchy
+ this.$hierarchy = $(
+ ``);
+
+ this.page.main.append(this.$hierarchy);
+ this.nodes = {};
+ }
+
make_svg_markers() {
$('#arrows').remove();
@@ -126,7 +156,7 @@ erpnext.HierarchyChart = class {
`);
}
- render_root_nodes() {
+ render_root_nodes(expanded_view=false) {
let me = this;
frappe.call({
@@ -156,7 +186,10 @@ erpnext.HierarchyChart = class {
expand_node = node;
});
- me.expand_node(expand_node);
+ if (!expanded_view) {
+ me.root_node = expand_node;
+ me.expand_node(expand_node);
+ }
}
});
}
@@ -196,11 +229,20 @@ erpnext.HierarchyChart = class {
$(`#${node.parent_id}`).addClass('active-path');
}
- load_children(node) {
- frappe.run_serially([
- () => this.get_child_nodes(node.id),
- (child_nodes) => this.render_child_nodes(node, child_nodes)
- ]);
+ load_children(node, deep=false) {
+ if (!deep) {
+ frappe.run_serially([
+ () => this.get_child_nodes(node.id),
+ (child_nodes) => this.render_child_nodes(node, child_nodes)
+ ]);
+ } else {
+ frappe.run_serially([
+ () => this.setup_hierarchy(),
+ () => this.render_root_nodes(true),
+ () => this.get_all_nodes(node.id, node.name),
+ (data_list) => this.render_children_of_all_nodes(data_list)
+ ]);
+ }
}
get_child_nodes(node_id) {
@@ -247,6 +289,70 @@ erpnext.HierarchyChart = class {
node.expanded = true;
}
+ get_all_nodes(node_id, node_name) {
+ return new Promise(resolve => {
+ frappe.call({
+ method: 'erpnext.utilities.hierarchy_chart.get_all_nodes',
+ args: {
+ method: this.method,
+ company: this.company,
+ parent: node_id,
+ parent_name: node_name
+ },
+ callback: (r) => {
+ resolve(r.message);
+ }
+ });
+ });
+ }
+
+ render_children_of_all_nodes(data_list) {
+ let entry = undefined;
+ let node = undefined;
+
+ while(data_list.length) {
+ // to avoid overlapping connectors
+ entry = data_list.shift();
+ node = this.nodes[entry.parent];
+ if (node) {
+ this.render_child_nodes_for_expanded_view(node, entry.data);
+ } else {
+ data_list.push(entry);
+ }
+ }
+ }
+
+ render_child_nodes_for_expanded_view(node, child_nodes) {
+ node.$children = $('')
+
+ const last_level = this.$hierarchy.find('.level:last').index();
+ const node_level = $(`#${node.id}`).parent().parent().parent().index();
+
+ if (last_level === node_level) {
+ this.$hierarchy.append(`
+
+ `);
+ node.$children.appendTo(this.$hierarchy.find('.level:last'));
+ } else {
+ node.$children.appendTo(this.$hierarchy.find('.level:eq(' + (node_level + 1) + ')'));
+ }
+
+ node.$children.hide().empty();
+
+ if (child_nodes) {
+ $.each(child_nodes, (_i, data) => {
+ this.add_node(node, data);
+ setTimeout(() => {
+ this.add_connector(node.id, data.id);
+ }, 250);
+ });
+ }
+
+ node.$children.show();
+ $(`path[data-parent="${node.id}"]`).show();
+ node.expanded = true;
+ }
+
add_node(node, data) {
return new this.Node({
id: data.id,
@@ -333,7 +439,7 @@ erpnext.HierarchyChart = class {
path.setAttribute("class", "active-connector");
path.setAttribute("marker-start", "url(#arrowstart-active)");
path.setAttribute("marker-end", "url(#arrowhead-active)");
- } else if (parent.hasClass('active-path')) {
+ } else {
path.setAttribute("class", "collapsed-connector");
path.setAttribute("marker-start", "url(#arrowstart-collapsed)");
path.setAttribute("marker-end", "url(#arrowhead-collapsed)");
diff --git a/erpnext/public/scss/hierarchy_chart.scss b/erpnext/public/scss/hierarchy_chart.scss
index dd523c3443..1c2f9421fa 100644
--- a/erpnext/public/scss/hierarchy_chart.scss
+++ b/erpnext/public/scss/hierarchy_chart.scss
@@ -194,6 +194,7 @@
.level {
margin-right: 8px;
align-items: flex-start;
+ flex-direction: column;
}
#arrows {
diff --git a/erpnext/utilities/hierarchy_chart.py b/erpnext/utilities/hierarchy_chart.py
new file mode 100644
index 0000000000..9b0279351f
--- /dev/null
+++ b/erpnext/utilities/hierarchy_chart.py
@@ -0,0 +1,29 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
+# MIT License. See license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe import _
+
+@frappe.whitelist()
+def get_all_nodes(parent, parent_name, method, company):
+ '''Recursively gets all data from nodes'''
+ method = frappe.get_attr(method)
+
+ if not method in frappe.whitelisted:
+ frappe.throw(_('Not Permitted'), frappe.PermissionError)
+
+ data = method(parent, company)
+ result = [dict(parent=parent, parent_name=parent_name, data=data)]
+
+ nodes_to_expand = [{'id': d.get('id'), 'name': d.get('name')} for d in data if d.get('expandable')]
+
+ while nodes_to_expand:
+ parent = nodes_to_expand.pop(0)
+ data = method(parent.get('id'), company)
+ result.append(dict(parent=parent.get('id'), parent_name=parent.get('name'), data=data))
+ for d in data:
+ if d.get('expandable'):
+ nodes_to_expand.append({'id': d.get('id'), 'name': d.get('name')})
+
+ return result
\ No newline at end of file