comparison default/node_modules/nestable2/jquery.nestable.js @ 0:1d038bc9b3d2 default tip

Up:default
author Liny <dev@neowd.com>
date Sat, 31 May 2025 09:21:51 +0800
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:1d038bc9b3d2
1 /*!
2 * Nestable jQuery Plugin - Copyright (c) 2014 Ramon Smit - https://github.com/RamonSmit/Nestable
3 */
4
5 (function($, window, document, undefined) {
6 var hasTouch = 'ontouchstart' in document;
7
8 /**
9 * Detect CSS pointer-events property
10 * events are normally disabled on the dragging element to avoid conflicts
11 * https://github.com/ausi/Feature-detection-technique-for-pointer-events/blob/master/modernizr-pointerevents.js
12 */
13 var hasPointerEvents = (function() {
14 var el = document.createElement('div'),
15 docEl = document.documentElement;
16 if (!('pointerEvents' in el.style)) {
17 return false;
18 }
19 el.style.pointerEvents = 'auto';
20 el.style.pointerEvents = 'x';
21 docEl.appendChild(el);
22 var supports = window.getComputedStyle && window.getComputedStyle(el, '').pointerEvents === 'auto';
23 docEl.removeChild(el);
24 return !!supports;
25 })();
26
27 var defaults = {
28 contentCallback: function(item) {return item.content || '' ? item.content : item.id;},
29 listNodeName: 'ol',
30 itemNodeName: 'li',
31 handleNodeName: 'div',
32 contentNodeName: 'span',
33 rootClass: 'dd',
34 listClass: 'dd-list',
35 itemClass: 'dd-item',
36 dragClass: 'dd-dragel',
37 handleClass: 'dd-handle',
38 contentClass: 'dd-content',
39 collapsedClass: 'dd-collapsed',
40 placeClass: 'dd-placeholder',
41 noDragClass: 'dd-nodrag',
42 noChildrenClass: 'dd-nochildren',
43 emptyClass: 'dd-empty',
44 expandBtnHTML: '<button class="dd-expand" data-action="expand" type="button">Expand</button>',
45 collapseBtnHTML: '<button class="dd-collapse" data-action="collapse" type="button">Collapse</button>',
46 group: 0,
47 maxDepth: 5,
48 threshold: 20,
49 fixedDepth: false, //fixed item's depth
50 fixed: false,
51 includeContent: false,
52 scroll: false,
53 scrollSensitivity: 1,
54 scrollSpeed: 5,
55 scrollTriggers: {
56 top: 40,
57 left: 40,
58 right: -40,
59 bottom: -40
60 },
61 effect: {
62 animation: 'none',
63 time: 'slow'
64 },
65 callback: function(l, e, p) {},
66 onDragStart: function(l, e, p) {},
67 beforeDragStop: function(l, e, p) {},
68 listRenderer: function(children, options) {
69 var html = '<' + options.listNodeName + ' class="' + options.listClass + '">';
70 html += children;
71 html += '</' + options.listNodeName + '>';
72
73 return html;
74 },
75 itemRenderer: function(item_attrs, content, children, options, item) {
76 var item_attrs_string = $.map(item_attrs, function(value, key) {
77 return ' ' + key + '="' + value + '"';
78 }).join(' ');
79
80 var html = '<' + options.itemNodeName + item_attrs_string + '>';
81 html += '<' + options.handleNodeName + ' class="' + options.handleClass + '">';
82 html += '<' + options.contentNodeName + ' class="' + options.contentClass + '">';
83 html += content;
84 html += '</' + options.contentNodeName + '>';
85 html += '</' + options.handleNodeName + '>';
86 html += children;
87 html += '</' + options.itemNodeName + '>';
88
89 return html;
90 }
91 };
92
93 function Plugin(element, options) {
94 this.w = $(document);
95 this.el = $(element);
96 options = options || defaults;
97
98 if (options.rootClass !== undefined && options.rootClass !== 'dd') {
99 options.listClass = options.listClass ? options.listClass : options.rootClass + '-list';
100 options.itemClass = options.itemClass ? options.itemClass : options.rootClass + '-item';
101 options.dragClass = options.dragClass ? options.dragClass : options.rootClass + '-dragel';
102 options.handleClass = options.handleClass ? options.handleClass : options.rootClass + '-handle';
103 options.collapsedClass = options.collapsedClass ? options.collapsedClass : options.rootClass + '-collapsed';
104 options.placeClass = options.placeClass ? options.placeClass : options.rootClass + '-placeholder';
105 options.noDragClass = options.noDragClass ? options.noDragClass : options.rootClass + '-nodrag';
106 options.noChildrenClass = options.noChildrenClass ? options.noChildrenClass : options.rootClass + '-nochildren';
107 options.emptyClass = options.emptyClass ? options.emptyClass : options.rootClass + '-empty';
108 }
109
110 this.options = $.extend({}, defaults, options);
111
112 // build HTML from serialized JSON if passed
113 if (this.options.json !== undefined) {
114 this._build();
115 }
116
117 this.init();
118 }
119
120 Plugin.prototype = {
121
122 init: function() {
123 var list = this;
124
125 list.reset();
126 list.el.data('nestable-group', this.options.group);
127 list.placeEl = $('<div class="' + list.options.placeClass + '"/>');
128
129 var items = this.el.find(list.options.itemNodeName);
130 $.each(items, function(k, el) {
131 var item = $(el),
132 parent = item.parent();
133 list.setParent(item);
134 if (parent.hasClass(list.options.collapsedClass)) {
135 list.collapseItem(parent.parent());
136 }
137 });
138
139 // Append the .dd-empty div if the list don't have any items on init
140 if (!items.length) {
141 this.appendEmptyElement(this.el);
142 }
143
144 list.el.on('click', 'button', function(e) {
145 if (list.dragEl) {
146 return;
147 }
148 var target = $(e.currentTarget),
149 action = target.data('action'),
150 item = target.parents(list.options.itemNodeName).eq(0);
151 if (action === 'collapse') {
152 list.collapseItem(item);
153 }
154 if (action === 'expand') {
155 list.expandItem(item);
156 }
157 });
158
159 var onStartEvent = function(e) {
160 var handle = $(e.target);
161 if (!handle.hasClass(list.options.handleClass)) {
162 if (handle.closest('.' + list.options.noDragClass).length) {
163 return;
164 }
165 handle = handle.closest('.' + list.options.handleClass);
166 }
167 if (!handle.length || list.dragEl) {
168 return;
169 }
170
171 list.isTouch = /^touch/.test(e.type);
172 if (list.isTouch && e.touches.length !== 1) {
173 return;
174 }
175
176 e.preventDefault();
177 list.dragStart(e.touches ? e.touches[0] : e);
178 };
179
180 var onMoveEvent = function(e) {
181 if (list.dragEl) {
182 e.preventDefault();
183 list.dragMove(e.touches ? e.touches[0] : e);
184 }
185 };
186
187 var onEndEvent = function(e) {
188 if (list.dragEl) {
189 e.preventDefault();
190 list.dragStop(e.touches ? e.changedTouches[0] : e);
191 }
192 };
193
194 if (hasTouch) {
195 list.el[0].addEventListener('touchstart', onStartEvent, false);
196 window.addEventListener('touchmove', onMoveEvent, false);
197 window.addEventListener('touchend', onEndEvent, false);
198 window.addEventListener('touchcancel', onEndEvent, false);
199 }
200
201 list.el.on('mousedown', onStartEvent);
202 list.w.on('mousemove', onMoveEvent);
203 list.w.on('mouseup', onEndEvent);
204
205 var destroyNestable = function()
206 {
207 if (hasTouch) {
208 list.el[0].removeEventListener('touchstart', onStartEvent, false);
209 window.removeEventListener('touchmove', onMoveEvent, false);
210 window.removeEventListener('touchend', onEndEvent, false);
211 window.removeEventListener('touchcancel', onEndEvent, false);
212 }
213
214 list.el.off('mousedown', onStartEvent);
215 list.w.off('mousemove', onMoveEvent);
216 list.w.off('mouseup', onEndEvent);
217
218 list.el.off('click');
219 list.el.unbind('destroy-nestable');
220
221 list.el.data("nestable", null);
222 };
223
224 list.el.bind('destroy-nestable', destroyNestable);
225
226 },
227
228 destroy: function ()
229 {
230 this.el.trigger('destroy-nestable');
231 },
232
233 add: function (item)
234 {
235 var listClassSelector = '.' + this.options.listClass;
236 var tree = $(this.el).children(listClassSelector);
237
238 if (item.parent_id !== undefined) {
239 tree = tree.find('[data-id="' + item.parent_id + '"]');
240 delete item.parent_id;
241
242 if (tree.children(listClassSelector).length === 0) {
243 tree = tree.append(this.options.listRenderer('', this.options));
244 }
245
246 tree = tree.find(listClassSelector + ':first');
247 this.setParent(tree.parent());
248 }
249
250 tree.append(this._buildItem(item, this.options));
251 },
252
253 replace: function (item)
254 {
255 var html = this._buildItem(item, this.options);
256
257 this._getItemById(item.id)
258 .replaceWith(html);
259 },
260
261 //removes item and additional elements from list
262 removeItem: function (item){
263 var opts = this.options,
264 el = this.el;
265
266 // remove item
267 item = item || this;
268 item.remove();
269
270 // remove empty children lists
271 var emptyListsSelector = '.' + opts.listClass
272 + ' .' + opts.listClass + ':not(:has(*))';
273 $(el).find(emptyListsSelector).remove();
274
275 // remove buttons if parents do not have children
276 var buttonsSelector = '[data-action="expand"], [data-action="collapse"]';
277 $(el).find(buttonsSelector).each(function() {
278 var siblings = $(this).siblings('.' + opts.listClass);
279 if (siblings.length === 0) {
280 $(this).remove();
281 }
282 });
283 },
284
285 //removes item by itemId and run callback at the end
286 remove: function (itemId, callback)
287 {
288 var opts = this.options;
289 var list = this;
290 var item = this._getItemById(itemId);
291
292 //animation style
293 var animation = opts.effect.animation || 'fade';
294
295 //animation time
296 var time = opts.effect.time || 'slow';
297
298 //add fadeOut effect when removing
299 if (animation === 'fade'){
300 item.fadeOut(time, function(){
301 list.removeItem(item);
302 });
303 }
304 else {
305 this.removeItem(item);
306 }
307
308 if (callback) callback();
309 },
310
311 //removes all items from the list and run callback at the end
312 removeAll: function(callback){
313
314 var list = this,
315 opts = this.options,
316 node = list.el.find(opts.listNodeName).first(),
317 items = node.children(opts.itemNodeName);
318
319 //animation style
320 var animation = opts.effect.animation || 'fade';
321
322 //animation time
323 var time = opts.effect.time || 'slow';
324
325 function remove(){
326 //Removes each item and its children.
327 items.each(function() {
328 list.removeItem($(this));
329 });
330 //Now we can again show our node element
331 node.show();
332 if (callback) callback();
333 }
334
335 //add fadeOut effect when removing
336 if (animation === 'fade'){
337 node.fadeOut(time, remove);
338 }
339 else {
340 remove();
341 }
342 },
343
344 _getItemById: function(itemId) {
345 return $(this.el).children('.' + this.options.listClass)
346 .find('[data-id="' + itemId + '"]');
347 },
348
349 _build: function() {
350 var json = this.options.json;
351
352 if (typeof json === 'string') {
353 json = JSON.parse(json);
354 }
355
356 $(this.el).html(this._buildList(json, this.options));
357 },
358
359 _buildList: function(items, options) {
360 if (!items) {
361 return '';
362 }
363
364 var children = '';
365 var that = this;
366
367 $.each(items, function(index, sub) {
368 children += that._buildItem(sub, options);
369 });
370
371 return options.listRenderer(children, options);
372 },
373
374 _buildItem: function(item, options) {
375 function escapeHtml(text) {
376 var map = {
377 '&': '&amp;',
378 '<': '&lt;',
379 '>': '&gt;',
380 '"': '&quot;',
381 "'": '&#039;'
382 };
383
384 return text + "".replace(/[&<>"']/g, function(m) { return map[m]; });
385 }
386
387 function filterClasses(classes) {
388 var new_classes = {};
389
390 for (var k in classes) {
391 // Remove duplicates
392 new_classes[classes[k]] = classes[k];
393 }
394
395 return new_classes;
396 }
397
398 function createClassesString(item, options) {
399 var classes = item.classes || {};
400
401 if (typeof classes === 'string') {
402 classes = [classes];
403 }
404
405 var item_classes = filterClasses(classes);
406 item_classes[options.itemClass] = options.itemClass;
407
408 // create class string
409 return $.map(item_classes, function(val) {
410 return val;
411 }).join(' ');
412 }
413
414 function createDataAttrs(attr) {
415 attr = $.extend({}, attr);
416
417 delete attr.children;
418 delete attr.classes;
419 delete attr.content;
420
421 var data_attrs = {};
422
423 $.each(attr, function(key, value) {
424 if (typeof value === 'object') {
425 value = JSON.stringify(value);
426 }
427
428 data_attrs["data-" + key] = escapeHtml(value);
429 });
430
431 return data_attrs;
432 }
433
434 var item_attrs = createDataAttrs(item);
435 item_attrs["class"] = createClassesString(item, options);
436
437 var content = options.contentCallback(item);
438 var children = this._buildList(item.children, options);
439 var html = $(options.itemRenderer(item_attrs, content, children, options, item));
440
441 this.setParent(html);
442
443 return html[0].outerHTML;
444 },
445
446 serialize: function() {
447 var data, list = this, step = function(level) {
448 var array = [],
449 items = level.children(list.options.itemNodeName);
450 items.each(function() {
451 var li = $(this),
452 item = $.extend({}, li.data()),
453 sub = li.children(list.options.listNodeName);
454
455 if (list.options.includeContent) {
456 var content = li.find('.' + list.options.contentClass).html();
457
458 if (content) {
459 item.content = content;
460 }
461 }
462
463 if (sub.length) {
464 item.children = step(sub);
465 }
466 array.push(item);
467 });
468 return array;
469 };
470 data = step(list.el.find(list.options.listNodeName).first());
471 return data;
472 },
473
474 asNestedSet: function() {
475 var list = this, o = list.options, depth = -1, ret = [], lft = 1;
476 var items = list.el.find(o.listNodeName).first().children(o.itemNodeName);
477
478 items.each(function () {
479 lft = traverse(this, depth + 1, lft);
480 });
481
482 ret = ret.sort(function(a,b){ return (a.lft - b.lft); });
483 return ret;
484
485 function traverse(item, depth, lft) {
486 var rgt = lft + 1, id, pid;
487
488 if ($(item).children(o.listNodeName).children(o.itemNodeName).length > 0 ) {
489 depth++;
490 $(item).children(o.listNodeName).children(o.itemNodeName).each(function () {
491 rgt = traverse($(this), depth, rgt);
492 });
493 depth--;
494 }
495
496 id = $(item).attr('data-id');
497 if (isInt(id)) {
498 id = parseInt(id);
499 }
500
501 pid = $(item).parent(o.listNodeName).parent(o.itemNodeName).attr('data-id') || '';
502 if (isInt(pid)) {
503 id = parseInt(pid);
504 }
505
506 if (id) {
507 ret.push({"id": id, "parent_id": pid, "depth": depth, "lft": lft, "rgt": rgt});
508 }
509
510 lft = rgt + 1;
511 return lft;
512 }
513
514 function isInt(value) {
515 return $.isNumeric(value) && Math.floor(value) == value;
516 }
517 },
518
519 returnOptions: function() {
520 return this.options;
521 },
522
523 serialise: function() {
524 return this.serialize();
525 },
526
527 toHierarchy: function(options) {
528
529 var o = $.extend({}, this.options, options),
530 ret = [];
531
532 $(this.element).children(o.items).each(function() {
533 var level = _recursiveItems(this);
534 ret.push(level);
535 });
536
537 return ret;
538
539 function _recursiveItems(item) {
540 var id = ($(item).attr(o.attribute || 'id') || '').match(o.expression || (/(.+)[-=_](.+)/));
541 if (id) {
542 var currentItem = {
543 "id": id[2]
544 };
545 if ($(item).children(o.listType).children(o.items).length > 0) {
546 currentItem.children = [];
547 $(item).children(o.listType).children(o.items).each(function() {
548 var level = _recursiveItems(this);
549 currentItem.children.push(level);
550 });
551 }
552 return currentItem;
553 }
554 }
555 },
556
557 toArray: function() {
558
559 var o = $.extend({}, this.options, this),
560 sDepth = o.startDepthCount || 0,
561 ret = [],
562 left = 2,
563 list = this,
564 element = list.el.find(list.options.listNodeName).first();
565
566 var items = element.children(list.options.itemNodeName);
567 items.each(function() {
568 left = _recursiveArray($(this), sDepth + 1, left);
569 });
570
571 ret = ret.sort(function(a, b) {
572 return (a.left - b.left);
573 });
574
575 return ret;
576
577 function _recursiveArray(item, depth, left) {
578
579 var right = left + 1,
580 id,
581 pid;
582
583 if (item.children(o.options.listNodeName).children(o.options.itemNodeName).length > 0) {
584 depth++;
585 item.children(o.options.listNodeName).children(o.options.itemNodeName).each(function() {
586 right = _recursiveArray($(this), depth, right);
587 });
588 depth--;
589 }
590
591 id = item.data().id;
592
593
594 if (depth === sDepth + 1) {
595 pid = o.rootID;
596 } else {
597
598 var parentItem = (item.parent(o.options.listNodeName)
599 .parent(o.options.itemNodeName)
600 .data());
601 pid = parentItem.id;
602
603 }
604
605 if (id) {
606 ret.push({
607 "id": id,
608 "parent_id": pid,
609 "depth": depth,
610 "left": left,
611 "right": right
612 });
613 }
614
615 left = right + 1;
616 return left;
617 }
618
619 },
620
621 reset: function() {
622 this.mouse = {
623 offsetX: 0,
624 offsetY: 0,
625 startX: 0,
626 startY: 0,
627 lastX: 0,
628 lastY: 0,
629 nowX: 0,
630 nowY: 0,
631 distX: 0,
632 distY: 0,
633 dirAx: 0,
634 dirX: 0,
635 dirY: 0,
636 lastDirX: 0,
637 lastDirY: 0,
638 distAxX: 0,
639 distAxY: 0
640 };
641 this.isTouch = false;
642 this.moving = false;
643 this.dragEl = null;
644 this.dragRootEl = null;
645 this.dragDepth = 0;
646 this.hasNewRoot = false;
647 this.pointEl = null;
648 },
649
650 expandItem: function(li) {
651 li.removeClass(this.options.collapsedClass);
652 },
653
654 collapseItem: function(li) {
655 var lists = li.children(this.options.listNodeName);
656 if (lists.length) {
657 li.addClass(this.options.collapsedClass);
658 }
659 },
660
661 expandAll: function() {
662 var list = this;
663 list.el.find(list.options.itemNodeName).each(function() {
664 list.expandItem($(this));
665 });
666 },
667
668 collapseAll: function() {
669 var list = this;
670 list.el.find(list.options.itemNodeName).each(function() {
671 list.collapseItem($(this));
672 });
673 },
674
675 setParent: function(li) {
676 //Check if li is an element of itemNodeName type and has children
677 if (li.is(this.options.itemNodeName) && li.children(this.options.listNodeName).length) {
678 // make sure NOT showing two or more sets data-action buttons
679 li.children('[data-action]').remove();
680 li.prepend($(this.options.expandBtnHTML));
681 li.prepend($(this.options.collapseBtnHTML));
682 }
683 },
684
685 unsetParent: function(li) {
686 li.removeClass(this.options.collapsedClass);
687 li.children('[data-action]').remove();
688 li.children(this.options.listNodeName).remove();
689 },
690
691 dragStart: function(e) {
692 var mouse = this.mouse,
693 target = $(e.target),
694 dragItem = target.closest(this.options.itemNodeName),
695 position = {
696 top : e.pageY,
697 left : e.pageX
698 };
699
700 var continueExecution = this.options.onDragStart.call(this, this.el, dragItem, position);
701
702 if (typeof continueExecution !== 'undefined' && continueExecution === false) {
703 return;
704 }
705
706 this.placeEl.css('height', dragItem.height());
707
708 mouse.offsetX = e.pageX - dragItem.offset().left;
709 mouse.offsetY = e.pageY - dragItem.offset().top;
710 mouse.startX = mouse.lastX = e.pageX;
711 mouse.startY = mouse.lastY = e.pageY;
712
713 this.dragRootEl = this.el;
714 this.dragEl = $(document.createElement(this.options.listNodeName)).addClass(this.options.listClass + ' ' + this.options.dragClass);
715 this.dragEl.css('width', dragItem.outerWidth());
716
717 this.setIndexOfItem(dragItem);
718
719 // fix for zepto.js
720 //dragItem.after(this.placeEl).detach().appendTo(this.dragEl);
721 dragItem.after(this.placeEl);
722 dragItem[0].parentNode.removeChild(dragItem[0]);
723 dragItem.appendTo(this.dragEl);
724
725 $(document.body).append(this.dragEl);
726 this.dragEl.css({
727 'left': e.pageX - mouse.offsetX,
728 'top': e.pageY - mouse.offsetY
729 });
730 // total depth of dragging item
731 var i, depth,
732 items = this.dragEl.find(this.options.itemNodeName);
733 for (i = 0; i < items.length; i++) {
734 depth = $(items[i]).parents(this.options.listNodeName).length;
735 if (depth > this.dragDepth) {
736 this.dragDepth = depth;
737 }
738 }
739 },
740
741 //Create sublevel.
742 // element : element which become parent
743 // item : something to place into new sublevel
744 createSubLevel: function(element, item) {
745 var list = $('<' + this.options.listNodeName + '/>').addClass(this.options.listClass);
746 if (item) list.append(item);
747 element.append(list);
748 this.setParent(element);
749 return list;
750 },
751
752 setIndexOfItem: function(item, index) {
753 index = index || [];
754
755 index.unshift(item.index());
756
757 if ($(item[0].parentNode)[0] !== this.dragRootEl[0]) {
758 this.setIndexOfItem($(item[0].parentNode), index);
759 }
760 else {
761 this.dragEl.data('indexOfItem', index);
762 }
763 },
764
765 restoreItemAtIndex: function(dragElement, indexArray) {
766 var currentEl = this.el,
767 lastIndex = indexArray.length - 1;
768
769 //Put drag element at current element position.
770 function placeElement(currentEl, dragElement) {
771 if (indexArray[lastIndex] === 0) {
772 $(currentEl).prepend(dragElement.clone(true)); //using true saves added to element events.
773 }
774 else {
775 $(currentEl.children[indexArray[lastIndex] - 1]).after(dragElement.clone(true)); //using true saves added to element events.
776 }
777 }
778 //Diggin through indexArray to get home for dragElement.
779 for (var i = 0; i < indexArray.length; i++) {
780 if (lastIndex === parseInt(i)) {
781 placeElement(currentEl, dragElement);
782 return;
783 }
784 //element can have no indexes, so we have to use conditional here to avoid errors.
785 //if element doesn't exist we defenetly need to add new list.
786 var element = (currentEl[0]) ? currentEl[0] : currentEl;
787 var nextEl = element.children[indexArray[i]];
788 currentEl = (!nextEl) ? this.createSubLevel($(element)) : nextEl;
789 }
790 },
791
792 dragStop: function(e) {
793 // fix for zepto.js
794 //this.placeEl.replaceWith(this.dragEl.children(this.options.itemNodeName + ':first').detach());
795 var position = {
796 top : e.pageY,
797 left : e.pageX
798 };
799 //Get indexArray of item at drag start.
800 var srcIndex = this.dragEl.data('indexOfItem');
801
802 var el = this.dragEl.children(this.options.itemNodeName).first();
803
804 el[0].parentNode.removeChild(el[0]);
805
806 this.dragEl.remove(); //Remove dragEl, cause it can affect on indexing in html collection.
807
808 //Before drag stop callback
809 var continueExecution = this.options.beforeDragStop.call(this, this.el, el, this.placeEl.parent());
810 if (typeof continueExecution !== 'undefined' && continueExecution === false) {
811 var parent = this.placeEl.parent();
812 this.placeEl.remove();
813 if (!parent.children().length) {
814 this.unsetParent(parent.parent());
815 }
816 this.restoreItemAtIndex(el, srcIndex);
817 this.reset();
818 return;
819 }
820
821 this.placeEl.replaceWith(el);
822
823 if (this.hasNewRoot) {
824 if (this.options.fixed === true) {
825 this.restoreItemAtIndex(el, srcIndex);
826 }
827 else {
828 this.el.trigger('lostItem');
829 }
830 this.dragRootEl.trigger('gainedItem');
831 }
832 else {
833 this.dragRootEl.trigger('change');
834 }
835
836 this.options.callback.call(this, this.dragRootEl, el, position);
837
838 this.reset();
839 },
840
841 dragMove: function(e) {
842 var list, parent, prev, next, depth,
843 opt = this.options,
844 mouse = this.mouse;
845
846 this.dragEl.css({
847 'left': e.pageX - mouse.offsetX,
848 'top': e.pageY - mouse.offsetY
849 });
850
851 // mouse position last events
852 mouse.lastX = mouse.nowX;
853 mouse.lastY = mouse.nowY;
854 // mouse position this events
855 mouse.nowX = e.pageX;
856 mouse.nowY = e.pageY;
857 // distance mouse moved between events
858 mouse.distX = mouse.nowX - mouse.lastX;
859 mouse.distY = mouse.nowY - mouse.lastY;
860 // direction mouse was moving
861 mouse.lastDirX = mouse.dirX;
862 mouse.lastDirY = mouse.dirY;
863 // direction mouse is now moving (on both axis)
864 mouse.dirX = mouse.distX === 0 ? 0 : mouse.distX > 0 ? 1 : -1;
865 mouse.dirY = mouse.distY === 0 ? 0 : mouse.distY > 0 ? 1 : -1;
866 // axis mouse is now moving on
867 var newAx = Math.abs(mouse.distX) > Math.abs(mouse.distY) ? 1 : 0;
868
869 // do nothing on first move
870 if (!mouse.moving) {
871 mouse.dirAx = newAx;
872 mouse.moving = true;
873 return;
874 }
875
876 // do scrolling if enable
877 if (opt.scroll) {
878 if (typeof window.jQuery.fn.scrollParent !== 'undefined') {
879 var scrolled = false;
880 var scrollParent = this.el.scrollParent()[0];
881 if (scrollParent !== document && scrollParent.tagName !== 'HTML') {
882 if ((opt.scrollTriggers.bottom + scrollParent.offsetHeight) - e.pageY < opt.scrollSensitivity)
883 scrollParent.scrollTop = scrolled = scrollParent.scrollTop + opt.scrollSpeed;
884 else if (e.pageY - opt.scrollTriggers.top < opt.scrollSensitivity)
885 scrollParent.scrollTop = scrolled = scrollParent.scrollTop - opt.scrollSpeed;
886
887 if ((opt.scrollTriggers.right + scrollParent.offsetWidth) - e.pageX < opt.scrollSensitivity)
888 scrollParent.scrollLeft = scrolled = scrollParent.scrollLeft + opt.scrollSpeed;
889 else if (e.pageX - opt.scrollTriggers.left < opt.scrollSensitivity)
890 scrollParent.scrollLeft = scrolled = scrollParent.scrollLeft - opt.scrollSpeed;
891 } else {
892 if (e.pageY - $(document).scrollTop() < opt.scrollSensitivity)
893 scrolled = $(document).scrollTop($(document).scrollTop() - opt.scrollSpeed);
894 else if ($(window).height() - (e.pageY - $(document).scrollTop()) < opt.scrollSensitivity)
895 scrolled = $(document).scrollTop($(document).scrollTop() + opt.scrollSpeed);
896
897 if (e.pageX - $(document).scrollLeft() < opt.scrollSensitivity)
898 scrolled = $(document).scrollLeft($(document).scrollLeft() - opt.scrollSpeed);
899 else if ($(window).width() - (e.pageX - $(document).scrollLeft()) < opt.scrollSensitivity)
900 scrolled = $(document).scrollLeft($(document).scrollLeft() + opt.scrollSpeed);
901 }
902 } else {
903 console.warn('To use scrolling you need to have scrollParent() function, check documentation for more information');
904 }
905 }
906
907 if (this.scrollTimer) {
908 clearTimeout(this.scrollTimer);
909 }
910
911 if (opt.scroll && scrolled) {
912 this.scrollTimer = setTimeout(function() {
913 $(window).trigger(e);
914 }, 10);
915 }
916
917 // calc distance moved on this axis (and direction)
918 if (mouse.dirAx !== newAx) {
919 mouse.distAxX = 0;
920 mouse.distAxY = 0;
921 }
922 else {
923 mouse.distAxX += Math.abs(mouse.distX);
924 if (mouse.dirX !== 0 && mouse.dirX !== mouse.lastDirX) {
925 mouse.distAxX = 0;
926 }
927 mouse.distAxY += Math.abs(mouse.distY);
928 if (mouse.dirY !== 0 && mouse.dirY !== mouse.lastDirY) {
929 mouse.distAxY = 0;
930 }
931 }
932 mouse.dirAx = newAx;
933
934 /**
935 * move horizontal
936 */
937 if (mouse.dirAx && mouse.distAxX >= opt.threshold) {
938 // reset move distance on x-axis for new phase
939 mouse.distAxX = 0;
940 prev = this.placeEl.prev(opt.itemNodeName);
941 // increase horizontal level if previous sibling exists, is not collapsed, and can have children
942 if (mouse.distX > 0 && prev.length && !prev.hasClass(opt.collapsedClass) && !prev.hasClass(opt.noChildrenClass)) {
943 // cannot increase level when item above is collapsed
944 list = prev.find(opt.listNodeName).last();
945 // check if depth limit has reached
946 depth = this.placeEl.parents(opt.listNodeName).length;
947 if (depth + this.dragDepth <= opt.maxDepth) {
948 // create new sub-level if one doesn't exist
949 if (!list.length) {
950 this.createSubLevel(prev, this.placeEl);
951 }
952 else {
953 // else append to next level up
954 list = prev.children(opt.listNodeName).last();
955 list.append(this.placeEl);
956 }
957 }
958 }
959 // decrease horizontal level
960 if (mouse.distX < 0) {
961 // we can't decrease a level if an item preceeds the current one
962 next = this.placeEl.next(opt.itemNodeName);
963 if (!next.length) {
964 parent = this.placeEl.parent();
965 this.placeEl.closest(opt.itemNodeName).after(this.placeEl);
966 if (!parent.children().length) {
967 this.unsetParent(parent.parent());
968 }
969 }
970 }
971 }
972
973 var isEmpty = false;
974
975 // find list item under cursor
976 if (!hasPointerEvents) {
977 this.dragEl[0].style.visibility = 'hidden';
978 }
979 this.pointEl = $(document.elementFromPoint(e.pageX - document.body.scrollLeft, e.pageY - (window.pageYOffset || document.documentElement.scrollTop)));
980 if (!hasPointerEvents) {
981 this.dragEl[0].style.visibility = 'visible';
982 }
983 if (this.pointEl.hasClass(opt.handleClass)) {
984 this.pointEl = this.pointEl.closest(opt.itemNodeName);
985 }
986 if (this.pointEl.hasClass(opt.emptyClass)) {
987 isEmpty = true;
988 }
989 else if (!this.pointEl.length || !this.pointEl.hasClass(opt.itemClass)) {
990 return;
991 }
992
993 // find parent list of item under cursor
994 var pointElRoot = this.pointEl.closest('.' + opt.rootClass),
995 isNewRoot = this.dragRootEl.data('nestable-id') !== pointElRoot.data('nestable-id');
996
997 /**
998 * move vertical
999 */
1000 if (!mouse.dirAx || isNewRoot || isEmpty) {
1001 // check if groups match if dragging over new root
1002 if (isNewRoot && opt.group !== pointElRoot.data('nestable-group')) {
1003 return;
1004 }
1005
1006 // fixed item's depth, use for some list has specific type, eg:'Volume, Section, Chapter ...'
1007 if (this.options.fixedDepth && this.dragDepth + 1 !== this.pointEl.parents(opt.listNodeName).length) {
1008 return;
1009 }
1010
1011 // check depth limit
1012 depth = this.dragDepth - 1 + this.pointEl.parents(opt.listNodeName).length;
1013 if (depth > opt.maxDepth) {
1014 return;
1015 }
1016 var before = e.pageY < (this.pointEl.offset().top + this.pointEl.height() / 2);
1017 parent = this.placeEl.parent();
1018 // if empty create new list to replace empty placeholder
1019 if (isEmpty) {
1020 list = $(document.createElement(opt.listNodeName)).addClass(opt.listClass);
1021 list.append(this.placeEl);
1022 this.pointEl.replaceWith(list);
1023 }
1024 else if (before) {
1025 this.pointEl.before(this.placeEl);
1026 }
1027 else {
1028 this.pointEl.after(this.placeEl);
1029 }
1030 if (!parent.children().length) {
1031 this.unsetParent(parent.parent());
1032 }
1033 if (!this.dragRootEl.find(opt.itemNodeName).length) {
1034 this.appendEmptyElement(this.dragRootEl);
1035 }
1036 // parent root list has changed
1037 this.dragRootEl = pointElRoot;
1038 if (isNewRoot) {
1039 this.hasNewRoot = this.el[0] !== this.dragRootEl[0];
1040 }
1041 }
1042 },
1043
1044 // Append the .dd-empty div to the list so it can be populated and styled
1045 appendEmptyElement: function(element) {
1046 element.append('<div class="' + this.options.emptyClass + '"/>');
1047 }
1048 };
1049
1050 $.fn.nestable = function(params) {
1051 var lists = this,
1052 retval = this,
1053 args = arguments;
1054
1055 if (!('Nestable' in window)) {
1056 window.Nestable = {};
1057 Nestable.counter = 0;
1058 }
1059
1060 lists.each(function() {
1061 var plugin = $(this).data("nestable");
1062
1063 if (!plugin) {
1064 Nestable.counter++;
1065 $(this).data("nestable", new Plugin(this, params));
1066 $(this).data("nestable-id", Nestable.counter);
1067 }
1068 else {
1069 if (typeof params === 'string' && typeof plugin[params] === 'function') {
1070 if (args.length > 1){
1071 var pluginArgs = [];
1072 for (var i = 1; i < args.length; i++) {
1073 pluginArgs.push(args[i]);
1074 }
1075 retval = plugin[params].apply(plugin, pluginArgs);
1076 }
1077 else {
1078 retval = plugin[params]();
1079 }
1080 }
1081 }
1082 });
1083
1084 return retval || lists;
1085 };
1086
1087 })(window.jQuery || window.Zepto, window, document);