Mercurial > nebulaweb3
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 '&': '&', | |
378 '<': '<', | |
379 '>': '>', | |
380 '"': '"', | |
381 "'": ''' | |
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); |