Commandstack/Rule; check resize

Hi,

i am trying to implement a customized modeler according to our business needs. However i am stuck and would appreciate your help.

All our pools have to have the same width, because of a custom item at the top. The user can still change the width of an lane or pool, this also changes the size for every other lane/pool. This works just fine and i am not allowed to change this behaviour.

The problem is illustrated in the following pictures.
beforeResize
I am not allowed to decrease the size of the top shape. However if i resize the bottom one the rules for resizing lanes/pools dont get checked.
AfterResize

So far i tried something like this:

import CommandInterceptor from "diagram-js/lib/command/CommandInterceptor.js";

const HIGH_PRIORITY = 5000;

//Dont allow resize of pools if a shape is in the way of any pool
export default class RestrictPoolResize extends CommandInterceptor {
    constructor(eventBus, modeling, elementRegistry, commandStack) {
        super(eventBus);

        this.canExecute("shape.resize", HIGH_PRIORITY,  context => {
        const element  = context.shape;
        let condition = true;
        console.log(context);
        if(!(element.type !== 'bpmn:Participant' || element.type !== 'bpmn:Lane')) return
        
            //https://github.com/bpmn-io/bpmn-js/blob/b37dc237b33de20478992ce5114709e849aae2a1/lib/features/modeling/behavior/ResizeBehavior.js#L84
            let allPools = elementRegistry.getAll().filter(element => element.type === 'bpmn:Participant');
            allPools.forEach(element => {
                if(!commandStack.shape.resize.canExecute(element)) condition = false;
            });
            return condition;
        },
        true
        );
    }
}

CommandInterceptor.$inject = ["eventBus", "modeling", "elementRegistry", "commandStack"];

I think i found your way of checking in the link (comment). Can i call this method for all pools prior to executing?
Is it in general possible to restrict behaviour with this approach? To my understanding rules dont work, because i would restrict the user from resizing at all.

Thanks in advance!

Hi again,

i found 2 possible solutions to this problem. Combining both of them seems to get the job done.
I would still appreciate if one of the maintainers could correct my way, because i dont believe it is the most optimal and basically it is the same behaviour the library has with multiple lanes inside one pool.

I did the following:

export default class CustomResize extends ResizeBehavior {
    constructor(eventBus, modeling) {
      super(eventBus);
      eventBus.on('resize.move', HIGHER_PRIORITY, function(event) {
        let context = event.context,
        shape = context.shape,
        direction = context.direction,
        balanced = context.balanced,
        newBounds = context.newBounds;
    
        if (is(shape, 'bpmn:Lane') || is(shape, 'bpmn:Participant') || is(shape, 'custom:Phase')) {
            //Allow resize of Participant, but not by user
            if(shape.type === 'bpmn:Participant') return null;
            const allPools = getAllElements().filter(element => element.type === 'bpmn:Participant');

            for(let i = 0; i < allPools.length; i++) {
                let currentPool = allPools[i];
                //Get resize constraints for current pool
                context.resizeConstraints = getParticipantResizeConstraints(currentPool, direction, balanced);

                if(newBounds === undefined) return

                //Resize right to left
                if(context.direction === 'e' || context.direction === 'se' || context.direction === 'ne') {
                    //Ensure x + width > min
                    if(newBounds.x + newBounds.width < context.resizeConstraints.min.right) {
                        return null;
                    }
                }
            }
        }
      });
    }
}


ResizeBehavior.$inject = [ 'eventBus', 'modeling'];

This already did what i wanted, however sometimes i would get “stuck”. I guess because i return null, which might be bad pratice. What i mean with stuck is, that the border got to close the element and this event always returned null. Resizing was not possible and the user has to use the other side to change the resize.context.direction.

A possible solution, so far:

export default class RestrictPoolResize extends CommandInterceptor {
    constructor(eventBus, modeling, elementRegistry, commandStack) {
        super(eventBus);

        this.canExecute("shape.resize", HIGH_PRIORITY,  context => {
            let shape = context.shape,
            direction = context.direction,
            balanced = context.balanced,
            newBounds = context.newBounds;
        
            if (is(shape, 'bpmn:Lane') || is(shape, 'bpmn:Participant') || is(shape, 'custom:Phase')) {
                const allPools = getAllElements().filter(element => element.type === 'bpmn:Participant');
                
                for(let i = 0; i < allPools.length; i++) {
                    let currentPool = allPools[i];
                    //Get resize constraints for current pool
                    context.resizeConstraints = getParticipantResizeConstraints(currentPool, direction, balanced);
    
                    if(newBounds === undefined) return
    
                    //Resize right to left
                    if(context.direction === 'e' || context.direction === 'se' || context.direction === 'ne') {
                        //Ensure x + width > min
                        if(newBounds.x + newBounds.width < context.resizeConstraints.min.right) {
                            return false;
                        }
                    }
                }
            }
        },
        true
        );
    }
}

CommandInterceptor.$inject = ["eventBus", "modeling", "elementRegistry", "commandStack"];
1 Like

Thanks for outlining your case. Resizing lanes is not trivial, nor is your requirement.

Let me shed some light on how we achieve with lanes and participants:

  • First of all, a ResizeBehavior sets size constraints on resize start. This is done to prevent expensive re-computation during resize.

    :arrow_right: You got to use the same mechanism to attach custom resizeConstraints on resize start

  • Secondly, resizing lanes and participants does work fundamentally differently than resizing any other element. Essentially, we implement a custom resize logic that does resizing not only of the single element, but all other affected elements (cf. ResizeLaneBehavior).

    :arrow_right: You got to implement your very custom resize behavior.

:warning: With all kinds of customizations, be aware that you’re working with internals that are kept stable for most parts, but may be subject to change without prior notice.

Thank you very much nikku!

I will look into your suggestion, however i wont be able to try it in the next days.

1 Like