Undo and Redo
The customization options of the undo mechanism in yFiles for HTML include changing the behavior of the built-in undo process, controlling the recording process of changes, and synchronizing your data and the changes made by undo.
The undo support in yFiles for HTML is managed by the UndoEngine class, which is usually associated with an IGraph instance. To obtain the UndoEngine from an IGraph where undo is enabled, use the undoEngine property.
The UndoEngine uses IUndoUnits which represent a change that can be undone and redone. Besides the standard undoable edits custom undo units can be added as well.
Furthermore, you can instruct the UndoEngine to record a sequence of changes and automatically create a single unit thereof over a period of time.
The UndoEngine dispatches the following events for each undo unit that it undoes or redoes:
Event | Occurs when … |
---|---|
Custom Undo Units
All changes to graph items are undoable by default. However, there are use cases where developers might want to add custom operations to be undoable, too. For example, while changing the style instance of an INode is undoable by default, changing a property of that style is not. If you have code that changes a property of a style dynamically, it might be a good idea to tell the UndoEngine about it, so that the change can be reverted by the user.
The simplest way to go about this — which is often sufficient on its own — is to use addUndoUnit. You can supply names for the Undo and Redo actions that may be shown in the user interface, as well as functions that implement the actual process of undoing and redoing a change.
The following example shows the quick way of adding a custom undo unit for changing a ShapeNodeStyle’s fill property:
For more complex use cases you may need to create a custom IUndoUnit and add it to the UndoEngine using the addUnit method when the change occurs. You can extend the UndoUnitBase class which removes some of the necessary boilerplate. The undo method is supposed to revert the change it represents and redo to restore the state after the change again, similar to the two functions passed to addUndoUnit above.
It is important that redo is the exact inverse operation of undo. That is, when executing redo after undo the graph and its related objects are in the exact same state as before the undo. This also requires the exact same references to object instances, as otherwise subsequent undo/redo operations may no longer work correctly.Furthermore, it is important to avoid enqueuing any additional undo units while performing an undo or redo operation. Attempting to do so will result in the silent discarding of these units, potentially leading to hard-to-diagnose bugs. Above all, avoid making any modifications to the graph within the implementation of the undo/redo unit (e.g. the delegates in addUndoUnit or in undo resp. redo)
The following example shows the same Undo/Redo action we used above, just implemented with a custom IUndoUnit:
Built-in Undo Units
As mentioned above, yFiles for HTML automatically records every change to the graph structure in an undo unit. There might be situations however when you want to customize the undo operation for graph structure changes, for example for node removals, as a means to synchronize your own business data, for example.
Even though the concrete implementation of these undo units is private, you can still decorate them by overriding their factory methods on DefaultGraph. For example, to decorate the undo unit that undoes the “delete node” action, override the createUndoUnitForNodeRemoval method. There are factory methods like this for every graph structure change.
Merging Changes into one Undo Unit
By default the UndoEngine merges multiple undo units that occur during a certain time span as defined
by autoMergeTime. You can disable this by setting the time span to a zero
value. Independently, the UndoEngine can also be configured to try to merge similar undo units into one single unit
if possible. This behavior relies on IUndoUnit’s
tryMergeUnit and
tryReplaceUnit methods.
You can enable it by setting mergeUnits to true
.
Sometimes we want to merge a chain of changes into one single unit. This happens, e.g., when a node is moved, which would create a lot of undo units for position changes. However, we only want one single undo unit for that, i.e. one change from its initial to its final position. The UndoEngine supports recording a set of changes and merging it into one single unit at the end of the gesture.
To start recording, call beginEdit on IGraph. This method returns an instance of ICompoundEdit which can be used as a hook to end or cancel the recording. To end the recording you need to call the commit method. The recorded undo unit is automatically added to the undo engine.
If the action should be canceled or something went wrong while executing the changes, call cancel to end the recording without adding an undo unit. For example, an input gesture could be added where pressing Escape would cancel the gesture, e.g. moving a node. In this case we would not want to add the changes to the UndoEngine.
Remembering States for Undo (Memento Design Pattern)
The yFiles for HTML undo mechanism also supports the memento design pattern, i.e. taking snapshots of the state of an object to be able to restore that state later. This concept can be used to add undo support for your business data. Mementos are particularly efficient where handling data states is easier than handling atomic changes to the items themselves.
The Memento design pattern is used for recording sessions of ICompoundEdit. Your model can be watched over a course of actions and changes to it can be appropriately undone and redone. Mementos are snapshots of states of arbitrary data. The IMementoSupport interface can be used to convert the data between these states. In this regard mementos are a more abstract concept of IUndoUnits where the cumbersome process of creating and inserting IUndoUnits at the right time into the UndoEngine is omitted.
- getState
- Returns an object which represents the current state of the given object.
- applyState
- Applies the given state to the given object, i.e. restores the saved state.
- stateEquals
- Whether the two given states are equal. Used to avoid saving mementos for unchanged states.
To use memento support implement the IMementoSupport interface …
... and add a lookup to your business data:
The following example shows how to record all changes to a list of business objects:
Note: If the business object classes cannot be modified and you cannot implement ILookup to provide an IMementoSupport implementation, you can provide an appropriate IMementoSupport implementation in the beginEdit<T> call: