UNPKG

pip-webui

Version:

HTML5 UI for LOB applications

650 lines (560 loc) 28.9 kB
/** * @file Drag & drop attachable behavior * @description * Based on https://github.com/fatlinesofcode/pipDraggable * @copyright Digital Living Software Corp. 2014-2016 */ /* global angular */ (function () { 'use strict'; var thisModule = angular.module("pipDraggable", ['pipUtils']); thisModule.service('pipDraggable', [function () { var scope = this; scope.inputEvent = function (event) { if (angular.isDefined(event.touches)) { return event.touches[0]; } //Checking both is not redundent. If only check if touches isDefined, angularjs isDefnied will return error and stop the remaining scripty if event.originalEvent is not defined. else if (angular.isDefined(event.originalEvent) && angular.isDefined(event.originalEvent.touches)) { return event.originalEvent.touches[0]; } return event; }; }]); thisModule.directive('pipDrag', ['$rootScope', '$parse', '$document', '$window', 'pipDraggable', 'pipUtils', function ($rootScope, $parse, $document, $window, pipDraggable, pipUtils) { return { restrict: 'A', link: function (scope, element, attrs) { scope.value = attrs.ngDrag; var offset, _centerAnchor = false, _mx, _my, _tx, _ty, _mrx, _mry; var _hasTouch = ('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch; var _pressEvents = 'touchstart mousedown'; var _moveEvents = 'touchmove mousemove'; var _releaseEvents = 'touchend mouseup'; var _dragHandle; // to identify the element in order to prevent getting superflous events when a single element has both drag and drop directives on it. var _myid = scope.$id; var _data = null; var _dragOffset = null; var _dragEnabled = false; var _pressTimer = null; var onDragStartCallback = $parse(attrs.pipDragStart) || null; var onDragStopCallback = $parse(attrs.pipDragStop) || null; var onDragSuccessCallback = $parse(attrs.pipDragSuccess) || null; var allowTransform = angular.isDefined(attrs.allowTransform) ? scope.$eval(attrs.allowTransform) : false; var getDragData = $parse(attrs.pipDragData); var verticalScroll = pipUtils.toBoolean(attrs.pipVerticalScroll) || true, horizontalScroll = pipUtils.toBoolean(attrs.pipHorizontalScroll) || true, activationDistance = parseFloat(attrs.pipActivationDistance) || 75, scrollDistance = parseFloat(attrs.pipScrollDistance) || 50, scrollParent = false, scrollContainer = angular.element(window), scrollContainerGetter = $parse(attrs.pipScrollContainer); // deregistration function for mouse move events in $rootScope triggered by jqLite trigger handler var _deregisterRootMoveListener = angular.noop; var initialize = function () { element.attr('pip-draggable', 'false'); // prevent native drag // check to see if drag handle(s) was specified // if querySelectorAll is available, we use this instead of find // as JQLite find is limited to tagnames if (element[0].querySelectorAll) { var dragHandles = angular.element(element[0].querySelectorAll('[pip-drag-handle]')); } else { var dragHandles = element.find('[pip-drag-handle]'); } if (dragHandles.length) { _dragHandle = dragHandles; } toggleListeners(true); // Initialize scroll container if (scrollParent) { scrollContainer = angular.element($element.parent()); } else if (attrs.pipScrollContainer) { scrollContainer = angular.element(scrollContainerGetter(scope)); } else { scrollContainer = angular.element(window); } }; var toggleListeners = function (enable) { if (!enable)return; // add listeners. scope.$on('$destroy', onDestroy); scope.$watch(attrs.pipDrag, onEnableChange); scope.$watch(attrs.pipCenterAnchor, onCenterAnchor); // wire up touch events if (_dragHandle) { // handle(s) specified, use those to initiate drag _dragHandle.on(_pressEvents, onpress); } else { // no handle(s) specified, use the element as the handle element.on(_pressEvents, onpress); } if (!_hasTouch && element[0].nodeName.toLowerCase() == "img") { element.on('mousedown', function () { return false; }); // prevent native drag for images } }; var onDestroy = function (enable) { toggleListeners(false); }; var onEnableChange = function (newVal, oldVal) { _dragEnabled = (newVal); }; var onCenterAnchor = function (newVal, oldVal) { if (angular.isDefined(newVal)) _centerAnchor = (newVal || 'true'); }; var isClickableElement = function (evt) { return ( angular.isDefined(angular.element(evt.target).attr("pip-cancel-drag")) ); }; /* * When the element is clicked start the drag behaviour * On touch devices as a small delay so as not to prevent native window scrolling */ var onpress = function (evt) { if (!_dragEnabled)return; if (isClickableElement(evt)) { return; } if (evt.type == "mousedown" && evt.button != 0) { // Do not start dragging on right-click return; } if (_hasTouch) { cancelPress(); _pressTimer = setTimeout(function () { cancelPress(); onlongpress(evt); }, 100); $document.on(_moveEvents, cancelPress); $document.on(_releaseEvents, cancelPress); } else { onlongpress(evt); } }; var cancelPress = function () { clearTimeout(_pressTimer); $document.off(_moveEvents, cancelPress); $document.off(_releaseEvents, cancelPress); }; var onlongpress = function (evt) { if (!_dragEnabled)return; evt.preventDefault(); offset = element[0].getBoundingClientRect(); if (allowTransform) _dragOffset = offset; else { _dragOffset = {left: document.body.scrollLeft, top: document.body.scrollTop}; } element.centerX = element[0].offsetWidth / 2; element.centerY = element[0].offsetHeight / 2; _mx = pipDraggable.inputEvent(evt).pageX; _my = pipDraggable.inputEvent(evt).pageY; _mrx = _mx - offset.left; _mry = _my - offset.top; if (_centerAnchor) { _tx = _mx - element.centerX - $window.pageXOffset; _ty = _my - element.centerY - $window.pageYOffset; } else { _tx = _mx - _mrx - $window.pageXOffset; _ty = _my - _mry - $window.pageYOffset; } $document.on(_moveEvents, onmove); $document.on(_releaseEvents, onrelease); // This event is used to receive manually triggered mouse move events // jqLite unfortunately only supports triggerHandler(...) // See http://api.jquery.com/triggerHandler/ // _deregisterRootMoveListener = $rootScope.$on('draggable:_triggerHandlerMove', onmove); _deregisterRootMoveListener = $rootScope.$on('draggable:_triggerHandlerMove', function (event, origEvent) { onmove(origEvent); }); }; var onmove = function (evt) { if (!_dragEnabled)return; evt.preventDefault(); if (!element.hasClass('pip-dragging')) { _data = getDragData(scope); element.addClass('pip-dragging'); $rootScope.$broadcast('draggable:start', { x: _mx, y: _my, tx: _tx, ty: _ty, event: evt, element: element, data: _data }); if (onDragStartCallback) { scope.$apply(function () { onDragStartCallback(scope, {$data: _data, $event: evt}); }); } } _mx = pipDraggable.inputEvent(evt).pageX; _my = pipDraggable.inputEvent(evt).pageY; if (horizontalScroll || verticalScroll) { dragToScroll(); } if (_centerAnchor) { _tx = _mx - element.centerX - _dragOffset.left; _ty = _my - element.centerY - _dragOffset.top; } else { _tx = _mx - _mrx - _dragOffset.left; _ty = _my - _mry - _dragOffset.top; } moveElement(_tx, _ty); $rootScope.$broadcast('draggable:move', { x: _mx, y: _my, tx: _tx, ty: _ty, event: evt, element: element, data: _data, uid: _myid, dragOffset: _dragOffset }); }; var onrelease = function (evt) { if (!_dragEnabled) return; evt.preventDefault(); $rootScope.$broadcast('draggable:end', { x: _mx, y: _my, tx: _tx, ty: _ty, event: evt, element: element, data: _data, callback: onDragComplete, uid: _myid }); element.removeClass('pip-dragging'); element.parent().find('.pip-drag-enter').removeClass('pip-drag-enter'); reset(); $document.off(_moveEvents, onmove); $document.off(_releaseEvents, onrelease); if (onDragStopCallback) { scope.$apply(function () { onDragStopCallback(scope, {$data: _data, $event: evt}); }); } _deregisterRootMoveListener(); }; var onDragComplete = function (evt) { if (!onDragSuccessCallback)return; scope.$apply(function () { onDragSuccessCallback(scope, {$data: _data, $event: evt}); }); }; var reset = function () { if (allowTransform) element.css({transform: '', 'z-index': '', '-webkit-transform': '', '-ms-transform': ''}); else element.css({'position': '', top: '', left: '', 'z-index': '', width: ''}); }; var moveElement = function (x, y) { var eWidth = element.css('width'); if (allowTransform) { element.css({ transform: 'matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, ' + x + ', ' + y + ', 0, 1)', 'z-index': 99999, '-webkit-transform': 'matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, ' + x + ', ' + y + ', 0, 1)', '-ms-transform': 'matrix(1, 0, 0, 1, ' + x + ', ' + y + ')' }); } else { element.css({ 'left': x + 'px', 'top': y + 'px', 'position': 'fixed', 'z-index': 100, width: eWidth }); } }; var dragToScroll = function () { var scrollX = 0, scrollY = 0, offset = function (element) { return element.offset() || {left: 0, top: 0}; }; if (horizontalScroll) { var containerLeft = offset(scrollContainer).left, containerWidth = scrollContainer.innerWidth(), containerRight = containerLeft + containerWidth; if ((_mx - containerLeft) < activationDistance) { scrollX = -scrollDistance; } else if ((containerRight - _mx) < activationDistance) { scrollX = scrollDistance; } } if (verticalScroll) { var containerTop = offset(scrollContainer).top, containerHeight = scrollContainer.innerHeight(), containerBottom = containerTop + containerHeight; if ((_my - containerTop) < activationDistance) { scrollY = -scrollDistance + 30; } else if ((containerBottom - _my) < activationDistance) { scrollY = scrollDistance - 30; } } if (scrollX !== 0 || scrollY !== 0) { var containerScrollLeft = scrollContainer.scrollLeft(), containerScrollTop = scrollContainer.scrollTop(); scrollContainer.scrollLeft(containerScrollLeft + scrollX); scrollContainer.scrollTop(containerScrollTop + scrollY); } }; initialize(); } }; }]); thisModule.directive('pipDrop', ['$parse', '$timeout', '$window', '$document', 'pipDraggable', function ($parse, $timeout, $window, $document, pipDraggable) { return { restrict: 'A', link: function (scope, element, attrs) { scope.value = attrs.pipDrop; scope.isTouching = false; var _lastDropTouch = null; var _myid = scope.$id; var _dropEnabled = false; var onDropCallback = $parse(attrs.pipDropSuccess);// || function(){}; var onDragStartCallback = $parse(attrs.pipDragStart); var onDragStopCallback = $parse(attrs.pipDragStop); var onDragMoveCallback = $parse(attrs.pipDragMove); var initialize = function () { toggleListeners(true); }; var toggleListeners = function (enable) { // remove listeners if (!enable)return; // add listeners. scope.$watch(attrs.pipDrop, onEnableChange); scope.$on('$destroy', onDestroy); scope.$on('draggable:start', onDragStart); scope.$on('draggable:move', onDragMove); scope.$on('draggable:end', onDragEnd); }; var onDestroy = function (enable) { toggleListeners(false); }; var onEnableChange = function (newVal, oldVal) { _dropEnabled = newVal; }; var onDragStart = function (evt, obj) { if (!_dropEnabled)return; isTouching(obj.x, obj.y, obj.element); if (attrs.pipDragStart) { $timeout(function () { onDragStartCallback(scope, {$data: obj.data, $event: obj}); }); } }; var onDragMove = function (evt, obj) { if (!_dropEnabled)return; isTouching(obj.x, obj.y, obj.element); if (attrs.pipDragMove) { $timeout(function () { onDragMoveCallback(scope, {$data: obj.data, $event: obj}); }); } }; var onDragEnd = function (evt, obj) { // don't listen to drop events if this is the element being dragged // only update the styles and return if (!_dropEnabled || _myid === obj.uid) { updateDragStyles(false, obj.element); return; } if (isTouching(obj.x, obj.y, obj.element)) { // call the pipDraggable pipDragSuccess element callback if (obj.callback) { obj.callback(obj); } if (attrs.pipDropSuccess) { $timeout(function () { onDropCallback(scope, { $data: obj.data, $event: obj, $target: scope.$eval(scope.value) }); }); } } if (attrs.pipDragStop) { $timeout(function () { onDragStopCallback(scope, {$data: obj.data, $event: obj}); }); } updateDragStyles(false, obj.element); }; var isTouching = function (mouseX, mouseY, dragElement) { var touching = hitTest(mouseX, mouseY); scope.isTouching = touching; if (touching) { _lastDropTouch = element; } updateDragStyles(touching, dragElement); return touching; }; var updateDragStyles = function (touching, dragElement) { if (touching) { element.addClass('pip-drag-enter'); dragElement.addClass('pip-drag-over'); } else if (_lastDropTouch == element) { _lastDropTouch = null; element.removeClass('pip-drag-enter'); dragElement.removeClass('pip-drag-over'); } }; var hitTest = function (x, y) { var bounds = element[0].getBoundingClientRect(); x -= $document[0].body.scrollLeft + $document[0].documentElement.scrollLeft; y -= $document[0].body.scrollTop + $document[0].documentElement.scrollTop; return x >= bounds.left && x <= bounds.right && y <= bounds.bottom && y >= bounds.top; }; initialize(); } }; }]); //thisModule.directive('pipDragClone', ['$parse', '$timeout', 'pipDraggable', function ($parse, $timeout, pipDraggable) { // return { // restrict: 'A', // link: function (scope, element, attrs) { // var img, _allowClone = true; // var _dragOffset = null; // scope.clonedData = {}; // var initialize = function () { // // img = element.find('img'); // element.attr('pip-draggable', 'false'); // img.attr('draggable', 'false'); // reset(); // toggleListeners(true); // }; // // // var toggleListeners = function (enable) { // // remove listeners // // if (!enable)return; // // add listeners. // scope.$on('draggable:start', onDragStart); // scope.$on('draggable:move', onDragMove); // scope.$on('draggable:end', onDragEnd); // preventContextMenu(); // // }; // var preventContextMenu = function () { // // element.off('mousedown touchstart touchmove touchend touchcancel', absorbEvent_); // img.off('mousedown touchstart touchmove touchend touchcancel', absorbEvent_); // // element.on('mousedown touchstart touchmove touchend touchcancel', absorbEvent_); // img.on('mousedown touchstart touchmove touchend touchcancel', absorbEvent_); // }; // var onDragStart = function (evt, obj, elm) { // _allowClone = true; // if (angular.isDefined(obj.data.allowClone)) { // _allowClone = obj.data.allowClone; // } // if (_allowClone) { // scope.$apply(function () { // scope.clonedData = obj.data; // }); // element.css('width', obj.element[0].offsetWidth); // element.css('height', obj.element[0].offsetHeight); // // moveElement(obj.tx, obj.ty); // } // // }; // var onDragMove = function (evt, obj) { // if (_allowClone) { // // _tx = obj.tx + obj.dragOffset.left; // _ty = obj.ty + obj.dragOffset.top; // // moveElement(_tx, _ty); // } // }; // var onDragEnd = function (evt, obj) { // //moveElement(obj.tx,obj.ty); // if (_allowClone) { // reset(); // } // }; // // var reset = function () { // element.css({left: 0, top: 0, position: 'fixed', 'z-index': -1, visibility: 'hidden'}); // }; // var moveElement = function (x, y) { // element.css({ // transform: 'matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, ' + x + ', ' + y + ', 0, 1)', // 'z-index': 99999, // 'visibility': 'visible', // '-webkit-transform': 'matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, ' + x + ', ' + y + ', 0, 1)', // '-ms-transform': 'matrix(1, 0, 0, 1, ' + x + ', ' + y + ')' // //,margin: '0' don't monkey with the margin, // }); // }; // // var absorbEvent_ = function (event) { // var e = event;//.originalEvent; // e.preventDefault && e.preventDefault(); // e.stopPropagation && e.stopPropagation(); // e.cancelBubble = true; // e.returnValue = false; // return false; // }; // // initialize(); // } // }; //}]); thisModule.directive('pipPreventDrag', ['$parse', '$timeout', function ($parse, $timeout) { return { restrict: 'A', link: function (scope, element, attrs) { var initialize = function () { element.attr('pip-draggable', 'false'); toggleListeners(true); }; var toggleListeners = function (enable) { // remove listeners if (!enable)return; // add listeners. element.on('mousedown touchstart touchmove touchend touchcancel', absorbEvent_); }; var absorbEvent_ = function (event) { var e = event.originalEvent; e.preventDefault && e.preventDefault(); e.stopPropagation && e.stopPropagation(); e.cancelBubble = true; e.returnValue = false; return false; }; initialize(); } }; }]); thisModule.directive('pipCancelDrag', [function () { return { restrict: 'A', link: function (scope, element, attrs) { element.find('*').attr('pip-cancel-drag', 'pip-cancel-drag'); } }; }]); })();