UNPKG

angular-gantt

Version:

Gantt chart component for AngularJS

494 lines (416 loc) 27.9 kB
/* Project: angular-gantt v1.2.9 - Gantt chart component for AngularJS Authors: Marco Schweighauser, Rémi Alvergnat License: MIT Homepage: https://www.angular-gantt.com Github: https://github.com/angular-gantt/angular-gantt.git */ (function(){ 'use strict'; angular.module('gantt.movable', ['gantt']).directive('ganttMovable', ['ganttMouseButton', 'ganttMouseOffset', 'ganttSmartEvent', 'ganttMovableOptions', 'ganttUtils', 'ganttDom', '$window', '$document', '$timeout', function(mouseButton, mouseOffset, smartEvent, movableOptions, utils, dom, $window, $document, $timeout) { // Provides moving and resizing of tasks return { restrict: 'E', require: '^gantt', scope: { enabled: '=?', allowMoving: '=?', allowResizing: '=?', allowRowSwitching: '=?' }, link: function(scope, element, attrs, ganttCtrl) { var api = ganttCtrl.gantt.api; // Load options from global options attribute. if (scope.options && typeof(scope.options.movable) === 'object') { for (var option in scope.options.movable) { scope[option] = scope.options[option]; } } movableOptions.initialize(scope); api.registerEvent('tasks', 'move'); api.registerEvent('tasks', 'moveBegin'); api.registerEvent('tasks', 'moveEnd'); api.registerEvent('tasks', 'resize'); api.registerEvent('tasks', 'resizeBegin'); api.registerEvent('tasks', 'resizeEnd'); api.registerEvent('tasks', 'change'); var _hasTouch = ('ontouchstart' in $window) || $window.DocumentTouch && $document[0] instanceof $window.DocumentTouch; var _pressEvents = 'touchstart mousedown'; var _moveEvents = 'touchmove mousemove'; var _releaseEvents = 'touchend mouseup'; var taskWithSmallWidth = 15; var resizeAreaWidthBig = 5; var resizeAreaWidthSmall = 3; var scrollSpeed = 15; var scrollTriggerDistance = 5; var mouseStartOffsetX; var moveStartX; api.directives.on.new(scope, function(directiveName, taskScope, taskElement) { if (directiveName === 'ganttTask') { var windowElement = angular.element($window); var ganttBodyElement = taskScope.row.rowsManager.gantt.body.$element; var ganttScrollElement = taskScope.row.rowsManager.gantt.scroll.$element; var taskHasBeenChanged = false; var taskHasBeenMovedFromAnotherRow = false; var scrollInterval; var foregroundElement = taskScope.task.getForegroundElement(); // IE<11 doesn't support `pointer-events: none` // So task content element must be added to support moving properly. var contentElement = taskScope.task.getContentElement(); var onPressEvents = function(evt) { evt.preventDefault(); if (_hasTouch) { evt = mouseOffset.getTouch(evt); } var taskMovable = taskScope.task.model.movable; var rowMovable = taskScope.task.row.model.movable; if (typeof(taskMovable) === 'boolean' || angular.isFunction(taskMovable)) { taskMovable = {enabled: taskMovable}; } if (typeof(rowMovable) === 'boolean' || angular.isFunction(rowMovable)) { rowMovable = {enabled: rowMovable}; } var enabledValue = utils.firstProperty([taskMovable, rowMovable], 'enabled', scope.enabled); var enabled = angular.isFunction(enabledValue) ? enabledValue(evt, taskScope.task): enabledValue; if (enabled) { var taskOffsetX = mouseOffset.getOffsetForElement(foregroundElement[0], evt).x; var mode = getMoveMode(taskOffsetX); if (mode !== '' && mouseButton.getButton(evt) === 1) { var bodyOffsetX = mouseOffset.getOffsetForElement(ganttBodyElement[0], evt).x; enableMoveMode(mode, bodyOffsetX); } taskScope.$digest(); } }; foregroundElement.on(_pressEvents, onPressEvents); contentElement.on(_pressEvents, onPressEvents); var onMousemove = function (evt) { var taskMovable = taskScope.task.model.movable; var rowMovable = taskScope.task.row.model.movable; if (typeof(taskMovable) === 'boolean' || angular.isFunction(taskMovable)) { taskMovable = {enabled: taskMovable}; } if (typeof(rowMovable) === 'boolean' || angular.isFunction(rowMovable)) { rowMovable = {enabled: rowMovable}; } var enabledValue = utils.firstProperty([taskMovable, rowMovable], 'enabled', scope.enabled); var enabled = angular.isFunction(enabledValue) ? enabledValue(evt, taskScope.task): enabledValue; if (enabled && !taskScope.task.isMoving) { var taskOffsetX = mouseOffset.getOffsetForElement(foregroundElement[0], evt).x; var mode = getMoveMode(taskOffsetX); if (mode !== '' && mode !== 'M') { foregroundElement.css('cursor', getCursor(mode)); contentElement.css('cursor', getCursor(mode)); } else { foregroundElement.css('cursor', ''); contentElement.css('cursor', ''); } } }; foregroundElement.on('mousemove', onMousemove); contentElement.on('mousemove', onMousemove); var handleMove = function(evt) { if (taskScope.task.isMoving && !taskScope.destroyed) { clearScrollInterval(); moveTask(evt); scrollScreen(evt); } }; var moveTask = function(evt) { var oldTaskHasBeenChanged = taskHasBeenChanged; var mousePos = mouseOffset.getOffsetForElement(ganttBodyElement[0], evt); var x = mousePos.x; taskScope.task.mouseOffsetX = x; var taskOutOfRange = taskScope.task.row.rowsManager.gantt.options.value('taskOutOfRange'); var taskMovable = taskScope.task.model.movable; var rowMovable = taskScope.task.row.model.movable; if (typeof(taskMovable) === 'boolean' || angular.isFunction(taskMovable)) { taskMovable = {enabled: taskMovable}; } if (typeof(rowMovable) === 'boolean' || angular.isFunction(rowMovable)) { rowMovable = {enabled: rowMovable}; } if (taskScope.task.moveMode === 'M') { var allowRowSwitching = utils.firstProperty([taskMovable, rowMovable], 'allowRowSwitching', scope.allowRowSwitching); if (allowRowSwitching) { var scrollRect = ganttScrollElement[0].getBoundingClientRect(); var rowCenterLeft = scrollRect.left + scrollRect.width / 2; var ganttBody = angular.element($document[0].querySelectorAll('.gantt-body')); ganttBody.css('pointer-events', 'auto'); // pointer-events must be enabled for following to work. var targetRowElement = dom.findElementFromPoint(rowCenterLeft, evt.clientY, function(element) { return angular.element(element).hasClass('gantt-row'); }); ganttBody.css('pointer-events', ''); var rows = ganttCtrl.gantt.rowsManager.rows; var targetRow; for (var i= 0, l=rows.length; i<l; i++) { if (targetRowElement === rows[i].$element[0]) { targetRow = rows[i]; break; } } var sourceRow = taskScope.task.row; if (targetRow !== undefined && sourceRow !== targetRow) { targetRow.moveTaskToRow(taskScope.task, true); sourceRow.$scope.$digest(); targetRow.$scope.$digest(); taskHasBeenChanged = true; } } var allowMoving = utils.firstProperty([taskMovable, rowMovable], 'allowMoving', scope.allowMoving); if (allowMoving) { x = x - mouseStartOffsetX; if (taskOutOfRange !== 'truncate') { if (x < 0) { x = 0; } else if (x + taskScope.task.width >= taskScope.gantt.width) { x = taskScope.gantt.width - taskScope.task.width; } } taskScope.task.moveTo(x, true); taskScope.$digest(); if (taskHasBeenChanged) { taskScope.row.rowsManager.gantt.api.tasks.raise.move(taskScope.task); } taskHasBeenChanged = true; } } else if (taskScope.task.moveMode === 'E') { if (x <= taskScope.task.left) { x = taskScope.task.left; taskScope.task.moveMode = 'W'; setGlobalCursor(getCursor(taskScope.task.moveMode )); } if (taskOutOfRange !== 'truncate' && x >= taskScope.gantt.width) { x = taskScope.gantt.width; } taskScope.task.setTo(x, true); taskScope.$digest(); if (taskHasBeenChanged) { taskScope.row.rowsManager.gantt.api.tasks.raise.resize(taskScope.task); } taskHasBeenChanged = true; } else { if (x > taskScope.task.left + taskScope.task.width) { x = taskScope.task.left + taskScope.task.width; taskScope.task.moveMode = 'E'; setGlobalCursor(getCursor(taskScope.task.moveMode )); } if (taskOutOfRange !== 'truncate' && x < 0) { x = 0; } taskScope.task.setFrom(x, true); taskScope.$digest(); if (taskHasBeenChanged) { taskScope.row.rowsManager.gantt.api.tasks.raise.resize(taskScope.task); } taskHasBeenChanged = true; } if (!oldTaskHasBeenChanged && taskHasBeenChanged && !taskHasBeenMovedFromAnotherRow) { var backgroundElement = taskScope.task.getBackgroundElement(); if (taskScope.task.moveMode === 'M') { backgroundElement.addClass('gantt-task-moving'); taskScope.row.rowsManager.gantt.api.tasks.raise.moveBegin(taskScope.task); } else { backgroundElement.addClass('gantt-task-resizing'); taskScope.row.rowsManager.gantt.api.tasks.raise.resizeBegin(taskScope.task); } } }; var scrollScreen = function(evt) { var mousePos = mouseOffset.getOffsetForElement(ganttBodyElement[0], evt); var leftScreenBorder = ganttScrollElement[0].scrollLeft; var screenWidth = ganttScrollElement[0].offsetWidth; var scrollWidth = ganttScrollElement[0].scrollWidth; var rightScreenBorder = leftScreenBorder + screenWidth; var keepOnScrolling = false; if (mousePos.x < moveStartX) { // Scroll to the left if (leftScreenBorder > 0 && mousePos.x <= leftScreenBorder + scrollTriggerDistance) { mousePos.x -= scrollSpeed; keepOnScrolling = true; taskScope.row.rowsManager.gantt.api.scroll.left(scrollSpeed); } } else { // Scroll to the right if (rightScreenBorder < scrollWidth && mousePos.x >= rightScreenBorder - scrollTriggerDistance) { mousePos.x += scrollSpeed; keepOnScrolling = true; taskScope.row.rowsManager.gantt.api.scroll.right(scrollSpeed); } } if (keepOnScrolling) { scrollInterval = $timeout(function() { handleMove(evt); }, 100, true); } }; var clearScrollInterval = function() { if (scrollInterval !== undefined) { $timeout.cancel(scrollInterval); scrollInterval = undefined; } }; var getMoveMode = function(x) { var distance = 0; var taskMovable = taskScope.task.model.movable; var rowMovable = taskScope.task.row.model.movable; if (typeof(taskMovable) === 'boolean') { taskMovable = {enabled: taskMovable}; } if (typeof(rowMovable) === 'boolean') { rowMovable = {enabled: rowMovable}; } var allowResizing = utils.firstProperty([taskMovable, rowMovable], 'allowResizing', scope.allowResizing); var allowRowSwitching = utils.firstProperty([taskMovable, rowMovable], 'allowRowSwitching', scope.allowRowSwitching); var allowMoving = utils.firstProperty([taskMovable, rowMovable], 'allowMoving', scope.allowMoving); // Define resize&move area. Make sure the move area does not get too small. if (allowResizing) { distance = foregroundElement[0].offsetWidth < taskWithSmallWidth ? resizeAreaWidthSmall : resizeAreaWidthBig; } if (allowResizing && x > foregroundElement[0].offsetWidth - distance) { return 'E'; } else if (allowResizing && x < distance) { return 'W'; } else if ((allowMoving || allowRowSwitching) && x >= distance && x <= foregroundElement[0].offsetWidth - distance) { return 'M'; } else { return ''; } }; var getCursor = function(mode) { switch (mode) { case 'E': return 'e-resize'; case 'W': return 'w-resize'; case 'M': return 'move'; } }; var setGlobalCursor = function(cursor) { taskElement.css('cursor', cursor); angular.element($document[0].body).css({ '-moz-user-select': cursor === '' ? '': '-moz-none', '-webkit-user-select': cursor === '' ? '': 'none', '-ms-user-select': cursor === '' ? '': 'none', 'user-select': cursor === '' ? '': 'none', 'cursor': cursor }); }; var enableMoveMode = function(mode, x) { // Clone taskModel if (taskScope.task.originalModel === undefined) { taskScope.task.originalRow = taskScope.task.row; taskScope.task.originalModel = taskScope.task.model; taskScope.task.model = angular.copy(taskScope.task.originalModel); } // Init mouse start variables if (!taskHasBeenMovedFromAnotherRow) { moveStartX = x; mouseStartOffsetX = x - taskScope.task.modelLeft; } // Init task move taskHasBeenChanged = false; taskScope.task.moveMode = mode; taskScope.task.isMoving = true; taskScope.task.active = true; // Add move event handler var taskMoveHandler = function(evt) { evt.stopImmediatePropagation(); if (_hasTouch) { evt = mouseOffset.getTouch(evt); } handleMove(evt); }; var moveSmartEvent = smartEvent(taskScope, windowElement, _moveEvents, taskMoveHandler); moveSmartEvent.bind(); // Remove move event handler on mouse up / touch end smartEvent(taskScope, windowElement, _releaseEvents, function(evt) { if (_hasTouch) { evt = mouseOffset.getTouch(evt); } moveSmartEvent.unbind(); disableMoveMode(evt); taskScope.$digest(); }).bindOnce(); setGlobalCursor(getCursor(mode)); }; var disableMoveMode = function() { var getBackgroundElement = taskScope.task.getBackgroundElement(); getBackgroundElement.removeClass('gantt-task-moving'); getBackgroundElement.removeClass('gantt-task-resizing'); if (taskScope.task.originalModel !== undefined) { angular.extend(taskScope.task.originalModel, taskScope.task.model); taskScope.task.model = taskScope.task.originalModel; if (taskScope.task.row.model.id !== taskScope.task.originalRow.model.id) { var targetRow = taskScope.task.row; targetRow.removeTask(taskScope.task.model.id, false, true); taskScope.task.row = taskScope.task.originalRow; targetRow.moveTaskToRow(taskScope.task, false); } delete taskScope.task.originalModel; delete taskScope.task.originalRow; taskScope.$apply(); } taskHasBeenMovedFromAnotherRow = false; taskScope.task.isMoving = false; taskScope.task.active = false; // Stop any active auto scroll clearScrollInterval(); // Set mouse cursor back to default setGlobalCursor(''); // Raise task changed event if (taskHasBeenChanged === true) { // Raise move end event if (taskScope.task.moveMode === 'M') { taskScope.row.rowsManager.gantt.api.tasks.raise.moveEnd(taskScope.task); } else { taskScope.row.rowsManager.gantt.api.tasks.raise.resizeEnd(taskScope.task); } taskHasBeenChanged = false; taskScope.task.row.sortTasks(); // Sort tasks so they have the right z-order taskScope.row.rowsManager.gantt.api.tasks.raise.change(taskScope.task); } taskScope.task.moveMode = undefined; }; // Stop scroll cycle (if running) when scope is destroyed. // This is needed when the task is moved to a new row during scroll because // the old scope will continue to scroll otherwise taskScope.$on('$destroy', function() { taskScope.destroyed = true; clearScrollInterval(); }); if (taskScope.task.isResizing) { taskHasBeenMovedFromAnotherRow = true; enableMoveMode('E', taskScope.task.mouseOffsetX); delete taskScope.task.isResizing; } else if (taskScope.task.isMoving) { // In case the task has been moved to another row a new controller is is created by angular. // Enable the move mode again if this was the case. taskHasBeenMovedFromAnotherRow = true; enableMoveMode('M', taskScope.task.mouseOffsetX); } } }); } }; }]); }()); (function(){ 'use strict'; angular.module('gantt.movable').factory('ganttMovableOptions', [function() { return { initialize: function(options) { options.enabled = options.enabled !== undefined ? options.enabled : true; options.allowMoving = options.allowMoving !== undefined ? !!options.allowMoving : true; options.allowResizing = options.allowResizing !== undefined ? !!options.allowResizing : true; options.allowRowSwitching = options.allowRowSwitching !== undefined ? !!options.allowRowSwitching : true; return options; } }; }]); }()); angular.module('gantt.movable.templates', []).run(['$templateCache', function($templateCache) { }]); //# sourceMappingURL=angular-gantt-movable-plugin.js.map