Our pure JavaScript Scheduler component


Post by mikemcs »

In Using ShedulerPro, and inspiration from https://bryntum.com/products/schedulerpro/examples/drag-unplanned-tasks/

I load my stores using PHP without CRUD (not using CRUD I think is my issue). When I Right click an Event and unassign it on the Schedule, how can I get the event to show up in the Unplanned grid without refreshing the page?

    project : {

    resourceStore      : {
        createUrl   : 'Schedule.php?LOC=RESOURCE_CREATEDATA',
        readUrl      : 'Schedule.php?LOC=RESOURCE_READDATA',
        updateUrl : 'Schedule.php?LOC=RESOURCE_UPDATEDATA',

        autoLoad   : true,
        autoCommit : true,
        sendAsFormData : true
    },

    eventStore : {
        storeClass : TaskStore,
        createUrl   : 'Schedule.php?LOC=EVENT_CREATEDATA',
        readUrl      : 'Schedule.php?LOC=EVENT_READDATA',
        updateUrl : 'Schedule.php?LOC=EVENT_UPDATEDATA',

        autoLoad : true,
        autoCommit : true,
        sendAsFormData : true
    },

    assignmentStore :  {
        createUrl   : 'Schedule.php?LOC=ASSIGNMENT_CREATEDATA',
        readUrl      : 'Schedule.php?LOC=ASSIGNMENT_READDATA',
        updateUrl : 'Schedule.php?LOC=ASSIGNMENT_UPDATEDATA',
        deleteUrl   : 'Schedule.php?LOC=ASSIGNMENT_DELETEDATA',

        autoLoad   : true,
        autoCommit : true,
        sendAsFormData : true

    }

Sample.jpg
Sample.jpg (45.51 KiB) Viewed 798 times

Post by marcio »

Hey mikemcs,

You can use https://bryntum.com/products/gantt/docs/api/Core/data/mixin/StoreCRUD#function-add to add the record to the Grid store and from there it'll appear inside the Grid.

Best regards,
Márcio


Post by mikemcs »

Can I use this function even If I am not using CRUD?

How do I get to the json record when I click the unassignbutton to pass to the add function?


Post by mikemcs »

I thought I found the Found the answers on https://bryntum.com/products/schedulerpro/examples-scheduler/dragfromgrid/ but these seems to duplicate the event.

schedule.assignmentStore.on({
    // When a task is unassigned move it back to the unplanned tasks grid
    remove({ records }) {
        records.forEach(({ event }) => {
            schedule.eventStore.remove(event);
            unplannedGrid.store.add(event);
        });
    },
    thisObj : this
});

Last edited by mikemcs on Thu Mar 30, 2023 4:10 pm, edited 1 time in total.

Post by marcio »

That's the one mikemcs, glad that you found and figure it out! :)

Feel free to reach us when you need.

Best regards,
Márcio


Post by mikemcs »

This has an adverse effect, it seems to duplicate the record not move it.


Post by mikemcs »

Here is my drop code, this is actually from: https://bryntum.com/products/schedulerpro/examples/drag-unplanned-tasks/

    // Drop callback after a mouse up, take action and transfer the unplanned appointment to the real EventStore (if it's valid)
    async onDrop({ context }) {
        const
            me           = this,
            { schedule } = me;

    // If drop was done in a valid location, set the startDate and transfer the task to the Scheduler event store
    if (context.valid) {
        const
            { appointments, element, relatedElements = [], doctor } = context,
            coordinate                                              = DomHelper.getTranslateX(element),
            dropDate                                                = schedule.getDateFromCoordinate(coordinate, 'round', false),
            eventBarEls                                             = [element, ...relatedElements];


        for (let i = 0; i < appointments.length; i++) {
            // We hand over the data + existing element to the Scheduler so it do the scheduling
            // await is used so that we have a reliable end date in the case of multiple event drag
            await schedule.scheduleEvent({
                eventRecord    : appointments[i],

                // When dragging multiple appointments, add them back to back + assign to resource
                startDate      : i === 0 ? dropDate : appointments[i - 1].endDate,

                // Assign to the doctor (resource) it was dropped on
                resourceRecord : doctor,
                element        : eventBarEls[i]
            });
        }
    }

    schedule.disableScrollingCloseToEdges(schedule.timeAxisSubGrid);
    schedule.features.eventTooltip.disabled = false;

}
};

In the drag-unplanned-tasks example, the schedule and the grid share the same store but are filtered by if the event is scheduled or not.

// Holds unplanned sessions, that can be dragged to the schedule
const unplannedGrid = new UnplannedGrid({
    ref       : 'unplanned',
    flex      : '0 1 300px',
    appendTo  : 'main',
    project   : schedule.project,

});

and

    set project(project) {
        // Create a chained version of the event store as our store. It will be filtered to only display events that
        // lack assignments
        this.store = project.eventStore.chain(eventRecord => !eventRecord.assignments.length);

I think what I need to do is just refresh the unplanned grid not add a record to it, but I am not sure?


Post by marcio »

Hey mikemcs,

That sounds correct, you could use the following snippet to listen for changes and then update the Grid (retrieved from the same demo)

// When assignments change, update our chained store to reflect the changes
        project.assignmentStore.on({
            change() {
                this.store.fillFromMaster();
            },
            thisObj : this
        });

so the full project configuration would be like this

    set project(project) {
        // Create a chained version of the event store as our store. It will be filtered to only display events that
        // lack assignments
        this.store = project.eventStore.chain(eventRecord => !eventRecord.assignments.length);

    // When assignments change, update our chained store to reflect the changes
    project.assignmentStore.on({
        change() {
            this.store.fillFromMaster();
        },
        thisObj : this
    });
}

Best regards,
Márcio


Post by mikemcs »

Unfortunately, the code you suggested is what I already have.

I have attached a video of the issue here and a dump of my code.

My code is a Frankenstein as I have used bits and pieces from multiple examples learning how to interact with schedulePro:

import * as Module from '../../build/schedulerpro.module.js';
Object.assign(window, Module);

CSSHelper.insertRule('.weekend { background: transparent repeating-linear-gradient(-55deg, #dddddd99, #dddddd99 10px, #eeeeee99 5px, #eeeeee99 20px); }');

const Now = DateHelper.format(new Date(),  'MM/DD/YY HH:mm');
const Numberformatter = new NumberFormat('9,999.##');
const   zoomLevels = {
                       minuteAndHour : 1,
                       hourAndDay    : 1
                     };

// Handles dragging unscheduled appointment from the grid onto the schedule
class Drag extends DragHelper {
    static get defaultConfig() {
        return {
            callOnFunctions      : true,
            autoSizeClonedTarget : false,
            unifiedProxy         : true,

        // Prevent removing proxy on drop, we adopt it for usage in the Schedule
        removeProxyAfterDrop : false,

        // Don't drag the actual row element, clone it
        cloneTarget        : true,

        // Only allow drops on the schedule area
        dropTargetSelector : '.b-timeline-subgrid',

        // Only allow drag of row elements inside on the unplanned grid
        targetSelector     : '.b-grid-row:not(.b-group-row)'
    };
}

afterConstruct(config) {
    // Configure DragHelper with schedule's scrollManager to allow scrolling while dragging
    this.scrollManager = this.schedule.scrollManager;
}

createProxy(grabbedElement, initialXY) {
    const
        { context, schedule, grid } = this,
        draggedAppointment          = grid.getRecordFromElement(grabbedElement),
        durationInPixels            = schedule.timeAxisViewModel.getDistanceForDuration(draggedAppointment.durationMS),
        proxy                       = document.createElement('div');

    proxy.style.cssText = '';
    proxy.style.width  = `${durationInPixels}px`;
    proxy.style.height = schedule.rowHeight - (2 * schedule.resourceMargin) + 'px';

    // Fake an event bar
    proxy.classList.add('b-sch-event-wrap', 'b-sch-style-border', 'b-unassigned-class', 'b-sch-horizontal');
    proxy.innerHTML = StringHelper.xss`
        <div class="b-sch-event b-has-content b-sch-event-withicon">
            <div class="b-sch-event-content">
                <div>
                    <div>${draggedAppointment.name}</div>
                </div>
            </div>
        </div>
    `;

    let totalDuration = 0;
    grid.selectedRecords.forEach(appointment => totalDuration += appointment.duration);
    context.totalDuration = totalDuration;
    return proxy;
}

onDragStart({ context }) {
    const
        me                         = this,
        { schedule, grid }         = me,
        { selectedRecords, store } = grid,
        appointment                = grid.getRecordFromElement(context.grabbed);

    // save a reference to the appointments being dragged so we can access them later
    context.appointments    = selectedRecords.slice();
    context.relatedElements = selectedRecords.sort((r1, r2) => store.indexOf(r1) - store.indexOf(r2)).map(rec => rec !== appointment && grid.rowManager.getRowFor(rec).element).filter(el => el);

    schedule.enableScrollingCloseToEdges(schedule.timeAxisSubGrid);

    // Prevent tooltips from showing while dragging
    schedule.features.eventTooltip.disabled = true;
}

onDrag({ context }) {
    const
        { schedule }                    = this,
        { appointments, totalDuration } = context,

        newStartDate                    = schedule.getDateFromCoordinate(context.newX, 'round', false),
        lastAppointmentEndDate          = newStartDate && DateHelper.add(newStartDate, totalDuration, appointments[0].durationUnit),
        doctor                          = context.target && schedule.resolveResourceRecord(context.target),
        calendar                        = doctor?.calendar;

    // Save reference to the doctor so we can use it in onAppointmentDrop
    context.doctor = doctor;
}

// Drop callback after a mouse up, take action and transfer the unplanned appointment to the real EventStore (if it's valid)
async onDrop({ context }) {
    const
        me           = this,
        { schedule } = me;

    // If drop was done in a valid location, set the startDate and transfer the task to the Scheduler event store
    if (context.valid) {
        const
            { appointments, element, relatedElements = [], doctor } = context,
            coordinate                                              = DomHelper.getTranslateX(element),
            dropDate                                                = schedule.getDateFromCoordinate(coordinate, 'round', false),
            eventBarEls                                             = [element, ...relatedElements];


        for (let i = 0; i < appointments.length; i++) {
            // We hand over the data + existing element to the Scheduler so it do the scheduling
            // await is used so that we have a reliable end date in the case of multiple event drag
            await schedule.scheduleEvent({
                eventRecord    : appointments[i],

                // When dragging multiple appointments, add them back to back + assign to resource
                startDate      : i === 0 ? dropDate : appointments[i - 1].endDate,

                // Assign to the doctor (resource) it was dropped on
                resourceRecord : doctor,
                element        : eventBarEls[i]
            });
        }
    }

    schedule.disableScrollingCloseToEdges(schedule.timeAxisSubGrid);
    schedule.features.eventTooltip.disabled = false;

}
};

// Customized scheduler displaying hospital appointments
class Schedule extends SchedulerPro {
    static get $name() {
        return 'Schedule';
    }

static get configurable() {
    return {
        features          : {
            columnLines : true,

        },

        //rowHeight           : 80,
        barMargin           : 10,
        eventStyle          : 'border',
        eventColor          : 'indigo',
        //allowOverlap        : false,
        //useInitialAnimation : false,

        columns             : [
            {
                type           : 'resourceInfo',
                text           : 'Cell',
                field          : 'name',
                showImage      : false,
                filterable     : {
                    filterField : {
                        triggers : {
                            search : {
                                cls : 'b-icon b-fa-filter'
                            }
                        },
                        placeholder : 'Filter Cells'
                    }
                }
            },

        ]
    }
};

// Return a DOM config object for what to show inside the event bar (you can return an HTML string too)
eventRenderer({ eventRecord }) {

    return [
        {
            children : [
                {
                    class : 'b-event-name',
                    text  : 'Job:' + eventRecord.name + ' Qty:' + Numberformatter.format(eventRecord.qty)
                },
                {
                    class : 'b-event-name',
                    text  : eventRecord.partno
                }
            ]
        }
    ];
}

};

// Custom grid that holds unassigned appointments
class UnplannedGrid extends Grid {
    static get configurable() {
        return {
            selectionMode : {
                cell : false
            },

        features : {
            filterBar   : {
                compactMode : true
            },

            /*
            group  : {
                field : 'dept',
                renderer({ groupRowFor, column }) {
                    if (column.parentIndex === 0) {
                        return `Dept:${groupRowFor} Jobs`;
                    }
                }
            }
            */
        },

        columns : [
            {
                type     : 'template',
                text     : 'Jobs',
                flex     : 2,
                cellCls  : 'unscheduledNameCell',
                template : ({ record: appointment }) => `
                    <div class="name-container">
                        <span>${StringHelper.encodeHtml(appointment.name)}</span>
                        <span class="patient-name">${appointment.partno}</span>
                    </div>
                `
            },
            {
                type     : 'template',
                text     : 'Hours',
                flex     : 1,
                cellCls  : 'unscheduledNameCell',
                template : ({ record: appointment }) => `
                    <div class="name-container">
                        <span>${StringHelper.encodeHtml(appointment.duration)}</span>
                    </div>
                `
            },
            {
                type     : 'template',
                text     : 'Quantity',
                flex     : 1,
                cellCls  : 'unscheduledNameCell',
                template : ({ record: appointment }) => `
                    <div class="name-container">
                        <span>${StringHelper.encodeHtml(Numberformatter.format(appointment.qty))}</span>
                    </div>
                `
            }
        ],


        tbar : [
            {
                type : 'widget',
                tag  : 'strong',
                html : 'Unplanned Jobs',
                flex : 1
            },
            {
                icon     : 'b-fa b-fa-angle-double-down',
                cls      : 'b-transparent',
                tooltip  : 'Expand all groups',
                onAction : 'up.expandAll'
            },
            {
                icon     : 'b-fa b-fa-angle-double-up',
                cls      : 'b-transparent',
                tooltip  : 'Collapse all groups',
                onAction : 'up.collapseAll'
            },
        ],

        rowHeight : 65,
        disableGridRowModelWarning : true
    };
}

static get $name() {
    return 'UnplannedGrid';
}


set project(project) {
    // Create a chained version of the event store as our store. It will be filtered to only display events that
    // lack assignments
    this.store = project.eventStore.chain(eventRecord => !eventRecord.assignments.length);

// When assignments change, update our chained store to reflect the changes
project.assignmentStore.on({
    change() {
        this.store.fillFromMaster();
    },
    thisObj : this
});
}
};

class Task extends EventModel {

static $name = 'Task';

static get defaults() {
    return {
        // In this demo, default duration for tasks will be hours (instead of days)
        durationUnit : 'h',

        // Use a default name, for better look in the grid if unassigning a new event
        name : 'New event',

        // Use a default icon also
        iconCls : 'b-fa b-fa-asterisk'
    };
}
}

class TaskStore extends EventStore {

static $name = 'TaskStore';

static get defaultConfig() {
    return {
        modelClass : Task
    };
}

// Override add to reschedule any overlapping events caused by the add
add(records, silent = false) {
    const me = this;

    if (me.autoRescheduleTasks) {
        // Flag to avoid rescheduling during rescheduling
        me.isRescheduling = true;
        me.beginBatch();
    }

    if (!Array.isArray(records)) {
        records = [records];
    }

    super.add(records, silent);

    if (me.autoRescheduleTasks) {
        me.endBatch();
        me.isRescheduling = false;
    }
}

// Auto called when triggering the update event.
// Reschedule if the update caused the event to overlap any others.
onUpdate({ record }) {

    this.rescheduleOverlappingTasks(record);


    /*
    if (this.autoRescheduleTasks && !this.isRescheduling)  {
        alert('123');

        this.rescheduleOverlappingTasks(record);
    }
    */

}

rescheduleOverlappingTasks(eventRecord) {
    if (eventRecord.resource) {
        const
            futureEvents  = [],
            earlierEvents = [];

        // Split tasks into future and earlier tasks
        eventRecord.resource.events.forEach(event => {
            if (event !== eventRecord) {
                if (event.startDate >= eventRecord.startDate) {
                    futureEvents.push(event);
                }
                else {
                    earlierEvents.push(event);
                }
            }
        });

        if (futureEvents.length || earlierEvents.length) {
            futureEvents.sort((a, b) => a.startDate > b.startDate ? 1 : -1);
            earlierEvents.sort((a, b) => a.startDate > b.startDate ? -1 : 1);

            futureEvents.forEach((ev, i) => {
                const prev = futureEvents[i - 1] || eventRecord;

                ev.startDate = DateHelper.max(prev.endDate, ev.startDate);
            });


            // mjm 3/29/23 Removed this code as I dont want previous events rescheduling
            // Walk backwards and remove any overlap
            /*
            [eventRecord, ...earlierEvents].forEach((ev, i, all) => {
                const prev = all[i - 1];

                if (ev.endDate > Date.now() && ev !== eventRecord && prev) {
                    ev.setEndDate(DateHelper.min(prev.startDate, ev.endDate), true);
                }
            });
            */

            this.isRescheduling = false;
        }
    }
}
};

// Displays planned sessions
const schedule = new Schedule({
    ref       : 'schedule',
    appendTo  : 'main',
    durationUnit : 'h',


startDate : DateHelper.add(Now, -6, 'hours'),
endDate : DateHelper.add(Now, 7, 'days'),

//allowOverlap        : true,
autoRescheduleTasks : true,

flex      : 1,
autoHeight:true,
eventStyle        : 'border',
presets : PresetManager.records.filter(preset => zoomLevels[preset.id]),
//useInitialAnimation : 'slide-from-left',

features  : {


    filterBar   : {
        compactMode : true
    },

    timeRanges : {
                    showCurrentTimeLine :true,
                    showHeaderElements : true,
                    //enableResizing  : true
    },
  pan : true,
  eventDragCreate : false,
  dependencies : false,
 // headerZoom : true,
  percentBar : true,
  resourceNonWorkingTime : true,
      resourceNonWorkingTime : {
                                 maxTimeAxisUnit : 'week'
                               },
},

viewPreset : {
    base           : 'hourAndDay',
    columnLinesFor : 1,
    timeResolution : {
        unit      : 'hour',
        increment : 1
    },

    headers : [
        {
            unit       : 'day',
            increment  : 1,
            dateFormat : 'ddd DD MMM YY'
        },
        {
            unit       : 'hour',
            increment  : 2,
            dateFormat : 'hA'
        }
    ]
},

/*
    columns : [
        { text : 'Cell', field : 'name', width : 160 },
      ],
*/

project : {

    resourceStore      : {
        createUrl : 'http://mmcsherry/poem/Prod/Schedule/Schedule.php?LOC=RESOURCE_CREATEDATA',
        readUrl   : 'http://mmcsherry/poem/Prod/Schedule/Schedule.php?LOC=RESOURCE_READDATA',
        updateUrl : 'http://mmcsherry/poem/Prod/Schedule/Schedule.php?LOC=RESOURCE_UPDATEDATA',

        autoLoad   : true, // Load upon creation
        autoCommit : true,
        sendAsFormData : true
    },

    eventStore : {
        // Unassigned events should remain in store
        //removeUnassignedEvent : false,
        storeClass : TaskStore,
        createUrl : 'http://mmcsherry/poem/Prod/Schedule/Schedule.php?LOC=EVENT_CREATEDATA',
        readUrl   : 'http://mmcsherry/poem/Prod/Schedule/Schedule.php?LOC=EVENT_READDATA',
        updateUrl : 'http://mmcsherry/poem/Prod/Schedule/Schedule.php?LOC=EVENT_UPDATEDATA',

        autoLoad : true, // Load upon creation
        autoCommit : true,
        sendAsFormData : true
    },

    assignmentStore :  {
        createUrl : 'http://mmcsherry/poem/Prod/Schedule/Schedule.php?LOC=ASSIGNMENT_CREATEDATA',
        readUrl   : 'http://mmcsherry/poem/Prod/Schedule/Schedule.php?LOC=ASSIGNMENT_READDATA',
        updateUrl : 'http://mmcsherry/poem/Prod/Schedule/Schedule.php?LOC=ASSIGNMENT_UPDATEDATA',
        deleteUrl : 'http://mmcsherry/poem/Prod/Schedule/Schedule.php?LOC=ASSIGNMENT_DELETEDATA',

        autoLoad   : true, // Load upon creation
        autoCommit : true,
        sendAsFormData : true
    }
    ,

    calendarManagerStore : {
        readUrl   : 'http://mmcsherry/poem/Prod/Schedule/Schedule.php?LOC=CALANDAR_READDATA',

        autoLoad   : true, // Load upon creation
        sendAsFormData : true
    }
    ,

    /*
    calendarsData : [
        {
        id                       : 'X',
        name                     : 'X',
        unspecifiedTimeIsWorking : true,
        intervals                : [
                                        {
                                        recurrentStartDate : 'on Sat at 0:00',
                                        recurrentEndDate   : 'on Sun at 22:45',
                                        isWorking          : false,
                                        cls                : 'weekend'
                                        },
                                    ]
        },

        {
        id                       : 'weekends',
        name                     : 'Weekends',
        unspecifiedTimeIsWorking : true,
        intervals                : [
                                        {
                                        recurrentStartDate : 'on Sat at 0:00',
                                        recurrentEndDate   : 'on Sun at 22:00',
                                        isWorking          : false,
                                        cls                : 'weekend'
                                        }
                                    ]
        },
        {
        id                       : 'weekdays',
        name                     : 'Weekdays',
        unspecifiedTimeIsWorking : true,
        intervals                : [
                                        {
                                        recurrentStartDate : 'on mon at 0:00',
                                        recurrentEndDate   : 'on sat at 0:00',
                                        isWorking          : false,
                                        cls                : 'weekend'
                                        }
                                        ,
                                        {
                                        startDate : "2023-03-29T02:00",
                                        endDate   : "2023-03-29T08:00",
                                        isWorking          : true,
                                        }

                                    ]
        }
    ],
    */

},


timeRanges : [
    { id : 1, name : '@', startDate : Now, iconCls   : "b-fa b-fa-arrows-rotate"}
  ],

tbar : [
/*
{
    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 : 'button',
        ref  : 'zoomInButton',
        icon : 'b-icon-search-plus',
        text : 'Zoom in',
        onClick() {
            schedule.zoomIn();
        }
    },
    {
        type : 'button',
        ref  : 'zoomOutButton',
        icon : 'b-icon-search-minus',
        text : 'Zoom out',
        onClick() {
            schedule.zoomOut();
        }
    },
    {
        icon     : 'b-icon b-fa-caret-left',
        onClick() {
         schedule.shiftPrevious();
        }
    },
    {
        type     : 'button',
        text     : 'Today',
        onClick() {
         const startDate = DateHelper.clearTime(new Date());
         schedule.setTimeSpan(DateHelper.add(startDate, schedule.startDate, 'd'), DateHelper.add(startDate, schedule.endDate, 'd'));
        }
    },
    {
        icon     : 'b-icon b-fa-caret-right',
        onClick() {
         schedule.shiftNext();
        }
    },

    {
        type                 : 'textfield',
        ref                  : 'filterByName',
        icon                 : 'b-fa b-fa-filter',
        placeholder          : 'Find Scheduled Jobs',
        clearable            : true,
        keyStrokeChangeDelay : 100,
        width                : 200,
        triggers             : {
            filter : {
                align : 'start',
                cls   : 'b-fa b-fa-filter'
            }
        },
        onChange({ value }) {
            value = value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');

            // Replace all previous filters and set a new filter
            schedule.eventStore.filter({
                filters : event => event.name.match(new RegExp(value, 'i')),
                replace : true
            });
        }
    },

    {
        type                 : 'textfield',
        ref                  : 'filterByPartno',
        icon                 : 'b-fa b-fa-filter',
        placeholder          : 'Find Scheduled Parts',
        clearable            : true,
        keyStrokeChangeDelay : 100,
        width                : 200,
        triggers             : {
            filter : {
                align : 'start',
                cls   : 'b-fa b-fa-filter'
            }
        },
        onChange({ value }) {
            value = value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');

            // Replace all previous filters and set a new filter
            schedule.eventStore.filter({
                filters : event => event.partno.match(new RegExp(value, 'i')),
                replace : true
            });
        }
    },


],

updateAutoRescheduleTasks(autoRescheduleTasks) {
    this.eventStore.autoRescheduleTasks = autoRescheduleTasks;
}

});


const splitter = new Splitter({
    appendTo : 'main'
});

// Holds unplanned sessions, that can be dragged to the schedule
const unplannedGrid = new UnplannedGrid({
    ref       : 'unplanned',
    flex      : '0 1 300px',
    appendTo  : 'main',
    project   : schedule.project,

});


// Handles dragging
const drag = new Drag({
    grid         : unplannedGrid,
    schedule,
    constrain    : false,
    outerElement : unplannedGrid.element
});



//mjm 3/30/23 The way the stores are used currently in this code (grid store is a chained copy of schedule store) this kinda works but it duplicates the record[attachment=0]Demo.mp4[/attachment]
/*
schedule.assignmentStore.on({
    // When a task is unassigned move it back to the unplanned tasks grid
    remove({ records }) {
        records.forEach(({ event }) => {
            schedule.eventStore.remove(event);
            unplannedGrid.store.add(event);


    });
},
thisObj : this
});
*/
Attachments
Demo.mp4
(8.26 MiB) Downloaded 18 times

Post by marcio »

Hey mikemcs,

Could you provide some data samples for us to use with your project sample?? We're looking into the code, but we need some samples of data to test it.

Best regards,
Márcio


Post Reply