Our pure JavaScript Scheduler component


Post by ayp »

We are using Bryntum with React, CRUD manager and with a side panel Grid of unscheduled events. We also have a logout / login feature implemented without page reload.

The steps to reproduce are the following:

  1. Add Bryntum with CRUD manager and with a side panel Grid of unscheduled events

  2. Open a page with the Scheduler displayed

  3. Do something that doesn't reload the page but that unmounts and than mounts the Bryntum component. In our case, we would logout and log in back without reloading the page;

  4. Drag an item from Grid to the main Schedule. There would be a sync request with data like { added: [item] };

  5. Double click on that item at the Schedule to edit it, make some changes and save. There would be another sync request with data like { added: [item] } (instead of { updated: [item] } like normally).

  6. Reload the page and see the same event present twice at the Scheduler.

When we mount the Scheduler component, we create a new scheduler config for it with a new CrudManager, and with new event stores. We even tried calling the "destroy" method on all of these objects before initializing new ones.

const initSchedulerConfig = () => {
    const config = {
        // ...
        crudManager:            initCrudManager(),
        // ...
    };
    
// ... return config; }; export const initCrudManager = () => { resetBryntumStores(); const crudManager = new CrudManager({ eventStore, resourceStore, stores: [ unassignedEventStore ], autoLoad: true, autoSync: true, transport: { load: { url: 'our_load_endpoint_url' }, sync: { url: 'our_sync_endpoint_url' }, }, // ... }); return crudManager; }; const resetBryntumStores = () => { resourceStore = initResourceStore(); eventStore = initEventStore(); unassignedEventStore = initUnassignedEventStore(); }; function initResourceStore() { const nextEventStore = new ResourceStore({ id: 'resourceStore', autoCommit: true, }); return nextEventStore; } function initEventStore() { const nextEventStore = new EventStore({ id: 'eventStore', autoCommit: true, writeAllFields: true, }); return nextEventStore; } function initUnassignedEventStore() { const nextUnassignedEventStore = new Store({ id: 'unassignedProjects', syncDataOnLoad: true, }); return nextUnassignedEventStore; } let resourceStore = initResourceStore(); let eventStore = initEventStore(); let unassignedEventStore = initUnassignedEventStore();

Scheduler component example:

const useSchedulerConfig = () => {
    const [ schedulerConfig, setSchedulerConfig ] = useState(null);

useEffect(() => {
    setSchedulerConfig(initSchedulerConfig());
}, []);

return schedulerConfig;
};

export const Scheduler = () => {
    const [ scheduler, setScheduler ] = useState(null);

const schedulerConfig = useSchedulerConfig();

const setSchedulerRef = useCallback((bryntumScheduler) => {
    if (bryntumScheduler !== null) {
        const schedulerInstance = bryntumScheduler.instance;
        setScheduler(schedulerInstance);
    }
}, []);


if (schedulerConfig === null) return null;

return (
    <>
        <BryntumScheduler ref = { setSchedulerRef } { ...schedulerConfig } />
        <Sidebar scheduler = { scheduler } />
    </>
);
};

Sidebar component code example:

export const Sidebar = ({ scheduler }) => {
    const gridRef = useCallback(
        (node: UnassignedProjects) => {
            if (node !== null) {
                new Drag({
                    grid:         node.unassignedGrid,
                    schedule:     scheduler,
                    constrain:    false,
                    outerElement: node.unassignedGrid?.element,
                });
            }
        },
        [ scheduler ],
    );

return (
    <>
        <UnsheduledEvents ref = { gridRef } />
    </>
);
};

UnsheduledEvents component code example:

export class UnsheduledEvents extends React.Component {
    componentDidMount() {
        this.unassignedGrid = new UnassignedGrid({
            appendTo: 'unassignedContainer',
            store:    unassignedEventStore,
        });
    }

render() {
    return <div id = 'unassignedContainer' />;
}
}

UnassignedGrid code example:

import { EventModel, Grid } from '@bryntum/scheduler';

export default class UnassignedGrid extends Grid {
    static get $name() {
        return 'UnassignedGrid';
    }

static get defaultConfig() {
    return {
        // ...
        store:     {
            modelClass: EventModel,
        },
        // ...
    };
}
}
Last edited by ayp on Fri Feb 03, 2023 2:45 pm, edited 1 time in total.

Post by alex.l »

Hi ayp,

We reviewed code snippets you shared, we see nothing special in there that might help us to determine the root cause of that.
We need you share a runnable application that we could debug, with responses/requests from your server for review - for every request, starting from data loading.
But we could start from responses/requests only, because I believe the problem caused by ids that missed somewhere, or id field itself, or PhantomId field, we need to debug it to know exactly. Please review your response format and compare with ours https://bryntum.com/products/scheduler/docs/guide/Scheduler/data/crud_manager
Try to enable https://bryntum.com/products/scheduler/docs/guide/Scheduler/data/crud_manager#response-format-validation
Make sure you used latest released version.

All the best,
Alex


Post by ayp »

Thank you for the hints!

Normally, when we move an Event from a Grid, the Bryntum sends a request like this:

{events: {added}, gridStore: {removed}}

But after we do something that proudces unmount+mount of the Scheduler, the same action produces a request like this:

{events: {added, updated}, gridStore: {removed}}

This "updated" field contains a huge full array of events from the Resource line we moved our Event into.

So the issue is that by some reason Bryntum sent such a strange extra "updated" field that we didn't expect (and didn't handle properly).

But is there a chance that some of the Bryntum internals are not completely reset on a component re-mount (even after calling the "destroy" method and initializing new configs and stores) which produces that extra "updated" data on a first sync?


Post by alex.l »

But is there a chance that some of the Bryntum internals are not completely reset on a component re-mount (even after calling the "destroy" method and initializing new configs and stores) which produces that extra "updated" data on a first sync?

Yes, there is a chance. Could you please share a runnable app with us? We need to step it out to understand what exactly happens, as well as review the full code you used. Maybe it's possible to replicate this behaviour with one of our examples?

I see you init crudManager separately, that might be the cause. Could you check if it was destroyed and re-created too? As example, compare ids before and after re-mount. Try to destroy it manually after the Scheduler and see if it helped.

All the best,
Alex


Post Reply