Our pure JavaScript Scheduler component


Post by david-molnar-oculai »

Hi,

I'm using Bryntum Scheduler Pro with Vue3 (thin packages, v6.2).

I have normal events in the eventStore. These are created by the user. Change tracking and STM transactions should work normally for these.

But we also have events which need to be created programatically on some special actions. These we normally insert using eventStore.addAsync and remove using eventStore.remove. The problem is that these also show up in the eventStore.changes and create stm transactions when added/removed.

Disabling STM is already a nighmare. Setting stm.autoRecord=false and autoRecord=true afterwards plus stopping the running transaction works (stm.stopTransaction) in some cases, but not in all (e.g. it's problematic when trying to disable transaction in the dataChange event handler. Imagine the user removed and event - this we want to track, but also we want to remove events programatically - this we don't want to track).

What is the proper way to handle this?

  • don't add programatically added events to changes, simply disable tracking for them
  • don't record changes made to these programatically added events never ever, but leave normal event tracking intact. Also make it possible to "react" to normal event changes in the dataChange event handler without messing up stm transactions.

Thank you.


Post by marcio »

Hey David,

Thanks for reaching out.

To handle programmatically added events without affecting change tracking and STM transactions, you can use the ignoreRemoteChangesInSTM property of the StoreStm mixin. Set this property to false for the event store when adding or removing events programmatically. This will prevent these changes from being recorded in STM transactions.

Here's a code snippet to illustrate:

// Temporarily disable STM tracking for programmatic changes
eventStore.ignoreRemoteChangesInSTM = false;

// Add events programmatically
await eventStore.addAsync({ /* event data */ });

// Remove events programmatically
eventStore.remove(eventId);

// Re-enable STM tracking for user changes
eventStore.ignoreRemoteChangesInSTM = true;

This approach allows you to manage STM tracking specifically for programmatically added events while keeping normal event tracking intact. You can still react to normal event changes in the dataChange event handler without affecting STM transactions.

If that approach doesn't solve your issue, please let us know.

Best regards,
Márcio

How to ask for help? Please read our Support Policy


Post by david-molnar-oculai »

Thank you for your answer.

Maybe I'm doing something wrong, but for me this doesn't work at all. STM transactions are still being created (I can use undo/redo), dataChange event still comes for these newly added/removed items and eventStore.changes also has them. I'm a bit unsure if first you want to set ignoreRemoteChangesInSTM to false and then to true (either way doesn't work).

eventStore.ignoreRemoteChangesInSTM = false;
await eventStore.addAsync(...);
eventStore.ignoreRemoteChangesInSTM = true;

-> transaction is recorded in STM (I can simply undo - the events then are removed)
-> dataChange event comes with these newly added events
-> eventStore.changes is not null (contains added items)


Post by nickolay »

For stm to ignore an addition or removal of the record it needs to be disabled: stm.disable() (and enabled after). You mentioned it does not work for you, can you please provide an example?

To ignore changes of the record fields, one can override a private method of the Model class:

shouldRecordFieldChange(fieldName, oldValue, newValue) {
    if (this.IS_AUTO_CREATED) {
        return false
    }
    return super.shouldRecordFieldChange(fieldName, oldValue, newValue)
}

Post by david-molnar-oculai »

Thank you for your answer.

stm.disable() is not a good solution. Imagine the following scenario. On button click we trigger updating start/end of an event. This is normally tracked as a change and works. Together with this change we want to update also the parent of the event too. We apply that change. This is still propertly recorded in STM.

When the above mentioned change happens, we want to REMOVE events programatically from the scheduler WITHOUT STM transactions and WITHOUT any changes.

We use the dataChange handler for this purpose. We call stm.disable() and eventStore.remove(). Since the dataChange event handler is triggered in the middle of the above mentioned changes, it disables the stm in the middle of it and some of the changes are not recorded correctly. It's a mess.

One solution for now is to "postpone" the programatic removal by 201ms, so the auto recorded transactions are considered to be finished. This creates a little visual delay, which is unwanted.

Why is there no proper way to disable STM for changes which should not be recorded? The recommended way doesn't work well with event handlers and async/await functions (as those can "come" in the middle).

shouldRecordFieldChange doesn't help either. I want to be able to ADD an event to the eventStore, without eventStore.changes to have it. The shouldRecordFieldChange disables tracking for fields only, but not the full adding.

It's very disappointing that's it's so hard to implement this. We already wasted multiple days trying to make this work.


Post by david-molnar-oculai »

The following would be logical to me:

eventStore.addAsync(events, {disableChangeTracking: true});
eventStore.remove(events, {disableChangeTracking: true});

This then would add/remove the events from the initial data, cause no eventStore.changes and no recorded STM transactions. The dataChange handler could be still called with a special property set, so you know that these events were triggered without tracking in mind (event.trackingDisabled = true).


Post by nickolay »

The store.changes property is not related to stm - changes/additions/removals will always appear in that property. You can override the getter for that property on the store class to manually exclude unwanted records from it.

class MyEventStore extends EventStore {
    get changes () {
        const value = super.changes;
        // post-process the `value`
        return value
    }
}

After that, you will need to specify the newly created event store class as a project config:

const project = new ProjectModel({
    eventStoreClass : MyEventStore
})

Regarding shouldRecordFieldChange - correct, it is purposed only for field changes, not for additions/removals. Unfortunately there's no similar hook for those, but you can override certain internal methods for that:

class MyStm extends StateTrackingManager {
    onModelInsertChild (parentModel, index, childModels, context, orderedBeforeNode) {
        // filter `childModels` and call `super` with it, probably will require the override for `store.changes` to be in place too
    }

    onModelRemoveChild (parentModel, childModels, context) {
        // similar to above
    }
}

After that you will need to manually instantiate stm instance and provide it as a project config:

const stm = new MyStm({
    // regular stm configs here
});

const project = new ProjectModel({
    stm 
})

Please let me know if that helped.


Post by david-molnar-oculai »

Thank you.

I'm getting Error: Unknown identifier ModelClass-1006.$.assignmentsByCalendar errors. Any idea what this could be?

Uncaught (in promise) Error: Unknown identifier ModelClass-1006.$.assignmentsByCalendar
    at throwUnknownIdentifier (Identifier.js:221:9)
    at EngineTransaction.addEdge (Transaction.js:508:79)
    at EngineTransaction.read (Transaction.js:229:20)
    at Proxy.effectiveSchedulingModeSync (HasSchedulingModeMixin.js:44:37)
    at Proxy.dispatcherClass (HasSchedulingModeMixin.js:61:35)
    at Proxy.prepareDispatcher (BaseEventMixin.js:81:36)
    at prepareDispatcher.next (<anonymous>)
    at Proxy.prepareDispatcher (SplitEventMixin.js:163:39)
    at prepareDispatcher.next (<anonymous>)
    at Proxy.prepareDispatcher (HasSchedulingModeMixin.js:57:52)

I'm creating the project like this and passing it through v-bind.

const project = new ProjectModel({
    stm: new StateTrackingManager({ autoRecord: false }),
  });

Post by nickolay »

Hard to say, please try to reproduce with the latest code, also need the reproducible test case.


Post by david-molnar-oculai »

I managed to run the custom state manager like this:

class PlannerStateTrackingManager extends StateTrackingManager {
  onModelInsertChild(...args) {
    return super.onModelInsertChild(...args);
  }

  onModelRemoveChild(...args) {
    return super.onModelRemoveChild(...args);
  }
}
...
project: {
      onDataReady(this: SchedulerPro, event) {
        if (event.isInitialCommit) {
          const config = {
            autoRecord: true,
          };
          this.project.stm = new PlannerStateTrackingManager(config);
          this.project.stm.addStore(this.resourceStore);
          this.project.stm.addStore(this.assignmentStore);
          this.project.stm.addStore(this.dependencyStore);
          this.project.stm.addStore(this.eventStore);
          this.project.stm.enable();
        }
      },
    }

This works as normal. My overriden functions onModelInsertChild and onModelRemoveChild are not called though at all.

What to do next?


Post Reply