mirror of
https://github.com/sartography/bpmn-js.git
synced 2025-02-03 20:53:25 +00:00
parent
6bbedc47d1
commit
ddc10154c9
247
lib/features/modeling/behavior/SubProcessPlaneBehavior.js
Normal file
247
lib/features/modeling/behavior/SubProcessPlaneBehavior.js
Normal file
@ -0,0 +1,247 @@
|
||||
import inherits from 'inherits';
|
||||
|
||||
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
|
||||
|
||||
import { find } from 'min-dash';
|
||||
import { isExpanded } from '../../../util/DiUtil';
|
||||
import { getBusinessObject, is } from '../../../util/ModelUtil';
|
||||
import { getMid } from 'diagram-js/lib/layout/LayoutUtil';
|
||||
import { getBBox } from 'diagram-js/lib/util/Elements';
|
||||
import { asPlaneId, planeId } from '../../../util/DrilldownUtil';
|
||||
|
||||
|
||||
/**
|
||||
* Creates diPlanes and canvas planes when collapsed subprocesses are created.
|
||||
*
|
||||
*
|
||||
* @param {Canvas} canvas
|
||||
* @param {EventBus} eventBus
|
||||
* @param {Modeling} modeling
|
||||
* @param {ElementFactory} elementFactory
|
||||
* @param {BpmnFactory} bpmnFactory
|
||||
* @param {Bpmnjs} bpmnjs
|
||||
* @param {ElementRegistry} elementRegistry
|
||||
*/
|
||||
export default function SubProcessPlaneBehavior(
|
||||
canvas, eventBus, modeling,
|
||||
elementFactory, bpmnFactory, bpmnjs, elementRegistry) {
|
||||
|
||||
CommandInterceptor.call(this, eventBus);
|
||||
|
||||
this._canvas = canvas;
|
||||
this._eventBus = eventBus;
|
||||
this._modeling = modeling;
|
||||
this._elementFactory = elementFactory;
|
||||
this._bpmnFactory = bpmnFactory;
|
||||
this._bpmnjs = bpmnjs;
|
||||
this._elementRegistry = elementRegistry;
|
||||
|
||||
var self = this;
|
||||
|
||||
function isCollapsedSubProcess(element) {
|
||||
return is(element, 'bpmn:SubProcess') && !isExpanded(element);
|
||||
}
|
||||
|
||||
function createRoot(context) {
|
||||
var shape = context.shape,
|
||||
rootElement = context.newRootElement;
|
||||
|
||||
var businessObject = getBusinessObject(shape);
|
||||
|
||||
rootElement = self._addDiagram(rootElement || businessObject);
|
||||
|
||||
context.newRootElement = canvas.addRootElement(rootElement);
|
||||
}
|
||||
|
||||
function removeRoot(context) {
|
||||
var shape = context.shape;
|
||||
|
||||
var businessObject = getBusinessObject(shape);
|
||||
self._removeDiagram(businessObject);
|
||||
|
||||
var rootElement = context.newRootElement = elementRegistry.get(planeId(businessObject));
|
||||
|
||||
canvas.removeRootElement(rootElement);
|
||||
}
|
||||
|
||||
// add plane elements for newly created sub-processes
|
||||
// this ensures we can actually drill down into the element
|
||||
this.executed('shape.create', function(context) {
|
||||
var shape = context.shape;
|
||||
if (!isCollapsedSubProcess(shape)) {
|
||||
return;
|
||||
}
|
||||
|
||||
createRoot(context);
|
||||
}, true);
|
||||
|
||||
|
||||
this.reverted('shape.create', function(context) {
|
||||
var shape = context.shape;
|
||||
if (!isCollapsedSubProcess(shape)) {
|
||||
return;
|
||||
}
|
||||
|
||||
removeRoot(context);
|
||||
}, true);
|
||||
|
||||
|
||||
// rename secondary elements (roots) when the primary element changes
|
||||
// this ensures rootElement.id = element.id + '_plane'
|
||||
this.executed('element.updateProperties', function(context) {
|
||||
var shape = context.element;
|
||||
|
||||
if (!isCollapsedSubProcess(shape)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var properties = context.properties;
|
||||
var oldProperties = context.oldProperties;
|
||||
|
||||
var oldId = oldProperties.id,
|
||||
newId = properties.id;
|
||||
|
||||
if (oldId === newId) {
|
||||
return;
|
||||
}
|
||||
|
||||
var planeElement = elementRegistry.get(asPlaneId(oldId));
|
||||
|
||||
if (!planeElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
elementRegistry.updateId(planeElement, asPlaneId(newId));
|
||||
}, true);
|
||||
|
||||
|
||||
this.reverted('element.updateProperties', function(context) {
|
||||
var shape = context.element;
|
||||
|
||||
if (!isCollapsedSubProcess(shape)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var properties = context.properties;
|
||||
var oldProperties = context.oldProperties;
|
||||
|
||||
var oldId = oldProperties.id,
|
||||
newId = properties.id;
|
||||
|
||||
if (oldId === newId) {
|
||||
return;
|
||||
}
|
||||
|
||||
var planeElement = elementRegistry.get(asPlaneId(newId));
|
||||
|
||||
if (!planeElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
elementRegistry.updateId(planeElement, asPlaneId(oldId));
|
||||
}, true);
|
||||
}
|
||||
|
||||
inherits(SubProcessPlaneBehavior, CommandInterceptor);
|
||||
|
||||
|
||||
/**
|
||||
* Adds a given diagram to the definitions and returns a .
|
||||
*
|
||||
* @param {Object} planeElement
|
||||
*/
|
||||
SubProcessPlaneBehavior.prototype._addDiagram = function(planeElement) {
|
||||
var bpmnjs = this._bpmnjs;
|
||||
var diagrams = bpmnjs.getDefinitions().diagrams;
|
||||
|
||||
if (!planeElement.businessObject) {
|
||||
planeElement = this._createNewDiagram(planeElement);
|
||||
}
|
||||
|
||||
diagrams.push(planeElement.di.$parent);
|
||||
|
||||
return planeElement;
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a given rootElement to the bpmnDi diagrams.
|
||||
*
|
||||
* @param {Object} rootElement
|
||||
* @returns {Object} planeElement
|
||||
*/
|
||||
SubProcessPlaneBehavior.prototype._addDiagram = function(planeElement) {
|
||||
var bpmnjs = this._bpmnjs;
|
||||
var diagrams = bpmnjs.getDefinitions().diagrams;
|
||||
|
||||
if (!planeElement.businessObject) {
|
||||
planeElement = this._createNewDiagram(planeElement);
|
||||
}
|
||||
|
||||
diagrams.push(planeElement.di.$parent);
|
||||
|
||||
return planeElement;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new plane element for the given sub process.
|
||||
*
|
||||
* @param {Object} bpmnElement
|
||||
*
|
||||
* @return {Object} new diagram element
|
||||
*/
|
||||
SubProcessPlaneBehavior.prototype._createNewDiagram = function(bpmnElement) {
|
||||
var bpmnFactory = this._bpmnFactory;
|
||||
var elementFactory = this._elementFactory;
|
||||
|
||||
var diPlane = bpmnFactory.create('bpmndi:BPMNPlane', {
|
||||
bpmnElement: bpmnElement
|
||||
});
|
||||
var diDiagram = bpmnFactory.create('bpmndi:BPMNDiagram', {
|
||||
plane: diPlane
|
||||
});
|
||||
diPlane.$parent = diDiagram;
|
||||
|
||||
// add a virtual element (not being drawn),
|
||||
// a copy cat of our BpmnImporter code
|
||||
var planeElement = elementFactory.createRoot({
|
||||
id: planeId(bpmnElement),
|
||||
type: bpmnElement.$type,
|
||||
di: diPlane,
|
||||
businessObject: bpmnElement,
|
||||
collapsed: true
|
||||
});
|
||||
|
||||
return planeElement;
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes the diagram for a given root element
|
||||
*
|
||||
* @param {Object} rootElement
|
||||
* @returns {Object} removed bpmndi:BPMNDiagram
|
||||
*/
|
||||
SubProcessPlaneBehavior.prototype._removeDiagram = function(rootElement) {
|
||||
var bpmnjs = this._bpmnjs;
|
||||
|
||||
var diagrams = bpmnjs.getDefinitions().diagrams;
|
||||
|
||||
var removedDiagram = find(diagrams, function(diagram) {
|
||||
return diagram.plane.bpmnElement.id === rootElement.id;
|
||||
});
|
||||
|
||||
diagrams.splice(diagrams.indexOf(removedDiagram), 1);
|
||||
|
||||
return removedDiagram;
|
||||
};
|
||||
|
||||
|
||||
SubProcessPlaneBehavior.$inject = [
|
||||
'canvas',
|
||||
'eventBus',
|
||||
'modeling',
|
||||
'elementFactory',
|
||||
'bpmnFactory',
|
||||
'bpmnjs',
|
||||
'elementRegistry'
|
||||
];
|
@ -28,6 +28,7 @@ import ResizeLaneBehavior from './ResizeLaneBehavior';
|
||||
import RemoveElementBehavior from './RemoveElementBehavior';
|
||||
import SpaceToolBehavior from './SpaceToolBehavior';
|
||||
import SubProcessStartEventBehavior from './SubProcessStartEventBehavior';
|
||||
import SubProcessPlaneBehavior from './SubProcessPlaneBehavior';
|
||||
import ToggleElementCollapseBehaviour from './ToggleElementCollapseBehaviour';
|
||||
import UnclaimIdBehavior from './UnclaimIdBehavior';
|
||||
import UpdateFlowNodeRefsBehavior from './UpdateFlowNodeRefsBehavior';
|
||||
@ -66,6 +67,7 @@ export default {
|
||||
'toggleElementCollapseBehaviour',
|
||||
'spaceToolBehavior',
|
||||
'subProcessStartEventBehavior',
|
||||
'subProcessPlaneBehavior',
|
||||
'unclaimIdBehavior',
|
||||
'unsetDefaultFlowBehavior',
|
||||
'updateFlowNodeRefsBehavior'
|
||||
@ -101,6 +103,7 @@ export default {
|
||||
toggleElementCollapseBehaviour : [ 'type', ToggleElementCollapseBehaviour ],
|
||||
spaceToolBehavior: [ 'type', SpaceToolBehavior ],
|
||||
subProcessStartEventBehavior: [ 'type', SubProcessStartEventBehavior ],
|
||||
subProcessPlaneBehavior: [ 'type', SubProcessPlaneBehavior ],
|
||||
unclaimIdBehavior: [ 'type', UnclaimIdBehavior ],
|
||||
updateFlowNodeRefsBehavior: [ 'type', UpdateFlowNodeRefsBehavior ],
|
||||
unsetDefaultFlowBehavior: [ 'type', UnsetDefaultFlowBehavior ]
|
||||
|
29
lib/util/DrilldownUtil.js
Normal file
29
lib/util/DrilldownUtil.js
Normal file
@ -0,0 +1,29 @@
|
||||
import { is } from './ModelUtil';
|
||||
|
||||
|
||||
export var planePostfix = '_plane';
|
||||
|
||||
/**
|
||||
* Returns the ID of the plane associated with an element.
|
||||
*
|
||||
* @param {djs.model.Base|ModdleElement} element
|
||||
* @returns {String} id of the associated plane
|
||||
*/
|
||||
export function planeId(element) {
|
||||
if (is(element, 'bpmn:SubProcess')) {
|
||||
return element.id + planePostfix;
|
||||
}
|
||||
|
||||
return element.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns returns the plane ID for a given ID, as if it was associated with a
|
||||
* subprocess.
|
||||
*
|
||||
* @param {String} shape ID
|
||||
* @returns
|
||||
*/
|
||||
export function asPlaneId(string) {
|
||||
return string + planePostfix;
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" id="Definitions_007va6i" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.2.0-dev">
|
||||
<bpmn:process id="Process_1giw3j5" isExecutable="true">
|
||||
<bpmn:task id="Task_1" />
|
||||
<bpmn:subProcess id="SubProcess_1" />
|
||||
<bpmn:subProcess id="SubProcess_2" name="Collapsed Nested">
|
||||
<bpmn:subProcess id="Activity_0xa1lp6">
|
||||
<bpmn:task id="nested_task" />
|
||||
</bpmn:subProcess>
|
||||
</bpmn:subProcess>
|
||||
</bpmn:process>
|
||||
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
||||
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1giw3j5">
|
||||
<bpmndi:BPMNShape id="Task_07xra8r_di" bpmnElement="Task_1">
|
||||
<dc:Bounds x="156" y="81" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="SubProcess_01nq2r1_di" bpmnElement="SubProcess_1" isExpanded="true">
|
||||
<dc:Bounds x="160" y="280" width="350" height="200" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_1t6m7ms_di" bpmnElement="SubProcess_2">
|
||||
<dc:Bounds x="310" y="81" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
</bpmndi:BPMNPlane>
|
||||
</bpmndi:BPMNDiagram>
|
||||
<bpmndi:BPMNDiagram id="BPMNDiagram_1pythet">
|
||||
<bpmndi:BPMNPlane id="BPMNPlane_0bbk4a7" bpmnElement="SubProcess_2">
|
||||
<bpmndi:BPMNShape id="Activity_0dirtvd_di" bpmnElement="Activity_0xa1lp6">
|
||||
<dc:Bounds x="150" y="120" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
</bpmndi:BPMNPlane>
|
||||
</bpmndi:BPMNDiagram>
|
||||
<bpmndi:BPMNDiagram id="BPMNDiagram_0rferxm">
|
||||
<bpmndi:BPMNPlane id="BPMNPlane_0nr9ud3" bpmnElement="Activity_0xa1lp6">
|
||||
<bpmndi:BPMNShape id="nested_task_di" bpmnElement="nested_task">
|
||||
<dc:Bounds x="140" y="110" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
</bpmndi:BPMNPlane>
|
||||
</bpmndi:BPMNDiagram>
|
||||
</bpmn:definitions>
|
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" id="Definitions_007va6i" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.2.0-dev">
|
||||
<bpmn:process id="Process_1giw3j5" isExecutable="true">
|
||||
<bpmn:task id="Task_1" />
|
||||
<bpmn:subProcess id="SubProcess_1" />
|
||||
</bpmn:process>
|
||||
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
||||
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1giw3j5">
|
||||
<bpmndi:BPMNShape id="Task_07xra8r_di" bpmnElement="Task_1">
|
||||
<dc:Bounds x="156" y="81" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="SubProcess_01nq2r1_di" bpmnElement="SubProcess_1" isExpanded="true">
|
||||
<dc:Bounds x="160" y="280" width="350" height="200" />
|
||||
</bpmndi:BPMNShape>
|
||||
</bpmndi:BPMNPlane>
|
||||
</bpmndi:BPMNDiagram>
|
||||
</bpmn:definitions>
|
@ -0,0 +1,209 @@
|
||||
import {
|
||||
bootstrapModeler,
|
||||
inject
|
||||
} from 'test/TestHelper';
|
||||
|
||||
import coreModule from 'lib/core';
|
||||
import modelingModule from 'lib/features/modeling';
|
||||
import replaceModule from 'lib/features/replace';
|
||||
import { is } from 'lib/util/ModelUtil';
|
||||
|
||||
describe('features/modeling/behavior - subprocess planes', function() {
|
||||
|
||||
var diagramXML = require('./SubProcessBehavior.planes.bpmn');
|
||||
|
||||
beforeEach(bootstrapModeler(diagramXML, {
|
||||
modules: [
|
||||
coreModule,
|
||||
modelingModule,
|
||||
replaceModule
|
||||
]
|
||||
}));
|
||||
|
||||
|
||||
describe('create', function() {
|
||||
|
||||
it('should create new diagram for collapsed subprocess', inject(function(elementFactory, modeling, canvas, bpmnjs) {
|
||||
|
||||
// given
|
||||
var subProcess = elementFactory.createShape({
|
||||
type: 'bpmn:SubProcess',
|
||||
isExpanded: false
|
||||
});
|
||||
|
||||
// when
|
||||
modeling.createShape(subProcess, { x: 300, y: 300 }, canvas.getRootElement());
|
||||
|
||||
// then
|
||||
var diagrams = bpmnjs.getDefinitions().diagrams;
|
||||
expect(diagrams.length).to.equal(2);
|
||||
expect(canvas.findRoot(planeId(subProcess))).to.exist;
|
||||
}));
|
||||
|
||||
|
||||
it('should not create new plane for expanded subprocess', inject(function(elementFactory, modeling, canvas, bpmnjs) {
|
||||
|
||||
// given
|
||||
var subProcess = elementFactory.createShape({
|
||||
type: 'bpmn:SubProcess',
|
||||
isExpanded: true
|
||||
});
|
||||
|
||||
// when
|
||||
modeling.createShape(subProcess, { x: 300, y: 300 }, canvas.getRootElement());
|
||||
|
||||
// then
|
||||
var diagrams = bpmnjs.getDefinitions().diagrams;
|
||||
expect(diagrams.length).to.equal(1);
|
||||
expect(canvas.findRoot(planeId(subProcess))).to.not.exist;
|
||||
}));
|
||||
|
||||
|
||||
it('should undo', inject(function(elementFactory, modeling, commandStack, canvas, bpmnjs) {
|
||||
|
||||
// given
|
||||
var subProcess = elementFactory.createShape({
|
||||
type: 'bpmn:SubProcess',
|
||||
isExpanded: false
|
||||
});
|
||||
modeling.createShape(subProcess, { x: 300, y: 300 }, canvas.getRootElement());
|
||||
|
||||
// when
|
||||
commandStack.undo();
|
||||
|
||||
// then
|
||||
var diagrams = bpmnjs.getDefinitions().diagrams;
|
||||
expect(diagrams.length).to.equal(1);
|
||||
expect(canvas.findRoot(planeId(subProcess))).to.not.exist;
|
||||
}));
|
||||
|
||||
|
||||
it('should redo', inject(function(elementFactory, modeling, commandStack, canvas, bpmnjs) {
|
||||
|
||||
// given
|
||||
var subProcess = elementFactory.createShape({
|
||||
type: 'bpmn:SubProcess',
|
||||
isExpanded: false
|
||||
});
|
||||
modeling.createShape(subProcess, { x: 300, y: 300 }, canvas.getRootElement());
|
||||
var plane = canvas.findRoot(planeId(subProcess));
|
||||
|
||||
// when
|
||||
commandStack.undo();
|
||||
commandStack.redo();
|
||||
|
||||
// then
|
||||
var diagrams = bpmnjs.getDefinitions().diagrams;
|
||||
expect(diagrams.length).to.equal(2);
|
||||
expect(canvas.findRoot(planeId(subProcess))).to.exist;
|
||||
expect(canvas.findRoot(planeId(subProcess))).to.equal(plane);
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
|
||||
describe('replace', function() {
|
||||
|
||||
describe('task -> collapsed subprocess', function() {
|
||||
|
||||
it('should add new diagram for collapsed subprocess', inject(
|
||||
function(elementRegistry, bpmnReplace, bpmnjs, canvas) {
|
||||
|
||||
// given
|
||||
var task = elementRegistry.get('Task_1'),
|
||||
collapsedSubProcess;
|
||||
|
||||
// when
|
||||
collapsedSubProcess = bpmnReplace.replaceElement(task, {
|
||||
type: 'bpmn:SubProcess',
|
||||
isExpanded: false
|
||||
});
|
||||
|
||||
// then
|
||||
var diagrams = bpmnjs.getDefinitions().diagrams;
|
||||
expect(diagrams.length).to.equal(2);
|
||||
expect(canvas.findRoot(planeId(collapsedSubProcess))).to.exist;
|
||||
}
|
||||
));
|
||||
|
||||
|
||||
it('should undo', inject(
|
||||
function(elementRegistry, bpmnReplace, bpmnjs, canvas, commandStack) {
|
||||
|
||||
// given
|
||||
var task = elementRegistry.get('Task_1'),
|
||||
collapsedSubProcess = bpmnReplace.replaceElement(task, {
|
||||
type: 'bpmn:SubProcess',
|
||||
isExpanded: false
|
||||
});
|
||||
|
||||
// when
|
||||
commandStack.undo();
|
||||
|
||||
|
||||
// then
|
||||
var diagrams = bpmnjs.getDefinitions().diagrams;
|
||||
expect(diagrams.length).to.equal(1);
|
||||
expect(canvas.findRoot(planeId(collapsedSubProcess))).to.not.exist;
|
||||
}
|
||||
));
|
||||
|
||||
|
||||
it('should redo', inject(
|
||||
function(elementRegistry, bpmnReplace, bpmnjs, canvas, commandStack) {
|
||||
|
||||
// given
|
||||
var task = elementRegistry.get('Task_1'),
|
||||
collapsedSubProcess = bpmnReplace.replaceElement(task, {
|
||||
type: 'bpmn:SubProcess',
|
||||
isExpanded: false
|
||||
});
|
||||
|
||||
// when
|
||||
commandStack.undo();
|
||||
commandStack.redo();
|
||||
|
||||
// then
|
||||
var diagrams = bpmnjs.getDefinitions().diagrams;
|
||||
expect(diagrams.length).to.equal(2);
|
||||
expect(canvas.findRoot(planeId(collapsedSubProcess))).to.exist;
|
||||
}
|
||||
));
|
||||
});
|
||||
|
||||
describe('task -> expanded subprocess', function() {
|
||||
|
||||
it('should not add new diagram for collapsed subprocess', inject(
|
||||
function(elementRegistry, bpmnReplace, bpmnjs, canvas) {
|
||||
|
||||
// given
|
||||
var task = elementRegistry.get('Task_1'),
|
||||
collapsedSubProcess;
|
||||
|
||||
// when
|
||||
collapsedSubProcess = bpmnReplace.replaceElement(task, {
|
||||
type: 'bpmn:SubProcess',
|
||||
isExpanded: true
|
||||
});
|
||||
|
||||
// then
|
||||
var diagrams = bpmnjs.getDefinitions().diagrams;
|
||||
expect(diagrams.length).to.equal(1);
|
||||
expect(canvas.findRoot(planeId(collapsedSubProcess))).to.not.exist;
|
||||
}
|
||||
));
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
function planeId(element) {
|
||||
if (is(element, 'bpmn:SubProcess')) {
|
||||
return element.id + '_plane';
|
||||
}
|
||||
|
||||
return element.id;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user