Skip to content

Library: DnD start()

Eugene Lazutkin edited this page Jul 3, 2018 · 6 revisions

start() module is a function, which initializes a DOM node for DnD operations. After that, a user can interact with the DOM node as a container of items, which can be picked up and dragged.

The demo: https://researchnow.github.io/reno/pages/dnd.html

Usage

In a module-enabled environment (like Babel) it can be accessed like that:

import start from '@researchnow/reno/src/utils/dnd/start';

In global-based environments (like a browser) it is frequently mapped to Reno.utils.dnd.start.

start(container, options)

The function takes the following arguments:

  • container is a DOM node, which contains DnD items.
  • options is an object, which contains properties guiding DnD operations:
    • filter is a filter object used by on() to detect clickable items. The default: '.reno-dnd-item'.
      • Usually it is a CSS selector for matches(). Every mouse-down is tested if it is done on a DOM node that satisfies the selector.
      • In complex cases it can be a function. See on() documentation for details.
    • init is a function to initialize a Move object (described below) when a user starts a DnD operation.
      • It takes one argument: a newly-created Move object.
      • The default: a no-op function.
    • destroy is a function to clean up when a DnD operation is finished. The default: a no-op function.
    • avatarClass is a CSS class to use for an avatar (a dragged element created after a DnD operation was started). It is used by the default avatar creator (see makeAvatar below). The default: 'reno-dnd-avatar'.
    • makeAvatar is a function, which takes a Move object (contains the clicked DOM node), and produces a new DOM node, which will be dragged. It should be absolutely positioned and in a proper place. The default is a function, which clones the clicked DOM node, and does all prep and positioning.
    • moving is a function, which takes a Move object and a mousemove event object. It updates a position of the avatar by updating the Move object. The default: a function that linearly "moves" the avatar:
      const moving = (mover, e) => {
        mover.x += e.pageX - mover.mouseX;
        mover.y += e.pageY - mover.mouseY;
      };
    • over is a function, which is called after repositioning the avatar to reflect changes (if any) to underlying ("under the avatar") DOM nodes. It is called with a single argument: a Move object. The default: a no-op function.
    • drop is a function, which is called when a user "dropped" the avatar. It is called with a single argument: a Move object. The default: a no-op function.
    • target is a CSS selector to find valid DnD drop targets. The default: '.reno-dnd-item'.

The returned value is a handle from on(), which is set up to handle mousedown event. It is an object with following methods:

  • remove() removes an event handler canceling future DnD operations.
  • pause() pauses an event handler temporarily.
  • resume() resumes an event handler.

Move

This is an object created when a user initiated a DnD operation. It is used to hold all relevant information and passed through to various callback functions defined in options.

Callbacks can use the following public properties:

  • container is a DOM node passed to start() described above.
  • options is an object passed to start() described above. It can be used to pass custom options as well.
  • node is a DOM node, which is being dragged.
  • mouseX and mouseY are the previous mouse position. It can be used to calculate how the avatar was moved.
  • avatar is a DOM node, which is actually being moved instead of node. It is produced by makeAvatar option described above.

Callbacks may define their own properties and methods if needed.

When a user initiates a DnD operation by pressing a mouse button down certain DOM nodes will be ignored:

  • A valid node should satisfy a filter requirement, e.g., it should have reno-dnd-item CSS class.
  • It should not be a form element or a link element.
  • It should not have reno-dnd-ignore CSS class.

HTML

The DnD package does not produce HTML but consumes whatever a markup produced by a user. During a DnD operation, CSS classes can be updated, and an avatar is created, which is usually attached to a body.

CSS

The following CSS classes are used by the DnD package:

  • User-supplied mark-up classes:
    • reno-dnd-item marks a draggable object. It can be used to visually highlight what can be dragged to avoid user's confusion.
      • This class is a default filter described above. It can be overridden or ignored.
    • reno-dnd-ignore marks an otherwise valid node as non-draggable.
  • The dragged node:
    • reno-dnd-dragged is assigned to the dragged item. It can be used to highlight what is being dragged.
  • Other items:
    • reno-dnd-over is assigned by default callbacks to drop targets, which are under the avatar at the moment.
  • Avatar:
    • reno-dnd-avatar is assigned to the avatar.
  • Container:
    • reno-dnd-dragged-container is assigned to a container, which contains the dragged node. It can be used to highlight the container.
  • Body (more specifically document.documentElement):
    • reno-dnd-in-flight is assigned when a DnD operation is in progress. It can be used to tone down the web page while dragging.

Examples

Rearrange a vertical list:

<div id="v">
  <div class="reno-dnd-item">One</div>
  <div class="reno-dnd-item">Two</div>
  <div class="reno-dnd-item">Three</div>
  <div class="reno-dnd-item">Four</div>
  <div class="reno-dnd-item">Five</div>
</div>

The code supports drops and restricts a horizontal movement.

Reno.utils.dnd.start(document.getElementById('v'), {
  init: Reno.utils.dnd.init,
  over: Reno.utils.dnd.over,
  drop: Reno.utils.dnd.rearrangeable.dropY,
  moving: Reno.utils.dnd.movingY
});

Group items:

<div id="g">
  <div class="reno-dnd-item reno-dnd-target">One</div>
  <div class="reno-dnd-item reno-dnd-target">Two</div>
  <div class="reno-dnd-item reno-dnd-target">Three</div>
  <div class="reno-dnd-item reno-dnd-target">Four</div>
  <div class="reno-dnd-item reno-dnd-target">Five</div>
</div>

The corresponding code:

Reno.utils.dnd.start(document.getElementById('g'), {
  target: '.reno-dnd-target',
  init: mover => {
    Reno.utils.dnd.init(mover);
    mover.avatar.classList.remove('reno-dnd-target');
  },
  over: Reno.utils.dnd.over,
  drop: mover => {
    if (mover.previousOverItem) {
      if (mover.previousOverItem !== mover.node) {
        let target = mover.previousOverItem;
        if (target.classList.contains('reno-dnd-item')) {
          target = target.ownerDocument.createElement('div');
          target.classList.add('reno-dnd-target');
          mover.previousOverItem.classList.remove('reno-dnd-target');
          mover.previousOverItem.parentNode.replaceChild(
            target, mover.previousOverItem);
          target.appendChild(mover.previousOverItem);
        }
        mover.node.classList.remove('reno-dnd-target');
        target.appendChild(mover.node);
      }
    } else {
      const parent = mover.node.parentNode;
      mover.container.appendChild(mover.node);
      mover.node.classList.add('reno-dnd-target');
      if (!parent.firstElementChild) {
        parent.parentNode.removeChild(parent);
      }
    }
  }
});

The results can be seen in the demo page: https://researchnow.github.io/reno/pages/dnd.html

Clone this wiki locally