tui-calendar
Version:
TOAST UI Calendar
465 lines (401 loc) • 12.8 kB
JavaScript
/**
* @fileoverview Guide element controller for creation, resize in month view
* @author NHN FE Development Lab <dl_javascript@nhn.com>
*/
'use strict';
var util = require('tui-code-snippet');
var config = require('../../config'),
domutil = require('../../common/domutil'),
datetime = require('../../common/datetime'),
TZDate = require('../../common/timezone').Date,
tmpl = require('./guide.hbs');
var mmax = Math.max,
mmin = Math.min,
mabs = Math.abs,
mfloor = Math.floor;
/**
* @constructor
* @param {object} [options] - options
* @param {boolean} [options.useHandle=false] - whether displaying resize handle on
* guide element?
* @param {boolean} [options.isResizeMode=false] - whether resize mode?
* @param {Month} monthView - Month view instance
*/
function MonthGuide(options, monthView) {
/**
* @type {object}
*/
this.options = util.extend({
top: 0,
height: '20px',
bgColor: '#f7ca88',
label: 'New event',
isResizeMode: false,
isCreationMode: false,
styles: this._getStyles(monthView.controller.theme)
}, options);
/**
* @type {Month}
*/
this.view = monthView;
/**
* @type {WeekdayInMonth[]}
*/
this.weeks = monthView.children.sort(function(a, b) {
return util.stamp(a) - util.stamp(b);
});
/**
* @type {number}
*/
this.days = monthView.children.single().getRenderDateRange().length;
/**
* start coordinate of guide effect. (x, y) (days, weeks) effect can't
* start lower than this coordinate.
* @type {number[]}
*/
this.startCoord = [0, 0];
/**
* @type {Object.<string, HTMLElement>}
*/
this.guideElements = {};
/**
* horizontal grid information
* @type {Object}
*/
this.grids = monthView.grids;
}
/**
* Destructor
*/
MonthGuide.prototype.destroy = function() {
this.clear();
this.options = this.view = this.weeks = this.days =
this.startCoord = this.guideElements = null;
};
MonthGuide.prototype.clearGuideElement = function() {
this.destroy();
};
/**
* Get ratio value in week.
* @param {number} value - value for calc ratio in week
* @returns {number} percent value
*/
MonthGuide.prototype._getRatioValueInWeek = function(value) {
var grid = this.grids[value] || {left: 100};
return grid.left;
};
/**
* Create guide element
* @returns {HTMLElement} guide element
*/
MonthGuide.prototype._createGuideElement = function() {
var guide = document.createElement('div');
guide.innerHTML = tmpl(this.options);
return guide.firstChild;
};
/**
* Get guide element. if not exist then create one
* @param {number} y - y coordinate
* @returns {?HTMLElement} guide element
*/
MonthGuide.prototype._getGuideElement = function(y) {
var guideElements = this.guideElements,
guide = guideElements[y],
weekdayView = this.weeks[y],
container;
if (!weekdayView) {
return null;
}
if (!guide) {
guide = this._createGuideElement();
container = weekdayView.container;
container.appendChild(guide);
guideElements[y] = guide;
}
return guide;
};
/**
* Get coordinate by supplied date in month
* @param {TZDate} date - date to find coordinate
* @returns {number[]} coordinate (x, y)
*/
MonthGuide.prototype._getCoordByDate = function(date) {
var WEEKEND_DAYS = 2;
var weeks = this.weeks;
var isWorkWeek = util.pick(this.view, 'options', 'workweek');
var days = isWorkWeek ? this.days + WEEKEND_DAYS : this.days;
var getIdxFromDiff = function(d1, d2) {
return mfloor(datetime.millisecondsTo('day', mabs(d2 - d1)));
},
monthStart = datetime.start(weeks[0].options.renderStartDate),
isBefore = date < monthStart,
start = new TZDate(monthStart),
end = new TZDate(monthStart).addDate(isBefore ? -days : days).addDate(-1),
x = getIdxFromDiff(date, start),
y = 0;
while (!datetime.isBetweenWithDate(date, start, end)) {
start.addDate(isBefore ? -days : days);
end = new TZDate(start).addDate(days - 1);
x = getIdxFromDiff(date, start);
y += (isBefore ? -1 : 1);
}
return [x, y];
};
/**
* Get limited coordinate by supplied coordinates
* @param {number[]} coord - coordinate need to limit
* @param {number[]} [min] - minimum limitation of coordinate
* @param {number[]} [max] - maximum limitation of coordinate
* @returns {number[]} limited coordiate
*/
MonthGuide.prototype._getLimitedCoord = function(coord, min, max) {
var toIndex = 1,
x = coord[0],
y = coord[1],
result;
min = min || [0, 0];
max = max || [this.days - toIndex, this.weeks.length - toIndex];
if (y < min[1]) {
result = min.slice(0);
} else if (y > max[1]) {
result = max.slice(0);
} else {
x = mmax(min[0], x);
x = mmin(max[0], x);
result = [x, y];
}
return result;
};
/**
* Prepare guide element modification
* @param {object} dragStartEvent - dragStart schedule data from *guide
*/
MonthGuide.prototype.start = function(dragStartEvent) {
var opt = this.options,
target = dragStartEvent.target,
model = dragStartEvent.model,
x = dragStartEvent.x,
y = dragStartEvent.y,
renderMonth = new TZDate(this.view.options.renderMonth),
temp;
if (opt.isCreationMode) {
if (model && !datetime.isSameMonth(renderMonth, model.start)) {
model.start.setMonth(renderMonth.getMonth());
model.start.setDate(1);
model.end.setMonth(renderMonth.getMonth());
model.end.setDate(1);
}
} else {
temp = this._getCoordByDate(model.getStarts());
x = temp[0];
y = temp[1];
util.extend(this.options, {
top: parseInt(target.style.top, 10) + 'px',
height: parseInt(target.style.height, 10) + 'px',
label: model.title
}, model);
}
if (util.isUndefined(x) || util.isUndefined(y)) {
temp = this._getCoordByDate(model.getStarts());
x = temp[0];
y = temp[1];
}
this.startCoord = [x, y];
this.update(x, y);
};
/**
* Data for update several guide elements
* @typedef UpdateIndication
* @type {object}
* @property {HTMLElement} guide - guide element
* @property {number} left - left style value
* @property {number} width - width style value
* @property {boolean} [exceedL=false] - whether schedule is exceeded past weeks?
* @property {boolean} [exceedR=false] - whether schedule is exceeded future weeks?
*/
/**
* Modify HTML element that uses for guide element
* @param {UpdateIndication[]} inds - indication of update severel guide element
*/
MonthGuide.prototype._updateGuides = function(inds) {
util.forEach(inds, function(ind) {
var guide = ind.guide,
exceedLClass = config.classname('month-exceed-left'),
exceedRClass = config.classname('month-exceed-right');
guide.style.display = 'block';
guide.style.left = ind.left + '%';
guide.style.width = ind.width + '%';
if (ind.exceedL) {
domutil.addClass(guide, exceedLClass);
} else {
domutil.removeClass(guide, exceedLClass);
}
if (ind.exceedR) {
domutil.addClass(guide, exceedRClass);
} else {
domutil.removeClass(guide, exceedRClass);
}
});
};
/**
* Get guide element indicate for origin week
* @param {number[]} startCoord - drag start coordinate
* @param {number[]} mouseCoord - mouse coordinate
* @returns {object} indicate
*/
MonthGuide.prototype._getOriginIndicate = function(startCoord, mouseCoord) {
var left = mmin(startCoord[0], mouseCoord[0]),
right = mmax(startCoord[0], mouseCoord[0]) + 1,
exceedL, exceedR;
if (mouseCoord[1] > startCoord[1]) {
left = startCoord[0];
right = this.days;
exceedR = true;
} else if (mouseCoord[1] < startCoord[1]) {
left = 0;
right = startCoord[0] + 1;
exceedL = true;
}
return {
left: this._getRatioValueInWeek(left),
width: this._getRatioValueInWeek(right) -
this._getRatioValueInWeek(left),
exceedL: exceedL,
exceedR: exceedR
};
};
/**
* Get guide element indicate for week related with mouse position
* @param {number[]} startCoord - drag start coordinate
* @param {number[]} mouseCoord - mouse coordinate
* @returns {object} indicate
*/
MonthGuide.prototype._getMouseIndicate = function(startCoord, mouseCoord) {
var left = mouseCoord[0],
right = mouseCoord[0] + 1,
exceedL, exceedR;
if (mouseCoord[1] > startCoord[1]) {
left = 0;
exceedL = true;
} else if (mouseCoord[1] < startCoord[1]) {
right = this.days;
exceedR = true;
}
return {
left: this._getRatioValueInWeek(left),
width: this._getRatioValueInWeek(right) -
this._getRatioValueInWeek(left),
exceedL: exceedL,
exceedR: exceedR
};
};
/**
* Get guide element indicate for contained weeks
* @returns {object} indicate
*/
MonthGuide.prototype._getContainIndicate = function() {
return {
left: 0,
width: 100,
exceedL: true,
exceedR: true
};
};
/**
* Remove several guide element that supplied by parameter
* @param {number[]} yCoords - array of y coordinate to remove guide element
*/
MonthGuide.prototype._removeGuideElements = function(yCoords) {
var guides = this.guideElements;
util.forEach(yCoords, function(y) {
domutil.remove(guides[y]);
delete guides[y];
});
};
/**
* Get excluded numbers in range
* @param {number[]} range - the range. value must be sequential.
* @param {number[]} numbers - numbers to check
* @returns {number[]} excluded numbers
*/
MonthGuide.prototype._getExcludesInRange = function(range, numbers) {
var min = mmin.apply(null, range),
max = mmax.apply(null, range),
excludes = [];
util.forEach(numbers, function(num) {
num = parseInt(num, 10);
if (num < min || num > max) {
excludes.push(num);
}
});
return excludes;
};
/**
* Update guide elements by coordinate in month grid from mousemove event
* @param {number} x - x coordinate
* @param {number} y - y coordinate
*/
MonthGuide.prototype.update = function(x, y) {
var self = this,
startCoord = this.startCoord,
mouseCoord = [x, y],
limitedCoord = this.options.isResizeMode ?
this._getLimitedCoord(mouseCoord, startCoord) : mouseCoord,
renderedYIndex = util.keys(this.guideElements),
yCoordsToUpdate = util.range(
mmin(startCoord[1], limitedCoord[1]),
mmax(startCoord[1], limitedCoord[1]) + 1
),
yCoordsToRemove = this._getExcludesInRange(
yCoordsToUpdate,
renderedYIndex
),
renderIndication = {};
this._removeGuideElements(yCoordsToRemove);
util.forEach(yCoordsToUpdate, function(guideYCoord) {
var guide = self._getGuideElement(guideYCoord),
indicate;
if (!guide) {
return;
}
if (guideYCoord === startCoord[1]) {
indicate = self._getOriginIndicate(startCoord, limitedCoord);
} else if (guideYCoord === mouseCoord[1]) {
indicate = self._getMouseIndicate(startCoord, mouseCoord);
} else {
indicate = self._getContainIndicate();
}
renderIndication[guideYCoord] = util.extend({
guide: guide
}, indicate);
});
this._updateGuides(renderIndication);
};
/**
* Clear all guide elements
*/
MonthGuide.prototype.clear = function() {
util.forEach(this.guideElements, function(element) {
domutil.remove(element);
});
this.guideElements = {};
};
/**
* Get the styles from theme
* @param {Theme} theme - theme instance
* @returns {object} styles - styles object
*/
MonthGuide.prototype._getStyles = function(theme) {
var styles = {};
if (theme) {
styles.border = theme.common.creationGuide.border;
styles.backgroundColor = theme.common.creationGuide.backgroundColor;
styles.scheduleHeight = theme.month.schedule.height;
styles.scheduleGutter = theme.month.schedule.marginTop;
styles.marginLeft = theme.month.schedule.marginLeft;
styles.marginRight = theme.month.schedule.marginRight;
styles.borderRadius = theme.month.schedule.borderRadius;
}
return styles;
};
module.exports = MonthGuide;