React Diagram - Restrict Operations
The Diagram UI component raises the requestEditOperation event every time a user attempts an edit operation. This article contains code samples that demonstrate how to use this event's parameters to prohibit individual edit operations and customize a shape collection in the toolbox and context toolbox.
Refer to the following section for more information on the requestEditOperation event's parameters: Prohibit Individual Operations.
Prohibit Creating Loops
The example below demonstrates how to prevent users from connecting a shape to itself:
jQuery
$(function() {
var diagram = $("#diagram").dxDiagram({
onRequestEditOperation: function(e) {
if (e.operation === "changeConnection")
if (e.args.connector && e.args.connector.fromId === e.args.connector.toId)
e.allowed = false;
},
}).dxDiagram("instance");
});Angular
<dx-diagram #diagram id="diagram" (onRequestEditOperation)="requestEditOperationHandler($event)"> </dx-diagram>
import { Component, ViewChild} from '@angular/core';
import { DxDiagramModule, DxDiagramComponent } from 'devextreme-angular';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
@ViewChild(DxDiagramComponent, { static: false }) diagram: DxDiagramComponent;
requestEditOperationHandler(e) {
if (e.operation === "changeConnection")
if (e.args.connector && e.args.connector.fromId === e.args.connector.toId)
e.allowed = false;
}
}Vue
<template>
<DxDiagram
id="diagram"
ref="diagram"
@request-edit-operation="onRequestEditOperation">
</DxDiagram>
</template>
<script>
import DxDiagram from 'devextreme-vue/diagram';
export default {
components: {
DxDiagram,
},
methods: {
onRequestEditOperation(e) {
if (e.operation === "changeConnection")
if (e.args.connector && e.args.connector.fromId === e.args.connector.toId)
e.allowed = false;
},
}
};
</script>React
import React from 'react';
import Diagram from 'devextreme-react/diagram';
class App extends React.Component {
constructor(props) {
super(props);
this.diagramRef = React.createRef();
this.onRequestEditOperation = this.onRequestEditOperation.bind(this);
}
onRequestEditOperation(e) {
if (e.operation === 'changeConnection')
if (e.args.connector && e.args.connector.fromId === e.args.connector.toId)
e.allowed = false;
}
render() {
return (
<Diagram id="diagram" ref={this.diagramRef} onRequestEditOperation={this.onRequestEditOperation}>
</Diagram>
);
}
}
export default App;Prohibit Adding Shapes Twice
The example below demonstrates how to prevent users from adding more than one shape of each type to a chart:
jQuery
$(function() {
var diagram = $("#diagram").dxDiagram({
onRequestEditOperation(e) {
if (e.operation === 'addShape') {
// Gets types of shapes the chart contains
var itemsTypes = e.component.getItems().filter(function(item) {
return (item.itemType === "shape") && (item.id !== e.args.shape.id);
}).map(a => a.type);
// Cancels the operation if the chart contains a shape with the same type as the shape that is about to be added
if (itemsTypes.indexOf(e.args.shape.type) !== -1) {
e.allowed = false;
return;
}
}
},
}).dxDiagram("instance");
});Angular
<dx-diagram #diagram id="diagram" (onRequestEditOperation)="requestEditOperation($event)"> </dx-diagram>
import { Component, ViewChild} from '@angular/core';
import { DxDiagramModule, DxDiagramComponent } from 'devextreme-angular';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
@ViewChild(DxDiagramComponent, { static: false }) diagram: DxDiagramComponent;
requestEditOperation(e) {
if (e.operation === 'addShape') {
// Gets types of shapes the chart contains
var itemsTypes = e.component.getItems().filter(function(item) {
return (item.itemType === "shape") && (item.id !== e.args.shape.id);
}).map(a => a.type);
// Cancels the operation if the chart contains a shape with the same type as the shape that is about to be added
if (itemsTypes.indexOf(e.args.shape.type) !== -1) {
e.allowed = false;
return;
}
}
}
}Vue
<template>
<DxDiagram
id="diagram"
ref="diagram"
@request-edit-operation="onRequestEditOperation">
</DxDiagram>
</template>
<script>
import DxDiagram from 'devextreme-vue/diagram';
export default {
components: {
DxDiagram
},
methods: {
onRequestEditOperation(e) {
if (e.operation === 'addShape') {
// Gets types of shapes the chart contains
var itemsTypes = e.component.getItems().filter(function(item) {
return (item.itemType === "shape") && (item.id !== e.args.shape.id);
}).map(a => a.type);
// Cancels the operation if the chart contains a shape with the same type as the shape that is about to be added
if (itemsTypes.indexOf(e.args.shape.type) !== -1) {
e.allowed = false;
return;
}
}
},
}
};
</script>React
import React from 'react';
import Diagram, { ContextToolbox,} from 'devextreme-react/diagram';
var currentShapeId;
class App extends React.Component {
constructor(props) {
super(props);
this.diagramRef = React.createRef();
this.onRequestEditOperation = this.onRequestEditOperation.bind(this);
}
onRequestEditOperation(e) {
if (e.operation === 'addShape') {
// Gets types of shapes the chart contains
var itemsTypes = e.component.getItems().filter(function(item) {
return (item.itemType === "shape") && (item.id !== e.args.shape.id);
}).map(a => a.type);
// Cancels the operation if the chart contains a shape with the same type as the shape that is about to be added
if (itemsTypes.indexOf(e.args.shape.type) !== -1) {
e.allowed = false;
return;
}
}
}
render() {
return (
<Diagram id="diagram" ref={this.diagramRef} onRequestEditOperation={this.onRequestEditOperation} >
</Diagram>
);
}
}
export default App;See Also
Remove Shapes from Toolboxes
In the example below, the Diagram component updates the shape collection in the toolbox and context toolbox as follows:
- Removes a shape from these toolboxes after a user adds it to a chart
- Returns a shape to these toolboxes after a user deletes it from a chart
jQuery
$(function () {
var shapeCount = 0;
var diagram = $("#diagram").dxDiagram({
onOptionChanged: function (e) {
// Detects changes of the Diagram model
if (e.name === "hasChanges" && e.value) {
e.component.option("hasChanges", false);
var currentShapeCount = e.component.getItems().filter(function(item) {
return (item.itemType ==="shape")
}).length;
// Updates the toolbox and context toolbox if a shape was added or deleted
if (shapeCount !== currentShapeCount) {
shapeCount = currentShapeCount;
window.setTimeout(function() {
e.component.updateToolbox();
}, 0);
}
}
},
onRequestEditOperation(e) {
if (e.operation === "addShapeFromToolbox") {
e.component.getItems().forEach(function(item) {
// Removes a shape from the toolboxes if the chart contains a shape of this type
if (item.itemType === "shape" && item.type === e.args.shapeType)
e.allowed = false;
});
}
},
})
.dxDiagram("instance");
});Angular
<dx-diagram #diagram id="diagram" (onOptionChanged)="optionChanged($event)" (onRequestEditOperation)="requestEditOperation($event)"> </dx-diagram>
import { Component, ViewChild} from '@angular/core';
import { DxDiagramModule, DxDiagramComponent } from 'devextreme-angular';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
@ViewChild(DxDiagramComponent, { static: false }) diagram: DxDiagramComponent;
shapeCount: any;
optionChanged(e) {
// Detects changes of the Diagram model
if (e.name === "hasChanges" && e.value) {
e.component.option("hasChanges", false);
var currentShapeCount = e.component.getItems().filter(function(item) {
return (item.itemType ==="shape")
}).length;
// Updates the toolbox and context toolbox if a shape was added or deleted
if (this.shapeCount !== currentShapeCount) {
this.shapeCount = currentShapeCount;
window.setTimeout(function() {
e.component.updateToolbox();
}, 0);
}
}
}
requestEditOperation(e) {
if (e.operation === "addShapeFromToolbox") {
e.component.getItems().forEach(function(item) {
// Removes a shape from the toolboxes if the chart contains a shape of this type
if (item.itemType === "shape" && item.type === e.args.shapeType)
e.allowed = false;
});
}
}
}Vue
<template>
<DxDiagram
id="diagram"
ref="diagram"
@request-edit-operation="onRequestEditOperation"
@option-changed="onOptionChanged">
</DxDiagram>
</template>
<script>
import DxDiagram from 'devextreme-vue/diagram';
var shapeCount = 0;
export default {
components: {
DxDiagram
},
methods: {
onOptionChanged(e) {
// Detects changes of the Diagram model
if (e.name === "hasChanges" && e.value) {
e.component.option("hasChanges", false);
var currentShapeCount = e.component.getItems().filter(function(item) {
return (item.itemType ==="shape")
}).length;
// Updates the toolbox and context toolbox if a shape was added or deleted
if (this.shapeCount !== currentShapeCount) {
this.shapeCount = currentShapeCount;
window.setTimeout(function() {
e.component.updateToolbox();
}, 0);
}
}
},
onRequestEditOperation(e) {
if (e.operation === "addShapeFromToolbox") {
e.component.getItems().forEach(function(item) {
// Removes a shape from the toolboxes if the chart contains a shape of this type
if (item.itemType === "shape" && item.type === e.args.shapeType)
e.allowed = false;
});
}
},
}
};
</script>React
import React from 'react';
import Diagram from 'devextreme-react/diagram';
var shapeCount;
class App extends React.Component {
constructor(props) {
super(props);
this.diagramRef = React.createRef();
this.onRequestEditOperation = this.onRequestEditOperation.bind(this);
this.onOptionChanged = this.onOptionChanged.bind(this);
}
onOptionChanged(e) {
// Detects changes of the Diagram model
if (e.name === "hasChanges" && e.value) {
e.component.option("hasChanges", false);
var currentShapeCount = e.component.getItems().filter(function(item) {
return (item.itemType ==="shape")
}).length;
// Updates the toolbox and context toolbox if a shape was added or deleted
if (this.shapeCount !== currentShapeCount) {
this.shapeCount = currentShapeCount;
window.setTimeout(function() {
e.component.updateToolbox();
}, 0);
}
}
}
onRequestEditOperation(e) {
if (e.operation === "addShapeFromToolbox") {
e.component.getItems().forEach(function(item) {
// Removes a shape from the toolboxes if the chart contains a shape of this type
if (item.itemType === "shape" && item.type === e.args.shapeType)
e.allowed = false;
});
}
}
render() {
return (
<Diagram id="diagram" ref={this.diagramRef} onRequestEditOperation={this.onRequestEditOperation} onOptionChanged={this.onOptionChanged}>
</Diagram>
);
}
}
export default App;See Also
Prohibit Moving Shapes Between Containers
The example below demonstrates how to prevent users from moving a shape from one container to another:
jQuery
$(function() {
var containerIds = {};
var diagram = $("#diagram").dxDiagram({
onRequestEditOperation: function(e) {
if (e.operation === "moveShape")
// Cancels the operation if a user moves a shape outside its container.
if (containerIds[e.args.shape.id] !== e.args.shape.containerId)
e.allowed = false;
},
onSelectionChanged: function(e) {
e.component.getItems().forEach(item => {containerIds[item.id] = item.containerId;});
},
}).dxDiagram("instance");
});Angular
<dx-diagram #diagram id="diagram" (onSelectionChanged)="selectionChanged($event)" (onRequestEditOperation)="requestEditOperation($event)"> </dx-diagram>
import { Component, ViewChild} from '@angular/core';
import { DxDiagramModule, DxDiagramComponent } from 'devextreme-angular';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
@ViewChild(DxDiagramComponent, { static: false }) diagram: DxDiagramComponent;
containerIds: any = {};
requestEditOperation(e) {
if (e.operation === "moveShape")
// Cancels the operation if a user moves a shape outside its container.
if (this.containerIds[e.args.shape.id] !== e.args.shape.containerId)
e.allowed = false;
}
selectionChanged(e) {
e.component.getItems().forEach(item => {
this.containerIds[item.id] = item.containerId;
});
}
}Vue
<template>
<DxDiagram
id="diagram"
ref="diagram"
@request-edit-operation="onRequestEditOperation"
@selection-changed="onSelectionChanged">
</DxDiagram>
</template>
<script>
import DxDiagram from 'devextreme-vue/diagram';
var containerIds = {};
export default {
components: {
DxDiagram
},
methods: {
onRequestEditOperation(e) {
if (e.operation === "moveShape")
// Cancels the operation if a user moves a shape outside its container.
if (containerIds[e.args.shape.id] !== e.args.shape.containerId)
e.allowed = false;
},
onSelectionChanged(e) {
e.component.getItems().forEach(item => {containerIds[item.id] = item.containerId;});
}
},
};
</script>React
import React from 'react';
import Diagram, from 'devextreme-react/diagram';
var containerIds = {};
class App extends React.Component {
constructor(props) {
super(props);
this.diagramRef = React.createRef();
this.onRequestEditOperation = this.onRequestEditOperation.bind(this);
this.onSelectionChanged = this.onSelectionChanged.bind(this);
}
onRequestEditOperation(e) {
if (e.operation === "moveShape")
// Cancels the operation if a user moves a shape outside its container.
if (containerIds[e.args.shape.id] !== e.args.shape.containerId)
e.allowed = false;
}
onSelectionChanged(e) {
e.component.getItems().forEach(item => {containerIds[item.id] = item.containerId;});
}
render() {
return (
<Diagram id="diagram" ref={this.diagramRef} onRequestEditOperation={this.onRequestEditOperation} onSelectionChanged={this.onSelectionChanged}>
</Diagram>
);
}
}
export default App;Customize Shape Collection in the Context Toolbox
The following example demonstrates how to hide shapes in the context toolbox depending on the connector's start node type:
jQuery
$(function() {
var currentShapeId;
var diagram = $("#diagram").dxDiagram({
onRequestEditOperation: function(e) {
if (e.operation === "changeConnection" && e.args.connector)
// Gets the connector's start node identifier
this.currentShapeId = e.args.connector.fromId;
if (e.operation === "addShapeFromToolbox") {
// Gets the connector's start node type
var currentShape = e.component.getItemById(this.currentShapeId);
if (e.args.shapeType === "terminator")
// If the connector's start node type is "decision"
if (currentShape && currentShape.type === "decision")
// Hides the "terminator" shape in the context toolbox
e.allowed = false;
}
},
contextToolbox: {
enabled: true,
shapeIconsPerRow: 3,
shapes: [ "process", "decision", "terminator" ],
},
})
.dxDiagram("instance");
});Angular
<dx-diagram #diagram id="diagram" (onRequestEditOperation)="requestEditOperation($event)">
<dxo-context-toolbox [enabled]="true" [shapes]='["process", "decision", "terminator"]' [shapeIconsPerRow]="3">
</dxo-context-toolbox>
</dx-diagram>
import { Component, ViewChild} from '@angular/core';
import { DxDiagramModule, DxDiagramComponent } from 'devextreme-angular';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
@ViewChild(DxDiagramComponent, { static: false }) diagram: DxDiagramComponent;
currentShapeId : number;
requestEditOperation(e) {
if (e.operation === "changeConnection" && e.args.connector)
// Gets the connector's start node identifier
this.currentShapeId = e.args.connector.fromId;
if (e.operation === "addShapeFromToolbox") {
// Gets the connector's start node type
var currentShape = e.component.getItemById(this.currentShapeId);
if (e.args.shapeType === "terminator")
// If the connector's start node type is "decision"
if (currentShape && currentShape.type === "decision")
// Hides the "terminator" shape in the context toolbox
e.allowed = false;
}
}
}Vue
<template>
<DxDiagram
id="diagram"
ref="diagram"
@request-edit-operation="onRequestEditOperation">
<DxContextToolbox
:shape-icons-per-row="3"
:width="100"
:shapes="['process', 'decision', 'terminator']"
/>
</DxDiagram>
</template>
<script>
import { DxDiagram, DxContextToolbox} from 'devextreme-vue/diagram';
var currentShapeId;
export default {
components: {
DxDiagram, DxContextToolbox
},
methods: {
onRequestEditOperation(e) {
if (e.operation === "changeConnection" && e.args.connector)
// Gets the connector's start node identifier
this.currentShapeId = e.args.connector.fromId;
if (e.operation === "addShapeFromToolbox") {
// Gets the connector's start node type
var currentShape = e.component.getItemById(this.currentShapeId);
if (e.args.shapeType === "terminator")
// If the connector's start node type is "decision"
if (currentShape && currentShape.type === "decision")
// Hides the "terminator" shape in the context toolbox
e.allowed = false;
}
}
}
};
</script>React
import React from 'react';
import Diagram, { ContextToolbox,} from 'devextreme-react/diagram';
var currentShapeId;
class App extends React.Component {
constructor(props) {
super(props);
this.diagramRef = React.createRef();
this.onRequestEditOperation = this.onRequestEditOperation.bind(this);
}
onRequestEditOperation(e) {
if (e.operation === 'changeConnection' && e.args.connector)
// Gets the connector's start node identifier
this.currentShapeId = e.args.connector.fromId;
if (e.operation === 'addShapeFromToolbox') {
// Gets the connector's start node type
var currentShape = e.component.getItemById(this.currentShapeId);
if (e.args.shapeType === 'terminator')
// If the connector's start node type is "decision"
if (currentShape && currentShape.type === 'decision')
// Hides the "terminator" shape in the context toolbox
e.allowed = false;
}
}
render() {
return (
<Diagram id="diagram" ref={this.diagramRef} onRequestEditOperation={this.onRequestEditOperation} >
<ContextToolbox shapeIconsPerRow={3} width={100} shapes={['process', 'decision', 'terminator']}>
</ContextToolbox>
</Diagram>
);
}
}
export default App;If you have technical questions, please create a support ticket in the DevExpress Support Center.