Transaction is null in model ChangedEvent

Hi,

I am programatically updating an array in my model:

let model = part.diagram.model;
let existingPhotoNames: Array<string> = part.data["photoNames"];
model.skipsUndoManager = true;
model.startTransaction("Add photo");
if (Array.isArray(existingPhotoNames)) {
     model.setDataProperty(part.data, "photoNames", [...existingPhotoNames, fileName]);
} else {
    model.setDataProperty(part.data, "photoNames", [fileName]);
}
model.commitTransaction("Add photo");
model.skipsUndoManager = false;

However, in the model changedEvent, the transaction object (event.object) is always null for the commitedTransaction event.

Can you give me a hint what I am doing wrong?

Best regards,
Dominic

The ChangedEvent.object for a property change is supposed to be the data object whose property was changed. It’s not supposed to be a Transaction.

For a Transaction ChangedEvent that property may be a Transaction.

Hm…
I am trying to save the model incrementally using this example So I have no possibility to iterate over the concrete property changes in the transaction?

If I want to save the model incrementally even for property changes, what should I do?

Usually one does incremental saves whenever a transaction has finished – in other words when ChangedEvent.isTransactionFinished is true. That includes undo and redo operations.
See Update Demo GoJS Sample.
See also State Chart With Incremental Saves.

Hi,

in the first example you iterate over all changes if the transaction is finished:

if (tx !== null) {
        var allChanges = tx.changes;
        var odd = true;
        allChanges.each(function(change) {
            var childData = {
              color: (odd ? "white" : "#E0FFED"),
              text: change.toString(),
              parent: parentKey
            };
            undoModel.addNodeData(childData)
            odd = !odd;
          });
        undoDisplay.commandHandler.collapseTree(parentNode);
      }

This is what I want to do. However in my case (see above), e.object is null for the CommittedTransaction event. The documentation only says the transaction might be null if no changes have occurred, however this is not the case as the photoNames property has changed.
So basically the questions are:

  • Is it normal that the transaction (e.object) is null in my case or am I doing something wrong?
  • If it is normal, what should I do if I want to do something when the photoNames property changes?

That’s odd. Could you tell us how to reproduce that situation? I just added this DiagramEvent listener:

          "ModelChanged": function(e) {
            if (e.isTransactionFinished && !e.object) alert(e);
          },

and I did a bunch of editing operations and undo’s and redo’s. I never got an alert.

Until I tried:

myDiagram.model.commit(m => m, "NOTHING")

at which point I immediately got an alert, as expected when there were no ChangedEvents during the transaction.

But I also question why you want to make changes to the model whenever a transaction has finished. That would be really bad for undo and redo. And it could be bad even during normal transactions.

I highly recommend that not make any changes to the model or to any diagram showing that model during a “Transaction” type ChangedEvent.

Hm I must be doing something wrong…
Here is an example: jQuery addClass example - JSFiddle - Code Playground

Sorry, maybe my previous post was misleading. The code snippet was from your example. I wanted to iterate over all changes when the transaction is finished (just as in the code snippet).
I didn’t want to make changes to the model when a transaction has finished. Currently I (want to) do two things:

  • Save the model to a database after every change
  • Delete a physical file when a node is deleted or when a certain property of a node has been changed. Basically the node has an array with file names. When the array has changed, I want to delete the files physically from the disk. This is a bit tricky if undo is used but I haven’t found a better way…

Ah, OK, I misunderstood you. Performing side-effects on other Diagrams, Models, the HTML DOM, or the rest of the universe is quite OK upon a Transaction ChangedEvent.

Anyway, back to the principal issue: the problem is that you have set Diagram.skipsUndoManager to true when you perform the transaction. That causes no ChangedEvents to be recorded in the UndoManager. Hence no Transaction upon the commitTransaction.

But even without setting skipsUndoManager the transaction is null?

Ah, that’s more interesting.

So I’ll put your code in a function:

    function test() {
      var model = myDiagram.model;
      //model.skipsUndoManager = true;
      var nodeData = model.nodeDataArray[0];
      model.startTransaction("Add photo");
      model.setDataProperty(nodeData, "test", ["ff"]);
      model.commitTransaction("Add photo");
      //model.skipsUndoManager = false;
    }

If you call test() interactively, you will not get an alert. For example, if you call it by:

<button onclick="test()">Test</button>

But in your case you are in the midst of loading a model. You may have noticed that the one time when you can make changes to a model or a diagram without using transactions is during the initialization of a model or a diagram. The reasons for that are several. One is that during that phase there might not be an UndoManager yet. Another is that most programmers really don’t want to bother with transactions at that time.

So your situation of wanting a transaction before the diagram has been fully initialized during the loading of a model is somewhat unusual. It should generally be the case that you can make whatever data changes you like before setting up the model. And that includes performing side effects to make sure the rest of the world matches the data that you have in your model.

Thank you @walter.
I’ve just checked my code and the issue was indeed that I am skipping the undo manager.

The case that the model is changed before it is completely loaded was only in the online example I’ve created. I didn’t realize that issue here was caused by something else.

So basically, in my case, I don’t need undo, therefore I don’t need to execute the model change in a transaction and I can just listen directly for the property ChangeEvent? Or would this have any drawbacks?

Yes, you can watch for particular Property or Insert or Remove ChangedEvents. It’s good that you don’t have to worry about undoing the deletion of an external resource.

Perfect, thanks!