onBeforeTaskDisplay called multiple times when render

Hi,

I am trying to use onBeforeTaskDisplay for filtering of task by priority.

If you see on GanttTL.js, I printed the log to console and then call render(). The method call would then cause multiple calls to “re-render” the react component (it seem) or something similar.

So this is the first render, which rendered twice.

image

When I clicked on the button to filter the priority (high/normal/low), the console log looks like this now (2nd time). Notice that it printed twice now.

image

And the third time I clicked to filter.

image

If I do not call render(), it wouldn’t have this (multiple) issue.
I would need to call render() as the zoomLevel may change, and it requires to be re-rendered.

And the filter isn’t working quite correctly as well, not too sure which part is the issue. But this isn’t the main issue yet. More interested in the rendering portion.

Demo on GitHub.

Thanks!

Hi,

I managed to track down the issue, so apparently, I did not detach the event. So now I will perform a cleanup everytime the priority change.

// To re-render the chart when zoomLevel changes
    useEffect(() => {
        useZoomLevel(zoomLevel);
        gantt.render();

    }, [zoomLevel]);

    useEffect(() => {
        console.log(`Current selected priority is ${priority}`);
        
        const id = gantt.attachEvent('onBeforeTaskDisplay', (id, task) => {
            console.log(`${id} priority is ${task.priority}`);
            if (task.priority === priority) {
                return true;
            }

            return false;
        });

        gantt.refreshData();

        // perform cleanup
        return () => {
            gantt.detachEvent(id);
        }
    }, [priority]);

As above, I handle zoomLevel and priority on different useEffect so I am able to use refreshData() on priority and render() on zoomLevel. It works almost perfectly with one small exception that calling render() will still cause the log to display twice.

Initial Load

image

onPriorityChange - now priority behavior is working correctly as well

image

But if I change the zoomLevel which will then make a call to render(), this will happen.

image

Any idea? Or is there any other way to do this?

Thanks!

Regards,

PS: I did not commit to the Github on the latest changes yet.

Some more update.

I ran on debug mode, and did a breakpoint on const id and console.log line. Weird thing is that even though the console printed all those lines ${id} priority is ${task.priority}, the breakpoint did not hit when I change my zoomLevel. Why would it print those logs?

const id = gantt.attachEvent('onBeforeTaskDisplay', (id, task) => {
            console.log(`${id} priority is ${task.priority}`);
            if (task.priority === priority) {
                return true;
            }

            return false;
        });

I have now added another event Add to the toolbar, and it has the same behavior (of printing multiple lines of logs) without hitting the breakpoint.

Have updated the GitHub project.

Hi, @Joseph !
I’ve debugged your code locally and I think everything works as expected.
onBeforeTaskDisplay event is indeed attached only once, and the component does not re-render multiple times.

I’ve reproduced that the event can be fired multiple times for each task per repaint, but if you check the stack trace it’s called from different places.
The event is connected to the internal filter method, and there is seems to be redundancy which causes gantt to run filter multiple times per repaint.

It may add some performance overhead if you have very complex filters, but usually I don’t think it’s a problem.

Hi @guldmi,

Thanks for the help…

On the surface, everything looks to be working as it should be, but because of the multiple logs that is seen on the console without the event being triggered. I am worried about some side-effects which may not be obvious with couple of tasks.

Could you please explain this in more details. I’m not so clear about this.

Does that meant that it’s caused by the gantt library itself hence, that is why I am still seeing multiple log message in the console?

If I need to do 2-3 type of filter, would that be considered as complex? Such as filter by Date AND Priority AND (text-match) or Date AND Priority AND STATUS-TYPE.

And what about having a few thousands of rows rendered? That is to say that if in my current example, there is 8000 tasks, the console would have then shown 16 000 lines.

Thank you once again.

Hi @Joseph!

Could you please explain this in more details. I’m not so clear about this.

sure. Pls consider this,

I’ve put a breakpoint into the filter. There are four tasks in the gantt, so when the filtering is refreshed - I expect the code to hit a breakpoint four times.
Firstly, if I call gantt.render - all as expected, the code stops 4 times at breakpoint.

Now, if I click Day/Hour - the breakpoint is hit 8 times. If I look at the stack trace of first 4 calls - they originate from the anonymous function as a part of gantt.render call tree,

and the second 4 calls originate from gantt.refreshData inside the same gantt.render call.

Meaning that both calls are the part of gantt repaint process, and there are no duplicated event handlers.

Does that meant that it’s caused by the gantt library itself hence, that is why I am still seeing multiple log message in the console?

yes, exactly

If I need to do 2-3 type of filter, would that be considered as complex? Such as filter by Date AND Priority AND (text-match) or Date AND Priority AND STATUS-TYPE.

No, I don’t think so.
If filtering of a single task would have required accessing a DOM element or something else that can be potentially heavy, a 2x or 3x more function calls would likely affect the user experience. But in case if simple string/date operations I don’t think such an increase will be a problem.
If you think it still affects the overall performance, you can optimize it a bit by moving filtering logic from onBeforeTaskDispaly to a separate function, where you precalculate visibility of each task in gantt chart and store it in some variable. And inside onBeforeTaskDisplay you simply check whether is task should be visible or not.That way you’ll be able to run actual filtering only when needed, and any redundant calls of onBeforeTaskDisplay will cost only accessing the precalculated value
For example, regular text filter:

Precalculate values in a separate function:

And what about having a few thousands of rows rendered? That is to say that if in my current example, there is 8000 tasks, the console would have then shown 16 000 lines.

Gantt performance in case of that amount of tasks will depend on settings and extensions you use, so it’s hard to tell in advance. I’m almost sure that filtering won’t be the most impactful factor in that case.
Btw,

the console would have then shown 16 000 lines.

Please, be sure to comment out console logs if you’re doing such test. Console output works much slower than anything else in your code, so having thousands of console.log calls will dramatically increase the execution time.

I get what you mean, but is that the correct behavior for the library (gantt) to print out the console that I placed in another event during the repaint process? I am not exactly well-versed in JavaScript, so forgive me if I understand certain concept wrongly.

Yes, I understand that. I am currently logging out to understand certain behavior.

And thanks for the example, and detailed explanation.

Well, it’s not an optimal or behavior. There is clearly redundancy in event calls, so it’s not entirely correct. On the other hand, because redundant event calls don’t seem to cause any kind of problem it is not in our bug list.

I can (and will) put it into our internal issue tracker to investigate and reduce the number of event calls, but it will likely be a low priority task.

Thank you so much, appreciate that.

1 Like

Hello Joseph,
The dev team fixed the bug with the onBeforeTaskDisplay event handler. Now it fires once for each task:
https://docs.dhtmlx.com/gantt/whatsnew.html#71
You can see how it works in the following snippet:
https://snippet.dhtmlx.com/5/a75bf65f0