Dragger

A dragger is a controller object that coordinates dragging draggables in response to user interactions.

There are very few circumstances where you would want to implement a custom dragger, because there's not a lot that you might want to customize about coordinating a drag. The scroll-options plugin implements a custom dragger because it wanted to add scrolling at the edge of the workspace, which changes how pixel coordinates are converted to workspace coordinates.

Responsibilities

The dragger has several responsibilities when executing drags:

  • Calling drag methods on the draggable.
  • Calculating the position the draggable should move to in workspace coordinates.
  • Calling drag target methods on any hovered drag targets.

Implementation

To create a custom dragger you have to implement the IDragger interface.

class MyDragger implements IDragger {
  // Takes in the draggable being dragged and the workspace the drag
  // is occurring in.
  constructor(draggable, workspace);
}

You can also subclass the built-in Blockly.dragging.Dragger, which handles the basic responsibilities already.

Start drags

The onDragStart method initializes a drag. It should store any data needed to execute the drag. It should also call startDrag on the draggable being dragged.

onDragStart(e) {
  this.startLoc = this.draggable.getRelativeToSurfaceXY();

  this.draggable.startDrag(e);
}

Drag

The onDrag method executes a drag. It should calculate the new workspace position for the draggable based on the totalDelta, which is given in pixel coordinates.

It should also update any drag targets that are being hovered over.

  • wouldDelete should always be called before calling other hooks on the drag target.
  • onDragExit should always be called on the old drag target before calling onDragEnter on the new drag target.
  • onDragOver should be called after onDragEnter the first time the drag target is hovered, and on each additional call to onDrag where the drag target is still hovered.
onDrag(e, totalDelta) {
  // Update the draggable location.
  const delta = this.pixelsToWorkspaceUnits(totalDelta);
  const newLoc = Coordinate.sum(this.startLoc, delta);
  this.draggable.drag(newLoc, e);

  // Call wouldDeleteDraggable.
  if (isDeletable(this.draggable)) {
    this.draggable.setDeleteStyle(
      // Checks that the drag target is an `IDeleteArea` and calls `wouldDelete`
      // on it.
      this.wouldDeleteDraggable(e, this.draggable),
    );
  }

  const newDragTarget = this.workspace.getDragTarget(e);
  if (this.dragTarget !== newDragTarget) {
    // Call `onDragExit` then `onDragEnter`.
    this.dragTarget?.onDragExit(this.draggable);
    newDragTarget?.onDragEnter(this.draggable);
  }
  // Always call `onDragOver`
  newDragTarget?.onDragOver(this.draggable);
  this.dragTarget = newDragTarget;
}

End drags

The onEndDrag method ends a drag. It should notify the draggable that the drag has ended and any hovered drag target that the draggable has been dropped. It should also dispose of the draggable if the drag target is a delete area.

  • onDrop should always be called before other methods.
  • revertDrag should be called if the drag target prevents drags.
  • endDrag should be called after reverting the drag, but before disposing.
  • dispose should be called if the drag target is a delete area.
onDragEnd(e) {
  // Call `onDrop` first.
  const dragTarget = this.workspace.getDragTarget(e);
  if (dragTarget) {
    this.dragTarget?.onDrop(this.draggable);
  }

  // Then revert the drag (if applicable).
  if (this.shouldReturnToStart(e, this.draggable)) {
    this.draggable.revertDrag();
  }

  // Then end the drag.
  this.draggable.endDrag(e);

  // Then delete the draggable (if applicable).
  if (
    isDeletable(this.draggable) &&
    this.wouldDeleteDraggable(e, this.draggable)
  ) {
    this.draggable.dispose();
  }
}

Registration

Your dragger class needs to be registered so that it can be created when drags are detected.

// Note that the type is BLOCK_DRAGGER even though draggers drag more than
// blocks. The name is for backwards compatibility.
Blockly.registry.register(registry.Type.BLOCK_DRAGGER, 'MY_DRAGGER', MyDragger);

Usage

After you have implemented your custom dragger you can use it by passing it to your injection config struct.

const myWorkspace = Blockly.inject('blocklyDiv', {
  plugins: {
    // Note that we pass this to blockDragger even though draggers drag more
    // than blocks. The name is for backwards compatibility.
    blockDragger: MyDragger,
  },
});