perf: Optimise Rendering

- optimise get_children function

- use promises instead of callbacks

- optimise selectors

- use const wherever possible

- use pure js instead of jquery for connectors for faster rendering
This commit is contained in:
Rucha Mahabal 2021-07-08 09:53:31 +05:30
parent 6eec251273
commit df3bb9ea8c
3 changed files with 78 additions and 91 deletions

View File

@ -2,33 +2,25 @@ from __future__ import unicode_literals
import frappe import frappe
@frappe.whitelist() @frappe.whitelist()
def get_children(parent=None, company=None, exclude_node=None, is_root=False, is_tree=False, fields=None): def get_children(parent=None, company=None):
filters = [['status', '!=', 'Left']] filters = [['status', '!=', 'Left']]
if company and company != 'All Companies': if company and company != 'All Companies':
filters.append(['company', '=', company]) filters.append(['company', '=', company])
if not fields:
fields = ['employee_name as name', 'name as id', 'reports_to', 'image', 'designation as title']
if is_root:
parent = ''
if exclude_node:
filters.append(['name', '!=', exclude_node])
if parent and company and parent != company: if parent and company and parent != company:
filters.append(['reports_to', '=', parent]) filters.append(['reports_to', '=', parent])
else: else:
filters.append(['reports_to', '=', '']) filters.append(['reports_to', '=', ''])
employees = frappe.get_list('Employee', fields=fields, employees = frappe.get_list('Employee',
filters=filters, order_by='name') fields=['employee_name as name', 'name as id', 'reports_to', 'image', 'designation as title'],
filters=filters,
order_by='name'
)
for employee in employees: for employee in employees:
is_expandable = frappe.get_all('Employee', filters=[ is_expandable = frappe.db.count('Employee', filters={'reports_to': employee.get('id')})
['reports_to', '=', employee.get('id')]
])
employee.connections = get_connections(employee.id) employee.connections = get_connections(employee.id)
employee.expandable = 1 if is_expandable else 0 employee.expandable = 1 if is_expandable else 0

View File

@ -7,7 +7,6 @@ erpnext.HierarchyChart = class {
- this method should return id, name, title, image, and connections for each node - this method should return id, name, title, image, and connections for each node
*/ */
constructor(doctype, wrapper, method) { constructor(doctype, wrapper, method) {
this.wrapper = $(wrapper);
this.page = wrapper.page; this.page = wrapper.page;
this.method = method; this.method = method;
this.doctype = doctype; this.doctype = doctype;
@ -61,6 +60,8 @@ erpnext.HierarchyChart = class {
frappe.breadcrumbs.add('HR'); frappe.breadcrumbs.add('HR');
let me = this; let me = this;
if ($(`[data-fieldname="company"]`).length) return;
let company = this.page.add_field({ let company = this.page.add_field({
fieldtype: 'Link', fieldtype: 'Link',
options: 'Company', options: 'Company',
@ -131,32 +132,30 @@ erpnext.HierarchyChart = class {
method: me.method, method: me.method,
args: { args: {
company: me.company company: me.company
}, }
callback: function(r) { }).then(r => {
if (r.message.length) { if (r.message.length) {
let nodes = r.message; let node = undefined;
let node = undefined; let first_root = undefined;
let first_root = undefined;
$.each(nodes, (i, data) => { $.each(r.message, (i, data) => {
node = new me.Node({ node = new me.Node({
id: data.id, id: data.id,
parent: $('<li class="child-node"></li>').appendTo(me.$hierarchy.find('.node-children')), parent: $('<li class="child-node"></li>').appendTo(me.$hierarchy.find('.node-children')),
parent_id: undefined, parent_id: undefined,
image: data.image, image: data.image,
name: data.name, name: data.name,
title: data.title, title: data.title,
expandable: true, expandable: true,
connections: data.connections, connections: data.connections,
is_root: true is_root: true
});
if (i == 0)
first_root = node;
}); });
me.expand_node(first_root); if (i == 0)
} first_root = node;
});
me.expand_node(first_root);
} }
}); });
} }
@ -204,18 +203,14 @@ erpnext.HierarchyChart = class {
} }
get_child_nodes(node_id) { get_child_nodes(node_id) {
let me = this;
return new Promise(resolve => { return new Promise(resolve => {
frappe.call({ frappe.call({
method: this.method, method: this.method,
args: { args: {
parent: node_id, parent: node_id,
company: me.company company: this.company
},
callback: (r) => {
resolve(r.message);
} }
}); }).then(r => resolve(r.message));
}); });
} }
@ -266,27 +261,28 @@ erpnext.HierarchyChart = class {
} }
add_connector(parent_id, child_id) { add_connector(parent_id, child_id) {
// using pure javascript for better performance
const parent_node = document.querySelector(`#${parent_id}`); const parent_node = document.querySelector(`#${parent_id}`);
const child_node = document.querySelector(`#${child_id}`); const child_node = document.querySelector(`#${child_id}`);
let path = document.createElementNS('http://www.w3.org/2000/svg', 'path'); let path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
// we need to connect right side of the parent to the left side of the child node // we need to connect right side of the parent to the left side of the child node
let pos_parent_right = { const pos_parent_right = {
x: parent_node.offsetLeft + parent_node.offsetWidth, x: parent_node.offsetLeft + parent_node.offsetWidth,
y: parent_node.offsetTop + parent_node.offsetHeight / 2 y: parent_node.offsetTop + parent_node.offsetHeight / 2
}; };
let pos_child_left = { const pos_child_left = {
x: child_node.offsetLeft - 5, x: child_node.offsetLeft - 5,
y: child_node.offsetTop + child_node.offsetHeight / 2 y: child_node.offsetTop + child_node.offsetHeight / 2
}; };
let connector = this.get_connector(pos_parent_right, pos_child_left); const connector = this.get_connector(pos_parent_right, pos_child_left);
path.setAttribute('d', connector); path.setAttribute('d', connector);
this.set_path_attributes(path, parent_id, child_id); this.set_path_attributes(path, parent_id, child_id);
$('#connectors').append(path); document.getElementById('connectors').appendChild(path);
} }
get_connector(pos_parent_right, pos_child_left) { get_connector(pos_parent_right, pos_child_left) {
@ -330,12 +326,13 @@ erpnext.HierarchyChart = class {
set_path_attributes(path, parent_id, child_id) { set_path_attributes(path, parent_id, child_id) {
path.setAttribute("data-parent", parent_id); path.setAttribute("data-parent", parent_id);
path.setAttribute("data-child", child_id); path.setAttribute("data-child", child_id);
const parent = $(`#${parent_id}`);
if ($(`#${parent_id}`).hasClass('active')) { if (parent.hasClass('active')) {
path.setAttribute("class", "active-connector"); path.setAttribute("class", "active-connector");
path.setAttribute("marker-start", "url(#arrowstart-active)"); path.setAttribute("marker-start", "url(#arrowstart-active)");
path.setAttribute("marker-end", "url(#arrowhead-active)"); path.setAttribute("marker-end", "url(#arrowhead-active)");
} else if ($(`#${parent_id}`).hasClass('active-path')) { } else if (parent.hasClass('active-path')) {
path.setAttribute("class", "collapsed-connector"); path.setAttribute("class", "collapsed-connector");
path.setAttribute("marker-start", "url(#arrowstart-collapsed)"); path.setAttribute("marker-start", "url(#arrowstart-collapsed)");
path.setAttribute("marker-end", "url(#arrowhead-collapsed)"); path.setAttribute("marker-end", "url(#arrowhead-collapsed)");
@ -343,8 +340,9 @@ erpnext.HierarchyChart = class {
} }
set_selected_node(node) { set_selected_node(node) {
// remove .active class from the current node // remove active class from the current node
$('.active').removeClass('active'); if (this.selected_node)
this.selected_node.$link.removeClass('active');
// add active class to the newly selected node // add active class to the newly selected node
this.selected_node = node; this.selected_node = node;
@ -411,9 +409,9 @@ erpnext.HierarchyChart = class {
} }
remove_levels_after_node(node) { remove_levels_after_node(node) {
let level = $(`#${node.id}`).parent().parent().parent(); let level = $(`#${node.id}`).parent().parent().parent().index();
level = $('.hierarchy > li:eq('+ level.index() + ')'); level = $('.hierarchy > li:eq('+ level + ')');
level.nextAll('li').remove(); level.nextAll('li').remove();
let nodes = level.find('.node-card'); let nodes = level.find('.node-card');
@ -431,8 +429,8 @@ erpnext.HierarchyChart = class {
remove_orphaned_connectors() { remove_orphaned_connectors() {
let paths = $('#connectors > path'); let paths = $('#connectors > path');
$.each(paths, (_i, path) => { $.each(paths, (_i, path) => {
let parent = $(path).data('parent'); const parent = $(path).data('parent');
let child = $(path).data('child'); const child = $(path).data('child');
if ($(`#${parent}`).length && $(`#${child}`).length) if ($(`#${parent}`).length && $(`#${child}`).length)
return; return;

View File

@ -7,7 +7,6 @@ erpnext.HierarchyChartMobile = class {
- this method should return id, name, title, image, and connections for each node - this method should return id, name, title, image, and connections for each node
*/ */
constructor(doctype, wrapper, method) { constructor(doctype, wrapper, method) {
this.wrapper = $(wrapper);
this.page = wrapper.page; this.page = wrapper.page;
this.method = method; this.method = method;
this.doctype = doctype; this.doctype = doctype;
@ -63,6 +62,8 @@ erpnext.HierarchyChartMobile = class {
frappe.breadcrumbs.add('HR'); frappe.breadcrumbs.add('HR');
let me = this; let me = this;
if ($(`[data-fieldname="company"]`).length) return;
let company = this.page.add_field({ let company = this.page.add_field({
fieldtype: 'Link', fieldtype: 'Link',
options: 'Company', options: 'Company',
@ -139,24 +140,21 @@ erpnext.HierarchyChartMobile = class {
args: { args: {
company: me.company company: me.company
}, },
callback: function(r) { }).then(r => {
if (r.message.length) { if (r.message.length) {
let nodes = r.message; $.each(r.message, (_i, data) => {
return new me.Node({
$.each(nodes, (_i, data) => { id: data.id,
return new me.Node({ parent: me.$hierarchy.find('.root-level'),
id: data.id, parent_id: undefined,
parent: me.$hierarchy.find('.root-level'), image: data.image,
parent_id: undefined, name: data.name,
image: data.image, title: data.title,
name: data.name, expandable: true,
title: data.title, connections: data.connections,
expandable: true, is_root: true
connections: data.connections,
is_root: true
});
}); });
} });
} }
}); });
} }
@ -237,11 +235,8 @@ erpnext.HierarchyChartMobile = class {
parent: node_id, parent: node_id,
company: me.company, company: me.company,
exclude_node: exclude_node exclude_node: exclude_node
},
callback: (r) => {
resolve(r.message);
} }
}); }).then(r => resolve(r.message));
}); });
} }
@ -286,10 +281,10 @@ erpnext.HierarchyChartMobile = class {
} }
add_connector(parent_id, child_id) { add_connector(parent_id, child_id) {
let parent_node = document.querySelector(`#${parent_id}`); const parent_node = document.querySelector(`#${parent_id}`);
let child_node = document.querySelector(`#${child_id}`); const child_node = document.querySelector(`#${child_id}`);
let path = document.createElementNS('http://www.w3.org/2000/svg', 'path'); const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
let connector = undefined; let connector = undefined;
@ -299,10 +294,10 @@ erpnext.HierarchyChartMobile = class {
connector = this.get_connector_for_collapsed_node(parent_node, child_node); connector = this.get_connector_for_collapsed_node(parent_node, child_node);
} }
path.setAttribute("d", connector); path.setAttribute('d', connector);
this.set_path_attributes(path, parent_id, child_id); this.set_path_attributes(path, parent_id, child_id);
$('#connectors').append(path); document.getElementById('connectors').appendChild(path);
} }
get_connector_for_active_node(parent_node, child_node) { get_connector_for_active_node(parent_node, child_node) {
@ -351,19 +346,21 @@ erpnext.HierarchyChartMobile = class {
set_path_attributes(path, parent_id, child_id) { set_path_attributes(path, parent_id, child_id) {
path.setAttribute("data-parent", parent_id); path.setAttribute("data-parent", parent_id);
path.setAttribute("data-child", child_id); path.setAttribute("data-child", child_id);
const parent = $(`#${parent_id}`);
if ($(`#${parent_id}`).hasClass('active')) { if (parent.hasClass('active')) {
path.setAttribute("class", "active-connector"); path.setAttribute("class", "active-connector");
path.setAttribute("marker-start", "url(#arrowstart-active)"); path.setAttribute("marker-start", "url(#arrowstart-active)");
path.setAttribute("marker-end", "url(#arrowhead-active)"); path.setAttribute("marker-end", "url(#arrowhead-active)");
} else if ($(`#${parent_id}`).hasClass('active-path')) { } else if (parent.hasClass('active-path')) {
path.setAttribute("class", "collapsed-connector"); path.setAttribute("class", "collapsed-connector");
} }
} }
set_selected_node(node) { set_selected_node(node) {
// remove .active class from the current node // remove .active class from the current node
$('.active').removeClass('active'); if (this.selected_node)
this.selected_node.$link.removeClass('active');
// add active class to the newly selected node // add active class to the newly selected node
this.selected_node = node; this.selected_node = node;
@ -494,9 +491,9 @@ erpnext.HierarchyChartMobile = class {
} }
remove_levels_after_node(node) { remove_levels_after_node(node) {
let level = $(`#${node.id}`).parent().parent(); let level = $(`#${node.id}`).parent().parent().index();
level = $('.hierarchy-mobile > li:eq('+ (level.index()) + ')'); level = $('.hierarchy-mobile > li:eq('+ level + ')');
level.nextAll('li').remove(); level.nextAll('li').remove();
let current_node = level.find(`#${node.id}`); let current_node = level.find(`#${node.id}`);
@ -512,8 +509,8 @@ erpnext.HierarchyChartMobile = class {
remove_orphaned_connectors() { remove_orphaned_connectors() {
let paths = $('#connectors > path'); let paths = $('#connectors > path');
$.each(paths, (_i, path) => { $.each(paths, (_i, path) => {
let parent = $(path).data('parent'); const parent = $(path).data('parent');
let child = $(path).data('child'); const child = $(path).data('child');
if ($(`#${parent}`).length && $(`#${child}`).length) if ($(`#${parent}`).length && $(`#${child}`).length)
return; return;