Our pure JavaScript Scheduler component


Post by jasony »

Hi,

We upgraded from Bryntum 5.1.0 to 5.2.0 and since the upgrade (we are now on the latest 5.2.3) we are having an issue with the display of our TimeRanges.

We load the time ranges from our server and set it inside of our componentDidMount as follows:

this.schedulerInstance.features.timeRanges.store.data = shiftInstances;

The Scheduler used to display them on page load, but now it only displays a few of them until the user interacts with the page or waits for around 10 seconds, then the TimeRanges suddenly show up.

We managed to get them showing on load by adding this hack right after setting them in the store

this.schedulerInstance.onSchedulerHorizontalScroll(this.schedulerInstance);

Is there perhaps some update call we are missing?


Post by tasnim »

Hello,
If you use this solution viewtopic.php?p=113597#p113597 does that work for you?


Post by alex.l »

Could you please share the code of your component and timeRanges data example.
We actually use useEffect for these things, but it should be ok for both.
Please also try this way to set data:

this.schedulerInstance.timeRanges = shiftInstances;

All the best,
Alex


Post by jasony »

tasnim wrote: Fri Nov 25, 2022 7:34 am

Hello,
If you use this solution viewtopic.php?p=113597#p113597 does that work for you?

Unfortunately I do not think we can use this, our data is coming from a GraphQL endpoint. I am not sure an Ajax store will work with our use case


Post by jasony »

alex.l wrote: Fri Nov 25, 2022 8:00 am

Could you please share the code of your component and timeRanges data example.
We actually use useEffect for these things, but it should be ok for both.
Please also try this way to set data:

this.schedulerInstance.timeRanges = shiftInstances;

Tried your suggestion and it made no difference, here is a sample of our component code

export default class BryntumScheduler extends Component {
    componentDidMount() {
        const {
            ...
            shiftInstances,
            unavailabilities,
        } = this.props;

        this.schedulerInstance = new Scheduler(config);

    ...

        this.schedulerInstance.timeRanges = shiftInstances;
        this.schedulerInstance.project.resourceTimeRangeStore.data = unavailabilities;

        this.resetScheduleTimeRange({centerDate: new Date(), zoomLevel, startDate, endDate});

    ...
        // Refresh the scheduler so that shifts immediately show up when reloading the page
        //this.schedulerInstance.onSchedulerHorizontalScroll(this.schedulerInstance);
    }

    ...
    
    resetScheduleTimeRange({centerDate, zoomLevel, startDate, endDate}) {
        this.schedulerInstance.zoomTo({
            ...zoomLevelMapping(zoomLevel),
            startDate,
            centerDate: centerDate || this.schedulerInstance.viewportCenterDate,
            endDate,
        });
    }
}

We just render it in our container

<BryntumScheduler
      config={config}
     ...
      shiftInstances={shiftInstanceTimeRanges}
      unavailabilities={unavailabilityTimeRanges}
     ...
    />

Time ranges are in this format (converted after the call comes back from the backend):

{
      id: uniqueId,
      name: shiftHeader,
      startDate: shiftInstance.scheduledStart,
      endDate: shiftInstance.scheduledEnd,
    }

Could you explain more how to use the useEffect way?


Post by alex.l »

In your code I see you used wrapper and at the same time you manually created another instance of Scheduler without wrapper

    componentDidMount() {
        const {
            ...
            shiftInstances,
            unavailabilities,
        } = this.props;

    this.schedulerInstance = new Scheduler(config);

No need to do that, it will be automatically created, you just need to use that (get a link for the instance). Please have a look in our React examples. Something like that


componentDidMount() {
    this.scheduler = this.schedulerRef.current.instance;
}

Or using useEffect

const App = () => {
    const schedulerRef = useRef();

// ...
useEffect(() => {
    schedulerRef.current.instance.timeRanges = data;
    // ....

}, []);

We do not recommend to use Scheduler without wrapper for React applications.

All the best,
Alex


Post by jasony »

I think without the full code there was a misunderstanding

 BryntumScheduler 

is actually our own component and not imported from Bryntum. We dont current use any of the wrappers.

We will look into whether our code can be adapted to use the provided React wrappers and get back to this thread if we are unable to use the wrappers or have questions.

Thank you


Post by jasony »

Hi,

We updated our code to use the react wrapper (as well as the react scheduler npm package). We got it to render great on initial load (even with removing the hack we originally logged this ticket for). However, everytime we do anything with an event, all the events and the date/time headers disappear. The scheduler then goes into a state where we cannot do anything except scroll. If we try to schedule by dragging for example, we get errors like this one:

Uncaught TypeError: Cannot read properties of undefined (reading 'onCellClick')
    at Scheduler.triggerCellMouseEvent (GridElementEvents.js:310:1)
    at Scheduler.onElementClick (GridElementEvents.js:403:1)
    at Scheduler.onElementClick (TimelineDomEvents.js:319:1)
    at functionChainRunner (InstancePlugin.js:44:1)
    at plugInto.<computed> [as onElementClick] (InstancePlugin.js:332:1)
    at Scheduler.onHandleElementClick (GridElementEvents.js:389:1)
    at Scheduler.handleEvent (GridElementEvents.js:228:1)
    at HTMLDivElement.handler (EventHelper.js:470:1)

When things change we update the event store directly (after a rerender) but we also tried not doing that and just sending the time ranges and events into the BryntumScheduler.

We are now getting the scheduler instance by using the ref as suggested. Sometimes the events reappear when scrolling but not always.

Some code samples:

<BryntumScheduler
          ref={this.schedulerRef}
          {...this.props.config}
          events={this.props.events}
          timeRanges={this.props.shiftInstances}
        />
updateEvents(updatedEvents) {
    const eventStore = this.schedulerInstance.eventStore;

this.handleEventsDeleted(updatedEvents);

updatedEvents.forEach((event) => {
  const existingEvent = eventStore.getById(event.id);
  if (!existingEvent) {
    this.handleEventAdded(event);
  } else {
    this.handleEventUpdated(existingEvent, event);
  }
});

eventStore.commit();
  }

  handleEventsDeleted(newEvents) {
    const currentEvents = this.schedulerInstance.events;
    const newEventIds = newEvents.map((event) => event.id);

const eventsToRemove = currentEvents.filter((event) => !newEventIds.includes(event.id));

eventsToRemove.forEach((event) => {
  this.schedulerInstance.eventStore.remove(event);
});
  }

  handleEventUpdated(oldEvent, newEvent) {
    oldEvent.set(newEvent);
    oldEvent.cls = newEvent.cls;
  }

  handleEventAdded(newEvent) {
    const eventStore = this.schedulerInstance.eventStore;
    eventStore.add(newEvent);
  }


Post by mats »

Any chance you can get us a full runnable test case?


Post by jasony »

Im not sure if that will be possible, we have a lot of customization in our application, right now we are just trying to get to where we are using it correctly as well as getting rid of checking in the compiled code (umd module i believe). We can attempt to create an example, but we work with data from a graphql backend so setting up an example might take a bit long.

Alternatively is there any other piece of code that i can send to help?


Post Reply