Undo redo in Vue Diagram component
1 Jan 202524 minutes to read
Diagram tracks the history of actions that are performed after initializing the diagram and provides support to reverse and restore those changes.
Undo and redo
Diagram provides built-in support to track the changes that are made through interaction and through public APIs. The changes can be reverted or restored either through shortcut keys or through commands.
NOTE
If you want to use Undo-Redo in diagram, you need to inject UndoRedo in the diagram.
Undo/redo through shortcut keys
Undo/redo commands can be executed through shortcut keys. Shortcut key for undo is Ctrl+z
and shortcut key for redo is Ctrl+y
.
Undo/redo through public APIs
The client-side methods undo
and redo
help you to revert/restore the changes. The following code example illustrates how to undo/redo the changes through script.
export default {
name: 'app'
data() {
return {
width: "100%",
height: "350px",
}
}
mounted: function() {
let diagramInstance: Diagram;
let diagramObj: any = document.getElementById("diagram");
diagramInstance = diagramObj.ej2_instances[0];
// Reverts the last action performed
diagramInstance.undo();
// Restores the last undone action
diagramInstance.redo();
}
}
Undo/Redo for diagram can be enabled/disabled with the constraints
property of diagram.
When a change in the diagram is reverted or restored (undo/redo), the historyChange
event gets triggered.
Group multiple changes
History list allows to revert or restore multiple changes through a single undo/redo command. For example, revert/restore the fill color change of multiple elements at a time.
The diagram method startGroupAction
allows you to log multiple actions at a time in the history manager stack. It is easier to undo or revert the changes made in the diagram in a single undo/redo process instead of reverting every actions one by one.The diagram method endGroupAction
allows you to end the group actions that are stored in the stack history. The following code illustrates how to undo/redo multiple fillColor change of a node at a time.
<template>
<div id="app">
<ejs-diagram id="diagram" ref="diagram" :width='width' :height='height' :nodes='nodes'>
</ejs-diagram>
</div>
</template>
<script setup>
import { provide, onMounted, ref } from "vue";
import { DiagramComponent as EjsDiagram, UndoRedo } from '@syncfusion/ej2-vue-diagrams';
const diagram = ref(null);
const nodes = [{
id: 'Start',
width: 100,
height: 100,
offsetX: 300,
offsetY: 100,
annotations: [{
id: 'label1',
content: 'Annotation'
}],
}]
const width = "750px";
const height = "350px";
onMounted(function () {
const diagramInstance = diagram.value.ej2Instances;
//Start to group the changes
diagramInstance.startGroupAction();
//Makes the changes
let color = ['black', 'red', 'green', 'yellow']
for (var i = 0; i < color.length; i++) {
// Updates the fillColor for all the child elements.
diagramInstance.nodes[0].style.fill = color[i];
diagramInstance.dataBind();
}
//Ends grouping the changes
diagramInstance.endGroupAction();
})
provide('diagram', [UndoRedo]);
</script>
<style>
@import "../node_modules/@syncfusion/ej2-vue-diagrams/styles/material.css";
</style>
<template>
<div id="app">
<ejs-diagram id="diagram" ref="diagram" :width='width' :height='height' :nodes='nodes'></ejs-diagram>
</div>
</template>
<script>
import { DiagramComponent, UndoRedo } from '@syncfusion/ej2-vue-diagrams';
let nodes = [{
id: 'Start',
width: 100,
height: 100,
offsetX: 300,
offsetY: 100,
annotations: [{
id: 'label1',
content: 'Annotation'
}],
}]
export default {
name: "App",
components: {
"ejs-diagram": DiagramComponent
},
data() {
return {
width: "100%",
height: "350px",
nodes: nodes,
}
},
mounted: function () {
const diagramInstance = this.$refs.diagram.ej2Instances;
//Start to group the changes
diagramInstance.startGroupAction();
//Makes the changes
let color = ['black', 'red', 'green', 'yellow']
for (var i = 0; i < color.length; i++) {
// Updates the fillColor for all the child elements.
diagramInstance.nodes[0].style.fill = color[i];
diagramInstance.dataBind();
}
//Ends grouping the changes
diagramInstance.endGroupAction();
},
provide: {
diagram: [UndoRedo]
}
}
</script>
<style>
@import "../node_modules/@syncfusion/ej2-vue-diagrams/styles/material.css";
</style>
Stack Limit
The stackLimit
property of history manager is used to limits the number of actions to be stored on the history manager.
<template>
<div id="app">
<ejs-diagram id="diagram" :width='width' :height='height' :nodes='nodes' :historyManager='historyManager'>
</ejs-diagram>
</div>
</template>
<script setup>
import { provide } from "vue";
import { DiagramComponent as EjsDiagram, UndoRedo } from '@syncfusion/ej2-vue-diagrams';
const nodes = [{
id: 'Start',
width: 100,
height: 100,
offsetX: 300,
offsetY: 100,
annotations: [{
id: 'label1',
content: 'Annotation'
}],
}]
const width = "750px";
const height = "350px";
const historyManager = {
stackLimit: 2
}
provide('diagram', [UndoRedo]);
</script>
<style>
@import "../node_modules/@syncfusion/ej2-vue-diagrams/styles/material.css";
</style>
<template>
<div id="app">
<ejs-diagram id="diagram" :width='width' :height='height' :nodes='nodes' :historyManager='historyManager'>
</ejs-diagram>
</div>
</template>
<script>
import { DiagramComponent, UndoRedo } from '@syncfusion/ej2-vue-diagrams';
let nodes = [{
id: 'Start',
width: 100,
height: 100,
offsetX: 300,
offsetY: 100,
annotations: [{
id: 'label1',
content: 'Annotation'
}],
}]
export default {
name: "App",
components: {
"ejs-diagram": DiagramComponent
},
data() {
return {
width: "100%",
height: "500px",
nodes: nodes,
historyManager: {
stackLimit: 2
}
}
},
provide: {
diagram: [UndoRedo]
}
}
</script>
<style>
@import "../node_modules/@syncfusion/ej2-vue-diagrams/styles/material.css";
</style>
Restrict Undo/Redo
Undo, Redo process can be avoided for particular element by using canLog
property in the history manager. The following example illustrates how to prevent history entry using canLog
function.
<template>
<div id="app">
<ejs-diagram id="diagram" ref="diagram" :width='width' :height='height' :nodes='nodes'></ejs-diagram>
</div>
</template>
<script setup>
import { ref, onMounted, provide } from "vue";
import { DiagramComponent as EjsDiagram, UndoRedo } from '@syncfusion/ej2-vue-diagrams';
const diagram = ref(null);
const nodes = [{
id: 'Start',
width: 100,
height: 100,
offsetX: 300,
offsetY: 100,
annotations: [{
id: 'label1',
content: 'Annotation'
}],
}];
const width = "750px";
const height = "500px";
onMounted(function () {
const diagramInstance = diagram.value.ej2Instances;
// canLog decide whether the entry add or not in history List
diagramInstance.historyManager.canLog = function (entry) {
entry.cancel = true;
return entry;
}
})
provide('diagram', [UndoRedo]);
</script>
<style>
@import "../node_modules/@syncfusion/ej2-vue-diagrams/styles/material.css";
</style>
<template>
<div id="app">
<ejs-diagram id="diagram" ref="diagram" :width='width' :height='height' :nodes='nodes'></ejs-diagram>
</div>
</template>
<script>
import { DiagramComponent, UndoRedo } from '@syncfusion/ej2-vue-diagrams';
let nodes = [{
id: 'Start',
width: 100,
height: 100,
offsetX: 300,
offsetY: 100,
annotations: [{
id: 'label1',
content: 'Annotation'
}],
}];
export default {
name: "App",
components: {
"ejs-diagram": DiagramComponent
},
data() {
return {
width: "100%",
height: "500px",
nodes: nodes,
}
},
mounted: function () {
const diagramInstance = this.$refs.diagram.ej2Instances;
// canLog decide whether the entry add or not in history List
diagramInstance.historyManager.canLog = function (entry) {
entry.cancel = true;
return entry;
}
},
provide: {
diagram: [UndoRedo]
}
}
</script>
<style>
@import "../node_modules/@syncfusion/ej2-vue-diagrams/styles/material.css";
</style>
undo/redo stack
The undoStack
property is used to get the collection of undo actions which should be performed in the diagram. The redoStack
property is used to get the collection of redo actions which should be performed in the diagram. The undoStack/redoStack is the read-only property.
export default {
name: 'app'
data() {
return {
width: "100%",
height: "350px",
}
}
mounted: function() {
let diagramInstance: Diagram;
let diagramObj: any = document.getElementById("diagram");
diagramInstance = diagramObj.ej2_instances[0];
//get the collection of undoStack objects
let undoStack = diagramInstance.historyList.undoStack;
//get the collection of redoStack objects
let redoStack = diagramInstance.historyList.redoStack;
}
}
canUndo and canRedo
The canUndo
property returns true if there are actions in the undo history stack; otherwise, it returns false. This property helps identify whether any actions are present in the undo stack.The canRedo
property returns true if there are actions in the redo history stack; otherwise, it returns false. This property helps identify whether any actions are present in the redo stack.
The following code demonstrates how to use these properties:
<template>
<div id="app">
<ejs-diagram id="diagram" ref="diagram" :width='width' :height='height' :nodes='nodes' :historyChange="historyChange">
</ejs-diagram>
</div>
</template>
<script setup>
import { provide, ref } from "vue";
import { DiagramComponent as EjsDiagram, UndoRedo } from '@syncfusion/ej2-vue-diagrams';
const nodes = [{
id: 'Start',
width: 100,
height: 100,
offsetX: 300,
offsetY: 100,
annotations: [{
id: 'label1',
content: 'Annotation'
}],
}]
const diagram = ref(null);
const width = "750px";
const height = "350px";
const historyChange = (args) => {
const diagramInstance = diagram.value.ej2Instances;;
console.log(diagramInstance.historyManager.currentEntry);
};
provide('diagram', [UndoRedo]);
</script>
<style>
@import "../node_modules/@syncfusion/ej2-vue-diagrams/styles/material.css";
</style>
<template>
<div id="app">
<ejs-diagram id="diagram" ref="diagram" :width='width' :height='height' :nodes='nodes' :historyChange="historyChange">
</ejs-diagram>
</div>
</template>
<script>
import { DiagramComponent, UndoRedo } from '@syncfusion/ej2-vue-diagrams';
let nodes = [{
id: 'Start',
width: 100,
height: 100,
offsetX: 300,
offsetY: 100,
annotations: [{
id: 'label1',
content: 'Annotation'
}],
}]
export default {
name: "App",
components: {
"ejs-diagram": DiagramComponent
},
data() {
return {
width: "100%",
height: "500px",
nodes: nodes,
historyChange: (args) => {
const diagramInstance = this.$refs.diagram.ej2Instances;
console.log(diagramInstance.historyManager.currentEntry);
},
}
},
provide: {
diagram: [UndoRedo]
}
}
</script>
<style>
@import "../node_modules/@syncfusion/ej2-vue-diagrams/styles/material.css";
</style>
Current entry
While performing interactions with a node or connector, the current history entry is added to the currentEntry
property of the historyManager
.
The following code shows how to get the current entry from the diagram history:
<template>
<div id="app">
<ejs-diagram id="diagram" ref="diagram" :width='width' :height='height' :nodes='nodes' :historyChange="historyChange">
</ejs-diagram>
</div>
</template>
<script setup>
import { provide, ref } from "vue";
import { DiagramComponent as EjsDiagram, UndoRedo } from '@syncfusion/ej2-vue-diagrams';
const nodes = [{
id: 'Start',
width: 100,
height: 100,
offsetX: 300,
offsetY: 100,
annotations: [{
id: 'label1',
content: 'Annotation'
}],
}]
const diagram = ref(null);
const width = "750px";
const height = "350px";
const historyChange = (args) => {
const diagramInstance = diagram.value.ej2Instances;;
console.log(diagramInstance.historyManager.currentEntry);
};
provide('diagram', [UndoRedo]);
</script>
<style>
@import "../node_modules/@syncfusion/ej2-vue-diagrams/styles/material.css";
</style>
<template>
<div id="app">
<ejs-diagram id="diagram" ref="diagram" :width='width' :height='height' :nodes='nodes' :historyChange="historyChange">
</ejs-diagram>
</div>
</template>
<script>
import { DiagramComponent, UndoRedo } from '@syncfusion/ej2-vue-diagrams';
let nodes = [{
id: 'Start',
width: 100,
height: 100,
offsetX: 300,
offsetY: 100,
annotations: [{
id: 'label1',
content: 'Annotation'
}],
}]
export default {
name: "App",
components: {
"ejs-diagram": DiagramComponent
},
data() {
return {
width: "100%",
height: "500px",
nodes: nodes,
historyChange: (args) => {
const diagramInstance = this.$refs.diagram.ej2Instances;
console.log(diagramInstance.historyManager.currentEntry);
},
}
},
provide: {
diagram: [UndoRedo]
}
}
</script>
<style>
@import "../node_modules/@syncfusion/ej2-vue-diagrams/styles/material.css";
</style>
Clear history
The clearHistory
method of diagram is used to remove all the recorded actions from the undo and redo history.
//Clears all the histories
diagramInstance.clearHistory();
Get history stack
The getHistoryStack
method of the diagram retrieves the undoStack
or redoStack
from the historyManager. This method takes a single parameter, isUndoStack. Pass true to get the undoStack or false to get the redoStack.
// Fetch undoStack from history manager
diagramInstance.getHistoryStack(true)
// Fetch redoStack from history manager
diagramInstance.getHistoryStack(false)
History change event
The historyChange
event triggers, whenever the interaction of the node and connector is take place. When interacting, the entries get added to the history manager to trigger this event. The following example shows how to get this event in diagram.
<template>
<div id="app">
<ejs-diagram id="diagram" :width='width' :height='height' :nodes='nodes' :connectors='connectors' :historyChange="historyChange">
</ejs-diagram>
</div>
</template>
<script setup>
import { provide } from "vue";
import { DiagramComponent as EjsDiagram, UndoRedo } from '@syncfusion/ej2-vue-diagrams';
const nodes = [{
id: 'Start',
width: 140,
height: 50,
offsetX: 300,
offsetY: 50,
annotations: [
{
content: 'Node1',
},
],
},
{
id: 'Init',
width: 140,
height: 50,
offsetX: 300,
offsetY: 140,
annotations: [
{
content: 'Node2',
},
],
}];
const connectors = [
{
// Unique name for the connector
id: 'connector1',
sourceID: 'Start',
targetID: 'Init',
type: 'Orthogonal',
},
];
const width = "750px";
const height = "350px";
const historyChange = (args) => {
//Triggers while interacting with diagram and performing undo-redo
console.log(args);
};
provide('diagram', [UndoRedo]);
</script>
<style>
@import "../node_modules/@syncfusion/ej2-vue-diagrams/styles/material.css";
</style>
<template>
<div id="app">
<ejs-diagram id="diagram" ref="diagram" :width='width' :height='height' :nodes='nodes' :connectors='connectors' :historyChange="historyChange">
</ejs-diagram>
</div>
</template>
<script>
import { DiagramComponent, UndoRedo } from '@syncfusion/ej2-vue-diagrams';
let nodes = [{
id: 'Start',
width: 140,
height: 50,
offsetX: 300,
offsetY: 50,
annotations: [
{
content: 'Node1',
},
],
},
{
id: 'Init',
width: 140,
height: 50,
offsetX: 300,
offsetY: 140,
annotations: [
{
content: 'Node2',
},
],
}]
let connectors = [
{
// Unique name for the connector
id: 'connector1',
sourceID: 'Start',
targetID: 'Init',
type: 'Orthogonal',
},
];
export default {
name: "App",
components: {
"ejs-diagram": DiagramComponent
},
data() {
return {
width: "100%",
height: "500px",
nodes: nodes,
connectors: connectors,
historyChange: (args) => {
//Triggers while interacting with diagram and performing undo-redo
console.log(args);
},
}
},
provide: {
diagram: [UndoRedo]
}
}
</script>
<style>
@import "../node_modules/@syncfusion/ej2-vue-diagrams/styles/material.css";
</style>
While interacting with diagram, this event can be used to do the customization.