support
support copied to clipboard
Selecting/deselecting all columns triggers excessive re-rendering
Performance profiling: Trace-20240515T143230.json
To reproduce place this content into Gantt/examples/advanced/app.js
import '../_shared/shared.js'; // not required, our example styling etc.
import Gantt from '../../lib/Gantt/view/Gantt.js';
import '../../lib/Gantt/column/AllColumns.js';
import '../../lib/Scheduler/feature/TimeRanges.js';
import '../../lib/SchedulerPro/feature/DependencyEdit.js';
import '../../lib/Gantt/feature/Baselines.js';
import '../../lib/Scheduler/feature/RowResize.js';
import '../../lib/Grid/feature/Filter.js';
import '../../lib/Gantt/feature/Labels.js';
import '../../lib/Gantt/feature/ParentArea.js';
import '../../lib/Gantt/feature/ProjectLines.js';
import '../../lib/Gantt/feature/ProgressLine.js';
import '../../lib/Gantt/feature/Rollups.js';
import '../../lib/Gantt/feature/TaskEdit.js';
import '../../lib/Grid/feature/CellCopyPaste.js';
import '../../lib/Grid/feature/FillHandle.js';
import './lib/GanttToolbar.js';
import Task from './lib/Task.js';
import './lib/StatusColumn.js';
import './lib/ComplexityColumn.js';
const gantt = new Gantt({
appendTo : 'container',
dependencyIdField : 'wbsCode',
selectionMode : {
cell : true,
dragSelect : true,
rowNumber : true
},
project : {
// Let the Project know we want to use our own Task model with custom fields / methods
taskModelClass : Task,
transport : {
load : {
url : '../_datasets/launch-saas.json'
}
},
autoLoad : true,
taskStore : {
wbsMode : 'auto'
},
// The State TrackingManager which the UndoRedo widget in the toolbar uses
stm : {
autoRecord : true
},
// Reset Undo / Redo after each load
resetUndoRedoQueuesAfterLoad : true
},
// For best initial performance, configure startDate & endDate to determine the date range that needs to be
// rendered initially
startDate : '2019-01-12',
endDate : '2019-03-24',
resourceImageFolderPath : '../_shared/images/users/',
scrollTaskIntoViewOnCellClick : true,
columns : [
{ type : 'wbs' },
{ type : 'name', width : 250 },
{ type : 'startdate' },
{ type : 'duration' },
{ type : 'resourceassignment', width : 120, showAvatars : true },
{ type : 'percentdone', showCircle : true, width : 70 },
{
type : 'predecessor',
width : 112
},
{
type : 'successor',
width : 112
},
{ type : 'schedulingmodecolumn' },
{ type : 'calendar' },
{ type : 'constrainttype' },
{ type : 'constraintdate' },
{ type : 'statuscolumn' },
{ type : 'complexitycolumn' },
{ type : 'deadlinedate' },
{ type : 'addnew' },
{ type : 'wbs' },
{ type : 'name', width : 250 },
{ type : 'startdate' },
{ type : 'duration' },
{ type : 'resourceassignment', width : 120, showAvatars : true },
{ type : 'percentdone', showCircle : true, width : 70 },
{
type : 'predecessor',
width : 112
},
{
type : 'successor',
width : 112
},
{ type : 'schedulingmodecolumn' },
{ type : 'calendar' },
{ type : 'constrainttype' },
{ type : 'constraintdate' },
{ type : 'statuscolumn' },
{ type : 'complexitycolumn' },
{ type : 'deadlinedate' },
{ type : 'addnew' },
{ type : 'wbs' },
{ type : 'name', width : 250 },
{ type : 'startdate' },
{ type : 'duration' },
{ type : 'resourceassignment', width : 120, showAvatars : true },
{ type : 'percentdone', showCircle : true, width : 70 },
{
type : 'predecessor',
width : 112
},
{
type : 'successor',
width : 112
},
{ type : 'schedulingmodecolumn' },
{ type : 'calendar' },
{ type : 'constrainttype' },
{ type : 'constraintdate' },
{ type : 'statuscolumn' },
{ type : 'complexitycolumn' },
{ type : 'deadlinedate' },
{ type : 'addnew' },
{ type : 'wbs' },
{ type : 'name', width : 250 },
{ type : 'startdate' },
{ type : 'duration' },
{ type : 'resourceassignment', width : 120, showAvatars : true },
{ type : 'percentdone', showCircle : true, width : 70 },
{
type : 'predecessor',
width : 112
},
{
type : 'successor',
width : 112
},
{ type : 'schedulingmodecolumn' },
{ type : 'calendar' },
{ type : 'constrainttype' },
{ type : 'constraintdate' },
{ type : 'statuscolumn' },
{ type : 'complexitycolumn' },
{ type : 'deadlinedate' },
{ type : 'addnew' },
],
subGridConfigs : {
locked : {
flex : 3
},
normal : {
flex : 4
}
},
columnLines : false,
// Shows a color field in the task editor and a color picker in the task menu.
// Both lets the user select the Task bar's background color
showTaskColorPickers : true,
features : {
baselines : {
disabled : true
},
dependencies : {
showLagInTooltip : true,
// Soften up dependency line corners
radius : 3,
// Make dependencies easier to reach using the mouse
clickWidth : 5
},
dependencyEdit : true,
filter : true,
labels : {
left : {
field : 'name',
editor : {
type : 'textfield'
}
}
},
parentArea : {
disabled : true
},
progressLine : {
disabled : true,
statusDate : new Date(2019, 0, 25)
},
rollups : {
disabled : true
},
rowResize : {
cellSelector : '.b-rownumber-cell'
},
rowReorder : {
showGrip : true,
preserveSorters : true
},
timeRanges : {
showCurrentTimeLine : true
},
fillHandle : true,
cellCopyPaste : true,
taskCopyPaste : {
useNativeClipboard : true
},
headerMenu : {
processItems(props) {
const { items : { columns }, feature : { client : grid } } = props;
const items = [];
let allSelected = true;
grid.columns.allRecords.forEach(column => {
items.push({
grid,
column,
text : column.text,
checked : !column.hidden,
disabled : false,
cls : ''
});
if (allSelected !== false) allSelected = !column.hidden;
});
items.unshift({
grid,
ref : 'selectAll',
text : 'Select All',
checked : allSelected
});
columns.menu.items = items;
},
items : {
columns : {
text : 'Columns',
cls : 'b-separator',
icon : 'b-fw-icon b-icon-columns',
weight : 120,
menu : {},
onToggle(props) {
const { menu, item, checked } = props;
if (item.text === 'Select All') {
Object.keys(menu.widgetMap).forEach((key) => {
const col = menu.widgetMap[key];
col.checked = checked;
});
}
else {
item.column && (item.column.hidden = !checked);
}
}
},
// Hide the default column picker
columnPicker : null
}
}
},
tbar : {
type : 'gantttoolbar'
}
});
Right click in the left sub-grid header, point to Columns -> Select All
. Observe a significant delay in processing.
It seems what happens is that after every change of the column "checked" state, a full re-rendering is performed.
Probably this "full re-rendering" can be buffered on 10ms to allow batch updates.
What we recommend in similar cases (not involving columns) is to suspendRefresh()
, perform all operations, resumeRefresh()
. That will lead to a single redraw on resume.
But, columns change handling does not take refresh suspension into account. We can make it do that, but it is going to have to keep track of that columns has changed when resuming, since column changes require a more brutal update (renderContents()
instead of refresh()
)
An optimized version of onToggle
handler:
onToggle(props) {
const { menu, item, checked } = props;
const gantt = menu.up('gantt');
if (item.text === 'Select All') {
gantt.columns.beginBatch();
gantt.suspendRefresh();
Object.keys(menu.items).forEach((key, index) => {
const col = menu.items[key];
col.checked = checked;
});
gantt.resumeRefresh();
gantt.columns.endBatch();
}
else {
item.column && (item.column.hidden = !checked);
}
}
It makes the refresh process instant, but events rendering is broken: