mirror of
https://github.com/wassname/fullcalendar.git
synced 2026-06-27 16:10:13 +08:00
1159 lines
30 KiB
JavaScript
1159 lines
30 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
|
|
// });
|
|
|
|
|
|
setDefaults({
|
|
allDaySlot: true,
|
|
allDayText: 'all-day',
|
|
|
|
scrollTime: '06:00:00',
|
|
|
|
slotMinutes: 30,
|
|
slotDuration: '00:30:00',
|
|
|
|
// axisFormat: generateAgendaAxisFormat,
|
|
// timeFormat: {
|
|
// agenda: generateAgendaTimeFormat
|
|
// },
|
|
|
|
dragOpacity: {
|
|
agenda: .5
|
|
},
|
|
minTime: '00:00:00',
|
|
maxTime: '24:00:00',
|
|
slotEventOverlap: true
|
|
});
|
|
|
|
|
|
// 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.computeDateTop = computeDateTop;
|
|
// //t.defaultEventEnd = defaultEventEnd;
|
|
// //t.timePosition = timePosition;
|
|
// t.getIsCellAllDay = getIsCellAllDay;
|
|
// t.allDayRow = function() { return allDayRow; }; // badly named
|
|
// //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.getMinTime = function() { return minTime; };
|
|
// t.getMaxTime = function() { return maxTime; };
|
|
|
|
// 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.renderAgenda = renderAgenda;
|
|
t.renderResource = renderResource;
|
|
t.setWidth = setWidth;
|
|
t.setHeight = setHeight;
|
|
t.afterRender = afterRender;
|
|
t.computeDateTop = computeDateTop;
|
|
t.getIsCellAllDay = getIsCellAllDay;
|
|
t.allDayRow = function() { return allDayRow; }; // badly named
|
|
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.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.getSnapDuration = function() { return snapDuration; };
|
|
t.getSlotHeight = function() { return slotHeight; };
|
|
t.getSlotDuration = function() { return slotDuration; };
|
|
t.getMinTime = function() { return minTime; };
|
|
t.getMaxTime = function() { return maxTime; };
|
|
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 daySelectionMousedown = t.daySelectionMousedown; // overridden
|
|
var slotSegHtml = t.slotSegHtml;
|
|
var cellToDate = t.cellToDate;
|
|
var dateToCell = t.dateToCell;
|
|
var rangeToSegments = t.rangeToSegments;
|
|
var formatDate = calendar.formatDate;
|
|
var calculateWeekNumber = calendar.calculateWeekNumber;
|
|
|
|
// 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 selectionHelper;
|
|
|
|
var viewWidth;
|
|
var viewHeight;
|
|
var axisWidth;
|
|
var colWidth;
|
|
var gutterWidth;
|
|
|
|
var slotDuration;
|
|
var slotHeight; // TODO: what if slotHeight changes? (see issue 650)
|
|
|
|
var snapDuration;
|
|
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 minTime;
|
|
var maxTime;
|
|
var colFormat;
|
|
var resources = t.getResources;
|
|
// 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 slotDuration;
|
|
// var tm;
|
|
// var rtl;
|
|
// var minMinute, maxMinute;
|
|
// var colFormat;
|
|
// var showWeekNumbers;
|
|
// var weekNumberTitle;
|
|
// var weekNumberFormat;
|
|
// var resources = t.getResources;
|
|
// var minTime;
|
|
// var maxTime;
|
|
|
|
/* Rendering
|
|
-----------------------------------------------------------------------------*/
|
|
|
|
|
|
disableTextSelection(element.addClass('fc-agenda'));
|
|
|
|
|
|
function renderResource(resourceColumnsCnt) {
|
|
colCnt = resourceColumnsCnt;
|
|
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');
|
|
colFormat = opt('columnFormat');
|
|
|
|
minTime = moment.duration(opt('minTime'));
|
|
maxTime = moment.duration(opt('maxTime'));
|
|
|
|
slotDuration = moment.duration(opt('slotDuration'));
|
|
snapDuration = opt('snapDuration');
|
|
snapDuration = snapDuration ? moment.duration(snapDuration) : slotDuration;
|
|
}
|
|
|
|
|
|
|
|
/* Build DOM
|
|
-----------------------------------------------------------------------*/
|
|
|
|
|
|
function buildSkeleton() {
|
|
var s;
|
|
var headerClass = tm + "-widget-header";
|
|
var contentClass = tm + "-widget-content";
|
|
var slotTime;
|
|
var slotDate;
|
|
var minutes;
|
|
var slotNormal = slotDuration.asMinutes() % 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('allDayHTML') ||
|
|
htmlEscape(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>";
|
|
|
|
slotTime = moment.duration(+minTime); // i wish there was .clone() for durations
|
|
slotCnt = 0;
|
|
while (slotTime < maxTime) {
|
|
slotDate = t.start.clone().time(slotTime); // will be in UTC but that's good. to avoid DST issues
|
|
minutes = slotDate.minutes();
|
|
s +=
|
|
"<tr class='fc-slot" + slotCnt + ' ' + (!minutes ? '' : 'fc-minor') + "'>" +
|
|
"<th class='fc-agenda-axis " + headerClass + "'>" +
|
|
((!slotNormal || !minutes) ?
|
|
htmlEscape(formatDate(slotDate, opt('axisFormat'))) :
|
|
' '
|
|
) +
|
|
"</th>" +
|
|
"<td class='" + contentClass + "'>" +
|
|
"<div style='position:relative'> </div>" +
|
|
"</td>" +
|
|
"</tr>";
|
|
slotTime.add(slotDuration);
|
|
slotCnt++;
|
|
}
|
|
|
|
s +=
|
|
"</tbody>" +
|
|
"</table>";
|
|
// slotTable = $(s).appendTo(slotContainer);
|
|
// slotTableFirstInner = slotTable.find('div:first');
|
|
slotTable = $(s).appendTo(slotContainer);
|
|
|
|
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 (opt('weekNumbers')) {
|
|
date = cellToDate(0, 0);
|
|
weekText = calculateWeekNumber(date);
|
|
if (rtl) {
|
|
weekText += opt('weekNumberTitle');
|
|
}
|
|
else {
|
|
weekText = opt('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 resource = resources()[col];
|
|
|
|
var classNames = [ // added
|
|
'fc-col' + col,
|
|
resource.className instanceof Array ? resource.className.join(' ') : resource.className,
|
|
headerClass
|
|
];
|
|
|
|
html +=
|
|
"<th class='" + classNames.join(' ') + "'>" +
|
|
htmlEscape(resource.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++) {
|
|
var resource = resources()[col];
|
|
date = t.intervalStart.clone();
|
|
|
|
classNames = [
|
|
'fc-col' + col,
|
|
resource.className instanceof Array ? resource.className.join(' ') : resource.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);
|
|
|
|
// the stylesheet guarantees that the first row has no border.
|
|
// this allows .height() to work well cross-browser.
|
|
var slotHeight0 = slotTable.find('tr:first').height() + 1; // +1 for bottom border
|
|
var slotHeight1 = slotTable.find('tr:eq(1)').height();
|
|
// HACK: i forget why we do this, but i think a cross-browser issue
|
|
slotHeight = (slotHeight0 + slotHeight1) / 2;
|
|
|
|
snapRatio = slotDuration / snapDuration;
|
|
snapHeight = slotHeight / snapRatio;
|
|
|
|
// 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 top = computeTimeTop(
|
|
moment.duration(opt('scrollTime'))
|
|
) + 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, col);
|
|
var match = this.parentNode.className.match(/fc-slot(\d+)/); // TODO: maybe use data
|
|
|
|
ev.data = resources()[col]; // added
|
|
|
|
if (match) {
|
|
var slotIndex = parseInt(match[1], 10);
|
|
date.add(minTime + slotIndex * slotDuration);
|
|
date = calendar.rezoneDate(date);
|
|
trigger(
|
|
'dayClick',
|
|
dayBodyCells[col],
|
|
date,
|
|
ev
|
|
);
|
|
}else{
|
|
trigger(
|
|
'dayClick',
|
|
dayBodyCells[col],
|
|
date,
|
|
ev
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/* Semi-transparent Overlay Helpers
|
|
-----------------------------------------------------*/
|
|
// TODO: should be consolidated with BasicView's methods
|
|
|
|
|
|
function renderDayOverlay(overlayStart, overlayEnd, refreshCoordinateGrid, col) { // overlayEnd is exclusive
|
|
if (refreshCoordinateGrid) {
|
|
coordinateGrid.build();
|
|
}
|
|
|
|
var segments = rangeToSegments(overlayStart, overlayEnd);
|
|
|
|
for (var i=0; i<segments.length; i++) {
|
|
var segment = segments[i];
|
|
dayBind(
|
|
renderCellOverlay(
|
|
segment.row,
|
|
col, //segment.leftCol,
|
|
segment.row,
|
|
col //segment.rightCol
|
|
)
|
|
);
|
|
}
|
|
|
|
// 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) {
|
|
// normalize, because dayStart/dayEnd have stripped time+zone
|
|
overlayStart = overlayStart.clone().stripZone();
|
|
overlayEnd = overlayEnd.clone().stripZone();
|
|
|
|
//for (var i=0; i<colCnt; i++) { // loop through the day columns
|
|
|
|
var dayStart = cellToDate(0, 0);
|
|
var dayEnd = dayStart.clone().add('days', 1);
|
|
|
|
var stretchStart = dayStart < overlayStart ? overlayStart : dayStart; // the max of the two
|
|
var stretchEnd = dayEnd < overlayEnd ? dayEnd : overlayEnd; // the min of the two
|
|
|
|
if (stretchStart < stretchEnd) {
|
|
var rect = coordinateGrid.rect(0, col, 0, col, slotContainer); // only use it for horizontal coords
|
|
var top = computeDateTop(stretchStart, dayStart);
|
|
var bottom = computeDateTop(stretchEnd, dayStart);
|
|
|
|
rect.top = top;
|
|
rect.height = bottom - top;
|
|
slotBind(
|
|
renderOverlay(rect, slotContainer)
|
|
);
|
|
}
|
|
//}
|
|
|
|
// 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 date = cellToDate(0, 0); // updated
|
|
var snapIndex = cell.row;
|
|
|
|
if (opt('allDaySlot')) {
|
|
snapIndex--;
|
|
}
|
|
|
|
if (snapIndex >= 0) {
|
|
date.time(moment.duration(minTime + snapIndex * snapDuration));
|
|
date = calendar.rezoneDate(date);
|
|
}
|
|
|
|
return date;
|
|
}
|
|
|
|
|
|
function computeDateTop(date, startOfDayDate) {
|
|
return computeTimeTop(
|
|
moment.duration(
|
|
date.clone().stripZone() - startOfDayDate.clone().stripTime()
|
|
)
|
|
);
|
|
}
|
|
|
|
|
|
function computeTimeTop(time) { // time is a duration
|
|
|
|
if (time < minTime) {
|
|
return 0;
|
|
}
|
|
if (time >= maxTime) {
|
|
return slotTable.height();
|
|
}
|
|
|
|
var slots = (time - minTime) / slotDuration;
|
|
var slotIndex = Math.floor(slots);
|
|
var slotPartial = slots - slotIndex;
|
|
var slotTop = slotTopCache[slotIndex];
|
|
|
|
// find the position of the corresponding <tr>
|
|
// need to use this tecnhique because not all rows are rendered at same height sometimes.
|
|
if (slotTop === undefined) {
|
|
slotTop = slotTopCache[slotIndex] =
|
|
slotTable.find('tr').eq(slotIndex).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
|
|
}
|
|
|
|
var top =
|
|
slotTop - 1 + // because first row doesn't have a top border
|
|
slotPartial * slotHeight; // part-way through the row
|
|
|
|
top = Math.max(top, 0);
|
|
|
|
return top;
|
|
}
|
|
|
|
// // 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 = computeDateTop(startDate, startDate);
|
|
var bottom = computeDateTop(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(snapDuration), // calculate minutes depending on selection slot minutes
|
|
d2,
|
|
d2.clone().add(snapDuration)
|
|
].sort(dateCompare);
|
|
renderSlotSelection(dates[0], dates[3], cell.col); // updated
|
|
}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]; // added
|
|
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);
|
|
}
|
|
}
|
|
|
|
|
|
}
|