Adornment button on hover of an adornment button

I have a requirement where on hove of node a button will come in the top right side and on hover of that button a panel will come in side of that button which will have option to choose and if you move outside the option panel all the buttons will disappear.

Can you help me with a small demo.

For showing a button on hover, I hope you have adapted the code in the Hover Buttons sample: Buttons Shown on Hover over Node | GoJS Diagramming Library

You can then hide the Adornment holding that button and then show a new Adornment holding whatever it is that you want to show. And a mouseLeave event handler there can cause that panel to disappear.

When i will be showing the last panel as adornment, the previous adornment button should also be visible, but what’s happening it as i am adding the new adornment panel on hover of the adromant button, all the adornments are getting removed as on mouse leave of node i will remove the first adornment and second adornment panel will come on hover of first adromant button.
Can you try it once.

Perhaps your Node.mouseLeave event handler should be smarter – don’t hide the Adornment(s) if the mouse has entered them. Use the third argument to the event handler.

How we shall handle it, let’s suppose in mouse leave of the main node , i check if the both the adornment exist then i won’t hide it, and in 1st adornment’s mouse leave event i will check if the 2nd adornment exists then i won’t remove it.
But the issue is the first adornment is at the outside border of the node , so it will close it.
How would you have handled it?? and how do we use the third argument?

Maybe don’t have two separate Adornments – instead just show one with the information Panel initially not visible.

Remember to set the Adornment’s background to “transparent” (or whatever color you like).

Oh yes, that’s also a way of doing it, i will try and let you know.

var nodeHoverAdornment = this.setupMouseOverDelete();

 private setupMouseOverDelete(): go.Adornment | undefined {
    return $(go.Adornment, 'Spot',
      $(go.Placeholder,
        {
          isActionable: true,  // needed because this is in a temporary Layer
          click: (e: any, obj: any) => {
            var node = obj.part.adornedPart;
            node.diagram.select(node);
          }
        }),
      $(go.Panel, "Horizontal", {
        alignment: go.Spot.TopRight,
        alignmentFocus: new go.Spot(0, 0, 0, 0),
      },
        $('Button', {
          'ButtonBorder.figure': 'Circle',
          'ButtonBorder.stroke': 'transparent',
          mouseHover: (e, obj) => {
            const anim = new go.Animation({ duration: 100 });
            anim.add(obj, "angle", obj.angle, obj.angle + 90);
            anim.start();
          }

        },
          { click: (e, obj) => this.remove(e, obj, this.diagram) },
          $(go.Shape, 'Circle', {
            fill: '#ff6600', width: 20, height: 20, stroke: null, strokeWidth: 0, name: 'DELETE_SHAPE'
          },), this.addIcon('\ue872', { stroke: '#E0E0E0', height: 14 }, '', [], {}),
        ),
        $(go.Panel, 'Vertical',
          {
            name: 'view-panel',
            visible: true,
          },
           $(go.Panel, 'Auto',
            $(go.Shape, 'Rectangle', { stroke: '#DDDDDD', fill: 'white', name: 'SHAPE' }),
            $(go.TextBlock, 'View', { margin: 4 }),
          ),
          $(go.TextBlock, 'Delete')
        )
      )
    );
  }
mouseHover: (e: any, obj: { part: any }): void => {
        const node = obj.part;
        if (node.data && node.data.disable) { return; }
        if (nodeHoverAdornment) { nodeHoverAdornment.adornedObject = node; }
        node.addAdornment('mouseHover', nodeHoverAdornment);

      }, 
mouseLeave: (e, obj) => {
        setTimeout(function () {
          if (obj.part) {
            obj.part.removeAdornment('mouseHover');
          }
        }, 3000);
      }

I added the other adornment as the panel itself in the first adornment but, can you help in handling the mouse leave function of the main node ,as i want to keep the adornment button outside the node and on hovering that button we will make the visible property of the panel to true and show the hidden panel as soon as we hover out the panel we will hide it and we will remove the adornment.
but as soon as i hover on the adorned button mouseLeave event get trigger and it hide the adornment how can we handle it.
I have added 3 second delay but it’s not a feasible solution.

Yes, the problem with explicit delays is that some other user action might change the state in a way that the delayed action is no longer desired.

I suggest that you set the background of the Adornment to be “transparent”, so that anywhere within the Adornment the mouse would be “inside” the Adornment.

You are also missing a mouseLeave event handler for the Adornment itself, or at least for the stuff you want to show.

Note that I suggested that you have that Panel with the extra buttons be not visible initially.

<!DOCTYPE html>
<html>
<head>
  <title>Minimal GoJS Sample</title>
  <!-- Copyright 1998-2024 by Northwoods Software Corporation. -->
</head>
<body>
  <div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:600px"></div>
  <textarea id="mySavedModel" style="width:100%;height:250px"></textarea>

  <script src="https://cdn.jsdelivr.net/npm/gojs/release/go.js"></script>
  <script id="code">
const myDiagram =
  new go.Diagram("myDiagramDiv", {
      "undoManager.isEnabled": true,
      "ModelChanged": e => {     // just for demonstration purposes,
        if (e.isTransactionFinished) {  // show the model data in the page's TextArea
          document.getElementById("mySavedModel").textContent = e.model.toJson();
        }
      }
    });

const $ = go.GraphObject.make;
const nodeHoverAdornment = 
   $(go.Adornment, 'Spot',
     $(go.Placeholder),
     $(go.Panel, "Horizontal",
       {
         background: "transparent", alignment: go.Spot.TopRight, alignmentFocus: go.Spot.TopLeft,
         mouseLeave: (e, panel, next) => {
           if (!(next && next.part instanceof go.Adornment)) {
             panel.part.adornedPart.removeAdornment('mouseHover');
           }
         }
       },
       $('Button', {
          'ButtonBorder.figure': 'Circle',
          'ButtonBorder.stroke': 'transparent',
          mouseHover: (e, button) => {
            const anim = new go.Animation({ duration: 100 });
            anim.add(button, "angle", button.angle, button.angle + 90);
            anim.start();
            button.part.findObject("view-panel").visible = true;
          },
          click: (e, button) => button.part.removeAdornment("mouseHover")
         },
         $(go.Shape, 'Circle', {
           fill: '#ff6600', width: 20, height: 20, stroke: null, strokeWidth: 0, name: 'DELETE_SHAPE'
         }),
         new go.TextBlock('\ue872', { stroke: '#E0E0E0', height: 14 }),
       ),
       $(go.Panel, 'Vertical',
         { name: 'view-panel', visible: false },
          $(go.Panel, 'Auto',
           $(go.Shape, 'Rectangle', { stroke: '#DDDDDD', fill: 'white', name: 'SHAPE' }),
           $(go.TextBlock, 'View', { margin: 4 }),
         ),
         $(go.TextBlock, 'Delete')
       )
     )
   );

myDiagram.nodeTemplate =
  new go.Node("Auto", {
      mouseHover: (e, node) => {
        if (node.data && node.data.disable) return;
        nodeHoverAdornment.adornedObject = node;
        nodeHoverAdornment.findObject("view-panel").visible = false;
        node.addAdornment('mouseHover', nodeHoverAdornment);
      },
      mouseLeave: (e, node, next) => {
        if (!(next && next.part instanceof go.Adornment)) {
          node.removeAdornment('mouseHover');
        }
      }
    })
    .add(
      new go.Shape({ fill: "white" })
        .bind("fill", "color"),
      new go.TextBlock({ margin: 8 })
        .bind("text")
    );

myDiagram.model = new go.GraphLinksModel(
[
  { key: 1, text: "Alpha", color: "lightblue" },
  { key: 2, text: "Beta", color: "orange" },
  { key: 3, text: "Gamma", color: "lightgreen" },
  { key: 4, text: "Delta", color: "pink" }
],
[
  { from: 1, to: 2 },
  { from: 1, to: 3 },
  { from: 2, to: 2 },
  { from: 3, to: 4 },
  { from: 4, to: 1 }
]);
  </script>
</body>
</html>

There is some delay when we hover on the node the button doesn’t come instantly can we remove that delay ?

Yes, that’s the definition of “hover” – waiting without moving.

You could decrease the time by setting ToolManager.hoverDelay, ToolManager | GoJS API But that would affect other hover behaviors, such as for tooltips.

You could not wait at all by using the mouseEnter event handler instead of the mouseHover event handler.

Thanks , it’s working fine

Hi,
one issue i have observed, when i am hovering on the green area, the node adormant is not removing as the mouseLeave event is not triggering because we are still in the panel.
image
image

I want to hide it if you are hovering outside button apart from the options panel.

This is the code you can use.

<!DOCTYPE html>
<html>

<head>
  <title>Minimal GoJS Sample</title>
  <!-- Copyright 1998-2024 by Northwoods Software Corporation. -->
</head>

<body>
  <div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:600px"></div>
  <textarea id="mySavedModel" style="width:100%;height:250px"></textarea>

  <script src="https://cdn.jsdelivr.net/npm/gojs/release/go.js"></script>
  <script id="code">
    const myDiagram =
      new go.Diagram("myDiagramDiv", {
        "undoManager.isEnabled": true,
        "ModelChanged": e => {     // just for demonstration purposes,
          if (e.isTransactionFinished) {  // show the model data in the page's TextArea
            document.getElementById("mySavedModel").textContent = e.model.toJson();
          }
        }
      });

    const $ = go.GraphObject.make;
    const nodeHoverAdornment =
      $(go.Adornment, 'Spot',
        $(go.Placeholder),
        $(go.Panel, "Vertical",
          {
            alignment: go.Spot.TopRight,
            alignmentFocus: new go.Spot(0, 0, -30, -20),
            alignmentFocusName: 'DELETE_SHAPE',
            background: "lightgreen",
            mouseLeave: (e, panel, next) => {
              if (!(next && next.part instanceof go.Adornment)) {
                panel.part.adornedPart.removeAdornment('mouseHover');
              }
            },
          },
          $('Button', {
            'ButtonBorder.figure': 'Circle',
            'ButtonBorder.stroke': 'transparent',

            mouseEnter: (e, button) => {
              button.part.findObject("view-panel").visible = true;
            },
            click: (e, button) => button.part.removeAdornment("mouseHover")
          },
            $(go.Shape, 'Circle', {
              fill: '#ff6600', width: 20, height: 20, stroke: null, strokeWidth: 0, name: 'DELETE_SHAPE'
            }),
            new go.TextBlock('\ue872', { stroke: '#E0E0E0', height: 14 }),
          ),
          $(go.Panel, 'Vertical', {
            background: '#ffffff',
            stretch: go.GraphObject.Fill
          },
            { name: 'view-panel', visible: false, background: 'white' },
            //view option
            $(go.Panel, 'Horizontal', {
              width: 60,
              stretch: go.GraphObject.Fill,
              alignment: go.Spot.Left,
              cursor: 'pointer',
              isActionable: true,
              margin: 4,
            },
              $(go.TextBlock, {
                text: 'View',
                stroke: '#0a599c',
                font: '8pt verdana',
                margin: new go.Margin(2, 5, 2, 4)
              }),
            ),
            //delete option
            $(go.Panel, 'Horizontal', {
              width: 60,
              stretch: go.GraphObject.Fill,
              alignment: go.Spot.Left,
              cursor: 'pointer',
              isActionable: true,
              margin: 4,
            }, $(go.TextBlock, {
              text: 'Delete',
              stroke: '#f44f50',
              font: '8pt verdana',
              margin: new go.Margin(2, 5, 2, 4)
            }),
            ),
            //clear option
            $(go.Panel, 'Horizontal', {
              width: 60,
              stretch: go.GraphObject.Fill,
              alignment: go.Spot.Left,
              cursor: 'pointer',
              isActionable: true,
              margin: 4,
            },
              $(go.TextBlock, {
                text: 'Clear',
                stroke: '#f44f50',
                font: '8pt verdana',
                margin: new go.Margin(2, 5, 2, 4)
              }),
            )

          )
        )
      );


    const nodeHoverAdornmentList =
      $(go.Adornment, 'Spot',
        $(go.Placeholder),
        $(go.Panel, "Horizontal",
          {
            alignment: go.Spot.RightCenter,
            // alignmentFocus: new go.Spot(0, 0, -30, -20),
            alignmentFocusName: 'DELETE_SHAPE',
            background: "lightgreen",
            mouseLeave: (e, panel, next) => {
              if (!(next && next.part instanceof go.Adornment)) {
                panel.part.adornedPart.removeAdornment('mouseHover');
              }
            },
          },
          $('Button', {
            'ButtonBorder.figure': 'Circle',
            'ButtonBorder.stroke': 'transparent',

            mouseEnter: (e, button) => {
              button.part.findObject("view-panel").visible = true;
            },
            click: (e, button) => button.part.removeAdornment("mouseHover")
          },
            $(go.Shape, 'Circle', {
              fill: '#ff6600', width: 20, height: 20, stroke: null, strokeWidth: 0, name: 'DELETE_SHAPE'
            }),
            new go.TextBlock('\ue872', { stroke: '#E0E0E0', height: 14 }),
          ),
          $(go.Panel, 'Vertical', {
            background: '#ffffff',
            stretch: go.GraphObject.Fill
          },
            { name: 'view-panel', visible: false, background: 'white' },
            //view option
            $(go.Panel, 'Horizontal', {
              width: 60,
              stretch: go.GraphObject.Fill,
              alignment: go.Spot.Left,
              cursor: 'pointer',
              isActionable: true,
              margin: 4,
            },
              $(go.TextBlock, {
                text: 'View',
                stroke: '#0a599c',
                font: '8pt verdana',
                margin: new go.Margin(2, 5, 2, 4)
              }),
            ),
            //delete option
            $(go.Panel, 'Horizontal', {
              width: 60,
              stretch: go.GraphObject.Fill,
              alignment: go.Spot.Left,
              cursor: 'pointer',
              isActionable: true,
              margin: 4,
            }, $(go.TextBlock, {
              text: 'Delete',
              stroke: '#f44f50',
              font: '8pt verdana',
              margin: new go.Margin(2, 5, 2, 4)
            }),
            ),
            //clear option
            $(go.Panel, 'Horizontal', {
              width: 60,
              stretch: go.GraphObject.Fill,
              alignment: go.Spot.Left,
              cursor: 'pointer',
              isActionable: true,
              margin: 4,
            },
              $(go.TextBlock, {
                text: 'Clear',
                stroke: '#f44f50',
                font: '8pt verdana',
                margin: new go.Margin(2, 5, 2, 4)
              }),
            )

          )
        )
      );

    myDiagram.nodeTemplate =
      new go.Node("Auto", {
        mouseEnter: (e, node) => {
          if (node.data.key === 1) {
            if (node.data && node.data.disable) return;
            nodeHoverAdornment.adornedObject = node;
            nodeHoverAdornment.findObject("view-panel").visible = false;
            node.addAdornment('mouseHover', nodeHoverAdornment);
          }
          else {
            if (node.data && node.data.disable) return;
            nodeHoverAdornmentList.adornedObject = node;
            nodeHoverAdornmentList.findObject("view-panel").visible = false;
            node.addAdornment('mouseHover', nodeHoverAdornmentList);
          }
        },
        mouseLeave: (e, node, next) => {
          if (!(next && next.part instanceof go.Adornment)) {
            node.removeAdornment('mouseHover');
          }
        }
      }).bind("width", '', data => data.key == 1 ? 100 : 200)
        .bind("height", '', data => data.key == 1 ? 100 : 30 )
        .add(
          new go.Shape({ fill: "white" })
            .bind("fill", "color"),
          new go.TextBlock({ margin: 8 })
            .bind("text")
        );

    myDiagram.model = new go.GraphLinksModel(
      [
        { key: 1, text: "GRID", color: "lightblue" },
        { key: 2, text: "LIST", color: "lightpink" },
      ],
      [
      ]);
  </script>
</body>

</html>

How can we do achieve it
Can we handle it by adding some mouseLeave Event on the button panel

You could add a mouseEnter event handler for that green object. Although I would recommend against such a design, since users might find it annoying.

What to write in mouseEnter event?

If i am hovering on the button it’s triggering the mouseEnter event for both then green panel and button too.

I thought you wanted to remove the Adornment when entering the green area.

Yes, if you entering in the green area, then we should remove the adormant, but if user enter in the menu option then it should not get removed.

OK, so what’s the problem? Code it to do what you want when you want it.

You might find useful the third argument of that event handler or of mouseLeave.

What the does the third argument exactly do, I am not finding enough info about it in the documentation.

GraphObject | GoJS API
The second argument gives you the GraphObject the mouse just entered, the third argument if non-null tells you what the mouse just left. If null, it had been in the background.