Our pure JavaScript Scheduler component


Post by juliemilligan »

Hi,
I want to retrieve scheduled tasks and unplanned tasks in one call to our server. Right now my app is calling scheduler.crudManager.load() and unplannedGrid.store.load() to do this. I want to include the unplanned data in the JSON retrieved by scheduler.crudManager.load(), then use the unplanned data in its JSON to load into the unplannedGrid. How would I do this?


Post by marcio »

Hey juliemilligan,

Thanks for reaching out.

You could use the following event listener https://bryntum.com/products/scheduler/docs/api/Scheduler/data/CrudManager#event-beforeResponseApply and apply the changes to a proper format to the scheduler, and also apply the data to the unplannedGrid.

Best regards,
Márcio


Post by juliemilligan »

Thanks Márcio for the reply. How would I apply the data to the unplanned grid?


Post by tasnim »

You can load it with a crudManager by adding a crudStore

First of all, this is how my data looks like

{
  "success"   : true,
  "resources" : {
    "rows" : [
      {
        "id"   : 1,
        "name" : "Arcady",
        "role" : "Core developer"
      },
      {
        "id"         : 2,
        "name"       : "Dave",
        "eventColor" : "green",
        "role"       : "Tech Sales"
      },
      {
        "id"         : 3,
        "name"       : "Henrik",
        "eventColor" : "green",
        "role"       : "Sales"
      },
      {
        "id"         : 4,
        "name"       : "Linda",
        "eventColor" : "red",
        "role"       : "Core developer"
      },
      {
        "id"         : 5,
        "name"       : "Maxim",
        "eventColor" : "red",
        "role"       : "Developer & UX"
      },
      {
        "id"         : 6,
        "name"       : "Mike",
        "eventColor" : "red",
        "role"       : "CEO"
      },
      {
        "id"   : 7,
        "name" : "Lee",
        "role" : "CTO"
      },
      {
        "id"   : 8,
        "name" : "Amit",
        "role" : "Core developer"
      },
      {
        "id"         : 9,
        "name"       : "Kate",
        "eventColor" : "blue",
        "role"       : "Tech Sales"
      },
      {
        "id"         : 10,
        "name"       : "Jong",
        "eventColor" : "blue",
        "role"       : "Sales"
      },
      {
        "id"   : 11,
        "name" : "Lola",
        "role" : "Core developer"
      },
      {
        "id"   : 12,
        "name" : "Lisa",
        "role" : "UX"
      },
      {
        "id"   : 13,
        "name" : "Steve",
        "role" : "COO"
      },
      {
        "id"   : 14,
        "name" : "Malik",
        "role" : "CFO"
      }
    ]
  },
  "events" : {
    "rows" : [
      {
        "id"           : "r1",
        "resourceId"   : 1,
        "name"         : "Restart server (not draggable)",
        "iconCls"      : "b-fa b-fa-server",
        "startDate"    : "2025-12-01T08:00:00",
        "duration"     : 3,
        "durationUnit" : "h",
        "draggable"    : false,
        "resizable"    : true
      },
      {
        "id"           : "r2",
        "resourceId"   : 1,
        "name"         : "Upgrade memory",
        "iconCls"      : "b-fa b-fa-laptop",
        "startDate"    : "2025-12-01T15:00:00",
        "cls"          : "",
        "duration"     : 3,
        "durationUnit" : "h",
        "draggable"    : true,
        "resizable"    : true
      },
      {
        "id"           : "r3",
        "resourceId"   : 2,
        "name"         : "Visit customer",
        "iconCls"      : "b-fa b-fa-user",
        "startDate"    : "2025-12-01T09:00:00",
        "cls"          : "",
        "duration"     : 3,
        "durationUnit" : "h",
        "draggable"    : true,
        "resizable"    : true
      },
      {
        "id"           : "r4",
        "resourceId"   : 3,
        "name"         : "Arrange meetup",
        "iconCls"      : "b-fa b-fa-users",
        "startDate"    : "2025-12-01T09:00:00",
        "cls"          : "",
        "duration"     : 3,
        "durationUnit" : "h",
        "draggable"    : true,
        "resizable"    : true
      },
      {
        "id"           : "r5",
        "resourceId"   : 7,
        "name"         : "Make coffee",
        "startDate"    : "2025-12-01T12:00:00",
        "iconCls"      : "b-fa b-fa-coffee",
        "duration"     : 4,
        "durationUnit" : "h",
        "draggable"    : true,
        "resizable"    : true
      },
      {
        "id"           : "r6",
        "resourceId"   : 9,
        "name"         : "Conference prep",
        "iconCls"      : "b-fa b-fa-building",
        "startDate"    : "2025-12-01T09:00:00",
        "cls"          : "Special",
        "duration"     : 3,
        "durationUnit" : "h",
        "draggable"    : true,
        "resizable"    : true
      },
      {
        "id"           : "r16",
        "resourceId"   : 11,
        "name"         : "Presentation",
        "iconCls"      : "b-fa b-fa-video",
        "startDate"    : "2025-12-01T13:00:00",
        "cls"          : "Special",
        "duration"     : 2,
        "durationUnit" : "h",
        "draggable"    : true,
        "resizable"    : true
      }
    ]
  },
  "timeRanges" : {
    "rows" : []
  },
  "unplannedTasks" : {
    "rows" : [
      {
        "id"           : 1,
        "name"         : "Fun task",
        "duration"     : 4,
        "durationUnit" : "h",
        "iconCls"      : "b-fa b-fa-fw b-fa-beer"
      },
      {
        "id"           : 2,
        "name"         : "Medium fun task",
        "duration"     : 8,
        "durationUnit" : "h",
        "iconCls"      : "b-fa b-fa-fw b-fa-cog"
      },
      {
        "id"           : 3,
        "name"         : "Outright boring task",
        "duration"     : 2,
        "durationUnit" : "h",
        "iconCls"      : "b-fa b-fa-fw b-fa-book"
      },
      {
        "id"           : 4,
        "name"         : "Inspiring task",
        "duration"     : 2,
        "durationUnit" : "h",
        "iconCls"      : "b-fa b-fa-fw b-fa-book"
      },
      {
        "id"           : 5,
        "name"         : "Mysterious task",
        "duration"     : 2,
        "durationUnit" : "h",
        "iconCls"      : "b-fa b-fa-fw b-fa-question"
      },
      {
        "id"           : 6,
        "name"         : "Answer forum question",
        "duration"     : 4,
        "durationUnit" : "h",
        "iconCls"      : "b-fa b-fa-fw b-fa-life-ring"
      },
      {
        "id"           : 7,
        "name"         : "Gym",
        "duration"     : 1,
        "durationUnit" : "h",
        "iconCls"      : "b-fa b-fa-fw b-fa-dumbbell"
      },
      {
        "id"           : 9,
        "name"         : "Book flight",
        "duration"     : 7,
        "durationUnit" : "h",
        "iconCls"      : "b-fa b-fa-fw b-fa-plane"
      },
      {
        "id"           : 10,
        "name"         : "Customer support call",
        "duration"     : 3,
        "durationUnit" : "h",
        "iconCls"      : "b-fa b-fa-fw b-fa-phone"
      },
      {
        "id"           : 11,
        "name"         : "Angular bug fix",
        "duration"     : 3,
        "durationUnit" : "h",
        "iconCls"      : "b-fa b-fa-fw b-fa-bug"
      },
      {
        "id"           : 12,
        "name"         : "React feature fix",
        "duration"     : 2,
        "durationUnit" : "h",
        "iconCls"      : "b-fa b-fa-fw b-fa-cog"
      }
    ]
  }
}

In your crudManager you load that data

const crudManager = new CrudManager({
    autoLoad         : true,
    // This config enables response 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,
    eventStore       : {
        storeClass : TaskStore
    },
    resourceStore : {
        modelClass : CustomResourceModel
    },
    transport : {
        load : {
            url : 'data/data.json'
        }
    }
});

And now you'd add the crudStore

crudManager.addCrudStore([{
    id : 'unplannedTasks', // same key from data
    modelClass : Task,
    objectify : false
}]);

And then you'd need to get the last store (your custom store)

const customStore = crudManager.stores[crudManager.stores.length - 1].store;

And then use it inside of the unplanned grid

const unplannedGrid = new UnplannedGrid({
    ref         : 'unplanned',
    appendTo    : 'main',
    title       : 'Unplanned Tasks',
    collapsible : true,
    flex        : '0 0 300px',
    ui          : 'toolbar',

// Schedulers stores are contained by a project, pass it to the grid to allow it to access them
project : schedule.project,
store : customStore
});

And you'll be good to go.

Here is what the whole code looks like all together

class CustomResourceModel extends ResourceModel {
    static get $name() {
        return 'CustomResourceModel';
    }

static get fields() {
    return [
        // Do not persist `cls` field because we change its value on dragging unplanned resources to highlight the row
        { name : 'cls', persist : false }
    ];
}
}

const crudManager = new CrudManager({
    autoLoad         : true,
    // This config enables response 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,
    eventStore       : {
        storeClass : TaskStore
    },
    resourceStore : {
        modelClass : CustomResourceModel
    },
    transport : {
        load : {
            url : 'data/data.json'
        }
    }
});

let schedule = new Schedule({
    ref         : 'schedule',
    insertFirst : 'main',
    startDate   : new Date(2025, 11, 1, 8),
    endDate     : new Date(2025, 11, 1, 18),
    flex        : 4,
    crudManager,

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;
        }
    }
]
});

crudManager.addCrudStore([{
    id : 'unplannedTasks',
    modelClass : Task,
    objectify : false
}]);

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

const customStore = crudManager.stores[crudManager.stores.length - 1].store;

const unplannedGrid = new UnplannedGrid({
    ref         : 'unplanned',
    appendTo    : 'main',
    title       : 'Unplanned Tasks',
    collapsible : true,
    flex        : '0 0 300px',
    ui          : 'toolbar',

// Schedulers stores are contained by a project, pass it to the grid to allow it to access them
project : schedule.project,
store : customStore
});

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

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
});

You can test this with our drag-from-grid demo locally

Here is the result

Screenshot 2024-02-28 125057.png
Screenshot 2024-02-28 125057.png (272.68 KiB) Viewed 162 times

Hope this helps

Best regards,
Tasnim


Post by juliemilligan »

That worked great! Thanks!


Post Reply