diff options
Diffstat (limited to 'uikit/static/js/components/sortable.js')
-rw-r--r-- | uikit/static/js/components/sortable.js | 643 |
1 files changed, 643 insertions, 0 deletions
diff --git a/uikit/static/js/components/sortable.js b/uikit/static/js/components/sortable.js new file mode 100644 index 0000000..37a369b --- /dev/null +++ b/uikit/static/js/components/sortable.js @@ -0,0 +1,643 @@ +/*! UIkit 2.21.0 | http://www.getuikit.com | (c) 2014 YOOtheme | MIT License */ +/* + * Based on nativesortable - Copyright (c) Brian Grinstead - https://github.com/bgrins/nativesortable + */ +(function(addon) { + + var component; + + if (window.UIkit) { + component = addon(UIkit); + } + + if (typeof define == "function" && define.amd) { + define("uikit-sortable", ["uikit"], function(){ + return component || addon(UIkit); + }); + } + +})(function(UI){ + + "use strict"; + + var supportsTouch = ('ontouchstart' in window) || (window.DocumentTouch && document instanceof DocumentTouch), + draggingPlaceholder, currentlyDraggingElement, currentlyDraggingTarget, dragging, moving, clickedlink, delayIdle, touchedlists, moved; + + function closestSortable(ele) { + + ele = UI.$(ele); + + do { + if (ele.data('sortable')) { + return ele; + } + ele = UI.$(ele).parent(); + } while(ele.length); + + return ele; + } + + UI.component('sortable', { + + defaults: { + + animation : 150, + threshold : 10, + + childClass : 'uk-sortable-item', + placeholderClass : 'uk-sortable-placeholder', + overClass : 'uk-sortable-over', + draggingClass : 'uk-sortable-dragged', + dragMovingClass : 'uk-sortable-moving', + baseClass : 'uk-sortable', + noDragClass : 'uk-sortable-nodrag', + dragCustomClass : '', + handleClass : false, + group : false, + + stop : function() {}, + start : function() {}, + change : function() {} + }, + + boot: function() { + + // auto init + UI.ready(function(context) { + + UI.$("[data-uk-sortable]", context).each(function(){ + + var ele = UI.$(this); + + if(!ele.data("sortable")) { + var plugin = UI.sortable(ele, UI.Utils.options(ele.attr("data-uk-sortable"))); + } + }); + }); + + UI.$html.on('mousemove touchmove', function(e) { + + if (delayIdle) { + + var src = e.originalEvent.targetTouches ? e.originalEvent.targetTouches[0] : e; + + if (Math.abs(src.pageX - delayIdle.pos.x) > delayIdle.threshold || Math.abs(src.pageY - delayIdle.pos.y) > delayIdle.threshold) { + delayIdle.apply(src); + } + } + + if (draggingPlaceholder) { + + if (!moving) { + moving = true; + draggingPlaceholder.show(); + + draggingPlaceholder.$current.addClass(draggingPlaceholder.$sortable.options.placeholderClass); + draggingPlaceholder.$sortable.element.children().addClass(draggingPlaceholder.$sortable.options.childClass); + + UI.$html.addClass(draggingPlaceholder.$sortable.options.dragMovingClass); + } + + var offset = draggingPlaceholder.data('mouse-offset'), + left = parseInt(e.originalEvent.pageX, 10) + offset.left, + top = parseInt(e.originalEvent.pageY, 10) + offset.top; + + draggingPlaceholder.css({'left': left, 'top': top }); + + // adjust document scrolling + if (top < UI.$win.scrollTop()) { + UI.$win.scrollTop(UI.$win.scrollTop() - Math.ceil(draggingPlaceholder.height()/2)); + } else if ( (top + draggingPlaceholder.height()) > (window.innerHeight + UI.$win.scrollTop()) ) { + UI.$win.scrollTop(UI.$win.scrollTop() + Math.ceil(draggingPlaceholder.height()/2)); + } + } + }); + + UI.$html.on('mouseup touchend', function(e) { + + delayIdle = clickedlink = false; + + // dragging? + if (!currentlyDraggingElement || !draggingPlaceholder) { + // completely reset dragging attempt. will cause weird delay behavior elsewise + currentlyDraggingElement = draggingPlaceholder = null; + return; + } + + // inside or outside of sortable? + var sortable = closestSortable(e.target), + component = draggingPlaceholder.$sortable, + ev = { type: e.type }; + + if (sortable[0]) { + component.dragDrop(ev, component.element); + } + component.dragEnd(ev, component.element); + }); + }, + + init: function() { + + var $this = this, + element = this.element[0], + children; + + touchedlists = []; + + // make sure :empty selector works on empty lists + if (this.element.children().length === 0) { + this.element.html(''); + } + + this.element.data('sortable-group', this.options.group ? this.options.group : UI.Utils.uid('sortable-group')); + + var handleDragStart = delegate(function(e) { + + var $target = UI.$(e.target), + $link = $target.is('a[href]') ? $target:$target.parents('a[href]'); + + if ($target.is(':input')) { + return; + } + + e.preventDefault(); + + if (!supportsTouch && $link.length) { + + $link.one('click', function(e){ + e.preventDefault(); + }).one('mouseup', function(){ + if(!moved) $link.trigger('click'); + }); + } + + return $this.dragStart(e, this); + }); + + var handleDragOver = delegate(function(e) { + + if (!currentlyDraggingElement) { + return true; + } + + if (e.preventDefault) { + e.preventDefault(); + } + + return false; + }); + + var handleDragEnter = delegate(UI.Utils.debounce(function(e) { + return $this.dragEnter(e, this); + }), 40); + + var handleDragLeave = delegate(function(e) { + + // Prevent dragenter on a child from allowing a dragleave on the container + var previousCounter = $this.dragenterData(this); + $this.dragenterData(this, previousCounter - 1); + + // This is a fix for child elements firing dragenter before the parent fires dragleave + if (!$this.dragenterData(this)) { + UI.$(this).removeClass($this.options.overClass); + $this.dragenterData(this, false); + } + }); + + var handleTouchMove = delegate(function(e) { + + if (!currentlyDraggingElement || + currentlyDraggingElement === this || + currentlyDraggingTarget === this) { + return true; + } + + $this.element.children().removeClass($this.options.overClass); + currentlyDraggingTarget = this; + + $this.moveElementNextTo(currentlyDraggingElement, this); + + return prevent(e); + }); + + // Bind/unbind standard mouse/touch events as a polyfill. + function addDragHandlers() { + if (supportsTouch) { + element.addEventListener("touchmove", handleTouchMove, false); + } else { + element.addEventListener('mouseover', handleDragEnter, false); + element.addEventListener('mouseout', handleDragLeave, false); + } + + // document.addEventListener("selectstart", prevent, false); + } + + function removeDragHandlers() { + if (supportsTouch) { + element.removeEventListener("touchmove", handleTouchMove, false); + } else { + element.removeEventListener('mouseover', handleDragEnter, false); + element.removeEventListener('mouseout', handleDragLeave, false); + } + + // document.removeEventListener("selectstart", prevent, false); + } + + this.addDragHandlers = addDragHandlers; + this.removeDragHandlers = removeDragHandlers; + + function handleDragMove(e) { + + if (!currentlyDraggingElement) { + return; + } + + $this.dragMove(e, $this); + } + + function delegate(fn) { + + return function(e) { + + var touch, target, context; + + if (e) { + touch = (supportsTouch && e.touches && e.touches[0]) || { }; + target = touch.target || e.target; + + // Fix event.target for a touch event + if (supportsTouch && document.elementFromPoint) { + target = document.elementFromPoint(e.pageX - document.body.scrollLeft, e.pageY - document.body.scrollTop); + } + } + + if (UI.$(target).hasClass($this.options.childClass)) { + fn.apply(target, [e]); + } else if (target !== element) { + + // If a child is initiating the event or ending it, then use the container as context for the callback. + context = moveUpToChildNode(element, target); + + if (context) { + fn.apply(context, [e]); + } + } + }; + } + + window.addEventListener(supportsTouch ? 'touchmove' : 'mousemove', handleDragMove, false); + element.addEventListener(supportsTouch ? 'touchstart': 'mousedown', handleDragStart, false); + }, + + dragStart: function(e, elem) { + + moved = false; + moving = false; + dragging = false; + + var $this = this, + target = UI.$(e.target), + children = $this.element.children(); + + if (!supportsTouch && e.button==2) { + return; + } + + if ($this.options.handleClass) { + + var handle = target.hasClass($this.options.handleClass) ? target : target.closest('.'+$this.options.handleClass, $this.element); + + if (!handle.length) { + //e.preventDefault(); + return; + } + } + + if (target.is('.'+$this.options.noDragClass) || target.closest('.'+$this.options._noDragClass).length) { + return; + } + + // prevent dragging if taget is a form field + if (target.is(':input')) { + return; + } + + currentlyDraggingElement = elem; + + // init drag placeholder + if (draggingPlaceholder) { + draggingPlaceholder.remove(); + } + + var $current = UI.$(currentlyDraggingElement), offset = $current.offset(); + + delayIdle = { + + pos : { x:e.pageX, y:e.pageY }, + threshold : $this.options.threshold, + apply : function(evt) { + + draggingPlaceholder = UI.$('<div class="'+([$this.options.draggingClass, $this.options.dragCustomClass].join(' '))+'"></div>').css({ + display : 'none', + top : offset.top, + left : offset.left, + width : $current.width(), + height : $current.height(), + padding : $current.css('padding') + }).data({ + 'mouse-offset': { + 'left' : offset.left - parseInt(evt.pageX, 10), + 'top' : offset.top - parseInt(evt.pageY, 10) + }, + 'origin' : $this.element, + 'index' : $current.index() + }).append($current.html()).appendTo('body'); + + draggingPlaceholder.$current = $current; + draggingPlaceholder.$sortable = $this; + $current.data('sortable-group', $this.options.group); + + $this.addDragHandlers(); + + $this.options.start(this, currentlyDraggingElement); + $this.trigger('start.uk.sortable', [$this, currentlyDraggingElement]); + + moved = true; + delayIdle = false; + } + }; + }, + + dragMove: function(e, elem) { + var overEl = UI.$(document.elementFromPoint(e.pageX - document.body.scrollLeft, e.pageY - (window.pageYOffset || document.documentElement.scrollTop))), + overRoot = overEl.closest('.'+this.options.baseClass), + groupOver = overRoot.data("sortable-group"), + $current = UI.$(currentlyDraggingElement), + currentRoot = $current.parent(), + groupCurrent = $current.data("sortable-group"), + overChild; + + if (overRoot[0] !== currentRoot[0] && groupCurrent !== undefined && groupOver === groupCurrent) { + overRoot.data('sortable').addDragHandlers(); + + touchedlists.push(overRoot); + overRoot.children().addClass(this.options.childClass); + + // swap root + if (overRoot.children().length > 0) { + overChild = overEl.closest('.'+this.options.childClass); + overChild.before($current); + } else { // empty list + overEl.append($current); + } + + // list empty? remove inner whitespace to make sure :empty selector works + if (currentRoot.children().length === 0) { + currentRoot.html(''); + } + + UIkit.$doc.trigger('mouseover'); + } + }, + + dragEnter: function(e, elem) { + + if (!currentlyDraggingElement || currentlyDraggingElement === elem) { + return true; + } + + // Prevent dragenter on a child from allowing a dragleave on the container + var previousCounter = this.dragenterData(elem); + + this.dragenterData(elem, previousCounter + 1); + + if (previousCounter === 0) { + + UI.$(elem).addClass(this.options.overClass); + + this.moveElementNextTo(currentlyDraggingElement, elem); + } + + return false; + }, + + dragEnd: function(e, elem) { + + var $this = this; + + // avoid triggering event twice + if (currentlyDraggingElement) { + // TODO: trigger on right element? + this.options.stop(elem); + this.trigger('stop.uk.sortable', [this]); + } + + currentlyDraggingElement = null; + currentlyDraggingTarget = null; + + touchedlists.push(this.element); + touchedlists.forEach(function(el, i) { + UI.$(el).children().each(function() { + if (this.nodeType === 1) { + UI.$(this).removeClass($this.options.overClass) + .removeClass($this.options.placeholderClass) + .removeClass($this.options.childClass); + $this.dragenterData(this, false); + } + }); + }); + + touchedlists = []; + + UI.$html.removeClass(this.options.dragMovingClass); + + this.removeDragHandlers(); + + if (draggingPlaceholder) { + draggingPlaceholder.remove(); + draggingPlaceholder = null; + } + }, + + dragDrop: function(e, elem) { + + if (e.type === 'drop') { + + if (e.stopPropagation) { + e.stopPropagation(); + } + + if (e.preventDefault) { + e.preventDefault(); + } + } + + this.triggerChangeEvents(); + }, + + triggerChangeEvents: function() { + + // trigger events once + if (!currentlyDraggingElement) return; + + var $current = UI.$(currentlyDraggingElement), + oldRoot = draggingPlaceholder.data("origin"), + newRoot = $current.closest('.'+this.options.baseClass), + triggers = []; + + // events depending on move inside lists or across lists + if (oldRoot[0] === newRoot[0] && draggingPlaceholder.data('index') != $current.index() ) { + triggers.push({el: this, mode: 'moved'}); + } else if (oldRoot[0] != newRoot[0]) { + triggers.push({el: newRoot, mode: 'added'}, {el: oldRoot, mode: 'removed'}); + } + + triggers.forEach(function (trigger, i) { + trigger.el.trigger('change.uk.sortable', [trigger.el, currentlyDraggingElement, trigger.mode]); + }); + }, + + dragenterData: function(element, val) { + + element = UI.$(element); + + if (arguments.length == 1) { + return parseInt(element.data('child-dragenter'), 10) || 0; + } else if (!val) { + element.removeData('child-dragenter'); + } else { + element.data('child-dragenter', Math.max(0, val)); + } + }, + + moveElementNextTo: function(element, elementToMoveNextTo) { + + dragging = true; + + var $this = this, + list = UI.$(element).parent().css('min-height', ''), + next = isBelow(element, elementToMoveNextTo) ? elementToMoveNextTo : elementToMoveNextTo.nextSibling, + children = list.children(), + count = children.length; + + if (!$this.options.animation) { + elementToMoveNextTo.parentNode.insertBefore(element, next); + UI.Utils.checkDisplay($this.element.parent()); + return; + } + + list.css('min-height', list.height()); + + children.stop().each(function(){ + var ele = UI.$(this), + offset = ele.position(); + + offset.width = ele.width(); + + ele.data('offset-before', offset); + }); + + elementToMoveNextTo.parentNode.insertBefore(element, next); + + UI.Utils.checkDisplay($this.element.parent()); + + children = list.children().each(function() { + var ele = UI.$(this); + ele.data('offset-after', ele.position()); + }).each(function() { + var ele = UI.$(this), + before = ele.data('offset-before'); + ele.css({'position':'absolute', 'top':before.top, 'left':before.left, 'min-width':before.width }); + }); + + children.each(function(){ + + var ele = UI.$(this), + before = ele.data('offset-before'), + offset = ele.data('offset-after'); + + ele.css('pointer-events', 'none').width(); + + setTimeout(function(){ + ele.animate({'top':offset.top, 'left':offset.left}, $this.options.animation, function() { + ele.css({'position':'','top':'', 'left':'', 'min-width': '', 'pointer-events':''}).removeClass($this.options.overClass).removeData('child-dragenter'); + count--; + if (!count) { + list.css('min-height', ''); + UI.Utils.checkDisplay($this.element.parent()); + } + }); + }, 0); + }); + }, + + serialize: function() { + + var data = [], item, attribute; + + this.element.children().each(function(j, child) { + item = {}; + for (var i = 0; i < child.attributes.length; i++) { + attribute = child.attributes[i]; + if (attribute.name.indexOf('data-') === 0) { + item[attribute.name.substr(5)] = UI.Utils.str2json(attribute.value); + } + } + data.push(item); + }); + + return data; + } + }); + + // helpers + + function isBelow(el1, el2) { + + var parent = el1.parentNode; + + if (el2.parentNode != parent) { + return false; + } + + var cur = el1.previousSibling; + + while (cur && cur.nodeType !== 9) { + if (cur === el2) { + return true; + } + cur = cur.previousSibling; + } + + return false; + } + + function moveUpToChildNode(parent, child) { + var cur = child; + if (cur == parent) { return null; } + + while (cur) { + if (cur.parentNode === parent) { + return cur; + } + + cur = cur.parentNode; + if ( !cur || !cur.ownerDocument || cur.nodeType === 11 ) { + break; + } + } + return null; + } + + function prevent(e) { + if (e.stopPropagation) { + e.stopPropagation(); + } + if (e.preventDefault) { + e.preventDefault(); + } + e.returnValue = false; + } + + return UI.sortable; +}); |