Undo redo in Vue Diagram component
28 Sep 202424 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();
}
}
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 client-side method startGroupAction
is used to notify the diagram to start grouping the changes. The client-side method endGroupAction
is used to notify to stop grouping the changes. The following code illustrates how to undo/redo fillColor change of multiple elements at a time.
<template>
<div id="app">
<ejs-diagram id="diagram" ref="diagram" :width='width' :height='height' :nodes='nodes'
:getNodeDefaults='getNodeDefaults'>
</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: 140,
height: 50,
offsetX: 300,
offsetY: 50,
annotations: [{
id: 'label1',
content: 'Start'
}],
shape: {
type: 'Flow',
shape: 'Terminator'
}
}]
const width = "100%";
const height = "350px";
const getNodeDefaults = (node) => {
node.height = 100;
node.width = 100;
node.style.fill = '#6BA5D7';
node.style.strokeColor = 'white';
return node;
}
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'
:getNodeDefaults='getNodeDefaults'></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: [{
id: 'label1',
content: 'Start'
}],
shape: {
type: 'Flow',
shape: 'Terminator'
}
}]
export default {
name: "App",
components: {
"ejs-diagram": DiagramComponent
},
data() {
return {
width: "100%",
height: "350px",
nodes: nodes,
getNodeDefaults: (node) => {
node.height = 100;
node.width = 100;
node.style.fill = '#6BA5D7';
node.style.strokeColor = 'white';
return node;
},
}
},
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>
canLog
canLog in the history list, which takes a history entry as argument and returns whether the specific entry can be added or not.
<template>
<div id="app">
<ejs-diagram id="diagram" ref="diagram" :width='width' :height='height' :nodes='nodes'
:getNodeDefaults='getNodeDefaults'></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: 140,
height: 50,
offsetX: 300,
offsetY: 50,
annotations: [{
id: 'label1',
content: 'Start'
}],
shape: {
type: 'Flow',
shape: 'Terminator'
}
}];
const width = "100%";
const height = "350px";
const getNodeDefaults = (node) => {
node.height = 100;
node.width = 100;
node.style.fill = '#6BA5D7';
node.style.strokeColor = 'white';
return node;
};
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'
:getNodeDefaults='getNodeDefaults'></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: [{
id: 'label1',
content: 'Start'
}],
shape: {
type: 'Flow',
shape: 'Terminator'
}
}];
export default {
name: "App",
components: {
"ejs-diagram": DiagramComponent
},
data() {
return {
width: "100%",
height: "350px",
nodes: nodes,
getNodeDefaults: (node) => {
node.height = 100;
node.width = 100;
node.style.fill = '#6BA5D7';
node.style.strokeColor = 'white';
return node;
},
}
},
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>
Track undo/redo actions
The historyList undoStack property is used to get the collection of undo 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;
}
}
History change event
The historyChange
event triggers, whenever the interaction of the node and connector is take place.
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];
diagramInstance.historyChange = (arg: IHistoryChangeArgs) => {
//causes of history change
let cause: string = arg.cause;
}
}
}
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' :getNodeDefaults='getNodeDefaults'>
</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: [{
id: 'label1',
content: 'Start'
}],
shape: {
type: 'Flow',
shape: 'Terminator'
}
}]
const width = "100%";
const height = "350px";
const getNodeDefaults = (node) => {
node.height = 100;
node.width = 100;
node.style.fill = '#6BA5D7';
node.style.strokeColor = 'white';
return node;
}
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' :getNodeDefaults='getNodeDefaults'>
</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: [{
id: 'label1',
content: 'Start'
}],
shape: {
type: 'Flow',
shape: 'Terminator'
}
}]
export default {
name: "App",
components: {
"ejs-diagram": DiagramComponent
},
data() {
return {
width: "100%",
height: "350px",
nodes: nodes,
getNodeDefaults: (node) => {
node.height = 100;
node.width = 100;
node.style.fill = '#6BA5D7';
node.style.strokeColor = 'white';
return node;
},
historyManager: {
stackLimit: 2
}
}
},
provide: {
diagram: [UndoRedo]
}
}
</script>
<style>
@import "../node_modules/@syncfusion/ej2-vue-diagrams/styles/material.css";
</style>
Retain selection
You can retain a selection at undo/redo operation by using the client-side API Method called updateSelection
. Using this method, we can select a diagram objects.
let diagramInstance: DiagramComponent;
ReactDOM.render( < DiagramComponent id = "diagram" ref={diagram => diagramInstance = diagram}
width = {
'100%'
}
height = {
'600px'
}
nodes = {
nodes
}
/>, document.getElementById("diagram") );
// history change event
diagramInstance.updateSelection: (object: NodeModel, diagram: Diagram) => {
let selArr = [];
selArr.push(object)
diagram.select(selArr);
},