7758ecba0a0869d4e6eb7a1bbcb566bef883fbfa
[Packages/TYPO3.CMS.git] / typo3 / sysext / form / Resources / Public / JavaScript / Backend / Vendor / jquery.mjs.nestedSortable.js
1 /*
2 * jQuery UI Nested Sortable
3 * v 2.1a / 2016-02-04
4 * https://github.com/ilikenwf/nestedSortable
5 *
6 * Depends on:
7 * jquery.ui.sortable.js 1.10+
8 *
9 * Copyright (c) 2010-2016 Manuele J Sarfatti and contributors
10 * Licensed under the MIT License
11 * http://www.opensource.org/licenses/mit-license.php
12 */
13 (function(factory) {
14 "use strict";
15
16 if (typeof define === "function" && define.amd) {
17
18 // AMD. Register as an anonymous module.
19 define([
20 "jquery",
21 "jquery-ui/sortable"
22 ], factory);
23 } else {
24
25 // Browser globals
26 factory(window.jQuery);
27 }
28 }(function($) {
29 "use strict";
30
31 function isOverAxis(x, reference, size) {
32 return (x > reference) && (x < (reference + size));
33 }
34
35 $.widget("mjs.nestedSortable", $.extend({}, $.ui.sortable.prototype, {
36
37 options: {
38 disableParentChange: false,
39 doNotClear: false,
40 expandOnHover: 700,
41 isAllowed: function() {
42 return true;
43 },
44 isTree: false,
45 listType: "ol",
46 maxLevels: 0,
47 protectRoot: false,
48 rootID: null,
49 rtl: false,
50 startCollapsed: false,
51 tabSize: 20,
52
53 branchClass: "mjs-nestedSortable-branch",
54 collapsedClass: "mjs-nestedSortable-collapsed",
55 disableNestingClass: "mjs-nestedSortable-no-nesting",
56 errorClass: "mjs-nestedSortable-error",
57 expandedClass: "mjs-nestedSortable-expanded",
58 hoveringClass: "mjs-nestedSortable-hovering",
59 leafClass: "mjs-nestedSortable-leaf",
60 disabledClass: "mjs-nestedSortable-disabled"
61 },
62
63 _create: function() {
64 var self = this,
65 err;
66
67 this.element.data("ui-sortable", this.element.data("mjs-nestedSortable"));
68
69 // mjs - prevent browser from freezing if the HTML is not correct
70 if (!this.element.is(this.options.listType)) {
71 err = "nestedSortable: " +
72 "Please check that the listType option is set to your actual list type";
73
74 throw new Error(err);
75 }
76
77 // if we have a tree with expanding/collapsing functionality,
78 // force 'intersect' tolerance method
79 if (this.options.isTree && this.options.expandOnHover) {
80 this.options.tolerance = "intersect";
81 }
82
83 $.ui.sortable.prototype._create.apply(this, arguments);
84
85 // prepare the tree by applying the right classes
86 // (the CSS is responsible for actual hide/show functionality)
87 if (this.options.isTree) {
88 $(this.items).each(function() {
89 var $li = this.item,
90 hasCollapsedClass = $li.hasClass(self.options.collapsedClass),
91 hasExpandedClass = $li.hasClass(self.options.expandedClass);
92
93 if ($li.children(self.options.listType).length) {
94 $li.addClass(self.options.branchClass);
95 // expand/collapse class only if they have children
96
97 if (!hasCollapsedClass && !hasExpandedClass) {
98 if (self.options.startCollapsed) {
99 $li.addClass(self.options.collapsedClass);
100 } else {
101 $li.addClass(self.options.expandedClass);
102 }
103 }
104 } else {
105 $li.addClass(self.options.leafClass);
106 }
107 });
108 }
109 },
110
111 _destroy: function() {
112 this.element
113 .removeData("mjs-nestedSortable")
114 .removeData("ui-sortable");
115 return $.ui.sortable.prototype._destroy.apply(this, arguments);
116 },
117
118 _mouseDrag: function(event) {
119 var i,
120 item,
121 itemElement,
122 intersection,
123 self = this,
124 o = this.options,
125 scrolled = false,
126 $document = $(document),
127 previousTopOffset,
128 parentItem,
129 level,
130 childLevels,
131 itemAfter,
132 itemBefore,
133 newList,
134 method,
135 a,
136 previousItem,
137 nextItem,
138 helperIsNotSibling;
139
140 //Compute the helpers position
141 this.position = this._generatePosition(event);
142 this.positionAbs = this._convertPositionTo("absolute");
143
144 if (!this.lastPositionAbs) {
145 this.lastPositionAbs = this.positionAbs;
146 }
147
148 //Do scrolling
149 if (this.options.scroll) {
150 if (this.scrollParent[0] !== document && this.scrollParent[0].tagName !== "HTML") {
151
152 if (
153 (
154 this.overflowOffset.top +
155 this.scrollParent[0].offsetHeight
156 ) -
157 event.pageY <
158 o.scrollSensitivity
159 ) {
160 scrolled = this.scrollParent.scrollTop() + o.scrollSpeed;
161 this.scrollParent.scrollTop(scrolled);
162 } else if (
163 event.pageY -
164 this.overflowOffset.top <
165 o.scrollSensitivity
166 ) {
167 scrolled = this.scrollParent.scrollTop() - o.scrollSpeed;
168 this.scrollParent.scrollTop(scrolled);
169 }
170
171 if (
172 (
173 this.overflowOffset.left +
174 this.scrollParent[0].offsetWidth
175 ) -
176 event.pageX <
177 o.scrollSensitivity
178 ) {
179 scrolled = this.scrollParent.scrollLeft() + o.scrollSpeed;
180 this.scrollParent.scrollLeft(scrolled);
181 } else if (
182 event.pageX -
183 this.overflowOffset.left <
184 o.scrollSensitivity
185 ) {
186 scrolled = this.scrollParent.scrollLeft() - o.scrollSpeed;
187 this.scrollParent.scrollLeft(scrolled);
188 }
189
190 } else {
191
192 if (
193 event.pageY -
194 $document.scrollTop() <
195 o.scrollSensitivity
196 ) {
197 scrolled = $document.scrollTop() - o.scrollSpeed;
198 $document.scrollTop(scrolled);
199 } else if (
200 $(window).height() -
201 (
202 event.pageY -
203 $document.scrollTop()
204 ) <
205 o.scrollSensitivity
206 ) {
207 scrolled = $document.scrollTop() + o.scrollSpeed;
208 $document.scrollTop(scrolled);
209 }
210
211 if (
212 event.pageX -
213 $document.scrollLeft() <
214 o.scrollSensitivity
215 ) {
216 scrolled = $document.scrollLeft() - o.scrollSpeed;
217 $document.scrollLeft(scrolled);
218 } else if (
219 $(window).width() -
220 (
221 event.pageX -
222 $document.scrollLeft()
223 ) <
224 o.scrollSensitivity
225 ) {
226 scrolled = $document.scrollLeft() + o.scrollSpeed;
227 $document.scrollLeft(scrolled);
228 }
229
230 }
231
232 if (scrolled !== false && $.ui.ddmanager && !o.dropBehaviour) {
233 $.ui.ddmanager.prepareOffsets(this, event);
234 }
235 }
236
237 //Regenerate the absolute position used for position checks
238 this.positionAbs = this._convertPositionTo("absolute");
239
240 // mjs - find the top offset before rearrangement,
241 previousTopOffset = this.placeholder.offset().top;
242
243 //Set the helper position
244 if (!this.options.axis || this.options.axis !== "y") {
245 this.helper[0].style.left = this.position.left + "px";
246 }
247 if (!this.options.axis || this.options.axis !== "x") {
248 this.helper[0].style.top = (this.position.top) + "px";
249 }
250
251 // mjs - check and reset hovering state at each cycle
252 this.hovering = this.hovering ? this.hovering : null;
253 this.mouseentered = this.mouseentered ? this.mouseentered : false;
254
255 // mjs - let's start caching some variables
256 (function() {
257 var _parentItem = this.placeholder.parent().parent();
258 if (_parentItem && _parentItem.closest(".ui-sortable").length) {
259 parentItem = _parentItem;
260 }
261 }.call(this));
262
263 level = this._getLevel(this.placeholder);
264 childLevels = this._getChildLevels(this.helper);
265 newList = document.createElement(o.listType);
266
267 //Rearrange
268 for (i = this.items.length - 1; i >= 0; i--) {
269
270 //Cache variables and intersection, continue if no intersection
271 item = this.items[i];
272 itemElement = item.item[0];
273 intersection = this._intersectsWithPointer(item);
274 if (!intersection) {
275 continue;
276 }
277
278 // Only put the placeholder inside the current Container, skip all
279 // items form other containers. This works because when moving
280 // an item from one container to another the
281 // currentContainer is switched before the placeholder is moved.
282 //
283 // Without this moving items in "sub-sortables" can cause the placeholder to jitter
284 // beetween the outer and inner container.
285 if (item.instance !== this.currentContainer) {
286 continue;
287 }
288
289 // No action if intersected item is disabled
290 // and the element above or below in the direction we're going is also disabled
291 if (itemElement.className.indexOf(o.disabledClass) !== -1) {
292 // Note: intersection hardcoded direction values from
293 // jquery.ui.sortable.js:_intersectsWithPointer
294 if (intersection === 2) {
295 // Going down
296 itemAfter = this.items[i + 1];
297 if (itemAfter && itemAfter.item.hasClass(o.disabledClass)) {
298 continue;
299 }
300
301 } else if (intersection === 1) {
302 // Going up
303 itemBefore = this.items[i - 1];
304 if (itemBefore && itemBefore.item.hasClass(o.disabledClass)) {
305 continue;
306 }
307 }
308 }
309
310 method = intersection === 1 ? "next" : "prev";
311
312 // cannot intersect with itself
313 // no useless actions that have been done before
314 // no action if the item moved is the parent of the item checked
315 if (itemElement !== this.currentItem[0] &&
316 this.placeholder[method]()[0] !== itemElement &&
317 !$.contains(this.placeholder[0], itemElement) &&
318 (
319 this.options.type === "semi-dynamic" ?
320 !$.contains(this.element[0], itemElement) :
321 true
322 )
323 ) {
324
325 // mjs - we are intersecting an element:
326 // trigger the mouseenter event and store this state
327 if (!this.mouseentered) {
328 $(itemElement).mouseenter();
329 this.mouseentered = true;
330 }
331
332 // mjs - if the element has children and they are hidden,
333 // show them after a delay (CSS responsible)
334 if (o.isTree && $(itemElement).hasClass(o.collapsedClass) && o.expandOnHover) {
335 if (!this.hovering) {
336 $(itemElement).addClass(o.hoveringClass);
337 this.hovering = window.setTimeout(function() {
338 $(itemElement)
339 .removeClass(o.collapsedClass)
340 .addClass(o.expandedClass);
341
342 self.refreshPositions();
343 self._trigger("expand", event, self._uiHash());
344 }, o.expandOnHover);
345 }
346 }
347
348 this.direction = intersection === 1 ? "down" : "up";
349
350 // mjs - rearrange the elements and reset timeouts and hovering state
351 if (this.options.tolerance === "pointer" || this._intersectsWithSides(item)) {
352 $(itemElement).mouseleave();
353 this.mouseentered = false;
354 $(itemElement).removeClass(o.hoveringClass);
355 if (this.hovering) {
356 window.clearTimeout(this.hovering);
357 }
358 this.hovering = null;
359
360 // mjs - do not switch container if
361 // it's a root item and 'protectRoot' is true
362 // or if it's not a root item but we are trying to make it root
363 if (o.protectRoot &&
364 !(
365 this.currentItem[0].parentNode === this.element[0] &&
366 // it's a root item
367 itemElement.parentNode !== this.element[0]
368 // it's intersecting a non-root item
369 )
370 ) {
371 if (this.currentItem[0].parentNode !== this.element[0] &&
372 itemElement.parentNode === this.element[0]
373 ) {
374
375 if (!$(itemElement).children(o.listType).length) {
376 itemElement.appendChild(newList);
377 if (o.isTree) {
378 $(itemElement)
379 .removeClass(o.leafClass)
380 .addClass(o.branchClass + " " + o.expandedClass);
381 }
382 }
383
384 if (this.direction === "down") {
385 a = $(itemElement).prev().children(o.listType);
386 } else {
387 a = $(itemElement).children(o.listType);
388 }
389
390 if (a[0] !== undefined) {
391 this._rearrange(event, null, a);
392 }
393
394 } else {
395 this._rearrange(event, item);
396 }
397 } else if (!o.protectRoot) {
398 this._rearrange(event, item);
399 }
400 } else {
401 break;
402 }
403
404 // Clear emtpy ul's/ol's
405 this._clearEmpty(itemElement);
406
407 this._trigger("change", event, this._uiHash());
408 break;
409 }
410 }
411
412 // mjs - to find the previous sibling in the list,
413 // keep backtracking until we hit a valid list item.
414 (function() {
415 var _previousItem = this.placeholder.prev();
416 if (_previousItem.length) {
417 previousItem = _previousItem;
418 } else {
419 previousItem = null;
420 }
421 }.call(this));
422
423 if (previousItem != null) {
424 while (
425 previousItem[0].nodeName.toLowerCase() !== "li" ||
426 previousItem[0].className.indexOf(o.disabledClass) !== -1 ||
427 previousItem[0] === this.currentItem[0] ||
428 previousItem[0] === this.helper[0]
429 ) {
430 if (previousItem[0].previousSibling) {
431 previousItem = $(previousItem[0].previousSibling);
432 } else {
433 previousItem = null;
434 break;
435 }
436 }
437 }
438
439 // mjs - to find the next sibling in the list,
440 // keep stepping forward until we hit a valid list item.
441 (function() {
442 var _nextItem = this.placeholder.next();
443 if (_nextItem.length) {
444 nextItem = _nextItem;
445 } else {
446 nextItem = null;
447 }
448 }.call(this));
449
450 if (nextItem != null) {
451 while (
452 nextItem[0].nodeName.toLowerCase() !== "li" ||
453 nextItem[0].className.indexOf(o.disabledClass) !== -1 ||
454 nextItem[0] === this.currentItem[0] ||
455 nextItem[0] === this.helper[0]
456 ) {
457 if (nextItem[0].nextSibling) {
458 nextItem = $(nextItem[0].nextSibling);
459 } else {
460 nextItem = null;
461 break;
462 }
463 }
464 }
465
466 this.beyondMaxLevels = 0;
467
468 // mjs - if the item is moved to the left, send it one level up
469 // but only if it's at the bottom of the list
470 if (parentItem != null &&
471 nextItem == null &&
472 !(o.protectRoot && parentItem[0].parentNode == this.element[0]) &&
473 (
474 o.rtl &&
475 (
476 this.positionAbs.left +
477 this.helper.outerWidth() > parentItem.offset().left +
478 parentItem.outerWidth()
479 ) ||
480 !o.rtl && (this.positionAbs.left < parentItem.offset().left)
481 )
482 ) {
483
484 parentItem.after(this.placeholder[0]);
485 helperIsNotSibling = !parentItem
486 .children(o.listItem)
487 .children("li:visible:not(.ui-sortable-helper)")
488 .length;
489 if (o.isTree && helperIsNotSibling) {
490 parentItem
491 .removeClass(this.options.branchClass + " " + this.options.expandedClass)
492 .addClass(this.options.leafClass);
493 }
494 if (typeof parentItem !== 'undefined')
495 this._clearEmpty(parentItem[0]);
496 this._trigger("change", event, this._uiHash());
497 // mjs - if the item is below a sibling and is moved to the right,
498 // make it a child of that sibling
499 } else if (previousItem != null &&
500 !previousItem.hasClass(o.disableNestingClass) &&
501 (
502 previousItem.children(o.listType).length &&
503 previousItem.children(o.listType).is(":visible") ||
504 !previousItem.children(o.listType).length
505 ) &&
506 !(o.protectRoot && this.currentItem[0].parentNode === this.element[0]) &&
507 (
508 o.rtl &&
509 (
510 this.positionAbs.left +
511 this.helper.outerWidth() <
512 previousItem.offset().left +
513 previousItem.outerWidth() -
514 o.tabSize
515 ) ||
516 !o.rtl &&
517 (this.positionAbs.left > previousItem.offset().left + o.tabSize)
518 )
519 ) {
520
521 this._isAllowed(previousItem, level, level + childLevels + 1);
522
523 if (!previousItem.children(o.listType).length) {
524 previousItem[0].appendChild(newList);
525 if (o.isTree) {
526 previousItem
527 .removeClass(o.leafClass)
528 .addClass(o.branchClass + " " + o.expandedClass);
529 }
530 }
531
532 // mjs - if this item is being moved from the top, add it to the top of the list.
533 if (previousTopOffset && (previousTopOffset <= previousItem.offset().top)) {
534 previousItem.children(o.listType).prepend(this.placeholder);
535 } else {
536 // mjs - otherwise, add it to the bottom of the list.
537 previousItem.children(o.listType)[0].appendChild(this.placeholder[0]);
538 }
539 if (typeof parentItem !== 'undefined')
540 this._clearEmpty(parentItem[0]);
541 this._trigger("change", event, this._uiHash());
542 } else {
543 this._isAllowed(parentItem, level, level + childLevels);
544 }
545
546 //Post events to containers
547 this._contactContainers(event);
548
549 //Interconnect with droppables
550 if ($.ui.ddmanager) {
551 $.ui.ddmanager.drag(this, event);
552 }
553
554 //Call callbacks
555 this._trigger("sort", event, this._uiHash());
556
557 this.lastPositionAbs = this.positionAbs;
558 return false;
559
560 },
561
562 _mouseStop: function(event) {
563 // mjs - if the item is in a position not allowed, send it back
564 if (this.beyondMaxLevels) {
565
566 this.placeholder.removeClass(this.options.errorClass);
567
568 if (this.domPosition.prev) {
569 $(this.domPosition.prev).after(this.placeholder);
570 } else {
571 $(this.domPosition.parent).prepend(this.placeholder);
572 }
573
574 this._trigger("revert", event, this._uiHash());
575
576 }
577
578 // mjs - clear the hovering timeout, just to be sure
579 $("." + this.options.hoveringClass)
580 .mouseleave()
581 .removeClass(this.options.hoveringClass);
582
583 this.mouseentered = false;
584 if (this.hovering) {
585 window.clearTimeout(this.hovering);
586 }
587 this.hovering = null;
588
589 this._relocate_event = event;
590 this._pid_current = $(this.domPosition.parent).parent().attr("id");
591 this._sort_current = this.domPosition.prev ? $(this.domPosition.prev).next().index() : 0;
592 $.ui.sortable.prototype._mouseStop.apply(this, arguments); //asybnchronous execution, @see _clear for the relocate event.
593 },
594
595 // mjs - this function is slightly modified
596 // to make it easier to hover over a collapsed element and have it expand
597 _intersectsWithSides: function(item) {
598
599 var half = this.options.isTree ? .8 : .5,
600 isOverBottomHalf = isOverAxis(
601 this.positionAbs.top + this.offset.click.top,
602 item.top + (item.height * half),
603 item.height
604 ),
605 isOverTopHalf = isOverAxis(
606 this.positionAbs.top + this.offset.click.top,
607 item.top - (item.height * half),
608 item.height
609 ),
610 isOverRightHalf = isOverAxis(
611 this.positionAbs.left + this.offset.click.left,
612 item.left + (item.width / 2),
613 item.width
614 ),
615 verticalDirection = this._getDragVerticalDirection(),
616 horizontalDirection = this._getDragHorizontalDirection();
617
618 if (this.floating && horizontalDirection) {
619 return (
620 (horizontalDirection === "right" && isOverRightHalf) ||
621 (horizontalDirection === "left" && !isOverRightHalf)
622 );
623 } else {
624 return verticalDirection && (
625 (verticalDirection === "down" && isOverBottomHalf) ||
626 (verticalDirection === "up" && isOverTopHalf)
627 );
628 }
629
630 },
631
632 _contactContainers: function() {
633
634 if (this.options.protectRoot && this.currentItem[0].parentNode === this.element[0]) {
635 return;
636 }
637
638 $.ui.sortable.prototype._contactContainers.apply(this, arguments);
639
640 },
641
642 _clear: function() {
643 var i,
644 item;
645
646 $.ui.sortable.prototype._clear.apply(this, arguments);
647
648 //relocate event
649 if (!(this._pid_current === this._uiHash().item.parent().parent().attr("id") &&
650 this._sort_current === this._uiHash().item.index())) {
651 this._trigger("relocate", this._relocate_event, this._uiHash());
652 }
653
654 // mjs - clean last empty ul/ol
655 for (i = this.items.length - 1; i >= 0; i--) {
656 item = this.items[i].item[0];
657 this._clearEmpty(item);
658 }
659
660 },
661
662 serialize: function(options) {
663
664 var o = $.extend({}, this.options, options),
665 items = this._getItemsAsjQuery(o && o.connected),
666 str = [];
667
668 $(items).each(function() {
669 var res = ($(o.item || this).attr(o.attribute || "id") || "")
670 .match(o.expression || (/(.+)[-=_](.+)/)),
671 pid = ($(o.item || this).parent(o.listType)
672 .parent(o.items)
673 .attr(o.attribute || "id") || "")
674 .match(o.expression || (/(.+)[-=_](.+)/));
675
676 if (res) {
677 str.push(
678 (
679 (o.key || res[1]) +
680 "[" +
681 (o.key && o.expression ? res[1] : res[2]) + "]"
682 ) +
683 "=" +
684 (pid ? (o.key && o.expression ? pid[1] : pid[2]) : o.rootID));
685 }
686 });
687
688 if (!str.length && o.key) {
689 str.push(o.key + "=");
690 }
691
692 return str.join("&");
693
694 },
695
696 toHierarchy: function(options) {
697
698 var o = $.extend({}, this.options, options),
699 ret = [];
700
701 $(this.element).children(o.items).each(function() {
702 var level = _recursiveItems(this);
703 ret.push(level);
704 });
705
706 return ret;
707
708 function _recursiveItems(item) {
709 var id = ($(item).attr(o.attribute || "id") || "").match(o.expression || (/(.+)[-=_](.+)/)),
710 currentItem;
711
712 var data = $(item).data();
713 if (data.nestedSortableItem) {
714 delete data.nestedSortableItem; // Remove the nestedSortableItem object from the data
715 }
716
717 if (id) {
718 currentItem = {
719 "id": id[2]
720 };
721
722 currentItem = $.extend({}, currentItem, data); // Combine the two objects
723
724 if ($(item).children(o.listType).children(o.items).length > 0) {
725 currentItem.children = [];
726 $(item).children(o.listType).children(o.items).each(function() {
727 var level = _recursiveItems(this);
728 currentItem.children.push(level);
729 });
730 }
731 return currentItem;
732 }
733 }
734 },
735
736 toArray: function(options) {
737
738 var o = $.extend({}, this.options, options),
739 sDepth = o.startDepthCount || 0,
740 ret = [],
741 left = 1;
742
743 if (!o.excludeRoot) {
744 ret.push({
745 "item_id": o.rootID,
746 "parent_id": null,
747 "depth": sDepth,
748 "left": left,
749 "right": ($(o.items, this.element).length + 1) * 2
750 });
751 left++;
752 }
753
754 $(this.element).children(o.items).each(function() {
755 left = _recursiveArray(this, sDepth, left);
756 });
757
758 ret = ret.sort(function(a, b) {
759 return (a.left - b.left);
760 });
761
762 return ret;
763
764 function _recursiveArray(item, depth, _left) {
765
766 var right = _left + 1,
767 id,
768 pid,
769 parentItem;
770
771 if ($(item).children(o.listType).children(o.items).length > 0) {
772 depth++;
773 $(item).children(o.listType).children(o.items).each(function() {
774 right = _recursiveArray($(this), depth, right);
775 });
776 depth--;
777 }
778
779 id = ($(item).attr(o.attribute || "id") || "").match(o.expression || (/(.+)[-=_](.+)/));
780
781 if (depth === sDepth) {
782 pid = o.rootID;
783 } else {
784 parentItem = ($(item).parent(o.listType)
785 .parent(o.items)
786 .attr(o.attribute || "id"))
787 .match(o.expression || (/(.+)[-=_](.+)/));
788 pid = parentItem[2];
789 }
790
791 if (id) {
792 var data = $(item).children('div').data();
793 var itemObj = $.extend(data, {
794 "id": id[2],
795 "parent_id": pid,
796 "depth": depth,
797 "left": _left,
798 "right": right
799 });
800 ret.push(itemObj);
801 }
802
803 _left = right + 1;
804 return _left;
805 }
806
807 },
808
809 _clearEmpty: function(item) {
810 function replaceClass(elem, search, replace, swap) {
811 if (swap) {
812 search = [replace, replace = search][0];
813 }
814
815 $(elem).removeClass(search).addClass(replace);
816 }
817
818 var o = this.options,
819 childrenList = $(item).children(o.listType),
820 hasChildren = childrenList.has('li').length;
821
822 var doNotClear =
823 o.doNotClear ||
824 hasChildren ||
825 o.protectRoot && $(item)[0] === this.element[0];
826
827 if (o.isTree) {
828 replaceClass(item, o.branchClass, o.leafClass, doNotClear);
829 }
830
831 if (!doNotClear) {
832 childrenList.parent().removeClass(o.expandedClass);
833 childrenList.remove();
834 }
835 },
836
837 _getLevel: function(item) {
838
839 var level = 1,
840 list;
841
842 if (this.options.listType) {
843 list = item.closest(this.options.listType);
844 while (list && list.length > 0 && !list.is(".ui-sortable")) {
845 level++;
846 list = list.parent().closest(this.options.listType);
847 }
848 }
849
850 return level;
851 },
852
853 _getChildLevels: function(parent, depth) {
854 var self = this,
855 o = this.options,
856 result = 0;
857 depth = depth || 0;
858
859 $(parent).children(o.listType).children(o.items).each(function(index, child) {
860 result = Math.max(self._getChildLevels(child, depth + 1), result);
861 });
862
863 return depth ? result + 1 : result;
864 },
865
866 _isAllowed: function(parentItem, level, levels) {
867 var o = this.options,
868 // this takes into account the maxLevels set to the recipient list
869 maxLevels = this
870 .placeholder
871 .closest(".ui-sortable")
872 .nestedSortable("option", "maxLevels"),
873
874 // Check if the parent has changed to prevent it, when o.disableParentChange is true
875 oldParent = this.currentItem.parent().parent(),
876 disabledByParentchange = o.disableParentChange && (
877 //From somewhere to somewhere else, except the root
878 typeof parentItem !== 'undefined' && !oldParent.is(parentItem) ||
879 typeof parentItem === 'undefined' && oldParent.is("li") //From somewhere to the root
880 );
881 // mjs - is the root protected?
882 // mjs - are we nesting too deep?
883 if (
884 disabledByParentchange ||
885 !o.isAllowed(this.placeholder, parentItem, this.currentItem)
886 ) {
887 this.placeholder.addClass(o.errorClass);
888 if (maxLevels < levels && maxLevels !== 0) {
889 this.beyondMaxLevels = levels - maxLevels;
890 } else {
891 this.beyondMaxLevels = 1;
892 }
893 } else {
894 if (maxLevels < levels && maxLevels !== 0) {
895 this.placeholder.addClass(o.errorClass);
896 this.beyondMaxLevels = levels - maxLevels;
897 } else {
898 this.placeholder.removeClass(o.errorClass);
899 this.beyondMaxLevels = 0;
900 }
901 }
902 }
903
904 }));
905
906 $.mjs.nestedSortable.prototype.options = $.extend(
907 {},
908 $.ui.sortable.prototype.options,
909 $.mjs.nestedSortable.prototype.options
910 );
911 }));