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