html5_dnd 0.3.6+2

  • Readme
  • Changelog
  • Example
  • Installing
  • 0

HTML5 Drag and Drop for Dart #

Note: This library is not maintained any more. #

Deprecated stuff... #

Features #

  • Make any HTML Element draggable.
  • Create dropzones and connect them with draggables.
  • Rearrange elements with sortables (similar to jQuery UI Sortable).
  • Support for touch events on touch screen devices.
  • Same functionality and API for IE9+, Firefox, Chrome and Safari.
  • Uses fast native HTML5 Drag and Drop of the browser whenever possible.
  • For browsers that do not support some features, the behaviour is emulated. This is the case for IE9 and partly for IE10 (when custom drag images are used).

Usage #

See the demo page above or the example directory to see some live examples with code.

In general, to make drag and drop work, we will have to do two things:

  1. Create draggables by installing HTML elements in a DraggableGroup.
  2. Create dropzones by installing HTML elements in a DropzoneGroup.

To make elements sortable it's even easier: Create sortables by installing HTML elements in a SortableGroup. The SortableGroup will make the installed elements both into draggables and dropzones and thus creates sortable behaviour.

Disable Touch Support #

There is a global property called enableTouchEvents which is true by default. This means that touch events are automatically enabled on devices that support it. If touch support should not be used even on touch devices, set this flag to false.

Draggables #

Any HTML element can be made draggable. First we'll have to create a DraggableGroup that manages draggable elements. The DraggableGroup holds all options for dragging and provides event streams we can listen to.

This is how a DraggableGroup is created. With install(...) or installAll(...) elements are made draggable and registered in the group.

// Creating a DraggableGroup and installing some elements.
DraggableGroup dragGroup = new DraggableGroup();

With uninstall(...) or uninstallAll(...) draggables can be removed from the group and the draggable behaviour is uninstalled.

Draggable Options

The DraggableGroup has three constructor options:

  • The dragImageFunction is used to provide a custom DragImage. If no dragImageFunction is supplied, the drag image is created from the HTML element of the draggable.
  • If a handle is provided, it is used as query String to find subelements of draggable elements. The drag is then restricted to those elements.
  • If cancel is provided, it is used as query String to find a subelement of drabbable elements. The drag is then prevented on those elements. The default is 'input,textarea,button,select,option'.
// Create a custom drag image from a png.
ImageElement png = new ImageElement(src: 'icons/smiley-happy.png');
DragImage img = new DragImage(png, 0, 0);

// Always return the same DragImage here. We could also create a different image 
// for each draggable.
DragImageFunction imageFunction = (Element draggable) {
  return img;

// Create DraggableGroup with custom drag image and a handle.
DraggableGroup dragGroupHandle = new DraggableGroup(
    dragImageFunction: imageFunction, handle: '.my-handle');
// Create DraggableGroup with a cancel query String.
DraggableGroup dragGroupCancel = new DraggableGroup(
    cancel: 'textarea, button, .no-drag');

Other options of the DraggableGroup:

DraggableGroup dragGroup = new DraggableGroup();

// CSS class set to html body during drag.
dragGroup.dragOccurringClass = 'dnd-drag-occurring';

// CSS class set to the draggable element during drag.
dragGroup.draggingClass = 'dnd-dragging';

// CSS class set to the dropzone when a draggable is dragged over it.
dragGroup.overClass = 'dnd-over';

// Changes mouse cursor when this draggable is dragged over a draggable.
dragGroup.dropEffect = DROP_EFFECT_COPY; 

Draggable Events

We can listen to dragStart, drag, and dragEnd events of a DraggableGroup.

DraggableGroup dragGroup = new DraggableGroup();

dragGroup.onDragStart.listen((DraggableEvent event) => print('drag started'));
dragGroup.onDrag.listen((DraggableEvent event) => print('dragging'));
dragGroup.onDragEnd.listen((DraggableEvent event) => print('drag ended'));

Dropzones #

Any HTML element can be made to a dropzone. Similar to how draggables are created, we create a dropzones:

// Creating a DropzoneGroup and installing some elements.
DropzoneGroup dropGroup = new DropzoneGroup();

Dropzone Options

The DropzoneGroup has an option to specify which DraggableGroups it accepts. If no accept group is specified, the DropzoneGroup will accept all draggables.

DraggableGroup dragGroup = new DraggableGroup();
// ... install some draggable elements ...

DropzoneGroup dropGroup = new DropzoneGroup();
// ... install some dropzone elements ...

// Make dropGroup only accept draggables from dragGroup.

Dropzone Events

We can listen to dragEnter, dragOver, dragLeave, and drop events of a DropzoneGroup.

DropzoneGroup dropGroup = new DropzoneGroup();

dropGroup.onDragEnter.listen((DropzoneEvent event) => print('drag entered'));
dropGroup.onDragOver.listen((DropzoneEvent event) => print('dragging over'));
dropGroup.onDragLeave.listen((DropzoneEvent event) => print('drag left'));
dropGroup.onDrop.listen((DropzoneEvent event) => print('dropped inside'));

Sortables #

For reordering of HTML elements we can use sortables.

// Creating a SortableGroup and installing some elements.
SortableGroup sortGroup = new SortableGroup();

Note: All sortables are at the same time draggables and dropzones. This means we can set all options of DraggableGroup and DropzoneGroup on sortables and listen to all their events.

Sortable Options

In addition to the inherited DraggableGroup and DropzoneGroup options, SortableGroup has the following options:

SortableGroup sortGroup = new SortableGroup();

// CSS class set to placeholder element. 
sortGroup.placeholderClass = 'dnd-placeholder';

// If true, forces the placeholder to have the computed size of the dragged element.
sortGroup.forcePlaceholderSize = true;

// Must be set to true for sortable grids. This ensures that different sized
// items in grids are handled correctly.
sortGroup.isGrid = false;

Sortable Events

Next to the inherited DraggableGroup and DropzoneGroup events SortableGroup has one additional event:

SortableGroup sortGroup = new SortableGroup();

sortGroup.onSortUpdate.listen((SortableEvent event) => print('elements were sorted'));

Thanks and Contributions #

I'd like to thank the people who kindly helped me with their answers or put some tutorial or code examples online. They've indirectly contributed to this project.

If you'd like to contribute, you're welcome to report issues or fork my repository on GitHub.

License #

The MIT License (MIT)

Changelog #

Version 0.3.5 (2013-11-11) #

  • Update to dart libraries 0.9.0.

Version 0.3.4 (2013-10-08) #

  • Fix Issue #13: html5_dnd is incompatible with js-interop and the latest SDK
    • Remove dependencies on meta package
    • Add version constraints on dependencies

Version 0.3.3 (2013-07-09) #

  • Support dragging of SVG elements (Issue #7). Dragging of SVG elements uses the emulated mode in all browsers.
  • Add tests for dragging SVG elements.

Version 0.3.2 (2013-06-28) #

  • Support 'cancel' query String to prevent dragging: A 'cancel' query String may be supplied for draggables to prevent starting a drag on specific elements.
  • Improve clearing of selection when drag starts.
  • Fix some bugs in touch support.
  • Firing sortUpdate events after dragEnd events: Firing sortUpdate at the end makes sure that the listener of update events can uninstall/install involved draggables without side-effects
  • Add some additional test examples.

Version 0.3.1 (2013-06-26) #

  • Touch Event support (Issue #3): Uses touchStart, touchMove, and touchEnd events to emulate HTML5 drag and drop behaviour.
  • Reorganized some parts. Now only html5_dnd.dart needs to be imported and sortable is imported automatically. If some functionality like sortable isn't used, Dart's treeshaking will make sure no unnecessary code is added.
  • Add extended usage documentation to readme.

Version 0.3.0 (2013-06-25) #

  • Completely emulating drag and drop in IE9 and partly in IE10 (when custom drag images are used):
    • The workaround with calling dragDrop() on IE did not work reliably and was slow. Also, we could not have the drag image under the mouse cursor as events would not be forwarded to element underneath.
    • No javascript file is needed any more and the dependency on js-interop has been removed.
    • Emulation works by listening to mouseDown, mouseUp and mouseMove events and translating them to the HTML5 dragStart, drag, dragEnd, dragEnter, dragOver, dragLeave and drop events.
  • More stable handling of nested elements. Instead of keeping track of dragOverElements in a list, the related target of the event is used to determine if it is an event that happened on the main element or bubbled up from child elements.

Version 0.2.1 (2013-06-17) #

  • Fix Issue #6: Bug in Firefox when dragging over nested elements
  • Fix Issue #1: overClass (.dnd-over) stays after drag ended
  • Fix Issue #4: Support any HTML Element as drag image
  • Fix Issue #5: Always use Drag Image Polyfill for IE9 drags

Version 0.2.0 (2013-06-10) #

  • Changed API to using groups of Draggables/Dropzones/Sortables. Now options and event listeners are set on a group instead of individual elements.
  • Ability to install/uninstall elements. Adds/Removes event subscriptions on an element.
  • SortableEvent now carries information about the original group of the dragged element and the new group it was dragged to. This enables uninstalling in the previous group and installing in the new group.
  • Other minor improvements in Sortable.

Version 0.1.0 (2013-06-05) #

  • First Version.


library html5_dnd_example;

import 'dart:html';

import 'package:intl/intl.dart';
import 'package:logging/logging.dart';
import 'package:html5_dnd/html5_dnd.dart';

main() {
  // Uncomment to enable logging.
  // Install Drag and Drop examples.
  // Install Sortable examples.

  // Used for the code examples on the blog website.
//  _installCodeblockTabs();

initLogging() {
  DateFormat dateFormat = new DateFormat(' HH:mm:ss.SSS');
  // Print output to console.
  Logger.root.onRecord.listen((LogRecord r) {
  // Root logger level.
  Logger.root.level = Level.FINEST;

sectionDraggableAndDropzone() {
  // Install draggables (documents).
  DraggableGroup dragGroup = new DraggableGroup()
  ..installAll(querySelectorAll('#draggable-dropzone .document'));
  // Install dropzone (trash).
  DropzoneGroup dropGroup = new DropzoneGroup()
  ..install(querySelector('#draggable-dropzone .trash'))
  ..onDrop.listen((DropzoneEvent event) {

sectionDraggingDivs() {
  // Install draggable.
  DraggableGroup dragGroup = new DraggableGroup()
  ..install(querySelector('#dragging-divs .dragme'));
  // Install dropzone.
  DropzoneGroup dropGroup = new DropzoneGroup()
  ..install(querySelector('#dragging-divs .dropzone'))

sectionDropEffects() {
  // Install draggables.
  DraggableGroup dragGroupMove = new DraggableGroup()
  ..dropEffect = DROP_EFFECT_MOVE
  ..install(querySelector('#drop-effects .move'));
  DraggableGroup dragGroupCopy = new DraggableGroup()
  ..dropEffect = DROP_EFFECT_COPY
  ..install(querySelector('#drop-effects .copy'));
  DraggableGroup dragGroupLink = new DraggableGroup()
  ..dropEffect = DROP_EFFECT_LINK
  ..install(querySelector('#drop-effects .link'));
  DraggableGroup dragGroupNone = new DraggableGroup()
  ..dropEffect = DROP_EFFECT_NONE
  ..install(querySelector('#drop-effects .none'));
  // Install dropzone.
  DropzoneGroup dropGroup = new DropzoneGroup()
  ..install(querySelector('#drop-effects .trash'))
  ..accept.addAll([dragGroupMove, dragGroupCopy, dragGroupLink, dragGroupNone])
  ..onDrop.listen((DropzoneEvent event) {

sectionDragImages() {
  ImageElement png = new ImageElement(src: 'icons/smiley-happy.png');
  CanvasElement canvas = new CanvasElement();
  var ctx = canvas.context2D
      ..fillStyle = "rgb(200,0,0)"
      ..fillRect(10, 10, 55, 50);
  var dataUrl = canvas.toDataUrl("image/jpeg", 0.95);
  //Create a new image element from the data URL.
  ImageElement canvasImage = new ImageElement(src: dataUrl);
  // Install draggables.
  DraggableGroup dragGroupOne = new DraggableGroup(
      dragImageFunction: (Element draggable) => new DragImage(png, 40, 40))
  ..install(querySelector('#drag-images .one'));
  DraggableGroup dragGroupTwo = new DraggableGroup(
      dragImageFunction: (Element draggable) => new DragImage(png, -20, -20))
  ..install(querySelector('#drag-images .two'));
  DraggableGroup dragGroupThree = new DraggableGroup(
      dragImageFunction: (Element draggable) => new DragImage(canvasImage, 20, 20))
  ..install(querySelector('#drag-images .three'));
  // Install dropzone.
  DropzoneGroup dropGroup = new DropzoneGroup()
  ..install(querySelector('#drag-images .dropzone'))
  ..accept.addAll([dragGroupOne, dragGroupTwo, dragGroupThree]);

sectionNestedElements() {
  TextAreaElement textarea = querySelector('#nested-elements .dropzone textarea');
  InputElement input = querySelector('#nested-elements .dropzone input');
  input.value = 'Drag here!';
  textarea.text = '';
  int enterLeaveCounter = 1;
  int overCounter = 1;
  // Install draggables.
  DraggableGroup dragGroup = new DraggableGroup()
  ..install(querySelector('#nested-elements .dragme'));
  // Install dropzone.
  DropzoneGroup dropGroup = new DropzoneGroup()
  ..install(querySelector('#nested-elements .dropzone'))
  ..onDragEnter.listen((DropzoneEvent event) {
    textarea.appendText('${enterLeaveCounter++} drag enter fired\n');
    textarea.scrollTop = textarea.scrollHeight;
  ..onDragOver.listen((DropzoneEvent event) {
    input.value = '${overCounter++} drag over fired';
  ..onDragLeave.listen((DropzoneEvent event) {
    textarea.appendText('${enterLeaveCounter++} drag leave fired\n');
    textarea.scrollTop = textarea.scrollHeight;
  ..onDrop.listen((DropzoneEvent event) {
    textarea.appendText('${enterLeaveCounter++} drop fired\n');
    textarea.scrollTop = textarea.scrollHeight;

sectionSortableList() {
  SortableGroup sortGroup = new SortableGroup()
  ..installAll(querySelectorAll('#sortable-list li'))
  ..onSortUpdate.listen((SortableEvent event) {
    // do something when user sorted the elements...
  // Only accept elements from this section.

sectionSortableGrid() {
  SortableGroup sortGroup = new SortableGroup()
  ..isGrid = true
  ..installAll(querySelectorAll('#sortable-grid li'));
  // Only accept elements from this section.

sectionSortableListHandles() {
  SortableGroup sortGroup = new SortableGroup(handle: 'span')
  ..installAll(querySelectorAll('#sortable-list-handles li'));
  // Only accept elements from this section.

sectionCancelDrag() {
  // Install draggable.
  DraggableGroup dragGroup = new DraggableGroup(
      cancel: 'textarea, button, .nodrag')
  ..install(querySelector('#cancel-drag .dragme'));

sectionSortableTwoGroups() {
  ImageElement png = new ImageElement(src: 'icons/smiley-happy.png');
  SortableGroup sortGroup1 = new SortableGroup()
  ..installAll(querySelectorAll('#sortable-two-groups .group1 li'))
  ..onSortUpdate.listen((SortableEvent event) {
  SortableGroup sortGroup2 = new SortableGroup(
      dragImageFunction: (Element draggable) => new DragImage(png, 5, 5))
  ..installAll(querySelectorAll('#sortable-two-groups .group2 li'))
  ..onSortUpdate.listen((SortableEvent event) {
    if (event.originalGroup != event.newGroup) {
  // Only accept elements from this section.
  sortGroup1.accept.addAll([sortGroup1, sortGroup2]);
  sortGroup2.accept.addAll([sortGroup1, sortGroup2]);

_installCodeblockTabs() {
  List<AnchorElement> tabLinks = querySelectorAll('.example-code .menu li a');
  for (AnchorElement link in tabLinks) {
    link.onClick.listen((MouseEvent event) {
      Element exampleCodeParent = link.parent.parent.parent;
      // Remove active class on all menu and content tabs.
      exampleCodeParent.querySelectorAll('[tab]').forEach((Element e) {

      // Add active class.
      String currentTab = link.attributes['tab'];
      exampleCodeParent.querySelector('.content [tab="$currentTab"]').classes.add('active');

Use this package as a library

1. Depend on it

Add this to your package's pubspec.yaml file:

  html5_dnd: ^0.3.6+2

2. Install it

You can install packages from the command line:

with pub:

$ pub get

Alternatively, your editor might support pub get. Check the docs for your editor to learn more.

3. Import it

Now in your Dart code, you can use:

import 'package:html5_dnd/html5_dnd.dart';
Describes how popular the package is relative to other packages. [more]
Code health derived from static analysis. [more]
Reflects how tidy and up-to-date the package is. [more]
Weighted score of the above. [more]
Learn more about scoring.

The package version is not analyzed, because it does not support Dart 2. Until this is resolved, the package will receive a health and maintenance score of 0.

Analysis issues and suggestions

Support Dart 2 in pubspec.yaml.

The SDK constraint in pubspec.yaml doesn't allow the Dart 2.0.0 release. For information about upgrading it to be Dart 2 compatible, please see