// 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 =
$("
")
.appendTo(element);
if (opt('allDaySlot')) {
daySegmentContainer =
$("")
.appendTo(slotLayer);
s =
"" +
"" +
"" +
"| " +
"" +
" | " +
"" +
"
" +
"
";
allDayTable = $(s).appendTo(slotLayer);
allDayRow = allDayTable.find('tr');
dayBind(allDayRow.find('td'));
slotLayer.append(
""
);
}else{
daySegmentContainer = $([]); // in jQuery 1.4, we can just do $()
}
slotScroller =
$("")
.appendTo(slotLayer);
slotContainer =
$("")
.appendTo(slotScroller);
slotSegmentContainer =
$("")
.appendTo(slotContainer);
s =
"" +
"";
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 +=
"" +
"" +
"| " +
" " +
" | " +
"
";
slotTime.add(slotDuration);
slotCnt++;
}
s +=
"" +
"
";
// 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 =
"" +
buildDayTableHeadHTML() +
buildDayTableBodyHTML() +
"
";
return html;
}
function buildDayTableHeadHTML() {
var headerClass = tm + "-widget-header";
var date;
var html = '';
var weekText;
var col;
html +=
"" +
"";
if (opt('weekNumbers')) {
date = cellToDate(0, 0);
weekText = calculateWeekNumber(date);
if (rtl) {
weekText += opt('weekNumberTitle');
}
else {
weekText = opt('weekNumberTitle') + weekText;
}
html +=
"";
}
else {
html += "";
}
for (col=0; col" +
htmlEscape(resource.name) +
"";
}
html +=
"" +
"
" +
"";
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 +=
"" +
"" +
"";
cellsHTML = '';
for (col=0; col" +
"" +
"";
cellsHTML += cellHTML;
}
html += cellsHTML;
html +=
"| | " +
"
" +
"";
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= 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
// 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);
}
}
}