Lean & Mean Drag and Drop is a small script for dragging, dropping, sorting and reordering html structures
Features
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 GridGreenPinkPurpleBlueLimeTealAmberOrangeBlack
-
filter_2Multiple containers + handleTasksreorderCode refactoring & optimizationsreorderCross browser testingImportant TasksreorderCall momDonereorderBasic Drag & Drop scriptreorderSmooth transitionsreorderSupport nested structuresreorderScroll 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:
- 'lmddbeforestart': Dispatched before any changes has been made to the DOM. Does not contain a detail object
- 'lmddstart': Dispatched when the drag operation starts. Contains a 'detail' object
- '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:
- Upon rendering your markup, set an LMDD instance on the relevant part of the DOM
- Set the dataMode to 'true'. (Which will undo any changes LMDD made on the DOM when the drag operation ends)
- Add an eventListener for the 'lmddend' event
- 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,
- setting up lmdd and adding an event listener (on Vue 'mounted' function).
- moving the relevant data object from it's original parent into it's new parent (on our 'handleDragEvent' function)
-
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: