Our pure JavaScript Scheduler component


Post by frederick45 »

Hi,
i use scheduler and grid in an salesforce lwc.
My script :
Promise.all([
loadScript(this, SCHEDULER + '/scheduler.lwc.module.js'),
loadStyle(this, SCHEDULER + '/scheduler.stockholm.css'),
])
when i drag an item from the grid to drop it in the scheduler, it always come back to the grid.
Do you have this issue ?

BR


Post by alex.l »

Hi frederick45,

Did you set up drop zone with a DragHelper?
Please check the code in demo here:
https://bryntum.com/products/schedulerpro/examples-scheduler/dragfromgrid/

All the best,
Alex


Post by frederick45 »

hi,

yes i do like this :

    createDrag() 
    {
        this.Drag = new bryntum.scheduler.DragHelper (
            {
                cloneTarget        : true,
                mode               : 'translateXY',               
dropTargetSelector : '.b-timeline-subgrid', targetSelector : '.b-grid-row:not(.b-group-row)',
grid : this.UnplannedGrid, schedule : this.scheduler, constrain : false, outerElement : this.UnplannedGrid.element, construct(config){ const me = this; super.construct(config); me.on({ dragstart : me.onDragStart, drag : me.onDrag, drop : me.onDrop, thisObj : me }); }, createProxy(element) { const proxy = document.createElement('div'), { schedule } = this, task = this.grid.getRecordFromElement(element), durationInPx = schedule.timeAxisViewModel.getDistanceForDuration(task.durationMS); // Fake an event bar proxy.classList.add('b-sch-event-wrap', 'b-sch-event', 'b-unassigned-class', `b-sch-${schedule.mode}`); proxy.innerHTML = `<div class="b-sch-event b-has-content b-sch-event-withicon"> <div class="b-sch-event-content"> <i class="${task.iconCls}"></i> ${task.name} </div> </div>`; if (schedule.isHorizontal) { proxy.style.height = `${schedule.rowHeight - (2 * schedule.resourceMargin)}px`; proxy.style.width = `${durationInPx}px`; } else { proxy.style.height = `${durationInPx}px`; proxy.style.width = `${schedule.resourceColumnWidth}px`; } return proxy; }, onDragStart({ context }) { const me = this, { schedule } = me, { eventTooltip, eventDrag } = schedule.features; // save a reference to the task so we can access it later context.task = me.grid.getRecordFromElement(context.grabbed); // Prevent tooltips from showing while dragging eventTooltip.disabled = true; schedule.enableScrollingCloseToEdges(schedule.timeAxisSubGrid); if (eventDrag.showTooltip && !me.tip) { me.tip = new Tooltip({ align : 'b-t', clippedBy : [schedule.timeAxisSubGridElement, schedule.bodyContainer], forElement : context.element, cls : 'b-popup b-sch-event-tooltip' }); } }, onDrag({ event, context }) { const me = this, .....

Post by alex.l »

Please post a complete code, we will debug it with our LWC examples.
I checked your code fragment visually and I don't see obvious problems.

All the best,
Alex


Post by frederick45 »

Hi Alex,

can you send me your our LWC examples

BR


Post by frederick45 »

my complete code :

/* globals bryntum : true */
import { LightningElement, wire } from 'lwc';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
import { loadScript, loadStyle } from 'lightning/platformResourceLoader';
import SCHEDULER from '@salesforce/resourceUrl/bryntum_scheduler';
import {dependencies, events, resources} from './data';
import getListResource from '@salesforce/apex/Scheduler.getListResource';
import getListEvent from '@salesforce/apex/Scheduler.getListEvent';
import getListEventUnplanned from '@salesforce/apex/Scheduler.getListEventUnplanned';
//import { columns, data } from './data';

export default class Scheduler_component extends LightningElement {

ListResource;
ListEvent;
ListEventNotPlanned;
currentDate = new Date();
scheduler;
UnplannedGrid;
splitter;
Drag;
DomHelper;
DateHelper;
me = this;

@wire(getListEventUnplanned)
wiredgetListofEventNotPlanned({ error, data }) 
{
    if (data) 
    {
        this.ListEventNotPlanned = JSON.parse(data);
        //console.log('this.ListEventNotPlanned JSON:'+JSON.stringify(this.ListEventNotPlanned));

    } else if (error) {
      console.log(error);
    }
}

@wire(getListEvent)
wiredgetListofEvent({ error, data }) 
{
    if (data) 
    {
        this.ListEvent = JSON.parse(data);
        //console.log('this.ListEvent JSON:'+JSON.stringify(this.ListEvent));

    } else if (error) {
      console.log(error);
    }
}

@wire(getListResource)
wiredgetListofResource({ error, data }) 
{
    if (data) 
    {
        this.ListResource = JSON.parse(data);
        //console.log('this.ListResource JSON:'+JSON.stringify(this.ListResource));

    } else if (error) {
      console.log(error);
    }
}

renderedCallback() {
    if (this.bryntumInitialized) {
        return;
    }
    this.bryntumInitialized = true;

    Promise.all([
        loadScript(this, SCHEDULER + '/scheduler.lwc.module.js'),
        loadStyle(this, SCHEDULER + '/scheduler.stockholm.css'),
    ])
        .then(() => {
            bryntum.scheduler.DomHelper.supportsTemplate = false;      
            var DH = bryntum.scheduler.DateHelper,
            date = DH.clearTime(new Date());

            console.log('date :'+date);
            this.createScheduler();
            this.createSplitter();
            this.createGrid(this.ListEventNotPlanned);
            this.createDrag();    
        })
        .catch(error => {
            console.log('createScheduler:'+error);
            this.dispatchEvent(
                new ShowToastEvent({
                    title: "Error loading Bryntum Scheduler",
                    message: error,
                    variant: "error"
                })
            );
        });
}


createDrag() 
{
    this.Drag = new bryntum.scheduler.DragHelper (
        {
            cloneTarget        : true,
            mode               : 'translateXY',               
            dropTargetSelector : '.b-timeline-subgrid',
            targetSelector : '.b-grid-row:not(.b-group-row)',

            grid         : this.UnplannedGrid,
            schedule     : this.scheduler,
            constrain    : false,
            outerElement : this.UnplannedGrid.element,
            
            construct(config){
                const me = this;                
                super.construct(config);     
                console.log('construct config');
                me.on({
                    dragstart : me.onDragStart,
                    drag      : me.onDrag,
                    drop      : me.onDrop,
                    thisObj   : me
                });
            },
            createProxy(element) {
                const
                    proxy        = document.createElement('div'),
                    { schedule } = this,
                    task         = this.grid.getRecordFromElement(element),
                    durationInPx = schedule.timeAxisViewModel.getDistanceForDuration(task.duration);

                    console.log('task.duration:'+task.duration);
                    console.log('durationInPx:'+durationInPx);
                    console.log('schedule.isHorizontal:'+schedule.isHorizontal);

                // Fake an event bar
                proxy.classList.add('b-sch-event-wrap', 'b-sch-event', 'b-unassigned-class', 'b-sch-${schedule.mode}');
                proxy.innerHTML = `<div class="b-sch-event b-has-content b-sch-event-withicon">
                    <div class="b-sch-event-content">
                        <i class="${task.iconCls}"></i> ${task.name}
                    </div>
                </div>`;
                
                if (schedule.isHorizontal) {
                    proxy.style.height = `${schedule.rowHeight - (2 * schedule.resourceMargin)}px`;
                    proxy.style.width  = `${durationInPx}px`;
                }
                else {
                    proxy.style.height = `${durationInPx}px`;
                    proxy.style.width  = `${schedule.resourceColumnWidth}px`;
                }
                console.log('proxy.style.width:'+proxy.style.width);

    
                return proxy;
            },
            onDragStart({ context }) {
                const
                    me                          = this,
                    { schedule }                = me,
                    { eventTooltip, eventDrag } = schedule.features;
        
                // save a reference to the task so we can access it later
                context.task = me.grid.getRecordFromElement(context.grabbed);
        
                // Prevent tooltips from showing while dragging
                eventTooltip.disabled = true;
        
                schedule.enableScrollingCloseToEdges(schedule.timeAxisSubGrid);
        
                if (eventDrag.showTooltip && !me.tip) {
                    me.tip = new Tooltip({
                        align      : 'b-t',
                        clippedBy  : [schedule.timeAxisSubGridElement, schedule.bodyContainer],
                        forElement : context.element,
        
                        cls : 'b-popup b-sch-event-tooltip'
                    });
                }
            },
            onDrag({ event, context }) {


                const
                    me           = this,
                    { schedule } = me,
                    { task }     = context,
                    coordinate   = bryntum.scheduler.DomHelper[`getTranslate${schedule.isHorizontal ? 'X' : 'Y'}`](context.element),
                    startDate    = schedule.getDateFromCoordinate(coordinate, 'round', false),
                    endDate      = startDate && DateHelper.add(startDate, task.duration, task.durationUnit),
                    // Coordinates required when used in vertical mode, since it does not use actual columns
                    resource     = context.target && schedule.resolveResourceRecord(context.target, [event.offsetX, event.offsetY]);
        
                // Don't allow drops anywhere, only allow drops if the drop is on the timeaxis and on top of a Resource
                context.valid = Boolean(startDate && resource) &&
                    (schedule.allowOverlap || schedule.isDateRangeAvailable(startDate, endDate, null, resource));
            
                // Save reference to resource so we can use it in onTaskDrop
                context.resource = resource;
                console.log('ondrag');
        
                if (me.tip && context.valid) {
                        const
                        dateFormat = schedule.displayDateFormat,
                        formattedStartDate = DateHelper.format(startDate, dateFormat),
                        formattedEndDate = DateHelper.format(endDate, dateFormat);
        
                    me.tip.html = `
                        <div class="b-sch-event-title">${task.name}</div>
                        <div class="b-sch-tooltip-startdate">Starts: ${formattedStartDate}</div>
                        <div class="b-sch-tooltip-enddate">Ends: ${formattedEndDate}</div>
                    `;
                    me.tip.showBy(context.element);
                }
                else {
                    me.tip?.hide();
                }
            },                
            // Drop callback after a mouse up, take action and transfer the unplanned task to the real EventStore (if it's valid)
            onDrop({ context, event }) {

                const
                    me                                         = this,
                    { schedule }                               = me,
                    { task, target, resource, valid, element } = context;

                me.tip?.hide();
                console.log('ondrop');

                schedule.disableScrollingCloseToEdges(me.schedule.timeAxisSubGrid);

                // If drop was done in a valid location, set the startDate and transfer the task to the Scheduler event store
                if (valid && target) {
                    const
                        coordinate        = bryntum.scheduler.DomHelper[`getTranslate${schedule.isHorizontal ? 'X' : 'Y'}`](element),
                        date              = schedule.getDateFromCoordinate(coordinate, 'round', false),
                        // Try resolving event record from target element, to determine if drop was on another event
                        targetEventRecord = schedule.resolveEventRecord(target);

                    if (date) {
                        // Remove from grid first so that the data change
                        // below does not fire events into the grid.
                        me.grid.store.remove(task);

                        task.startDate = date;

                        task.assign(resource);
                        schedule.eventStore.add(task);
                    }

                    // Dropped on a scheduled event, display toast
                    if (targetEventRecord) {
                        WidgetHelper.toast(`Dropped on ${targetEventRecord.name}`);
                    }
                }

                if (resource) {
                    resource.cls = '';
                }

                schedule.features.eventTooltip.disabled = false;
            },
            onDragAbort() {
                this.tip?.hide();
            }
        

        },
        
    );



}


createSplitter() {
    this.splitter = new bryntum.scheduler.Splitter({
        appendTo : this.template.querySelector('.main')
    });
    //console.log('this.splitter :'+this.splitter);

}

createScheduler() {

    this.scheduler = window.scheduler = new bryntum.scheduler.Scheduler({
        features: {
            resourceTimeRanges: true,
            nonWorkingTime : true,
            timeRanges: {
                showHeaderElements: true,
                showCurrentTimeLine: true
            },
            eventEdit: {
                extraWidgets: [
                    {
                        type: 'text',
                        name: 'WOnumber',
                        label: 'Number Work Order',
                        typeAhead: true,
                        forceSelection: true,
                        editor: false,
                        selectOnFocus: true,
                        readOnly: true,
                    }             
                ]
            },
            stripe           : true,
            timeRanges       : true,
            eventMenu : {
                items : {
                    unassign : {
                        text   : 'Unassign',
                        icon   : 'b-fa b-fa-user-times',
                        weight : 200,
                        onItem : ({ eventRecord, resourceRecord }) => eventRecord.unassign(resourceRecord)
                    }
                }
            }
        },
        insertFirst: this.template.querySelector('.main'),
        rowHeight: 50,
        barMargin: 8,
        columns: [
            {
                text: 'Salariés',
                width: 150,
                field: 'name',                        
            }
        ],
        startDate: new Date(this.currentDate.getFullYear(),this.currentDate.getMonth(),this.currentDate.getDate()-1),
        endDate: new Date(this.currentDate.getFullYear(),this.currentDate.getMonth(),this.currentDate.getDate()+1),
        viewPreset : {
            base: 'hourAndDay',
            tickWidth: 25,
            columnLinesFor: 0,
            headers: [
                {
                    unit: 'd',
                    align: 'center',
                    dateFormat: 'ddd DD MMM'
                },
                {
                    unit: 'h',
                    align: 'center',
                    dateFormat: 'HH'
                }
            ]
        },
        eventRenderer({ eventRecord, resourceRecord, renderData }) {

            const bgColor = resourceRecord.bg || '';

            renderData.style = `background:${bgColor};border-color:${bgColor};color:${resourceRecord.textColor}`;
            renderData.iconCls.add('b-fa', `b-fa-${resourceRecord.icon}`);

            return eventRecord.name;
        },
        resourceStore: new bryntum.scheduler.ResourceStore({
            data: this.ListResource
        }),
        eventStore: new bryntum.scheduler.EventStore({
            data: this.ListEvent
        }),
        dependencyStore: new bryntum.scheduler.DependencyStore({
            data: dependencies
        }),
        tbar : [
            //'Schedule view',
            '',
            '->',
            { type : 'viewpresetcombo' },
            /*{
                type        : 'button',
                toggleable  : true,
                icon        : 'b-fa-calendar',
                pressedIcon : 'b-fa-calendar-check',
                text        : 'Automatic rescheduling',
                tooltip     : 'Toggles whether to automatically reschedule overlapping tasks',
                cls         : 'reschedule-button',
                onToggle({ pressed }) {
                    schedule.autoRescheduleTasks = pressed;
                }
            },*/
            /*{
                type        : 'buttonGroup',
                toggleGroup : true,
                items       : [
                    {
                        icon            : 'b-fa-fw b-fa-arrows-alt-v',
                        pressed         : 'up.isVertical',
                        tooltip         : 'Vertical mode',
                        schedulerConfig : {
                            mode           : 'vertical',
                            subGridConfigs : {
                                locked : {
                                    minWidth : 100,
                                    flex     : null
                                }
                            }
                        }
                    },
                    {
                        icon            : 'b-fa-fw b-fa-arrows-alt-h',
                        pressed         : 'up.isHorizontal',
                        tooltip         : 'Horizontal mode',
                        schedulerConfig : {
                            mode : 'horizontal'
                        }
                    }
                ],
                onAction({ source : button }) {
                    const newConfig = { ...schedule.initialConfig, ...button.schedulerConfig };
    
                    // Recreate the scheduler to switch orientation
                    schedule.destroy();
                    schedule = new Schedule(newConfig);
    
                    // Provide drag helper a reference to the new instance
                    drag.schedule = schedule;
                }
            }*/
        ]
    });

    //console.log('this.scheduler :'+this.scheduler);


}

createGrid(ListEventNotPlanned) {
    this.UnplannedGrid = new bryntum.scheduler.Grid({
        features: {
            rowReorder: true,
            search: true
        },
        title       : 'Visites non planifiées',
        appendTo : this.template.querySelector('.main'), 
        collapsible : true,
        store : {
            fields : [
                { name : 'start', type : 'date' }
            ],
            data : ListEventNotPlanned
        },
        columns:[
            { type: 'rownumber' },
            {
                text: 'Visite',
                field: 'name',
                flex: 1,
                type: 'template',
                template: (data) => `${data.record.data.name}`,
            },
            {
                text: 'Date',
                field: 'start',
                flex: 1,
                type: 'date',
                format: 'DD/MM/YYYY',
            },
            { text: 'Durée', field: 'duration', flex: 1, type: 'time', format: 'HH:mm' },
        ]
    });
}
}

Post by alex.l »

can you send me your our LWC examples

You have it in examples folder of sources you downloaded. check examples/salesforce

All the best,
Alex


Post by frederick45 »

there is no example of the drag helper


Post by alex.l »

We do not have LWC example with Drag Helper. I asked you to provide the code that I can apply to our example and debug it at runtime, because I don't see the problem after visual checking of your code fragment.
I will check the complete code you posted and update you.

All the best,
Alex


Post by frederick45 »

close this


Post Reply