Project

General

Profile

1
/*! UIkit 2.27.5 | http://www.getuikit.com | (c) 2014 YOOtheme | MIT License */
2
/*
3
 * Based on Nestable jQuery Plugin - Copyright (c) 2012 David Bushell - http://dbushell.com/
4
 */
5
(function(addon) {
6

    
7
    var component;
8

    
9
    if (window.UIkit2) {
10
        component = addon(UIkit2);
11
    }
12

    
13
    if (typeof define == 'function' && define.amd) {
14
        define('uikit-nestable', ['uikit'], function(){
15
            return component || addon(UIkit2);
16
        });
17
    }
18

    
19
})(function(UI) {
20

    
21
    "use strict";
22

    
23
    var hasTouch     = 'ontouchstart' in window || 'MSGesture' in window || window.PointerEvent,
24
        html         = UI.$html,
25
        touchedlists = [],
26
        $win         = UI.$win,
27
        draggingElement;
28

    
29
    var eStart  = hasTouch ? ('MSGesture' in window || window.PointerEvent ? 'pointerdown':'touchstart')    : 'mousedown',
30
        eMove   = hasTouch ? ('MSGesture' in window || window.PointerEvent ? 'pointermove':'touchmove')     : 'mousemove',
31
        eEnd    = hasTouch ? ('MSGesture' in window || window.PointerEvent ? 'pointerup':'touchend')        : 'mouseup',
32
        eCancel = hasTouch ? ('MSGesture' in window || window.PointerEvent ? 'pointercancel':'touchcancel') : 'mouseup';
33

    
34

    
35
    UI.component('nestable', {
36

    
37
        defaults: {
38
            listBaseClass   : 'uk-nestable',
39
            listClass       : 'uk-nestable-list',
40
            listItemClass   : 'uk-nestable-item',
41
            dragClass       : 'uk-nestable-dragged',
42
            movingClass     : 'uk-nestable-moving',
43
            noChildrenClass : 'uk-nestable-nochildren',
44
            emptyClass      : 'uk-nestable-empty',
45
            handleClass     : '',
46
            collapsedClass  : 'uk-collapsed',
47
            placeholderClass: 'uk-nestable-placeholder',
48
            noDragClass     : 'uk-nestable-nodrag',
49
            group           : false,
50
            maxDepth        : 10,
51
            threshold       : 20,
52
            idlethreshold   : 10,
53
        },
54

    
55
        boot: function() {
56

    
57
            // adjust document scrolling
58
            UI.$html.on('mousemove touchmove', function(e) {
59

    
60
                if (draggingElement) {
61

    
62
                    var top = draggingElement.offset().top;
63

    
64
                    if (top < UI.$win.scrollTop()) {
65
                        UI.$win.scrollTop(UI.$win.scrollTop() - Math.ceil(draggingElement.height()/2));
66
                    } else if ( (top + draggingElement.height()) > (window.innerHeight + UI.$win.scrollTop()) ) {
67
                        UI.$win.scrollTop(UI.$win.scrollTop() + Math.ceil(draggingElement.height()/2));
68
                    }
69
                }
70
            });
71

    
72
            // init code
73
            UI.ready(function(context) {
74

    
75
                UI.$("[data-uk-nestable]", context).each(function(){
76

    
77
                    var ele = UI.$(this);
78

    
79
                    if (!ele.data("nestable")) {
80
                        UI.nestable(ele, UI.Utils.options(ele.attr("data-uk-nestable")));
81
                    }
82
                });
83
            });
84
        },
85

    
86
        init: function() {
87

    
88
            var $this = this;
89

    
90
            Object.keys(this.options).forEach(function(key){
91

    
92
                if(String(key).indexOf('Class')!=-1) {
93
                    $this.options['_'+key] = '.' + $this.options[key];
94
                }
95
            });
96

    
97
            this.find(this.options._listItemClass).find(">ul").addClass(this.options.listClass);
98

    
99
            this.checkEmptyList();
100

    
101
            this.reset();
102
            this.element.data('nestable-group', this.options.group || UI.Utils.uid('nestable-group'));
103

    
104
            this.find(this.options._listItemClass).each(function() {
105
                $this.setParent(UI.$(this));
106
            });
107

    
108
            this.on('click', '[data-nestable-action]', function(e) {
109

    
110
                if ($this.dragEl || (!hasTouch && e.button !== 0)) {
111
                    return;
112
                }
113

    
114
                e.preventDefault();
115

    
116
                var target = UI.$(e.currentTarget),
117
                    action = target.data('nestableAction'),
118
                    item   = target.closest($this.options._listItemClass);
119

    
120
                if (action === 'collapse') {
121
                    $this.collapseItem(item);
122
                }
123
                if (action === 'expand') {
124
                    $this.expandItem(item);
125
                }
126
                if (action === 'toggle') {
127
                    $this.toggleItem(item);
128
                }
129
            });
130

    
131
            var onStartEvent = function(e) {
132

    
133
                var handle = UI.$(e.target),
134
                    link   = handle.is('a[href]') ? handle:handle.parents('a[href]');
135

    
136
                if (e.target === $this.element[0]) {
137
                    return;
138
                }
139

    
140
                if (handle.is($this.options._noDragClass) || handle.closest($this.options._noDragClass).length) {
141
                    return;
142
                }
143

    
144
                if (handle.is('[data-nestable-action]') || handle.closest('[data-nestable-action]').length) {
145
                    return;
146
                }
147

    
148
                if ($this.options.handleClass && !handle.hasClass($this.options.handleClass)) {
149

    
150
                    if ($this.options.handleClass) {
151
                        handle = handle.closest($this.options._handleClass);
152
                    }
153
                }
154

    
155
                if (!handle.length || $this.dragEl || (!hasTouch && e.button !== 0) || (hasTouch && e.touches && e.touches.length !== 1)) {
156
                    return;
157
                }
158

    
159
                if (e.originalEvent && e.originalEvent.touches) {
160
                    e = evt.originalEvent.touches[0];
161
                }
162

    
163
                $this.delayMove = function(evt) {
164

    
165
                    link = false;
166

    
167
                    evt.preventDefault();
168
                    $this.dragStart(e);
169
                    $this.trigger('start.uk.nestable', [$this]);
170

    
171
                    $this.delayMove = false;
172
                };
173

    
174
                $this.delayMove.x         = parseInt(e.pageX, 10);
175
                $this.delayMove.y         = parseInt(e.pageY, 10);
176
                $this.delayMove.threshold = $this.options.idlethreshold;
177

    
178
                if (link.length && eEnd == 'touchend') {
179

    
180
                    $this.one(eEnd, function(){
181
                        if (link && link.attr('href').trim()) {
182
                            location.href = link.attr('href');
183
                        }
184
                    });
185
                }
186

    
187
                e.preventDefault();
188
            };
189

    
190
            var onMoveEvent = function(e) {
191

    
192
                if (e.originalEvent && e.originalEvent.touches) {
193
                    e = e.originalEvent.touches[0];
194
                }
195

    
196
                if ($this.delayMove && (Math.abs(e.pageX - $this.delayMove.x) > $this.delayMove.threshold || Math.abs(e.pageY - $this.delayMove.y) > $this.delayMove.threshold)) {
197

    
198
                    if (!window.getSelection().toString()) {
199
                        $this.delayMove(e);
200
                    } else {
201
                        $this.delayMove = false;
202
                    }
203
                }
204

    
205
                if ($this.dragEl) {
206
                    e.preventDefault();
207
                    $this.dragMove(e);
208
                    $this.trigger('move.uk.nestable', [$this]);
209
                }
210
            };
211

    
212
            var onEndEvent = function(e) {
213

    
214
                if ($this.dragEl) {
215
                    e.preventDefault();
216
                    $this.dragStop(hasTouch && e.touches ? e.touches[0] : e);
217
                }
218

    
219
                draggingElement = false;
220
                $this.delayMove = false;
221
            };
222

    
223
            if (hasTouch) {
224
                this.element[0].addEventListener(eStart, onStartEvent, false);
225
                window.addEventListener(eMove, onMoveEvent, false);
226
                window.addEventListener(eEnd, onEndEvent, false);
227
                window.addEventListener(eCancel, onEndEvent, false);
228
            } else {
229
                this.on(eStart, onStartEvent);
230
                $win.on(eMove, onMoveEvent);
231
                $win.on(eEnd, onEndEvent);
232
            }
233

    
234
        },
235

    
236
        serialize: function() {
237

    
238
            var data,
239
                depth = 0,
240
                list  = this,
241
                step  = function(level, depth) {
242

    
243
                    var array = [ ], items = level.children(list.options._listItemClass);
244

    
245
                    items.each(function() {
246

    
247
                        var li    = UI.$(this),
248
                            item  = {}, attribute,
249
                            sub   = li.children(list.options._listClass);
250

    
251
                        for (var i = 0, attr, val; i < li[0].attributes.length; i++) {
252
                            attribute = li[0].attributes[i];
253
                            if (attribute.name.indexOf('data-') === 0) {
254
                                attr       = attribute.name.substr(5);
255
                                val        =  UI.Utils.str2json(attribute.value);
256
                                item[attr] = (val || attribute.value=='false' || attribute.value=='0') ? val:attribute.value;
257
                            }
258
                        }
259

    
260
                        if (sub.length) {
261
                            item.children = step(sub, depth + 1);
262
                        }
263

    
264
                        array.push(item);
265

    
266
                    });
267
                    return array;
268
                };
269

    
270
            data = step(list.element, depth);
271

    
272
            return data;
273
        },
274

    
275
        list: function(options) {
276

    
277
            var data  = [],
278
                list  = this,
279
                depth = 0,
280
                step  = function(level, depth, parent) {
281

    
282
                    var items = level.children(options._listItemClass);
283

    
284
                    items.each(function(index) {
285
                        var li   = UI.$(this),
286
                            item = UI.$.extend({parent_id: (parent ? parent : null), depth: depth, order: index}, li.data()),
287
                            sub  = li.children(options._listClass);
288

    
289
                        data.push(item);
290

    
291
                        if (sub.length) {
292
                            step(sub, depth + 1, li.data(options.idProperty || 'id'));
293
                        }
294
                    });
295
                };
296

    
297
            options = UI.$.extend({}, list.options, options);
298

    
299
            step(list.element, depth);
300

    
301
            return data;
302
        },
303

    
304
        reset: function() {
305

    
306
            this.mouse = {
307
                offsetX   : 0,
308
                offsetY   : 0,
309
                startX    : 0,
310
                startY    : 0,
311
                lastX     : 0,
312
                lastY     : 0,
313
                nowX      : 0,
314
                nowY      : 0,
315
                distX     : 0,
316
                distY     : 0,
317
                dirAx     : 0,
318
                dirX      : 0,
319
                dirY      : 0,
320
                lastDirX  : 0,
321
                lastDirY  : 0,
322
                distAxX   : 0,
323
                distAxY   : 0
324
            };
325
            this.moving     = false;
326
            this.dragEl     = null;
327
            this.dragRootEl = null;
328
            this.dragDepth  = 0;
329
            this.hasNewRoot = false;
330
            this.pointEl    = null;
331

    
332
            for (var i=0; i<touchedlists.length; i++) {
333
                this.checkEmptyList(touchedlists[i]);
334
            }
335

    
336
            touchedlists = [];
337
        },
338

    
339
        toggleItem: function(li) {
340
            this[li.hasClass(this.options.collapsedClass) ? 'expandItem':'collapseItem'](li);
341
        },
342

    
343
        expandItem: function(li) {
344
            li.removeClass(this.options.collapsedClass);
345
        },
346

    
347
        collapseItem: function(li) {
348
            var lists = li.children(this.options._listClass);
349
            if (lists.length) {
350
                li.addClass(this.options.collapsedClass);
351
            }
352
        },
353

    
354
        expandAll: function() {
355
            var list = this;
356
            this.find(list.options._listItemClass).each(function() {
357
                list.expandItem(UI.$(this));
358
            });
359
        },
360

    
361
        collapseAll: function() {
362
            var list = this;
363
            this.find(list.options._listItemClass).each(function() {
364
                list.collapseItem(UI.$(this));
365
            });
366
        },
367

    
368
        setParent: function(li) {
369

    
370
            if (li.children(this.options._listClass).length) {
371
                li.addClass('uk-parent');
372
            }
373
        },
374

    
375
        unsetParent: function(li) {
376
            li.removeClass('uk-parent '+this.options.collapsedClass);
377
            li.children(this.options._listClass).remove();
378
        },
379

    
380
        dragStart: function(e) {
381

    
382
            var mouse    = this.mouse,
383
                target   = UI.$(e.target),
384
                dragItem = target.closest(this.options._listItemClass),
385
                offset   = dragItem.offset();
386

    
387
            this.placeEl = dragItem;
388

    
389
            mouse.offsetX = e.pageX - offset.left;
390
            mouse.offsetY = e.pageY - offset.top;
391

    
392
            mouse.startX = mouse.lastX = offset.left;
393
            mouse.startY = mouse.lastY = offset.top;
394

    
395
            this.dragRootEl = this.element;
396

    
397
            this.dragEl = UI.$('<ul></ul>').addClass(this.options.listClass + ' ' + this.options.dragClass).append(dragItem.clone());
398
            this.dragEl.css('width', dragItem.width());
399
            this.placeEl.addClass(this.options.placeholderClass);
400

    
401
            draggingElement = this.dragEl;
402

    
403
            this.tmpDragOnSiblings = [dragItem[0].previousSibling, dragItem[0].nextSibling];
404

    
405
            UI.$body.append(this.dragEl);
406

    
407
            this.dragEl.css({
408
                left : offset.left,
409
                top  : offset.top
410
            });
411

    
412
            // total depth of dragging item
413
            var i, depth, items = this.dragEl.find(this.options._listItemClass);
414

    
415
            for (i = 0; i < items.length; i++) {
416
                depth = UI.$(items[i]).parents(this.options._listClass+','+this.options._listBaseClass).length;
417
                if (depth > this.dragDepth) {
418
                    this.dragDepth = depth;
419
                }
420
            }
421

    
422
            html.addClass(this.options.movingClass);
423
        },
424

    
425
        dragStop: function(e) {
426

    
427
            var el       = UI.$(this.placeEl),
428
                root     = this.placeEl.parents(this.options._listBaseClass+':first');
429

    
430
            this.placeEl.removeClass(this.options.placeholderClass);
431
            this.dragEl.remove();
432

    
433
            if (this.element[0] !== root[0]) {
434

    
435
                root.trigger('change.uk.nestable',[root.data('nestable'), el, 'added']);
436
                this.element.trigger('change.uk.nestable', [this, el, 'removed']);
437

    
438
            } else {
439
                this.element.trigger('change.uk.nestable',[this, el, "moved"]);
440
            }
441

    
442
            this.trigger('stop.uk.nestable', [this, el]);
443

    
444
            this.reset();
445

    
446
            html.removeClass(this.options.movingClass);
447
        },
448

    
449
        dragMove: function(e) {
450
            var list, parent, prev, next, depth,
451
                opt      = this.options,
452
                mouse    = this.mouse,
453
                maxDepth = this.dragRootEl ? this.dragRootEl.data('nestable').options.maxDepth : opt.maxDepth;
454

    
455
            this.dragEl.css({
456
                left : e.pageX - mouse.offsetX,
457
                top  : e.pageY - mouse.offsetY
458
            });
459

    
460
            // mouse position last events
461
            mouse.lastX = mouse.nowX;
462
            mouse.lastY = mouse.nowY;
463
            // mouse position this events
464
            mouse.nowX  = e.pageX;
465
            mouse.nowY  = e.pageY;
466
            // distance mouse moved between events
467
            mouse.distX = mouse.nowX - mouse.lastX;
468
            mouse.distY = mouse.nowY - mouse.lastY;
469
            // direction mouse was moving
470
            mouse.lastDirX = mouse.dirX;
471
            mouse.lastDirY = mouse.dirY;
472
            // direction mouse is now moving (on both axis)
473
            mouse.dirX = mouse.distX === 0 ? 0 : mouse.distX > 0 ? 1 : -1;
474
            mouse.dirY = mouse.distY === 0 ? 0 : mouse.distY > 0 ? 1 : -1;
475
            // axis mouse is now moving on
476
            var newAx   = Math.abs(mouse.distX) > Math.abs(mouse.distY) ? 1 : 0;
477

    
478
            // do nothing on first move
479
            if (!mouse.moving) {
480
                mouse.dirAx  = newAx;
481
                mouse.moving = true;
482
                return;
483
            }
484

    
485
            // calc distance moved on this axis (and direction)
486
            if (mouse.dirAx !== newAx) {
487
                mouse.distAxX = 0;
488
                mouse.distAxY = 0;
489
            } else {
490
                mouse.distAxX += Math.abs(mouse.distX);
491
                if (mouse.dirX !== 0 && mouse.dirX !== mouse.lastDirX) {
492
                    mouse.distAxX = 0;
493
                }
494
                mouse.distAxY += Math.abs(mouse.distY);
495
                if (mouse.dirY !== 0 && mouse.dirY !== mouse.lastDirY) {
496
                    mouse.distAxY = 0;
497
                }
498
            }
499
            mouse.dirAx = newAx;
500

    
501
            /**
502
             * move horizontal
503
             */
504
            if (mouse.dirAx && mouse.distAxX >= opt.threshold) {
505
                // reset move distance on x-axis for new phase
506
                mouse.distAxX = 0;
507
                prev = this.placeEl.prev('li');
508

    
509
                // increase horizontal level if previous sibling exists, is not collapsed, and does not have a 'no children' class
510
                if (mouse.distX > 0 && prev.length && !prev.hasClass(opt.collapsedClass) && !prev.hasClass(opt.noChildrenClass)) {
511

    
512
                    // cannot increase level when item above is collapsed
513
                    list = prev.find(opt._listClass).last();
514

    
515
                    // check if depth limit has reached
516
                    depth = this.placeEl.parents(opt._listClass+','+opt._listBaseClass).length;
517

    
518
                    if (depth + this.dragDepth <= maxDepth) {
519

    
520
                        // create new sub-level if one doesn't exist
521
                        if (!list.length) {
522
                            list = UI.$('<ul/>').addClass(opt.listClass);
523
                            list.append(this.placeEl);
524
                            prev.append(list);
525
                            this.setParent(prev);
526
                        } else {
527
                            // else append to next level up
528
                            list = prev.children(opt._listClass).last();
529
                            list.append(this.placeEl);
530
                        }
531
                    }
532
                }
533

    
534
                // decrease horizontal level
535
                if (mouse.distX < 0) {
536

    
537
                    // we cannot decrease the level if an item precedes the current one
538
                    next = this.placeEl.next(opt._listItemClass);
539
                    if (!next.length) {
540

    
541
                        // get parent ul of the list item
542
                        var parentUl = this.placeEl.closest([opt._listBaseClass, opt._listClass].join(','));
543
                        // try to get the li surrounding the ul
544
                        var surroundingLi = parentUl.closest(opt._listItemClass);
545

    
546
                        // if the ul is inside of a li (meaning it is nested)
547
                        if (surroundingLi.length) {
548
                            // we can decrease the horizontal level
549
                            surroundingLi.after(this.placeEl);
550
                            // if the previous parent ul is now empty
551
                            if (!parentUl.children().length) {
552
                                this.unsetParent(surroundingLi);
553
                            }
554
                        }
555
                    }
556
                }
557
            }
558

    
559
            var isEmpty = false;
560

    
561
            // find list item under cursor
562
            var pointX = e.pageX - (window.pageXOffset || document.scrollLeft || 0),
563
                pointY = e.pageY - (window.pageYOffset || document.documentElement.scrollTop);
564
            this.pointEl = UI.$(document.elementFromPoint(pointX, pointY));
565

    
566
            if (opt.handleClass && this.pointEl.hasClass(opt.handleClass)) {
567

    
568
                this.pointEl = this.pointEl.closest(opt._listItemClass);
569

    
570
            } else {
571

    
572
                var nestableitem = this.pointEl.closest(opt._listItemClass);
573

    
574
                if (nestableitem.length) {
575
                    this.pointEl = nestableitem;
576
                }
577
            }
578

    
579
            if (this.placeEl.find(this.pointEl).length) {
580
                return;
581
            }
582

    
583
            if (this.pointEl.data('nestable') && !this.pointEl.children().length) {
584
                isEmpty = true;
585
                this.checkEmptyList(this.pointEl);
586
            } else if (!this.pointEl.length || !this.pointEl.hasClass(opt.listItemClass)) {
587
                return;
588
            }
589

    
590
            // find parent list of item under cursor
591
            var pointElRoot = this.element,
592
                tmpRoot     = this.pointEl.closest(this.options._listBaseClass),
593
                isNewRoot   = pointElRoot[0] != tmpRoot[0];
594

    
595
            /**
596
             * move vertical
597
             */
598
            if (!mouse.dirAx || isNewRoot || isEmpty) {
599

    
600
                // check if groups match if dragging over new root
601
                if (isNewRoot && opt.group !== tmpRoot.data('nestable-group')) {
602
                    return;
603
                } else {
604
                    touchedlists.push(pointElRoot);
605
                }
606

    
607
                // check depth limit
608
                depth = this.dragDepth - 1 + this.pointEl.parents(opt._listClass+','+opt._listBaseClass).length;
609

    
610
                if (depth > maxDepth) {
611
                    return;
612
                }
613

    
614
                var before = e.pageY < (this.pointEl.offset().top + this.pointEl.height() / 2);
615

    
616
                parent = this.placeEl.parent();
617

    
618
                if (isEmpty) {
619
                    this.pointEl.append(this.placeEl);
620
                } else if (before) {
621
                    this.pointEl.before(this.placeEl);
622
                } else {
623
                    this.pointEl.after(this.placeEl);
624
                }
625

    
626
                if (!parent.children().length) {
627
                    if (!parent.data('nestable')) this.unsetParent(parent.parent());
628
                }
629

    
630
                this.checkEmptyList(this.dragRootEl);
631
                this.checkEmptyList(pointElRoot);
632

    
633
                // parent root list has changed
634
                if (isNewRoot) {
635
                    this.dragRootEl = tmpRoot;
636
                    this.hasNewRoot = this.element[0] !== this.dragRootEl[0];
637
                }
638
            }
639
        },
640

    
641
        checkEmptyList: function(list) {
642

    
643
            list  = list ? UI.$(list) : this.element;
644

    
645
            if (this.options.emptyClass) {
646
                list[!list.children().length ? 'addClass':'removeClass'](this.options.emptyClass);
647
            }
648
        }
649

    
650
    });
651

    
652
    return UI.nestable;
653
});
(19-19/46)