mirror of
https://github.com/sartography/bpmn-js.git
synced 2025-01-12 10:04:16 +00:00
feat(rules): support attach from context-menu
* take target attach and parent hints on `connection.create` rule into account to implement create from context-menu Related to bpmn-io/diagram-js#242 Closes #742
This commit is contained in:
parent
11354e951c
commit
367399d509
@ -22,6 +22,7 @@ var RuleProvider = require('diagram-js/lib/features/rules/RuleProvider');
|
|||||||
|
|
||||||
var isBoundaryAttachment = require('../snapping/BpmnSnappingUtil').getBoundaryAttachment;
|
var isBoundaryAttachment = require('../snapping/BpmnSnappingUtil').getBoundaryAttachment;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* BPMN specific modeling rule
|
* BPMN specific modeling rule
|
||||||
*/
|
*/
|
||||||
@ -39,9 +40,32 @@ BpmnRules.prototype.init = function() {
|
|||||||
|
|
||||||
this.addRule('connection.create', function(context) {
|
this.addRule('connection.create', function(context) {
|
||||||
var source = context.source,
|
var source = context.source,
|
||||||
target = context.target;
|
target = context.target,
|
||||||
|
hints = context.hints || {},
|
||||||
|
targetParent = hints.targetParent,
|
||||||
|
targetAttach = hints.targetAttach;
|
||||||
|
|
||||||
|
// don't allow incoming connections on
|
||||||
|
// newly created boundary events
|
||||||
|
// to boundary events
|
||||||
|
if (targetAttach) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// temporarily set target parent for scoping
|
||||||
|
// checks to work
|
||||||
|
if (targetParent) {
|
||||||
|
target.parent = targetParent;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
return canConnect(source, target);
|
return canConnect(source, target);
|
||||||
|
} finally {
|
||||||
|
// unset temporary target parent
|
||||||
|
if (targetParent) {
|
||||||
|
target.parent = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.addRule('connection.reconnectStart', function(context) {
|
this.addRule('connection.reconnectStart', function(context) {
|
||||||
@ -87,13 +111,23 @@ BpmnRules.prototype.init = function() {
|
|||||||
canInsert(shapes, target, position);
|
canInsert(shapes, target, position);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.addRule([ 'shape.create', 'shape.append' ], function(context) {
|
this.addRule('shape.create', function(context) {
|
||||||
var target = context.target,
|
return canCreate(
|
||||||
shape = context.shape,
|
context.shape,
|
||||||
source = context.source,
|
context.target,
|
||||||
position = context.position;
|
context.source,
|
||||||
|
context.position
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
return canAttach([ shape ], target, source, position) || canCreate(shape, target, source, position);
|
this.addRule('shape.attach', function(context) {
|
||||||
|
|
||||||
|
return canAttach(
|
||||||
|
context.shape,
|
||||||
|
context.target,
|
||||||
|
null,
|
||||||
|
context.position
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.addRule('element.copy', function(context) {
|
this.addRule('element.copy', function(context) {
|
||||||
@ -173,17 +207,16 @@ function isSame(a, b) {
|
|||||||
|
|
||||||
function getOrganizationalParent(element) {
|
function getOrganizationalParent(element) {
|
||||||
|
|
||||||
var bo = getBusinessObject(element);
|
do {
|
||||||
|
if (is(element, 'bpmn:Process')) {
|
||||||
while (bo && !is(bo, 'bpmn:Process')) {
|
return getBusinessObject(element);
|
||||||
if (is(bo, 'bpmn:Participant')) {
|
|
||||||
return bo.processRef || bo;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bo = bo.$parent;
|
if (is(element, 'bpmn:Participant')) {
|
||||||
|
return getBusinessObject(element).processRef;
|
||||||
}
|
}
|
||||||
|
} while ((element = element.parent));
|
||||||
|
|
||||||
return bo;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function isTextAnnotation(element) {
|
function isTextAnnotation(element) {
|
||||||
@ -228,21 +261,20 @@ function isMessageFlowTarget(element) {
|
|||||||
|
|
||||||
function getScopeParent(element) {
|
function getScopeParent(element) {
|
||||||
|
|
||||||
var bo = getBusinessObject(element);
|
var parent = element;
|
||||||
|
|
||||||
|
while ((parent = parent.parent)) {
|
||||||
|
|
||||||
|
if (is(parent, 'bpmn:FlowElementsContainer')) {
|
||||||
|
return getBusinessObject(parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is(parent, 'bpmn:Participant')) {
|
||||||
|
return getBusinessObject(parent).processRef;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (is(bo, 'bpmn:Participant')) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
|
||||||
|
|
||||||
while (bo) {
|
|
||||||
bo = bo.$parent;
|
|
||||||
|
|
||||||
if (is(bo, 'bpmn:FlowElementsContainer')) {
|
|
||||||
return bo;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return bo;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function isSameScope(a, b) {
|
function isSameScope(a, b) {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var TestHelper = require('../../../TestHelper');
|
var getBpmnJS = require('../../../TestHelper').getBpmnJS;
|
||||||
|
|
||||||
var TestContainer = require('mocha-test-container-support');
|
var TestContainer = require('mocha-test-container-support');
|
||||||
|
|
||||||
@ -171,7 +171,7 @@ describe('features - context-pad', function() {
|
|||||||
|
|
||||||
function expectContextPadEntries(elementOrId, expectedEntries) {
|
function expectContextPadEntries(elementOrId, expectedEntries) {
|
||||||
|
|
||||||
TestHelper.getBpmnJS().invoke(function(elementRegistry, contextPad) {
|
getBpmnJS().invoke(function(elementRegistry, contextPad) {
|
||||||
|
|
||||||
var element = typeof elementOrId === 'string' ? elementRegistry.get(elementOrId) : elementOrId;
|
var element = typeof elementOrId === 'string' ? elementRegistry.get(elementOrId) : elementOrId;
|
||||||
|
|
||||||
@ -279,11 +279,68 @@ describe('features - context-pad', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
describe('create', function() {
|
||||||
|
|
||||||
|
var diagramXML = require('../../../fixtures/bpmn/simple.bpmn');
|
||||||
|
|
||||||
|
beforeEach(bootstrapModeler(diagramXML, {
|
||||||
|
modules: testModules
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
it('should attach boundary event', inject(function(dragging, contextPad, elementRegistry) {
|
||||||
|
|
||||||
|
// given
|
||||||
|
var task = elementRegistry.get('Task_1');
|
||||||
|
|
||||||
|
// when
|
||||||
|
contextPad.open(task);
|
||||||
|
|
||||||
|
contextPad.trigger('dragstart', padEvent('append.intermediate-event'));
|
||||||
|
|
||||||
|
dragging.move(canvasEvent({ x: task.x, y: task.y }));
|
||||||
|
dragging.hover({ element: task });
|
||||||
|
dragging.move(canvasEvent({ x: task.x + 80, y: task.y + 70 }));
|
||||||
|
dragging.end();
|
||||||
|
|
||||||
|
// then
|
||||||
|
expect(task.attachers).to.have.length(1);
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
it('should attach boundary event to other target', inject(
|
||||||
|
function(dragging, contextPad, elementRegistry) {
|
||||||
|
|
||||||
|
// given
|
||||||
|
var task = elementRegistry.get('Task_1');
|
||||||
|
|
||||||
|
var subProcess = elementRegistry.get('SubProcess_1');
|
||||||
|
|
||||||
|
// when
|
||||||
|
contextPad.open(task);
|
||||||
|
|
||||||
|
contextPad.trigger('dragstart', padEvent('append.intermediate-event'));
|
||||||
|
|
||||||
|
dragging.move(canvasEvent({ x: subProcess.x, y: subProcess.y }));
|
||||||
|
dragging.hover({ element: subProcess });
|
||||||
|
dragging.move(canvasEvent({ x: subProcess.x + 80, y: subProcess.y + 5 }));
|
||||||
|
dragging.end();
|
||||||
|
|
||||||
|
// then
|
||||||
|
expect(subProcess.attachers).to.have.length(1);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
describe('replace', function() {
|
describe('replace', function() {
|
||||||
|
|
||||||
var diagramXML = require('../../../fixtures/bpmn/simple.bpmn');
|
var diagramXML = require('../../../fixtures/bpmn/simple.bpmn');
|
||||||
|
|
||||||
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
|
beforeEach(bootstrapModeler(diagramXML, {
|
||||||
|
modules: testModules
|
||||||
|
}));
|
||||||
|
|
||||||
var container;
|
var container;
|
||||||
|
|
||||||
@ -297,20 +354,15 @@ describe('features - context-pad', function() {
|
|||||||
// given
|
// given
|
||||||
var element = elementRegistry.get('StartEvent_1'),
|
var element = elementRegistry.get('StartEvent_1'),
|
||||||
padding = 5,
|
padding = 5,
|
||||||
replaceMenuRect,
|
padMenuRect,
|
||||||
padMenuRect;
|
replaceMenuRect;
|
||||||
|
|
||||||
contextPad.open(element);
|
contextPad.open(element);
|
||||||
padMenuRect = contextPad.getPad(element).html.getBoundingClientRect();
|
|
||||||
|
|
||||||
// mock event
|
|
||||||
var event = {
|
|
||||||
target: padEntry(container, 'replace'),
|
|
||||||
preventDefault: function() {}
|
|
||||||
};
|
|
||||||
|
|
||||||
// when
|
// when
|
||||||
contextPad.trigger('click', event);
|
contextPad.trigger('click', padEvent('replace'));
|
||||||
|
|
||||||
|
padMenuRect = contextPad.getPad(element).html.getBoundingClientRect();
|
||||||
replaceMenuRect = domQuery('.bpmn-replace', container).getBoundingClientRect();
|
replaceMenuRect = domQuery('.bpmn-replace', container).getBoundingClientRect();
|
||||||
|
|
||||||
// then
|
// then
|
||||||
@ -319,7 +371,7 @@ describe('features - context-pad', function() {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
||||||
it('should not include control if replacement is disallowed',
|
it('should hide wrench if replacement is disallowed',
|
||||||
inject(function(elementRegistry, contextPad, customRules) {
|
inject(function(elementRegistry, contextPad, customRules) {
|
||||||
|
|
||||||
// given
|
// given
|
||||||
@ -337,10 +389,11 @@ describe('features - context-pad', function() {
|
|||||||
|
|
||||||
// then
|
// then
|
||||||
expect(padEntry(padNode, 'replace')).not.to.exist;
|
expect(padEntry(padNode, 'replace')).not.to.exist;
|
||||||
}));
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
|
||||||
it('should include control if replacement is allowed',
|
it('should show wrench if replacement is allowed',
|
||||||
inject(function(elementRegistry, contextPad, customRules) {
|
inject(function(elementRegistry, contextPad, customRules) {
|
||||||
|
|
||||||
// given
|
// given
|
||||||
@ -358,11 +411,15 @@ describe('features - context-pad', function() {
|
|||||||
|
|
||||||
// then
|
// then
|
||||||
expect(padEntry(padNode, 'replace')).to.exist;
|
expect(padEntry(padNode, 'replace')).to.exist;
|
||||||
}));
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
|
||||||
it('should open the replace menu after an element is created if it has modifier key',
|
describe('create + <CTRL>', function() {
|
||||||
|
|
||||||
|
it('should open replace',
|
||||||
inject(function(create, dragging, canvas, elementFactory) {
|
inject(function(create, dragging, canvas, elementFactory) {
|
||||||
|
|
||||||
// given
|
// given
|
||||||
var rootShape = canvas.getRootElement(),
|
var rootShape = canvas.getRootElement(),
|
||||||
startEvent = elementFactory.createShape({ type: 'bpmn:StartEvent' }),
|
startEvent = elementFactory.createShape({ type: 'bpmn:StartEvent' }),
|
||||||
@ -381,11 +438,13 @@ describe('features - context-pad', function() {
|
|||||||
|
|
||||||
// then
|
// then
|
||||||
expect(replaceMenu).to.exist;
|
expect(replaceMenu).to.exist;
|
||||||
}));
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
|
||||||
it('should open boundary event replace menu after an element is created if it has modifier key',
|
it('should open boundary event replace menu',
|
||||||
inject(function(create, dragging, canvas, elementFactory, modeling, popupMenu) {
|
inject(function(create, dragging, canvas, elementFactory, modeling, popupMenu) {
|
||||||
|
|
||||||
// given
|
// given
|
||||||
var rootShape = canvas.getRootElement();
|
var rootShape = canvas.getRootElement();
|
||||||
var task = elementFactory.createShape({ type: 'bpmn:Task' });
|
var task = elementFactory.createShape({ type: 'bpmn:Task' });
|
||||||
@ -406,10 +465,11 @@ describe('features - context-pad', function() {
|
|||||||
var replaceMenu = domQuery.all('[data-id$="-boundary"]', popupMenu._current.container);
|
var replaceMenu = domQuery.all('[data-id$="-boundary"]', popupMenu._current.container);
|
||||||
expect(replaceMenu).to.exist;
|
expect(replaceMenu).to.exist;
|
||||||
expect(replaceMenu.length).to.eql(13);
|
expect(replaceMenu.length).to.eql(13);
|
||||||
}));
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
|
||||||
it('should not open the replace menu after an element is created when there is none',
|
it('should not open non-existing replace menu',
|
||||||
inject(function(create, dragging, canvas, elementFactory) {
|
inject(function(create, dragging, canvas, elementFactory) {
|
||||||
// given
|
// given
|
||||||
var rootShape = canvas.getRootElement(),
|
var rootShape = canvas.getRootElement(),
|
||||||
@ -429,7 +489,10 @@ describe('features - context-pad', function() {
|
|||||||
|
|
||||||
// then
|
// then
|
||||||
expect(replaceMenu).to.not.exist;
|
expect(replaceMenu).to.not.exist;
|
||||||
}));
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -442,12 +505,6 @@ describe('features - context-pad', function() {
|
|||||||
modules: testModules.concat(autoPlaceModule)
|
modules: testModules.concat(autoPlaceModule)
|
||||||
}));
|
}));
|
||||||
|
|
||||||
var container;
|
|
||||||
|
|
||||||
beforeEach(function() {
|
|
||||||
container = TestContainer.get(this);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it('should trigger', inject(function(elementRegistry, contextPad) {
|
it('should trigger', inject(function(elementRegistry, contextPad) {
|
||||||
|
|
||||||
@ -457,12 +514,7 @@ describe('features - context-pad', function() {
|
|||||||
contextPad.open(element);
|
contextPad.open(element);
|
||||||
|
|
||||||
// mock event
|
// mock event
|
||||||
var event = {
|
var event = padEvent('append.gateway');
|
||||||
clientX: 100,
|
|
||||||
clientY: 100,
|
|
||||||
target: padEntry(container, 'append.gateway'),
|
|
||||||
preventDefault: function() {}
|
|
||||||
};
|
|
||||||
|
|
||||||
// when
|
// when
|
||||||
contextPad.trigger('click', event);
|
contextPad.trigger('click', event);
|
||||||
@ -522,3 +574,19 @@ describe('features - context-pad', function() {
|
|||||||
function padEntry(element, name) {
|
function padEntry(element, name) {
|
||||||
return domQuery('[data-action="' + name + '"]', element);
|
return domQuery('[data-action="' + name + '"]', element);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function padEvent(entry) {
|
||||||
|
|
||||||
|
return getBpmnJS().invoke(function(overlays) {
|
||||||
|
|
||||||
|
var target = padEntry(overlays._overlayRoot, entry);
|
||||||
|
|
||||||
|
return {
|
||||||
|
target: target,
|
||||||
|
preventDefault: function() {},
|
||||||
|
clientX: 100,
|
||||||
|
clientY: 100
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user