mirror of
https://github.com/wassname/fullcalendar.git
synced 2026-06-27 16:10:13 +08:00
921 lines
23 KiB
JavaScript
921 lines
23 KiB
JavaScript
setDefaults({
|
|
allDaySlot: true,
|
|
allDayText: 'all-day',
|
|
firstHour: 6,
|
|
slotMinutes: 30,
|
|
defaultEventMinutes: 120,
|
|
axisFormat: 'h(:mm)tt',
|
|
timeFormat: {
|
|
agenda: 'h:mm{ - h:mm}'
|
|
},
|
|
dragOpacity: {
|
|
agenda: .5
|
|
},
|
|
minTime: 0,
|
|
maxTime: 24
|
|
});
|
|
|
|
|
|
// TODO: make it work in quirks mode (event corners, all-day height)
|
|
// TODO: test liquid width, especially in IE6
|
|
|
|
function ResourceView(element, calendar, viewName) {
|
|
var t = this;
|
|
|
|
|
|
// exports
|
|
t.renderResource = renderResource;
|
|
t.setWidth = setWidth;
|
|
t.setHeight = setHeight;
|
|
t.afterRender = afterRender;
|
|
t.defaultEventEnd = defaultEventEnd;
|
|
t.timePosition = timePosition;
|
|
t.getIsCellAllDay = getIsCellAllDay;
|
|
t.allDayRow = getAllDayRow;
|
|
t.getCoordinateGrid = function() { return coordinateGrid; }; // specifically for AgendaEventRenderer
|
|
t.getHoverListener = function() { return hoverListener; };
|
|
t.colLeft = colLeft;
|
|
t.colRight = colRight;
|
|
t.colContentLeft = colContentLeft;
|
|
t.colContentRight = colContentRight;
|
|
t.getDaySegmentContainer = function() { return daySegmentContainer; };
|
|
t.getSlotSegmentContainer = function() { return slotSegmentContainer; };
|
|
t.getMinMinute = function() { return minMinute; };
|
|
t.getMaxMinute = function() { return maxMinute; };
|
|
t.getSlotContainer = function() { return slotContainer; };
|
|
t.getRowCnt = function() { return 1; };
|
|
t.getColCnt = function() { return colCnt; };
|
|
t.getColWidth = function() { return colWidth; };
|
|
t.getSnapHeight = function() { return snapHeight; };
|
|
t.getSnapMinutes = function() { return snapMinutes; };
|
|
t.defaultSelectionEnd = defaultSelectionEnd;
|
|
t.renderDayOverlay = renderDayOverlay;
|
|
t.renderSelection = renderSelection;
|
|
t.clearSelection = clearSelection;
|
|
t.reportDayClick = reportDayClick; // selection mousedown hack
|
|
t.dragStart = dragStart;
|
|
t.dragStop = dragStop;
|
|
t.getResources = calendar.fetchResources;
|
|
|
|
// imports
|
|
View.call(t, element, calendar, viewName);
|
|
OverlayManager.call(t);
|
|
SelectionManager.call(t);
|
|
ResourceEventRenderer.call(t);
|
|
var opt = t.opt;
|
|
var trigger = t.trigger;
|
|
var renderOverlay = t.renderOverlay;
|
|
var clearOverlays = t.clearOverlays;
|
|
var reportSelection = t.reportSelection;
|
|
var unselect = t.unselect;
|
|
var slotSegHtml = t.slotSegHtml;
|
|
var cellToDate = t.cellToDate;
|
|
var dateToCell = t.dateToCell;
|
|
var rangeToSegments = t.rangeToSegments;
|
|
var formatDate = calendar.formatDate;
|
|
|
|
|
|
// locals
|
|
|
|
var dayTable;
|
|
var dayHead;
|
|
var dayHeadCells;
|
|
var dayBody;
|
|
var dayBodyCells;
|
|
var dayBodyCellInners;
|
|
var dayBodyCellContentInners;
|
|
var dayBodyFirstCell;
|
|
var dayBodyFirstCellStretcher;
|
|
var slotLayer;
|
|
var daySegmentContainer;
|
|
var allDayTable;
|
|
var allDayRow;
|
|
var slotScroller;
|
|
var slotContainer;
|
|
var slotSegmentContainer;
|
|
var slotTable;
|
|
var slotTableFirstInner;
|
|
var selectionHelper;
|
|
|
|
var viewWidth;
|
|
var viewHeight;
|
|
var axisWidth;
|
|
var colWidth;
|
|
var gutterWidth;
|
|
var slotHeight; // TODO: what if slotHeight changes? (see issue 650)
|
|
|
|
var snapMinutes;
|
|
var snapRatio; // ratio of number of "selection" slots to normal slots. (ex: 1, 2, 4)
|
|
var snapHeight; // holds the pixel hight of a "selection" slot
|
|
|
|
var colCnt;
|
|
var slotCnt;
|
|
var coordinateGrid;
|
|
var hoverListener;
|
|
var colPositions;
|
|
var colContentPositions;
|
|
var slotTopCache = {};
|
|
|
|
var tm;
|
|
var rtl;
|
|
var minMinute, maxMinute;
|
|
var colFormat;
|
|
var showWeekNumbers;
|
|
var weekNumberTitle;
|
|
var weekNumberFormat;
|
|
var resources = t.getResources;
|
|
|
|
|
|
/* Rendering
|
|
-----------------------------------------------------------------------------*/
|
|
|
|
|
|
disableTextSelection(element.addClass('fc-agenda'));
|
|
|
|
|
|
function renderResource(c) {
|
|
colCnt = c;
|
|
updateOptions();
|
|
|
|
if (!dayTable) { // first time rendering?
|
|
buildSkeleton(); // builds day table, slot area, events containers
|
|
}
|
|
else {
|
|
buildDayTable(); // rebuilds day table
|
|
}
|
|
}
|
|
|
|
|
|
function updateOptions() {
|
|
|
|
tm = opt('theme') ? 'ui' : 'fc';
|
|
rtl = opt('isRTL');
|
|
minMinute = parseTime(opt('minTime'));
|
|
maxMinute = parseTime(opt('maxTime'));
|
|
colFormat = opt('columnFormat');
|
|
|
|
// week # options. (TODO: bad, logic also in other views)
|
|
showWeekNumbers = opt('weekNumbers');
|
|
weekNumberTitle = opt('weekNumberTitle');
|
|
if (opt('weekNumberCalculation') != 'iso') {
|
|
weekNumberFormat = "w";
|
|
}
|
|
else {
|
|
weekNumberFormat = "W";
|
|
}
|
|
|
|
snapMinutes = opt('snapMinutes') || opt('slotMinutes');
|
|
}
|
|
|
|
|
|
|
|
/* Build DOM
|
|
-----------------------------------------------------------------------*/
|
|
|
|
|
|
function buildSkeleton() {
|
|
var headerClass = tm + "-widget-header";
|
|
var contentClass = tm + "-widget-content";
|
|
var s;
|
|
var d;
|
|
var i;
|
|
var maxd;
|
|
var minutes;
|
|
var slotNormal = opt('slotMinutes') % 15 === 0;
|
|
|
|
buildDayTable();
|
|
|
|
slotLayer =
|
|
$("<div style='position:absolute;z-index:2;left:0;width:100%'/>")
|
|
.appendTo(element);
|
|
|
|
if (opt('allDaySlot')) {
|
|
|
|
daySegmentContainer =
|
|
$("<div class='fc-event-container' style='position:absolute;z-index:8;top:0;left:0'/>")
|
|
.appendTo(slotLayer);
|
|
|
|
s =
|
|
"<table style='width:100%' class='fc-agenda-allday' cellspacing='0'>" +
|
|
"<tr>" +
|
|
"<th class='" + headerClass + " fc-agenda-axis'>" + opt('allDayText') + "</th>" +
|
|
"<td>" +
|
|
"<div class='fc-day-content'><div style='position:relative'/></div>" +
|
|
"</td>" +
|
|
"<th class='" + headerClass + " fc-agenda-gutter'> </th>" +
|
|
"</tr>" +
|
|
"</table>";
|
|
allDayTable = $(s).appendTo(slotLayer);
|
|
allDayRow = allDayTable.find('tr');
|
|
|
|
dayBind(allDayRow.find('td'));
|
|
|
|
slotLayer.append(
|
|
"<div class='fc-agenda-divider " + headerClass + "'>" +
|
|
"<div class='fc-agenda-divider-inner'/>" +
|
|
"</div>"
|
|
);
|
|
|
|
}else{
|
|
|
|
daySegmentContainer = $([]); // in jQuery 1.4, we can just do $()
|
|
|
|
}
|
|
|
|
slotScroller =
|
|
$("<div style='position:absolute;width:100%;overflow-x:hidden;overflow-y:auto'/>")
|
|
.appendTo(slotLayer);
|
|
|
|
slotContainer =
|
|
$("<div style='position:relative;width:100%;overflow:hidden'/>")
|
|
.appendTo(slotScroller);
|
|
|
|
slotSegmentContainer =
|
|
$("<div class='fc-event-container' style='position:absolute;z-index:8;top:0;left:0'/>")
|
|
.appendTo(slotContainer);
|
|
|
|
s =
|
|
"<table class='fc-agenda-slots' style='width:100%' cellspacing='0'>" +
|
|
"<tbody>";
|
|
d = zeroDate();
|
|
maxd = d.clone().add('m', maxMinute);
|
|
d.add('m', minMinute);
|
|
slotCnt = 0;
|
|
for (i=0; d < maxd; i++) {
|
|
minutes = d.getMinutes();
|
|
s +=
|
|
"<tr class='fc-slot" + i + ' ' + (!minutes ? '' : 'fc-minor') + "'>" +
|
|
"<th class='fc-agenda-axis " + headerClass + "'>" +
|
|
((!slotNormal || !minutes) ? formatDate(d, opt('axisFormat')) : ' ') +
|
|
"</th>" +
|
|
"<td class='" + contentClass + "'>" +
|
|
"<div style='position:relative'> </div>" +
|
|
"</td>" +
|
|
"</tr>";
|
|
d.add('m', opt('slotMinutes'));
|
|
slotCnt++;
|
|
}
|
|
s +=
|
|
"</tbody>" +
|
|
"</table>";
|
|
slotTable = $(s).appendTo(slotContainer);
|
|
slotTableFirstInner = slotTable.find('div:first');
|
|
|
|
slotBind(slotTable.find('td'));
|
|
}
|
|
|
|
|
|
|
|
/* Build Day Table
|
|
-----------------------------------------------------------------------*/
|
|
|
|
|
|
function buildDayTable() {
|
|
var html = buildDayTableHTML();
|
|
|
|
if (dayTable) {
|
|
dayTable.remove();
|
|
}
|
|
dayTable = $(html).appendTo(element);
|
|
|
|
dayHead = dayTable.find('thead');
|
|
dayHeadCells = dayHead.find('th').slice(1, -1); // exclude gutter
|
|
dayBody = dayTable.find('tbody');
|
|
dayBodyCells = dayBody.find('td').slice(0, -1); // exclude gutter
|
|
dayBodyCellInners = dayBodyCells.find('> div');
|
|
dayBodyCellContentInners = dayBodyCells.find('.fc-day-content > div');
|
|
|
|
dayBodyFirstCell = dayBodyCells.eq(0);
|
|
dayBodyFirstCellStretcher = dayBodyCellInners.eq(0);
|
|
|
|
markFirstLast(dayHead.add(dayHead.find('tr')));
|
|
markFirstLast(dayBody.add(dayBody.find('tr')));
|
|
|
|
// TODO: now that we rebuild the cells every time, we should call dayRender
|
|
}
|
|
|
|
|
|
function buildDayTableHTML() {
|
|
var html =
|
|
"<table style='width:100%' class='fc-agenda-days fc-border-separate' cellspacing='0'>" +
|
|
buildDayTableHeadHTML() +
|
|
buildDayTableBodyHTML() +
|
|
"</table>";
|
|
|
|
return html;
|
|
}
|
|
|
|
|
|
function buildDayTableHeadHTML() {
|
|
var headerClass = tm + "-widget-header";
|
|
var date;
|
|
var html = '';
|
|
var weekText;
|
|
var col;
|
|
|
|
html +=
|
|
"<thead>" +
|
|
"<tr>";
|
|
|
|
if (showWeekNumbers) {
|
|
weekText = formatDate(date, weekNumberFormat);
|
|
if (rtl) {
|
|
weekText += weekNumberTitle;
|
|
}
|
|
else {
|
|
weekText = weekNumberTitle + weekText;
|
|
}
|
|
html +=
|
|
"<th class='fc-agenda-axis fc-week-number " + headerClass + "'>" +
|
|
htmlEscape(weekText) +
|
|
"</th>";
|
|
}
|
|
else {
|
|
html += "<th class='fc-agenda-axis " + headerClass + "'> </th>";
|
|
}
|
|
|
|
for (col=0; col<colCnt; col++) {
|
|
var classNames = [
|
|
'fc-col' + col,
|
|
resources()[col].className,
|
|
headerClass
|
|
];
|
|
|
|
html +=
|
|
"<th class='" + classNames.join(' ') + "'>" +
|
|
htmlEscape(resources()[col].name) +
|
|
"</th>";
|
|
}
|
|
|
|
html +=
|
|
"<th class='fc-agenda-gutter " + headerClass + "'> </th>" +
|
|
"</tr>" +
|
|
"</thead>";
|
|
|
|
return html;
|
|
}
|
|
|
|
|
|
function buildDayTableBodyHTML() {
|
|
var headerClass = tm + "-widget-header"; // TODO: make these when updateOptions() called
|
|
var contentClass = tm + "-widget-content";
|
|
var date;
|
|
var today = makeMoment(new Date()).stripTime();
|
|
var col;
|
|
var cellsHTML;
|
|
var cellHTML;
|
|
var classNames;
|
|
var html = '';
|
|
|
|
html +=
|
|
"<tbody>" +
|
|
"<tr>" +
|
|
"<th class='fc-agenda-axis " + headerClass + "'> </th>";
|
|
|
|
cellsHTML = '';
|
|
|
|
for (col=0; col<colCnt; col++) {
|
|
|
|
date = t.visStart.clone();
|
|
|
|
classNames = [
|
|
'fc-col' + col,
|
|
resources()[col].className,
|
|
contentClass
|
|
];
|
|
if (+date == +today) {
|
|
classNames.push(
|
|
tm + '-state-highlight',
|
|
'fc-today'
|
|
);
|
|
}
|
|
else if (date < today) {
|
|
classNames.push('fc-past');
|
|
}
|
|
else {
|
|
classNames.push('fc-future');
|
|
}
|
|
|
|
cellHTML =
|
|
"<td class='" + classNames.join(' ') + "'>" +
|
|
"<div>" +
|
|
"<div class='fc-day-content'>" +
|
|
"<div style='position:relative'> </div>" +
|
|
"</div>" +
|
|
"</div>" +
|
|
"</td>";
|
|
|
|
cellsHTML += cellHTML;
|
|
}
|
|
|
|
html += cellsHTML;
|
|
html +=
|
|
"<td class='fc-agenda-gutter " + contentClass + "'> </td>" +
|
|
"</tr>" +
|
|
"</tbody>";
|
|
|
|
return html;
|
|
}
|
|
|
|
|
|
// TODO: data-date on the cells
|
|
|
|
|
|
|
|
/* Dimensions
|
|
-----------------------------------------------------------------------*/
|
|
|
|
|
|
function setHeight(height) {
|
|
if (height === undefined) {
|
|
height = viewHeight;
|
|
}
|
|
viewHeight = height;
|
|
slotTopCache = {};
|
|
|
|
var headHeight = dayBody.position().top;
|
|
var allDayHeight = slotScroller.position().top; // including divider
|
|
var bodyHeight = Math.min( // total body height, including borders
|
|
height - headHeight, // when scrollbars
|
|
slotTable.height() + allDayHeight + 1 // when no scrollbars. +1 for bottom border
|
|
);
|
|
|
|
dayBodyFirstCellStretcher
|
|
.height(bodyHeight - vsides(dayBodyFirstCell));
|
|
|
|
slotLayer.css('top', headHeight);
|
|
|
|
slotScroller.height(bodyHeight - allDayHeight - 1);
|
|
|
|
slotHeight = slotTableFirstInner.height() + 1; // +1 for border
|
|
|
|
snapRatio = opt('slotMinutes') / snapMinutes;
|
|
snapHeight = slotHeight / snapRatio;
|
|
}
|
|
|
|
|
|
function setWidth(width) {
|
|
viewWidth = width;
|
|
colPositions.clear();
|
|
colContentPositions.clear();
|
|
|
|
var axisFirstCells = dayHead.find('th:first');
|
|
if (allDayTable) {
|
|
axisFirstCells = axisFirstCells.add(allDayTable.find('th:first'));
|
|
}
|
|
axisFirstCells = axisFirstCells.add(slotTable.find('th:first'));
|
|
|
|
axisWidth = 0;
|
|
setOuterWidth(
|
|
axisFirstCells
|
|
.width('')
|
|
.each(function(i, _cell) {
|
|
axisWidth = Math.max(axisWidth, $(_cell).outerWidth());
|
|
}),
|
|
axisWidth
|
|
);
|
|
|
|
var gutterCells = dayTable.find('.fc-agenda-gutter');
|
|
if (allDayTable) {
|
|
gutterCells = gutterCells.add(allDayTable.find('th.fc-agenda-gutter'));
|
|
}
|
|
|
|
var slotTableWidth = slotScroller[0].clientWidth; // needs to be done after axisWidth (for IE7)
|
|
|
|
gutterWidth = slotScroller.width() - slotTableWidth;
|
|
if (gutterWidth) {
|
|
setOuterWidth(gutterCells, gutterWidth);
|
|
gutterCells
|
|
.show()
|
|
.prev()
|
|
.removeClass('fc-last');
|
|
}else{
|
|
gutterCells
|
|
.hide()
|
|
.prev()
|
|
.addClass('fc-last');
|
|
}
|
|
|
|
colWidth = Math.floor((slotTableWidth - axisWidth) / colCnt);
|
|
setOuterWidth(dayHeadCells.slice(0, -1), colWidth);
|
|
}
|
|
|
|
|
|
|
|
/* Scrolling
|
|
-----------------------------------------------------------------------*/
|
|
|
|
|
|
function resetScroll() {
|
|
var d0 = zeroDate();
|
|
var scrollDate = d0.clone();
|
|
scrollDate.setHours(opt('firstHour'));
|
|
var top = timePosition(d0, scrollDate) + 1; // +1 for the border
|
|
function scroll() {
|
|
slotScroller.scrollTop(top);
|
|
}
|
|
scroll();
|
|
setTimeout(scroll, 0); // overrides any previous scroll state made by the browser
|
|
}
|
|
|
|
|
|
function afterRender() { // after the view has been freshly rendered and sized
|
|
resetScroll();
|
|
}
|
|
|
|
|
|
|
|
/* Slot/Day clicking and binding
|
|
-----------------------------------------------------------------------*/
|
|
|
|
|
|
function dayBind(cells) {
|
|
cells.click(slotClick)
|
|
.mousedown(daySelectionMousedown);
|
|
}
|
|
|
|
|
|
function slotBind(cells) {
|
|
cells.click(slotClick)
|
|
.mousedown(slotSelectionMousedown);
|
|
}
|
|
|
|
|
|
function slotClick(ev) {
|
|
if (!opt('selectable')) { // if selectable, SelectionManager will worry about dayClick
|
|
var col = Math.min(colCnt-1, Math.floor((ev.pageX - dayTable.offset().left - axisWidth) / colWidth));
|
|
var date = cellToDate(0, 0);
|
|
var rowMatch = this.parentNode.className.match(/fc-slot(\d+)/); // TODO: maybe use data
|
|
|
|
ev.data = resources()[col];
|
|
|
|
if (rowMatch) {
|
|
var mins = parseInt(rowMatch[1], 10) * opt('slotMinutes');
|
|
var hours = Math.floor(mins/60);
|
|
date.setHours(hours);
|
|
date.setMinutes(mins%60 + minMinute);
|
|
trigger('dayClick', dayBodyCells[col], date, false, ev);
|
|
}else{
|
|
trigger('dayClick', dayBodyCells[col], date, true, ev);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/* Semi-transparent Overlay Helpers
|
|
-----------------------------------------------------*/
|
|
// TODO: should be consolidated with BasicView's methods
|
|
|
|
|
|
function renderDayOverlay(overlayStart, overlayEnd, refreshCoordinateGrid, col) { // overlayEnd is exclusive
|
|
var allDayRow = 0;
|
|
if (refreshCoordinateGrid) {
|
|
coordinateGrid.build();
|
|
}
|
|
|
|
dayBind(renderCellOverlay(allDayRow, col, allDayRow, col));
|
|
}
|
|
|
|
|
|
function renderCellOverlay(row0, col0, row1, col1) { // only for all-day?
|
|
var rect = coordinateGrid.rect(row0, col0, row1, col1, slotLayer);
|
|
return renderOverlay(rect, slotLayer);
|
|
}
|
|
|
|
|
|
function renderSlotOverlay(overlayStart, overlayEnd, col) {
|
|
var dayStart = cellToDate(0, 0);
|
|
var dayEnd = dayStart.clone().add('d', 1);
|
|
var stretchStart = new Date(Math.max(dayStart, overlayStart));
|
|
var stretchEnd = new Date(Math.min(dayEnd, overlayEnd));
|
|
if (stretchStart < stretchEnd) {
|
|
var rect = coordinateGrid.rect(0, col, 0, col, slotContainer); // only use it for horizontal coords
|
|
var top = timePosition(dayStart, stretchStart);
|
|
var bottom = timePosition(dayStart, stretchEnd);
|
|
rect.top = top;
|
|
rect.height = bottom - top;
|
|
slotBind(
|
|
renderOverlay(rect, slotContainer)
|
|
);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/* Coordinate Utilities
|
|
-----------------------------------------------------------------------------*/
|
|
|
|
|
|
coordinateGrid = new CoordinateGrid(function(rows, cols) {
|
|
var e, n, p;
|
|
dayHeadCells.each(function(i, _e) {
|
|
e = $(_e);
|
|
n = e.offset().left;
|
|
if (i) {
|
|
p[1] = n;
|
|
}
|
|
p = [n];
|
|
cols[i] = p;
|
|
});
|
|
p[1] = n + e.outerWidth();
|
|
if (opt('allDaySlot')) {
|
|
e = allDayRow;
|
|
n = e.offset().top;
|
|
rows[0] = [n, n+e.outerHeight()];
|
|
}
|
|
var slotTableTop = slotContainer.offset().top;
|
|
var slotScrollerTop = slotScroller.offset().top;
|
|
var slotScrollerBottom = slotScrollerTop + slotScroller.outerHeight();
|
|
function constrain(n) {
|
|
return Math.max(slotScrollerTop, Math.min(slotScrollerBottom, n));
|
|
}
|
|
for (var i=0; i<slotCnt*snapRatio; i++) { // adapt slot count to increased/decreased selection slot count
|
|
rows.push([
|
|
constrain(slotTableTop + snapHeight*i),
|
|
constrain(slotTableTop + snapHeight*(i+1))
|
|
]);
|
|
}
|
|
});
|
|
|
|
|
|
hoverListener = new HoverListener(coordinateGrid);
|
|
|
|
colPositions = new HorizontalPositionCache(function(col) {
|
|
return dayBodyCellInners.eq(col);
|
|
});
|
|
|
|
colContentPositions = new HorizontalPositionCache(function(col) {
|
|
return dayBodyCellContentInners.eq(col);
|
|
});
|
|
|
|
|
|
function colLeft(col) {
|
|
return colPositions.left(col);
|
|
}
|
|
|
|
|
|
function colContentLeft(col) {
|
|
return colContentPositions.left(col);
|
|
}
|
|
|
|
|
|
function colRight(col) {
|
|
return colPositions.right(col);
|
|
}
|
|
|
|
|
|
function colContentRight(col) {
|
|
return colContentPositions.right(col);
|
|
}
|
|
|
|
|
|
function getIsCellAllDay(cell) {
|
|
return opt('allDaySlot') && !cell.row;
|
|
}
|
|
|
|
|
|
function realCellToDate(cell) { // ugh "real" ... but blame it on our abuse of the "cell" system
|
|
var d = cellToDate(0, 0);
|
|
var slotIndex = cell.row;
|
|
if (opt('allDaySlot')) {
|
|
slotIndex--;
|
|
}
|
|
if (slotIndex >= 0) {
|
|
addMinutes(d, minMinute + slotIndex * snapMinutes);
|
|
}
|
|
return d;
|
|
}
|
|
|
|
|
|
// get the Y coordinate of the given time on the given day (both Date objects)
|
|
function timePosition(day, time) { // both date objects. day holds 00:00 of current day
|
|
day = day.clone().stripTime();
|
|
if (time < day.clone().add('m', minMinute)) {
|
|
return 0;
|
|
}
|
|
if (time >= day.clone().add('m', maxMinute)) {
|
|
return slotTable.height();
|
|
}
|
|
var slotMinutes = opt('slotMinutes'),
|
|
minutes = time.getHours()*60 + time.getMinutes() - minMinute,
|
|
slotI = Math.floor(minutes / slotMinutes),
|
|
slotTop = slotTopCache[slotI];
|
|
if (slotTop === undefined) {
|
|
slotTop = slotTopCache[slotI] =
|
|
slotTable.find('tr').eq(slotI).find('td div')[0].offsetTop;
|
|
// .eq() is faster than ":eq()" selector
|
|
// [0].offsetTop is faster than .position().top (do we really need this optimization?)
|
|
// a better optimization would be to cache all these divs
|
|
}
|
|
return Math.max(0, Math.round(
|
|
slotTop - 1 + slotHeight * ((minutes % slotMinutes) / slotMinutes)
|
|
));
|
|
}
|
|
|
|
|
|
function getAllDayRow(index) {
|
|
return allDayRow;
|
|
}
|
|
|
|
|
|
function defaultEventEnd(event) {
|
|
var start = event.start.clone();
|
|
if (event.allDay) {
|
|
return start;
|
|
}
|
|
return start.add('m', opt('defaultEventMinutes'));
|
|
}
|
|
|
|
|
|
|
|
/* Selection
|
|
---------------------------------------------------------------------------------*/
|
|
|
|
|
|
function defaultSelectionEnd(startDate, allDay) {
|
|
if (allDay) {
|
|
return startDate.clone();
|
|
}
|
|
return startDate.clone().add('m', opt('slotMinutes'));
|
|
}
|
|
|
|
|
|
function renderSelection(startDate, endDate, allDay, col) { // only for all-day
|
|
if (allDay) {
|
|
if (opt('allDaySlot')) {
|
|
renderDayOverlay(startDate, endDate, true, col);
|
|
}
|
|
}else{
|
|
renderSlotSelection(startDate, endDate);
|
|
}
|
|
}
|
|
|
|
|
|
function renderSlotSelection(startDate, endDate, col) {
|
|
var helperOption = opt('selectHelper');
|
|
coordinateGrid.build();
|
|
if (helperOption) {
|
|
col = col || dateToCell(startDate).col;
|
|
if (col >= 0 && col < colCnt) { // only works when times are on same day
|
|
var rect = coordinateGrid.rect(0, col, 0, col, slotContainer); // only for horizontal coords
|
|
var top = timePosition(startDate, startDate);
|
|
var bottom = timePosition(startDate, endDate);
|
|
if (bottom > top) { // protect against selections that are entirely before or after visible range
|
|
rect.top = top;
|
|
rect.height = bottom - top;
|
|
rect.left += 2;
|
|
rect.width -= 5;
|
|
if ($.isFunction(helperOption)) {
|
|
var helperRes = helperOption(startDate, endDate);
|
|
if (helperRes) {
|
|
rect.position = 'absolute';
|
|
selectionHelper = $(helperRes)
|
|
.css(rect)
|
|
.appendTo(slotContainer);
|
|
}
|
|
}else{
|
|
rect.isStart = true; // conside rect a "seg" now
|
|
rect.isEnd = true; //
|
|
selectionHelper = $(slotSegHtml(
|
|
{
|
|
title: '',
|
|
start: startDate,
|
|
end: endDate,
|
|
className: ['fc-select-helper'],
|
|
editable: false
|
|
},
|
|
rect
|
|
));
|
|
selectionHelper.css('opacity', opt('dragOpacity'));
|
|
}
|
|
if (selectionHelper) {
|
|
slotBind(selectionHelper);
|
|
slotContainer.append(selectionHelper);
|
|
setOuterWidth(selectionHelper, rect.width, true); // needs to be after appended
|
|
setOuterHeight(selectionHelper, rect.height, true);
|
|
}
|
|
}
|
|
}
|
|
}else{
|
|
renderSlotOverlay(startDate, endDate, col);
|
|
}
|
|
}
|
|
|
|
|
|
function clearSelection() {
|
|
clearOverlays();
|
|
if (selectionHelper) {
|
|
selectionHelper.remove();
|
|
selectionHelper = null;
|
|
}
|
|
}
|
|
|
|
function daySelectionMousedown(ev) {
|
|
var cellToDate = t.cellToDate;
|
|
var getIsCellAllDay = t.getIsCellAllDay;
|
|
var hoverListener = t.getHoverListener();
|
|
var reportDayClick = t.reportDayClick; // this is hacky and sort of weird
|
|
var col;
|
|
if (ev.which == 1 && opt('selectable')) { // which==1 means left mouse button
|
|
unselect(ev);
|
|
var _mousedownElement = this;
|
|
var dates;
|
|
hoverListener.start(function(cell, origCell) { // TODO: maybe put cellToDate/getIsCellAllDay info in cell
|
|
clearSelection();
|
|
if (cell && getIsCellAllDay(cell)) {
|
|
col = cell.col;
|
|
dates = [ realCellToDate(origCell), realCellToDate(cell) ].sort(dateCompare);
|
|
renderSelection(dates[0], dates[1], true, col);
|
|
}else{
|
|
dates = null;
|
|
}
|
|
}, ev);
|
|
$(document).one('mouseup', function(ev) {
|
|
hoverListener.stop();
|
|
if (dates) {
|
|
if (+dates[0] == +dates[1]) {
|
|
reportDayClick(dates[0], true, ev);
|
|
}
|
|
ev.data = resources()[col];
|
|
reportSelection(dates[0], dates[1], true, ev);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
// select on the calendar somewhere
|
|
function slotSelectionMousedown(ev) {
|
|
if (ev.which == 1 && opt('selectable')) { // ev.which==1 means left mouse button
|
|
unselect(ev);
|
|
var dates;
|
|
var col;
|
|
hoverListener.start(function(cell, origCell) {
|
|
clearSelection();
|
|
if (cell && cell.col == origCell.col && !getIsCellAllDay(cell)) {
|
|
col = cell.col;
|
|
var d1 = realCellToDate(origCell);
|
|
var d2 = realCellToDate(cell);
|
|
dates = [
|
|
d1,
|
|
d1.clone().add('m', snapMinutes), // calculate minutes depending on selection slot minutes
|
|
d2,
|
|
d2.clone().add('m', snapMinutes)
|
|
].sort(dateCompare);
|
|
renderSlotSelection(dates[0], dates[3], cell.col);
|
|
}else{
|
|
dates = null;
|
|
}
|
|
}, ev);
|
|
$(document).one('mouseup', function(ev) {
|
|
hoverListener.stop();
|
|
if (dates) {
|
|
if (+dates[0] == +dates[1]) {
|
|
reportDayClick(dates[0], false, ev);
|
|
}
|
|
ev.data = resources()[col];
|
|
reportSelection(dates[0], dates[3], false, ev);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
|
|
function reportDayClick(date, allDay, ev) {
|
|
trigger('dayClick', dayBodyCells[dateToCell(date).col], date, allDay, ev);
|
|
}
|
|
|
|
|
|
|
|
/* External Dragging
|
|
--------------------------------------------------------------------------------*/
|
|
|
|
|
|
function dragStart(_dragElement, ev, ui) {
|
|
hoverListener.start(function(cell) {
|
|
clearOverlays();
|
|
if (cell) {
|
|
if (getIsCellAllDay(cell)) {
|
|
renderCellOverlay(cell.row, cell.col, cell.row, cell.col);
|
|
}else{
|
|
var d1 = realCellToDate(cell);
|
|
var d2 = d1.clone().add('m', opt('defaultEventMinutes'));
|
|
renderSlotOverlay(d1, d2, cell.col);
|
|
}
|
|
}
|
|
}, ev);
|
|
}
|
|
|
|
|
|
function dragStop(_dragElement, ev, ui) {
|
|
var cell = hoverListener.stop();
|
|
clearOverlays();
|
|
if (cell) {
|
|
ev.data = resources()[cell.col];
|
|
trigger('drop', _dragElement, realCellToDate(cell), getIsCellAllDay(cell), ev, ui);
|
|
}
|
|
}
|
|
|
|
|
|
}
|