JavaScript/jQuery 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
app.component.html
app.component.ts
<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
app.component.html
app.component.ts
<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
app.component.html
app.component.ts
<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
app.component.html
app.component.ts
<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
app.component.html
app.component.ts
<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;