Premium support for our pure JavaScript UI components


Post by jk@datapult.dk »

Howdy,

When I update from 5.2.1 to 5.2.6 my application breaks.

See this video and here is the stack trace: https://www.dropbox.com/s/zfvp5fdpl90dq58/Screen%20Recording%202022-12-31%20at%2008.40.06.mov?dl=0

EventNavigation.js:130 Uncaught (in promise) TypeError: Cannot read properties of null (reading 'instanceMeta')
    at Scheduler.isInTimeAxis (EventNavigation.js:130:15)
    at EventDrag.updateAssignments (EventDrag.js:1036:77)
    at EventDrag.updateRecords (EventDrag.js:686:30)
    at EventDrag.finalize (DragBase.js:559:20)
    at EventDrag.onDrop (DragBase.js:463:52)
    at EventDrag.onDrop (EventDrag.js:506:55)
    at DragHelper.trigger (Events.js:1212:25)
    at DragHelper.finishTranslateDrag (DragHelperTranslate.js:394:21)
    at DragHelper.onMouseUp (DragHelper.js:890:21)
    at HTMLDocument.handler (EventHelper.js:471:31)

This does not happen on 5.2.1.

I am not asking you to troubleshoot with this thread, I am asking how users can automatically test scenarios like this.

How do I write some code that mimics this drag and drop in a Scheduler?

I am not asking how to do frontend testing (our application has hundreds of tests written with Vitest).

I am just asking: Given a scheduler object, how do I programmatically carry out a drag and drop like in the video above?


Post by mats »

We use Siesta (our own home-built testing tool) to test our various drag drop scenarios. You can download a free version of it here, which supports drag drop gestures etc. https://www.npmjs.com/package/@bryntum/siesta

Sounds like isInTimeAxis is called with null which is unexpected, hopefully easy to track down what the issue is.


Post by jk@datapult.dk »

Thanks, Mats! Might there be a "quick guide" to using with Vue or a Bryntum component? Or maybe even a snippet on how you tests drag-drop scenarios yourself.

As you can see from my forum activity here the switching cost to Bryntum has been high as is already so any notch in a good direction is appreciated.

Happy New Year.


Post by mats »

No such quick guide I'm afraid, great idea though. Here's one of our drag drop tests for inspiration:

Docs: https://bryntum.com/products/siesta/docs/#!/api/Siesta.Test.UserAgent.Mouse-method-dragBy

t.it('Should be possible to set constrainDragToTimeline in beforeEventDragListener', async t => {
        await initSchedulers(t, {
            features : {
                eventDrag : {
                    constrainDragToTimeline : false,
                    constrainDragToResource : true
                }
            },
            listeners : {
                beforeEventDrag(event) {
                    event.source.features.eventDrag.constrainDragToTimeline = true;
                    event.source.features.eventDrag.constrainDragToResource = false;
                }
            }
        });

    t.firesOnce(scheduler.eventStore, 'update');
    t.wontFire(scheduler2.eventStore, 'change');

    await t.dragBy({
        source   : '#first .b-sch-event',
        delta    : [0, 2 * scheduler.rowHeight],
        dragOnly : true
    });

    await t.waitFor(() => scheduler.scrollable.y === scheduler.scrollable.maxY);

    t.isApprox(t.rect('.b-sch-event-wrap.b-dragging').bottom, t.rect('#first [data-id="r6"]').bottom - scheduler.resourceMargin, 10, 'Proxy aligned ok');
    t.is(scheduler.features.eventDrag.drag.cloneTarget, false, 'Not cloning for local drag');
    t.notOk(scheduler.features.eventDrag.drag.dragWithin, null, 'Not setting body as boundary for drag for local drag');

    await t.mouseUp();

    await t.waitFor(() => scheduler.eventStore.first.resourceId === 'r6');
});
dragtest.mov
(17.27 MiB) Downloaded 28 times

Hope this helps and please let us know if you discover the problem to be in our code and we'll fix it asap.


Post by jk@datapult.dk »

Hi Mats,

Spent two weeks being able to create a reproducible example. This works in 5.2.1 but in 5.2.6 this breaks.

You can copy the code below into https://bryntum.com/products/scheduler/examples/crudmanager/

When you do that you will get this error (trace as plaintext in my first post on this thread): https://www.dropbox.com/s/r5qp38u2cgiwfpv/Screen%20Recording%202023-01-14%20at%2020.58.30.mov?dl=0

import { Scheduler, Splitter } from '../../build/scheduler.module.js?464842';
import shared from '../_shared/shared.module.js?464842';

const cookie = 'PHPSESSID=scheduler-crudmanager';
if (!(document.cookie.includes(cookie))) {
    document.cookie = `${cookie}-${Math.random().toString(16).substring(2)}`;
}

const data = {
    resourcesData: [
        {
            "id": 16582,
            "name": "User 16743",
            "list_order": 4,
            "initials": "16743",
            "email": "user16743@email.com",
            "private_phone": "",
            "public_phone": "",
            "is_on_shift": true,
            "contract_id": 288,
            "color": null,
            "created_at": "2022-06-17T07:11:30.000000Z",
            "updated_at": "2022-12-08T10:34:03.000000Z",
            "deleted_at": null,
            "created_by": 9213,
            "updated_by": 9213,
            "pivot": {
                "team_id": 5311,
                "user_id": 16743,
                "start": null,
                "end": null
            }
        },
    ],
    eventsData: [
        {
            "start": "2023-01-14T23:00:00Z",
            "end": "2023-01-15T22:59:00Z",
            "userId": 16582,
            "name": "Afspadsering",
            "style": null,
            "frontendId": "WISHID10377",
            "backendId": 10377,
            "wishTypeId": 359,
            "roomId": null,
            "note": "Hej ",
            "eventType": "wish",
            "historic": false,
            "cls": "",
            "eventStyle": "dashed"
        }
    ],
}

const crudManager = {
    resourceStore: {
        fields: ['id', 'name', 'list_order', 'color']
    },
    eventStore: {
        fields: [
            { name: "name", type: "string" },
            { name: "id", dataSource: "frontendId", },
            { name: "backendId", type: "integer" },
            { name: "wishTypeId", type: "integer" },
            { name: "roomId", type: "integer" },
            { name: "resourceId", dataSource: "userId", },
            { name: "note", type: "string" },
            { name: "eventType", type: "string", defaultValue: "shift" },
            { name: "startDate", dataSource: "start", },
            { name: "endDate", dataSource: "end", },
            { name: "historic", type: "boolean", defaultValue: true },
            { name: "durationUnit", defaultValue: "day" },
        ]
    },
    validateResponse: true,
    transport: {
        load: {
            url: 'php/read.php',
            paramName: 'data'
        },
        sync: {
            url: 'php/sync.php'
        }
    },
    autoLoad: false,
    autoSync: false,
}

const config = {
    appendTo: 'container',
    columns: [{ text: "name", field: "name" }],
    flex: '1 1 50%',
    features: { eventDrag: { constrainDragToTimeline: false, } },
    startDate: new Date(2023, 0, 13, 6),
    endDate: new Date(2023, 0, 15, 18),
    viewPreset: 'dayAndWeek',
    rowHeight: 50,
    barMargin: 5,
    eventColor: 'orange',
    eventStyle: 'colored',
    syncMask: null,
    loadMask: null,
    crudManager,
}

const scheduler1 = new Scheduler(config);

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

const scheduler2 = new Scheduler(config);

scheduler1.crudManager.inlineData = data
scheduler2.crudManager.inlineData = data

Post by mats »

Thanks for creating the test case, I'm seeing a few odd things that we'll look into.

While we do so, can you please describe this use case and what you expect should happen after dragging the top scheduler event to the bottom one? The events have the same id and id's need to be unique just like in a database.


Post by jk@datapult.dk »

Thanks, Mats.

You are very right that it seems odd to move same event with the same ID to the same resource. This is of course just because it was the simplest possible way to reproduce the bug and show you.

I will explain how we are actually doing below and share some code as well but the simplest use case is the one above.

Background:
We are using your scheduler components to do workforce planning. Data flows from your components to the backend using your crudmanager.

We are using a partnered scheduler setup: The top scheduler filter to resource ID=0 (unassigned shifts), while the bottom scheduler filters to resources!=0 (assigned shifts)

We need to use two crudmanagers due to this bug that we've been going back end forth on.
viewtopic.php?t=23404

Ideally, we just had one crudmanager for both schedulers that would filter to different resources but this is not possible.

Now, the following is totally beyond the scope of this forum thread but I am glad to share the full context. With two crudmanagers in a partnered scheduler setup, when you drag from one scheduler onto the other, the first scheduler DELETES and the second scheduler CREATES!

Because of this bug, we are registering these event handlers on both schedulers.

export default (schedulers: Scheduler[]) => {
    return {
        eventDragStart: ({ source, eventRecords, assignmentRecords, startDate, endDate, newResource, context }) => {
            // We will add the origin scheduler to the context
            context.originScheduleId = source.id
            source.crudManager.on('beforesync', (object) => {
                console.log(`${context.originScheduleId}: IS SYNCING`)
                // Since the schedulers have their own CrudManager, the original
                // scheduler wants to delete events dragged to the other scheduler
                // This prevents that HTTP request from being sent
                const cancelRequest = object?.pack?.events?.removed?.length > 0 ?? false
                if (cancelRequest) {
                    console.log(`${context.originScheduleId}: Stopped delete with`, object?.pack?.events)
                    // Otherwise, the crudManager will try deleting it next time it receives a eventDragStart
                    source.crudManager.acceptChanges()
                    return false
                }
                console.log(`${context.originScheduleId}: Proceed with`, object?.pack?.events)
            })
        },
        beforeEventDelete: ({ source }) => {
            // This fires one shortcut usage as well as editor change so here we shall remove the listener preventing deletes
            source.crudManager.listeners?.beforesync?.forEach(l => source.crudManager.un({ beforeSync: l }))
        },
        eventDrop: ({ context, eventRecords, externalDropTarget, source }) => {
            const dropOntoSelf: boolean = context.originScheduleId === source.id

            console.log("eventDrop")

            // Bryntum does not itself disable scrolling close to edges on a partnered scheduler
            source.disableScrollingCloseToEdges(source.timeAxisSubGrid);

            // Bryntum does not itself delete the drag proxy
            const dragProxy = context.context.element
            dragProxy.remove()

            // Add the events to the event store. If the scheduler is itself, Bryntum will update the events and not duplicate
            const events = []
            source.eventStore.beginBatch()
            for (let i = 0; i < eventRecords.length; i++) {
                /* Custom copy/paste due to https://github.com/bryntum/support/issues/5770 */
                const event = dropOntoSelf ? eventRecords[i] : eventRecords[i].copy({ frontendId: null })
                const startDate = DateHelper.copyTimeValues(new Date(context.startDate), event.startDate)
                const endDate = DateHelper.copyTimeValues(new Date(context.endDate), event.endDate)
                event.setStartEndDate(startDate, endDate)
                event.assign(context.newResource, true)
                events.push(event)
            }
            source.eventStore.add(events)
            source.eventStore.endBatch()
        }
    }
}

So long story short:

  1. We need partnered schedulers filtered to disjoint resources with a crudmanager. Since ATM you do not support using just one crudmanager for this 1. we make two backend calls and 2. keep the two crudmanagers in sync with the above.

  2. Becuase of the bug described in this thread, we are not able to bump the version so even if you create a satisfactory fix to viewtopic.php?t=23404, the bug described in this thread still has to be fixed before we can update.

Thanks, Mats!


Post by mats »

Thanks for the info! So the case with two schedulers having an eventStore with an event having same id, it's not a scenario you will have? If it is, what would you expect should happen in this case?


Post by jk@datapult.dk »

Hi Mats. Thanks. Correct, the eventStore will not have the same IDs.

Upon reflection on your question, I guess it does for the moment since I am running two crudmanagers on the same data.. In this case (of treating symptoms rather than the cause) I expect that the resourceId be assign to the new resource so one crudmanager emits a "changed" event.


Post by alex.l »

Hi jk@datapult.dk,

Does your eventStores have different dataset for these 2 schedulers, or you load same data for both but apply filter afterwards?

All the best,
Alex


Post Reply