github.com/insionng/yougam@v0.0.0-20170714101924-2bc18d833463/public/js/nestable/jquery.nestable.js (about) 1 /*! 2 * Nestable jQuery Plugin - Copyright (c) 2012 David Bushell - http://dbushell.com/ 3 * Dual-licensed under the BSD or MIT licenses 4 */ 5 ;(function($, window, document, undefined) 6 { 7 var hasTouch = 'ontouchstart' in window; 8 9 /** 10 * Detect CSS pointer-events property 11 * events are normally disabled on the dragging element to avoid conflicts 12 * https://github.com/ausi/Feature-detection-technique-for-pointer-events/blob/master/modernizr-pointerevents.js 13 */ 14 var hasPointerEvents = (function() 15 { 16 var el = document.createElement('div'), 17 docEl = document.documentElement; 18 if (!('pointerEvents' in el.style)) { 19 return false; 20 } 21 el.style.pointerEvents = 'auto'; 22 el.style.pointerEvents = 'x'; 23 docEl.appendChild(el); 24 var supports = window.getComputedStyle && window.getComputedStyle(el, '').pointerEvents === 'auto'; 25 docEl.removeChild(el); 26 return !!supports; 27 })(); 28 29 var eStart = hasTouch ? 'touchstart' : 'mousedown', 30 eMove = hasTouch ? 'touchmove' : 'mousemove', 31 eEnd = hasTouch ? 'touchend' : 'mouseup'; 32 eCancel = hasTouch ? 'touchcancel' : 'mouseup'; 33 34 var defaults = { 35 listNodeName : 'ol', 36 itemNodeName : 'li', 37 rootClass : 'dd', 38 listClass : 'dd-list', 39 itemClass : 'dd-item', 40 dragClass : 'dd-dragel', 41 handleClass : 'dd-handle', 42 collapsedClass : 'dd-collapsed', 43 placeClass : 'dd-placeholder', 44 noDragClass : 'dd-nodrag', 45 emptyClass : 'dd-empty', 46 expandBtnHTML : '<button data-action="expand" type="button">Expand</button>', 47 collapseBtnHTML : '<button data-action="collapse" type="button">Collapse</button>', 48 group : 0, 49 maxDepth : 5, 50 threshold : 20 51 }; 52 53 function Plugin(element, options) 54 { 55 this.w = $(window); 56 this.el = $(element); 57 this.options = $.extend({}, defaults, options); 58 this.init(); 59 } 60 61 Plugin.prototype = { 62 63 init: function() 64 { 65 var list = this; 66 67 list.reset(); 68 69 list.el.data('nestable-group', this.options.group); 70 71 list.placeEl = $('<div class="' + list.options.placeClass + '"/>'); 72 73 $.each(this.el.find(list.options.itemNodeName), function(k, el) { 74 list.setParent($(el)); 75 }); 76 77 list.el.on('click', 'button', function(e) { 78 if (list.dragEl || (!hasTouch && e.button !== 0)) { 79 return; 80 } 81 var target = $(e.currentTarget), 82 action = target.data('action'), 83 item = target.parent(list.options.itemNodeName); 84 if (action === 'collapse') { 85 list.collapseItem(item); 86 } 87 if (action === 'expand') { 88 list.expandItem(item); 89 } 90 }); 91 92 var onStartEvent = function(e) 93 { 94 var handle = $(e.target); 95 if (!handle.hasClass(list.options.handleClass)) { 96 if (handle.closest('.' + list.options.noDragClass).length) { 97 return; 98 } 99 handle = handle.closest('.' + list.options.handleClass); 100 } 101 if (!handle.length || list.dragEl || (!hasTouch && e.button !== 0) || (hasTouch && e.touches.length !== 1)) { 102 return; 103 } 104 e.preventDefault(); 105 list.dragStart(hasTouch ? e.touches[0] : e); 106 }; 107 108 var onMoveEvent = function(e) 109 { 110 if (list.dragEl) { 111 e.preventDefault(); 112 list.dragMove(hasTouch ? e.touches[0] : e); 113 } 114 }; 115 116 var onEndEvent = function(e) 117 { 118 if (list.dragEl) { 119 e.preventDefault(); 120 list.dragStop(hasTouch ? e.touches[0] : e); 121 } 122 }; 123 124 if (hasTouch) { 125 list.el[0].addEventListener(eStart, onStartEvent, false); 126 window.addEventListener(eMove, onMoveEvent, false); 127 window.addEventListener(eEnd, onEndEvent, false); 128 window.addEventListener(eCancel, onEndEvent, false); 129 } else { 130 list.el.on(eStart, onStartEvent); 131 list.w.on(eMove, onMoveEvent); 132 list.w.on(eEnd, onEndEvent); 133 } 134 135 }, 136 137 serialize: function() 138 { 139 var data, 140 depth = 0, 141 list = this; 142 step = function(level, depth) 143 { 144 var array = [ ], 145 items = level.children(list.options.itemNodeName); 146 items.each(function() 147 { 148 var li = $(this), 149 item = $.extend({}, li.data()), 150 sub = li.children(list.options.listNodeName); 151 if (sub.length) { 152 item.children = step(sub, depth + 1); 153 } 154 array.push(item); 155 }); 156 return array; 157 }; 158 data = step(list.el.find(list.options.listNodeName).first(), depth); 159 return data; 160 }, 161 162 serialise: function() 163 { 164 return this.serialize(); 165 }, 166 167 reset: function() 168 { 169 this.mouse = { 170 offsetX : 0, 171 offsetY : 0, 172 startX : 0, 173 startY : 0, 174 lastX : 0, 175 lastY : 0, 176 nowX : 0, 177 nowY : 0, 178 distX : 0, 179 distY : 0, 180 dirAx : 0, 181 dirX : 0, 182 dirY : 0, 183 lastDirX : 0, 184 lastDirY : 0, 185 distAxX : 0, 186 distAxY : 0 187 }; 188 this.moving = false; 189 this.dragEl = null; 190 this.dragRootEl = null; 191 this.dragDepth = 0; 192 this.hasNewRoot = false; 193 this.pointEl = null; 194 }, 195 196 expandItem: function(li) 197 { 198 li.removeClass(this.options.collapsedClass); 199 li.children('[data-action="expand"]').hide(); 200 li.children('[data-action="collapse"]').show(); 201 li.children(this.options.listNodeName).show(); 202 }, 203 204 collapseItem: function(li) 205 { 206 var lists = li.children(this.options.listNodeName); 207 if (lists.length) { 208 li.addClass(this.options.collapsedClass); 209 li.children('[data-action="collapse"]').hide(); 210 li.children('[data-action="expand"]').show(); 211 li.children(this.options.listNodeName).hide(); 212 } 213 }, 214 215 expandAll: function() 216 { 217 var list = this; 218 list.el.find(list.options.itemNodeName).each(function() { 219 list.expandItem($(this)); 220 }); 221 }, 222 223 collapseAll: function() 224 { 225 var list = this; 226 list.el.find(list.options.itemNodeName).each(function() { 227 list.collapseItem($(this)); 228 }); 229 }, 230 231 setParent: function(li) 232 { 233 if (li.children(this.options.listNodeName).length) { 234 li.prepend($(this.options.expandBtnHTML)); 235 li.prepend($(this.options.collapseBtnHTML)); 236 } 237 li.children('[data-action="expand"]').hide(); 238 }, 239 240 unsetParent: function(li) 241 { 242 li.removeClass(this.options.collapsedClass); 243 li.children('[data-action]').remove(); 244 li.children(this.options.listNodeName).remove(); 245 }, 246 247 dragStart: function(e) 248 { 249 var mouse = this.mouse, 250 target = $(e.target), 251 dragItem = target.closest(this.options.itemNodeName); 252 253 this.placeEl.css('height', dragItem.height()); 254 255 mouse.offsetX = e.offsetX !== undefined ? e.offsetX : e.pageX - target.offset().left; 256 mouse.offsetY = e.offsetY !== undefined ? e.offsetY : e.pageY - target.offset().top; 257 mouse.startX = mouse.lastX = e.pageX; 258 mouse.startY = mouse.lastY = e.pageY; 259 260 this.dragRootEl = this.el; 261 262 this.dragEl = $(document.createElement(this.options.listNodeName)).addClass(this.options.listClass + ' ' + this.options.dragClass); 263 this.dragEl.css('width', dragItem.width()); 264 265 // fix for zepto.js 266 //dragItem.after(this.placeEl).detach().appendTo(this.dragEl); 267 dragItem.after(this.placeEl); 268 dragItem[0].parentNode.removeChild(dragItem[0]); 269 dragItem.appendTo(this.dragEl); 270 271 $(document.body).append(this.dragEl); 272 this.dragEl.css({ 273 'left' : e.pageX - mouse.offsetX, 274 'top' : e.pageY - mouse.offsetY 275 }); 276 // total depth of dragging item 277 var i, depth, 278 items = this.dragEl.find(this.options.itemNodeName); 279 for (i = 0; i < items.length; i++) { 280 depth = $(items[i]).parents(this.options.listNodeName).length; 281 if (depth > this.dragDepth) { 282 this.dragDepth = depth; 283 } 284 } 285 }, 286 287 dragStop: function(e) 288 { 289 // fix for zepto.js 290 //this.placeEl.replaceWith(this.dragEl.children(this.options.itemNodeName + ':first').detach()); 291 var el = this.dragEl.children(this.options.itemNodeName).first(); 292 el[0].parentNode.removeChild(el[0]); 293 this.placeEl.replaceWith(el); 294 295 this.dragEl.remove(); 296 this.el.trigger('change'); 297 if (this.hasNewRoot) { 298 this.dragRootEl.trigger('change'); 299 } 300 this.reset(); 301 }, 302 303 dragMove: function(e) 304 { 305 var list, parent, prev, next, depth, 306 opt = this.options, 307 mouse = this.mouse; 308 309 this.dragEl.css({ 310 'left' : e.pageX - mouse.offsetX, 311 'top' : e.pageY - mouse.offsetY 312 }); 313 314 // mouse position last events 315 mouse.lastX = mouse.nowX; 316 mouse.lastY = mouse.nowY; 317 // mouse position this events 318 mouse.nowX = e.pageX; 319 mouse.nowY = e.pageY; 320 // distance mouse moved between events 321 mouse.distX = mouse.nowX - mouse.lastX; 322 mouse.distY = mouse.nowY - mouse.lastY; 323 // direction mouse was moving 324 mouse.lastDirX = mouse.dirX; 325 mouse.lastDirY = mouse.dirY; 326 // direction mouse is now moving (on both axis) 327 mouse.dirX = mouse.distX === 0 ? 0 : mouse.distX > 0 ? 1 : -1; 328 mouse.dirY = mouse.distY === 0 ? 0 : mouse.distY > 0 ? 1 : -1; 329 // axis mouse is now moving on 330 var newAx = Math.abs(mouse.distX) > Math.abs(mouse.distY) ? 1 : 0; 331 332 // do nothing on first move 333 if (!mouse.moving) { 334 mouse.dirAx = newAx; 335 mouse.moving = true; 336 return; 337 } 338 339 // calc distance moved on this axis (and direction) 340 if (mouse.dirAx !== newAx) { 341 mouse.distAxX = 0; 342 mouse.distAxY = 0; 343 } else { 344 mouse.distAxX += Math.abs(mouse.distX); 345 if (mouse.dirX !== 0 && mouse.dirX !== mouse.lastDirX) { 346 mouse.distAxX = 0; 347 } 348 mouse.distAxY += Math.abs(mouse.distY); 349 if (mouse.dirY !== 0 && mouse.dirY !== mouse.lastDirY) { 350 mouse.distAxY = 0; 351 } 352 } 353 mouse.dirAx = newAx; 354 355 /** 356 * move horizontal 357 */ 358 if (mouse.dirAx && mouse.distAxX >= opt.threshold) { 359 // reset move distance on x-axis for new phase 360 mouse.distAxX = 0; 361 prev = this.placeEl.prev(opt.itemNodeName); 362 // increase horizontal level if previous sibling exists and is not collapsed 363 if (mouse.distX > 0 && prev.length && !prev.hasClass(opt.collapsedClass)) { 364 // cannot increase level when item above is collapsed 365 list = prev.find(opt.listNodeName).last(); 366 // check if depth limit has reached 367 depth = this.placeEl.parents(opt.listNodeName).length; 368 if (depth + this.dragDepth <= opt.maxDepth) { 369 // create new sub-level if one doesn't exist 370 if (!list.length) { 371 list = $('<' + opt.listNodeName + '/>').addClass(opt.listClass); 372 list.append(this.placeEl); 373 prev.append(list); 374 this.setParent(prev); 375 } else { 376 // else append to next level up 377 list = prev.children(opt.listNodeName).last(); 378 list.append(this.placeEl); 379 } 380 } 381 } 382 // decrease horizontal level 383 if (mouse.distX < 0) { 384 // we can't decrease a level if an item preceeds the current one 385 next = this.placeEl.next(opt.itemNodeName); 386 if (!next.length) { 387 parent = this.placeEl.parent(); 388 this.placeEl.closest(opt.itemNodeName).after(this.placeEl); 389 if (!parent.children().length) { 390 this.unsetParent(parent.parent()); 391 } 392 } 393 } 394 } 395 396 var isEmpty = false; 397 398 // find list item under cursor 399 if (!hasPointerEvents) { 400 this.dragEl[0].style.visibility = 'hidden'; 401 } 402 this.pointEl = $(document.elementFromPoint(e.pageX - document.body.scrollLeft, e.pageY - (window.pageYOffset || document.documentElement.scrollTop))); 403 if (!hasPointerEvents) { 404 this.dragEl[0].style.visibility = 'visible'; 405 } 406 if (this.pointEl.hasClass(opt.handleClass)) { 407 this.pointEl = this.pointEl.parent(opt.itemNodeName); 408 } 409 if (this.pointEl.hasClass(opt.emptyClass)) { 410 isEmpty = true; 411 } 412 else if (!this.pointEl.length || !this.pointEl.hasClass(opt.itemClass)) { 413 return; 414 } 415 416 // find parent list of item under cursor 417 var pointElRoot = this.pointEl.closest('.' + opt.rootClass), 418 isNewRoot = this.dragRootEl.data('nestable-id') !== pointElRoot.data('nestable-id'); 419 420 /** 421 * move vertical 422 */ 423 if (!mouse.dirAx || isNewRoot || isEmpty) { 424 // check if groups match if dragging over new root 425 if (isNewRoot && opt.group !== pointElRoot.data('nestable-group')) { 426 return; 427 } 428 // check depth limit 429 depth = this.dragDepth - 1 + this.pointEl.parents(opt.listNodeName).length; 430 if (depth > opt.maxDepth) { 431 return; 432 } 433 var before = e.pageY < (this.pointEl.offset().top + this.pointEl.height() / 2); 434 parent = this.placeEl.parent(); 435 // if empty create new list to replace empty placeholder 436 if (isEmpty) { 437 list = $(document.createElement(opt.listNodeName)).addClass(opt.listClass); 438 list.append(this.placeEl); 439 this.pointEl.replaceWith(list); 440 } 441 else if (before) { 442 this.pointEl.before(this.placeEl); 443 } 444 else { 445 this.pointEl.after(this.placeEl); 446 } 447 if (!parent.children().length) { 448 this.unsetParent(parent.parent()); 449 } 450 if (!this.dragRootEl.find(opt.itemNodeName).length) { 451 this.dragRootEl.append('<div class="' + opt.emptyClass + '"/>'); 452 } 453 // parent root list has changed 454 if (isNewRoot) { 455 this.dragRootEl = pointElRoot; 456 this.hasNewRoot = this.el[0] !== this.dragRootEl[0]; 457 } 458 } 459 } 460 461 }; 462 463 $.fn.nestable = function(params) 464 { 465 var lists = this, 466 retval = this; 467 468 lists.each(function() 469 { 470 var plugin = $(this).data("nestable"); 471 472 if (!plugin) { 473 $(this).data("nestable", new Plugin(this, params)); 474 $(this).data("nestable-id", new Date().getTime()); 475 } else { 476 if (typeof params === 'string' && typeof plugin[params] === 'function') { 477 retval = plugin[params](); 478 } 479 } 480 }); 481 482 return retval || lists; 483 }; 484 485 })(window.jQuery || window.Zepto, window, document);