documentationfor yFiles for HTML 2.6

Grouping

As mentioned in Grouping, nodes can be interactively inserted into a group, moved from one group to another and ungrouped entirely as part of a Moving Items gesture. This process is called node reparenting. Programmatically, this is controlled by an instance of IReparentNodeHandler which is set on GraphEditorInputMode using the reparentNodeHandler property.

You can disable node reparenting completely by setting GraphEditorInputMode.allowReparentNodes to false.

The IReparentNodeHandler interface defines methods that determine if a gesture should be interpreted as a reparenting gesture, if an item can be reparented, if a certain parent is a valid target for a node that is being reparented, and a method that actually performs the reparenting. Here is a detailed description:

isReparentGesture(context: IInputModeContext, node: INode): boolean
Whether the current gesture is a reparent gesture. The current gesture can be determined from the context.
shouldReparent(context: IInputModeContext, node: INode): boolean
Whether the parent of the current node can be changed.
isValidParent(context: IInputModeContext, node: INode, newParent: INode): boolean
Whether the node’s parent can be changed to newParent.
reparent(context: IInputModeContext, node: INode, newParent: INode): void
Called after the gesture has been finished to perform the actual change of the parent. Should be implemented to set newParent as the new parent of the node.

For your convenience, the default implementation of the IReparentNodeHandler interface is ReparentNodeHandler which implements the interface trivially by allowing all nodes to be reparented. You can use this implementation as a base for your own implementation if desired. For the implementation of isReparentGesture it uses a function that accepts key events as reparent gesture where the Shift modifier key is pressed. If you only want to change the gesture of the default behavior, simply set the ReparentNodeHandler.reparentRecognizer of the default reparentNodeHandler to something else.

A custom IReparentNodeHandler which uses labels to determined valid reparenting targets
/**
 * Custom IReparentNodeHandler which extends the default implementation.
 */
class CustomReparentNodeHandler extends ReparentNodeHandler {
  /**
   * Whether the current gesture is a reparent gesture
   * @param {!IInputModeContext} context
   * @param {!INode} node
   * @returns {boolean}
   */
  isReparentGesture(context, node) {
    // either the default gesture
    // or the node has the label "always"
    return super.isReparentGesture(context, node) || (node.labels.size > 0 && node.labels.get(0).text === 'always')
  }

  /**
   * @param {!IInputModeContext} context
   * @param {!INode} node
   * @returns {boolean}
   */
  shouldReparent(context, node) {
    // all nodes which do not have the label "never" can be reparented
    return node.labels.size === 0 || node.labels.get(0).text !== 'never'
  }

  /**
   * @param {!IInputModeContext} context
   * @param {!INode} node
   * @param {?INode} newParent
   * @returns {boolean}
   */
  isValidParent(context, node, newParent) {
    // forbid reparenting nodes to nodes which have the same label
    return (
      node.labels.size === 0 ||
      newParent === null ||
      newParent.labels.size === 0 ||
      node.labels.get(0).text !== newParent.labels.get(0).text
    )
  }
}/**
 * Custom IReparentNodeHandler which extends the default implementation.
 */
class CustomReparentNodeHandler extends ReparentNodeHandler {
  /**
   * Whether the current gesture is a reparent gesture
   */
  isReparentGesture(context: IInputModeContext, node: INode): boolean {
    // either the default gesture
    // or the node has the label "always"
    return super.isReparentGesture(context, node) || (node.labels.size > 0 && node.labels.get(0).text === 'always')
  }

  shouldReparent(context: IInputModeContext, node: INode): boolean {
    // all nodes which do not have the label "never" can be reparented
    return node.labels.size === 0 || node.labels.get(0).text !== 'never'
  }

  isValidParent(context: IInputModeContext, node: INode, newParent: INode | null): boolean {
    // forbid reparenting nodes to nodes which have the same label
    return (
      node.labels.size === 0 ||
      newParent === null ||
      newParent.labels.size === 0 ||
      node.labels.get(0).text !== newParent.labels.get(0).text
    )
  }
}

The source code of the Reparent Handler demo shows how to extend IReparentNodeHandler’s default implementation ReparentNodeHandler to customize node reparenting.