bryntum / support

An issues-only repository for the Bryntum project management component suite which includes powerful Grid, Scheduler, Calendar, Kanban Task Board and Gantt chart components all built in pure JS / CSS / TypeScript
https://www.bryntum.com
54 stars 6 forks source link

Scheduler lags with 800 events in one resource with same start/end date when eventLayout is none #9340

Closed taauntik closed 4 months ago

taauntik commented 5 months ago

Forum post

Hi Arcady, I did. I think I found the problem. In my scheduler i have made a eventLayout, which type is set to "none" as standard. The eventLayout are filtering on the event type, to "group" event types so it shows correctly order. I think the problem is when the eventLayout type is set to "none" because all events then are showing at once in one cell, if I understand it correctly. The lag shows up when you scroll vertical right where the events are. Try change the eventLayout type to "stack" and then scroll and see that it does not lag at all. The eventLayout is found at: https://bryntum.com/products/schedulerpro/examples/custom-layouts/.

Is it possible to make it NOT lag using the eventLayout type "none"?

https://github.com/bryntum/support/assets/75997756/7e87b3ec-4d08-4e73-8564-f7e45a7aa5ee

This is the full code using the resourceutilization example:

import { Splitter, SchedulerPro, ProjectModel, ResourceUtilization, ResourceStore, EventStore, AssignmentStore, DateHelper, StringHelper, Store } from '../../build/schedulerpro.module.js?476743';
import shared from '../_shared/shared.module.js?476743';

// Variables used in this demo
// Set group by Resource to City as initial default
let isGroupByResourceToCity = true;

const resources = [];
resources.push({
        id: 1,
        name: 'John',
        city: 'Test'
    });

for (let i = 1; i <= 9; i++) {
    resources.push({
        id: i + 1,
        name: 'John' + i.toString(),
        city: 'Test'
    });
}

const events = [];
for (let i = 1; i <= 800; i++) {
    events.push({
        id: i + 1,
        resourceId: 1,
        startDate: '2024-05-01T00:00:00',
        endDate: '2024-05-01T23:59:59',
        name: 'Test Event ' + i,
        type: 'task'
    });
}

const assignments = [];
for (let i = 1; i <= 800; i++) {
    assignments.push({
        id: i + 1,
        eventId: i + 1,
        resourceId: 1
    });
}

const resourceStore = new ResourceStore({
    data: resources
});

const eventStore = new EventStore({
    data: events
});

const assignmentStore = new AssignmentStore({
    data: assignments
});

const project = new ProjectModel({
    resourceStore: resourceStore,
    eventStore: eventStore,
    assignmentStore: assignmentStore,
    validateResponse: true
});

const eventLayout = {
        type: 'none',
        weights: {
            task: 1,
            internal_time: 2,
            holiday: 3,
            reserved: 4
        },
        groupBy : 'type'
    };

const scheduler = new SchedulerPro({
    appendTo  : 'container',
    minHeight : '10em',
    flex      : '1 1 50%',

project,
eventLayout,

startDate         : new Date(2024, 3, 28),
endDate           : new Date(2024, 4, 30),
viewPreset        : 'dayAndWeek',
eventStyle        : 'plain',
tickSize          : 50,
resourceImagePath : '../_shared/images/users/',

columns : [
    { type : 'resourceInfo', text : 'Name', field : 'name', width : 130 },
    { text : 'City', field : 'city', width : 90 }
],
eventRenderer({ eventRecord, renderData }) {
            const { groupBy } = eventLayout;

        // Color by group
        if (groupBy) {
            if (eventRecord[groupBy] == 'task' || eventRecord[groupBy] == true) {
                renderData.eventColor = '#66BB6A';
            } else if (eventRecord[groupBy] == 'internal_time' || eventRecord[groupBy] == true) {
                renderData.eventColor = '#42A5F5';
            } else if (eventRecord[groupBy] == 'holiday' || eventRecord[groupBy] == true) {
                renderData.eventColor = '#FFA726';
            } else if (eventRecord[groupBy] == 'reserved' || eventRecord[groupBy] == true) {
                renderData.eventColor = '#9575CD';
            }
        }

        // Encode name to protect against xss
        return StringHelper.encodeHtml(`${eventRecord.name}`);
    },

tbar : [
    {
        type     : 'button',
        ref      : 'zoomInButton',
        icon     : 'b-icon-search-plus',
        text     : 'Zoom in',
        onAction : () => scheduler.zoomIn()
    },
    {
        type     : 'button',
        ref      : 'zoomOutButton',
        icon     : 'b-icon b-icon-search-minus',
        text     : 'Zoom out',
        onAction : () => scheduler.zoomOut()
    }
]
});

new Splitter({
    appendTo : 'container'
});

// Prepare array of functions we are going to group the view store by
// (we make a constant since we are going to use it in few places)
const treeGroupLevels = [
    // group by resource
    ({ origin }) => {
        // If record is a resource means it has no assignments ..since this function is called for leaves only.
        // So further grouping makes no sense - stop grouping.
        if (origin.isResourceModel) {
            return Store.StopBranch;
        }

    return origin.resource;
},
// group by resource city
({ origin }) => origin.isResourceModel ? origin.city : origin.resource.city
];

const resourceUtilization = new ResourceUtilization({
    project,
    hideHeaders : true,
    partner     : scheduler,
    appendTo    : 'container',
    // set a bit larger row height since we use a custom renderer() below
    rowHeight   : 50,
    minHeight   : '10em',
    flex        : '1 1 50%',
    showBarTip  : true,
    features    : {
        treeGroup : {
            levels : treeGroupLevels
        }
    },
    columns : [
        {
            // this `cellCls` config is only used to target the example hint
            cellCls : 'tree-resource-event',
            type    : 'tree',
            text    : 'Resource/Event',
            flex    : 1,
            renderer({ record, grid }) {
                record = grid.resolveRecordToOrigin(record);

            // lets show event start/end for assignment row
            if (record.isAssignmentModel) {
                // get the assigned event
                const { event } = record;

                // add few nested tags for event name and for start/end dates
                return {
                    children : [
                        {
                            tag      : 'dl',
                            class    : 'b-assignment-info',
                            children : [
                                // value has event name
                                {
                                    tag  : 'dt',
                                    text : event.name
                                },
                                {
                                    tag  : 'dd',
                                    text : DateHelper.format(event.startDate, 'L') + ' - ' + DateHelper.format(event.endDate, 'L')
                                }
                            ]
                        }
                    ]
                };
            }
            else if (record.isResourceModel) {
                return record.name;
            }

            // record.key will have either resource or event so display its name
            return record.key?.name || record.key;
        }
    }
],

tbar : [
    {
        type    : 'checkbox',
        ref     : 'showBarTip',
        text    : 'Enable bar tooltip',
        tooltip : 'Check to show tooltips when moving mouse over bars',
        checked : true,
        onAction({ source }) {
            resourceUtilization.showBarTip = source.checked;
        }
    },
    '->',
    {
        type : 'label',
        text : 'Group by'
    },
    {
        type        : 'buttongroup',
        toggleGroup : true,
        cls         : 'group-buttons',
        items       : [
            {
                ref                  : 'resourceToCityButton', // for testing purpose
                text                 : 'Resource - City',
                tooltip              : 'Click to group by Resource → City',
                pressed              : true,
                supportsPressedClick : true,
                onAction() {
                    // toggle grouping fields order by resource to city
                    !isGroupByResourceToCity && treeGroupLevels.reverse();
                    isGroupByResourceToCity = true;
                    resourceUtilization.group(treeGroupLevels);

                }
            },
            {
                ref                  : 'cityToResourceButton', // for testing purpose
                text                 : 'City - Resource',
                tooltip              : 'Click to group by City → Resource',
                pressed              : false,
                supportsPressedClick : true,
                onAction() {
                    // toggle grouping fields order by city to resource
                    isGroupByResourceToCity && treeGroupLevels.reverse();
                    isGroupByResourceToCity = false;
                    resourceUtilization.group(treeGroupLevels);
                }
            },
            {
                ref     : 'defaultButton', // for testing purpose
                text    : 'Default',
                tooltip : 'Disable tree group feature and back to default Resource → Assignment look',
                onAction() {
                    if (!isGroupByResourceToCity) {
                        treeGroupLevels.reverse();
                        isGroupByResourceToCity = true;
                    }
                    resourceUtilization.clearGroups();
                }
            }
        ]
    }
]
});
matsbryntse commented 5 months ago

Should be removed / tuned (creates too many z-index contexts)


 else if (layoutEventData.length > 0) {
            for (let i = 0; i < layoutEventData.length; i++) {
                const data = layoutEventData[i];
                // $event-zindex scss var is 5
                data.wrapperStyle += `;z-index:${i + 5}`;
            }
        }

This filtering should not be needed unless DragCreating is in progress layoutEventData = eventsData.filter(({ eventRecord }) => eventRecord.isEvent && !eventRecord.meta.excludeFromLayout),

matsbryntse commented 5 months ago

Event tooltip flickers when hover CSS repeatedly tries to set z-index:108 to the topmost event.

https://github.com/bryntum/support/assets/218570/f017c2ce-7b68-48d8-8db9-8c52f0a5467f

matsbryntse commented 4 months ago

Fixed in other PR