Our pure JavaScript Scheduler component


Post by davidm »

Hi!
I'm working on work scheduling management. I'm sorry for long topic and multiple questions, I think it's more clear as it needs the context for understanding.

Every event has attribute hours says is how many hour per day should person spent on this task (MyEventModel).

export class MyEventModel extends EventModel {
  public hours: number; // hours per day
}

Every (Person) ResourceModel has working hours - which says how many hours is available on day:

availability: [
{
    resourceId: 23,
    availabilities: {
       "2021-06-01": 8,
       "2021-06-02": 8,
       "2021-06-03": 4,
       "2021-06-04": 0,
    } 
},
{
    resourceId: 24,
    availabilities: {
       "2021-06-01": 0,
       "2021-06-02": 2,
       "2021-06-03": 4,
       "2021-06-04": 0,
    } 
},
...
];

I need to visualise this information:

  1. Set default fix height of each resource row with default height (150px)
    const schedulerConfiguration = {
       rowHeight: 150,
       ...
    }
  2. Set height of each event based on formula MyEventModel.hours * 20 = height
  3. this steps works with
    const schedulerConfiguration = {
       ...
       eventRenderer: (data) => {
          data.renderData.height = data.eventRecord.hours * 20;
          ...
       }
       ...
    }

the issue:
The events are sized based on the set data.renderData.height = but spaces between events are not correct (small event has big space, but larger events are covering each other):

Screenshot 2021-06-16 at 10.01.45.png
Screenshot 2021-06-16 at 10.01.45.png (105.02 KiB) Viewed 1744 times

my questions:

  1. how to fix the issue above?

  2. When availability of the person on specific day is smaller than sum of hours of events on this day, I need to make the resource row higher - 300px.

  3. How to put a content into column which is not an Event? I need to show in the right bottom corner how many hours person is available on specific day.

Thank you very much!

David


Post by saki »

There is on config option that could help: https://bryntum.com/docs/scheduler/#Scheduler/view/mixin/TimelineEventRendering#config-managedEventSizing Set it to true and then the rendering logic won't interfere with your height settings. See also: https://localhost/bryntum-suite/Scheduler/examples/customeventstyling/

However, the placement of the events will now also be the responsibility of your renderer/CSS.

rowHeight can be set for a resource at data level, see https://bryntum.com/docs/scheduler/#Scheduler/model/mixin/ResourceModelMixin#field-rowHeight please.

How to put a content into column which is not an Event? I need to show in the right bottom corner how many hours person is available on specific day.

For anything to appear in the timeline automatically, it must be an event in the eventStore. You could give such 'non-events' a flag using custom event model that would distinguish them from others.


Post by davidm »

Thank for your response, saki!

I'm trying to demo it, but it doesn't seem it works for me. Please check the example:
https://bryntum.com/examples/scheduler/configuration/ - replace example code in the editor with:

import { DateHelper, PresetStore, EventModel, Scheduler, PresetManager } from '../../build/scheduler.module.js?450898';
import shared from '../_shared/shared.module.js?450898';


const
    resources = [
        { id : 1, name : 'Arcady', role : 'Core developer', eventColor : 'purple', rowHeight: 200 },
        { id : 2, name : 'Dave', role : 'Tech Sales', eventColor : 'indigo', rowHeight: 250 }
    ],
    events    = [
        {
            id          : 1,
            resourceId  : 1,
            height       : 50,
            startDate   : new Date(2021, 1, 1),
            endDate     : new Date(2021, 1, 2)
        },
        {
            id          : 2,
            resourceId  : 2,
            height       : 20,
            startDate   : new Date(2021, 1, 1),
            endDate     : new Date(2021, 1, 2)
        },

        {
            id          : 3,
            resourceId  : 1,
            height       : 100,
            startDate   : new Date(2021, 1, 1),
            endDate     : new Date(2021, 1, 2)
        }
 ];

class EventModelWithPercent extends EventModel {
    static get fields() {
        return [
            { name : 'hours', type : 'number', defaultValue : 0 }
        ];
    }
}

const scheduler = new Scheduler({
    appendTo          : 'container',
    resourceImagePath : '../_shared/images/users/',

    features : {
        stripe : true,
        sort   : 'name'
    },

    columns : [
        {
            type  : 'resourceInfo',
            text  : 'Staff',
            width : '10em'
        }
    ],

    resources  : resources,
    eventStore : {
        modelClass : EventModelWithPercent,
        data       : events
    },

managedEventSizing: false,
barMargin: 1,
    startDate : new Date(2021, 1, 1),
    endDate   : new Date(2021, 1, 5),

    eventRenderer : ({ eventRecord, renderData }) => {
        const value = eventRecord.height;
        renderData.height = eventRecord.height;
        renderData.children.push({
            style: { width: 100 },
            className : 'value',
            html : value
        });
    }
});
  • I set up resource row height:
    id: 1, rowHeight: 200;
    id: 2, rowHeight: 250;
    and added data with heights;

The first resource has still higher row then the second one and neither one has right height based on the setup.

The height of Events looks good, but how to get rid of those spaces between this events?


Post by mats »

Can you please share a screenshot of your desired UI appearance / event layout?


Post by davidm »

Hi Mats,

sure - I'm sending here:

requested.png
requested.png (274.41 KiB) Viewed 1725 times

Events

  • event are aligned to the top of the cell, each below each other, without spaces;
  • event height is loaded from attribute height from EventModel
    Row height
  • row height is loaded from rowHeght attribute from ResourceModel

Post by Maxim Gorkovsky »

Hello.
It seems like you need your own layout mode. Please refer to this example: https://www.bryntum.com/examples/scheduler/layouts/

By default we use stack layout, which assumes all events have equal heights. This assumption allows it to go band by band (band is a row inside the resource row, where all events have same top position) and place events vertically depending on the current band index. Your situation is not that simple. All events can have varying heights, which means you cannot just calculate event top using band index. Instead you would need to figure different approach.
Another layout mode is pack, which shrinkgs events to fit them inside the resource row. Events end up filling vertical slots.
You have to solve another problem: how to position events with fixed height vertically. This is a pretty difficult problem to solve in a forum thread, I can only point you to the starting point.

Unfortunately, extending event layouts is not really supported yet. You can only override existing layout. And there is only one layout that manages row height - stack. So for starters you can declare an override:

Override.apply(class MyLayout {
    static get target() {
        return {
            class : HorizontalLayoutStack
        };
    }

    // Input: Array of event layout data
    // heightRun is used when pre-calculating row heights, taking a cheaper path
    layoutEventsInBands(events, heightRun = false) {
        let verticalPosition = 0;

        do {
            let eventIndex = 0,
                event      = events[0];

            while (event) {
                if (!heightRun) {
                    // Apply band height to the event cfg
                    event.top = this.bandIndexToPxConvertFn.call(
                        this.bandIndexToPxConvertThisObj || this,
                        verticalPosition,
                        event.eventRecord,
                        event.resourceRecord
                    );
                }

                // Remove it from the array and continue searching
                events.splice(eventIndex, 1);

                eventIndex = this.findClosestSuccessor(event, events);
                event = events[eventIndex];
            }

            verticalPosition++;
        } while (events.length > 0);

        // Done!
        return verticalPosition;
    }

    // TODO: optimize this for better performance with many events per resource
    findClosestSuccessor(eventRenderData, events) {
        const
            eventEnd    = eventRenderData.endMS,
            isMilestone = eventRenderData.eventRecord && eventRenderData.eventRecord.duration === 0;

        let minGap      = Infinity,
            closest,
            gap,
            event;

        for (let i = 0, l = events.length; i < l; i++) {
            event = events[i];
            gap = event.startMS - eventEnd;

            if (
                gap >= 0 && gap < minGap &&
                // Two milestones should not overlap
                (gap > 0 || event.endMS - event.startMS > 0 || !isMilestone)
            ) {
                closest = i;
                minGap  = gap;
            }
        }

        return closest;
    }
});

Main entry point is layoutEventsInBands, which returns amount of these bands. Resulting resource height equals rowHeight * nbrOfBands. findClosestSuccessor in this layout returns next event with non-overlapping dates. But your layout is 2-dimentional, so you would also have to check if it overlaps your vertical slot.
The rest is up to you, I'm afraid. If you have further questions implementing this layout, you can ask them here. We will do our best to give you an answer. Although this problem is new to us too, I don't recall us implementing such layout before.

If you find this task to be too complex, you can request our help to implement that for you. In that case please contact sales at bryntum.com for a quote.


Post by davidm »

Hello Maxim,

thank you very much. Is there any possibility to get more information / documentation on the layout? I can't import HorizontalLayoutStack in the project, as well I can't find any mention in the documentation about this class. It would help me a lot.

Without the import I can't event build the project.

Another question - how can I define number of band per resource?

You mentioned "Main entry point is layoutEventsInBands, which returns amount of these bands." - But in code it looks more it return the index on which I want to display this band.

I think that ideal is to set 24 bands (= we have steps 0,5 hour, in this case 2 bands cover 1 hour in the scheduling).

Last question - can you help me with getting the example you sent above working in the https://www.bryntum.com/examples/scheduler/layouts/ example? Just the code which will console.log methods and I can get it back to my project via copy & paste. I can't get this running via Override.apply.

Thank you very much


Post by Maxim Gorkovsky »

You cannot find any documentation because this is a private class. I completely forgot to warn you about that, I'm sorry. If you keep this path, be extra cautious when upgrading scheduler sources, because we might change this behavior without notice. I recommend to add proper test coverage.

Without the import I can't event build the project.

This class is not exported in the bundle, as far as I understand. But it is there in the library code. You can try using individual imports, instead of bundle. Or change the bundle code. Or generate your own bundle. There are plenty of options.

Last question - can you help me with getting the example you sent above working in the https://www.bryntum.com/examples/scheduler/layouts/ example? Just the code which will console.log methods and I can get it back to my project via copy & paste. I can't get this running via Override.apply.

You can try it on the demo in your local distribution. In the zip you will find examples/layouts/ folder with index.html in it, which imports source code directly. Import lib/Scheduler/eventlayout/HorizontalLayout.js there and try overriding it.


Post by Maxim Gorkovsky »

This feature is implemented in the upcoming scheduler pro 4.3.0 release. You can take control over event rendering manually positioning them inside the row with eventLayout config.

https://github.com/bryntum/support/issues/1854


Post by davidm »

Maxim, great to hear! Thank you very much!


Post Reply