Premium support for our pure JavaScript UI components


Post by paul.manole »

Hello,

We have recently been tackling performance considerations with Scheduler Pro.

First of all, do you perhaps have a wiki or some other kind of resource about some common operations, and some common performance considerations/concerns and perhaps some common patterns for writing performant code when working with Scheduler? Things maybe users of Scheduler have asked about in the past? I know you offer demos and those are great, thank you for creating them, but sometimes they are not enough and having something like a blog series on patterns and some ideas would be great (I am thinking of something along the lines of this https://tkdodo.eu/blog/practical-react-query from the co-maintainer of the popular React/TanStack Query library).

For example:

  • how to load and reload data manually into stores (say on manual refresh or after window regains focus after a while, etc.)

  • how to perform UI updates in a performant way when using common REST-type APIs (i.e. update a resource on the server, let's say a Scheduler Event, get back the new resource representation (new DTO), then how to patch/update the UI view model and render the new data without doing manual field updates and duplicating server-side logic on the frontend, like... do you delete the old event and add a new one using the new model config, or is there a way to patch the existing one on the fly and reconcile changes, what's recommended and when, etc.)

  • how to implement infinite scrolling with custom APIs that aren't built to support CrudManager, etc.

  • how to implement or enable virtualization for large data sets

  • how clamp infinite scrolling to fixed size time blocks when scrolling in order to make use of caches that use unique query keys, where each block has to not overlap another on the time axis, etc.

I am assuming there have been a lot of interesting talks and discussions like that in the past with some of your customers, so maybe you've gathered some insights and usage patterns that you could share to help others develop with your products faster when its the first time they're using them?


And the second questions is regarding another performance consideration. Should UI updates that spawn off as side effects of some business logic be scheduled in Promises for best performance? Does that help?

Here is an example of a feature hook for React that I wrote that adds time span highlights based on selection:

 /**
 * Behavioral hook that applies different highlights to both timeline Schedulers
 * based on the selected duties.
 */
export const useTimelineTimeSpanHighlightFeature = () => {
  const [slocSched, emplSched] = useTimelineSchedulersStore((state) => [
    state.slocScheduler,
    state.emplScheduler,
  ]);
  const { view } = useTimelineOptions();

  const onSlocSelectionChange: OnEventSelectionChangeEventHandler = useCallback(
    ({ selection: slocSelection }) => {
      if (!slocSched || slocSched.isDestroying) return true;
      if (!emplSched || emplSched.isDestroying) return true;

      const options = { clearExisting: true };

      const isSingleSelection = slocSelection.length === 1;
      if (!isSingleSelection) {
        new Promise<void>((resolve) => {
          slocSched.highlightTimeSpans([], options);
          emplSched.highlightTimeSpans([], options);
          resolve();
        });
        return true;
      }

      const verticalHighlights = generateVerticalHighlightConfigs(
        slocSelection,
        view
      );
      const slocMtvHighlights = generateMTVHighlightConfigs(slocSelection);
      const emplMtvHighlights = generateMTVHighlightConfigs(
        emplSched.selectedEvents
      );

      // is it better to wrap these "UI commands" in a Promise to run them when the stack is free
      // because they are not needed for the business logic to continue to do its job,
      // or is the out of order execution possibly more problematic than it's worth to do this "optimisation"?
      new Promise<void>((resolve) => {
        slocSched.highlightTimeSpans(
          verticalHighlights.concat(slocMtvHighlights),
          options
        );
        emplSched.highlightTimeSpans(
          verticalHighlights.concat(emplMtvHighlights),
          options
        );
        resolve();
      });
      return true;
    },
    [emplSched, slocSched, view]
  );

  useEffect(() => {
    slocSched?.on("eventSelectionChange", onSlocSelectionChange);
    return () => {
      if (!slocSched?.isDestroying) {
        slocSched?.un("eventSelectionChange", onSlocSelectionChange);
      }
    };
  }, [slocSched, onSlocSelectionChange]);
};

/**
 * Generates the vertical time span highlight configurations for the given
 * selected duties and view.
 *
 * These configs do not clear existing highlights, so if that is required, it
 * must be handled by the caller.
 * @param duties The duties to generate highlight configurations for.
 * @param view The view to generate highlight configurations for.
 */
function generateVerticalHighlightConfigs(
  duties: ServiceDutyEventModel[],
  view: ServiceDutyView
): HighlightTimeSpan[] {
  return duties.map((duty) => {
    const startDate =
      view === ServiceDutyView.Planned
        ? duty.plannedStartDateTime
        : duty.actualStartDateTime;
    const endDate =
      view === ServiceDutyView.Planned
        ? duty.plannedEndDateTime
        : duty.actualEndDateTime;
    return {
      cls: HIGHLIGHT_TIMESPAN_CLS,
      startDate,
      endDate,
      clearExisting: false,
      isHighlightConfig: true,
    };
  });
}

/**
 * Generates the max time-variance (MTV) highlight configurations (blue
 * horizontal boxes) for the given selected duties.
 *
 * These configs do not clear existing highlights, so if that is required, it
 * must be handled by the caller.
 *
 * **WARNING**: These configs hold a reference to the duty resource record,
 * which is tied to a particular Scheduler instance, so do not use these configs
 * for a different Scheduler than the one the passed in duties belong to, or
 * else Scheduler will crash!
 * @param duties The duties to generate highlight configurations for.
 */
function generateMTVHighlightConfigs(
  duties: ServiceDutyEventModel[]
): HighlightTimeSpan[] {
  return duties.reduce<HighlightTimeSpan[]>((configs, duty) => {
    if (duty.maxTimeVarianceAfterDate && duty.maxTimeVarianceBeforeDate) {
      const mtvHighlight: HighlightTimeSpan = {
        cls: HIGHLIGHT_MTV_CLS,
        startDate: duty.maxTimeVarianceBeforeDate,
        endDate: duty.maxTimeVarianceAfterDate,
        // Warning: this resource reference is tied to the Scheduler instance!
        resourceRecord: duty.resource,
        clearExisting: false,
        isHighlightConfig: true,
      };
      configs.push(mtvHighlight);
    }
    return configs;
  }, []);
}

Post by alex.l »

Hi paul.manole,

That is a great idea, I've opened a ticket to add such article https://github.com/bryntum/support/issues/8841

I will try to answer all your questions one by one below.

how to load and reload data manually into stores (say on manual refresh or after window regains focus after a while, etc.)

There is a https://bryntum.com/products/schedulerpro/docs/api/Core/data/AjaxStore#function-load method for that.

how to perform UI updates in a performant way when using common REST-type APIs (i.e. update a resource on the server, let's say a Scheduler Event, get back the new resource representation (new DTO), then how to patch/update the UI view model and render the new data without doing manual field updates and duplicating server-side logic on the frontend, like... do you delete the old event and add a new one using the new model config, or is there a way to patch the existing one on the fly and reconcile changes, what's recommended and when, etc.)

Actually it should work out of the box. What fields did you mean? Are we talking about Grid/SchedulerPro or your custom forms?

how to implement infinite scrolling with custom APIs that aren't built to support CrudManager, etc.

Did you see our demo? It doesn't use CrudManager https://bryntum.com/products/scheduler/examples/infinite-scroll/

how to implement or enable virtualization for large data sets

This works out of the box. We do not render elements out of visible are + buffer.

how clamp infinite scrolling to fixed size time blocks when scrolling in order to make use of caches that use unique query keys, where each block has to not overlap another on the time axis, etc.

Did you see these https://bryntum.com/products/schedulerpro/docs/api/Scheduler/view/mixin/TimelineScroll#config-bufferCoef
https://bryntum.com/products/schedulerpro/docs/api/Scheduler/view/mixin/TimelineScroll#config-bufferThreshold
https://bryntum.com/products/schedulerpro/docs/api/Scheduler/data/mixin/GetEventsMixin#event-loadDateRange

And the second questions is regarding another performance consideration. Should UI updates that spawn off as side effects of some business logic be scheduled in Promises for best performance? Does that help?

Do not see how Promises might help, but useEffect and useState - are mandatory things, because React will re-create variables and unnecessary refresh not required props and handlers.

All the best,
Alex


Post Reply