├── .nojekyll ├── .gitattributes ├── gh-pages ├── images │ ├── logo.png │ ├── favicon.ico │ ├── logo-250.png │ ├── apple-icon.png │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon-96x96.png │ ├── ms-icon-70x70.png │ ├── GitHub-Mark-32px.png │ ├── GitHub-Mark-64px.png │ ├── apple-icon-57x57.png │ ├── apple-icon-60x60.png │ ├── apple-icon-72x72.png │ ├── apple-icon-76x76.png │ ├── drag_and_drop-128.png │ ├── ms-icon-144x144.png │ ├── ms-icon-150x150.png │ ├── ms-icon-310x310.png │ ├── android-icon-36x36.png │ ├── android-icon-48x48.png │ ├── android-icon-72x72.png │ ├── android-icon-96x96.png │ ├── apple-icon-114x114.png │ ├── apple-icon-120x120.png │ ├── apple-icon-144x144.png │ ├── apple-icon-152x152.png │ ├── apple-icon-180x180.png │ ├── android-icon-144x144.png │ ├── android-icon-192x192.png │ ├── apple-icon-precomposed.png │ ├── browserconfig.xml │ └── manifest.json ├── script.js └── style.css ├── .idea ├── vcs.xml ├── libraries │ └── Generated_files.xml └── watcherTasks.xml ├── dist ├── lmdd.min.css ├── lmdd.css ├── lmdd.min.js └── lmdd.js ├── README.md ├── LICENSE └── index.html /.nojekyll: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.html linguist-documentation 2 | *.css linguist-documentation -------------------------------------------------------------------------------- /gh-pages/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supraniti/Lean-Mean-Drag-and-Drop/HEAD/gh-pages/images/logo.png -------------------------------------------------------------------------------- /gh-pages/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supraniti/Lean-Mean-Drag-and-Drop/HEAD/gh-pages/images/favicon.ico -------------------------------------------------------------------------------- /gh-pages/images/logo-250.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supraniti/Lean-Mean-Drag-and-Drop/HEAD/gh-pages/images/logo-250.png -------------------------------------------------------------------------------- /gh-pages/images/apple-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supraniti/Lean-Mean-Drag-and-Drop/HEAD/gh-pages/images/apple-icon.png -------------------------------------------------------------------------------- /gh-pages/images/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supraniti/Lean-Mean-Drag-and-Drop/HEAD/gh-pages/images/favicon-16x16.png -------------------------------------------------------------------------------- /gh-pages/images/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supraniti/Lean-Mean-Drag-and-Drop/HEAD/gh-pages/images/favicon-32x32.png -------------------------------------------------------------------------------- /gh-pages/images/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supraniti/Lean-Mean-Drag-and-Drop/HEAD/gh-pages/images/favicon-96x96.png -------------------------------------------------------------------------------- /gh-pages/images/ms-icon-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supraniti/Lean-Mean-Drag-and-Drop/HEAD/gh-pages/images/ms-icon-70x70.png -------------------------------------------------------------------------------- /gh-pages/images/GitHub-Mark-32px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supraniti/Lean-Mean-Drag-and-Drop/HEAD/gh-pages/images/GitHub-Mark-32px.png -------------------------------------------------------------------------------- /gh-pages/images/GitHub-Mark-64px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supraniti/Lean-Mean-Drag-and-Drop/HEAD/gh-pages/images/GitHub-Mark-64px.png -------------------------------------------------------------------------------- /gh-pages/images/apple-icon-57x57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supraniti/Lean-Mean-Drag-and-Drop/HEAD/gh-pages/images/apple-icon-57x57.png -------------------------------------------------------------------------------- /gh-pages/images/apple-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supraniti/Lean-Mean-Drag-and-Drop/HEAD/gh-pages/images/apple-icon-60x60.png -------------------------------------------------------------------------------- /gh-pages/images/apple-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supraniti/Lean-Mean-Drag-and-Drop/HEAD/gh-pages/images/apple-icon-72x72.png -------------------------------------------------------------------------------- /gh-pages/images/apple-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supraniti/Lean-Mean-Drag-and-Drop/HEAD/gh-pages/images/apple-icon-76x76.png -------------------------------------------------------------------------------- /gh-pages/images/drag_and_drop-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supraniti/Lean-Mean-Drag-and-Drop/HEAD/gh-pages/images/drag_and_drop-128.png -------------------------------------------------------------------------------- /gh-pages/images/ms-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supraniti/Lean-Mean-Drag-and-Drop/HEAD/gh-pages/images/ms-icon-144x144.png -------------------------------------------------------------------------------- /gh-pages/images/ms-icon-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supraniti/Lean-Mean-Drag-and-Drop/HEAD/gh-pages/images/ms-icon-150x150.png -------------------------------------------------------------------------------- /gh-pages/images/ms-icon-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supraniti/Lean-Mean-Drag-and-Drop/HEAD/gh-pages/images/ms-icon-310x310.png -------------------------------------------------------------------------------- /gh-pages/images/android-icon-36x36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supraniti/Lean-Mean-Drag-and-Drop/HEAD/gh-pages/images/android-icon-36x36.png -------------------------------------------------------------------------------- /gh-pages/images/android-icon-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supraniti/Lean-Mean-Drag-and-Drop/HEAD/gh-pages/images/android-icon-48x48.png -------------------------------------------------------------------------------- /gh-pages/images/android-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supraniti/Lean-Mean-Drag-and-Drop/HEAD/gh-pages/images/android-icon-72x72.png -------------------------------------------------------------------------------- /gh-pages/images/android-icon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supraniti/Lean-Mean-Drag-and-Drop/HEAD/gh-pages/images/android-icon-96x96.png -------------------------------------------------------------------------------- /gh-pages/images/apple-icon-114x114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supraniti/Lean-Mean-Drag-and-Drop/HEAD/gh-pages/images/apple-icon-114x114.png -------------------------------------------------------------------------------- /gh-pages/images/apple-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supraniti/Lean-Mean-Drag-and-Drop/HEAD/gh-pages/images/apple-icon-120x120.png -------------------------------------------------------------------------------- /gh-pages/images/apple-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supraniti/Lean-Mean-Drag-and-Drop/HEAD/gh-pages/images/apple-icon-144x144.png -------------------------------------------------------------------------------- /gh-pages/images/apple-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supraniti/Lean-Mean-Drag-and-Drop/HEAD/gh-pages/images/apple-icon-152x152.png -------------------------------------------------------------------------------- /gh-pages/images/apple-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supraniti/Lean-Mean-Drag-and-Drop/HEAD/gh-pages/images/apple-icon-180x180.png -------------------------------------------------------------------------------- /gh-pages/images/android-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supraniti/Lean-Mean-Drag-and-Drop/HEAD/gh-pages/images/android-icon-144x144.png -------------------------------------------------------------------------------- /gh-pages/images/android-icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supraniti/Lean-Mean-Drag-and-Drop/HEAD/gh-pages/images/android-icon-192x192.png -------------------------------------------------------------------------------- /gh-pages/images/apple-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supraniti/Lean-Mean-Drag-and-Drop/HEAD/gh-pages/images/apple-icon-precomposed.png -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /gh-pages/images/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | #ffffff -------------------------------------------------------------------------------- /.idea/libraries/Generated_files.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /dist/lmdd.min.css: -------------------------------------------------------------------------------- 1 | .no-display{display:none !important}.no-transition,.no-transition *{transition:none !important}.unselectable{-webkit-user-select:none !important;-moz-user-select:none !important;-ms-user-select:none !important;user-select:none !important;cursor:not-allowed}.hidden-layer,.hidden-layer *{opacity:0;z-index:1;cursor:crosshair !important}.visible-layer,.visible-layer *{opacity:1;z-index:-1;pointer-events:none;transition:all .7s}.visible-layer .lmdd-mirror.gf-transition{transition:all .3s !important}.visible-layer .lmdd-mirror{z-index:1;position:absolute;margin:0;pointer-events:none;transition:top 00s,left 00s !important;opacity:.5}.lmdd-shadow{opacity:.5}.lmdd-hidden{visibility:hidden} -------------------------------------------------------------------------------- /gh-pages/script.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by יאיר on 18/01/2017. 3 | */ 4 | lmdd.set(document.getElementById('simple-grid-example'), { 5 | containerClass: 'simple-grid', 6 | draggableItemClass: 'grid-item' 7 | }); 8 | lmdd.set(document.getElementById('nested-example'), { 9 | containerClass: 'nestable', 10 | draggableItemClass: 'nested-item', 11 | positionDelay:true 12 | }); 13 | lmdd.set(document.getElementById('handle-example'), { 14 | containerClass: 'handle-grid', 15 | draggableItemClass: 'handle-item', 16 | handleClass:'handle' 17 | }); 18 | lmdd.set(document.getElementById('features'), { 19 | containerClass: 'feature-grid', 20 | draggableItemClass: 'feature-item' 21 | }); 22 | 23 | -------------------------------------------------------------------------------- /gh-pages/images/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "App", 3 | "icons": [ 4 | { 5 | "src": "\/android-icon-36x36.png", 6 | "sizes": "36x36", 7 | "type": "image\/png", 8 | "density": "0.75" 9 | }, 10 | { 11 | "src": "\/android-icon-48x48.png", 12 | "sizes": "48x48", 13 | "type": "image\/png", 14 | "density": "1.0" 15 | }, 16 | { 17 | "src": "\/android-icon-72x72.png", 18 | "sizes": "72x72", 19 | "type": "image\/png", 20 | "density": "1.5" 21 | }, 22 | { 23 | "src": "\/android-icon-96x96.png", 24 | "sizes": "96x96", 25 | "type": "image\/png", 26 | "density": "2.0" 27 | }, 28 | { 29 | "src": "\/android-icon-144x144.png", 30 | "sizes": "144x144", 31 | "type": "image\/png", 32 | "density": "3.0" 33 | }, 34 | { 35 | "src": "\/android-icon-192x192.png", 36 | "sizes": "192x192", 37 | "type": "image\/png", 38 | "density": "4.0" 39 | } 40 | ] 41 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Lean-Mean-Drag-and-Drop 2 | Lean & Mean Drag and Drop is a small script for dragging, dropping, sorting and reordering html structures 3 | 4 | ### Features 5 | 6 | - Supports nested structures ('nestable sortables') 7 | - Smooth transitions 8 | - Auto scroll while dragging 9 | - Lightweight (~3.5kb gzipped) With no dependencies 10 | - Supports touch events 11 | - Super easy to integrate 12 | 13 | ### Usage 14 | 15 | ``` 16 | //Load LMDD css and js files 17 | 18 | 19 | 20 | 21 | //Initialize LMDD instance with your preferred options 22 | 23 | 24 | ``` 25 | 26 | ### Examples & Instrcutions 27 | 28 | can be found here: https://supraniti.github.io/Lean-Mean-Drag-and-Drop/ 29 | 30 | ### Notes 31 | - Bug reports, Feature requests, Questions and any other feedback are welcome 32 | 33 | ### License 34 | MIT 35 | -------------------------------------------------------------------------------- /dist/lmdd.css: -------------------------------------------------------------------------------- 1 | .no-display { 2 | display: none !important; 3 | } 4 | 5 | .no-transition, .no-transition * { 6 | transition: none !important; 7 | } 8 | 9 | .unselectable { 10 | -webkit-user-select: none !important; 11 | -moz-user-select: none !important; 12 | -ms-user-select: none !important; 13 | user-select: none !important; 14 | cursor: not-allowed; 15 | } 16 | .hidden-layer, 17 | .hidden-layer * { 18 | opacity: 0; 19 | z-index:1; 20 | cursor:crosshair !important; 21 | } 22 | .visible-layer, 23 | .visible-layer * { 24 | opacity:1; 25 | z-index:-1; 26 | pointer-events: none; 27 | transition: all 0.7s; 28 | } 29 | .visible-layer .lmdd-mirror.gf-transition{ 30 | transition:all 0.3s !important; 31 | } 32 | .visible-layer .lmdd-mirror{ 33 | z-index:1; 34 | position:absolute; 35 | margin:0; 36 | pointer-events:none; 37 | transition: top 00s, left 00s !important; 38 | opacity:0.5; 39 | } 40 | .lmdd-shadow{ 41 | opacity:0.5; 42 | } 43 | .lmdd-hidden{ 44 | visibility:hidden; 45 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Yair Levy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.idea/watcherTasks.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 16 | 23 | 24 | 35 | 42 | 43 | -------------------------------------------------------------------------------- /gh-pages/style.css: -------------------------------------------------------------------------------- 1 | #features{ 2 | margin-bottom:15px; 3 | } 4 | .feature-grid{ 5 | display:flex; 6 | padding:7px; 7 | flex-flow: row wrap; 8 | justify-content: space-between; 9 | border: 1px dashed black; 10 | } 11 | .feature-item{ 12 | padding:5px; 13 | margin:5px; 14 | font-size:15px; 15 | border: 3px solid white; 16 | flex: 1 1 auto; 17 | vertical-align: middle; 18 | cursor:move; 19 | } 20 | .feature{ 21 | color:white; 22 | text-align:center; 23 | padding: 5px; 24 | } 25 | 26 | .example { 27 | background-color: white; 28 | } 29 | 30 | .simple-grid{ 31 | display:flex; 32 | padding:7px; 33 | max-width: 350px; 34 | flex-flow: row wrap; 35 | justify-content:space-around; 36 | } 37 | .grid-item{ 38 | text-align: center; 39 | vertical-align: middle; 40 | line-height:78px; 41 | margin:5px; 42 | width:78px; 43 | height:78px; 44 | color:white; 45 | font-size:15px; 46 | border: 3px solid white; 47 | cursor:move; 48 | } 49 | 50 | #handle-example { 51 | display: flex; 52 | justify-content: space-around; 53 | } 54 | 55 | .handle-grid { 56 | flex: 1 1 50%; 57 | display: flex; 58 | padding: 7px; 59 | max-width: 350px; 60 | flex-flow: column nowrap; 61 | } 62 | 63 | .handle-item { 64 | padding: 3px; 65 | margin: 5px; 66 | color: white; 67 | font-size: 15px; 68 | background-color: grey; 69 | border: 3px solid lightgoldenrodyellow; 70 | } 71 | 72 | .regular .handle-item { 73 | background-color: #8bc34a; 74 | } 75 | 76 | .priority .handle-item { 77 | background-color: #E57373; 78 | } 79 | 80 | .done .handle-item { 81 | background-color: lightgrey; 82 | color: gray; 83 | text-decoration: line-through; 84 | } 85 | 86 | .handle { 87 | cursor: move; 88 | } 89 | 90 | .task { 91 | border: 1px dotted white; 92 | padding: 5px; 93 | } 94 | 95 | .title { 96 | text-align: center; 97 | font-weight: bold; 98 | padding: 10px; 99 | margin: 5px; 100 | } 101 | 102 | .nestable{ 103 | display:flex; 104 | flex-flow:row wrap; 105 | border:3px solid white !important; 106 | } 107 | .nested-item{ 108 | flex: 1 1 calc(33.3333% - 14px); 109 | min-height:50px; 110 | border: 3px solid white; 111 | padding: 7px; 112 | margin:7px; 113 | cursor:move; 114 | text-align: center; 115 | vertical-align: middle; 116 | color: white; 117 | font-size: 15px; 118 | } 119 | .w-1-1{ 120 | flex: 1 1 100%; 121 | border: 5px solid white; 122 | padding: 7px; 123 | } 124 | 125 | .github-link{ 126 | position:absolute; 127 | top:12px; 128 | right:32px; 129 | height:32px; 130 | width:32px; 131 | filter:invert(100%); 132 | transition:0.3s; 133 | } 134 | @media only screen and (min-width: 601px){ 135 | .github-link { 136 | top: 16px; 137 | } 138 | } 139 | 140 | .github-link:hover { 141 | transform: rotate(360deg); 142 | } 143 | 144 | .collapsible.popout > li { 145 | background-color: #F0F0F0; 146 | } 147 | 148 | td, th { 149 | padding-top: 15px !important; 150 | padding-bottom: 15px !important; 151 | padding-left: 20px !important; 152 | padding-right: 5px !important; 153 | } 154 | 155 | .obd { 156 | list-style-type: decimal !important; 157 | } 158 | 159 | .ubd { 160 | list-style-type: disc !important; 161 | } -------------------------------------------------------------------------------- /dist/lmdd.min.js: -------------------------------------------------------------------------------- 1 | var lmdd=function(){function x(a,b,l,c){l?a.classList.add(b):a.classList.remove(b);c&&p[c].push(function(){l?a.classList.remove(b):a.classList.add(b)})}function D(a,b,l,c,d){a.addEventListener(b,l,c);p[d].push(function(){a.removeEventListener(b,l,c)})}function S(a,b){var l={};Object.keys(a).forEach(function(c){l[c]=Object.prototype.hasOwnProperty.call(b,c)?b[c]:a[c]});return l}function K(a){for(var b=0;bb){e--;break}for(;f<=d&&f!==d&&!(a[f].top>b);f++);for(b= 6 | e+1;b<=f;b++)if(b===f){g=b;break}else if(a[b].left>k.clientX){g=b;break}a=g===d?a[g-1].index+1:a[g].index}c.currentPosition=a}else c.currentPosition=!1}function F(){var a;if(a=c.currentContainer){var b=c.currentContainer;a=g;a.contains(b)||b.classList.contains("lmdd-dispatcher")?a=!1:d.lmddOptions.matchObject?(b=b.dataset.containerType||!1,a=a.dataset.itemType||!1,a=b?a?d.lmddOptions.matchObject[b][a]:d.lmddOptions.matchObject[b]["default"]:d.lmddOptions.matchObject["default"]):a=!0}a?(c.currentContainer.insertBefore(g, 7 | c.currentContainer.childNodes[c.currentPosition]),c.currentIndex=Array.prototype.indexOf.call(g.parentNode.childNodes,g),n&&!t&&(h.elref.classList.remove("no-display"),h.elref.cloneRef.classList.remove("no-display"),h.elref.cloneRef.classList.add("no-transition"),E(g)),t=!0):d.lmddOptions.revert&&(c.originalContainer.insertBefore(g,c.originalNextSibling),c.currentIndex=c.originalIndex);z();A=k}function G(a){a.cloneRef.rectRef=a.getBoundingClientRect();var b=window.getComputedStyle?getComputedStyle(a, 8 | null):a.currentStyle;a.cloneRef.styleRef={top:{padding:parseInt(b.paddingTop,10),margin:parseInt(b.marginTop,10),border:parseInt(b.borderTopWidth,10)},left:{padding:parseInt(b.paddingLeft,10),margin:parseInt(b.marginLeft,10),border:parseInt(b.borderLeftWidth,10)}};a.classList.contains("lmdd-block")||Array.prototype.forEach.call(a.childNodes,function(a){1===a.nodeType&&G(a)})}function U(a){var b=a.cloneNode(!0);b.id+="-lmddClone";var c=[],d=[],e=function(a,b){b.push(a);Array.prototype.forEach.call(a.childNodes, 9 | function(a){e(a,b)})};e(a,c);e(b,d);for(a=0;a=this.el.clientHeight-this.sm&&window.pageYOffset=this.el.clientWidth-this.sm&&window.pageXOffset 0){ 59 | createRectRefs(scope); 60 | animate(scope); 61 | } 62 | }); 63 | }); 64 | //tasks manager (makes sure we don't forget to undo whatever we do) 65 | var tasks = { 66 | executeTask: function (batch) { 67 | tasks[batch].forEach(function (fn) { 68 | fn(); 69 | }); 70 | tasks[batch] = []; 71 | }, 72 | onDragEnd: [], 73 | onTransitionEnd: [] 74 | }; 75 | function toggleClass(el, className, action, undo) { 76 | (action) ? el.classList.add(className) : el.classList.remove(className); 77 | if (undo) { 78 | tasks[undo].push(function () { 79 | if (action) { 80 | el.classList.remove(className); 81 | } 82 | else { 83 | el.classList.add(className); 84 | } 85 | }); 86 | } 87 | } 88 | function toggleEvent(el, listener, fn, useCapture, undo) { 89 | el.addEventListener(listener, fn, useCapture); 90 | tasks[undo].push(function () { 91 | el.removeEventListener(listener, fn, useCapture); 92 | }); 93 | } 94 | //scroll controller for replacing the native scroll behaviour 95 | var scrollManager = { 96 | event: null, 97 | active: true, 98 | sm: 20,//scroll margin 99 | el: document.documentElement,//scroll scope 100 | scrollInvoked: { 101 | top: false, 102 | left: false, 103 | bottom: false, 104 | right: false 105 | }, 106 | get willScroll() { 107 | return { 108 | top: (this.event.clientY <= this.sm) && (window.pageYOffset > 0), 109 | left: (this.event.clientX <= this.sm) && (window.pageXOffset > 0), 110 | bottom: (this.event.clientY >= this.el.clientHeight - this.sm) && (window.pageYOffset < this.el.scrollHeight - this.el.clientHeight), 111 | right: (this.event.clientX >= this.el.clientWidth - this.sm) && (window.pageXOffset < this.el.scrollWidth - this.el.clientWidth) 112 | }; 113 | }, 114 | get speed() { 115 | return Math.max(0, this.sm + (Math.max(0 - this.event.clientY, 0 - this.event.clientX, this.event.clientY - this.el.clientHeight, this.event.clientX - this.el.clientWidth))); 116 | }, 117 | updateEvent: function (e) { 118 | this.event = e; 119 | for (var key in this.willScroll) { 120 | if ((this.willScroll[key]) && (!this.scrollInvoked[key])) { 121 | this.move(key); 122 | } 123 | } 124 | }, 125 | move: function (key) { 126 | var self = this; 127 | this.scrollInvoked[key] = window.setInterval(function () { 128 | switch (key) { 129 | case "top": 130 | window.scrollTo(window.pageXOffset, window.pageYOffset - self.speed); 131 | break; 132 | case "left": 133 | window.scrollTo(window.pageXOffset - self.speed, window.pageYOffset); 134 | break; 135 | case "bottom": 136 | window.scrollTo(window.pageXOffset, window.pageYOffset + self.speed); 137 | break; 138 | case"right": 139 | window.scrollTo(window.pageXOffset + self.speed, window.pageYOffset); 140 | break; 141 | } 142 | if ((!self.willScroll[key]) || (!self.active)) { 143 | clearInterval(self.scrollInvoked[key]); 144 | self.scrollInvoked[key] = false; 145 | } 146 | }, 16); 147 | } 148 | }; 149 | //helper functions 150 | function assignOptions(defaults, settings) { 151 | var target = {}; 152 | Object.keys(defaults).forEach(function (key) { 153 | target[key] = (Object.prototype.hasOwnProperty.call(settings, key) ? settings[key] : defaults[key]); 154 | }); 155 | return target; 156 | } 157 | function clean(node) { 158 | for (var n = 0; n < node.childNodes.length; n++) { 159 | var child = node.childNodes[n]; 160 | if 161 | ( 162 | (child.nodeType === 3 && !/\S/.test(child.nodeValue)) 163 | ) { 164 | node.removeChild(child); 165 | n--; 166 | } 167 | else if (child.nodeType === 1) { 168 | clean(child); 169 | } 170 | } 171 | } 172 | function getOffset(el1, el2) {//get horizontal and vertical offset between two elements 173 | var rect1 = el1.cloneRef.rectRef, 174 | rect2 = el2.cloneRef.rectRef; 175 | var borderWidth = { 176 | left: el2.cloneRef.styleRef.left.border, 177 | top: el2.cloneRef.styleRef.top.border 178 | }; 179 | return { 180 | x: rect1.left - rect2.left - borderWidth.left, 181 | y: rect1.top - rect2.top - borderWidth.top 182 | }; 183 | } 184 | function getWrapper(el, wrapperClass) {//get wrapper element by class name 185 | var path = []; 186 | var wrapper = false; 187 | for (; el && el !== document; el = el.parentNode) { 188 | path.unshift(el); 189 | if ((el.classList.contains(wrapperClass)) && (!wrapper)) { 190 | wrapper = el; 191 | } 192 | } 193 | return (path.indexOf(scope) > -1) ? wrapper : false; 194 | } 195 | function simulateMouseEvent(event) {//convert touch to mouse events 196 | if (event.touches.length > 1) { 197 | return; 198 | } 199 | var simulatedType = (event.type === "touchstart") ? "mousedown" : (event.type === "touchend") ? "mouseup" : "mousemove"; 200 | var simulatedEvent = new MouseEvent(simulatedType, { 201 | "view": window, 202 | "bubbles": true, 203 | "cancelable": true, 204 | "screenX": (event.touches[0]) ? event.touches[0].screenX : 0, 205 | "screenY": (event.touches[0]) ? event.touches[0].screenY : 0, 206 | "clientX": (event.touches[0]) ? event.touches[0].clientX : 0, 207 | "clientY": (event.touches[0]) ? event.touches[0].clientY : 0, 208 | "button": 0, 209 | "buttons": 1 210 | }); 211 | var eventTarget = (event.type === "touchmove") ? document.elementFromPoint(simulatedEvent.clientX, simulatedEvent.clientY) || document.body : event.target; 212 | if (status === "dragStart") { 213 | event.preventDefault(); 214 | } 215 | eventTarget.dispatchEvent(simulatedEvent); 216 | } 217 | function createLmddEvent(type) {//custom app event 218 | return new CustomEvent(type, { 219 | "bubbles": true, 220 | "detail": { 221 | "dragType": (cloning) ? "clone" : "move", 222 | "draggedElement": (cloning) ? clone.elref : dragged, 223 | "from": {"container": positions.originalContainer,"index": positions.originalIndex}, 224 | "to": (positioned) ? {"container": positions.currentContainer,"index": positions.currentIndex} : false 225 | } 226 | }); 227 | } 228 | function muteEvent(event) {//mute unwanted events 229 | event.preventDefault(); 230 | event.stopPropagation(); 231 | return false; 232 | } 233 | //helper functions for handling mouse movement and element positioning 234 | function getCoordinates(el) { 235 | var coordinates = []; 236 | Array.prototype.forEach.call(el.childNodes, function (node, index) { 237 | if (node.nodeType === 1) { 238 | var coordinate = node.getBoundingClientRect(); 239 | coordinate.index = index; 240 | if (node.classList.contains(scope.lmddOptions.draggableItemClass)) { 241 | coordinates.push(coordinate); 242 | } 243 | } 244 | }); 245 | return coordinates; 246 | } 247 | function getPosition(coordinates, top, left) { 248 | var length = coordinates.length; 249 | if (length === 0) { 250 | return null 251 | } 252 | var lastAbove = 0; 253 | var firstBelow = 0; 254 | var position = -1; 255 | for (; lastAbove <= length; lastAbove++) { 256 | if (lastAbove === length) { 257 | lastAbove--; 258 | break; 259 | } else if (coordinates[lastAbove].bottom > top) { 260 | lastAbove--; 261 | break; 262 | } 263 | } 264 | for (; firstBelow <= length; firstBelow++) { 265 | if (firstBelow === length) { 266 | break; 267 | } else if (coordinates[firstBelow].top > top) { 268 | break; 269 | } 270 | } 271 | var firstRight = lastAbove + 1; 272 | for (; firstRight <= firstBelow; firstRight++) { 273 | if (firstRight === firstBelow) { 274 | position = firstRight; 275 | break; 276 | } else if (coordinates[firstRight].left > left) { 277 | position = firstRight; 278 | break; 279 | } 280 | } 281 | if (position === length) { 282 | return coordinates[position - 1].index + 1; 283 | } 284 | return coordinates[position].index; 285 | } 286 | function updateOriginalPosition(el) { 287 | positions.originalContainer = el.parentNode; 288 | positions.originalNextSibling = el.nextSibling; 289 | positions.originalIndex = Array.prototype.indexOf.call(el.parentNode.childNodes, el) 290 | } 291 | function updateCurrentContainer() { 292 | positions.previousContainer = positions.currentContainer; 293 | if (positions.currentTarget !== lastEvent.target) { 294 | positions.currentTarget = lastEvent.target; 295 | positions.currentContainer = getWrapper(lastEvent.target, scope.lmddOptions.containerClass); 296 | } 297 | } 298 | function updateCurrentCoordinates() { 299 | if (positions.currentContainer) { 300 | positions.currentCoordinates = getCoordinates(positions.currentContainer); 301 | } 302 | else { 303 | positions.currentCoordinates = getCoordinates(positions.originalContainer); 304 | } 305 | } 306 | function updateCurrentPosition() { 307 | positions.previousPosition = positions.currentPosition; 308 | if (positions.currentContainer) { 309 | positions.currentPosition = getPosition(positions.currentCoordinates, lastEvent.clientY, lastEvent.clientX); 310 | } 311 | else { 312 | positions.currentPosition = false; 313 | } 314 | } 315 | function appendDraggedElement() { 316 | if ((positions.currentContainer) && (acceptDrop(positions.currentContainer, dragged))) { 317 | positions.currentContainer.insertBefore(dragged, positions.currentContainer.childNodes[positions.currentPosition]); 318 | positions.currentIndex = Array.prototype.indexOf.call(dragged.parentNode.childNodes, dragged); 319 | if (cloning && !positioned) { 320 | clone.elref.classList.remove("no-display"); 321 | clone.elref.cloneRef.classList.remove("no-display"); 322 | clone.elref.cloneRef.classList.add("no-transition"); 323 | updateOriginalPosition(dragged); 324 | } 325 | positioned = true; 326 | } 327 | else if (scope.lmddOptions.revert) { 328 | positions.originalContainer.insertBefore(dragged, positions.originalNextSibling); 329 | positions.currentIndex = positions.originalIndex; 330 | } 331 | updateCurrentCoordinates(); 332 | refEvent = lastEvent; 333 | } 334 | function acceptDrop(container, item) { 335 | if (item.contains(container)) { 336 | return false; 337 | } 338 | if (container.classList.contains("lmdd-dispatcher")) { 339 | return false; 340 | } 341 | if (scope.lmddOptions.matchObject) { 342 | var cType = container.dataset.containerType || false; 343 | var iType = item.dataset.itemType || false; 344 | return ((cType) ? ((iType) ? scope.lmddOptions.matchObject [cType][iType] : scope.lmddOptions.matchObject[cType]["default"]) : scope.lmddOptions.matchObject["default"]); 345 | } 346 | return true; 347 | } 348 | function createRectRefs(el){ 349 | el.cloneRef.rectRef = el.getBoundingClientRect(); 350 | var style = window.getComputedStyle ? getComputedStyle(el, null) : el.currentStyle; 351 | el.cloneRef.styleRef = { 352 | top:{ 353 | padding: parseInt(style.paddingTop,10), 354 | margin: parseInt(style.marginTop,10), 355 | border: parseInt(style.borderTopWidth,10) 356 | }, 357 | left:{ 358 | padding: parseInt(style.paddingLeft,10), 359 | margin: parseInt(style.marginLeft,10), 360 | border: parseInt(style.borderLeftWidth,10) 361 | } 362 | }; 363 | if (!el.classList.contains('lmdd-block')){ 364 | Array.prototype.forEach.call(el.childNodes,function(node){ 365 | if (node.nodeType === 1){ 366 | createRectRefs(node); 367 | } 368 | }) 369 | } 370 | } 371 | //helper functions for managing the animation layer 372 | function createReference(el) { 373 | var clone = el.cloneNode(true); 374 | clone.id += "-lmddClone"; 375 | var elArray = []; 376 | var cloneArray = []; 377 | var traverse = function (el, refArray) { 378 | refArray.push(el); 379 | Array.prototype.forEach.call(el.childNodes, function (node) { 380 | traverse(node, refArray); 381 | }); 382 | }; 383 | traverse(el, elArray); 384 | traverse(clone, cloneArray); 385 | for (var i = 0; i < elArray.length; i++) { 386 | elArray[i].cloneRef = cloneArray[i]; 387 | } 388 | } 389 | function deleteReference(el) { 390 | delete(el.cloneRef); 391 | Array.prototype.forEach.call(el.childNodes, function (node) { 392 | deleteReference(node); 393 | }); 394 | } 395 | function animate(el) { 396 | animateNode(el); 397 | if (!el.classList.contains('lmdd-block')) { 398 | Array.prototype.forEach.call(el.childNodes, function (node) { 399 | if (node.nodeType === 1) { 400 | animate(node); 401 | } 402 | }) 403 | } 404 | } 405 | function animateNode(elNode) { 406 | var cloneNode = elNode.cloneRef; 407 | var parentCloneNode = elNode.parentNode.cloneRef; 408 | cloneNode.style.position = "absolute"; 409 | cloneNode.style.webkitBackfaceVisibility = "hidden"; 410 | cloneNode.style.width = cloneNode.rectRef.width + "px"; 411 | cloneNode.style.height = cloneNode.rectRef.height + "px"; 412 | cloneNode.style.display = "block"; 413 | if (elNode === scope) { 414 | cloneNode.style.top = cloneNode.rectRef.top + window.pageYOffset + "px"; 415 | cloneNode.style.left = cloneNode.rectRef.left + window.pageXOffset + "px"; 416 | } else { 417 | var refContainer = (cloning) ? positions.referenceContainer.cloneRef : positions.originalContainer.cloneRef; 418 | var offsetX = cloneNode.rectRef.left - ((cloneNode === shadow) ? refContainer.rectRef.left : parentCloneNode.rectRef.left); 419 | var offsetY = cloneNode.rectRef.top - ((cloneNode === shadow) ? refContainer.rectRef.top : parentCloneNode.rectRef.top); 420 | var fixX = (cloneNode === shadow) ? refContainer.styleRef.left.border + refContainer.styleRef.left.padding + shadow.offsetFix.left : parentCloneNode.styleRef.left.border + parentCloneNode.styleRef.left.padding + cloneNode.styleRef.left.margin; 421 | var fixY = (cloneNode === shadow) ? refContainer.styleRef.top.border + refContainer.styleRef.top.padding + shadow.offsetFix.top : parentCloneNode.styleRef.top.border + parentCloneNode.styleRef.top.padding + cloneNode.styleRef.top.margin; 422 | cloneNode.style.transform = "translate3d(" + (offsetX - fixX) + "px, " + (offsetY - fixY) + "px,0px)"; 423 | } 424 | } 425 | function updateMirrorLocation() { 426 | mirror.style.top = (lastEvent.pageY - parseInt(mirror.parentNode.style.top, 10) + scrollDelta.y - dragOffset.y) + "px"; 427 | mirror.style.left = (lastEvent.pageX - parseInt(mirror.parentNode.style.left, 10) + scrollDelta.x - dragOffset.x) + "px"; 428 | } 429 | //main 430 | function eventManager(event) {//handle events lifecycle and app status 431 | switch (status) { 432 | case "waitDragStart": 433 | if ((event.type === "mousedown") && (event.button === 0)) {//trigger timeout function to enable clicking and text selection 434 | scope = this; 435 | lastEvent = event; 436 | toggleEvent(window, "mouseup", eventManager, false, "onDragEnd"); 437 | toggleEvent(document, "mousemove", eventManager, false, "onDragEnd"); 438 | toggleEvent(document, "scroll", eventManager, false, "onDragEnd"); 439 | status = "dragStartTimeout"; 440 | window.setTimeout(function () { 441 | if (status === "dragStartTimeout") {//no events fired during the timeout 442 | if ((scope.lmddOptions.handleClass) && (!event.target.classList.contains(scope.lmddOptions.handleClass))) {//not dragging with handle 443 | killEvent(); 444 | } 445 | else { 446 | var target = getWrapper(event.target, scope.lmddOptions.draggableItemClass); 447 | if (!target) {//not dragging a draggable 448 | killEvent(); 449 | } 450 | else { 451 | scope.dispatchEvent(new CustomEvent('lmddbeforestart', {"bubbles": true})); 452 | dragOffset.x = event.clientX - target.getBoundingClientRect().left; 453 | dragOffset.y = event.clientY - target.getBoundingClientRect().top; 454 | setElements(target); 455 | if (document.body.setCapture) { 456 | document.body.setCapture(false); 457 | tasks.onDragEnd.push(function () { 458 | document.releaseCapture(); 459 | }); 460 | } 461 | clearInterval(calcInterval);//make sure interval was not set already 462 | calcInterval = window.setInterval(eventTicker, scope.lmddOptions.calcInterval);//calculation interval for mouse movement 463 | observer.observe(scope, {childList: true, subtree:true }); 464 | if (!scope.lmddOptions.nativeScroll) {//disable native scrolling on mouse down 465 | event.preventDefault(); 466 | } 467 | scope.dispatchEvent(createLmddEvent("lmddstart")); 468 | status = "dragStart"; 469 | scrollManager.active = true; 470 | } 471 | } 472 | } 473 | }, scope.lmddOptions.dragstartTimeout); 474 | } 475 | break; 476 | case "dragStartTimeout": 477 | if(mousemoveCounter === 0){ 478 | mousemoveCounter ++; 479 | return; 480 | } 481 | killEvent(); 482 | break; 483 | case "dragStart": 484 | if ((event.type === "mousedown") || (event.type === "mouseup") || (event.type === "mousemove") && (event.buttons === 0)) {//or mousemove with no buttons in case mouseup event was not fired 485 | if (!dragged) { 486 | killEvent(); 487 | return; 488 | } 489 | scrollManager.active = false; 490 | mirror.classList.add("gf-transition"); 491 | var mirrorRect = mirror.getBoundingClientRect(); 492 | var offsetX = mirrorRect.left - shadow.rectRef.left; 493 | var offsetY = mirrorRect.top - shadow.rectRef.top; 494 | var offset = getOffset(dragged, scope); 495 | mirror.style.width = shadow.rectRef.width + "px"; 496 | mirror.style.height = shadow.rectRef.height + "px"; 497 | mirror.style.transform = "scale(1,1)"; 498 | mirror.style.top = offset.y + "px"; 499 | mirror.style.left = offset.x + "px"; 500 | if (Math.abs(offsetX) + Math.abs(offsetY) > 3) {//make a graceful finish... 501 | status = "waitDragEnd"; 502 | tasks.onTransitionEnd.push(function () { 503 | killEvent(); 504 | }); 505 | } 506 | else{ 507 | killEvent(); 508 | return; 509 | } 510 | } 511 | if (event.type === "mousemove") { 512 | lastEvent = event; 513 | if (!scope.lmddOptions.nativeScroll) {//disable native scrolling on mouse down 514 | event.preventDefault(); 515 | } 516 | scrollDelta.lastX = window.pageXOffset; 517 | scrollDelta.lastY = window.pageYOffset; 518 | updateMirrorLocation(); 519 | } 520 | if (event.type === "scroll") { 521 | updateMirrorLocation(); 522 | updateCurrentCoordinates(); 523 | } 524 | break; 525 | case "waitDragEnd": 526 | if (event.type === "transitionend") { 527 | tasks.executeTask("onTransitionEnd"); 528 | } 529 | if (event.type === "mouseup"){ 530 | killEvent(); 531 | } 532 | break; 533 | } 534 | } 535 | function setElements(el) {//set animated and cloned elements 536 | if (el.classList.contains("lmdd-clonner")) {//clone the target 537 | cloning = true; 538 | clone = el.parentNode.insertBefore(el.cloneNode(true), el); 539 | clone.classList.remove("lmdd-clonner");//prevent the clone from acting as a clonner 540 | el.classList.add("no-display");//hide the cloned target until the original target will be positioned 541 | clone.elref = el;//create a reference to the original clonner 542 | } 543 | createReference(scope);//create a clone reference for every element on scope 544 | dragged = (cloning) ? clone : el; 545 | shadow = dragged.cloneRef; 546 | mirror = shadow.cloneNode(true); 547 | if (mirror.tagName === 'LI') { 548 | var wrapper = document.createElement('ul'); 549 | wrapper.appendChild(mirror); 550 | wrapper.style.padding = 0; 551 | mirror.style.margin = 0; 552 | mirror = wrapper; 553 | mirror.wrapped = true; 554 | } 555 | toggleClass(dragged, "lmdd-hidden", true, "onDragEnd"); 556 | shadow.classList.add("lmdd-shadow"); 557 | updateOriginalPosition(dragged); 558 | positions.referenceContainer = positions.originalContainer; 559 | updateCurrentContainer(); 560 | updateCurrentCoordinates(); 561 | window.getSelection().removeAllRanges();//disable text selection on FF and IE - JS 562 | toggleClass(document.body, "unselectable", true, "onDragEnd");//disable text selection on CHROME - CSS 563 | scope.parentNode.appendChild(scope.cloneRef); //insert the clone into the dom 564 | tasks.onDragEnd.push(function () { 565 | scope.parentNode.removeChild(scope.cloneRef); 566 | deleteReference(scope); 567 | }); 568 | var temp = shadow; 569 | while (scope.cloneRef.contains(temp)) { 570 | temp.style.zIndex = 0; 571 | temp = temp.parentNode; 572 | } 573 | var cStyle = window.getComputedStyle ? getComputedStyle(shadow, null) : shadow.currentStyle; 574 | shadow.offsetFix = { 575 | left : parseInt(cStyle.marginLeft, 10), 576 | top : parseInt(cStyle.marginTop, 10), 577 | parent : dragged.parentNode.cloneRef 578 | }; 579 | createRectRefs(scope); 580 | animate(scope); 581 | mirror.classList.add("lmdd-mirror"); 582 | var props = ['width','height','padding','paddingTop','paddingBottom','paddingLeft','paddingRight','lineHeight']; 583 | props.forEach(function(prop){ 584 | if (mirror.wrapped){ 585 | mirror.childNodes[0].style[prop] = cStyle[prop]; 586 | } 587 | else{ 588 | mirror.style[prop] = cStyle[prop]; 589 | } 590 | }); 591 | var scaleX = scope.lmddOptions.mirrorMaxWidth / shadow.getBoundingClientRect().width; 592 | var scaleY = scope.lmddOptions.mirrorMinHeight / shadow.getBoundingClientRect().height; 593 | var scale = Math.min(1, Math.max(scaleX, scaleY)); 594 | dragOffset.x *= scale; 595 | dragOffset.y *= scale; 596 | mirror.style.transform = "scale(" + scale + "," + scale + ")"; 597 | mirror.style.transformOrigin = "0 0"; 598 | scope.cloneRef.appendChild(mirror); 599 | mirror.addEventListener("transitionend", eventManager, false); 600 | scrollDelta.lastX = window.pageXOffset; 601 | scrollDelta.lastY = window.pageYOffset; 602 | updateMirrorLocation(); 603 | toggleClass(scope, "hidden-layer", true, "onDragEnd"); 604 | toggleClass(scope.cloneRef, "visible-layer", true, false); 605 | } 606 | function eventTicker() {//interval function for updating and handling mouse movements while dragging 607 | if (!scope.lmddOptions.nativeScroll) { 608 | scrollManager.updateEvent(lastEvent); 609 | } 610 | if (refEvent === lastEvent) { 611 | return false; 612 | } 613 | if(refEvent && scope.lmddOptions.positionDelay){ 614 | if(lastEvent.timeStamp - refEvent.timeStamp < scope.lmddOptions.calcInterval){ 615 | return false; 616 | } 617 | } 618 | if (status !== "dragStart"){ 619 | return false; 620 | } 621 | updateCurrentContainer(); 622 | if (!positions.currentContainer) {//no container found 623 | if (positions.previousContainer && scope.lmddOptions.revert) {//execute once (revert) 624 | appendDraggedElement(); 625 | } 626 | } 627 | else {//found a container 628 | if (positions.currentContainer !== positions.previousContainer) {//its a new one... 629 | updateCurrentCoordinates(); 630 | updateCurrentPosition(); 631 | appendDraggedElement(); 632 | } 633 | else {//same container 634 | updateCurrentPosition(); 635 | if (positions.currentPosition !== positions.previousPosition) {//new position 636 | appendDraggedElement(); 637 | } 638 | } 639 | } 640 | } 641 | function killEvent() {//end current drag event 642 | observer.disconnect(); 643 | clearInterval(calcInterval); 644 | calcInterval = null; 645 | mousemoveCounter = 0; 646 | scrollManager.active = false; 647 | if (cloning && !positioned) { 648 | clone.elref.classList.remove("no-display"); 649 | clone.parentNode.removeChild(clone); 650 | } 651 | if (cloning) { 652 | updateOriginalPosition(clone.elref) 653 | } 654 | tasks.executeTask("onDragEnd"); 655 | if (status !== "dragStartTimeout" && status !== "waitDragStart") { 656 | scope.dispatchEvent(createLmddEvent("lmddend")); 657 | } 658 | if (scope.lmddOptions.dataMode) {//undo DOM mutations 659 | if (positioned && cloning) { 660 | dragged.parentNode.removeChild(dragged); 661 | } 662 | else if (positioned) { 663 | positions.originalContainer.insertBefore(dragged, positions.originalNextSibling); 664 | } 665 | } 666 | positioned = false; 667 | cloning = false; 668 | status = "waitDragStart"; 669 | } 670 | return {//exposed methods 671 | set: function (el, lmddOptions) { 672 | if (!el.lmdd) { 673 | clean(el);//get rid of whitespaces 674 | el.lmdd = true; 675 | el.lmddOptions = assignOptions(options, lmddOptions);//create options object 676 | el.addEventListener("mousedown", eventManager, false); 677 | document.addEventListener("drag", muteEvent, false); 678 | document.addEventListener("dragstart", muteEvent, false); 679 | window.addEventListener("touchstart", simulateMouseEvent); 680 | window.addEventListener("touchmove", simulateMouseEvent, {passive: false}); 681 | window.addEventListener("touchend", simulateMouseEvent); 682 | } 683 | }, 684 | unset: function (el) { 685 | if (el.lmdd) { 686 | el.removeEventListener("mousedown", eventManager, false); 687 | el.lmdd = false; 688 | delete(el.lmddOptions); 689 | } 690 | }, 691 | kill: function () { 692 | document.removeEventListener("drag", muteEvent, false); 693 | document.removeEventListener("dragstart", muteEvent, false); 694 | window.removeEventListener("touchstart", simulateMouseEvent); 695 | window.removeEventListener("touchmove", simulateMouseEvent, {passive: false}); 696 | window.removeEventListener("touchend", simulateMouseEvent); 697 | } 698 | }; 699 | })(); 700 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Lean & Mean Drag and Drop - LMDD 10 | 11 | 12 | 13 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 40 | 41 | 42 | 43 | 44 | 45 |
46 | 70 |
71 |
72 | 75 | 76 | 77 |
78 |
79 |

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

80 |
Features
81 |
82 |
83 |
84 |
Supports nested structures ('nestable sortables')
85 |
86 |
87 |
Smooth transitions
88 |
89 |
90 |
Auto scroll while dragging
91 |
92 |
93 |
Lightweight (~3.5kb gzipped)
94 |
95 |
96 |
No dependencies
97 |
98 |
99 |
Super easy to integrate with your own app
100 |
101 |
102 |
Supports touch events
103 |
104 |
105 |
Compatible with all modern browsers
106 |
107 |
108 |
Can work with any type of layout
109 |
110 |
111 |
112 |
113 |
114 |
Usage
115 |
//Load LMDD css and js files
116 | <link href="../css/lmdd.min.css" rel="stylesheet">
117 | <script src="../js/lmdd.min.js"></script>
118 | 
119 | //Initialize LMDD instance with your preferred options
120 | <script>lmdd.set(document.getElementById('markupID'),{optionsObject});</script>
121 |
122 |
123 |
Examples
124 |
    125 |
  • 126 |
    speaker_notes
    127 |
    128 |

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

    134 |
    135 |
  • 136 |
  • 137 |
    filter_1Simple Grid
    138 |
    139 |
    140 |
    141 |
    Green
    142 |
    Pink
    143 |
    Purple
    144 |
    Blue
    145 |
    Lime
    146 |
    Teal
    147 |
    Amber
    148 |
    Orange
    149 |
    Black
    150 |
    151 |
    152 |
    153 |
  • 154 |
  • 155 |
    filter_2Multiple containers + handle
    156 |
    157 |
    158 |
    159 |
    Tasks
    160 |
    161 |
    162 | reorder 163 |
    Code refactoring & optimizations
    164 |
    165 |
    166 |
    167 |
    168 | reorder 169 |
    Cross browser testing
    170 |
    171 |
    172 |
    173 |
    174 |
    Important Tasks
    175 |
    176 |
    177 | reorder 178 |
    Call mom
    179 |
    180 |
    181 |
    182 |
    183 |
    Done
    184 |
    185 |
    186 | reorder 187 |
    Basic Drag & Drop script
    188 |
    189 |
    190 |
    191 |
    192 | reorder 193 |
    Smooth transitions
    194 |
    195 |
    196 |
    197 |
    198 | reorder 199 |
    Support nested structures
    200 |
    201 |
    202 |
    203 |
    204 | reorder 205 |
    Scroll while dragging
    206 |
    207 |
    208 |
    209 |
    210 |
    211 |
  • 212 |
  • 213 |
    filter_3Nested Grid
    214 |
    215 |
    216 |
    217 |
    218 |

    Red

    219 |

    Red

    220 |

    Red

    221 |
    222 |
    223 |

    Green

    224 |

    Green

    225 |

    Green

    226 |
    227 |
    228 |

    Blue

    229 |

    Blue

    230 |

    Blue

    231 |
    232 |
    233 |
    234 |
    235 |
  • 236 |
  • 237 |
    code
    238 |
    239 |
       Simple
    240 |     <div id="basic-example"> (scope)
    241 |         <div class="grid"> (container)
    242 |             <div class="item"></div> (draggable)
    243 |         </div>
    244 |     </div>
    245 |     <script>
    246 |         lmdd.set(document.getElementById('basic-example'), {
    247 |             containerClass: 'grid',
    248 |             draggableItemClass: 'item'
    249 |     });
    250 |     </script>
    251 | 
    252 |     Multiple Containers and handle
    253 |     <div id="handle-example"> (scope)
    254 |         <div class="grid"> (container)
    255 |             <div class="item"> (draggable)
    256 |                 <div class="handle"></div> (handle)
    257 |             </div>
    258 |         </div>
    259 |         <div class="grid"> (another container)
    260 |         </div>
    261 |     </div>
    262 |     <script>
    263 |         lmdd.set(document.getElementById('handle-example'), {
    264 |             containerClass: 'grid',
    265 |             draggableItemClass: 'item',
    266 |             handleClass:'handle'
    267 |         });
    268 |     </script>
    269 | 
    270 |     Nested
    271 |     <div id="nested-example"> (scope)
    272 |         <div class="grid"> (container)
    273 |             <div class="grid item"> (container AND draggable)
    274 |                 <div class="item"></div> (draggable)
    275 |                 <div class="item"></div> (draggable)
    276 |                 <div class="item"></div> (draggable)
    277 |             </div>
    278 |             <div class="grid item"> (container AND draggable)
    279 |                 <div class="item"></div> (draggable)
    280 |                 <div class="item"></div> (draggable)
    281 |                 <div class="item"></div> (draggable)
    282 |             </div>
    283 |         </div>
    284 |     </div>
    285 |     <script>
    286 |         lmdd.set(document.getElementById('nested-example'), {
    287 |             containerClass: 'grid',
    288 |             draggableItemClass: 'item'
    289 |         });
    290 |     </script>
    291 |
    292 |
  • 293 |
294 |
295 |
296 |
Options
297 |
    298 |
  • 299 |
    speaker_notes
    300 |
    301 |
    302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 327 | 328 | 329 | 330 | 333 | 334 | 335 | 336 | 339 | 340 | 341 | 342 | 345 | 346 | 347 | 350 | 351 | 352 | 353 | 354 | 357 | 358 | 359 | 360 | 363 | 364 | 365 |
    OptionDescription
    containerClassContainers act as drop zones for draggable items
    draggableItemClassDraggable items can be moved inside and between containers
    handleClassRestricts drag start to a specific element which acts as a drag "handle"
    dragstartTimeoutDelays the drag start for a short time to distinct it from other user intentions 325 | (such as selecting text) 326 |
    calcIntervalTime interval in which LMDD evaluates the dragged element position, Short 331 | interval means more responsive experience (and more CPU usage) 332 |
    revertWhen set to true the draggable item will revert to its original position when 337 | the cursor is out of the container bounds 338 |
    nativeScrollLMDD uses its own auto-scroll function, you can eliminate it and use the native 343 | browser scroll 344 |
    mirrorMinHeight, 348 | mirrorMaxWidth 349 | Scale down the mirror size when it exceeds the maximum width set
    positionDelayWhen set to true - position will not be recalculated when the mouse stops moving 355 | (Prevents flickering on deep nested structures) 356 |
    dataModeWhen set to 'true' LMDD will undo all DOM mutations when the drag event ends, 361 | This is useful for integrating LMDD with Angular/React/Vue etc. 362 |
    366 |
    367 |
    368 |
  • 369 |
  • 370 |
    code
    371 |
    372 |
     //LMDD Options default values:
    373 |     lmdd.set(document.getElementById('someID'), {
    374 |         containerClass:'lmdd-container',
    375 |         draggableItemClass: 'lmdd-draggable',
    376 |         handleClass: false,
    377 |         dragstartTimeout: 50,
    378 |         calcInterval: 200,
    379 |         revert: true,
    380 |         nativeScroll: false,
    381 |         mirrorMinHeight: 100,
    382 |         mirrorMaxWidth: 500,
    383 |         positionDelay: false,
    384 |         dataMode: false
    385 |     });
    386 |
    387 |
  • 388 |
389 |
390 |
391 |
Advanced
392 |
    393 |
  • 394 |
    content_copyCloning
    395 |
    396 |

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

    403 |
       //Example markup:
    404 |     <div id="clone-example"> (scope)
    405 |         <div class="grid lmdd-dispatcher"> (dragging items out from this container will clone them)
    406 |             <div class="item lmdd-clonner">(this draggable item will be cloned when dragged)
    407 |             </div>
    408 |         </div>
    409 |         <div class="grid"> (container)
    410 |         </div>
    411 |     </div>
    412 |             
    413 |
    414 |
  • 415 |
  • 416 |
    eventEvents
    417 |
    418 |

    LMDD dispatches three events:

    419 |
      420 |
    1. 'lmddbeforestart': Dispatched before any changes has been made to the 421 | DOM. Does not contain a detail object 422 |
    2. 423 |
    3. 'lmddstart': Dispatched when the drag operation starts. Contains a 424 | 'detail' object 425 |
    4. 426 |
    5. 'lmddend':Dispatched when the drag operation ends. Contains a 427 | 'detail' object 428 |
    6. 429 |
    430 |

    The detail object carries the following information:

    431 |
      432 |
    • dragType: "move" or "clone"
    • 433 |
    • draggedElement: HTML element being dragged
    • 434 |
    • from.container: HTML element that originally contained the dragged 435 | element 436 |
    • 437 |
    • from.index: The original index of the dragged element
    • 438 |
    • to.container: HTML element that currently contains the dragged 439 | element 440 |
    • 441 |
    • to.index: The current index of the dragged element
    • 442 |
    443 |
    444 |
    445 |
  • 446 |
  • 447 |
    blockRendering Blocks
    448 |
    449 |

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

    459 |
       //Example markup:
    460 |     <div id="basic-example"> (scope)
    461 |         <div class="grid"> (container)
    462 |             <div class="item lmdd-block">(draggable)
    463 |                 <--! A lot of additional markup -->
    464 |             </div>
    465 |         </div>
    466 |     </div>
    467 |
    468 |
  • 469 |
470 |
471 |
472 |
Integration
473 |
    474 |
  • 475 |
    bookmarkGuidelines
    476 |
    477 |

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

    481 |
      482 |
    1. Upon rendering your markup, set an LMDD instance on the relevant part of the 483 | DOM 484 |
    2. 485 |
    3. Set the dataMode to 'true'. (Which will undo any changes LMDD made on the 486 | DOM when the drag operation ends) 487 |
    4. 488 |
    5. Add an eventListener for the 'lmddend' event
    6. 489 |
    7. Use an event handler to process the event details and implement the 490 | necessary changes on your app data/state 491 |
    8. 492 |
    493 |
    494 |
    495 |
  • 496 |
  • 497 |
    filter_1Vue Example
    498 |
    499 |

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

    504 | 505 |
    506 | Note that the relevant code for implementing the drag and drop operation is made out of two 507 | simple functions, 508 |
      509 |
    1. setting up lmdd and adding an event listener (on Vue 'mounted' 510 | function). 511 |
    2. 512 |
    3. moving the relevant data object from it's original parent into it's new 513 | parent (on our 'handleDragEvent' function) 514 |
    4. 515 |
    516 | Everything else is just proper markup for the "todo-item" and "todo-container" components. 517 |
    518 |
    519 |
  • 520 |
  • 521 |
    code
    522 |
    523 |
      mounted: function() {
    524 |     //set lmdd
    525 |     lmdd.set(document.getElementById('drag-scope'), {
    526 |       containerClass: 'todo-container',
    527 |       draggableItemClass: 'todo-item',
    528 |       handleClass: 'handle',
    529 |       dataMode: true //set dataMode to true
    530 |     });
    531 |     //listen to 'lmddend' event
    532 |     this.$el.addEventListener('lmddend', this.handleDragEvent);
    533 |   }
    534 |   methods: {
    535 |       //handle 'lmddend' event
    536 |       handleDragEvent: function(event) {
    537 |           // Process the event detail
    538 |           var newIndex = event.detail.to.index;
    539 |           var oldIndex = event.detail.from.index;
    540 |           var newContainer = event.detail.to.container.__vue__.data;
    541 |           var oldContainer = event.detail.from.container.__vue__.data;
    542 |           if (event.detail.dragType === 'move') {
    543 |             //implement the changes on the app data
    544 |             newContainer.splice(newIndex, 0, oldContainer.splice(oldIndex, 1)[0]);
    545 |           }
    546 |       }
    547 |   }
    548 |
    549 |
  • 550 |
551 |
Angular and React integration is pretty much the same, examples will be added to this section 552 | later on. 553 |
554 |
555 |
556 |
More
557 |

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

562 |

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

563 | 574 |
575 |
576 |
Discussion
577 |
578 |
579 |
580 | 598 |
599 |
600 | 607 |
608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 627 | 650 | 651 | 652 | --------------------------------------------------------------------------------