1/* 2 * jQuery UI Nested Sortable 3 * v 1.3.5 / 21 jun 2012 4 * http://mjsarfatti.com/code/nestedSortable 5 * 6 * Depends on: 7 * jquery.ui.sortable.js 1.8+ 8 * 9 * Copyright (c) 2010-2012 Manuele J Sarfatti 10 * Licensed under the MIT License 11 * http://www.opensource.org/licenses/mit-license.php 12 */ 13 14(function ($) { 15 $.widget( 16 'mjs.nestedSortable', 17 $.extend({}, $.ui.sortable.prototype, { 18 options: { 19 tabSize: 20, 20 disableNesting: 'mjs-nestedSortable-no-nesting', 21 errorClass: 'mjs-nestedSortable-error', 22 doNotClear: false, 23 listType: 'ol', 24 maxLevels: 0, 25 protectRoot: false, 26 rootID: null, 27 rtl: false, 28 isAllowed: function (item, parent) { 29 return true; 30 }, 31 }, 32 33 _create: function () { 34 this.element.data( 35 'sortable', 36 this.element.data('nestedSortable') 37 ); 38 39 if (!this.element.is(this.options.listType)) 40 throw new Error( 41 'nestedSortable: Please check the listType option is set to your actual list type' 42 ); 43 44 return $.ui.sortable.prototype._create.apply(this, arguments); 45 }, 46 47 destroy: function () { 48 this.element 49 .removeData('nestedSortable') 50 .unbind('.nestedSortable'); 51 return $.ui.sortable.prototype.destroy.apply(this, arguments); 52 }, 53 54 _mouseDrag: function (event) { 55 //Compute the helpers position 56 this.position = this._generatePosition(event); 57 this.positionAbs = this._convertPositionTo('absolute'); 58 59 if (!this.lastPositionAbs) { 60 this.lastPositionAbs = this.positionAbs; 61 } 62 63 var o = this.options; 64 65 //Do scrolling 66 if (this.options.scroll) { 67 var scrolled = false; 68 if ( 69 this.scrollParent[0] != document && 70 this.scrollParent[0].tagName != 'HTML' 71 ) { 72 if ( 73 this.overflowOffset.top + 74 this.scrollParent[0].offsetHeight - 75 event.pageY < 76 o.scrollSensitivity 77 ) 78 this.scrollParent[0].scrollTop = scrolled = 79 this.scrollParent[0].scrollTop + o.scrollSpeed; 80 else if ( 81 event.pageY - this.overflowOffset.top < 82 o.scrollSensitivity 83 ) 84 this.scrollParent[0].scrollTop = scrolled = 85 this.scrollParent[0].scrollTop - o.scrollSpeed; 86 87 if ( 88 this.overflowOffset.left + 89 this.scrollParent[0].offsetWidth - 90 event.pageX < 91 o.scrollSensitivity 92 ) 93 this.scrollParent[0].scrollLeft = scrolled = 94 this.scrollParent[0].scrollLeft + o.scrollSpeed; 95 else if ( 96 event.pageX - this.overflowOffset.left < 97 o.scrollSensitivity 98 ) 99 this.scrollParent[0].scrollLeft = scrolled = 100 this.scrollParent[0].scrollLeft - o.scrollSpeed; 101 } else { 102 if ( 103 event.pageY - $(document).scrollTop() < 104 o.scrollSensitivity 105 ) 106 scrolled = $(document).scrollTop( 107 $(document).scrollTop() - o.scrollSpeed 108 ); 109 else if ( 110 $(window).height() - 111 (event.pageY - $(document).scrollTop()) < 112 o.scrollSensitivity 113 ) 114 scrolled = $(document).scrollTop( 115 $(document).scrollTop() + o.scrollSpeed 116 ); 117 118 if ( 119 event.pageX - $(document).scrollLeft() < 120 o.scrollSensitivity 121 ) 122 scrolled = $(document).scrollLeft( 123 $(document).scrollLeft() - o.scrollSpeed 124 ); 125 else if ( 126 $(window).width() - 127 (event.pageX - $(document).scrollLeft()) < 128 o.scrollSensitivity 129 ) 130 scrolled = $(document).scrollLeft( 131 $(document).scrollLeft() + o.scrollSpeed 132 ); 133 } 134 135 if ( 136 scrolled !== false && 137 $.ui.ddmanager && 138 !o.dropBehaviour 139 ) 140 $.ui.ddmanager.prepareOffsets(this, event); 141 } 142 143 //Regenerate the absolute position used for position checks 144 this.positionAbs = this._convertPositionTo('absolute'); 145 146 // Find the top offset before rearrangement, 147 var previousTopOffset = this.placeholder.offset().top; 148 149 //Set the helper position 150 if (!this.options.axis || this.options.axis != 'y') 151 this.helper[0].style.left = this.position.left + 'px'; 152 if (!this.options.axis || this.options.axis != 'x') 153 this.helper[0].style.top = this.position.top + 'px'; 154 155 //Rearrange 156 for (var i = this.items.length - 1; i >= 0; i--) { 157 //Cache variables and intersection, continue if no intersection 158 var item = this.items[i], 159 itemElement = item.item[0], 160 intersection = this._intersectsWithPointer(item); 161 if (!intersection) continue; 162 163 if ( 164 itemElement != this.currentItem[0] && //cannot intersect with itself 165 this.placeholder[ 166 intersection == 1 ? 'next' : 'prev' 167 ]()[0] != itemElement && //no useless actions that have been done before 168 !$.contains(this.placeholder[0], itemElement) && //no action if the item moved is the parent of the item checked 169 (this.options.type == 'semi-dynamic' 170 ? !$.contains(this.element[0], itemElement) 171 : true) 172 //&& itemElement.parentNode == this.placeholder[0].parentNode // only rearrange items within the same container 173 ) { 174 $(itemElement).mouseenter(); 175 176 this.direction = intersection == 1 ? 'down' : 'up'; 177 178 if ( 179 this.options.tolerance == 'pointer' || 180 this._intersectsWithSides(item) 181 ) { 182 $(itemElement).mouseleave(); 183 this._rearrange(event, item); 184 } else { 185 break; 186 } 187 188 // Clear emtpy ul's/ol's 189 this._clearEmpty(itemElement); 190 191 this._trigger('change', event, this._uiHash()); 192 break; 193 } 194 } 195 196 var parentItem = 197 this.placeholder[0].parentNode.parentNode && 198 $(this.placeholder[0].parentNode.parentNode).closest( 199 '.ui-sortable' 200 ).length 201 ? $(this.placeholder[0].parentNode.parentNode) 202 : null, 203 level = this._getLevel(this.placeholder), 204 childLevels = this._getChildLevels(this.helper); 205 206 // To find the previous sibling in the list, keep backtracking until we hit a valid list item. 207 var previousItem = this.placeholder[0].previousSibling 208 ? $(this.placeholder[0].previousSibling) 209 : null; 210 if (previousItem != null) { 211 while ( 212 previousItem[0].nodeName.toLowerCase() != 'li' || 213 previousItem[0] == this.currentItem[0] || 214 previousItem[0] == this.helper[0] 215 ) { 216 if (previousItem[0].previousSibling) { 217 previousItem = $(previousItem[0].previousSibling); 218 } else { 219 previousItem = null; 220 break; 221 } 222 } 223 } 224 225 // To find the next sibling in the list, keep stepping forward until we hit a valid list item. 226 var nextItem = this.placeholder[0].nextSibling 227 ? $(this.placeholder[0].nextSibling) 228 : null; 229 if (nextItem != null) { 230 while ( 231 nextItem[0].nodeName.toLowerCase() != 'li' || 232 nextItem[0] == this.currentItem[0] || 233 nextItem[0] == this.helper[0] 234 ) { 235 if (nextItem[0].nextSibling) { 236 nextItem = $(nextItem[0].nextSibling); 237 } else { 238 nextItem = null; 239 break; 240 } 241 } 242 } 243 244 var newList = document.createElement(o.listType); 245 246 this.beyondMaxLevels = 0; 247 248 // If the item is moved to the left, send it to its parent's level unless there are siblings below it. 249 if ( 250 parentItem != null && 251 nextItem == null && 252 ((o.rtl && 253 this.positionAbs.left + this.helper.outerWidth() > 254 parentItem.offset().left + 255 parentItem.outerWidth()) || 256 (!o.rtl && 257 this.positionAbs.left < parentItem.offset().left)) 258 ) { 259 parentItem.after(this.placeholder[0]); 260 this._clearEmpty(parentItem[0]); 261 this._trigger('change', event, this._uiHash()); 262 } 263 // If the item is below a sibling and is moved to the right, make it a child of that sibling. 264 else if ( 265 previousItem != null && 266 ((o.rtl && 267 this.positionAbs.left + this.helper.outerWidth() < 268 previousItem.offset().left + 269 previousItem.outerWidth() - 270 o.tabSize) || 271 (!o.rtl && 272 this.positionAbs.left > 273 previousItem.offset().left + o.tabSize)) 274 ) { 275 this._isAllowed( 276 previousItem, 277 level,
278 level + childLevels + 1 279 ); 280 if (!previousItem.children(o.listType).length) { 281 previousItem[0].appendChild(newList); 282 } 283 // If this item is being moved from the top, add it to the top of the list. 284 if ( 285 previousTopOffset && 286 previousTopOffset <= previousItem.offset().top 287 ) { 288 previousItem 289 .children(o.listType) 290 .prepend(this.placeholder); 291 } 292 // Otherwise, add it to the bottom of the list. 293 else { 294 previousItem 295 .children(o.listType)[0] 296 .appendChild(this.placeholder[0]); 297 } 298 this._trigger('change', event, this._uiHash()); 299 } else { 300 this._isAllowed(parentItem, level, level + childLevels); 301 } 302 303 //Post events to containers 304 this._contactContainers(event); 305 306 //Interconnect with droppables 307 if ($.ui.ddmanager) $.ui.ddmanager.drag(this, event); 308 309 //Call callbacks 310 this._trigger('sort', event, this._uiHash()); 311 312 this.lastPositionAbs = this.positionAbs; 313 return false; 314 }, 315 316 _mouseStop: function (event, noPropagation) { 317 // If the item is in a position not allowed, send it back 318 if (this.beyondMaxLevels) { 319 this.placeholder.removeClass(this.options.errorClass); 320 321 if (this.domPosition.prev) { 322 $(this.domPosition.prev).after(this.placeholder); 323 } else { 324 $(this.domPosition.parent).prepend(this.placeholder); 325 } 326 327 this._trigger('revert', event, this._uiHash()); 328 } 329 330 // Clean last empty ul/ol 331 for (var i = this.items.length - 1; i >= 0; i--) { 332 var item = this.items[i].item[0]; 333 this._clearEmpty(item); 334 } 335 336 $.ui.sortable.prototype._mouseStop.apply(this, arguments); 337 }, 338 339 serialize: function (options) { 340 var o = $.extend({}, this.options, options), 341 items = this._getItemsAsjQuery(o && o.connected), 342 str = []; 343 344 $(items).each(function () { 345 var res = ( 346 $(o.item || this).attr(o.attribute || 'id') || '' 347 ).match(o.expression || /(.+)[-=_](.+)/), 348 pid = ( 349 $(o.item || this) 350 .parent(o.listType) 351 .parent(o.items) 352 .attr(o.attribute || 'id') || '' 353 ).match(o.expression || /(.+)[-=_](.+)/); 354 355 if (res) { 356 str.push( 357 (o.key || res[1]) + 358 '[' + 359 (o.key && o.expression ? res[1] : res[2]) + 360 ']' + 361 '=' + 362 (pid 363 ? o.key && o.expression 364 ? pid[1] 365 : pid[2] 366 : o.rootID) 367 ); 368 } 369 }); 370 371 if (!str.length && o.key) { 372 str.push(o.key + '='); 373 } 374 375 return str.join('&'); 376 }, 377 378 toHierarchy: function (options) { 379 var o = $.extend({}, this.options, options), 380 sDepth = o.startDepthCount || 0, 381 ret = []; 382 383 $(this.element) 384 .children(o.items) 385 .each(function () { 386 var level = _recursiveItems(this);
387 ret.push(level); 388 }); 389 390 return ret; 391 392 function _recursiveItems(item) { 393 var id = ($(item).attr(o.attribute || 'id') || '').match( 394 o.expression || /(.+)[-=_](.+)/ 395 ); 396 if (id) { 397 var currentItem = { id: id[2] }; 398 if ( 399 $(item).children(o.listType).children(o.items) 400 .length > 0 401 ) { 402 currentItem.children = []; 403 $(item) 404 .children(o.listType) 405 .children(o.items) 406 .each(function () { 407 var level = _recursiveItems(this); 408 currentItem.children.push(level); 409 }); 410 } 411 return currentItem; 412 } 413 } 414 }, 415 416 toArray: function (options) { 417 var o = $.extend({}, this.options, options), 418 sDepth = o.startDepthCount || 0, 419 ret = [], 420 left = 2; 421 422 ret.push({ 423 item_id: o.rootID, 424 parent_id: 'none', 425 depth: sDepth, 426 left: '1', 427 right: ($(o.items, this.element).length + 1) * 2, 428 }); 429 430 $(this.element) 431 .children(o.items) 432 .each(function () { 433 left = _recursiveArray(this, sDepth + 1, left); 434 }); 435 436 ret = ret.sort(function (a, b) { 437 return a.left - b.left; 438 }); 439 440 return ret; 441 442 function _recursiveArray(item, depth, left) { 443 var right = left + 1, 444 id, 445 pid; 446 447 if ( 448 $(item).children(o.listType).children(o.items).length > 449 0 450 ) { 451 depth++; 452 $(item) 453 .children(o.listType) 454 .children(o.items) 455 .each(function () { 456 right = _recursiveArray($(this), depth, right); 457 }); 458 depth--; 459 } 460 461 id = $(item) 462 .attr(o.attribute || 'id') 463 .match(o.expression || /(.+)[-=_](.+)/); 464 465 if (depth === sDepth + 1) { 466 pid = o.rootID; 467 } else { 468 var parentItem = $(item) 469 .parent(o.listType) 470 .parent(o.items) 471 .attr(o.attribute || 'id') 472 .match(o.expression || /(.+)[-=_](.+)/); 473 pid = parentItem[2]; 474 } 475 476 if (id) { 477 ret.push({ 478 item_id: id[2], 479 parent_id: pid, 480 depth: depth, 481 left: left, 482 right: right, 483 }); 484 } 485 486 left = right + 1; 487 return left; 488 } 489 }, 490 491 _clearEmpty: function (item) { 492 var emptyList = $(item).children(this.options.listType); 493 if ( 494 emptyList.length && 495 !emptyList.children().length && 496 !this.options.doNotClear 497 ) { 498 emptyList.remove(); 499 } 500 }, 501 502 _getLevel: function (item) { 503 var level = 1; 504 505 if (this.options.listType) { 506 var list = item.closest(this.options.listType); 507 while ( 508 list && 509 list.length > 0 && 510 !list.is('.ui-sortable') 511 ) { 512 level++; 513 list = list.parent().closest(this.options.listType); 514 } 515 } 516 517 return level; 518 }, 519 520 _getChildLevels: function (parent, depth) { 521 var self = this, 522 o = this.options, 523 result = 0; 524 depth = depth || 0; 525 526 $(parent) 527 .children(o.listType) 528 .children(o.items) 529 .each(function (index, child) { 530 result = Math.max( 531 self._getChildLevels(child, depth + 1), 532 result 533 ); 534 }); 535 536 return depth ? result + 1 : result; 537 }, 538 539 _isAllowed: function (parentItem, level, levels) { 540 var o = this.options, 541 isRoot = $(this.domPosition.parent).hasClass('ui-sortable') 542 ? true 543 : false, 544 maxLevels = this.placeholder 545 .closest('.ui-sortable') 546 .nestedSortable('option', 'maxLevels'); // this takes into account the maxLevels set to the recipient list 547 548 // Is the root protected? 549 // Are we trying to nest under a no-nest? 550 // Are we nesting too deep? 551 if ( 552 !o.isAllowed(this.currentItem, parentItem) || 553 (parentItem && parentItem.hasClass(o.disableNesting)) || 554 (o.protectRoot && 555 ((parentItem == null && !isRoot) || 556 (isRoot && level > 1))) 557 ) { 558 this.placeholder.addClass(o.errorClass); 559 if (maxLevels < levels && maxLevels != 0) { 560 this.beyondMaxLevels = levels - maxLevels; 561 } else { 562 this.beyondMaxLevels = 1; 563 } 564 } else { 565 if (maxLevels < levels && maxLevels != 0) { 566 this.placeholder.addClass(o.errorClass); 567 this.beyondMaxLevels = levels - maxLevels; 568 } else { 569 this.placeholder.removeClass(o.errorClass); 570 this.beyondMaxLevels = 0; 571 } 572 } 573 }, 574 }) 575 ); 576 577 $.mjs.nestedSortable.prototype.options = $.extend( 578 {}, 579 $.ui.sortable.prototype.options, 580 $.mjs.nestedSortable.prototype.options 581 ); 582})(jQuery);
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.