UNPKG

ng-drag-to-reorder

Version:

Lightweight AngularJS drag and drop functionality to reorder lists without any dependencies other than Angular.

318 lines (289 loc) 11.8 kB
(function () { 'use strict'; angular.module('ngDragToReorder', []) .factory('ngDragToReorder', function () { return { isSupported: function () { var div = document.createElement('div'); return ('draggable' in div) || ('ondragstart' in div && 'ondrop' in div); } }; }) .directive('dragToReorder', function () { return { restrict: 'A', scope: true, controller: ['$parse', '$attrs', '$scope', '$element', function ($parse, $attrs, $scope, $element) { this.getList = function () { //return a shallow copy and prevents the updating of the parent object return $parse($attrs.dragToReorder)($scope).slice(); }; this.elem = function () { return $element[0]; }; }] } }) .directive('dragToReorderBind', function () { return { restrict: 'A', scope: true, controller: ['$parse', '$attrs', '$scope', '$element', function ($parse, $attrs, $scope, $element) { this.getList = function () { return $parse($attrs.dragToReorderBind)($scope); }; this.elem = function () { return $element[0]; }; }] } }) .directive('dtrDraggable', ['ngDragToReorder', '$parse', function (ngDragToReorder, $parse) { return { restrict: 'A', require: ['?^^dragToReorder', '?^^dragToReorderBind'], link: function (scope, element, attrs, ctrls) { if (!ngDragToReorder.isSupported()) return; var el = element[0], list, stringIdx, int, item, listCtrl = ctrls[0] ? ctrls[0] : ctrls[1], newIdx, prevIdx, target, offsetY, dragging = 'dtr-dragging', over = 'dtr-over', droppingAbove = 'dtr-dropping-above', droppingBelow = 'dtr-dropping-below', transition = 'dtr-transition', eventName = 'dropped', delay = 1000, loaded = false, above = [], below = [], i, j, topOffset = 50, bottomOffset = 50, windowHeight, listHeight, slowScroll = false, fastScroll = false, listScrollbar = false, listEl, listScroll = false, itemOffestTop, startY, listTopY, isChrome = !!window.chrome && !!window.chrome.webstore, isIE = !!document.documentMode, isEdge = !isIE && !!window.StyleMedia, isFirefox = typeof InstallTrigger !== 'undefined', isSafari = /constructor/i.test(window.HTMLElement) || (function (p) { return p.toString() === "[object SafariRemoteNotification]"; })(!window['safari'] || safari.pushNotification); if (attrs.dtrEvent) { eventName = attrs.dtrEvent || 'dropped'; } if (attrs.dtrInit) { attrs.$observe('dtrInit', function (val) { if (val && val !== 'false') { addListeners(); } else if (loaded) { removeListeners(); } loaded = true; }); } else { addListeners(); } function addListeners() { el.draggable = true; el.addEventListener('dragstart', dragStart, false); el.addEventListener('dragend', dragEnd, false); el.addEventListener('dragenter', dragEnter, false); el.addEventListener('dragleave', dragLeave, false); el.addEventListener('dragover', dragOver, false); el.addEventListener('drop', drop, false); if (isChrome || isIE || isEdge) el.addEventListener('drag', drag, false); } function removeListeners() { el.draggable = false; el.removeEventListener('dragstart', dragStart, false); el.removeEventListener('dragend', dragEnd, false); el.removeEventListener('dragenter', dragEnter, false); el.removeEventListener('dragleave', dragLeave, false); el.removeEventListener('dragover', dragOver, false); el.removeEventListener('drop', drop, false); el.removeEventListener('drag', drag, false); } function drag(e) { if (listScrollbar) { if (e.pageY - listTopY <= 25) { if (listEl.scrollTop > 0) { if (!listScroll) { listScroll = true; scrollList(-3); } } } else if (listTopY + listHeight - e.pageY <= 25) { if (listEl.scrollTop < listEl.scrollHeight - listHeight) { if (!listScroll) { listScroll = true; scrollList(3); } } } else listScroll = false; } if (e.clientY <= topOffset / 2) { if (!fastScroll) { slowScroll = false; fastScroll = true; scrollFast(-6); } } else if (e.clientY <= topOffset) { if (!slowScroll) { fastScroll = false; slowScroll = true; scrollSlow(-3); } } else if (e.clientY >= windowHeight - bottomOffset / 2) { if (!fastScroll) { slowScroll = false; fastScroll = true; scrollFast(6); } } else if (e.clientY >= windowHeight - bottomOffset) { if (!slowScroll) { fastScroll = false; slowScroll = true; scrollSlow(3); } } else { slowScroll = false; fastScroll = false; } } function scrollSlow(step) { window.scrollBy(0, step); if (slowScroll) setTimeout(function () { scrollSlow(step); }, 20) } function scrollFast(step) { window.scrollBy(0, step); if (fastScroll) setTimeout(function () { scrollFast(step); }, 20) } function scrollList(step) { if (listScroll) { listEl.scrollTop = listEl.scrollTop + step; setTimeout(function () { scrollList(step); }, 20) } } function dragStart(e) { windowHeight = window.innerHeight; listHeight = this.parentElement.clientHeight; listScrollbar = this.parentElement.scrollHeight > listHeight; listTopY = angular.element(this.parentElement).offset().top; itemOffestTop = angular.element(e.target).position().top; startY = e.pageY; listEl = this.parentElement; e.dataTransfer.effectAllowed = 'move'; stringIdx = scope.$index.toString(); e.dataTransfer.setData('text', stringIdx); this.classList.add(dragging); this.classList.add(transition); return false; } function dragEnd(e) { slowScroll = false; fastScroll = false; listScroll = false; target = this; target.classList.remove(dragging); if (attrs.dtrTransitionTimeout) { int = parseInt($parse(attrs.dtrTransitionTimeout)(scope), 10); if (typeof int === 'number' && int >= 0) delay = int; } setTimeout(function () { target.classList.remove(transition) }, delay); cleanupClasses(); return false; } function drop(e) { e.preventDefault(); if (e.stopPropagation) e.stopPropagation(); prevIdx = parseInt(e.dataTransfer.getData('text'), 10); this.classList.remove(over); if (this.classList.contains(droppingAbove)) { if (prevIdx < scope.$index) { newIdx = scope.$index - 1; } else { newIdx = scope.$index; } } else { if (prevIdx < scope.$index) { newIdx = scope.$index; } else { newIdx = scope.$index + 1; } } list = listCtrl.getList(); item = list.splice(prevIdx, 1)[0]; list.splice(newIdx, 0, item); scope.$apply(function () { scope.$emit('dragToReorder.' + eventName, { list: list, item: item, newIndex: newIdx, prevIndex: prevIdx }); }); this.classList.remove(over); this.classList.remove(droppingAbove); this.classList.remove(droppingBelow); /* if (this.previousElementSibling) { this.previousElementSibling.classList.remove(over); this.previousElementSibling.classList.remove(droppingAbove); this.previousElementSibling.classList.remove(droppingBelow); } if (this.nextElementSibling) { this.nextElementSibling.classList.remove(over); this.nextElementSibling.classList.remove(droppingAbove); this.nextElementSibling.classList.remove(droppingBelow); }*/ } function dragOver(e) { e.preventDefault(); e.dataTransfer.dropEffect = 'move'; offsetY = e.offsetY; if (!this.classList.contains(dragging)) { if (offsetY < (this.offsetHeight / 2)) { this.classList.remove(droppingBelow); this.classList.add(droppingAbove); /* if (this.previousElementSibling) this.previousElementSibling.classList.add(droppingBelow); if (this.nextElementSibling) this.nextElementSibling.classList.remove(droppingAbove);*/ } else { this.classList.remove(droppingAbove); this.classList.add(droppingBelow); /* if (this.previousElementSibling) this.previousElementSibling.classList.remove(droppingBelow); if (this.nextElementSibling) this.nextElementSibling.classList.add(droppingAbove);*/ } } return false; } function dragEnter(e) { e.preventDefault(); if (!this.classList.contains(dragging)) this.classList.add(over); } function dragLeave(e) { this.classList.remove(over); this.classList.remove(droppingAbove); this.classList.remove(droppingBelow); return false; } function cleanupClasses() { above = document.querySelectorAll('.' + droppingAbove); below = document.querySelectorAll('.' + droppingBelow); if (above.length) { for (i = 0; i < above.length; i++) { above[i].classList.remove(droppingAbove); } } if (below.length) { for (j = 0; j < below.length; j++) { below[j].classList.remove(droppingBelow); } } } } }; }]); })();