Lean & Mean Drag and Drop is a small script for dragging, dropping, sorting and reordering html structures

Features
Supports nested structures ('nestable sortables')
Smooth transitions
Auto scroll while dragging
Lightweight (~3.5kb gzipped)
No dependencies
Super easy to integrate with your own app
Supports touch events
Compatible with all modern browsers
Can work with any type of layout
Usage
//Load LMDD css and js files
<link href="../css/lmdd.min.css" rel="stylesheet">
<script src="../js/lmdd.min.js"></script>

//Initialize LMDD instance with your preferred options
<script>lmdd.set(document.getElementById('markupID'),{optionsObject});</script>
Examples
  • speaker_notes

    The basic script is essentially the same for all cases. Set LMDD on an element (containing all that has to be dragged or nested in), and declare the relevant classes (containerClass,draggableItemClass & handleClass). The rest is done through proper HTML markup.

  • filter_1Simple Grid
    Green
    Pink
    Purple
    Blue
    Lime
    Teal
    Amber
    Orange
    Black
  • filter_2Multiple containers + handle
    Tasks
    reorder
    Code refactoring & optimizations
    reorder
    Cross browser testing
    Important Tasks
    reorder
    Call mom
    Done
    reorder
    Basic Drag & Drop script
    reorder
    Smooth transitions
    reorder
    Support nested structures
    reorder
    Scroll while dragging
  • filter_3Nested Grid

    Red

    Red

    Red

    Green

    Green

    Green

    Blue

    Blue

    Blue

  • code
       Simple
        <div id="basic-example"> (scope)
            <div class="grid"> (container)
                <div class="item"></div> (draggable)
            </div>
        </div>
        <script>
            lmdd.set(document.getElementById('basic-example'), {
                containerClass: 'grid',
                draggableItemClass: 'item'
        });
        </script>
    
        Multiple Containers and handle
        <div id="handle-example"> (scope)
            <div class="grid"> (container)
                <div class="item"> (draggable)
                    <div class="handle"></div> (handle)
                </div>
            </div>
            <div class="grid"> (another container)
            </div>
        </div>
        <script>
            lmdd.set(document.getElementById('handle-example'), {
                containerClass: 'grid',
                draggableItemClass: 'item',
                handleClass:'handle'
            });
        </script>
    
        Nested
        <div id="nested-example"> (scope)
            <div class="grid"> (container)
                <div class="grid item"> (container AND draggable)
                    <div class="item"></div> (draggable)
                    <div class="item"></div> (draggable)
                    <div class="item"></div> (draggable)
                </div>
                <div class="grid item"> (container AND draggable)
                    <div class="item"></div> (draggable)
                    <div class="item"></div> (draggable)
                    <div class="item"></div> (draggable)
                </div>
            </div>
        </div>
        <script>
            lmdd.set(document.getElementById('nested-example'), {
                containerClass: 'grid',
                draggableItemClass: 'item'
            });
        </script>
Options
  • speaker_notes
    Option Description
    containerClass Containers act as drop zones for draggable items
    draggableItemClass Draggable items can be moved inside and between containers
    handleClass Restricts drag start to a specific element which acts as a drag "handle"
    dragstartTimeout Delays the drag start for a short time to distinct it from other user intentions (such as selecting text)
    calcInterval Time interval in which LMDD evaluates the dragged element position, Short interval means more responsive experience (and more CPU usage)
    revert When set to true the draggable item will revert to its original position when the cursor is out of the container bounds
    nativeScroll LMDD uses its own auto-scroll function, you can eliminate it and use the native browser scroll
    mirrorMinHeight, mirrorMaxWidth Scale down the mirror size when it exceeds the maximum width set
    positionDelay When set to true - position will not be recalculated when the mouse stops moving (Prevents flickering on deep nested structures)
    dataMode When set to 'true' LMDD will undo all DOM mutations when the drag event ends, This is useful for integrating LMDD with Angular/React/Vue etc.
  • code
     //LMDD Options default values:
        lmdd.set(document.getElementById('someID'), {
            containerClass:'lmdd-container',
            draggableItemClass: 'lmdd-draggable',
            handleClass: false,
            dragstartTimeout: 50,
            calcInterval: 200,
            revert: true,
            nativeScroll: false,
            mirrorMinHeight: 100,
            mirrorMaxWidth: 500,
            positionDelay: false,
            dataMode: false
        });
Advanced
  • content_copyCloning

    To clone dragged elements, just add two additional classes to your markup:
    'lmdd-clonner' - for every element you want to clone when dragged
    'lmdd-dispatcher' - for the container (parent of the elements to be cloned)
    containers having the 'lmdd-dispatcher' class will not act as drop zones, the cloning operation will begin when the mouse cursor enters a different container.

       //Example markup:
        <div id="clone-example"> (scope)
            <div class="grid lmdd-dispatcher"> (dragging items out from this container will clone them)
                <div class="item lmdd-clonner">(this draggable item will be cloned when dragged)
                </div>
            </div>
            <div class="grid"> (container)
            </div>
        </div>
                
  • eventEvents

    LMDD dispatches three events:

    1. 'lmddbeforestart': Dispatched before any changes has been made to the DOM. Does not contain a detail object
    2. 'lmddstart': Dispatched when the drag operation starts. Contains a 'detail' object
    3. 'lmddend':Dispatched when the drag operation ends. Contains a 'detail' object

    The detail object carries the following information:

    • dragType: "move" or "clone"
    • draggedElement: HTML element being dragged
    • from.container: HTML element that originally contained the dragged element
    • from.index: The original index of the dragged element
    • to.container: HTML element that currently contains the dragged element
    • to.index: The current index of the dragged element

  • blockRendering Blocks

    To animate transitions LMDD makes a copy of all movable elements on its scope and manage their location with relative positioning,
    this is an expensive process and might (in some cases) alter the way your markup renders while dragging.
    To prevent this from happening, AND save CPU on drag operations, add the class 'lmdd-block' to the parts of your markup which are not needed to be animated.
    This will stop LMDD from traversing down the DOM tree and keep all children of the element intact.

       //Example markup:
        <div id="basic-example"> (scope)
            <div class="grid"> (container)
                <div class="item lmdd-block">(draggable)
                    <--! A lot of additional markup -->
                </div>
            </div>
        </div>
Integration
  • bookmarkGuidelines

    LMDD is fairly easy to integrate with any other script. I would recommend treating the drag operation as any other user input, think of it as some kind of a sophisticated mouse gesture. Having this point of view in mind, the integration process should be as follows:

    1. Upon rendering your markup, set an LMDD instance on the relevant part of the DOM
    2. Set the dataMode to 'true'. (Which will undo any changes LMDD made on the DOM when the drag operation ends)
    3. Add an eventListener for the 'lmddend' event
    4. Use an event handler to process the event details and implement the necessary changes on your app data/state

  • filter_1Vue Example

    See the Pen vue.js drag and drop with lmdd by Yair Levy (@supraniti) on CodePen.

    Note that the relevant code for implementing the drag and drop operation is made out of two simple functions,
    1. setting up lmdd and adding an event listener (on Vue 'mounted' function).
    2. moving the relevant data object from it's original parent into it's new parent (on our 'handleDragEvent' function)
    Everything else is just proper markup for the "todo-item" and "todo-container" components.
  • code
      mounted: function() {
        //set lmdd
        lmdd.set(document.getElementById('drag-scope'), {
          containerClass: 'todo-container',
          draggableItemClass: 'todo-item',
          handleClass: 'handle',
          dataMode: true //set dataMode to true
        });
        //listen to 'lmddend' event
        this.$el.addEventListener('lmddend', this.handleDragEvent);
      }
      methods: {
          //handle 'lmddend' event
          handleDragEvent: function(event) {
              // Process the event detail
              var newIndex = event.detail.to.index;
              var oldIndex = event.detail.from.index;
              var newContainer = event.detail.to.container.__vue__.data;
              var oldContainer = event.detail.from.container.__vue__.data;
              if (event.detail.dragType === 'move') {
                //implement the changes on the app data
                newContainer.splice(newIndex, 0, oldContainer.splice(oldIndex, 1)[0]);
              }
          }
      }
Angular and React integration is pretty much the same, examples will be added to this section later on.
More

Bug reports & feature requests are more than welcome.
Feel free to ask questions and contribute on github

If you are interested in a drag&drop functionality for your web apps, you might also like:

Discussion