Gojs+show a button like context menu when you click on icon on the card

Guys ,

I am using gojs to show different cards and each card i have a 3dot menu and when you click on that menu , i should see 2 button in action like context menu , like view profile and view ownership.and each have its own action. (This is my actual work)

For demo purpose i have a sandbox and on click of each card , should open 2 buttons for each car

For example if you click the above link you can see one one big card with 3 accordion and inside each accordion you can see individual entries from array and when you click on each item it should show 2 buttons for each item.
And when you click outside, it should show go

Is there a reason you don’t want to use a context menu? Your three-dot button’s click event handler can call CommandHandler.showContextMenu.

okay @walter , so i had used 3 dots menu and inside that i used a click function , so that context menu options are displayed.

BUT when the node is too long , it comes some where in middle and sometimes user cant even see that context menu .

Here is my code for 3 dots menu

$(
            go.Picture,
            {
              width: 16,
              height: 16,
              source: `assets/img/threedots.svg`,
              margin: new go.Margin(-33, 1, 2, 220),

              name: 'ThreeDotsExpanded',
            },
            {
              click: (e, node) => e.diagram.commandHandler.showContextMenu(node),
            }
          ),

And my code for the context menu is something like this

  contextMenu: $(
          'ContextMenu',
          { width: 160 },
          $(
            'ContextMenuButton',
            $(go.TextBlock, 'View Price', {
              stroke: '#161616',
              alignment: go.Spot.Left,
              margin: new go.Margin(1, 1, 1, 1),
            }),
            {
              click: (e, node) => viewPriceDetails(e, node),
            }
          ),
        ),

I need to show context menu always near to 3 dots menu. Can you please help same , i show a same card , but the way context menu implemented seems diff,

Do you really want to change the context menu so that it is a “Spot” Panel?
By default it is a “Vertical” Panel.

Setting alignment on the “ContextMenu” Adornment has no effect.

On what object did you declare the contextMenu?

@walter am sorry those spot and alignment comes as a result of keep on trying with different approaches and forgot to take it from there . i had removed both spot and alignment from the code.
on the main node i had given context menu , it goes like this

go.Node,
      'Auto',
      {
        doubleClick: (e, node) => updatePrice(e, node),
      },
      nodeStyle(),
      {
        stretch: go.GraphObject.Fill,
        locationObjectName: 'SHAPE',
        resizeObjectName: 'SHAPE',
        contextMenu: $(
     //same code for context menu//
} //node style ends here

      $(
        go.Panel,
        'Vertical',
....

3 dots picture code here

Happy to tell more if you need any more info

ContextMenuTool.positionContextMenu, if the Adornment has no Placeholder, will position it relative to the Diagram.lastInput.

You can override that method to do whatever you want. Here is its definition:

public positionContextMenu(contextmenu: Adornment, obj: GraphObject | null): void {
    if (contextmenu.placeholder !== null) return;

    const diagram = this.diagram;

    const p = diagram.lastInput.documentPoint.copy();
    const ttb = contextmenu.measuredBounds;
    const viewb = diagram.viewportBounds;
    // when touch event -- shift towards the left, so it's not obscured by the finger
    if (diagram.lastInput.isTouchEvent) {
      p.x -= ttb.width;
    }
    // if extends too far to the right -- shift left
    if (p.x + ttb.width > viewb.right) {
      p.x -= ttb.width + 5 / diagram.scale;
    }
    // but don't go beyond the left edge of the viewport
    if (p.x < viewb.x) {
      p.x = viewb.x;
    }
    // if extends too far down -- shift up
    if (p.y + ttb.height > viewb.bottom) {
      p.y -= ttb.height + 5 / diagram.scale;
    }
    // but don't go beyond the top edge of the viewport
    if (p.y < viewb.y) {
      p.y = viewb.y;
    }
    contextmenu.position = p;
  }

Thanks @walter for the quick reply , but unfortunately its not working , i gave like this

diagram.positionContextMenu = function (contextmenu, obj) {
      if (contextmenu.placeholder !== null) return;

      const diagram = this.diagram;

      const p = diagram.lastInput.documentPoint.copy();
      const ttb = contextmenu.measuredBounds;
      const viewb = diagram.viewportBounds;
      // when touch event -- shift towards the left, so it's not obscured by the finger
      if (diagram.lastInput.isTouchEvent) {
        p.x -= ttb.width;
      }
      // if extends too far to the right -- shift left
      if (p.x + ttb.width > viewb.right) {
        p.x -= ttb.width + 5 / diagram.scale;
      }
      // but don't go beyond the left edge of the viewport
      if (p.x < viewb.x) {
        p.x = viewb.x;
      }
      // if extends too far down -- shift up
      if (p.y + ttb.height > viewb.bottom) {
        p.y -= ttb.height + 5 / diagram.scale;
      }
      // but don't go beyond the top edge of the viewport
      if (p.y < viewb.y) {
        p.y = viewb.y;
      }
      contextmenu.position = p;
    };

but still its coming almost in the center of the node

That is a method of ContextMenuTool, not of Diagram.

Note that if you set a breakpoint in your code it is never called.

@walter yes you are right . But still didnt get how to get that context menu near to 3 dots menu .not sure am i missing something

Instead of diagram.lastInput.documentPoint, maybe you want to use

let p = diagram.lastInput.documentPoint.copy();
const pic = contextmenu.adornedPart.findObject('ThreeDotsExpanded');
if (pic) p = pic.getDocumentPoint(go.Spot.BottomRight);

okay @walter

diagram.toolManager.contextMenuTool.positionContextMenu = function (contextmenu, obj) {
      alert(1);
      debugger; // eslint-disable-line no-debugger
      if (contextmenu.placeholder !== null) return;

      const diagram = this.diagram;

      const p = diagram.lastInput.documentPoint.copy();
      const pic = contextmenu.adornedPart.findObject('ThreeDotsExpanded');
      if (pic) p = pic.getDocumentPoint(go.Spot.BottomRight);
    };

Here alert and debugger is cald ,is this way you asked to write ?

Yes, but then you don’t actually set the position of the contextmenu.

am sorry but am not setting any position inn my context menu

 contextMenu: $(
          'ContextMenu',
          { width: 160 },
          $(
            'ContextMenuButton',
            $(go.TextBlock, 'View Price', {
              stroke: '#161616',
              alignment: go.Spot.Left,
              margin: new go.Margin(1, 1, 1, 1),
            }),
            {
              click: (e, node) => viewPriceDetails(e, node),
            }
          ),
        ),

You didn’t include the rest of the code that I gave you, or something similar?

@walter when i give like below it started working

diagram.toolManager.contextMenuTool.positionContextMenu = function (contextmenu, obj) {
      if (contextmenu.placeholder !== null) return;

      const diagram = this.diagram;

      const p = diagram.lastInput.documentPoint.copy();
      const coordinates = obj.part.jb.loc.replace(/,/g, '');
      let xCord = coordinates.split(' ')[0];
      let yCord = coordinates.split(' ')[1];
      p.x = parseInt(xCord);
      p.y = +parseInt(yCord) + +50;
      contextmenu.position = p;
    };

BUT here issue when we click on 3dots and when you right click always context menu is taking coordinates as per the above code , thats not right, on 3 dots it should take coordinates from above code and on right click where it got right clicked , it should show context menu there .
Is there a way i can differentiate both ?
Answer will be helpful

Don’t use two-letter properties! They are minified names and will change with each build. Only use documented properties and methods. Don’t you mean obj.part.data?

Also, string parsing is another way to introduce errors. Call the static function Point.parse to convert a string into an instance of Point so that you can work with its numbers. But couldn’t you just use obj.part.position?

To answer your question, you could see if the diagram.lastInput.documentPoint is in the button.getDocumentBounds(). Or in your button’s click event handler you could set some flag that your override method could check and then clear.

    diagram.toolManager.contextMenuTool.positionContextMenu = function (contextmenu, obj) {
      if (contextmenu.placeholder !== null) return;

      const diagram = this.diagram;

      const p = diagram.lastInput.documentPoint.copy();
      const coordinates = obj.part.location;
      let xCord = coordinates.x;
      let yCord = coordinates.y;
      p.x = xCord;
      p.y = +yCord + +50;
      contextmenu.position = p;
    };

I changed like this and is working as expected , but didnt understood how to override my function or how to check button.getDocumentBounds() ?
For reference am pasting 3dots click function

$(
          go.Picture,
          {
            width: 16,
            height: 16,
            column: 0,
            source: `assets/img/threedots.svg`,
            margin: new go.Margin(-20, 1, 2, 220),
            imageAlignment: go.Spot.Right,
            name: 'ThreeDotsExpanded',
          },
          {
            click: (e, node) => e.diagram.commandHandler.showContextMenu(node),
          }
        ),

This is the click function for 3dots menu , so can you please help me to understand how o override ? if its click from 3dots yes its should go above function and use obj.part.location , both x and y coordinates , if its from right hand menu , it dont want to use this location coordinates
OR is there a way we can get if its right clicked and to get coordinates of where the right click happend and its x and y coordinates

Your override needs to decide which policy to use, so it has to have some sort of check and then act either the way you have it or else call the super method.

I already suggested:

const button = obj.part.findObject("ThreeDotsExpanded");
if (button && button.getDocumentBounds().containsPoint(diagram.lastInput.documentPoint)) {
  ... your code ...
} else {
  go.ContextMenuTool.prototype.positionContextMenu.call(this);
}

@water i tried above , may be i am missing something , so i just come with a mockup


Here in this wireframe you can see 2 view prices ,the first view prices next to 3 dots menu is when you click on 3 dots and showing context menu NEXT to 3 dots

The other view prices context menu is shown almost down of the panel , where user right clicked and NEED to show context menu in that places (exactly)where he right clicked.

so you are asked to check
obj.part.findObject("ThreeDotsExpanded");
Which will be always there , hence for me always context menu is showing next to 3 dots, which should not be the case when you right click ( need to show context menu where he right clicked)

Yes, the button will always be there, but the last mouse point might not be. I’d expect the mouse to be in the button upon a mouse-up event.