documentationfor yFiles for HTML 2.6

Drag and Drop

Out of the box, GraphEditorInputMode’s NodeDropInputMode can handle drop events where an INode instance is dropped. A copy of the node will then be created at the location of the drop. Similarly, ILabels and IPorts are handled by LabelDropInputMode and PortDropInputMode, respectively.

However, you can use the drag and drop mechanism in yFiles for HTML to drag and drop anything into the GraphComponent. As usual, an IInputMode is responsible for handling the appropriate events on the GraphComponent.

General Drag and Drop Support

The DropInputMode class provides an abstraction for drop event handling. Each instance of DropInputMode is specific for a ’format’ string that is passed to the constructor. This string serves as an identifier for the input mode and lets you install several input modes that handle different droppped data. The DropInputMode processes any drag events that occur on the GraphComponent and fires its events accordingly if the drag operation carries data of the expected format.

For example, if you wish to process files that are dropped onto the GraphComponent, you could use the following DropInputMode:

const dropInputMode = new DropInputMode('someFormat')

Its subclasses ItemDropInputMode<T> and NodeDropInputMode handle only drag and drop operations that carry IModelItems and INodes, respectively.

If you wish to process drag and drop operations for arbitrary types of objects however, you need to configure a DropInputMode appropriately. For this, you can react to the following drag related events:

Related events
Event Occurs when …​
DragEntered…​ a drag operation enters the GraphComponent.
DragOver…​ a drag operation is over the GraphComponent.
DragLeft…​ a previously entered drag operation left the GraphComponent or was canceled.
DragDropped…​ a drop onto the GraphComponent has been performed.

For example, you could implement a DropInputMode that accepts strings, so you can drag and drop a string into the GraphComponent and automatically create a node with the droppped string as label by reacting to the DragDropped event as illustrated in the following example:

const dropInputMode = new DropInputMode('someFormat')
dropInputMode.addDragDroppedListener((source, args) => {
  // drop data is list of dropped files
  const data = dropInputMode.dropData

  if (data.length > 0) {
    // this is a string... - create a node with a label using the given text
    const node = graphComponent.graph.createNode()
    graph.addLabel(node, data)
  }
})
dropInputMode.priority = graphEditorInputMode.nodeDropInputMode.priority - 1
graphEditorInputMode.add(dropInputMode)

You can also control the area on which a drop may occur via the validDropHitTestable property. By default, the area is unrestricted. For example, you could set the property to NEVER, thus effectively preventing to drop anywhere, and then only react to the above mentioned drag events:

dropInputMode.addDragEnteredListener((sender, args) => {
  // first call the super class method that does some basic checks
  // (is the input mode active, is the GraphComponent editable, does the type of the drag match)
  const dropData = dropInputMode.dropData
  // we only accept dropping one file
  if (dropData === 'magicData') {
    // set the valid drop HitTestable so that nowhere can be dropped (effectively rejecting the drag)
    sender.validDropHitTestable = IHitTestable.NEVER
    // important: remember to reset the HitTestable when the drag is exited/canceled
  }
})
// reset the HitTestable when the drag is exited/canceled
// The DragLeft event is raised in those cases, so we can do this here
dropInputMode.addDragLeftListener((sender, args) => {
  sender.validDropHitTestable = IHitTestable.ALWAYS
})

Drag and Drop of Graph Items

The ItemDropInputMode<T> adds highlight and preview functionality to DropInputMode and is specific for IModelItem. It also supports snapping of the previewed item. You can disable these features as desired using the properties listed below.

showPreview
Whether to show a preview of the dragged item during the drag.
snappingEnabled
Whether snapping is enabled. Note that a valid snap context either has to be set as snapContext or it has to be retrieved from the IInputModeContext, which is the case if snapping is enabled on the parent GraphEditorInputMode.
highlightDropTarget
Whether to indicate a potential drop target item with a highlight visualization.

After a drop operation the ItemDropInputMode<T> delegates the creation of an item to a callback that can be set using the itemCreator property. The callback gets all information needed to create a copy of the dragged item in the GraphComponent’s graph:

Parameter list of the itemCreator
Parameter Description
contextThe IInputModeContext for which the item should be created.
graphThe graph in which to create the item.
draggedItemThe item that was dragged and should therefore be created. Can serve as blueprint for a copy.
dropTargetThe item on which the item is dropped, e.g. a potential parent group node. Might be null.
dropLocationThe location where the item should be created.

A newly created default ItemDropInputMode<T> without a set itemCreator doesn’t do anything on its own. You have to either set an itemCreator or, in the case of INode or IStripe, use the subclasses NodeDropInputMode or StripeDropInputMode. They have an itemCreator set by default to handle dropping the items that they are specified for.

Preview within the GraphComponent

The GraphComponent can display a preview of the currently dragged item with proper snapping support while dragging within the GraphComponent. There are several protected methods that you can override to customize the drop preview:

initializePreview
Is called when the drag enters the GraphComponent. The default implementation initializes a separate graph model that is meant to contain the preview items. It then calls the following two methods:
populatePreviewGraph
Subclasses are meant to create items that act as a drop preview in this method. The given IGraph instance is not the IGraph of the GraphComponent, but a separate instance that only contains the preview items. The base implementation does nothing.
updatePreview
Called for each drag event that occurs while dragging over the GraphComponent. Again, the given IGraph instance is not the IGraph of the GraphComponent but a separate instance that only contains the preview items. The derived classes should update the drop preview items in this method accordingly, e.g. adjusting the preview item’s layout to the drag location or similar.
cleanUpPreview
This method is called once the drag and drop operation is completed or canceled and gives client the chance to free resources etc.

The following example shows how to customize the preview to make the preview item transparent:

Drop preview customization example
class CustomPreviewNodeDropInputMode extends NodeDropInputMode {
  initializePreview() {
    // call super method that generates a preview node in the preview graph
    super.initializePreview()
    const previewNode = this.previewGraph.nodes.first()
    // assume ShapeNodeStyle for sake of brevity
    const nodeStyle = previewNode.style.clone()
    // important: clone the style to not alter the original style,
    // because the reference to that style is used for the new node
    const color = nodeStyle.fill.color // assume it's a solid color fill
    // use the same color, but transparent
    const transparentFill = new SolidColorFill(Color.fromArgb(100, color.r, color.g, color.b))
    transparentFill.freeze()
    nodeStyle.fill = transparentFill
    this.previewGraph.setStyle(previewNode, nodeStyle)
  }
}

const nodeDropInputMode = new CustomPreviewNodeDropInputMode()
graphEditorInputMode.nodeDropInputMode = nodeDropInputMode
class CustomPreviewNodeDropInputMode extends NodeDropInputMode {
  initializePreview(): void {
    // call super method that generates a preview node in the preview graph
    super.initializePreview()
    const previewNode = this.previewGraph!.nodes.first()
    // assume ShapeNodeStyle for sake of brevity
    const nodeStyle = previewNode.style.clone() as ShapeNodeStyle
    // important: clone the style to not alter the original style,
    // because the reference to that style is used for the new node
    const color = (nodeStyle.fill as SolidColorFill).color // assume it's a solid color fill
    // use the same color, but transparent
    const transparentFill = new SolidColorFill(Color.fromArgb(100, color.r, color.g, color.b))
    transparentFill.freeze()
    nodeStyle.fill = transparentFill
    this.previewGraph!.setStyle(previewNode, nodeStyle)
  }
}

const nodeDropInputMode = new CustomPreviewNodeDropInputMode()
graphEditorInputMode.nodeDropInputMode = nodeDropInputMode

Preview outside of the GraphComponent

The library provides direct support for two approaches to display a drop preview while dragging the element outside of the GraphComponent.

Providing a Custom Preview Element

A custom preview element can be provided as last argument of the startDrag method. A similar method is also available for the LabelDropInputMode, PortDropInputMode and StripeDropInputMode.

This approach needs pointer-events="none" support and thus does not work in IE9 and IE10.

The given element will automatically be included in the DOM and positioned below the pointer location during the drag operation. It will be removed when the gesture is canceled or the element is actually dropped into the GraphComponent.

We recommend to hide this custom preview element while the dragging location is within the GraphComponent and let the ItemDropInputMode<T> display its preview element instead because only then, item snapping can be considered and properly applied to the dragged item.

To hide the custom preview element, a suitable CSS class may be applied when the drag gesture enters the GraphComponent or removed again when the gesture leaves the GraphComponent. The QueryContinueDragEventArgs of the DragSource return value of startDrag. provides a reference to the current DropTarget which can be used to determine if there is a DropTarget that can handle the visualization of the preview element.

Custom drop preview element example
// create a drag preview element for a node that is shown during the drag
const dragPreview = document.getElementById('myCustomNodePreviewElement')

// start the drag
const dragSource = NodeDropInputMode.startDrag(dragElement, simpleNode, DragDropEffects.ALL, true, dragPreview)

// let the GraphComponent handle the preview rendering if possible
dragSource.addQueryContinueDragListener((src, args) => {
  if (args.dropTarget === null) {
    removeClass(dragPreview, 'hidden')
  } else {
    addClass(dragPreview, 'hidden')
  }
})

The Simple Drag And Drop demo illustrates this approach.

Using native HTML5 Drag and Drop API

The native HTML5 drag and drop API is also supported and may be used as alternative approach for showing a drag preview that is also visible outside of the GraphComponent.

In contrast to the approach of providing a custom preview element, there is no way of letting the ItemDropInputMode<T> take control while the dragged element is within the GraphComponent due to how the HTML5 drag and drop API is designed. Therefore, there is no previewGraph and no snapping support.

To use this approach, the draggable elements need to have the draggable attribute set to true as well as a dragstart listener that configures some drop properties and also sets the transfer data to an item ID that represents the actual dragged graph item. Also, a customized itemCreator needs to provide different items to insert into the graph. The transferred data in the drag event may only be strings, which is why we need to use a map from the node id to the actual node item which holds the information about the node’s layout, style, tag etc.

Native HTML5 drag and drop example
// this map is used to get the node instance from the string data of the data transfer during 'drop'
const nodes = new Map()

// fill the map with the draggable nodes
for (let i = 0; i < draggableNodeItems.length; i++) {
  // the actual node item that is dragged
  const node = draggableNodeItems[i]

  // the node ID is used to get the node instance from the data transfer during 'drop'
  const nodeID = `node ${i}`
  nodes.set(`node ${i}`, node)

  // creates a div with draggable="true" that contains a representation of the dragged node
  const nodeElement = createNodeVisual(node, exportGraphComponent)

  // set the node ID as data of the data transfer and configure some other drop properties
  nodeElement.addEventListener('dragstart', (e) => {
    e.dataTransfer.dropEffect = 'copy'
    e.dataTransfer.effectAllowed = 'copy'
    e.dataTransfer.setData('INode', nodeID)
  })

  // finally, add the visual to palette
  dragDropPalette.appendChild(nodeElement)
}

// make the item creator return the node according to the given id
const oldItemCreator = nodeDropInputMode.itemCreator
nodeDropInputMode.itemCreator = (context, graph, info, dropTarget, dropLocation) => {
  const node = typeof info === 'string' ? nodes.get(info) : info
  // The old item creator handles the placement of the new node in the graph
  return oldItemCreator(context, graph, node, dropTarget, dropLocation)
}
// this map is used to get the node instance from the string data of the data transfer during 'drop'
const nodes = new Map()

// fill the map with the draggable nodes
for (let i = 0; i < draggableNodeItems.length; i++) {
  // the actual node item that is dragged
  const node = draggableNodeItems[i]

  // the node ID is used to get the node instance from the data transfer during 'drop'
  const nodeID = `node ${i}`
  nodes.set(`node ${i}`, node)

  // creates a div with draggable="true" that contains a representation of the dragged node
  const nodeElement = createNodeVisual(node, exportGraphComponent)

  // set the node ID as data of the data transfer and configure some other drop properties
  nodeElement.addEventListener('dragstart', (e) => {
    e.dataTransfer!.dropEffect = 'copy'
    e.dataTransfer!.effectAllowed = 'copy'
    e.dataTransfer!.setData('INode', nodeID)
  })

  // finally, add the visual to palette
  dragDropPalette.appendChild(nodeElement)
}

// make the item creator return the node according to the given id
const oldItemCreator = nodeDropInputMode.itemCreator!
nodeDropInputMode.itemCreator = (
  context: IInputModeContext,
  graph: IGraph,
  info: any,
  dropTarget: IModelItem | null,
  dropLocation: Point
): any => {
  const node = typeof info === 'string' ? nodes.get(info) : info
  // The old item creator handles the placement of the new node in the graph
  return oldItemCreator(context, graph, node, dropTarget, dropLocation)
}

Drag and Drop of INodes

The NodeDropInputMode is a specialized ItemDropInputMode<T> for nodes. It specifies the expected format to be ‘INode’.

To customize the creation process of the item that is being dropped, override the createNode method.

The default implementation of the createNode method is very sophisticated and minimizes the effort that is necessary to customize node dropping behavior. We’d recommend to try to configure the NodeDropInputMode properties and override the auxiliary methods like getNodeLayout first to achieve the desired behavior before resorting to override this method. Even then it is probably wise to let the derived class call the super method to let it perform the actual node creation first and then apply the desired customizations to the created node instead of implementing the whole process yourself.

An example for an application that uses the NodeDropInputMode to drag nodes from a palette and drop them into the GraphComponent is the Drag and Drop

Behavior of NodeDropInputMode

In the default configuration, the NodeDropInputMode manages dropping nodes directly into groups. The created node will then be part of that group. You can customize what groups are valid targets for the drop by setting an appropriate isValidParentPredicate.

Furthermore, you can enable additional grouping/folding features on the NodeDropInputMode. For example, you can instruct it to automatically convert regular nodes to group nodes when dragging and dropping a node on it by enabling the allowNonGroupNodeAsParent property.

Additionally, enabling allowFolderNodeAsParent lets you drop nodes into collapsed group nodes, adding them to the corresponding group nodes as well.

Customizing the Dropped Node

NodeDropInputMode provides hooks to alter the node that was created in the IGraph as a result of the drag and drop operation after the operation is completed.

You can:

Drag and Drop of ILabels

The LabelDropInputMode is a specialized ItemDropInputMode<T> for labels which specifies the expected data format to be ILabel.

To customize the creation process of the item that is being dropped, override the createLabel method.

The default implementation of the createLabel queries the lookup of the provided ILabelOwner for an IEditLabelHelper.

Customizing the Dropped Label

LabelDropInputMode provides hooks to alter the label that was created in the IGraph as a result of the drag and drop operation after the operation is completed.

You can:

  • Change the ILabelModelParameter of the dropped label by overriding getNewLabelModelParameter.
  • Change the owner of the newly created label by overriding getDropTarget. Note that returning null will prevent a label from being created at the given location.
  • Make alterations to the created label by overriding createLabel[] as mentioned earlier.

Drag and Drop of IPorts

The PortDropInputMode is a specialized ItemDropInputMode<T> for ports which specifies the expected data format to be IPort.

To customize the creation process of the item that is being dropped, override the createPort method.

The default implementation of the createPort method also copies the labels from the dragged port to the newly created port.

Customizing the Dropped Port

PortDropInputMode provides hooks to alter the port that was created in the IGraph as a result of the drag and drop operation after the operation is completed.

You can:

  • Change the IPortLocationModelParameter of the dropped port by overriding getNewPortLocationModelParameter.
  • Change the owner of the newly created port by overriding getDropTarget. Note that returning null will prevent a port from being created at the given location.
  • Make alterations to the created port by overriding createPort as mentioned earlier.