├── .gitignore ├── bower.json ├── package.json ├── demo ├── demo.css └── demo.html ├── src ├── draganddrop.css └── draganddrop.js ├── LICENSE.txt └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | -* 2 | bower_components 3 | node_modules 4 | package-lock.json 5 | .DS_Store 6 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "draganddrop", 3 | "version": "0.0.13", 4 | "authors": [ 5 | "Ole Trenner " 6 | ], 7 | "description": "Drag and Drop: Dragaware, Draggable, Nested Sortable.", 8 | "keywords": [ 9 | "jquery", 10 | "draggable", 11 | "sortable", 12 | "nested" 13 | ], 14 | "license": "MIT", 15 | "private": true, 16 | "ignore": [ 17 | "**/.*", 18 | "node_modules", 19 | "bower_components", 20 | "test", 21 | "tests" 22 | ], 23 | "dependencies": { 24 | "jquery": "*" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "draganddrop", 3 | "version": "0.0.13", 4 | "description": "Drag and Drop: Dragaware, Draggable, Nested Sortable.", 5 | "keywords": [ 6 | "drag and drop" 7 | ], 8 | "main": "src/draganddrop.js", 9 | "scripts": {}, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/gardiner/draganddrop.git" 13 | }, 14 | "author": "Ole Trenner ", 15 | "bugs": { 16 | "url": "https://github.com/gardiner/draganddrop/issues" 17 | }, 18 | "dependencies": { 19 | "jquery": "*" 20 | }, 21 | "homepage": "https://github.com/gardiner/draganddrop#readme" 22 | } 23 | -------------------------------------------------------------------------------- /demo/demo.css: -------------------------------------------------------------------------------- 1 | h3 { 2 | margin: 30px 0 20px 0; 3 | clear: both; 4 | } 5 | 6 | ul.normal { 7 | width: 500px; 8 | } 9 | 10 | ul.normal li { 11 | padding: 5px; 12 | } 13 | 14 | ul.float { 15 | list-style-position: inside; 16 | padding: 0; 17 | margin: 0; 18 | overflow: hidden; 19 | } 20 | 21 | ul.float li { 22 | float: left; 23 | margin: 10px; 24 | } 25 | 26 | ul.inline { 27 | list-style-position: inside; 28 | padding: 0; 29 | margin: 0; 30 | } 31 | 32 | ul.inline li { 33 | display: inline-block; 34 | vertical-align: top; 35 | margin: 10px; 36 | } 37 | 38 | .list div { 39 | margin: 2px 0; 40 | padding: 2px; 41 | } 42 | 43 | .drop { 44 | margin: 20px 0; 45 | padding: 5px; 46 | border: 2px dotted orange; 47 | border-radius: 5px; 48 | } 49 | 50 | .drop.hovering { 51 | background-color: #F6BF66; 52 | } -------------------------------------------------------------------------------- /src/draganddrop.css: -------------------------------------------------------------------------------- 1 | .unselectable { 2 | -moz-user-select: -moz-none, 3 | -moz-user-select: none, 4 | -o-user-select: none, 5 | -khtml-user-select: none, 6 | -webkit-user-select: none, 7 | -ms-user-select: none, 8 | user-select: none 9 | } 10 | 11 | .dragaware { 12 | cursor: pointer; 13 | } 14 | 15 | .draggable_clone { 16 | position: absolute; /* also set via javascript */ 17 | z-index: 100001; 18 | pointer-events:none; /* disable mouse events on the clone */ 19 | } 20 | 21 | .draggable.dragging, .draggable .dragging { 22 | opacity: 0.5; 23 | } 24 | 25 | .sortable .sortable_clone { 26 | position: absolute; /* also set via javascript */ 27 | z-index: 100001; 28 | list-style-type: none; 29 | opacity: 0.5; 30 | pointer-events: none; 31 | } 32 | 33 | .sortable .sortable_placeholder { 34 | box-sizing: border-box; 35 | list-style-type: none; 36 | background: #eee; 37 | border: 2px dotted #52b218; 38 | } -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Ole Trenner 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Drag and Drop 2 | ============= 3 | 4 | Basic jQuery plugin to allow drag and drop: 5 | 6 | * dragging 7 | * dropping of dragged elements 8 | * sorting of lists and other nested html structures (```ul```, ```ol```, ```div``` etc.) 9 | 10 | 11 | 12 | Requirements 13 | ------------ 14 | 15 | - jQuery 16 | 17 | 18 | 19 | Usage 20 | ----- 21 | 22 | See [demo.html](demo/demo.html) for a working demo. 23 | 24 | 1. Install draganddrop: 25 | 26 | ```bash 27 | $ bower install -S gardiner/draganddrop 28 | ``` 29 | 30 | 2. Include jQuery and draganddrop in document head: 31 | 32 | ```html 33 | 34 | 35 | 36 | ``` 37 | 38 | 3. Create html structure: 39 | 40 | ```html 41 | 55 | 56 | 57 | ``` 58 | 59 | 4. Initialize draggable and sortable: 60 | 61 | ```javascript 62 | $('#drag').draggable(/* options/callbacks or command */); 63 | $('#list').sortable(/* options/callbacks or command */); 64 | ``` 65 | 66 | 67 | Draggable 68 | --------- 69 | 70 | Options and callbacks: 71 | 72 | * ```handle``` (string) - selector of handle element (if null the complete element will be used as handle) 73 | * ```delegate``` (string) - selector of delegate element (allows delegate-binding, delegate will be referenced as this in the event callbacks) 74 | * ```revert``` (boolean) - if true the element reverts to its origin after dropping 75 | * ```placeholder``` (boolean) - if true a transparent clone of the element is left at the origin while dragging 76 | * ```droptarget``` (string) - selector for valid drop targets 77 | * ```scroll``` (boolean) - if true the scrolling parent will be automatically scrolled while dragging 78 | * ```update``` (function) - callback after dragging. Arguments: event, Draggable instance. This: draggable element. 79 | * ```drop``` (function) - callback after dropping on valid droptarget. Arguments: event, droptarget element. This: draggable.element 80 | 81 | Commands: 82 | 83 | ```javascript 84 | $('#drag').draggable('destroy'); 85 | ``` 86 | 87 | * ```destroy``` - deactivates the Draggable instance and unbinds all listeners 88 | 89 | 90 | Limitations/Issues: 91 | 92 | When ```placeholder``` or ```revert``` is set to true, click events on the draggable element would be swallowed by the generated clone. For most browsers (except Internet Explorer) this has been fixed by setting the clone to ```pointer-events: none;```. 93 | 94 | 95 | Sortable 96 | -------- 97 | 98 | Options and callbacks: 99 | 100 | * ```handle``` (string) - selector of handle element (if null the complete element will be used as handle) 101 | * ```container``` (string) - selector for container elements 102 | * ```nodes``` (string) - selector for node elements 103 | * ```autocreate``` (boolean) - automatically create nested containers within nodes 104 | * ```group``` (boolean) - if true all elements in the jQuery object are grouped (items can dragged between them) 105 | * ```scroll``` (boolean) - if true the scrolling parent will be automatically scrolled while dragging 106 | * ```update``` (function) - callback after sorting. Arguments: event, Sortable instance. This: sortable element. 107 | 108 | Commands: 109 | 110 | ```javascript 111 | $('#list').sortable('serialize'); 112 | $('#list').sortable('destroy'); 113 | ``` 114 | 115 | * ```serialize``` - returns an object representing the structure of the nested list 116 | * ```destroy``` - deactivates the Sortable instance and unbinds all listeners 117 | 118 | -------------------------------------------------------------------------------- /demo/demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 55 | Drag and Drop Demo 56 | 57 | 58 |

Drag and Drop Demo

59 | 60 |

Draggables

61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 |

Drop here

72 | 73 |

Sortable – Normal List

74 | 92 | 93 |

Sortable – Floating LI elements

94 | 108 | 109 |

Sortable – Inline-block LI elements

110 | 124 | 125 |

Sortable – Grouped Lists

126 | 134 | 142 | 143 |
144 |

Sortable – List of DIVs

145 |
Child 1
146 |
Child 2
147 |
148 | Child 3 149 |
150 |
Subchild
151 |
152 |
153 |
154 | 155 |

Off Switch

156 | 157 | 158 | 159 | 160 | -------------------------------------------------------------------------------- /src/draganddrop.js: -------------------------------------------------------------------------------- 1 | function factory($) { 2 | "use strict"; 3 | 4 | 5 | function Sortable(el, options) { 6 | var self = this, 7 | $sortable = $(el), 8 | container_type = $sortable[0].nodeName, 9 | node_type = (container_type == 'OL' || container_type == 'UL') ? 'LI' : 'DIV', 10 | defaults = { 11 | //options 12 | handle: false, 13 | container: container_type, 14 | container_type: container_type, 15 | same_depth: false, 16 | make_unselectable: false, 17 | nodes: node_type, 18 | nodes_type: node_type, 19 | placeholder_class: null, 20 | auto_container_class: 'sortable_container', 21 | autocreate: false, 22 | group: false, 23 | scroll: false, 24 | //callbacks 25 | update: null 26 | }; 27 | 28 | self.$sortable = $sortable.data('sortable', self); 29 | self.options = $.extend({}, defaults, options); 30 | 31 | self.init(); 32 | } 33 | 34 | Sortable.prototype.invoke = function(command) { 35 | var self = this; 36 | if (command === 'destroy') { 37 | return self.destroy(); 38 | } else if (command === 'serialize') { 39 | return self.serialize(self.$sortable); 40 | } 41 | }; 42 | 43 | Sortable.prototype.init = function() { 44 | var self = this, 45 | $clone, 46 | $placeholder, 47 | origin, 48 | info; 49 | 50 | if (self.options.make_unselectable) { 51 | $('html').unselectable(); 52 | } 53 | 54 | self.$sortable 55 | .addClass('sortable') 56 | .on('destroy.sortable', function() { 57 | self.destroy(); 58 | }); 59 | 60 | function find_insert_point($node, offset) { 61 | var containers, 62 | best, 63 | depth; 64 | 65 | if (!offset) { 66 | return; 67 | } 68 | 69 | containers = self.$sortable 70 | .add(self.$sortable.find(self.options.container)) 71 | .not($node.find(self.options.container)) 72 | .not($clone.find(self.options.container)) 73 | .not(self.find_nodes()); 74 | 75 | if (self.options.same_depth) { 76 | depth = $node.parent().nestingDepth('ul'); 77 | containers = containers.filter(function() { 78 | return $(this).nestingDepth('ul') == depth; 79 | }); 80 | } 81 | 82 | $placeholder.hide(); 83 | containers.each(function(ix, container) { 84 | var $trailing = $(self.create_placeholder()).appendTo(container), 85 | $children = $(container).children(self.options.nodes).not('.sortable_clone'), 86 | $candidate, 87 | n, 88 | dist; 89 | 90 | for (n = 0; n < $children.length; n++) { 91 | $candidate = $children.eq(n); 92 | dist = self.square_dist($candidate.offset(), offset); 93 | if (!best || best.dist > dist) { 94 | best = {container: container, before: $candidate[0], dist: dist}; 95 | } 96 | } 97 | 98 | $trailing.remove(); 99 | }); 100 | $placeholder.show(); 101 | 102 | return best; 103 | } 104 | 105 | function insert($element, best) { 106 | var $container = $(best.container); 107 | if (best.before && best.before.closest('html')) { 108 | $element.insertBefore(best.before); 109 | } else { 110 | $element.appendTo($container); 111 | } 112 | }; 113 | 114 | self.$sortable.dragaware($.extend({}, self.options, { 115 | delegate: self.options.nodes, 116 | /** 117 | * drag start - create clone and placeholder, keep drag start origin. 118 | */ 119 | dragstart: function(evt) { 120 | var $node = $(this); 121 | 122 | $clone = $node.clone() 123 | .removeAttr('id') 124 | .addClass('sortable_clone') 125 | .css({position: 'absolute'}) 126 | .insertAfter($node) 127 | .offset($node.offset()); 128 | $placeholder = self.create_placeholder() 129 | .css({height: $node.outerHeight(), width: $node.outerWidth()}) 130 | .insertAfter($node); 131 | $node.hide(); 132 | 133 | origin = new PositionHelper($clone.offset()); 134 | info = {from: $node.index()}; 135 | 136 | if (self.options.autocreate) { 137 | self.find_nodes().filter(function(ix, el) { 138 | return $(el).find(self.options.container).length == 0; 139 | }).append('<' + self.options.container_type + ' class="' + self.options.auto_container_class + '"/>'); 140 | } 141 | }, 142 | 143 | /** 144 | * drag - reposition clone, check for best insert position, move placeholder in dom accordingly. 145 | */ 146 | drag: function(evt, pos) { 147 | var $node = $(this), 148 | offset = origin.absolutize(pos), 149 | best = find_insert_point($node, offset); 150 | 151 | $clone.offset(offset); 152 | insert($placeholder, best); 153 | }, 154 | 155 | /** 156 | * drag stop - clean up. 157 | */ 158 | dragstop: function(evt, pos) { 159 | var $node = $(this), 160 | offset = origin.absolutize(pos), 161 | best = find_insert_point($node, offset); 162 | 163 | if (best) { 164 | insert($node, best); 165 | } 166 | $node.show(); 167 | 168 | if ($clone) { 169 | $clone.remove(); 170 | } 171 | if ($placeholder) { 172 | $placeholder.remove(); 173 | } 174 | $clone = null; 175 | $placeholder = null; 176 | info.to = $node.index(); 177 | 178 | if (best && self.options.update) { 179 | self.options.update.call(self.$sortable, evt, self, info); 180 | } 181 | self.$sortable.trigger('update'); 182 | } 183 | })); 184 | }; 185 | 186 | Sortable.prototype.destroy = function() { 187 | var self = this; 188 | 189 | if (self.options.make_unselectable) { 190 | $('html').unselectable('destroy'); 191 | } 192 | 193 | self.$sortable 194 | .removeClass('sortable') 195 | .off('.sortable') 196 | .dragaware('destroy'); 197 | }; 198 | 199 | Sortable.prototype.serialize = function(container) { 200 | var self = this; 201 | return container.children(self.options.nodes).not(self.options.container).map(function(ix, el) { 202 | var $el = $(el), 203 | text = $el.clone().children().remove().end().text().trim(), //text only without children 204 | id = $el.attr('id'), 205 | node = {id: id || text}; 206 | if ($el.find(self.options.nodes).length) { 207 | node.children = self.serialize($el.children(self.options.container)); 208 | } 209 | return node; 210 | }).get(); 211 | }; 212 | 213 | Sortable.prototype.find_nodes = function() { 214 | var self = this; 215 | return self.$sortable.find(self.options.nodes).not(self.options.container); 216 | }; 217 | 218 | Sortable.prototype.create_placeholder = function() { 219 | var self = this; 220 | return $('<' + self.options.nodes_type + '/>') 221 | .addClass('sortable_placeholder') 222 | .addClass(self.options.placeholder_class); 223 | }; 224 | 225 | Sortable.prototype.square_dist = function(pos1, pos2) { 226 | return Math.pow(pos2.left - pos1.left, 2) + Math.pow(pos2.top - pos1.top, 2); 227 | }; 228 | 229 | 230 | 231 | 232 | function Draggable(el, options) { 233 | var self = this, 234 | defaults = { 235 | //options 236 | handle: false, 237 | delegate: false, 238 | revert: false, 239 | placeholder: false, 240 | droptarget: false, 241 | container: false, 242 | scroll: false, 243 | //callbacks 244 | update: null, 245 | drop: null 246 | }; 247 | 248 | self.$draggable = $(el).data('draggable', self); 249 | self.options = $.extend({}, defaults, options); 250 | 251 | self.init(); 252 | } 253 | 254 | Draggable.prototype.init = function() { 255 | var self = this, 256 | $clone, 257 | origin; 258 | 259 | self.$draggable 260 | .addClass('draggable') 261 | .on('destroy.draggable', function() { 262 | self.destroy(); 263 | }); 264 | 265 | function check_droptarget(pos) { 266 | var $over; 267 | 268 | $('.hovering').removeClass('hovering'); 269 | 270 | $clone.hide(); 271 | $over = $(document.elementFromPoint(pos.clientX, pos.clientY)).closest(self.options.droptarget); 272 | $clone.show(); 273 | 274 | if ($over.length) { 275 | $over.addClass('hovering'); 276 | return $over; 277 | } 278 | } 279 | 280 | self.$draggable.dragaware($.extend({}, self.options, { 281 | /** 282 | * drag start - create clone, keep drag start origin. 283 | */ 284 | dragstart: function(evt) { 285 | var $this = $(this); 286 | if (self.options.placeholder || self.options.revert) { 287 | $clone = $this.clone() 288 | .removeAttr('id') 289 | .addClass('draggable_clone') 290 | .css({position: 'absolute'}) 291 | .appendTo(self.options.container || $this.parent()) 292 | .offset($this.offset()); 293 | if (!self.options.placeholder) { 294 | $(this).invisible(); 295 | } 296 | } else { 297 | $clone = $this; 298 | } 299 | 300 | origin = new PositionHelper($clone.offset()); 301 | }, 302 | 303 | /** 304 | * drag - reposition clone. 305 | */ 306 | drag: function(evt, pos) { 307 | var $droptarget = check_droptarget(pos); 308 | $clone.offset(origin.absolutize(pos)); 309 | }, 310 | 311 | /** 312 | * drag stop - clean up. 313 | */ 314 | dragstop: function(evt, pos) { 315 | var $this = $(this), 316 | $droptarget = check_droptarget(pos); 317 | 318 | if (self.options.revert || self.options.placeholder) { 319 | $this.visible(); 320 | if (!self.options.revert) { 321 | $this.offset(origin.absolutize(pos)); 322 | } 323 | $clone.remove(); 324 | } 325 | 326 | $clone = null; 327 | 328 | if (self.options.update) { 329 | self.options.update.call($this, evt, self); 330 | } 331 | 332 | $this.trigger('update'); 333 | 334 | if ($droptarget) { 335 | if (self.options.drop) { 336 | self.options.drop.call($this, evt, $droptarget[0]); 337 | } 338 | $droptarget.trigger('drop', [$this]); 339 | $droptarget.removeClass('hovering'); 340 | } 341 | else { 342 | if (self.options.onrevert) { 343 | self.options.onrevert.call($this, evt); 344 | } 345 | } 346 | } 347 | })); 348 | }; 349 | 350 | Draggable.prototype.destroy = function() { 351 | var self = this; 352 | 353 | self.$draggable 354 | .dragaware('destroy') 355 | .removeClass('draggable') 356 | .off('.draggable'); 357 | }; 358 | 359 | 360 | 361 | 362 | function Droppable(el, options) { 363 | var self = this, 364 | defaults = { 365 | //options 366 | accept: false, 367 | //callbacks 368 | drop: null 369 | }; 370 | 371 | self.$droppable = $(el).data('droppable', self); 372 | self.options = $.extend({}, defaults, options); 373 | 374 | self.init(); 375 | } 376 | 377 | Droppable.prototype.init = function() { 378 | var self = this; 379 | 380 | self.$droppable 381 | .addClass('droppable') 382 | .on('drop', function(evt, $draggable) { 383 | if (self.options.accept && !$draggable.is(self.options.accept)) { 384 | return; 385 | } 386 | if (self.options.drop) { 387 | self.options.drop.call(self.$droppable, evt, $draggable); 388 | } 389 | }) 390 | .on('destroy.droppable', function() { 391 | self.destroy(); 392 | }); 393 | }; 394 | 395 | Droppable.prototype.destroy = function() { 396 | var self = this; 397 | 398 | self.$droppable 399 | .removeClass('droppable') 400 | .off('.droppable'); 401 | }; 402 | 403 | 404 | 405 | 406 | function Dragaware(el, options) { 407 | var $dragaware = $(el), 408 | $reference = null, 409 | origin = null, 410 | lastpos = null, 411 | defaults = { 412 | //options 413 | handle: null, 414 | delegate: null, 415 | scroll: false, 416 | scrollspeed: 15, 417 | scrolltimeout: 50, 418 | //callbacks 419 | dragstart: null, 420 | drag: null, 421 | dragstop: null 422 | }, 423 | scrolltimeout; 424 | 425 | options = $.extend({}, defaults, options); 426 | 427 | /** 428 | * Returns the event position 429 | * dX, dY relative to drag start 430 | * pageX, pageY relative to document 431 | * clientX, clientY relative to browser window 432 | */ 433 | function evtpos(evt) { 434 | evt = window.hasOwnProperty('event') ? window.event : evt; 435 | //extract touch event if present 436 | if (evt.type.substr(0, 5) === 'touch') { 437 | evt = evt.hasOwnProperty('originalEvent') ? evt.originalEvent : evt; 438 | evt = evt.touches[0]; 439 | } 440 | 441 | return { 442 | pageX: evt.pageX, 443 | pageY: evt.pageY, 444 | clientX: evt.clientX, 445 | clientY: evt.clientY, 446 | dX: origin ? evt.pageX - origin.pageX : 0, 447 | dY: origin ? evt.pageY - origin.pageY : 0 448 | }; 449 | } 450 | 451 | function autoscroll(pos) { 452 | //TODO: allow window scrolling 453 | //TODO: handle nested scroll containers 454 | var sp = $dragaware.scrollParent(), 455 | mouse = {x: pos.pageX, y: pos.pageY}, 456 | offset = sp.offset(), 457 | scrollLeft = sp.scrollLeft(), 458 | scrollTop = sp.scrollTop(), 459 | width = sp.width(), 460 | height = sp.height(); 461 | 462 | window.clearTimeout(scrolltimeout); 463 | 464 | if (scrollLeft > 0 && mouse.x < offset.left) { 465 | sp.scrollLeft(scrollLeft - options.scrollspeed); 466 | } else if (scrollLeft < sp.prop('scrollWidth') - width && mouse.x > offset.left + width) { 467 | sp.scrollLeft(scrollLeft + options.scrollspeed); 468 | } else if (scrollTop > 0 && mouse.y < offset.top) { 469 | sp.scrollTop(scrollTop - options.scrollspeed); 470 | } else if (scrollTop < sp.prop('scrollHeight') - height && mouse.y > offset.top + height) { 471 | sp.scrollTop(scrollTop + options.scrollspeed); 472 | } else { 473 | return; //so we don't set the next timeout 474 | } 475 | 476 | scrolltimeout = window.setTimeout(function() { autoscroll(pos); }, options.scrolltimeout); 477 | } 478 | 479 | function start(evt) { 480 | var $target = $(evt.target); 481 | 482 | $reference = options.delegate ? $target.closest(options.delegate) : $dragaware; 483 | 484 | if ($target.closest(options.handle || '*').length && (evt.type == 'touchstart' || evt.button == 0)) { 485 | origin = lastpos = evtpos(evt); 486 | if (options.dragstart) { 487 | options.dragstart.call($reference, evt, lastpos); 488 | } 489 | 490 | $reference.addClass('dragging'); 491 | $reference.trigger('dragstart'); 492 | 493 | //late binding of event listeners 494 | $(document) 495 | .on('touchend.dragaware mouseup.dragaware click.dragaware', end) 496 | .on('touchmove.dragaware mousemove.dragaware', move); 497 | return false 498 | } 499 | } 500 | 501 | function move(evt) { 502 | lastpos = evtpos(evt); 503 | 504 | if (options.scroll) { 505 | autoscroll(lastpos); 506 | } 507 | 508 | $reference.trigger('dragging'); 509 | 510 | if (options.drag) { 511 | options.drag.call($reference, evt, lastpos); 512 | return false; 513 | } 514 | } 515 | 516 | function end(evt) { 517 | window.clearTimeout(scrolltimeout); 518 | 519 | if (options.dragstop) { 520 | options.dragstop.call($reference, evt, lastpos); 521 | } 522 | 523 | $reference.removeClass('dragging'); 524 | $reference.trigger('dragstop'); 525 | 526 | origin = false; 527 | lastpos = false; 528 | $reference = false; 529 | 530 | //unbinding of event listeners 531 | $(document) 532 | .off('.dragaware'); 533 | 534 | return false; 535 | } 536 | 537 | $dragaware 538 | .addClass('dragaware') 539 | .on('touchstart.dragaware mousedown.dragaware', options.delegate, start); 540 | 541 | $dragaware.on('destroy.dragaware', function() { 542 | $dragaware 543 | .removeClass('dragaware') 544 | .off('.dragaware'); 545 | }); 546 | } 547 | 548 | 549 | 550 | 551 | function PositionHelper(origin) { 552 | this.origin = origin; 553 | } 554 | PositionHelper.prototype.absolutize = function(pos) { 555 | if (!pos) { 556 | return this.origin; 557 | } 558 | return {top: this.origin.top + pos.dY, left: this.origin.left + pos.dX}; 559 | }; 560 | 561 | 562 | 563 | 564 | // Plugin registration. 565 | 566 | 567 | /** 568 | * Sortable plugin. 569 | */ 570 | $.fn.sortable = function(options) { 571 | var filtered = this.not(function() { 572 | return $(this).is('.sortable') || $(this).closest('.sortable').length; 573 | }); 574 | 575 | if (this.data('sortable') && typeof options === 'string') { 576 | return this.data('sortable').invoke(options); 577 | } 578 | 579 | if (filtered.length && options && options.group) { 580 | new Sortable(filtered, options); 581 | } else { 582 | filtered.each(function(ix, el) { 583 | new Sortable(el, options); 584 | }); 585 | } 586 | return this; 587 | }; 588 | 589 | 590 | /** 591 | * Draggable plugin. 592 | */ 593 | $.fn.draggable = function(options) { 594 | if (options === 'destroy') { 595 | this.trigger('destroy.draggable'); 596 | } else { 597 | this.not('.draggable').each(function(ix, el) { 598 | new Draggable(el, options); 599 | }); 600 | } 601 | return this; 602 | }; 603 | 604 | 605 | /** 606 | * Droppable plugin. 607 | */ 608 | $.fn.droppable = function(options) { 609 | if (options === 'destroy') { 610 | this.trigger('destroy.droppable'); 611 | } else { 612 | this.not('.droppable').each(function(ix, el) { 613 | new Droppable(el, options); 614 | }); 615 | } 616 | return this; 617 | }; 618 | 619 | 620 | /** 621 | * Dragaware plugin. 622 | */ 623 | $.fn.dragaware = function(options) { 624 | if (options === 'destroy') { 625 | this.trigger('destroy.dragaware'); 626 | } else { 627 | this.not('.dragaware').each(function(ix, el) { 628 | new Dragaware(el, options); 629 | }); 630 | } 631 | return this; 632 | }; 633 | 634 | 635 | /** 636 | * Disables mouse selection. 637 | */ 638 | $.fn.unselectable = function(command) { 639 | function disable() { 640 | return false; 641 | } 642 | 643 | if (command == 'destroy') { 644 | return this 645 | .removeClass('unselectable') 646 | .removeAttr('unselectable') 647 | .off('selectstart.unselectable'); 648 | } else { 649 | return this 650 | .addClass('unselectable') 651 | .attr('unselectable','on') 652 | .on('selectstart.unselectable', disable); 653 | } 654 | }; 655 | 656 | 657 | $.fn.invisible = function() { 658 | return this.css({visibility: 'hidden'}); 659 | }; 660 | 661 | 662 | $.fn.visible = function() { 663 | return this.css({visibility: 'visible'}); 664 | }; 665 | 666 | 667 | $.fn.scrollParent = function() { 668 | return this.parents().addBack().filter(function() { 669 | var p = $(this); 670 | return (/(scroll|auto)/).test(p.css("overflow-x") + p.css("overflow-y") + p.css("overflow")); 671 | }); 672 | }; 673 | 674 | $.fn.nestingDepth = function(selector) { 675 | var parent = this.parent().closest(selector || '*'); 676 | if (parent.length) { 677 | return parent.nestingDepth(selector) + 1; 678 | } else { 679 | return 0; 680 | } 681 | }; 682 | 683 | return { 684 | Sortable, 685 | Draggable, 686 | Droppable, 687 | Dragaware, 688 | PositionHelper, 689 | }; 690 | 691 | 692 | } 693 | 694 | 695 | if (typeof define !== 'undefined') { 696 | define(['jquery'], factory); 697 | } else { 698 | factory(jQuery, factory); 699 | } 700 | --------------------------------------------------------------------------------