Prevent Auto resizing the group

In sample - Table Layout
when nodes moved outside the groups, it is not allowed to move - which is correct.
Also nodes can be moved from one group to another.
But when node is partially moved on left or top, the group gets resized. Also moveing partially in different group resizes the groups automatically.

What, I need to do is - when the node’s new position is completly on the group - then only it should be shown as highlighted and move operation should be possible and when is partially in group, then no group should be highlighted and drop should be disabled.

What change needs in the example to make it possible.

In the Group template there are three event handlers: GraphObject.mouseDragEnter, mouseDragLeave, and mouseDrop. Those operate under the assumption that the mouse point determines which group gets the dropped node(s), and only the mouse point.

You need to adapt those handlers not to respond when the dragged/copied node is not entirely within the target area. Furthermore it’s even more complicated because you probably need to handle the case where multiple nodes and links are being dragged. There are several reasonable possibilities – you’ll need to figure out which one you want.

So in the case that there’s only one node being dragged, you’ll want to check whether the group.actualBounds.containsRect(node.actualBounds). That’s a check you’ll need to make in all three event handlers.

You also have some choices in the mouseDrop handler for when the dragged node does overlap the edge of a group. You can see in the current implementation of the Table sample that the mouseDrop handler just cancels the tool if the drop is not permitted. You could adopt this same policy for your cases, or you could do something else.

Hi Walter,

I am not able to get it working in my app (not checked in extensions/table.html).
Seems there are two issues with it -

Issue 1 :
In mouseDragEnter event, i am not able to get e.diagram.selection.first() - hence not able to get the single selected node new bounds for comparision whether it is completly inside or outside of group. I am checking this in mouseDragEnter event to highlight the group only when single selected object is completely inside the group. Also to show cursor “not-allowed” when it is partially in the group. But on mouseDrop i am able to get selection and cancel the operation when it is partially outside.

Issue 2 :
Also in my test mouseDragEnter event gets fired only once when node is moved in the group OR moving object in existing group slightly. Since it is not fired when node gets partially moved out of group, i won’t be able to change its cursor to “not-allowed”. ( Even after resolving point 1 somehow, i won’t be able to change cursor & dehighlight group because event gets fired only once for the group )

There is no event like mouseDragOver for GraphObject. It is available for diagram object.
How this can be resolved ??

Even I tried to adapt code in mouseDragOver function of the diagram object. But this doesn’t get invoked until mouse pointer is on actual diagram canvas (instead of any node/graphobject).

Also tried to hook the code in DraggingTool implementation where doDragOver is overridden. It doesn’t get invoked but canStart function gets invoked of the custom DraggingTool.

Ah, you’re right – the events only happen in transitions between objects and not within an object, even though what the user is dragging might change its relationship with the group’s bounds.

Try this:

  function CustomDraggingTool() {
    go.DraggingTool.call(this);
    this.highlighted = null;
  }
  go.Diagram.inherit(CustomDraggingTool, go.DraggingTool);

  CustomDraggingTool.prototype.doDragOver = function(pt, obj) {
    if (obj !== null && (obj.part instanceof go.Group || obj.part.containingGroup !== null)) {
      var group = obj.part;
      if (!(group instanceof go.Group)) group = group.containingGroup;
      var node = (this.draggedParts || this.copiedParts).iteratorKeys.first();
      var highlight = (group.actualBounds.containsRect(node.actualBounds));
      if (highlight) {
        if (this.highlighted !== group && this.highlighted !== null) {
          this.highlighted.isHighlighted = false;
        }
        group.isHighlighted = true;
        this.highlighted = group;
        return;
      }
    }
    if (this.highlighted !== null) {
      this.highlighted.isHighlighted = false;
      this.highlighted = null;
    }
  }

Install in your Diagram by:

      $(go.Diagram, "myDiagramDiv",
        { . . .,
          draggingTool: new CustomDraggingTool(),
          allowDrop: true,
          // feedback that dropping in the background is not allowed
          mouseDragOver: function(e) { e.diagram.currentCursor = "not-allowed"; },
          // when dropped in the background, not on a Node or a Group, cancel the drop
          mouseDrop: function(e) { e.diagram.currentTool.doCancel(); },
          . . .
        });

You might still need to tweak this code to cover cases or bugs that I missed.

Thanks Walter.

Checked it. Seems two issues there -

  1. I am not able to change the cursor to “not-allowed”.
    I added the required code, in the last if statement. the cursor gets changed slightly but moving object further outside, it
    gets changed to arrow.
  2. Moving the node within the group and relasing mouse , remains the node as highlighted.
    • Will look in it futher.

That’s true – I did not include code to happen at “drop” time to remove any highlighting. I suggest overriding CustomDraggingTool.doDeactivate to do the following and then call the super method:

CustomDraggingTool.prototype.doDeactivate = function() {
  if (this.highlighted !== null) {
    this.highlighted.isHighlighted = false;
    this.highlighted = null;
  }
  go.DraggingTool.prototype.doDeactivate.call(this);
}

Working now as expected. Adding cursor = "not-allowed at the end of doDragOver also solves the problem with cursor.

One more thing needed is - if while moving pressing “Cancel” button highlights the group node (which is not same like when mouse is released).

Tried by adding “doCancel” overridding and same code as doDeactivate. But it is not working.

"Canceling drag operation with Esc key" should dehighlight the highlighted group.

Update : Even pressing cacel, doDeactivate gets fired but it the code inside if doesn’t get executed hence group remains highlighted;

OK, that’s what happens when I am hurried on a Saturday and don’t get a chance to try out the code I write.

Here’s a better version of the CustomDraggingTool:

  function CustomDraggingTool() {
    go.DraggingTool.call(this);
    this.highlighted = null;
  }
  go.Diagram.inherit(CustomDraggingTool, go.DraggingTool);

  CustomDraggingTool.prototype.removeHighlight = function() {
    if (this.highlighted !== null) {
      var oldskips = this.diagram.skipsUndoManager;
      this.diagram.skipsUndoManager = true;
      this.highlighted.isHighlighted = false;
      this.diagram.skipsUndoManager = oldskips;
      this.highlighted = null;
    }
  }

  CustomDraggingTool.prototype.doDragOver = function(pt, obj) {
    // OK when over a Group or over a Part that is a member of a Group
    if (obj !== null && (obj.part instanceof go.Group || obj.part.containingGroup !== null)) {
      var group = obj.part;
      if (!(group instanceof go.Group)) group = group.containingGroup;
      // only works for one Part, but works when copying as well as when moving
      var node = (this.copiedParts || this.draggedParts).iteratorKeys.first();
      var highlight = (group.actualBounds.containsRect(node.actualBounds));
      if (highlight) {
        if (this.highlighted !== group) this.removeHighlight();  // from previous group
        this.highlighted = group;  // remember group as currently highlighted
        var oldskips = this.diagram.skipsUndoManager;
        this.diagram.skipsUndoManager = true;  // don't remember any side-effects
        group.isHighlighted = true;
        this.diagram.skipsUndoManager = oldskips;
        return;
      } // else drop through to remove highlight
    }
    this.removeHighlight();
    this.diagram.currentCursor = "not-allowed";
  }

  CustomDraggingTool.prototype.doDropOnto = function(pt, obj) {
    this.removeHighlight();
    if (obj !== null && (obj.part instanceof go.Group || obj.part.containingGroup !== null)) {
      var group = obj.part;
      if (!(group instanceof go.Group)) group = group.containingGroup;
      var node = (this.copiedParts || this.draggedParts).iteratorKeys.first();
      var ok = (group.actualBounds.containsRect(node.actualBounds));
      if (!ok) this.doCancel();
    }
  }

  CustomDraggingTool.prototype.doDeactivate = function() {
    this.removeHighlight();
    go.DraggingTool.prototype.doDeactivate.call(this);
  }
  // end CustomDraggingTool

Regarding canceling the drag-and-drop from a Palette, add this override to the Palette’s DraggingTool:

      $(go.Palette, "myPaletteDiv",
        { . . .,
          "draggingTool.doCancel": function() {
            myDiagram.toolManager.draggingTool.removeHighlight();
            go.DraggingTool.prototype.doCancel.call(this);
          },
          . . . 
        })

Thanks for the updated version.

For Group template has event was handled with following statement in

mouseDrop: function (e, group) {
var ok = group.addMembers(e.diagram.selection, true);
}

When members are added in group, nodeGroupChanged event gets fired which performs database updates.

With above code, when node is dropped partially on group ( mouse position is in group for which node is not member)
and part of node is in original group, action gets cancelled (due to doDropOnto check)

Even action gets cancelled , my mouseDrop function gets invoked causing sending request twice to server.
First request with the groupID of group where mouse released and second request with the original groupID.

In my opinion, the mouseDrop event shouldn’t get fired due to cancelling of action.

As I said in my first reply, you want to modify the behavior of the mouseDrop event handler that is in the sample app.

Note that there is no way to prevent the user from letting go of a mouse button where they want. So the event will happen whether we like it or not. It is up to each app to decide what to do, if anything.

But perhaps I am misunderstanding the situation that you are describing if there are two Diagrams and thus two DraggingTools. Are you saying that there are two mouseDrop events happening when the user cancels their dragging gesture?

EDIT: I just confirmed, by adding a console.log statement to the mouseDrop event handler, that it does not get called when the user cancels a dragging operation. That’s true both for internal and for external dragging operations.

Whats wrong with following code - I updated same to support for checking whether any selected node is out side the group or not. It works sometimes but sometimes fails -
I observed following things as shown in image -

When second DragOver gets encountered,the value of first nodes actual bounds are was the previous results total bounds of both selected node. ( I mean the width and height)

You really ought to be using go-debug.js – I think it would catch the error in your code.

Your code is modifying the GraphObject.actualBounds of a Node. I suggest you do:

    bounds = node.actualBounds.copy();

Thanks. In my code bounds are not changed anywhere but in onDragOver operation only - when union is made, and check the actualbounds of first node, it seems updated.
With copy of actualbounds its working.

Walter,
The code your provided (doDragOver) function works when I move the object within or outside group.

This code flickers the highlighting when there are link visible in the group. ( at the time when mouse cursor in on link - to test purpose i increased the link stroke)

I adapted the following changes in my code -

  1. when the valid group obtained ( before the getting first node) checked whether group is undefined or its category is of required type. [ Based on this futher code gets executed and highlight variable set ]

Otherwise
get all the required group nodes from the diagram , and find the first one which contains pt which is received as parameter. With go-debug.js version I am getting following error -
Rect.contains:x must be a real number type, and not NaN or Infinity : Point(980.39082,2439.2884)

The code is

        this.diagram.nodes.each(function (node) {
            if (node.category === "Group") {
                if ((group == null || group == undefined) && node.actualBounds.contains(pt)) {
                    group = node;
                    highlight = true;
                }
            }
        });

That code looks both wrong and terribly inefficient, and I still don’t know what you really want to do.

First, don’t you want to check node instanceof go.Group?

Second, you are iterating over all of the Nodes in the Diagram, even after you found a satisfactory result.

Third, you should call Diagram.findObjectAt, which has the opportunity to be much more efficient.

But if you know that you will never have more than a few nodes in your diagram, I suppose my efficiency argument is moot.

In any case it sounds like you need to change your algorithm to accomodate any Links that you see. Perhaps this is a different topic.