UNPKG

angular-gantt

Version:

Gantt chart component for AngularJS

466 lines (406 loc) 17.9 kB
(function(){ 'use strict'; /** * Calendar factory is used to define working periods, non working periods, and other specific period of time, * and retrieve effective timeFrames for each day of the gantt. */ angular.module('gantt').factory('GanttCalendar', ['$filter', 'moment', function($filter, moment) { /** * TimeFrame represents time frame in any day. parameters are given using options object. * * @param {moment|string} start start of timeFrame. If a string is given, it will be parsed as a moment. * @param {moment|string} end end of timeFrame. If a string is given, it will be parsed as a moment. * @param {boolean} working is this timeFrame flagged as working. * @param {boolean} magnet is this timeFrame will magnet. * @param {boolean} default is this timeFrame will be used as default. * @param {color} css color attached to this timeFrame. * @param {string} classes css classes attached to this timeFrame. * * @constructor */ var TimeFrame = function(options) { if (options === undefined) { options = {}; } this.start = options.start; this.end = options.end; this.working = options.working; this.magnet = options.magnet !== undefined ? options.magnet : true; this.default = options.default; this.color = options.color; this.classes = options.classes; this.internal = options.internal; }; TimeFrame.prototype.updateView = function() { if (this.$element) { var cssStyles = {}; if (this.left !== undefined) { cssStyles.left = this.left + 'px'; } else { cssStyles.left = ''; } if (this.width !== undefined) { cssStyles.width = this.width + 'px'; } else { cssStyles.width = ''; } if (this.color !== undefined) { cssStyles['background-color'] = this.color; } else { cssStyles['background-color'] = ''; } this.$element.css(cssStyles); var classes = ['gantt-timeframe' + (this.working ? '' : '-non') + '-working']; if (this.classes) { classes = classes.concat(this.classes); } for (var i = 0, l = classes.length; i < l; i++) { this.$element.toggleClass(classes[i], true); } } }; TimeFrame.prototype.getDuration = function() { if (this.end !== undefined && this.start !== undefined) { return this.end.diff(this.start, 'milliseconds'); } }; TimeFrame.prototype.clone = function() { return new TimeFrame(this); }; /** * TimeFrameMapping defines how timeFrames will be placed for each days. parameters are given using options object. * * @param {function} func a function with date parameter, that will be evaluated for each distinct day of the gantt. * this function must return an array of timeFrame names to apply. * @constructor */ var TimeFrameMapping = function(func) { this.func = func; }; TimeFrameMapping.prototype.getTimeFrames = function(date) { var ret = this.func(date); if (!(ret instanceof Array)) { ret = [ret]; } return ret; }; TimeFrameMapping.prototype.clone = function() { return new TimeFrameMapping(this.func); }; /** * A DateFrame is date range that will use a specific TimeFrameMapping, configured using a function (evaluator), * a date (date) or a date range (start, end). parameters are given using options object. * * @param {function} evaluator a function with date parameter, that will be evaluated for each distinct day of the gantt. * this function must return a boolean representing matching of this dateFrame or not. * @param {moment} date date of dateFrame. * @param {moment} start start of date frame. * @param {moment} end end of date frame. * @param {array} targets array of TimeFrameMappings/TimeFrames names to use for this date frame. * @param {boolean} default is this dateFrame will be used as default. * @constructor */ var DateFrame = function(options) { this.evaluator = options.evaluator; if (options.date) { this.start = moment(options.date).startOf('day'); this.end = moment(options.date).endOf('day'); } else { this.start = options.start; this.end = options.end; } if (options.targets instanceof Array) { this.targets = options.targets; } else { this.targets = [options.targets]; } this.default = options.default; }; DateFrame.prototype.dateMatch = function(date) { if (this.evaluator) { return this.evaluator(date); } else if (this.start && this.end) { return date >= this.start && date <= this.end; } else { return false; } }; DateFrame.prototype.clone = function() { return new DateFrame(this); }; /** * Register TimeFrame, TimeFrameMapping and DateMapping objects into Calendar object, * and use Calendar#getTimeFrames(date) function to retrieve effective timeFrames for a specific day. * * @constructor */ var Calendar = function() { this.timeFrames = {}; this.timeFrameMappings = {}; this.dateFrames = {}; }; /** * Remove all objects. */ Calendar.prototype.clear = function() { this.timeFrames = {}; this.timeFrameMappings = {}; this.dateFrames = {}; }; /** * Register TimeFrame objects. * * @param {object} timeFrames with names of timeFrames for keys and TimeFrame objects for values. */ Calendar.prototype.registerTimeFrames = function(timeFrames) { angular.forEach(timeFrames, function(timeFrame, name) { this.timeFrames[name] = new TimeFrame(timeFrame); }, this); }; /** * Removes TimeFrame objects. * * @param {array} timeFrames names of timeFrames to remove. */ Calendar.prototype.removeTimeFrames = function(timeFrames) { angular.forEach(timeFrames, function(name) { delete this.timeFrames[name]; }, this); }; /** * Remove all TimeFrame objects. */ Calendar.prototype.clearTimeFrames = function() { this.timeFrames = {}; }; /** * Register TimeFrameMapping objects. * * @param {object} mappings object with names of timeFrames mappings for keys and TimeFrameMapping objects for values. */ Calendar.prototype.registerTimeFrameMappings = function(mappings) { angular.forEach(mappings, function(timeFrameMapping, name) { this.timeFrameMappings[name] = new TimeFrameMapping(timeFrameMapping); }, this); }; /** * Removes TimeFrameMapping objects. * * @param {array} mappings names of timeFrame mappings to remove. */ Calendar.prototype.removeTimeFrameMappings = function(mappings) { angular.forEach(mappings, function(name) { delete this.timeFrameMappings[name]; }, this); }; /** * Removes all TimeFrameMapping objects. */ Calendar.prototype.clearTimeFrameMappings = function() { this.timeFrameMappings = {}; }; /** * Register DateFrame objects. * * @param {object} dateFrames object with names of dateFrames for keys and DateFrame objects for values. */ Calendar.prototype.registerDateFrames = function(dateFrames) { angular.forEach(dateFrames, function(dateFrame, name) { this.dateFrames[name] = new DateFrame(dateFrame); }, this); }; /** * Remove DateFrame objects. * * @param {array} mappings names of date frames to remove. */ Calendar.prototype.removeDateFrames = function(dateFrames) { angular.forEach(dateFrames, function(name) { delete this.dateFrames[name]; }, this); }; /** * Removes all DateFrame objects. */ Calendar.prototype.clearDateFrames = function() { this.dateFrames = {}; }; var filterDateFrames = function(inputDateFrames, date) { var dateFrames = []; angular.forEach(inputDateFrames, function(dateFrame) { if (dateFrame.dateMatch(date)) { dateFrames.push(dateFrame); } }); if (dateFrames.length === 0) { angular.forEach(inputDateFrames, function(dateFrame) { if (dateFrame.default) { dateFrames.push(dateFrame); } }); } return dateFrames; }; /** * Retrieves TimeFrame objects for a given date, using whole configuration for this Calendar object. * * @param {moment} date * * @return {array} an array of TimeFrame objects. */ Calendar.prototype.getTimeFrames = function(date) { var timeFrames = []; var dateFrames = filterDateFrames(this.dateFrames, date); angular.forEach(dateFrames, function(dateFrame) { if (dateFrame !== undefined) { angular.forEach(dateFrame.targets, function(timeFrameMappingName) { var timeFrameMapping = this.timeFrameMappings[timeFrameMappingName]; if (timeFrameMapping !== undefined) { // If a timeFrame mapping is found timeFrames.push(timeFrameMapping.getTimeFrames()); } else { // If no timeFrame mapping is found, try using direct timeFrame var timeFrame = this.timeFrames[timeFrameMappingName]; if (timeFrame !== undefined) { timeFrames.push(timeFrame); } } }, this); } }, this); var dateYear = date.year(); var dateMonth = date.month(); var dateDate = date.date(); var validatedTimeFrames = []; if (timeFrames.length === 0) { angular.forEach(this.timeFrames, function(timeFrame) { if (timeFrame.default) { timeFrames.push(timeFrame); } }); } angular.forEach(timeFrames, function(timeFrame) { timeFrame = timeFrame.clone(); if (timeFrame.start !== undefined) { timeFrame.start.year(dateYear); timeFrame.start.month(dateMonth); timeFrame.start.date(dateDate); } if (timeFrame.end !== undefined) { timeFrame.end.year(dateYear); timeFrame.end.month(dateMonth); timeFrame.end.date(dateDate); if (moment(timeFrame.end).startOf('day') === timeFrame.end) { timeFrame.end.add(1, 'day'); } } validatedTimeFrames.push(timeFrame); }); return validatedTimeFrames; }; /** * Solve timeFrames. * * Smaller timeFrames have priority over larger one. * * @param {array} timeFrames Array of timeFrames to solve * @param {moment} startDate * @param {moment} endDate */ Calendar.prototype.solve = function(timeFrames, startDate, endDate) { var color; var classes; var minDate; var maxDate; angular.forEach(timeFrames, function(timeFrame) { if (minDate === undefined || minDate > timeFrame.start) { minDate = timeFrame.start; } if (maxDate === undefined || maxDate < timeFrame.end) { maxDate = timeFrame.end; } if (color === undefined && timeFrame.color) { color = timeFrame.color; } if (timeFrame.classes !== undefined) { if (classes === undefined) { classes = []; } classes = classes.concat(timeFrame.classes); } }); if (startDate === undefined) { startDate = minDate; } if (endDate === undefined) { endDate = maxDate; } var solvedTimeFrames = [new TimeFrame({start: startDate, end: endDate, internal: true})]; timeFrames = $filter('filter')(timeFrames, function(timeFrame) { return (timeFrame.start === undefined || timeFrame.start < endDate) && (timeFrame.end === undefined || timeFrame.end > startDate); }); angular.forEach(timeFrames, function(timeFrame) { if (!timeFrame.start) { timeFrame.start = startDate; } if (!timeFrame.end) { timeFrame.end = endDate; } }); var orderedTimeFrames = $filter('orderBy')(timeFrames, function(timeFrame) { return -timeFrame.getDuration(); }); angular.forEach(orderedTimeFrames, function(timeFrame) { var tmpSolvedTimeFrames = solvedTimeFrames.slice(); var i=0; var dispatched = false; var treated = false; angular.forEach(solvedTimeFrames, function(solvedTimeFrame) { if (!treated) { if (!timeFrame.end && !timeFrame.start) { // timeFrame is infinite. tmpSolvedTimeFrames.splice(i, 0, timeFrame); treated = true; dispatched = false; } else if (timeFrame.end > solvedTimeFrame.start && timeFrame.start < solvedTimeFrame.end) { // timeFrame is included in this solvedTimeFrame. // solvedTimeFrame:|ssssssssssssssssssssssssssssssssss| // timeFrame: |tttttt| // result:|sssssssss|tttttt|sssssssssssssssss| var newSolvedTimeFrame = solvedTimeFrame.clone(); solvedTimeFrame.end = moment(timeFrame.start); newSolvedTimeFrame.start = moment(timeFrame.end); tmpSolvedTimeFrames.splice(i + 1, 0, timeFrame.clone(), newSolvedTimeFrame); treated = true; dispatched = false; } else if (!dispatched && timeFrame.start < solvedTimeFrame.end) { // timeFrame is dispatched on two solvedTimeFrame. // First part // solvedTimeFrame:|sssssssssssssssssssssssssssssssssss|s+1;s+1;s+1;s+1;s+1;s+1| // timeFrame: |tttttt| // result:|sssssssssssssssssssssssssssssss|tttttt|;s+1;s+1;s+1;s+1;s+1| solvedTimeFrame.end = moment(timeFrame.start); tmpSolvedTimeFrames.splice(i + 1, 0, timeFrame.clone()); dispatched = true; } else if (dispatched && timeFrame.end > solvedTimeFrame.start) { // timeFrame is dispatched on two solvedTimeFrame. // Second part solvedTimeFrame.start = moment(timeFrame.end); dispatched = false; treated = true; } i++; } }); solvedTimeFrames = tmpSolvedTimeFrames; }); solvedTimeFrames = $filter('filter')(solvedTimeFrames, function(timeFrame) { return !timeFrame.internal && (timeFrame.start === undefined || timeFrame.start < endDate) && (timeFrame.end === undefined || timeFrame.end > startDate); }); return solvedTimeFrames; }; return Calendar; }]); }());