How to make Selection always on top for Groups in Groups

Hi, I have found a solution for selection always on top in the below post.
https://forum.nwoods.com/t/setting-the-layername-or-zorder-of-all-the-nodes-and-links-inside-a-group/13385
But this only works for one group and sibling nodes. Whereas in a scenario where groups can be part of other groups, need something else.

Scenario:
1.Nodes should always stay on top of other nodes and groups and group members.
2.In a Group, If a member is selected/Dragged(selection happens on Drag) it should always stay on top of other group members and also if tried to drag onto other nodes that are outside the group, The group expands the nodes outside the group should not appear inside the loop.

Summary:
Group should stay on top of outside nodes and members of group should stay on top of other members. Groups inside groups is also possible, during which the current group should stay on top of the members of parent group.

Any suggestions are appreciated.

From your description, I would think that the solution you found in that other forum post should work for groups containing nested groups, contained by other groups, and when dragging over anything else. Could you please show us a sinple counterexample?

I assume your diagram does not use the “Foreground” Layer for any other purpose. Certainly that other forum post makes that assumption as well.

Giving you two edited versions using the sample from the gojs samples. one with your fix and another i worked on top of yours.
Please let me know if you think my fix might have any issues.

Recursive Function →

const changeLayer = (g,newlay) => {
      if(g.containingGroup){
        changeLayer(g.containingGroup,newlay)
        g.containingGroup.memberParts.each(function (m) {
          if(g.key !==m.key) {
            if(m instanceof go.Group){
              m.findSubGraphParts().each((siblingPart)=>{
                siblingPart.layerName = newlay;
              })
            }
            m.layerName = newlay;
          }
        });
        g.layerName = newlay;
      }
      else{
        g.layerName = newlay;
      }
    }

Group →

selectionChanged: g => {
        var newlay = g.isSelected ? "Foreground" : "";
        changeLayer(g,newlay);
        g.findSubGraphParts().each((memberPart) => {
          memberPart.layerName = newlay;
        });
      },

Node →

selectionChanged: g => {
        var newlay = g.isSelected ? "Foreground" : "";
        if(g.containingGroup){
          changeLayer(g,newlay);
        } else{
          g.layerName = newlay;
        }
      }

Full Sample with your fix →

<!DOCTYPE html>
<html lang="en">

<body>
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/release/go.js"></script>
  <p>
    This is a minimalist HTML and JavaScript skeleton of the GoJS Sample
    <a href="https://gojs.net/latest/samples/regrouping.html">regrouping.html</a>. It was automatically generated from a
    button on the sample page,
    and does not contain the full HTML. It is intended as a starting point to adapt for your own usage.
    For many samples, you may need to inspect the
    <a href="https://github.com/NorthwoodsSoftware/GoJS/blob/master/samples/regrouping.html">full source on Github</a>
    and copy other files or scripts.
  </p>
  <div id="allSampleContent" class="p-4 w-full">
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="">
    <link href="https://fonts.googleapis.com/css2?family=Lora&amp;display=swap" rel="stylesheet">
    <script id="code">
      function init() {

        myDiagram = new go.Diagram('myDiagramDiv', {
          'InitialLayoutCompleted': e => updateTotalGroupDepth(),
          // when a drag-drop occurs in the Diagram's background, make it a top-level node
          mouseDrop: e => finishDrop(e, null),
          layout: new go.GridLayout({ // Diagram has simple horizontal layout
            wrappingWidth: Infinity,
            alignment: go.GridAlignment.Position,
            cellSize: new go.Size(1, 1)
          }),
          'commandHandler.archetypeGroupData': { isGroup: true, text: 'Group', horiz: false },
          'undoManager.isEnabled': true
        });

        const colors = {
          white: '#fffcf6',
          blue: '#dfebe5',
          darkblue: '#cae0d8',
          yellow: '#fcdba2',
          gray: '#59524c',
          black: '#000000'
        }

        // The one template for Groups can be configured to either layout out its members
        // horizontally or vertically, each with a different default color.

        function makeLayout(horiz) {  // a Binding conversion function
          return new go.GridLayout({
            wrappingColumn: horiz ? Infinity : 1,
            alignment: go.GridAlignment.Position,
            cellSize: new go.Size(1, 1),
            spacing: horiz ? new go.Size(8, 8) : new go.Size(5, 5)
          });
        }

        function defaultColor(horiz) {  // a Binding conversion function
          return horiz ? colors.white : colors.darkblue;
        }

        // this function is used to highlight a Group that the selection may be dropped into
        function highlightGroup(e, grp, show) {
          if (!grp) return;
          e.handled = true;
          if (show) {
            // cannot depend on the grp.diagram.selection in the case of external drag-and-drops;
            // instead depend on the DraggingTool.draggedParts or .copiedParts
            var tool = grp.diagram.toolManager.draggingTool;
            var map = tool.draggedParts || tool.copiedParts;  // this is a Map
            // now we can check to see if the Group will accept membership of the dragged Parts
            if (grp.canAddMembers(map.toKeySet())) {
              grp.isHighlighted = true;
              return;
            }
          }
          grp.isHighlighted = false;
        }

        // Upon a drop onto a Group, we try to add the selection as members of the Group.
        // Upon a drop onto the background, or onto a top-level Node, make selection top-level.
        // If this is OK, we're done; otherwise we cancel the operation to rollback everything.
        function finishDrop(e, grp) {
          var ok = (grp !== null
            ? grp.addMembers(grp.diagram.selection, true)
            : e.diagram.commandHandler.addTopLevelParts(e.diagram.selection, true));
          if (!ok) e.diagram.currentTool.doCancel();

          const slider = document.getElementById('levelSlider');
          const oldMax = parseInt(slider.max);

          updateTotalGroupDepth();

          const newMax = parseInt(slider.max);
          // keep the slider value accurate to the current depth
          slider.value = parseInt(slider.value) + newMax - oldMax;
        }

        myDiagram.groupTemplate = new go.Group('Auto', {
          ungroupable: true,
          // highlight when dragging into the Group
          mouseDragEnter: (e, grp, prev) => highlightGroup(e, grp, true),
          mouseDragLeave: (e, grp, next) => highlightGroup(e, grp, false),
          // computesBoundsAfterDrag: true,
          // computesBoundsIncludingLocation: true,
          // when the selection is dropped into a Group, add the selected Parts into that Group;
          // if it fails, cancel the tool, rolling back any changes
          mouseDrop: finishDrop,
          handlesDragDropForMembers: true,  // don't need to define handlers on member Nodes and Links
          // Groups containing Groups lay out their members horizontally
          layout: makeLayout(false),
          background: defaultColor(false), // default value if not specified in data
          selectionChanged: g => {
            var newlay = g.isSelected ? "Foreground" : "";
            g.layerName = newlay;
            g.findSubGraphParts().each(m => { m.layerName = newlay; });
          }
        })
          .bind('background', 'horiz', defaultColor)
          .bind('layout', 'horiz', makeLayout)
          .add(
            new go.Shape('Rectangle', { stroke: colors.gray, strokeWidth: 1 })
              .bindObject('fill', 'isHighlighted', h => h ? 'rgba(0,255,0,.3)' : 'transparent'),
            new go.Panel('Vertical')  // title above Placeholder
              .add(
                new go.Panel('Horizontal', { stretch: go.Stretch.Horizontal }) // button next to TextBlock
                  .add(
                    go.GraphObject.build('SubGraphExpanderButton', { alignment: go.Spot.Right, margin: 5 }),
                    new go.TextBlock({
                      alignment: go.Spot.Left,
                      editable: true,
                      margin: new go.Margin(6, 10, 6, 1),
                      font: 'bold 16px Lora, serif',
                      opacity: 0.95,  // allow some color to show through
                      stroke: colors.black
                    })
                      .bind('font', 'horiz', (horiz) => horiz ? 'bold 20px Lora, serif' : 'bold 16px Lora, serif')
                      .bindTwoWay('text')
                  ),  // end Horizontal Panel
                new go.Placeholder({ padding: 8, margin: 4, alignment: go.Spot.TopLeft })
              )  // end Vertical Panel
          )  // end Auto Panel


        myDiagram.nodeTemplate = new go.Node('Auto', {
          // dropping on a Node is the same as dropping on its containing Group, even if it's top-level
          mouseDrop: (e, node) => finishDrop(e, node.containingGroup),
          isShadowed: true,
          shadowBlur: 0,
          shadowColor: colors.gray,
          shadowOffset: new go.Point(4.5, 3.5),
          selectionChanged: g => {
            var newlay = g.isSelected ? "Foreground" : "";
            g.layerName = newlay;
          }
        })
          .add(
            new go.Shape('RoundedRectangle', { fill: colors.yellow, stroke: null, strokeWidth: 0 }),
            new go.TextBlock({
              margin: 8,
              editable: true,
              font: '13px Lora, serif'
            })
              .bindTwoWay('text')
          );

        // initialize the Palette and its contents
        myPalette = new go.Palette('myPaletteDiv', {
          nodeTemplateMap: myDiagram.nodeTemplateMap,
          groupTemplateMap: myDiagram.groupTemplateMap
        });

        myPalette.model = new go.GraphLinksModel([
          { text: 'New Node', color: '#ACE600' },
          { isGroup: true, text: 'H Group', horiz: true },
          { isGroup: true, text: 'V Group', horiz: false }
        ]);

        var slider = document.getElementById('levelSlider');
        slider.addEventListener('change', reexpand);
        slider.addEventListener('input', reexpand);

        load();
      }

      function reexpand(e) {
        myDiagram.commit(diag => {
          var level = parseInt(document.getElementById('levelSlider').value);
          diag.findTopLevelGroups().each(g => expandGroups(g, 0, level))
        }, 'reexpand');
      }
      function expandGroups(g, i, level) {
        if (!(g instanceof go.Group)) return;
        g.isSubGraphExpanded = i < level;
        g.memberParts.each(m => expandGroups(m, i + 1, level))
      }
      function updateTotalGroupDepth() {
        let d = 0;
        myDiagram.findTopLevelGroups().each(g => d = Math.max(d, groupDepth(g)));
        document.getElementById('levelSlider').max = Math.max(0, d);
      }
      function groupDepth(g) {
        if (!(g instanceof go.Group)) return 0;
        let d = 1;
        g.memberParts.each(m => d = Math.max(d, groupDepth(m) + 1));
        return d;
      }

      // save a model to and load a model from JSON text, displayed below the Diagram
      function save() {
        document.getElementById('mySavedModel').value = myDiagram.model.toJson();
        myDiagram.isModified = false;
      }
      function load() {
        myDiagram.model = go.Model.fromJson(document.getElementById('mySavedModel').value);
      }
      window.addEventListener('DOMContentLoaded', init);
    </script>

    <div id="sample">
      <div style="width: 100%; display: flex; justify-content: space-between">
        <div id="myPaletteDiv"
          style="width: 130px; margin-right: 2px; background-color: #dfebe5; border: solid 1px black">
        </div>
        <div id="myDiagramDiv" style="flex-grow: 1; height: 500px; background-color: #dfebe5; border: solid 1px black">
        </div>
      </div>
      <p>
        This sample allows the user to drag nodes, including groups, into and out of groups,
        both from the Palette as well as from within the Diagram.
        See the <a href="../intro/groups.html">Groups Intro page</a> for an explanation of GoJS Groups.
      </p>
      <p>
        Highlighting to show feedback about potential addition to a group during a drag is implemented
        using <a>GraphObject.mouseDragEnter</a> and <a>GraphObject.mouseDragLeave</a> event handlers.
        Because <a>Group.computesBoundsAfterDrag</a> is true, the Group's <a>Placeholder</a>'s bounds are
        not computed until the drop happens, so the group does not continuously expand as the user drags
        a member of a group.
      </p>
      <p>
        When a drop occurs on a Group or a regular Node, the <a>GraphObject.mouseDrop</a> event handler
        adds the selection (the dragged Nodes) to the Group as new members.
        The <a>Diagram.mouseDrop</a> event handler changes the dragged selected Nodes to be top-level,
        rather than members of whatever Groups they had been in.
      </p>
      <p>
        The slider controls how many nested levels of Groups are expanded. <br>
        Semantic zoom level: <input id="levelSlider" type="range" min="0" max="5">
      </p>
      <div id="buttons">
        <button id="saveModel" onclick="save()">Save</button>
        <button id="loadModel" onclick="load()">Load</button>
        Diagram Model saved in JSON format:
      </div>
      <textarea id="mySavedModel" style="width:100%;height:300px">{ "class": "go.GraphLinksModel",
  "nodeDataArray": [
{"key":1, "isGroup":true, "text":"Main 1", "horiz":true},
{"key":2, "isGroup":true, "text":"Main 2", "horiz":true},
{"key":3, "isGroup":true, "text":"Group A", "group":1},
{"key":4, "isGroup":true, "text":"Group B", "group":1},
{"key":5, "isGroup":true, "text":"Group C", "group":2},
{"key":6, "isGroup":true, "text":"Group D", "group":2},
{"key":7, "isGroup":true, "text":"Group E", "group":6},
{"text":"First A", "group":3, "key":-7},
{"text":"Second A", "group":3, "key":-8},
{"text":"First B", "group":4, "key":-9},
{"text":"Second B", "group":4, "key":-10},
{"text":"Third B", "group":4, "key":-11},
{"text":"First C", "group":5, "key":-12},
{"text":"Second C", "group":5, "key":-13},
{"text":"First D", "group":6, "key":-14},
{"text":"First E", "group":7, "key":-15}
 ],
  "linkDataArray": [  ]}
  </textarea>
    </div>

  </div>
</body>

</html>

Full Sample with my fix →

<!DOCTYPE html>
<html lang="en">
<body>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/release/go.js"></script>
<p>
  This is a minimalist HTML and JavaScript skeleton of the GoJS Sample
  <a href="https://gojs.net/latest/samples/regrouping.html">regrouping.html</a>. It was automatically generated from a button on the sample page,
  and does not contain the full HTML. It is intended as a starting point to adapt for your own usage.
  For many samples, you may need to inspect the
  <a href="https://github.com/NorthwoodsSoftware/GoJS/blob/master/samples/regrouping.html">full source on Github</a>
  and copy other files or scripts.
</p>
<div id="allSampleContent" class="p-4 w-full">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="">
<link href="https://fonts.googleapis.com/css2?family=Lora&amp;display=swap" rel="stylesheet">
<script id="code">
  function init() {

    myDiagram = new go.Diagram('myDiagramDiv', {
      'InitialLayoutCompleted': e => updateTotalGroupDepth(),
      // when a drag-drop occurs in the Diagram's background, make it a top-level node
      mouseDrop: e => finishDrop(e, null),
      layout: new go.GridLayout({ // Diagram has simple horizontal layout
        wrappingWidth: Infinity,
        alignment: go.GridAlignment.Position,
        cellSize: new go.Size(1, 1)
      }),
      'commandHandler.archetypeGroupData': { isGroup: true, text: 'Group', horiz: false },
      'undoManager.isEnabled': true
    });

    const colors = {
      white: '#fffcf6',
      blue: '#dfebe5',
      darkblue: '#cae0d8',
      yellow: '#fcdba2',
      gray: '#59524c',
      black: '#000000'
    }

    // The one template for Groups can be configured to either layout out its members
    // horizontally or vertically, each with a different default color.

    function makeLayout(horiz) {  // a Binding conversion function
      return new go.GridLayout({
        wrappingColumn: horiz ? Infinity : 1,
        alignment: go.GridAlignment.Position,
        cellSize: new go.Size(1, 1),
        spacing: horiz ? new go.Size(8, 8) : new go.Size(5, 5)
      });
    }

    function defaultColor(horiz) {  // a Binding conversion function
      return horiz ? colors.white : colors.darkblue;
    }

    // this function is used to highlight a Group that the selection may be dropped into
    function highlightGroup(e, grp, show) {
      if (!grp) return;
      e.handled = true;
      if (show) {
        // cannot depend on the grp.diagram.selection in the case of external drag-and-drops;
        // instead depend on the DraggingTool.draggedParts or .copiedParts
        var tool = grp.diagram.toolManager.draggingTool;
        var map = tool.draggedParts || tool.copiedParts;  // this is a Map
        // now we can check to see if the Group will accept membership of the dragged Parts
        if (grp.canAddMembers(map.toKeySet())) {
          grp.isHighlighted = true;
          return;
        }
      }
      grp.isHighlighted = false;
    }

    // Upon a drop onto a Group, we try to add the selection as members of the Group.
    // Upon a drop onto the background, or onto a top-level Node, make selection top-level.
    // If this is OK, we're done; otherwise we cancel the operation to rollback everything.
    function finishDrop(e, grp) {
      var ok = (grp !== null
        ? grp.addMembers(grp.diagram.selection, true)
        : e.diagram.commandHandler.addTopLevelParts(e.diagram.selection, true));
      if (!ok) e.diagram.currentTool.doCancel();

      const slider = document.getElementById('levelSlider');
      const oldMax = parseInt(slider.max);

      updateTotalGroupDepth();

      const newMax = parseInt(slider.max);
      // keep the slider value accurate to the current depth
      slider.value = parseInt(slider.value) +  newMax - oldMax;
    }

    myDiagram.groupTemplate = new go.Group('Auto', {
      ungroupable: true,
      // highlight when dragging into the Group
      mouseDragEnter: (e, grp, prev) => highlightGroup(e, grp, true),
      mouseDragLeave: (e, grp, next) => highlightGroup(e, grp, false),
      selectionChanged: g => {
        var newlay = g.isSelected ? "Foreground" : "";
        changeLayer(g,newlay);
        g.findSubGraphParts().each((memberPart) => {
          memberPart.layerName = newlay;
        });
      },
      // computesBoundsAfterDrag: true,
      // computesBoundsIncludingLocation: true,
      // when the selection is dropped into a Group, add the selected Parts into that Group;
      // if it fails, cancel the tool, rolling back any changes
      mouseDrop: finishDrop,
      handlesDragDropForMembers: true,  // don't need to define handlers on member Nodes and Links
      // Groups containing Groups lay out their members horizontally
      layout: makeLayout(false),
      background: defaultColor(false) // default value if not specified in data
    })
      .bind('background', 'horiz', defaultColor)
      .bind('layout', 'horiz', makeLayout)
      .add(
        new go.Shape('Rectangle', { stroke: colors.gray, strokeWidth: 1 })
          .bindObject('fill', 'isHighlighted', h => h ? 'rgba(0,255,0,.3)' : 'transparent'),
        new go.Panel('Vertical')  // title above Placeholder
          .add(
            new go.Panel('Horizontal', { stretch: go.Stretch.Horizontal }) // button next to TextBlock
              .add(
                go.GraphObject.build('SubGraphExpanderButton', { alignment: go.Spot.Right, margin: 5 }),
                new go.TextBlock({
                  alignment: go.Spot.Left,
                  editable: true,
                  margin: new go.Margin(6, 10, 6, 1),
                  font: 'bold 16px Lora, serif',
                  opacity: 0.95,  // allow some color to show through
                  stroke: colors.black
                })
                  .bind('font', 'horiz', (horiz) => horiz ? 'bold 20px Lora, serif' : 'bold 16px Lora, serif')
                  .bindTwoWay('text')
              ),  // end Horizontal Panel
            new go.Placeholder({ padding: 8, margin: 4, alignment: go.Spot.TopLeft })
          )  // end Vertical Panel
      )  // end Auto Panel


    const changeLayer = (g,newlay) => {
      if(g.containingGroup){
        changeLayer(g.containingGroup,newlay)
        g.containingGroup.memberParts.each(function (m) {
          if(g.key !==m.key) {
            if(m instanceof go.Group){
              m.findSubGraphParts().each((siblingPart)=>{
                siblingPart.layerName = newlay;
              })
            }
            m.layerName = newlay;
          }
        });
        g.layerName = newlay;
      }
      else{
        g.layerName = newlay;
      }
    }

    myDiagram.nodeTemplate = new go.Node('Auto', {
      // dropping on a Node is the same as dropping on its containing Group, even if it's top-level
      mouseDrop: (e, node) => finishDrop(e, node.containingGroup),
      isShadowed: true,
      shadowBlur: 0,
      shadowColor: colors.gray,
      shadowOffset: new go.Point(4.5, 3.5),
      selectionChanged: g => {
        var newlay = g.isSelected ? "Foreground" : "";
        if(g.containingGroup){
          changeLayer(g,newlay);
        } else{
          g.layerName = newlay;
        }
      //   if (g.containingGroup) {
      //       g.containingGroup.layerName = newlay;
      //       g.containingGroup.findSubGraphParts().each(function(m) {
      //       m.layerName = newlay;
      //   });
      // }
      },
    })
      .add(
        new go.Shape('RoundedRectangle', { fill: colors.yellow, stroke: null, strokeWidth: 0 }),
        new go.TextBlock({
          margin: 8,
          editable: true,
          font: '13px Lora, serif'
        })
          .bindTwoWay('text')
      );

    // initialize the Palette and its contents
    myPalette = new go.Palette('myPaletteDiv', {
      nodeTemplateMap: myDiagram.nodeTemplateMap,
      groupTemplateMap: myDiagram.groupTemplateMap
    });

    myPalette.model = new go.GraphLinksModel([
      { text: 'New Node', color: '#ACE600' },
      { isGroup: true, text: 'H Group', horiz: true },
      { isGroup: true, text: 'V Group', horiz: false }
    ]);

    var slider = document.getElementById('levelSlider');
    slider.addEventListener('change', reexpand);
    slider.addEventListener('input', reexpand);

    load();
  }

  function reexpand(e) {
    myDiagram.commit(diag => {
      var level = parseInt(document.getElementById('levelSlider').value);
      diag.findTopLevelGroups().each(g => expandGroups(g, 0, level))
    }, 'reexpand');
  }
  function expandGroups(g, i, level) {
    if (!(g instanceof go.Group)) return;
    g.isSubGraphExpanded = i < level;
    g.memberParts.each(m => expandGroups(m, i + 1, level))
  }
  function updateTotalGroupDepth() {
    let d = 0;
    myDiagram.findTopLevelGroups().each(g => d = Math.max(d, groupDepth(g)));
    document.getElementById('levelSlider').max = Math.max(0, d);
  }
  function groupDepth(g) {
    if (!(g instanceof go.Group)) return 0;
    let d = 1;
    g.memberParts.each(m => d = Math.max(d, groupDepth(m)+1));
    return d;
  }

  // save a model to and load a model from JSON text, displayed below the Diagram
  function save() {
    document.getElementById('mySavedModel').value = myDiagram.model.toJson();
    myDiagram.isModified = false;
  }
  function load() {
    myDiagram.model = go.Model.fromJson(document.getElementById('mySavedModel').value);
  }
  window.addEventListener('DOMContentLoaded', init);
</script>

<div id="sample">
  <div style="width: 100%; display: flex; justify-content: space-between">
    <div id="myPaletteDiv" style="width: 130px; margin-right: 2px; background-color: #dfebe5; border: solid 1px black">
    </div>
    <div id="myDiagramDiv" style="flex-grow: 1; height: 500px; background-color: #dfebe5; border: solid 1px black">
    </div>
  </div>
  <p>
    This sample allows the user to drag nodes, including groups, into and out of groups,
    both from the Palette as well as from within the Diagram.
    See the <a href="../intro/groups.html">Groups Intro page</a> for an explanation of GoJS Groups.
  </p>
  <p>
    Highlighting to show feedback about potential addition to a group during a drag is implemented
    using <a>GraphObject.mouseDragEnter</a> and <a>GraphObject.mouseDragLeave</a> event handlers.
    Because <a>Group.computesBoundsAfterDrag</a> is true, the Group's <a>Placeholder</a>'s bounds are
    not computed until the drop happens, so the group does not continuously expand as the user drags
    a member of a group.
  </p>
  <p>
    When a drop occurs on a Group or a regular Node, the <a>GraphObject.mouseDrop</a> event handler
    adds the selection (the dragged Nodes) to the Group as new members.
    The <a>Diagram.mouseDrop</a> event handler changes the dragged selected Nodes to be top-level,
    rather than members of whatever Groups they had been in.
  </p>
  <p>
    The slider controls how many nested levels of Groups are expanded. <br>
    Semantic zoom level: <input id="levelSlider" type="range" min="0" max="5">
  </p>
  <div id="buttons">
    <button id="saveModel" onclick="save()">Save</button>
    <button id="loadModel" onclick="load()">Load</button>
    Diagram Model saved in JSON format:
  </div>
  <textarea id="mySavedModel" style="width:100%;height:300px">{ "class": "go.GraphLinksModel",
  "nodeDataArray": [
{"key":1, "isGroup":true, "text":"Main 1", "horiz":true},
{"key":2, "isGroup":true, "text":"Main 2", "horiz":true},
{"key":3, "isGroup":true, "text":"Group A", "group":1},
{"key":4, "isGroup":true, "text":"Group B", "group":1},
{"key":5, "isGroup":true, "text":"Group C", "group":2},
{"key":6, "isGroup":true, "text":"Group D", "group":2},
{"key":7, "isGroup":true, "text":"Group E", "group":6},
{"text":"First A", "group":3, "key":-7},
{"text":"Second A", "group":3, "key":-8},
{"text":"First B", "group":4, "key":-9},
{"text":"Second B", "group":4, "key":-10},
{"text":"Third B", "group":4, "key":-11},
{"text":"First C", "group":5, "key":-12},
{"text":"Second C", "group":5, "key":-13},
{"text":"First D", "group":6, "key":-14},
{"text":"First E", "group":7, "key":-15}
 ],
  "linkDataArray": [  ]}
  </textarea>
</div>
          
        </div>
</body>
</html>

Thanks.

Your changeLayer function is going up the tree of Part.containingGroups and calling changeLayer on every single subgraph Part of them too.

If I were you, I would not want to modify any containing Group.

And if I did want to modify a containing Group, I would just call Group.findSubGraphParts only once, since it recursively walks the tree of groups to find all nested Parts, including nested Groups.

Try stepping through your code to see what I mean.

I understand what you mean. But in case of sibling groups, if we add to layer using findSubGraphParts we will have the siblings staying on top of the current group in which we have selection. If you see i am deliberately excluding adding the current node or group to the layer in loop so all other elements move first.

 g.containingGroup.memberParts.each(function (m) {
          if(g.key !==m.key) {
            if(m instanceof go.Group){
              m.findSubGraphParts().each((siblingPart)=>{
                siblingPart.layerName = newlay;
              })
            }
            m.layerName = newlay;
          }
        });
        g.layerName = newlay;

and i am adding current one after the loop ends so that the selected node and/or group will always stay on top of the siblings.
Using the function mentioned we cannot track the relevant group on each level in order to follow that logic since all of them are together.

If you have any alternative or a way to use it to find current relevant groups on each level, Please let me know.

Thanks

Is there a reason you commented out the setting of Group.computesBoundsAfterDrag?

computesBoundsAfterDrag: true

Yes, In our scenario while dragging the group will expand. This prevents that so i have commented it.

So that would make moving a node from one group to another harder. Does your app need to support that possibility? Is that why you are doing something much more complicated than what I had proposed?

If so, I don’t think your code can accomplish what you want. In your case it seems impossible to drag Group E into Main 1.

In the case of my code, but with computesBoundsAfterDrag setting commented out, it is possible when dragging groups.

Are you trying to move the selected node’s top-level group to the “Background”, and the selected node (and if it’s a group, its members) to the “Foreground”? That might work.

OK, if you use this Group.selectionChanged event handler:

          selectionChanged: g => {
            if (!g.isTopLevel) {
              const topg = g.findTopLevelPart();
              const newlay = g.isSelected ? "Background" : "";
              topg.layerName = newlay;
              topg.findSubGraphParts().each(m => m.layerName = newlay);
            }
            const newlay = g.isSelected ? "Foreground" : "";
            g.layerName = newlay;
            g.findSubGraphParts().each(m => m.layerName = newlay);
          }

and this Node.selectionChanged event handler:

          selectionChanged: n => {
            if (!n.isTopLevel) {
              const topg = n.findTopLevelPart();
              const newlay = n.isSelected ? "Background" : "";
              topg.layerName = newlay;
              topg.findSubGraphParts().each(m => m.layerName = newlay);
            }
            n.layerName = n.isSelected ? "Foreground" : "";
          }

Then one can freely “regroup” any Group or Node, whether nested or not, into any other Group.

HOWEVER, as I pointed out before, it is still not always possible to drag a Node or Group into any other group within the same top-level group, or into the background. That’s because Group.computesBoundsAfterDrag has not been set to true.

To accomplish that, one would have to make sure that every sibling to each containing group is in front of that containing group. I think that could be arranged by controlling their Part.zOrders, but I don’t have time now to do that.

In our scenerio, Once a node is inside a group, it should NOT be allowed to move outside. Can only move to inner groups.

In that case I think what I originally suggested should work.

On the Group template:

          selectionChanged: g => {
            const newlay = g.isSelected ? "Foreground" : "";
            g.layerName = newlay;
            g.findSubGraphParts().each(m => m.layerName = newlay);
          }

and presumably you have set Group.memberValidation to a predicate that is true only when you want a Node or Group to be added to a Group. Read more about this at: Validation | GoJS

On the Node template:

          selectionChanged: n => {
            n.layerName = n.isSelected ? "Foreground" : "";
          }