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