Our pure JavaScript Scheduler component


Post by andrew.perera »

Hi Team,

Ever since we changed our version 4.0.8 to 5.0.1 eventDragAbort does not fire. However, in our two scheduler design when dragging the event from top-grid to bottom-grid we had event fire during eventDragAbort. We have this inside a useEffect without dependencies.

Currently we are using :
v5.0.1
scheduler.umd.min.js
cheduler.umd.min.js.map

 useEffect(() => {
    ref.current.schedulerEngine.on('eventDragAbort', (eventRecords) => {
      // console.log('targetResource', targetResource);
      let resourceBranch;
      const draggedEvent = eventRecords.draggedRecords[0].data;
      // console.log('draggedEvent', draggedEvent);
      const { duration: eventDuration, type: serviceType } = draggedEvent;
      const { typeCode, engineer, projectResource } = targetResource;

  if (!targetResource.projectBranches) {
    resourceBranch = draggedEvent.ownerCode;
  } else {
    resourceBranch = targetResource.projectBranches.find(
      (resBranch) => resBranch.code === draggedEvent.ownerCode
    );

Please do let me know if you need more info. This issue is a bit critical as none of the validations are firing. Cheers


Post by alex.l »

Hi andrew.perera,

Hard to say with no context. Please post a runnable test case, not clear what's inside ref.current.schedulerEngine. Or check yourself if it's Scheduler instance. And please provide steps when do you expect to have this event fired.

https://bryntum.com/docs/scheduler/api/Scheduler/view/SchedulerBase#event-eventDragAbort

All the best,
Alex


Post by andrew.perera »

Hi Alex,
Thanks for getting back mate,

Yes, "ref.current.schedulerEngine" is the scheduler instance.

We fire this event,

  1. Event is dragged to the bottom grid
  2. Pre-validation is run via - using validatorFn
  3. Event will push back to where it was ( Return to the top grid)
  4. eventDragAbort runs to show users an error modal.

However, the eventDragAbort does not fire as expected. this did work 4.0.8 though.

Kind Regards

Andrew Perera.


Post by andrew.perera »

Hi Alex,
I tried changing the sample code in https://bryntum.com/examples/scheduler/validation/

  1. In this particular sample code validatorfn will fire after an eventDrag
  2. I have included eventDragAbort as a listener
  3. Now I tried to drag Scrum Task to a Sales or CEO/CTO guy
  4. I am expecting eventDragAbort to fire here.

Have I done anything wrong?

Please find the code here.

import { ResourceModel, EventModel, DateHelper, Scheduler, Toast, MessageDialog, StringHelper } from '../../build/scheduler.module.js?458964';
import shared from '../_shared/shared.module.js?458964';

class Employee extends ResourceModel {
    static get fields() {
        return [
            { name : 'available', type : 'boolean', defaultValue : true },
            { name : 'statusMessage', defaultValue : 'Gone fishing' }
        ];
    }

get cls() {
    return this.available ? '' : 'unavailable';
}
}

class Task extends EventModel {
    static get fields() {
        return ['type'];
    }

get dragValidationText() {
    const { resource, type } = this;

    let result = '';

    switch (type) {
        case 'Golf':
            result = 'Only C-suite people can play Golf';
            break;
        case 'Meeting':
            result = `Only ${resource.role} can participate in meetings`;
            break;
        case 'Coding':
            result = `Only ${resource.role} can do coding`;
            break;
        case 'Sales':
            result = `Only ${resource.role} can prepare marketing strategies`;
            break;
        case 'Fixed':
            result = 'Fixed time event - may be reassigned, but not rescheduled';
            break;
    }

    return result;
}

get resizeValidationText() {
    let result = '';

    switch (this.type) {
        case 'Golf':
            result = 'Golf game has always fixed duration';
            break;
        case 'Coding':
            result = 'Programming task duration cannot be shortened';
            break;
    }

    return result;
}
}

const scheduler = new Scheduler({
    appendTo          : 'container',
    // don't allow tasks to overlap
    allowOverlap      : false,
    resourceImagePath : '../_shared/images/users/',
    features          : {
        stripe       : true,
        timeRanges   : true,
        eventTooltip : {
            template : data => {
                const task = data.eventRecord;
                return `
                    ${task.name ? StringHelper.xss`<div class="b-sch-event-title">${task.name}</div>` : ''}
                    ${data.startClockHtml}
                    ${data.endClockHtml}
                    ${(task.dragValidationText || task.resizeValidationText) ? `<div class="restriction-title"><b>Restrictions:</b></div>
                    <ul class="restriction-list">
                        ${task.dragValidationText ? `<li>${task.dragValidationText}</li>` : ''}
                        ${task.resizeValidationText ? `<li>${task.resizeValidationText}</li>` : ''}
                    </ul>` : ''}
                `;
            }
        },
        eventDrag : {
            validatorFn({ eventRecords, newResource }) {
                console.log("eventDrag  validatorFn");
                const
                    task    = eventRecords[0],
                    isValid = task.type === 'Fixed' ||
                        // Only C-suite people can play Golf
                        (task.type === 'Golf' && ['CEO', 'CTO'].includes(newResource.role)) ||
                        // Tasks that have type defined cannot be assigned to another resource type
                        !(task.type && newResource.role !== task.resource.role);

            return {
                valid   : newResource.available && isValid,
                message : !newResource.available ? newResource.statusMessage : (!isValid ? task.dragValidationText : '')
            };
        }
    },
    eventResize : {
        validatorFn({ eventRecord : task, endDate, startDate }) {
            const
                originalDuration = task.endDate - task.startDate,
                isValid          = !(task.type === 'Golf' || (task.type === 'Coding' && originalDuration > endDate - startDate));
            return {
                valid   : isValid,
                message : isValid ? '' : task.resizeValidationText
            };
        }
    },
    eventDragCreate : {
        validatorFn({ resourceRecord : resource }) {
            return {
                valid   : resource.available,
                message : resource.available ? '' : resource.statusMessage
            };
        }
    }
},

subGridConfigs : {
    locked : { width : 350 }
},

columns : [
    {
        type : 'resourceInfo',
        text : 'Staff'
    },
    {
        text   : 'Role',
        field  : 'role',
        flex   : 1,
        editor : {
            type        : 'combo',
            items       : ['Sales', 'Developer', 'Marketing', 'Product manager'],
            editable    : false,
            pickerWidth : 140
        }
    },
    {
        text  : 'Available',
        type  : 'check',
        field : 'available'
    }
],

crudManager : {
    autoLoad   : true,
    eventStore : {
        modelClass : Task
    },
    resourceStore : {
        modelClass : Employee
    },
    transport : {
        load : {
            url : 'data/data.json'
        }
    },
    // This config enables CrudManager responses validation and dumping of found errors to the browser console.
    // It's meant to be used as a development stage helper only so please set it to false for production systems.
    validateResponse : true
},

barMargin  : 2,
rowHeight  : 50,
startDate  : new Date(2019, 1, 7, 8),
endDate    : new Date(2019, 1, 7, 22),
viewPreset : {
    base      : 'hourAndDay',
    tickWidth : 100
},
multiEventSelect : true,

// Specialized body template with header and footer
eventBodyTemplate : data => `${data.iconCls ? `<i class="${data.iconCls}"></i>` : ''}` +
    StringHelper.xss`<section>
        <div class="b-sch-event-header">${data.headerText}</div>
        <div class="b-sch-event-footer">${data.footerText}</div>
    </section>
`,

eventRenderer({ eventRecord, resourceRecord, renderData }) {
    return {
        headerText : DateHelper.format(eventRecord.startDate, 'LT'),
        footerText : eventRecord.name || '',
        iconCls    : eventRecord.iconCls
    };
},

listeners : {
    beforeEventAdd({ resourceRecords }) {
        const
            [resource] = resourceRecords,
            available  = resource.available;

        if (!available) {
            Toast.show(`Resource not available: ${resource.statusMessage}`);
        }

        return available;
    },

    beforeEventDrag({ eventRecord }) {
        // Only Henrik can be assigned Marketing tasks.
        // constrainDragToResource prevents dragging up or down.
        console.log("beforeEventDrag");
        scheduler.features.eventDrag.constrainDragToResource = eventRecord.type === 'Marketing' && eventRecord.resource.name === 'Henrik';

        // Events with type Fixed must not change time slot.
        scheduler.features.eventDrag.constrainDragToTimeSlot = eventRecord.type === 'Fixed';
    },
    eventDragAbort({ eventRecord }){ 
        console.log("eventDragAbort");
    },

    async beforeEventDropFinalize({ source : scheduler, context }) {
        if (scheduler.confirmationsEnabled) {
            context.async = true;

            const
                namesInQuotes = context.eventRecords.map(eventRecord => `"${StringHelper.encodeHtml(eventRecord.name)}"`),
                result        = await MessageDialog.confirm({
                    title   : 'Please confirm',
                    message : `${namesInQuotes.join(', ')} ${namesInQuotes.length > 1 ? 'were' : 'was'} moved. Allow this operation?`
                });

            // `true` to accept the changes or `false` to reject them
            context.finalize(result === MessageDialog.yesButton);
        }
    },

    async beforeEventResizeFinalize({ source : scheduler, context }) {
        if (scheduler.confirmationsEnabled) {
            context.async = true;

            const
                eventRecord = context.eventRecord,
                result      = await MessageDialog.confirm({
                    title   : 'Please confirm',
                    message : StringHelper.xss`"${eventRecord.name}" duration changed. Allow this operation?`
                });

            // `true` to accept the changes or `false` to reject them
            context.finalize(result === MessageDialog.yesButton);
        }
    }
},

tbar : [
    {
        type        : 'button',
        ref         : 'confirmationBtn',
        text        : 'Enable confirmations',
        toggleable  : true,
        icon        : 'b-fa-square',
        pressedIcon : 'b-fa-check-square',
        onAction    : ({ source : button }) => scheduler.confirmationsEnabled = button.pressed
    },
    {
        type        : 'button',
        ref         : 'lockBtn',
        text        : 'Read only',
        toggleable  : true,
        icon        : 'b-fa-square',
        pressedIcon : 'b-fa-check-square',
        onAction    : ({ source : button }) => scheduler.readOnly = button.pressed
    }
]
});

Cheers!!


Post by mats »

Reproduced, we will look into this! https://github.com/bryntum/support/issues/4658


Post by andrew.perera »

@mats Thanks Mate,

Is there a rough time when we can expect the 5.0.5 release?

Cheers.


Post by alex.l »

It should be released in next few weeks, cannot provide exact dates unfortunately.

All the best,
Alex


Post by mats »

@andrew.perera Can you please try using afterEventDrop event and check the valid flag to detect invalid drops? Does that solve it for you?

We plan to refactor EventDrag later this year, so would be ideal if we can avoid turbulence until we start that task.


Post by andrew.perera »

Hi Mats,
Thanks for the suggession,
We need to see the code and see how much work is involved in converting them to retain the business logic.


Post by alex.l »

Here is an example

const scheduler = new Scheduler({
    appendTo          : 'container',
    
// ... listeners : { afterEventDrop({ eventRecords, valid }) { if (!valid) { // do action if drop is invalid } }, }

All the best,
Alex


Post Reply