Skip to main content
  1. Index

Transactions and the UndoManager

GoJS models and diagrams make use of an UndoManager that can record all changes and support undoing and redoing those changes. Each state change (values before and aftert) is recorded in a ChangedEvent. Such changes are grouped together into Transactions so that a user action, which may result in many changes, can be undone and redone as a single operation.

Enabling the Undo Manager

You can set Diagram.undoManager.isEnabled to true during Diagram initialization:

Not all state changes result in ChangedEvents that can be recorded by the UndoManager. Some properties are considered transient, such as Diagram.position, Diagram.scale, Diagram.currentTool, Diagram.currentCursor, or Diagram.isModified. Some changes are structural or considered unchanging, such as Diagram.model, any property of CommandHandler, or any of the tool or layout properties. But most GraphObject and model properties do raise a ChangedEvent on the Diagram or Model, respectively, when a property value has been changed.

Transactions

Whenever you modify a model or its data programmatically in response to some event, you should wrap the code in a transaction. Call Diagram.startTransaction or Model.startTransaction, make the changes, and then call Diagram.commitTransaction or Model.commitTransaction. Although the primary benefit from using transactions is to group together side-effects for undo/redo, you should use transactions even if your application does not support undo/redo by the user.

js

A typical case for using transactions is when some command makes a change to the model. Select a node and press the "addChild()" button to execute the manually created transaction. This creates a new node and link together, which can be undone.

As with database transactions, you will want to perform transactions that are short and infrequent. Do not leave transactions ongoing between user actions. Consider whether it would be better to have a single transaction surrounding a loop instead of starting and finishing a transaction repeatedly within a loop. Do not execute transactions within a property setter -- such granularity is too small. Instead execute a transaction where the properties are set in response to some user action or external event.

The only exception is that transactions are unnecessary when initializing a model or a diagram before assigning the model to the Diagram.model property. (A Diagram only gets access to an UndoManager via the Model, the Model.undoManager property.)

GoJS Commands and handlers create transactions for you

Many event handlers and listeners are already executed within transactions that are conducted by Tools or CommandHandler commands, so you often will not need to start and commit a transaction within such functions. For example, implementing an "ExternalObjectsDropped" DiagramEvent listener, which usually does want to modify the just-dropped Parts in the Diagram.selection, is called within the DraggingTool's transaction, so no additional start/commit transaction calls are needed. However, the GraphObject.click event handler to respond to a click on a GraphObject needs to perform a transaction if it wants to modify the model or the diagram. Most custom click event handlers do not change the diagram but instead update some HTML.

Read the API documentation for details about whether a function is called within a transaction, and see the Commands page and Events page for built-in transaction examples.

Finally, some customizations, such as the Node.linkValidation predicate, should not modify the diagram or model at all.

Both model changes and diagram changes are recorded in the UndoManager only if the model's UndoManager.isEnabled has been set to true. If you do not want the user to be able to perform undo or redo and also prevent the recording of any Transactions, but you still want to get "Transaction"-type ChangedEvents because you want to update a database, you can set UndoManager.maxHistoryLength to zero.

Supporting the UndoManager

Changes to JavaScript data properties do not automatically result in any notifications that can be observed. Thus when you want to change the value of a property in a manner that can be undone and redone, you should call Model.setDataProperty (or Model.set, which is an abbreviation for that method). This will get the previous value for the property, set the property to the new value, and call Model.raiseDataChanged, which will also automatically update any target bindings in the Node corresponding to the data.