Click on any parent node and all the Child Node get selected in OrgChartEditor

Click on any parent node and all the Child Node get selected under that parent in OrgChartEditor. Is it possible and if yes, could you please provide any example implementation or code.

Yes, that’s certainly possible. Do you want the whole subtree selected, or just to highlight the subtree upon selection of a parent node?

You could iterate over the children using Node.findTreeChildrenNodes and then select/highlight them. More generally, you may be interested in the Navigation sample.

Put the code to select nodes in a click event handler of the node template.

$(go.Node, . . .,
  {
    click: (e, node) => {
      e.diagram.selectCollection(node.findTreeChildrenNodes());
    }
  },
  . . .
)

I have implemented the orgHierarchy in modal. So when I select multiple nodes and its highlights like focus but when I click outside immediately the focus unselect the node.

Is there any way that highlight should be select and unselect based on the node click and should not unselect the highlight while click outside.

I don’t understand what you want to do. Could you provide a before and after screenshots/sketches of the behavior you want? Before clicking a node, after clicking a node, after clicking away.

In the above diagram, you can see that 2nd level child “Vertical-2” has been selected so there is a focus with blue border.

But when I click just outside the node the focus has been unselected as below mentioned “Vertical-2” node. It should always highlight or focus unless you again click the same node and it should not remove focus on just click outside the node.

I hope you understood now. In simple terms the nodes selection has to be persist even after close the modal and open again.

You can use this override of standardMouseSelect to change selection behavior:

  // Override the clickSelectingTool's standardMouseSelect
  // Unless shift is held, toggle the selection when clicking
  // Clicks on diagram background do not deselect
  myDiagram.toolManager.clickSelectingTool.standardMouseSelect = function() {
    const diagram = this.diagram;
    if (diagram === null || !diagram.allowSelect) return;
    var e = diagram.lastInput;
    var curobj = diagram.findPartAt(e.documentPoint, false);
    if (curobj !== null) {
      if (e.shift) {  // add the part to the selection
        if (!curobj.isSelected) {
          diagram.raiseDiagramEvent('ChangingSelection', diagram.selection);
          let part = curobj;
          while (part !== null && !part.canSelect()) part = part.containingGroup;
          if (part !== null) part.isSelected = true;
          diagram.raiseDiagramEvent('ChangedSelection', diagram.selection);
        }
      } else {  // otherwise, toggle the selection
        diagram.raiseDiagramEvent('ChangingSelection', diagram.selection);
        let part = curobj;
        while (part !== null && !part.canSelect()) part = part.containingGroup;
        if (part !== null) part.isSelected = !part.isSelected;
        diagram.raiseDiagramEvent('ChangedSelection', diagram.selection);
      }
    }
    // don't clear selection on background click
  }

If you want to persist selection state when closing/reopening, you’ll probably need to save it in your model by adding a binding to your node template:
new go.Binding("isSelected", "selected").makeTwoWay()

Thanks Jhardy, the above shared code for the standardMouseSelect is working fine.

Now I have only the issue with persisting the selection state. As you have mentioned above “save it in your model and add a binding”. I am not saving in the model and below is the code I am using. Please let me know how can I add the change to persist the selection state.

const $ = go.GraphObject.make;

const sharedModel: go.TreeModel = $(go.TreeModel,
    {
        // positive keys for nodes
        makeUniqueKeyFunction: (m: go.Model, data: any) => {
            let k = data.key || 1;
            while (m.findNodeDataForKey(k)) k++;
            data.key = k;
            return k;
        }
    });


const initDiagram = (): go.Diagram => {
    go.Diagram.licenseKey = ""
    const diagram = $(go.Diagram,
        {
            'undoManager.isEnabled': true,  // must be set to allow for model change listening
            validCycle: go.Diagram.CycleDestinationTree, // make sure users can only create trees
            initialAutoScale: go.Diagram.Uniform,
            layout: new OrgChartLayout(),
            model: sharedModel
        });

    diagram.toolManager.clickSelectingTool.standardMouseSelect = function () {
        const diagram = this.diagram;
        if (diagram === null || !diagram.allowSelect) return;
        var e = diagram.lastInput;
        var curobj = diagram.findPartAt(e.documentPoint, false);
        if (curobj !== null) {
            if (e.shift) {  // add the part to the selection
                if (!curobj.isSelected) {
                    diagram.raiseDiagramEvent('ChangingSelection', diagram.selection);
                    let part = curobj;
                   // while (part !== null && !part.canSelect()) part = part.containingGroup;
                    if (part !== null) part.isSelected = true;
                    diagram.raiseDiagramEvent('ChangedSelection', diagram.selection);
                }
            } else {  // otherwise, toggle the selection
                diagram.raiseDiagramEvent('ChangingSelection', diagram.selection);
                let part = curobj;
               // while (part !== null && !part.canSelect()) part = part.containingGroup;
                if (part !== null) part.isSelected = !part.isSelected;
                diagram.raiseDiagramEvent('ChangedSelection', diagram.selection);
            }
        }
        // don't clear selection on background click
    }

    // This function provides a common style for most of the TextBlocks.
    // Some of these values may be overridden in a particular TextBlock.
    function textStyle() {
        return { font: "9pt  Segoe UI,sans-serif", stroke: "white" };
    }

    function nodeClickHandler(s:any) {
        props.updateSelectedModels(s);
    }

    // define the Node template
    diagram.nodeTemplate =
        $(go.Node, "Auto",
    /* { doubleClick: nodeDoubleClick }, */
            // for sorting, have the Node.text be the data.name
            new go.Binding("text", "name"),
            // bind the Part.layerName to control the Node's layer depending on whether it isSelected
            new go.Binding("layerName", "isSelected", function (sel) { return sel ? "Foreground" : ""; }).ofObject(),
            new go.Binding("isSelected", "sel").makeTwoWay(),
            // define the node's outer shape
            $(go.Shape, "Rectangle",
                {
                    name: "SHAPE", fill: "#333333", stroke: 'white', strokeWidth: 3.5,
                    // set the port properties:
                    portId: "", fromLinkable: true, toLinkable: true, cursor: "pointer"
                }),
            $(go.Panel, "Horizontal",
                // define the panel where the text will appear
                $(go.Panel, "Table",
                    {
                        minSize: new go.Size(130, NaN),
                        maxSize: new go.Size(150, NaN),
                        margin: new go.Margin(6, 10, 0, 6),
                        alignment: go.Spot.Center
                    },
                    $(go.RowColumnDefinition, { column: 2, width: 4 }),
                    $(go.TextBlock, textStyle(),  // the name
                        {
                            row: 0, column: 0, columnSpan: 5,
                            font: "12pt Segoe UI,sans-serif",
                            editable: true, isMultiline: false,
                            minSize: new go.Size(10, 16),
                            verticalAlignment: go.Spot.Center
                        },
                        new go.Binding("text", "title").makeTwoWay()
                    ),
                    $(go.TextBlock, textStyle(),  // the comments
                        {
                            row: 3, column: 0, columnSpan: 5,
                            font: "italic 9pt sans-serif",
                            wrap: go.TextBlock.WrapFit,
                            editable: true,  // by default newlines are allowed
                            minSize: new go.Size(10, 14)
                        },
                        new go.Binding("text", "comments").makeTwoWay())
                )  // end Table Panel
            ), // end Horizontal Panel
            { click: function (e: any, obj: any) { nodeClickHandler(obj.part.data.title);} },
        );  // end Node


    // define the Link template
    diagram.linkTemplate = $(go.Link, go.Link.Orthogonal,
        { corner: 5, relinkableFrom: true, relinkableTo: true },
        $(go.Shape, { strokeWidth: 1.5, stroke: "#F5F5F5" }))
    
    diagram.commandHandler.zoomToFit();
    diagram.contentAlignment = go.Spot.Top;
    return diagram;
}

If you are using React, you should have an onModelChange function that updates state whenever the GoJS model changes. You can save the “sel” property in your React state there.

Is there any way that by default all the nodes has to be selected when I open the model?

Currently by default all the nodes is unselected and user can select if needed. I have attached 2 screenshots, one with fully unselected and second is just 2 nodes selected.


Call CommandHandler.selectAll after all of the Nodes and Links exist.
diagram.commandHandler.selectAll()

Thanks and let me check. So to unselect all the nodes. Is there any method?

Oh, if you only want to select all of the Nodes:
diagram.selectCollection(diagram.nodes)

To unselect everything:
diagram.clearSelection()

I have added both the methods in my code but both are not selecting all the nodes. Below is my code for your reference. Also, I have downloaded HTML page from “Org Chart Editor” and added both the functions and not selecting all the nodes. Could you please check how to add in TreeLayout instead of GraphlinkModel.

// define the Link template
    diagram.linkTemplate = $(go.Link, go.Link.Orthogonal,
        { corner: 5, relinkableFrom: true, relinkableTo: true },
        $(go.Shape, { strokeWidth: 1.5, stroke: "#F5F5F5" }))
    
    diagram.commandHandler.zoomToFit();
    diagram.contentAlignment = go.Spot.Top;
    diagram.commandHandler.selectAll();
    diagram.selectCollection(diagram.nodes);
    return diagram;
}

You need to select (or de-select) after there exist Nodes, and that happens only after you have provided all of the model data.

I have added the below code for node selection. The first click is for the individual node selection and second click for parent-child relationship. But when I add these two only second click is working but not first one.

But I need both parent-child relationship and single node selection at same time. How can I implement both the functionality in single click? The below code I am using at the end of the diagram.nodeTemplate.

{ click: function (e: any, obj: any) { nodeClickHandler(obj.part.data.title); } },
{ click: (e: any, node: any) => { e.diagram.selectCollection(node.findTreeChildrenNodes()); } },

Are those two settings of click on the same template?

If you evaluate { prop: 17, prop: 19 }, the result will be equivalent to: { prop 19 }. In other words, setting the same property twice on the same object discards any previous value and only remembers the last value.

yes, two settings of click on the same template. is it possible?

Also, one more functionality like I am showing the ORG Hierarchy inside the modal. So when I open the modal by default all the nodes has to be selected. When I use the below code in nodeTemplate it works but on click. The same if I put without click event outside the nodeTemplate it’s not working “diagram.selectCollection(diagram.nodes)”. What change I need to do to select all the nodes by default.

{ click: (e: any, obj: any) => { e.diagram.selectCollection(diagram.nodes) } },

I’m saying that there can only be one click event handler, but it could do two or more things.