Premium support for our pure JavaScript UI components


Post by rakshith.snps »

Hi Maxim and Alex,
Thank you by deep cloning the project data we were able to load the data in the scheduler on init.
But in our code there is a scenario where we use this.scheduler.destroy() to destroy the current instance and build a new one.
This destory method seems to fail to destory in the Latest versions but its working fine in the older version. we tried debugging but its something out of our context.

This where we encounter the issue.
1) when we load the screen / intialize it we gets the scheduler to load the data properly this is a OK situation.
2) when we select the number of records to be displayed.
We use a Batch process in Apex to do the processing again its the same data but with more records.
In this case we destory the current scheduler and recreate a new one . this is where the scheduler keeps loading.
On doing investigation we noticed that destroy() method doesnt get executed/ completed properly.

you can reproduce the issue in here
https://energy-computing-2694-dev-ed.scratch.my.salesforce.com
rakshith18@synapse-i.jp
23BT6MA022

DEMO

noofrecords.gif
noofrecords.gif (1.57 MiB) Viewed 497 times

Post by alex.l »

I reproduced the problem in your app. I need to check your source code to make sure you did all correct while re-creating the scheduler.
Could you please share it?

I tried to re-create the component with very basic code in our Salesforce examples:

        tbar : [
            {
                type : 'button',
                text : 'Destroy/Create',
                onClick : (event) => {
                    scheduler.destroy();
                    me.createScheduler();

            }
        },

And it works great.

All the best,
Alex


Post by rakshith.snps »

Hi Alex ,
Thank you for the reply.
This is the source code that we are using to recreate the scheduler.

 umScheduler.scheduler = window.schedulerpro =
      new bryntum.schedulerpro.SchedulerPro({
        project: {
          calendar: ArrayUtil.deepClone(umScheduler.targetCalendar),
          resourcesData: ArrayUtil.deepClone(umScheduler.resources),
          eventsData: ArrayUtil.deepClone(umScheduler.events),
          assignmentsData: ArrayUtil.deepClone(umScheduler.assignments),
          calendarsData: ArrayUtil.deepClone(umScheduler.calendars),
          dependenciesData: ArrayUtil.deepClone(dependenciesLagFixed),
          stm: {
            autoRecord: true,
          },
        },

    columns: umScheduler.columns,
    startDate: umScheduler.startDate,
    endDate: umScheduler.endDate,
    rowHeight: 50,
    barMargin: 2,
    viewPreset: umScheduler.viewPreset,
    timeResolution: {
      //Valid values are "millisecond", "second", "minute", "hour", "day", "week", "month", "quarter", "year".
      unit: 'minute',
      increment: 5,
    },
    multiEventSelect: true,
    /**
     * 日付ズーム時処理
     * @param {*} event
     * @returns
     */
    onPresetChange(event) {
      const startDate = event?.startDate;
      const endDate = event?.endDate;
      if (startDate !== undefined && endDate !== undefined) {
        const eventData = {
          startDate: startDate,
          endDate: endDate,
          preset: event?.preset.data.id,
        };
        const dateEvent = new CustomEvent('changepreset', {
          detail: ArrayUtil.toString(eventData),
        });
        umScheduler.dispatchEvent(dateEvent);
      }
    },

    features: {
      percentBar: umScheduler.shouldUsePercentBar,
      group: umScheduler.defaultGroup,
      dependencies: umScheduler.hasDependencies,
      dependencyEdit: umScheduler.hasDependencies,
      resourceNonWorkingTime: true,
      timeRanges: {
        showCurrentTimeLine: umScheduler.showCurrentTimeLine,
        showHeaderElements: umScheduler.showCurrentTimeLine,
      },
      // pdfExport: {
      //   exportServer: 'https://dev.bryntum.com:8082',
      // },
      // https://www.bryntum.com/examples/examples-scheduler/validation/
      eventDrag: {
        /**
         * イベントドラッグ中の移動制約
         * @param {*} param0
         * @returns
         */
        validatorFn({ eventRecords, newResource }) {
          try {
            // thisが指すものがこのコンポーネントのグローバル変数ではなくなっているため、変数は引数として受け取っている
            const resources = umScheduler.ganttInlineData.resourcesData;
            // 「移動元リソースの要素番号」と「移動先リソースの要素番号」の差分は、同時に動かしたイベントのリソース要素番号差分と一致する
            const firstOldResourceIndex = resources.findIndex(
              (x) => x.id === eventRecords[0].resource.id,
            );
            const firstNewResorceIndex = resources.findIndex(
              (x) => x.id === newResource.id,
            );
            const resourceIndexDiff =
              firstOldResourceIndex - firstNewResorceIndex;

            const totalResourceCount = resources?.length ?? 0;
            const isValid = eventRecords.every((event) => {
              const oldResourceIndex = resources.findIndex(
                (x) => x.id === event.resource.id,
              );
              // 移動したイベントが末尾リソースに対してであれば、末尾リソースより大きな要素番号へは移動しない
              if (
                oldResourceIndex - resourceIndexDiff >
                totalResourceCount - 1
              ) {
                return false;
              }

              const maxEventIndex = Math.min(
                oldResourceIndex - resourceIndexDiff,
                totalResourceCount - 1,
              );
              // 移動したイベントが最初リソースに対してであれば、最初リソースより若い要素番号へは移動しない
              if (maxEventIndex < 0) {
                return false;
              }

              const newResourceIndex = Math.max(maxEventIndex, 0);
              // JSON.parse出来ない時は不正なリソースとみなす
              let newResourceIdObject;
              let oldResourceIdObject;
              try {
                newResourceIdObject = ArrayUtil.fromString(
                  resources[newResourceIndex].id,
                );
                oldResourceIdObject = ArrayUtil.fromString(
                  resources[oldResourceIndex].id,
                );
              } catch {
                return false;
              }

              return umScheduler.editionProhibitedFields.every(
                (x) => newResourceIdObject[x] === oldResourceIdObject[x],
              );
            });

            return {
              valid: isValid,
              // TODO:移動できるときのツールチップへの表示内容
              message: isValid ? '' : moveEventErrorMessage,
            };
          } catch (error) {
            console.log(error);
            return {
              valid: false,
              message: moveEventErrorMessage,
            };
          }
        },
      },
      // https://www.bryntum.com/docs/scheduler-pro/#Grid/feature/HeaderMenu
      // https://www.bryntum.com/docs/scheduler-pro/#Scheduler/feature/TimeAxisHeaderMenu
      timeAxisHeaderMenu: {
        // 標準でeventsFilter・zoomLevel・dateRangeの3つのメニューが表示される
        // この3つのメニューに関しては標準の状態から一部カスタマイズする形
        items: {
          eventsFilter: {
            weight: 10,
          },
          zoomLevel: {
            weight: 20,
          },
          dateRange: {
            weight: 30,
            menu: {
              items: {
                startDateField: {
                  format: 'YYYY/MM/DD',
                  onChange: (event) => {
                    umScheduler._dispatchDateRangeEvent(
                      event.value,
                      umScheduler.endDate,
                    );
                  },
                },
                endDateField: {
                  format: 'YYYY/MM/DD',
                  onChange: (event) => {
                    umScheduler._dispatchDateRangeEvent(
                      umScheduler.startDate,
                      event.value,
                    );
                  },
                },
              },
            },
          },
        },
      },
      taskEdit: {
        // Customize its contents
        items: {
          generalTab: {
            items: {
              resultInputField: {
                type: 'button',
                cls: 'b-transparent',
                name: '',
                icon: 'b-fa-external-link-alt',
                iconAlign: 'end',
                text: resultInputLabel,
                onClick: () => {
                  umScheduler.openResultInput();
                },
                weight: 650,
              },
            },
          },
        },
      },
    },

    // onRenderEvent
    /**
     * イベントドロップ時処理
     */
    onEventDrop() {
      const dataEvent = new CustomEvent('changeevent', {
        detail: ArrayUtil.toString(umScheduler.ganttInlineData),
      });
      umScheduler.dispatchEvent(dataEvent);

      if (umScheduler.shouldUseResourceDiagram) {
        // when events are changed resource histogram should automatically calculate
        umScheduler._setUpResourceHistogram(
          umScheduler.ganttInlineData,
          false,
        );
      }

      if (this.hasPlanAndResult) {
        this._colorRowsByMainObject();
      }
    },
    /**
     * 確定ボタン押下時処理
     */
    onAfterEventSave({ eventRecord: task }) {
      const eventData = {
        inlineData: umScheduler.ganttInlineData,
        event: task,
      };
      const dataEvent = new CustomEvent('saveevent', {
        detail: ArrayUtil.toString(eventData),
      });
      umScheduler.dispatchEvent(dataEvent);

      const targetResoruce = ArrayUtil.isNotEmpty(task.resources)
        ? ArrayUtil.fromString(task.resources[0].id)
        : {};
      const addedItem = {
        startDate: task.startDate,
        endDate: task.endDate,
        resource: targetResoruce,
      };

      const globalEvent = new CustomEvent('umAddScheduleData', {
        detail: ArrayUtil.toString(addedItem),
      });
      window.dispatchEvent(globalEvent);

      if (umScheduler.shouldUseResourceDiagram) {
        // when events are changed resource histogram should automatically calculate
        umScheduler._setUpResourceHistogram(
          umScheduler.ganttInlineData,
          false,
        );
      }
    },
    /**
     * イベントの開始・終了の幅変更時処理
     */
    onEventResizeEnd() {
      const dataEvent = new CustomEvent('changeevent', {
        detail: ArrayUtil.toString(umScheduler.ganttInlineData),
      });
      umScheduler.dispatchEvent(dataEvent);

      if (umScheduler.shouldUseResourceDiagram) {
        // when events are changed resource histogram should automatically calculate
        umScheduler._setUpResourceHistogram(
          umScheduler.ganttInlineData,
          false,
        );
      }

      if (this.hasPlanAndResult) {
        this._colorRowsByMainObject();
      }
    },
    /**
     * イベントデータ変更時処理
     * @param {*} param0
     * @returns
     */
    eventRenderer({ eventRecord: task }) {
      return task.name;
    },

    tbar: [
      {
        type: 'button',
        name: 'start-date-picker',
        icon: 'b-fa-chart',
        iconAlign: 'end',
        text: 'Start-Date-Picker',
        menu: {
          type: 'datepicker',
          date: new Date(),
          onSelectionChange: ({ selection }) => {
            const dateEvent = new CustomEvent('changestartdate', {
              detail: ArrayUtil.toString({
                startDate: `${selection[0].getFullYear()}/${
                  selection[0].getMonth() + 1
                }/${selection[0].getDate()}`,
              }),
            });
            umScheduler.dispatchEvent(dateEvent);
          },
        },
        weight: 650,
      },
      {
        type: 'button',
        name: 'end-date-picker',
        icon: 'b-fa-chart',
        iconAlign: 'end',
        text: 'End-Date-Picker',
        menu: {
          type: 'datepicker',
          date: new Date(),
          onSelectionChange: ({ selection }) => {
            const dateEvent = new CustomEvent('changeenddate', {
              detail: ArrayUtil.toString({
                month: selection[0].getMonth() + 1,
                endDate: `${selection[0].getFullYear()}/${
                  selection[0].getMonth() + 1
                }/${selection[0].getDate()}`,
              }),
            });
            umScheduler.dispatchEvent(dateEvent);
          },
        },
        weight: 650,
      },
    ],
  });

Post by alex.l »

Thank you for sharing the code. I can't find a method where you destroy and create the SchedulerPro again.
I also don't see in what container you render it. No appendTo instruction, so how was it added and how was it destroyed? In what moment? Do you see this problem in our Salesforce example if you apply same approach of re-creating Scheduler as you used in your app?

All the best,
Alex


Post by rakshith.snps »

Hi Alex this where we destory the scheduler.

recreateSchedulerInternal(ganttData) {
    if (this.scheduler !== undefined && this.scheduler !== null) {
      this.scheduler.destroy(); // not working in the latest version
    }

if (
  this.resourceHistogram !== undefined &&
  this.resourceHistogram !== null
) {
  this.resourceHistogram.destroy();
}

if (this.splitter !== undefined && this.splitter !== null) {
  this.splitter.destroy();
}

// 行の色を付ける時にcolumn内にrendererが必要になるので、columnを直接取得
this.columnsInternal = ganttData.columns;
// スケジュールの開始日・終了日は描画開始時に必要なので、先にこちらから直接取得
this.startDateInternal = ganttData.startDate;
this.endDateInternal = ganttData.endDate;

this.createScheduler(this); // the above code which we sent.
  }

the recreate scheduler is called from here.

@api
  recreateScheduler(ganttData) {
    // eventsがない場合、schedulerを作成する内容が無いので、早期リターン
    if (this.events === undefined || this.events === null) {
      return;
    }

this.isLoading = true;
try {
  // eslint-disable-next-line @lwc/lwc/no-async-operation
  const wait = setInterval(() => {
    if (this.bryntumInitialized) {
      clearInterval(wait);
      const clonedGanttData = ArrayUtil.deepClone(ganttData);
      this.recreateSchedulerInternal(clonedGanttData);
    }
  }, 300);
} catch {
  this.isLoading = false;
}
  }

We render the schduler like this

this.container = this.template.querySelector('.container');
umScheduler.scheduler.render(this.container);

Post by alex.l »

Code looks ok to me. What about other questions from my last reply? Are you able to reproduce it in our app?

Btw, I tried your demo once again and I don't see that problem anymore, when I followed steps from your GIF.

All the best,
Alex


Post by rakshith.snps »

Hi Alex ,
thanks for the reply
Reagrding the error not appearing in the org - Sorry we were testing something with a old scheduler version in the environment which i gave you, so it used to work fine . But i have updated the environment with the latest source so the problem still exists.

we tried to produce the issue in Bryntum salesforce examples.
Here are the results
It seems like after the console.log in the click method . the control never reaches the createScheduler.

sandbox-destory.gif
sandbox-destory.gif (1.29 MiB) Viewed 308 times

Post by alex.l »

Will that work if you remove Histogram and will render only Scheduler?
Do you have any errors in console? Did you try to step into t.destroy() and see what exactly is wrong there?
Does it possible to apply the code you used here to our Salesforce example and share with us, so we will have not minimized code for debugging on our side, it will help a lot to go faster.

All the best,
Alex


Post by rakshith.snps »

Hi Alex , Sorry for the delay in answer.
We have turned the debug mode in the Bryntum example where the destroy method doesnt work . it would be easier to debug now.
We did try to step in the destroy method but the control seems to go into aura.prod.js files and we are not aware what exactly goes wrong .

https://energy-computing-2694-dev-ed.scratch.my.salesforce.com
rakshith18@synapse-i.jp
23BT6MA022


Post by Maxim Gorkovsky »

Hello.
I've debugged this problem (you need to enable pause on caught exceptions) and I saw some old bug which was fixed in 5.2.8 https://github.com/bryntum/support/issues/5960
Upgrading to the latest release should help.


Post Reply