angular-gantt
Version:
Gantt chart component for AngularJS
494 lines (416 loc) • 27.9 kB
JavaScript
/*
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