tui-calendar
Version:
TOAST UI Calendar
391 lines (328 loc) • 13.3 kB
JavaScript
/* eslint no-shadow: 0 */
/**
* @fileoverview Controller mixin modules for day views.
* @author NHN FE Development Lab <dl_javascript@nhn.com>
*/
;
var util = require('tui-code-snippet');
var Collection = require('../../common/collection');
var array = require('../../common/array');
var datetime = require('../../common/datetime');
var TZDate = require('../../common/timezone').Date;
var SCHEDULE_MIN_DURATION = datetime.MILLISECONDS_SCHEDULE_MIN_DURATION;
/**
* @mixin Base.Week
*/
var Week = {
/**********
* TIME GRID VIEW
**********/
/**
* Make array with start and end times on schedules.
* @this Base.Week
* @param {array[]} matrix - matrix from controller.
* @returns {array[]} starttime, endtime array (exclude first row's schedules)
*/
generateTimeArrayInRow: function(matrix) {
var row,
col,
schedule,
start,
end,
map = [],
cursor = [],
maxColLen = Math.max.apply(null, util.map(matrix, function(col) {
return col.length;
}));
for (col = 1; col < maxColLen; col += 1) {
row = 0;
schedule = util.pick(matrix, row, col);
while (schedule) {
start = schedule.getStarts().getTime() - datetime.millisecondsFrom('minutes', schedule.valueOf().goingDuration);
end = schedule.getEnds().getTime() + datetime.millisecondsFrom('minutes', schedule.valueOf().comingDuration);
if (Math.abs(end - start) < SCHEDULE_MIN_DURATION) {
end += SCHEDULE_MIN_DURATION;
}
cursor.push([start, end]);
row += 1;
schedule = util.pick(matrix, row, col);
}
map.push(cursor);
cursor = [];
}
return map;
},
/**
* Get collision information from list
* @this Base
* @param {array.<number[]>} arr - list to detecting collision. [[start, end], [start, end]]
* @param {number} start - schedule start time that want to detect collisions.
* @param {number} end - schedule end time that want to detect collisions.
* @returns {boolean} target has collide in supplied array?
*/
hasCollide: function(arr, start, end) {
var startStart,
startEnd,
endStart,
endEnd,
getFunc = function(index) {
return function(block) {
return block[index];
};
},
abs = Math.abs,
compare = array.compare.num.asc,
hasCollide;
if (!arr.length) {
return false;
}
startStart = abs(array.bsearch(arr, start, getFunc(0), compare));
startEnd = abs(array.bsearch(arr, start, getFunc(1), compare));
endStart = abs(array.bsearch(arr, end, getFunc(0), compare));
endEnd = abs(array.bsearch(arr, end, getFunc(1), compare));
hasCollide = !(startStart === startEnd && startEnd === endStart && endStart === endEnd);
return hasCollide;
},
/**
* Initialize values to viewmodels for detect real collision at rendering phase.
* @this Base
* @param {array[]} matrices - Matrix data.
*/
getCollides: function(matrices) {
util.forEachArray(matrices, function(matrix) {
var binaryMap,
maxRowLength;
binaryMap = Week.generateTimeArrayInRow(matrix);
maxRowLength = Math.max.apply(null, util.map(matrix, function(row) {
return row.length;
}));
util.forEachArray(matrix, function(row) {
util.forEachArray(row, function(viewModel, col) {
var startTime,
endTime,
hasCollide,
i;
if (!viewModel) {
return;
}
startTime = viewModel.getStarts().getTime();
endTime = viewModel.getEnds().getTime();
if (Math.abs(endTime - startTime) < SCHEDULE_MIN_DURATION) {
endTime += SCHEDULE_MIN_DURATION;
}
startTime -= datetime.millisecondsFrom('minutes', viewModel.valueOf().goingDuration);
endTime += datetime.millisecondsFrom('minutes', viewModel.valueOf().comingDuration);
endTime -= 1;
for (i = (col + 1); i < maxRowLength; i += 1) {
hasCollide = Week.hasCollide(binaryMap[i - 1], startTime, endTime);
if (hasCollide) {
viewModel.hasCollide = true;
break;
}
viewModel.extraSpace += 1;
}
});
});
});
},
/**
* create view model for time view part
* @this Base
* @param {Date} start - start date.
* @param {Date} end - end date.
* @param {Collection} time - view model collection.
* @param {number} hourStart - start hour to be shown
* @param {number} hourEnd - end hour to be shown
* @returns {object} view model for time part.
*/
getViewModelForTimeView: function(start, end, time, hourStart, hourEnd) {
var self = this,
ymdSplitted = this.splitScheduleByDateRange(start, end, time),
result = {};
var _getViewModel = Week._makeGetViewModelFuncForTimeView(hourStart, hourEnd);
util.forEach(ymdSplitted, function(collection, ymd) {
var viewModels = _getViewModel(collection);
var collisionGroups, matrices;
collisionGroups = self.Core.getCollisionGroup(viewModels);
matrices = self.Core.getMatrices(collection, collisionGroups);
self.Week.getCollides(matrices);
result[ymd] = matrices;
});
return result;
},
/**
* make view model function depending on start and end hour
* if time view option has start or end hour condition
* it add filter
* @param {number} hourStart - start hour to be shown
* @param {number} hourEnd - end hour to be shown
* @returns {function} function
*/
_makeGetViewModelFuncForTimeView: function(hourStart, hourEnd) {
if (hourStart === 0 && hourEnd === 24) {
return function(collection) {
return collection.sort(array.compare.schedule.asc);
};
}
return function(collection) {
return collection.find(Week._makeHourRangeFilter(hourStart, hourEnd))
.sort(array.compare.schedule.asc);
};
},
/**
* make a filter function that is not included range of start, end hour
* @param {number} hStart - hour start
* @param {number} hEnd - hour end
* @returns {function} - filtering function
*/
_makeHourRangeFilter: function(hStart, hEnd) {
// eslint-disable-next-line complexity
return function(schedule) {
var ownHourStart = schedule.model.start;
var ownHourEnd = schedule.model.end;
var yyyy = ownHourStart.getFullYear();
var mm = ownHourStart.getMonth();
var dd = ownHourStart.getDate();
var hourStart = new TZDate(yyyy, mm, dd).setHours(hStart);
var hourEnd = new TZDate(yyyy, mm, dd).setHours(hEnd);
return (ownHourStart >= hourStart && ownHourStart < hourEnd) ||
(ownHourEnd > hourStart && ownHourEnd <= hourEnd) ||
(ownHourStart < hourStart && ownHourEnd > hourStart) ||
(ownHourEnd > hourEnd && ownHourStart < hourEnd);
};
},
/**********
* ALLDAY VIEW
**********/
/**
* Set hasMultiDates flag to true and set date ranges for rendering
* @this Base
* @param {Collection} vColl - view model collection
*/
_addMultiDatesInfo: function(vColl) {
vColl.each(function(viewModel) {
var model = viewModel.model;
var start = model.getStarts();
var end = model.getEnds();
viewModel.hasMultiDates = true;
viewModel.renderStarts = datetime.start(start);
viewModel.renderEnds = datetime.renderEnd(start, end);
});
},
/**
* create view model for allday view part
* @this Base
* @param {Date} start start date.
* @param {Date} end end date.
* @param {Collection} viewModelColl - allday schedule viewModel viewModels.
* @returns {object} allday viewModel.
*/
getViewModelForAlldayView: function(start, end, viewModelColl) {
var ctrlCore = this.Core,
ctrlWeek = this.Week,
viewModels,
collisionGroups,
matrices;
if (!viewModelColl || !viewModelColl.length) {
return [];
}
ctrlWeek._addMultiDatesInfo(viewModelColl);
ctrlCore.limitRenderRange(start, end, viewModelColl);
viewModels = viewModelColl.sort(array.compare.schedule.asc);
collisionGroups = ctrlCore.getCollisionGroup(viewModels);
matrices = ctrlCore.getMatrices(viewModelColl, collisionGroups);
ctrlCore.positionViewModels(start, end, matrices);
return matrices;
},
/**********
* READ
**********/
/**
* Populate schedules in date range.
* @this Base
* @param {Date} start start date.
* @param {Date} end end date.
* @param {Array.<object>} panels - schedule panels like 'milestone', 'task', 'allday', 'time'
* @param {function[]} [andFilters] - optional filters to applying search query
* @param {Object} options - week view options
* @returns {object} schedules grouped by dates.
*/
findByDateRange: function(start, end, panels, andFilters, options) {
var ctrlCore = this.Core,
ctrlWeek = this.Week,
filter = ctrlCore.getScheduleInDateRangeFilter(start, end),
scheduleTypes = util.pluck(panels, 'name'),
hourStart = util.pick(options, 'hourStart'),
hourEnd = util.pick(options, 'hourEnd'),
modelColl,
group;
andFilters = andFilters || [];
filter = Collection.and.apply(null, [filter].concat(andFilters));
modelColl = this.schedules.find(filter);
modelColl = ctrlCore.convertToViewModel(modelColl);
group = modelColl.groupBy(scheduleTypes, this.groupFunc);
util.forEach(panels, function(panel) {
var name = panel.name;
if (panel.type === 'daygrid') {
group[name] = ctrlWeek.getViewModelForAlldayView(start, end, group[name]);
} else if (panel.type === 'timegrid') {
group[name] = ctrlWeek.getViewModelForTimeView(start, end, group[name], hourStart, hourEnd);
}
});
return group;
},
/* eslint max-nested-callbacks: 0 */
/**
* Make exceed date information
* @param {number} maxCount - exceed schedule count
* @param {Array} eventsInDateRange - matrix of ScheduleViewModel
* @param {Array.<TZDate>} range - date range of one week
* @returns {object} exceedDate
*/
getExceedDate: function(maxCount, eventsInDateRange, range) {
var exceedDate = {};
util.forEach(range, function(date) {
var ymd = datetime.format(date, 'YYYYMMDD');
exceedDate[ymd] = 0;
});
util.forEach(eventsInDateRange, function(matrix) {
util.forEach(matrix, function(column) {
util.forEach(column, function(viewModel) {
var period;
if (!viewModel || viewModel.top < maxCount) {
return;
}
period = datetime.range(
viewModel.getStarts(),
viewModel.getEnds(),
datetime.MILLISECONDS_PER_DAY
);
util.forEach(period, function(date) {
var ymd = datetime.format(date, 'YYYYMMDD');
exceedDate[ymd] += 1;
});
});
});
});
return exceedDate;
},
/**
* Exclude overflow schedules from matrices
* @param {array} matrices - The matrices for schedule placing.
* @param {number} visibleScheduleCount - maximum visible count on panel
* @returns {array} - The matrices for schedule placing except overflowed schedules.
*/
excludeExceedSchedules: function(matrices, visibleScheduleCount) {
return matrices.map(function(matrix) {
return matrix.map(function(row) {
if (row.length > visibleScheduleCount) {
return row.filter(function(item) {
return item.top < visibleScheduleCount;
}, this);
}
return row;
}, this);
}, this);
}
};
module.exports = Week;