Our pure JavaScript Scheduler component


Post by jbyvik »

Hey!

Im currently running two SchedulerPros paired to allow me to drag and drop tasks between the schedulers (set up as shown in the demo).

I have a small issue when im adding some logic to one of the two schedulers. When I drop events into this scheduler (both from itself and from its sibling scheduler), I want to set its resourceId and its startDate/endDate according to customFields on the event. Since I want to still alow dragging between schedulers, I didnt find a way to use the eventDrag features validatorFn (this prevented draging to the sibling scheduler) and instead opted for the eventDrop event listener. So this all works fine when im moving events inside the same scheduler. But when im moving an event from the first scheduler to the second scheduler(with the eventDrop function) it doesnt move the event, but instead plops it down where I drop it.

Im doing a pretty vanilla setup so Im guessing this is a problem where some events arent triggered when an event is dragged from external scheduler (im unsure if the events are deleted from the scheduler when its dragged to the paired one, or if its just assigned?) looking forward for some pointers :)

Config for the scheduler

  const config = {
    ref: 'taskScheduler',
    cls: 'planning-task-scheduler',
    flex: '1 0 50%',
    startDate: dateUtils.startOfToday(),
    endDate: dateUtils.endOfToday(),
    viewPreset: {
      base: 'hourAndDay',
      timeResolution: {
        unit: 'minute',
        increment: 1,
      },
      shiftUnit: 'day',
    },
    features: {
      cellEdit: false,
      dependencies: false,
      eventDrag: {
        // Allow drag outside of this Scheduler
        constrainDragToTimeline: false,
      },
      eventDragCreate: false,
      eventDragSelect: false,
      eventResize: false,
      headerMenu: false,
      timeAxisHeaderMenu: false,
      sort: false,
      cellMenu: false,
      eventMenu: false,
      scheduleMenu: false,
      scheduleTooltip: false,
      eventTooltip: {
        allowOver: true,
        template: (data: any) =>
          UnplannedActionTooltip(data.eventRecord.data, localization),
      },
    },
    columns: [
      {
        field: 'firstName',
        renderer: ({ record }: { record: Customer }) =>
          getName({
            firstName: record.firstName,
            givenName: record.givenName,
            lastName: record.lastName,
          }) ?? '',
        text: translate(planningLabels.unplannedTasks),
        width: 165,
        draggable: false,
      },
    ],
    removeUnassignedEvent: true,
    createEventOnDblClick: false,
    selectionMode: { row: false, cell: false },
    enableDeleteKey: false,
    zoomOnMouseWheel: false,
    zoomOnTimeAxisDoubleClick: false,
    multiEventSelect: false,
    listeners: {
      eventDrop({ source, context }: any) {
        context.eventRecord.resourceId = context.eventRecord.customerId;
        context.eventRecord.startDate = context.eventRecord.possibleStartDate;
        context.eventRecord.endDate = context.eventRecord.possibleEndDate;
      },
      async beforeEventDropFinalize({ source: scheduler, context }: any) {
        context.async = true;
        if (
          context.eventRecords[0].taskType === TaskTypeEnumV1.break ||
          context.eventRecords[0].taskType === TaskTypeEnumV1.report
        ) {
          context.finalize(false);
          return;
        }
        context.finalize(true);
      },
    },
  };

second scheduler

  const config = {
    ref: 'planningScheduler',
    cls: 'planning-plan-scheduler',
    flex: '1 0 50%',
    startDate: dateUtils.startOfToday(),
    endDate: dateUtils.endOfToday(),
    viewPreset: {
      base: 'hourAndDay',
      timeResolution: {
        unit: 'minute',
        increment: 1,
      },
      shiftUnit: 'day',
    },
    features: {
      cellEdit: false,
      dependencies: false,
      eventDrag: {
        // Allow drag outside of this Scheduler
        constrainDragToTimeline: false,
      },
      eventDragCreate: false,
      eventDragSelect: false,
      eventResize: false,
      headerMenu: false,
      timeAxisHeaderMenu: false,
      sort: false,
      cellMenu: false,
      eventMenu: false,
      scheduleMenu: false,
      scheduleTooltip: false,
      eventTooltip: {
        allowOver: true,
        template: (data: any) =>
          PlannedActionTooltip(data.eventRecord.data, localization),
      },
      resourceTimeRanges: true,
      cellTooltip: true,
    },
    columns: [
      {
        field: 'employeeFirstName',
        renderer: ({ record }: { record: WorkShift }) =>
          getName({
            firstName: record.employeeFirstName,
            givenName: record.employeeGivenName,
            lastName: record.employeeLastName,
          }) ?? '',
        text: translate(planningLabels.plannedTasks),
        width: 165,
        draggable: false,

    tooltipRenderer: ({ record }: { record: WorkShift }) =>
      HeaderTooltip(record, localization),
  },
],
selectionMode: { row: false, cell: false },
createEventOnDblClick: false,
removeUnassignedEvent: true,
enableDeleteKey: false,
zoomOnMouseWheel: false,
zoomOnTimeAxisDoubleClick: false,
multiEventSelect: false,
listeners: {},
  };

Post by saki »

Check please your conditions in beforeEventDropFinalize. Is it possible that it always calls context.finalize(false)?

I've tried the following code in our Drag between Schedulers React demo (bottom scheduler) and it behaves as expected, i.e. when called with false then record jumps back to the top scheduler after 1s from drop. If called with true then in stays at the bottom after drag&drop.

    listeners : {
        eventDrop({source, context}) {
            const { eventRecord } = context;
            console.log(eventRecord);
        },
        beforeEventDropFinalize({context}) {
            context.async = true;
            console.log('beforeEventDropFinalize');
            setTimeout(() => {
                console.log('finalizing with false')
                context.finalize(false);
            }, 1000);
        }
    },

Try to remove async from the listener function first.

If this does not help, post please a runnable showcase that we can investigate, run and debug.


Post by jbyvik »

Hmm ok, Mby I explained to much (late nights). The issue isnt in beforeEventDropFinaalize but in eventDrop function.

Using https://bryntum.com/examples/scheduler/drag-between-schedulers/

I added test: 'danny' to all of the events.

I then added this to top-scheduler

listeners: {
eventDrop({ source, context }) {
        context.eventRecord.name= context.eventRecord.test;
      },
},

Resulting in the following behaviour
https://imgur.com/a/K1kN61q

where the "problem" is that its not assigning name to danny when draging an event from bottom > top. Works perfectly fine in top > top :)


Post by saki »

I see. It is too late to update the dragged records on eventDrop event listener. Put the following in beforeEventDropFinalize and it should work:

beforeEventDropFinalize({context}) {
    context.eventRecords[0].name = 'foo';
},

However, is should behave the same way whether the event is dragged within the scheduler or from another one. The ticket is here: https://github.com/bryntum/support/issues/2819


Post by jbyvik »

Great, will try this out :)

//J


Post by jbyvik »

A continuation on this :) Im currently being able to set resource and endDate on the beforeEventDropFinalize but startDate seems to be take from something thats not eventRecords[0]. Tried setting context.startDate to a value without effect (its reassigning the event correctly so its getting passed the context.finalize(false) func and return;

When im setting endDate it sets the "dropped" startDate but modified endDate (meaning it extends events).
Whats used for setting the startDate for events here?

ps. this way of assigning startDate is working on eventDrop, but only if its inside the same scheduler, not between two)

async beforeEventDropFinalize({ source, context }: any) {
        const scheduler: SchedulerPro = source;

    context.async = true;
    if (
      context.eventRecords[0].taskType === TaskTypeEnumV1.break ||
      context.eventRecords[0].taskType === TaskTypeEnumV1.report
    ) {
      context.finalize(false);
      return;
    }
    context.newResource = scheduler.resourceStore.findRecord(
      'id',
      context.eventRecord.customerId,
    );
    context.eventRecords[0].startDate =
      context.eventRecord.possibleStartDate;
    context.finalize(true);
  },

Post by saki »

It seems that the both issues have the same root and I assume both will be fixed with the fix of the above ticket. As to startDate, it is calculated from the drop position and it also depends on time slots, etc.

You could probably use validatorFn for startDate, similar way as here: https://bryntum.com/examples/scheduler/validation/ or another afterDrop event as a workaround until the issue gets fixed.


Post by jbyvik »

Suspected as much :) I will do a workaround. Looking forward to the fix! Thanks for the help :)


Post by mats »

Please try this as a workaround:

eventDrop({ context }) {
            console.log('eventDrop');

        const eventRecord = scheduler2.eventStore.getById(context.eventRecord.id);

        eventRecord.name += 'world';
    }

Post Reply