Context Pad Provider Error

I’m working on the context pad where in the screw wrench is included and upon clicking the screw wrench icon, I should get a popup menu (empty for now) where in I will add entries later on. But the issue at the moment with my code (included below) is I get an error shown in the image below. Any help with resolving this will be greatly appreciated. Thanks.

image

import {
    assign,
    forEach,
    isArray,
    every
} from 'min-dash';

import inherits from 'inherits-browser';

import ContextPadProvider from 'bpmn-js/lib/features/context-pad/ContextPadProvider'

import {is} from 'bpmn-js/lib/util/ModelUtil'

export default class DcrContextPadProvider {
    constructor(config, contextPad, create, elementFactory, injector, translate) {
        this.create = create;
        this.elementFactory = elementFactory;
        this.translate = translate;
        this.contextPad = contextPad;
        this.popupMenu = this.popupMenu;

        if (config.autoPlace !== false) {
            this.autoPlace = injector.get('autoPlace', false);
        }

        contextPad.registerProvider(this);
    }


    getContextPadEntries(element) {
        
        const {
            autoPlace,
            create,
            elementFactory,
            translate,
            contextPad,
            //popupMenu
        } = this;

        var popupMenu = this.popupMenu;

        var businessObject = element.businessObject;


        function appendDcrTask(event, element) {
            if (autoPlace) {
                const shape = elementFactory.createShape({ type: 'dcr:DcrTask' });
          
                autoPlace.append(element, shape);
              } else {
                appendDcrTaskStart(event, element);
              }
        }

        function startConnect(event, element) {
            connect.start(event, element);
        }
        

        function getReplaceMenuPosition(element) {
            var Y_OFFSET = 5;

            var pad = contextPad.getPad(element).html;

            var padRect = pad.getBoundingClientRect();

            var pos = {
            x: padRect.left,
            y: padRect.bottom + Y_OFFSET
            };

            return pos;
        }
        
        function appendDcrTaskStart(event) {
            const shape = elementFactory.createShape({ type: 'dcr:DcrTaskInc' });
        
            create.start(event, shape, element);

        }

        if (is(businessObject, 'dcr:DcrTaskInc')) {

            return function (actions) {

                delete actions['append.end-event']
                delete actions['append.gateway']
                delete actions['append.intermediate-event']
                delete actions['append.text-annotation']
                delete actions['append.append-task']
                delete actions['replace']
                delete actions['connect']




                return {
                    ...actions,
                    'append-dcrtask': {
                        group: 'model',
                        className: 'bpmn-icon-start-event-non-interrupting-parallel-multiple',
                        title: translate('Append DcrTask'),
                        action: {
                        click: appendDcrTask,
                        dragstart: appendDcrTask
                        }
                    },

                    'append-dcrtaskinc': {
                        group: 'model',
                        className: 'bpmn-icon-lane-divide-two',
                        title: translate('Append DcrTaskinc'),
                        action: {
                        click: appendDcrTaskStart,
                        dragstart: appendDcrTaskStart
                        }
                    },

                    'append-replace': {
                        group: 'edit',
                        className: 'bpmn-icon-screw-wrench',
                        title: translate('Change Event Type'),
                        action: {
                        click: function(event, element) {//appendDcrTaskStart

                            var position = assign(getReplaceMenuPosition(element), {
                                cursor: { x: event.x, y: event.y }
                            });

                            popupMenu.open(element, 'bpmn-replace', position, {
                                title: translate('Change element'),
                                width: 300,
                                search: true
                              });
                            }
                        }
                    },

                    'append-connect': {
                        group: 'append',
                        className: 'bpmn-icon-connection',
                        title: translate('Connect using Custom Connector'),
                        action: {
                          click: startConnect,
                          dragstart: startConnect
                        }
                    },

                }
                    
            }
        }
            
    };
}


DcrContextPadProvider.$inject = [
    'config',
    'contextPad',
    'create',
    'elementFactory',
    'injector',
    'translate',
    'popupMenu',
];



The line with from the code above causing the error is the popupMenu.open() function bolded below:

popupMenu.open(element, ‘bpmn-replace’, position, {
title: translate(‘Change element’),
width: 300,
search: true
});

Try adding an empty object return in the case that the if condition is not met. This way, you will return an object of empty entries instead of undefined.

I’m quite new to programming in JavaScript.

So I tried something like this but got thesame error;

else if(!is(businessObject, 'dcr:DcrTaskInc')) {
            return null;
        }

What exactly is your goal? If you’re goal is to open an empty popup menu if the element is not dcr:DcrTaskInc, then you can:

if(is(businessObject, 'dcr:DcrTaskInc')) {
   ...
} else {
   return {} 
}

No there are different if statements for different custom tasks and connectors and each of them has a context pad. So I wanted to specify what each element’s context pad contains and the actions permitted. The first of which is dcr:DcrTaskInc, it’s contextpad contains a delete(trash can), connect, replace etc. The error comes when I want to specify the action for the replace(that is the screw wrench). Given that I haven’t worked on the replace menu provider containing entries yet, I assumed it should just return an empty popup menu. Hence the error above any time I click on the screw wrench icon.

Ok I believe I understand. The issue is only when you click to try to open the popup menu? Could you please share a CodeSandbox that demonstrates this issue so I can help you further?

Here you go:

I couldn’t reproduce the specific error above. But the reason why the popup menu doesn’t open in the CodeSandbox is because in the DcrContextPadProvider constructor, you didn’t use the injected popup menu.

Screenshot 2023-03-10 at 15.55.23

I’ve made changes to that section of the code, so now it looks like this but I still get the original error;

image

I’ve had a look at the code again and was wondering if the issue is in this line:

popupMenu.open(element, 'bpmn-replace', position, {
      ....
});

As it may not know what bpmn:replace is given that the replacemenu provider hasn’t been created yet.

The problem above is that you did not define this.popupMenu. You should add it to the injection list:

DcrContextPadProvider.$inject = [
  ...
  "popupMenu"
];

And then you can retrieve it:

class DcrContextPadProvider {
    constructor(..., popupMenu) {
        ...
        this.popupMenu = popupMenu;
   }
}

bpmn-replace is a registered provider with bpmn-js, so what should happen (even if you don’t add your own entries to it) is that it should open the default bpmn-js replace provider.

1 Like