Draggable

A draggable is a rendered object that exists in the workspace and can be dragged and dropped. They implement the IDraggable interface.

There are very few circumstances when you would want to add a new draggable to Blockly (e.g. the multiselect plugin, or changing how an existing object handles drags), because you cannot add new rendered objects to Blockly. The only rendered objects that can exist within a workspace are blocks, bubbles, and workspace comments.

Responsibilities

Draggables have several responsibilities when executing drags:

  • Moving svg elements to the drag layer.
  • Translating svg elements.
  • Firing move events.

Implementation

To create a new draggable, you have to implement the IRenderedElement and IDraggable interfaces. This lets Blockly know that your object is visible and can be dragged.

class MyDraggable extends IRenderedElement, IDraggable {}

Return the root SVG element

The getRootSvg method returns the root svg element (usually a group) that holds all of the other elements making up the view for the draggable.

getSvgRoot() {
  return this.rootSvg;
}

Return movability

The isMovable method returns whether the draggable is currently movable (since you may want to temporarily disable dragging an object). If isMovable returns false the workspace is dragged instead.

isMovable() {
  return true;
}

Return position

The getRelativeToSurfaceXY method returns a Coordinate specifying the location of the top-start corner of the draggable in workspace coordinates.

Workspace coordinates have an origin at the absolute left and absolute top of the workspace. And they don't change when the workspace is scaled or moved.

getRelativeToSurfaceXY() {
  return this.loc;
}

Start drags

The startDrag method initializes a drag on the draggable. This method doesn't move the draggable. But you should store any data or construct any objects you need to complete the drag. This includes any data you would need to revert the drag if revertDrag is called.

It should also change the svg elements to be on the drag layer of the workspace, so they exist above any other elements.

It also takes in an event, which you can use to check for pressed keys. This lets you (for example) treat a drag while shifting differently than a normal drag.

startDrag(e) {
  // Save the original location so we can revert the drag.
  this.startLoc = this.getRelativeToSurfaceXY();

  // Disable resizing the workspace for performance.
  this.workspace.setResizesEnabled(false);

  // Put the element on the drag layer.
  this.workspace.getLayerManager()?.moveToDragLayer(this);

  // Fire a drag event...

  // etc...
}

Drag

The drag method actually moves the draggable object. The newLoc is in workspace coordinates, and there is also an event passed that you can use to check for pressed keys.

drag(newLoc, e) {
  this.moveTo(newLoc);
}

Revert drags

The revertDrag method returns the draggable to the position it was at at the beginning of the drag. This happens if, for example, the draggable is dropped on a drag target that prevents movement.

revertDrag() {
  // Move back to the position that was stored in `startDrag`.
  this.moveTo(this.startLoc);
}

End drags

The endDrag method cleans up a drag, releasing any stored data or objects, and returning the draggable to its original layer.

endDrag is always called after revertDrag if revertDrag is called.

endDrag() {
  // Put the element back on its original layer (in this case BLOCK).
  this.workspace
    .getLayerManager()
    ?.moveOffDragLayer(this, Blockly.layers.BLOCK);

  // Allow the workspace to start resizing again.
  this.workspace.setResizesEnabled(true);
}

Selection

The element that gets dragged is determined by the element that is selected when a drag is detected.

ISelectable

To be selected a draggable must implement the ISelectable interface.

class MyDraggable implements ISelectable {
  constructor(workspace) {
    this.id = Blockly.utils.idGenerator.genUid();
    this.workspace = workspace;
  }

  select() {
    // Visually indicate this draggable is selected.
  }

  unselect() {
    // Visually indicate this draggable is not selected.
  }
}

Set selection

The selected element can be set by calling Blockly.common.setSelected(). usually you'll want to do this in response to a pointerdown event from the user.

  constructor() {
    this.initSvg();

    Blockly.browserEvents.conditionalBind(
      this.getSvgRoot(),
      'pointerdown',
      this,
      () => Blockly.common.setSelected(this));
  }

Compatibility

Your draggable can implement additional interfaces so that it can interact with other systems in Blockly.

Deletable

You can implement the IDeleteable interface to allow your draggable to be disposed by the trashcan or other delete targets.

class MyDraggable implements IDeletable {
  isDeletable() {
    return true;
  }

  dispose() {
    // Dispose of any references to data or SVG elements.
  }

  setDeleteStyle() {
    // Visually indicate that the draggable is about to be deleted.
  }
}

Copyable

You can implement the ICopyable interface to allow your draggable to be copied and define an IPaster to allow it to be pasted.

For more information about copying pasting see Copy paste.