Our pure JavaScript Scheduler component


Post by sactory »

Hello Bryntum team,

I have an issue with Bryntum Scheduler filtering feature.

In horizontal mode, I have configuration to use filtering bar, and assign a listener to detect when user change the input.

At the first time scheduler is loaded, everything work normally, but when I navigate away from page, then come back, the listener stop working, even though filtering still work.

Bellow is my configuration part that involve filtering

    <scheduler
      ref="scheduler"
      :resources="schedulerConfig.resources"
      :events="schedulerConfig.events"
      :start-date="schedulerConfig.startDate"
      :end-date="schedulerConfig.endDate"
      :columns="schedulerConfig.columns"
      :view-preset="schedulerConfig.viewPreset"
      :min-height="schedulerConfig.minHeight"
      :filterBarFeature="schedulerConfig.filterBarHorizontal"
    />
  filterBarHorizontal: {
    filter: [{ property: 'name', value: '' }]
  },
  columns: [
    {
      text: 'Name',
      field: 'name',
      width: 130,
      filterable: {
        filterField: {
          listeners: {
            change({ value }) {
              console.log('filter changed', value);
            }
          },
          filterFn: ({ record, value }) => {
            const filterValue = value.trim();
            return record.name
              .toLowerCase()
              .includes(filterValue.toLowerCase());
          }
        }
      }
    }
  ],

I attached sample project and video to reproduce this issue. Sample project use standard vue-cli starter template.

  • Bryntum version: 3.1.9
  • Browser: Google Chrome 85.0.4183.83 (64 bit)
  • OS: MacOS Catalina (10.15.6)

Please help me with this.
Thank you

Attachments
Source.zip
(11.13 MiB) Downloaded 106 times
Screen Recording.zip
(5.25 MiB) Downloaded 110 times

Post by saki »

What is actually happening is the following:

  1. user navigates to Scheduler page

  2. Scheduler is created; both HTML markup and JavaScript code and variables

  3. user navigates away from page

  4. Vue router unconditionally destroys HTML markup of Scheduler

  5. user navigates back to Scheduler page

  6. new instance of Scheduler is created; HTML markup anew, some parts of JavaScript code and variables shared with step 2, some new.

The result is unpredictable and some parts of the code/markup could work and some not. Therefore, we cannot let any other code to destroy Scheduler's markup.

There are two possible ways of handling it:

  1. You do not let router to destroy the markup:

    <template>
        <div id="app">
            <demo-header title="Advanced Vue demo" />
            <keep-alive>
                <router-view/>
            </keep-alive>
      </div>
    </template>

    this is the way we use in our advanced example

  2. You intercept the routing event and you call destroy() method of the Scheduler instance before navigating away from it. This way both markup and JavaScript will be cleaned up correctly and navigating back to the scheduler page will cleanly re-create it from scratch.


Post by sactory »

Thank you for your response saki.

About the two solutions you suggested:

  1. With how we structure our project <keep-alive> is not a viable option, it mess up some of our business logic so we can not use it.

  2. I tried to use beforeRouteLeave guard in route component to destroy scheduler instance, but an error show up. You can see it in the sample.

My component tree: App (root) > About (route component) > Schedule > BryntumScheduler

I have a ref to BryntumScheduler in Scheduler component, so I use event to make a referrer to the ref in About component, then use beforeRouteLeave at About component and destroy BryntumScheduler instance.

Sample code:

// About.vue
<template>
  <div class="about">
    <div class="schedule">
      <Schedule @saveReferrer="saveReferrer" />
    </div>
  </div>
</template>
<script>
import Schedule from '@/components/Schedule';
export default {
  name: 'About',
  components: {
    Schedule
  },
  data: function() {
    return {
      scheduler: undefined
    };
  },
  methods: {
    saveReferrer(scheduler) {
      this.scheduler = scheduler;
    }
  },
  beforeRouteLeave(to, from, next) {
    if (this.scheduler) {
      this.scheduler.schedulerInstance.destroy();
    }
    next();
  }
};
</script>
// Schedule.vue
<template>
  <div>
    <scheduler
      ref="scheduler"
      :resources="schedulerConfig.resources"
      :events="schedulerConfig.events"
      :start-date="schedulerConfig.startDate"
      :end-date="schedulerConfig.endDate"
      :columns="schedulerConfig.columns"
      :view-preset="schedulerConfig.viewPreset"
      :min-height="schedulerConfig.minHeight"
      :filterBarFeature="schedulerConfig.filterBarHorizontal"
    />
  </div>
</template>
<script>
import Scheduler from '../../bryntum/_shared/src/Scheduler.vue';
import 'bryntum-scheduler/scheduler.stockholm.css';
import schedulerConfig from './schedulerConfig';

export default {
  name: 'Schedule',
  components: {
    Scheduler
  },
  data: function() {
    return {
      schedulerConfig
    };
  },
  mounted: function() {
    this.$emit('saveReferrer', this.$refs.scheduler);
  }
};
</script>

When I navigate away it show this error message in console

vue-router.esm.js?8c4f:2208 TypeError: Cannot read property 'map' of undefined
    at ColumnStore.map (scheduler.module.js?7d42:34554)
    at Scheduler.getState (scheduler.module.js?7d42:70962)
    at Scheduler.getState (scheduler.module.js?7d42:108309)
    at Scheduler.get state [as state] (scheduler.module.js?7d42:25885)
    at Object.replacer (backend.js:315)
    at encode (backend.js:3100)
    at encode (backend.js:3101)
    at encode (backend.js:3101)
    at encode (backend.js:3101)
    at encode (backend.js:3110)

What am I doing wrong here?

Attachments
Source.v2.zip
(11.13 MiB) Downloaded 92 times

Post by saki »

The wrapper already contains the logic to call destroy() of the instance so the following will work:

  beforeRouteLeave(to, from, next) {
    if (this.scheduler) {
      this.scheduler.destroy();
    }
    next();
  }

I've tested it and then destroy and re-create works for me. However, one more workaround is needed:

      filterable: {
        filterField: {
          onChange:({value}) => {
            console.log(`changed ${value}`);
          },

We're still investigating why it doesn't work with listeners because it should.


Post by pmiklashevich »

The issue with listeners is fixed in upcoming 4.0 release. You can download 4.0 beta from the Customer Zone. Thanks!

Pavlo Miklashevych
Sr. Frontend Developer


Post by sactory »

Thank you for your support.

After some trying I come up with a work around for this issue, I think the root cause can be the way configuration being initialize: configuration being exported from separate file as object then imported in Scheduler component, when we navigate away then come back it was re-used.
I changed schedulerConfig.js to export init function that return the object instead of export the object directly, then call init function in Scheduler component, this way when Scheduler is created it always use fresh configuration.
This solution solve my issue with out the need of manually call destroy method of BryntumScheduler.


Post by arcady »

Ok, good to hear you're unblocked and thanks very much for sharing the solution!


Post Reply