I'm using undomanager and trying to implement a cut/copy/paste functionality on a doubly linked list. First, here's my code.
it('should cut and paste a node with working undo/redo', function () {
var list = listFactory.createList();
var node1 = list.appendNode(
nodeFactory.createNode({
//nodeinfo
})
);
var node2 = list.appendNode(
nodeFactory.createNode({
//nodeinfo
})
);
// here we cut the node2 out of the profile
list.cutNode(node2.id);
expect(list.clipboard.id).toBe(node2.id);
expect(list.getLength()).toBe(2);
// undoing the cut should clear the clipboard and return the node2 to where it was.
list.undo();
expect(list.clipboard).toBe(null);
expect(list.getLength()).toBe(2);
// redoing the cut should remove node2
list.redo();
expect(list.clipboard.id).toBe(node2.id);
expect(list.getLength()).toBe(1);
// pasting node2 in front of node1
list.pasteNode(indexSeg.id);
expect(list.getLength()).toBe(2);
// the first undo should remove node2 from the front
list.undo();
expect(list.getLength()).toBe(1);
// this should reset the list back to its original state
list.undo(); // THIS COMMAND FAILS
});
var List = function () {
this.clipboard = null;
this.head = null;
this.tail = null;
this.head.next = tail;
this.tail.prev = head;
// blahblah typical linked list stuff
};
// Inserts a node in front of the node with nodeId
List.prototype.insertNode = function(node, nodeId) {
this.insertAt(node, nodeId);
var list = this;
this.undoManager.add({
undo: function() {
list.deleteNode(newNode.id);
},
redo: function() {
list.insertNode(node, nodeId);
}
});
return node;
};
// put node at tail
List.prototype.appendNode = function(node) {
this.insertAt(node, null);
var list = this;
this.undoManager.add({
undo: function() {
list.deleteNode(node.id);
},
redo: function() {
list.appendNode(node);
}
});
return node;
};
// delete node with nodeId
List.prototype.deleteNode = function(nodeId) {
var nextId = this.getNextNodeId(nodeId); // returns null if nodeId is at tail
var deletedNode = this.deleteById(nodeId);
var list = this;
this.undoManager.add({
undo: function() {
//special case for handling last node
if(!nextId)
list.appendNode(deletedNode);
else
list.insertNode(deletedNode, nextId);
},
redo: function() {
list.deleteNode(nodeId);
}
});
return deletedNode;
};
// Removes the node with nodeId from list and stores it in clipboard
List.prototype.cutNode = function (nodeId) {
var nextNodeId = this.getNextNodeId(nodeId); // returns null if nodeId is tail
var cuttNode = this.deletedNode(nodeId);
var oldClipboard = this.clipboard;
this.clipboard = cuttNode;
var list = this;
this.undoManager.add({
undo: function() {
if (!nextNodeId) {
list.appendNode(cuttNode);
} else {
list.insertNode(cuttNode, nextNodeId);
}
list.clipboard = oldClipboard;
},
redo: function() {
list.cutNode(nodeId);
}
});
};
// duplicate node with nodeId and store in clipboard
List.prototype.copyNode = function (nodeId) {
var node = this.
var oldClipboard = this.clipboard;
// duplicate() copies the node data to a new node object which generats a new/unique node id
this.clipboard = node.duplicate();
var list = this;
this.undoManager.add({
undo: function() {
list.clipboard = oldClipboard;
},
redo: function() {
list.clipboard = node;
}
});
};
// pastes clipboard node to list before nodeId
List.prototype.pasteNode = function (nodeId) {
if (this.clipboard !== null) {
var pastedNode = this.insertNode(this.clipboard, nodeId);
// just in case we want to paste again, we need a unique node
this.clipboard = pastedNode.duplicate();
var list = this;
this.undoManager.add({
undo: function() {
list.clipboard = list.deleteNode(pastedNode.id);
},
redo: function() {
list.pasteNode(nodeId);
}
});
}
};
The last undo command in the karma test fails with the following error:
Error: Unable to delete node with id 14914926779057942
This node id belongs to the duplicated node from the paste operation: this.clipboard = pastedNode.duplicate()
The undo manager calls deleteNode from the pasteNode undo function on the id of the duplicated node, rather than the pastedNode. Since the duplicated node exists only in the clipboard and not in the linked list, it errors out.
Am I not understanding the undo/redo stack correctly? What is the correct way to write cut/copy/paste?
The problem here was that the undo manager's undo and redo stack were being double-stacked when the paste function called insertNode
.
For example, calling cutNode would call deleteNode, which would throw a command on both the undo and redo stack. Then cutNode would add another layer on to both stacks after deleteNode returned.
I discovered the double-stacking by using UndoManager's getIndex()
function in between calls to cut, copy, and paste.