aboutsummaryrefslogtreecommitdiffstats
path: root/uikit/static/js/components/sortable.js
diff options
context:
space:
mode:
Diffstat (limited to 'uikit/static/js/components/sortable.js')
-rw-r--r--uikit/static/js/components/sortable.js643
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;
+});