Programatic use of undo/redo

I’m adapting a test to test that the undo/redo functionality is working correctly, but whilst it works fine with manual testing I’m having trouble getting it to work automatically.

` it('check Analytics block moving ', () => {
const diagram: go.Diagram = component.getGoJSDiagramProvider().getGoJSDiagram();

    if (instanceOfAnalyticsCanvasModel(diagram.model)) {
        diagram.model.addBlock(testUtil.getAnalyticsBlock('MY_BLOCK'), new go.Point(20, 40));
        let node: go.Node = testUtil.getBlockNode(diagram, 'MY_BLOCK');
        expect(node.location.x).toBe(20);            expect(node.location.y).toBe(40);            // moving node             node.moveTo(10, 15);
        node = testUtil.getBlockNode(diagram, 'MY_BLOCK');
    // Now check undo/redo
    const undo: HTMLElement = document.getElementById('undo');
    const redo: HTMLElement = document.getElementById('redo');


The section between the transaction statements is the pre-existing test which works fine. The undoManager.isEnabled logs as true, as expected. However, the section after commitTransaction has canUndo() still false and the undo element still disabled. I’m not clear on why the diagram undo functionality isn’t to be working.

GoJS 1.8.16, Windows 7

One can only perform undo or redo between transactions – not during a transaction. Has your detectChanges function accidentally made some changes?

I’m not attempting any undo within the transaction. The steps are:

  1. Start with blank canvas
  2. Start transaction
  3. Add block to canvas
  4. Check block location
  5. Move block
  6. Check block location
  7. Commit transaction
  8. Check canUndo() which is returning false.

How are you updating your “Undo” and “Redo” buttons?

I just tried the code that is demonstrated at GoJS Commands -- Northwoods Software, and it worked as expected. Here’s my code:

function init() {
  . . . initialization of myDiagram . . .

    // enable or disable a particular button
    function enable(name, ok) {
      var button = document.getElementById(name);
      if (button) button.disabled = !ok;
    // enable or disable all command buttons
    function enableAll() {
      var cmdhnd = myDiagram.commandHandler;
      enable("Undo", cmdhnd.canUndo());
      enable("Redo", cmdhnd.canRedo());

    myDiagram.addModelChangedListener(function(e) {
      if (e.isTransactionFinished) enableAll();

    setTimeout(enableAll, 1);

  function test() {
    var n = myDiagram.nodes.first();
    n.location = new go.Point(200, 50);

    var undobutton = document.getElementById("Undo");
    var redobutton = document.getElementById("Redo");

And my buttons are defined by:

  <button onclick="test()">Test</button>
  <input id="Undo" type="button" onclick="myDiagram.commandHandler.undo()" value="Undo" />
  <input id="Redo" type="button" onclick="myDiagram.commandHandler.redo()" value="Redo" />

Clicking on the “Test” button produces the expected results, including the console output:

true  // CommandHandler.canUndo() returned true
false // the "Undo" button is not disabled -- i.e. it is enabled, and looks it and works!
true  // the "Redo" button is disabled -- i.e. it looks disabled and is not responsive

Thanks for that. Based on this I went back to scratch to create as simple a test function as possible for Angular:

it('test', () => { //const diagram: go.Diagram = component.getGoJSDiagramProvider().getGoJSDiagram(); const diagram: go.Diagram = new go.Diagram(); diagram.undoManager.isEnabled = true; diagram.model = new go.GraphLinksModel([{key: 'alpha'}], []); diagram.startTransaction("test"); var n = diagram.nodes.first(); n.location = new go.Point(200, 50); diagram.commitTransaction("test"); console.log("ENABLED: " + diagram.undoManager.isEnabled); console.log("CANUNDO: " + diagram.commandHandler.canUndo()); });

If I use this with the brand new go.Diagram and set isEnabled true, it works as expected. I get isEnabled = true and canUndo() = true. If, however, I comment out lines 2-3 and use the go.Diagram retrieved from the div, I still get isEnabled = true, but canUndo() stays resolutely false. Clearly there is something in the retrieved go.Diagram which is somehow stopping the undoManager from working correctly. I’ll investigate more tomorrow, but do you have any ideas as to what the difference might be or where to look first?

Incidentally, this is only affecting the automated test. The undo/redo functionality is working fine when used manually in the browser.

Even if I knew what test system you were using and what else was going on in your page, I wouldn’t know anything about it.

Here’s the definition of UndoManager.canUndo:

UndoManager.prototype.canUndo = function() {
  if (!this.isEnabled) return false;
  if (this.transactionLevel > 0) return false;
  var curr = this.transactionToUndo;
  if (curr !== null && curr.canUndo()) return true;
  return false;

The UndoManager.transactionToUndo is defined with this getter:

  function() {
    if (this.historyIndex >= 0 && this.historyIndex <= this.history.count - 1) {
      return this.history.elt(this.historyIndex);
    return null;

Thanks. This helps a lot in knowing what to check.

One thing Ive noticed is that there’s a typo in historyIndex in UndoManager with it being defined as hisotryIndex.

Does commitTranaction() have any checks as to whether it adds a transaction to the history?

In both cases there is a currentTransaction immediately prior to calling commitTransaction(). With the clean go.Diagram, the transaction is added to the history and we history.count = 1, but in the other case history remains empty with count = 0.

I just checked all of our sources, including samples and documentation, and found no instance of the string “sotry”.

I suggest that you look at the changes that are recorded in that earlier Transaction, if there are any.

Am I correct is assuming that when a change happens outside of a transaction that will stop the undo/redo working as it will be impossible to keep the model consistent through that point of the history?

I ask because in my test, in the case that isn’t working I’m getting a Change not within transaction message. As far as I can tell though it is being induced during the commit. I don’t see that warning with the new go.Diagram that works. To track it down I wrapped the commit in the code above as follows:

console.log("HERE"); const commitResult: boolean = diagram.commitTransaction("test"); console.log("HERE2");
And get the following log output:

e[1Ae[2KLOG: ‘HERE’
HeadlessChrome 65.0.3325 (Windows 7.0.0): Executed 9 of 19 SUCCESS (0 secs / 1.277 secs)
LOG: ‘Change not within a transaction: !dChangedEvent.Remove parts: Layer “” old: Part#4548 0’
e[1Ae[2KLOG: ‘Change not within a transaction: !dChangedEvent.Remove parts: Layer “” old: Part#4548 0’
HeadlessChrome 65.0.3325 (Windows 7.0.0): Executed 9 of 19 SUCCESS (0 secs / 1.277 secs)
LOG: ‘Change not within a transaction: !dChangedEvent.Remove parts: Layer “” old: Part#4548 0’
e[1Ae[2KLOG: ‘HERE2’
HeadlessChrome 65.0.3325 (Windows 7.0.0): Executed 9 of 19 SUCCESS (0 secs / 1.277 secs)

(The multiple repetitions are a feature of the test framework.)

Yes, that is what I have been saying since my first reply. You need to figure out why you are removing a Part from the diagram between transactions.

A post was split to a new topic: Undo is changing link data