How to get a shape object reference from complete diagram

I have a GoJS diagram with an organizational chart.

In this, I want that dropdown menu should be closed even if I click outside the diagram canvas and the select dropdown arrow icon should be flipped back.

I am able to close the dropdown menu, but the arrow is not flipping back.

For note:

  1. The dropdown menu is an HTML element, wrapped as a custom editor that I am using in defaultTextEditor.
  2. To close the dropdown menu, when it is clicked outside the diagram, I am handling that in angular component using stopTool() method

For Eg:
@HostListener(‘document:click’, [‘$event’])
hideDropDownMenu(): void {
const targetElement = event?.target as HTMLElement;
const insideDiagram = targetElement.closest(‘#myDiagramDiv’);

if (!insideDiagram && this.diagram) {
  this.diagram.currentTool.stopTool();  // using stopTool method
}

}

Now I want to get the reference of that “SELECT” panel so that I can flip the arrow figure in that but I am not able to find that panel from complete diagram object.

How to handle this?

Thanks

Is that SELECT panel just some part of the Node template? If so, give the Panel (or even the Shape that is the arrow) a GraphObject.name value. Then you can do:

// in the properties of some GraphObject:
{ 
  name: 'SomeNameValue'
}

// when looking for the object to modify:
const graphObject = theNode.findObject('SomeNameValue');
// if (graphObject !== null) ... do something

Is the Diagram.currentTool an instance of TextEditingTool at that time? If so, I think you want to call Tool.doCancel, not Tool.stopTool.

Hi @simon ,
Yes that SELECT panel is basically as textblock inside one node.
Also I have given a name to it {name: “DROPDOWN_ARROW”} something like this, but the main issue is I in angular component where I have hostlistner, I have only access to complete gojs diagram and not a particular node.

How to find shape in complete diagram object?

Also @walter, I will try what you said.

Before you stop/cancel your custom TextEditingTool, you can look at TextEditingTool.textBlock.part to find that Node.

I tried your approach @simon, something like this :

if (this.diagram.toolManager.textEditingTool.textBlock) {
const part = this.diagram?.toolManager.textEditingTool.textBlock?.part;
if (part) {
const arrow: any = part.findObject(‘DROPDOWN_ARROW’);
if (arrow?.figure && arrow?.stroke) {
arrow.figure = ‘LineDown’;
arrow.stroke = ‘#B5B5B5’;
}
}
}

I am now able to access, arrow figure but the new value is not getting assigned, means its not updating the arrow figure with new value.

I even tried adding this functionality inside diagram.commit(), but its, still not updating.

How is your “Select” button (including arrow) now defined?

I didn’t understand.
If you are asking how this Select button is defined in gojs diagram, we have a panel inside which I have a textBlock and a Arrow Shape aligned horizontally.

This is what I am trying:
In angular component, to handle click event when clicked outside gojs diagram,
@HostListener(‘document:click’, [‘$event’])
hideDropDownMenu(): void {
const targetElement = event?.target as HTMLElement;
const insideDiagram = targetElement.closest(‘#myDiagramDiv’);

if (!insideDiagram && this.diagram) {
  if (this.diagram.toolManager.textEditingTool.textBlock) {
    const part = this.diagram?.toolManager.textEditingTool.textBlock?.part;
    if (part) {
      const arrow: any = part.findObject('DROPDOWN');
      if (arrow?.figure && arrow?.stroke) {
        arrow.figure = NEW_FIGURE;
        arrow.stroke = NEW_COLOR_CODE;
      }
    }
  }

  this.diagram.currentTool.doCancel();
}

}

Yes, I was asking for the definition of the panel. If defined correctly so that its state was dependent on some property, then changing the property would automatically change the appearance of the button.

Ok, apologies for the confusion.

This is how it is defined:
$(go.Panel, ‘Horizontal’, {
alignment: go.Spot.Left,
background: ‘transparent’,
},
$(go.TextBlock, {
name: ‘NEW_NODE_SELECT’,
text: ‘Select’,
stroke: STROKE_COLOR,
textEdited: (thisTextBlock: go.TextBlock, oldString: string, newString: string): boolean =>
fun()
},
new go.Binding(‘choices’, ‘’, getNewNodeTypeList),
),
$(go.Shape, ‘LineDown’, { name: ‘DROPDOWN_ARROW’, width: 7, height: 6, margin: 2, strokeWidth: 1, stroke: ‘#B5B5B5’ })
)

I have a choices state upon which I am populating all the options. Other than this no such state I have it here.

You don’t set or bind TextBlock.editable, so is the only way the user gets to edit the text is by your calling CommandHandler.editTextBlock?

There’s no binding on the Shape.figure property, so it’s not surprising that you need to programmatically update the shape at various times.

Your code to modify that Shape.figure (and also its stroke) seems to be reasonable. But I’m wondering if your canceling the text editing is automatically restoring the Shape properties, as part of the undoing process.

Here’s an example of how you could implement the LineDown/LineUp shape figure to be controlled by whether or not the TextEditingTool is operating:

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

  <script src="https://cdn.jsdelivr.net/npm/gojs/release/go-debug.js"></script>
  <script id="code">
class CustomTextEditingTool extends go.TextEditingTool {
  doActivate() {
    super.doActivate();
    if (this.textBlock !== null) {
      this.diagram.commit(() => {
        const shp = this.textBlock.panel.findObject("SHP");
        if (shp !== null) shp.figure = "LineUp";
      }, null);  // null means skipsUndoManager
    }
  }
  doDeactivate() {
    if (this.textBlock !== null) {
      this.diagram.commit(() => {
        const shp = this.textBlock.panel.findObject("SHP");
        if (shp !== null) shp.figure = "LineDown";
      }, null);
    }
    super.doDeactivate();
  }
}

const myDiagram =
  new go.Diagram("myDiagramDiv", {
      textEditingTool: new CustomTextEditingTool(),
      "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();
        }
      }
    });

myDiagram.nodeTemplate =
  new go.Node("Auto")
    .add(
      new go.Shape({ fill: "white" })
        .bind("fill", "color"),
      new go.Panel("Horizontal", {
          margin: 8,
          click: (e, pnl) => {
            if (e.diagram.currentTool instanceof go.TextEditingTool) {
              e.diagram.currentTool.doCancel();
            } else {
              e.diagram.commandHandler.editTextBlock(pnl.findObject("TB"));
            }
          }
        })
        .add(
          new go.TextBlock({ name: "TB" })
            .bindTwoWay("text"),
          new go.Shape("LineDown", { name: "SHP", width: 12, height: 12, margin: 4, background: "transparent" })
        )
    );

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>