event rendering optimizations for agenda view w/ refactoring

This commit is contained in:
Adam Shaw 2010-01-24 22:56:57 -08:00
parent f19e54488c
commit a00104b10b
4 changed files with 411 additions and 685 deletions

View file

@ -88,15 +88,16 @@ function Agenda(element, options, methods) {
nwe, // no weekends (int) nwe, // no weekends (int)
rtl, dis, dit, // day index sign / translate rtl, dis, dit, // day index sign / translate
minMinute, maxMinute, minMinute, maxMinute,
dayContentElements=[], colContentPositions = new HorizontalPositionCache(function(col) {
dayContentLefts=[], return bg.find('td:eq(' + col + ') div div');
dayContentRights=[], }),
// ... // ...
view = $.extend(this, viewMethods, methods, { view = $.extend(this, viewMethods, methods, {
renderAgenda: renderAgenda, renderAgenda: renderAgenda,
renderEvents: renderEvents, renderEvents: renderEvents,
rerenderEvents: rerenderEvents, rerenderEvents: rerenderEvents,
clearEvents: clearEvents,
updateSize: updateSize, updateSize: updateSize,
shown: resetScroll, shown: resetScroll,
defaultEventEnd: function(event) { defaultEventEnd: function(event) {
@ -287,19 +288,16 @@ function Agenda(element, options, methods) {
function updateSize(width, height) { function updateSize(width, height) {
viewWidth = width; viewWidth = width;
viewHeight = height; viewHeight = height;
dayContentLefts = []; colContentPositions.clear();
dayContentRights = [];
bodyTable.width(''); body.width(width);
body.height(height - head.height()); body.height(height - head.height());
bodyTable.width('');
// need this for IE6/7. triggers clientWidth to be calculated for
// later user in this function. this is ridiculous
body[0].clientWidth;
var topTDs = head.find('tr:first th'), var topTDs = head.find('tr:first th'),
stripeTDs = bg.find('td'), stripeTDs = bg.find('td'),
contentWidth = body[0].clientWidth; contentWidth = slotSegmentContainer.width(); // body[0].clientWidth isn't reliable here in IE6
bodyTable.width(contentWidth); bodyTable.width(contentWidth);
// time-axis width // time-axis width
@ -327,10 +325,6 @@ function Agenda(element, options, methods) {
}); });
slotHeight = body.find('tr:first div').height() + 1; slotHeight = body.find('tr:first div').height() + 1;
// TODO:
//reportTBody(bodyTable.find('tbody'));
// Opera 9.25 doesn't detect the bug when called from agenda
} }
function slotClick(ev) { function slotClick(ev) {
@ -402,348 +396,62 @@ function Agenda(element, options, methods) {
/* cell/cell-content positioning calculating/caching
-----------------------------------------------------------------------------*/
// DERIVED FROM grid.js
function dayContentElement(dayOfWeek) {
if (dayContentElements[dayOfWeek] == undefined) {
dayContentElements[dayOfWeek] = bg.find('td:eq(' + ((dayOfWeek - Math.max(firstDay,nwe)+colCnt) % colCnt) + ') div div');
}
return dayContentElements[dayOfWeek];
}
function dayContentLeft(dayOfWeek) {
if (dayContentLefts[dayOfWeek] == undefined) {
dayContentLefts[dayOfWeek] = dayContentElement(dayOfWeek).position().left + axisWidth;
}
return dayContentLefts[dayOfWeek];
}
function dayContentRight(dayOfWeek) {
if (dayContentRights[dayOfWeek] == undefined) {
dayContentRights[dayOfWeek] = dayContentLeft(dayOfWeek) + dayContentElement(dayOfWeek).width();
}
return dayContentRights[dayOfWeek];
}
// renders 'all-day' events at the top // renders 'all-day' events at the top
function renderDaySegs(segRow) { function renderDaySegs(segRow) {
if (options.allDaySlot) { if (options.allDaySlot) {
var html='', _renderDaySegs(
td = head.find('td'), [segRow],
tdInner = td.find('div div'), view,
tr = td.parent(), axisWidth,
top = safePosition(tdInner, td, tr, tr.parent()).top, viewWidth,
rowContentHeight = 0, function() {
i, len=segRow.length, level, return head.find('tr.fc-all-day')
levelHeight, },
j, seg, function(dayOfWeek) {
event, return axisWidth + colContentPositions.left(day2col(dayOfWeek));
className, },
left, right, function(dayOfWeek) {
triggerRes, return axisWidth + colContentPositions.right(day2col(dayOfWeek));
l=0, },
_eventElements, daySegmentContainer,
eventLefts=[], eventRights=[], bootstrapDayEventHandlers
eventHSides=[], );
eventOuterHeights=[]; updateSize(viewWidth, viewHeight); // might have pushed the body down, so resize
for (i=0; i<len; i++) {
level = segRow[i];
for (j=0; j<level.length; j++) {
seg = level[j];
event = seg.event;
className = 'fc-event fc-event-hori ';
if (rtl) {
if (seg.isStart) {
className += 'fc-corner-right ';
}
if (seg.isEnd) {
className += 'fc-corner-left ';
}
left = seg.isEnd ? 0 : dayContentLeft(seg.end.getDay()-1);
right = seg.isStart ? viewWidth : dayContentRight(seg.start.getDay());
}else{
if (seg.isStart) {
className += 'fc-corner-left ';
}
if (seg.isEnd) {
className += 'fc-corner-right ';
}
left = seg.isStart ? dayContentLeft(seg.start.getDay()) : 0;
right = seg.isEnd ? dayContentRight(seg.end.getDay()-1) : viewWidth;
}
eventLefts[l] = left;
eventRights[l] = right;
html +=
"<div class='" + className + event.className.join(' ') + "' style='position:absolute;z-index:8;left:"+left+"px'>" +
"<a" + (event.url ? " href='" + htmlEscape(event.url) + "'" : '') + ">" +
(!event.allDay && seg.isStart ?
"<span class='fc-event-time'>" +
htmlEscape(formatDates(event.start, event.end, view.option('timeFormat'), options)) +
"</span>"
:'') +
"<span class='fc-event-title'>" + htmlEscape(event.title) + "</span>" +
"</a>" +
"</div>";
l++;
} }
} }
daySegmentContainer.html(html);
_eventElements = daySegmentContainer[0].childNodes;
l = 0;
for (i=0; i<len; i++) {
level = segRow[i];
for (j=0; j<level.length; j++) {
seg = level[j];
event = seg.event;
eventElement = $(_eventElements[l]);
triggerRes = view.trigger('eventRender', event, event, eventElement);
if (triggerRes !== false) {
if (triggerRes && typeof triggerRes != 'boolean') {
eventElement = $(triggerRes).appendTo(segmentContainer);
}
eventOuterHeights[l] = eventElement.outerHeight(true);
eventHSides[l] = hsides(eventElement, true);
seg.element = eventElement;
//bootstrapEventHandlers(event, seg, eventElement);
view.reportEventElement(event, eventElement);
}
l++;
}
}
l = 0;
for (i=0; i<len; i++) {
level = segRow[i];
levelHeight = 0;
for (j=0; j<level.length; j++) {
seg = level[j];
if (eventElement = seg.element) {
eventElement.css('top', top);
if (rtl && rtlLeftDiff == undefined) {
// bug in IE6 where offsets are miscalculated with direction:rtl
rtlLeftDiff = eventLefts[l] - eventElement.position().left;
if (rtlLeftDiff) {
eventElement.css('left', eventLefts[l] + rtlLeftDiff);
}
}
eventElement.width(eventRights[l] - eventLefts[l] - eventHSides[l]);
view.trigger('eventAfterRender', event, event, eventElement);
levelHeight = Math.max(levelHeight, eventOuterHeights[l]);
}
l++;
}
rowContentHeight += levelHeight;
top += levelHeight;
}
tdInner.height(rowContentHeight);
updateSize(viewWidth, viewHeight); // tdInner might have pushed the body down, so resize
}
}
/*
// the original function
function renderDaySegs2(segRow) {
if (options.allDaySlot) {
var td = head.find('td'),
tdInner = td.find('div div'),
tr = td.parent(),
top = safePosition(tdInner, td, tr, tr.parent()).top,
rowContentHeight = 0,
i, len=segRow.length, level,
levelHeight,
j, seg,
event,
className,
leftDay, leftRounded,
rightDay, rightRounded,
left, right,
eventElement, anchorElement,
triggerRes;
for (i=0; i<len; i++) {
level = segRow[i];
levelHeight = 0;
for (j=0; j<level.length; j++) {
seg = level[j];
event = seg.event;
className = 'fc-event fc-event-hori ';
if (rtl) {
leftDay = seg.end.getDay() - 1;
leftRounded = seg.isEnd;
rightDay = seg.start.getDay();
rightRounded = seg.isStart;
}else{
leftDay = seg.start.getDay();
leftRounded = seg.isStart;
rightDay = seg.end.getDay() - 1;
rightRounded = seg.isEnd;
}
if (leftRounded) {
className += 'fc-corner-left ';
left = bg.find('td:eq('+(((leftDay-Math.max(firstDay,nwe)+colCnt)%colCnt)*dis+dit)+') div div').position().left + axisWidth;
}else{
left = axisWidth;
}
if (rightRounded) {
className += 'fc-corner-right ';
right = bg.find('td:eq('+(((rightDay-Math.max(firstDay,nwe)+colCnt)%colCnt)*dis+dit)+') div div');
right = right.position().left + right.width() + axisWidth;
}else{
right = axisWidth + bg.width();
}
eventElement = $("<div class='" + className + event.className.join(' ') + "'/>")
.append(anchorElement = $("<a/>")
.append($("<span class='fc-event-title' />")
.text(event.title)));
if (event.url) {
anchorElement.attr('href', event.url);
}
triggerRes = view.trigger('eventRender', event, event, eventElement);
if (triggerRes !== false) {
if (triggerRes && typeof triggerRes != 'boolean') {
eventElement = $(triggerRes);
}
eventElement
.css({
position: 'absolute',
top: top,
left: left,
zIndex: 8
})
.appendTo(head);
setOuterWidth(eventElement, right-left, true);
view.eventElementHandlers(event, eventElement);
if (event.editable || event.editable == undefined && options.editable) {
draggableDayEvent(event, eventElement, seg.isStart);
if (seg.isEnd) {
view.resizableDayEvent(event, eventElement, colWidth);
}
}
view.reportEventElement(event, eventElement);
view.trigger('eventAfterRender', event, event, eventElement);
levelHeight = Math.max(levelHeight, eventElement.outerHeight(true));
}
}
top += levelHeight;
rowContentHeight += levelHeight;
}
tdInner.height(rowContentHeight);
updateSize(viewWidth, viewHeight); // tdInner might have pushed the body down, so resize
}
}
*/
// renders events in the 'time slots' at the bottom // renders events in the 'time slots' at the bottom
function renderSlotSegs(segCols) { function renderSlotSegs(segCols) {
renderSlotSegs2(segCols);
/* var event,
var html='',
colI, colLen=segCols.length, col,
levelI, level,
segI, seg,
forward,
event,
top, bottom,
tdInner,
width, left,
eventElement,
className, className,
l=0, top,
bottom,
leftmost,
availWidth,
forward,
width,
left,
eventTops=[],
eventLefts=[], eventLefts=[],
eventOuterWidths=[], eventOuterWidths=[],
triggerRes; eventOuterHeights=[],
for (colI=0; colI<colLen; colI++) { html='',
col = segCols[colI]; eventElements,
for (levelI=0; levelI<col.length; levelI++) { eventElement,
level = col[levelI]; triggerRes,
for (segI=0; segI<level.length; segI++) { eventVSides=[],
seg = level[segI]; eventHSides=[],
forward = seg.forward || 0; eventTitleTops=[],
height;
// calculate desired position/dimensions, create html
eachLeaf(segCols, function(l, seg, segI, levelI, colI) {
event = seg.event; event = seg.event;
top = timePosition(seg.start, seg.start);
bottom = timePosition(seg.start, seg.end);
tdInner = bg.find('td:eq(' + (colI*dis + dit) + ') div div');
availWidth = tdInner.width();
availWidth = Math.min(availWidth-6, availWidth*.95); // TODO: move this to CSS
if (levelI) {
// indented and thin
width = availWidth / (levelI + forward + 1);
}else{
if (forward) {
// moderately wide, aligned left still
width = ((availWidth / (forward + 1)) - (12/2)) * 2; // 12 is the predicted width of resizer =
}else{
// can be entire width, aligned left
width = availWidth;
}
}
left = axisWidth + tdInner.position().left + // leftmost possible
(availWidth / (levelI + forward + 1) * levelI) // indentation
* dis + (rtl ? availWidth - width : 0); // rtl
eventLefts[l] = left;
eventOuterWidths[l] = width;
}
}
}
*/
}
// the original function
function renderSlotSegs2(segCols) {
var colI, colLen=segCols.length, col,
levelI, level,
segI, seg,
forward,
event,
top, bottom,
tdInner,
width, left,
className,
eventElement, anchorElement, timeElement, titleElement,
triggerRes;
for (colI=0; colI<colLen; colI++) {
col = segCols[colI];
for (levelI=0; levelI<col.length; levelI++) {
level = col[levelI];
for (segI=0; segI<level.length; segI++) {
seg = level[segI];
forward = seg.forward || 0;
event = seg.event;
top = timePosition(seg.start, seg.start);
bottom = timePosition(seg.start, seg.end);
tdInner = bg.find('td:eq(' + (colI*dis + dit) + ') div div');
availWidth = tdInner.width();
availWidth = Math.min(availWidth-6, availWidth*.95); // TODO: move this to CSS
if (levelI) {
// indented and thin
width = availWidth / (levelI + forward + 1);
}else{
if (forward) {
// moderately wide, aligned left still
width = ((availWidth / (forward + 1)) - (12/2)) * 2; // 12 is the predicted width of resizer =
}else{
// can be entire width, aligned left
width = availWidth;
}
}
left = axisWidth + tdInner.position().left + // leftmost possible
(availWidth / (levelI + forward + 1) * levelI) // indentation
* dis + (rtl ? availWidth - width : 0); // rtl
className = 'fc-event fc-event-vert '; className = 'fc-event fc-event-vert ';
if (seg.isStart) { if (seg.isStart) {
className += 'fc-corner-top '; className += 'fc-corner-top ';
@ -751,48 +459,90 @@ function Agenda(element, options, methods) {
if (seg.isEnd) { if (seg.isEnd) {
className += 'fc-corner-bottom '; className += 'fc-corner-bottom ';
} }
eventElement = $("<div class='" + className + event.className.join(' ') + "' />") top = timePosition(seg.start, seg.start);
.append(anchorElement = $("<a><span class='fc-event-bg'/></a>") bottom = timePosition(seg.start, seg.end);
.append(timeElement = $("<span class='fc-event-time'/>") leftmost = axisWidth + colContentPositions.left(colI*dis + dit);
.text(formatDates(event.start, event.end, view.option('timeFormat')))) availWidth = axisWidth + colContentPositions.right(colI*dis + dit) - leftmost;
.append(titleElement = $("<span class='fc-event-title'/>") availWidth = Math.min(availWidth-6, availWidth*.95); // TODO: move this to CSS
.text(event.title))) forward = seg.forward || 0;
if (event.url) { if (levelI) {
anchorElement.attr('href', event.url); // indented and thin
width = availWidth / (levelI + forward + 1);
}else{
if (forward) {
// moderately wide, aligned left still
width = ((availWidth / (forward + 1)) - (12/2)) * 2; // 12 is the predicted width of resizer =
}else{
// can be entire width, aligned left
width = availWidth;
} }
}
left = leftmost + // leftmost possible
(availWidth / (levelI + forward + 1) * levelI) // indentation
* dis + (rtl ? availWidth - width : 0); // rtl
eventTops[l] = top;
eventLefts[l] = left;
eventOuterWidths[l] = width;
eventOuterHeights[l] = bottom - top;
html +=
"<div class='" + className + event.className.join(' ') + "' style='position:absolute;z-index:8;top:" + top + "px;left:" + left + "px'>" +
"<a" + (event.url ? " href='" + htmlEscape(event.url) + "'" : '') + ">" +
"<span class='fc-event-time'>" + htmlEscape(formatDates(event.start, event.end, view.option('timeFormat'))) + "</span>" +
"<span class='fc-event-title'>" + htmlEscape(event.title) + "</span>" +
"<span class='fc-event-bg'/>" +
"</a>" +
((event.editable || event.editable == undefined && options.editable) && !options.disableResizing && $.fn.resizable ?
"<div class='ui-resizable-handle ui-resizable-s'>=</div>"
: '') +
"</div>";
});
slotSegmentContainer.html(html);
eventElements = slotSegmentContainer.children();
// retrieve elements, run through eventRender callback, record outer-edge dimensions
eachLeaf(segCols, function(l, seg) {
event = seg.event;
eventElement = eventElements.eq(l);
triggerRes = view.trigger('eventRender', event, event, eventElement); triggerRes = view.trigger('eventRender', event, event, eventElement);
if (triggerRes !== false) { if (triggerRes === false) {
if (triggerRes && typeof triggerRes != 'boolean') { eventElement.remove();
eventElement = $(triggerRes); }else{
} if (triggerRes && triggerRes !== true) {
eventElement eventElement.remove();
eventElement = $(triggerRes)
.css({ .css({
position: 'absolute', top: eventTops[l],
zIndex: 8, left: eventLefts[l]
top: top,
left: left
}) })
.appendTo(bodyContent); .appendTo(slotSegmentContainer);
setOuterWidth(eventElement, width, true);
setOuterHeight(eventElement, bottom-top, true);
if (eventElement.height() - titleElement.position().top < 10) {
// event title doesn't have enough room, put next to the time
timeElement.text(formatDate(event.start, view.option('timeFormat')) + ' - ' + event.title);
titleElement.remove();
}
view.eventElementHandlers(event, eventElement);
if (event.editable || event.editable == undefined && options.editable) {
draggableSlotEvent(event, eventElement, timeElement);
if (seg.isEnd) {
resizableSlotEvent(event, eventElement, timeElement);
}
}
} }
seg.element = eventElement;
eventVSides[l] = vsides(eventElement, true);
eventHSides[l] = hsides(eventElement, true);
eventTitleTops[l] = eventElement.find('span.fc-event-title').position().top;
bootstrapSlotEventHandlers(event, seg, eventElement);
view.reportEventElement(event, eventElement); view.reportEventElement(event, eventElement);
}
});
// set all positions/dimensions at once
eachLeaf(segCols, function(l, seg) {
if (eventElement = seg.element) {
eventElement
.width(eventOuterWidths[l] - eventHSides[l])
.height(height = eventOuterHeights[l] - eventVSides[l]);
event = seg.event;
if (height - eventTitleTops[l] < 10) {
// not enough room for title, put it in the time header
eventElement.find('span.fc-event-time')
.text(formatDate(event.start, view.option('timeFormat')) + ' - ' + event.title);
eventElement.find('span.fc-event-title')
.remove();
}
view.trigger('eventAfterRender', event, event, eventElement); view.trigger('eventAfterRender', event, event, eventElement);
} }
} });
}
} }
@ -817,6 +567,45 @@ function Agenda(element, options, methods) {
function bootstrapDayEventHandlers(event, seg, eventElement) {
var attached = false;
eventElement.mouseover(function(ev) {
if (!attached) {
view.eventElementHandlers(event, eventElement);
if (event.editable || event.editable == undefined && options.editable) {
draggableDayEvent(event, eventElement, seg.isStart);
if (seg.isEnd) {
view.resizableDayEvent(event, eventElement, colWidth);
}
}
attached = true;
view.trigger('eventMouseover', this, event, ev);
}
});
}
function bootstrapSlotEventHandlers(event, seg, eventElement) {
var attached = false;
eventElement.mouseover(function(ev) {
if (!attached) {
view.eventElementHandlers(event, eventElement);
if (event.editable || event.editable == undefined && options.editable) {
var timeElement = eventElement.find('span.fc-event-time');
draggableSlotEvent(event, eventElement, timeElement);
if (seg.isEnd) {
resizableSlotEvent(event, eventElement, timeElement);
}
}
attached = true;
view.trigger('eventMouseover', this, event, ev);
}
});
}
/* Event Dragging /* Event Dragging
-----------------------------------------------------------------------------*/ -----------------------------------------------------------------------------*/
@ -1038,7 +827,9 @@ function Agenda(element, options, methods) {
var slotDelta, prevSlotDelta; var slotDelta, prevSlotDelta;
eventElement eventElement
.resizable({ .resizable({
handles: 's', handles: {
s: 'div.ui-resizable-s'
},
grid: slotHeight, grid: slotHeight,
start: function(ev, ui) { start: function(ev, ui) {
slotDelta = prevSlotDelta = 0; slotDelta = prevSlotDelta = 0;
@ -1074,15 +865,11 @@ function Agenda(element, options, methods) {
// BUG: if event was really short, need to put title back in span // BUG: if event was really short, need to put title back in span
} }
} }
}) });
.find('div.ui-resizable-s').text('=');
} }
} }
// ALL-DAY event resizing w/ 'view' methods...
/* Misc /* Misc
@ -1105,10 +892,17 @@ function Agenda(element, options, methods) {
td = tr.find('td'), td = tr.find('td'),
innerDiv = td.find('div'); innerDiv = td.find('div');
return Math.max(0, Math.round( return Math.max(0, Math.round(
safePosition(innerDiv, td, tr, tr.parent()).top - 1 + slotHeight * ((minutes % slotMinutes) / slotMinutes) innerDiv.position().top + topCorrect(tr, td) - 1 + slotHeight * ((minutes % slotMinutes) / slotMinutes)
)); ));
} }
function day2col(dayOfWeek) {
return ((dayOfWeek - Math.max(firstDay,nwe)+colCnt) % colCnt)*dis+dit;
}
} }

View file

@ -110,7 +110,7 @@ views.basicDay = function(element, options) {
// rendering bugs // rendering bugs
var tdHeightBug, rtlLeftDiff; var tdHeightBug;
function Grid(element, options, methods) { function Grid(element, options, methods) {
@ -125,9 +125,9 @@ function Grid(element, options, methods) {
cachedEvents=[], cachedEvents=[],
segments=[], segments=[],
segmentContainer, segmentContainer,
dayContentElements=[], dayContentPositions = new HorizontalPositionCache(function(dayOfWeek) {
dayContentLefts=[], return tbody.find('td:eq(' + ((dayOfWeek - Math.max(firstDay,nwe)+colCnt) % colCnt) + ') div div')
dayContentRights=[], }),
// ... // ...
// initialize superclass // initialize superclass
@ -135,6 +135,7 @@ function Grid(element, options, methods) {
renderGrid: renderGrid, renderGrid: renderGrid,
renderEvents: renderEvents, renderEvents: renderEvents,
rerenderEvents: rerenderEvents, rerenderEvents: rerenderEvents,
clearEvents: clearEvents,
updateSize: updateSize, updateSize: updateSize,
defaultEventEnd: function(event) { // calculates an end if event doesnt have one, mostly for resizing defaultEventEnd: function(event) { // calculates an end if event doesnt have one, mostly for resizing
return cloneDate(event.start); return cloneDate(event.start);
@ -322,8 +323,7 @@ function Grid(element, options, methods) {
function updateSize(width, height) { // does not render/position the events function updateSize(width, height) { // does not render/position the events
viewWidth = width; viewWidth = width;
viewHeight = height; viewHeight = height;
dayContentLefts = []; dayContentPositions.clear();
dayContentRights = [];
var leftTDs = tbody.find('tr td:first-child'), var leftTDs = tbody.find('tr td:first-child'),
tbodyHeight = viewHeight - thead.height(), tbodyHeight = viewHeight - thead.height(),
@ -336,8 +336,6 @@ function Grid(element, options, methods) {
rowHeight2 = tbodyHeight - rowHeight1*(rowCnt-1); rowHeight2 = tbodyHeight - rowHeight1*(rowCnt-1);
} }
reportTBody(tbody);
if (tdHeightBug == undefined) { if (tdHeightBug == undefined) {
// bug in firefox where cell height includes padding // bug in firefox where cell height includes padding
var tr = tbody.find('tr:first'), var tr = tbody.find('tr:first'),
@ -363,36 +361,6 @@ function Grid(element, options, methods) {
/* cell/cell-content positioning calculating/caching
-----------------------------------------------------------------------------*/
function dayContentElement(dayOfWeek) {
if (dayContentElements[dayOfWeek] == undefined) {
dayContentElements[dayOfWeek] = tbody.find('td:eq(' + ((dayOfWeek - Math.max(firstDay,nwe)+colCnt) % colCnt) + ') div div');
}
return dayContentElements[dayOfWeek];
}
function dayContentLeft(dayOfWeek) {
if (dayContentLefts[dayOfWeek] == undefined) {
dayContentLefts[dayOfWeek] = dayContentElement(dayOfWeek).position().left;
}
return dayContentLefts[dayOfWeek];
}
function dayContentRight(dayOfWeek) {
if (dayContentRights[dayOfWeek] == undefined) {
dayContentRights[dayOfWeek] = dayContentLeft(dayOfWeek) + dayContentElement(dayOfWeek).width();
}
return dayContentRights[dayOfWeek];
}
/* Event Rendering /* Event Rendering
-----------------------------------------------------------------------------*/ -----------------------------------------------------------------------------*/
@ -430,233 +398,21 @@ function Grid(element, options, methods) {
function renderSegs(segRows) { function renderSegs(segCols) {
//renderSegs2(segRows); _renderDaySegs(
//return; segCols,
var html='', view,
i, len = segRows.length, levels, 0,
tr, td, viewWidth,
innerDiv, function(i) {
top, return tbody.find('tr:eq('+i+')');
rowContentHeight, },
j, segs, dayContentPositions.left,
levelHeight, dayContentPositions.right,
k, seg, segmentContainer,
event, bootstrapEventHandlers
className, );
left, right,
eventElement,
triggerRes,
l=0,
_eventElements,
eventLefts=[], eventRights=[],
eventHSides=[],
eventOuterHeights=[];
for (i=0; i<len; i++) {
levels = segRows[i];
for (j=0; j<levels.length; j++) {
segs = levels[j];
for (k=0; k<segs.length; k++) {
seg = segs[k];
event = seg.event;
className = 'fc-event fc-event-hori ';
if (rtl) {
if (seg.isStart) {
className += 'fc-corner-right ';
} }
if (seg.isEnd) {
className += 'fc-corner-left ';
}
left = seg.isEnd ? 0 : dayContentLeft(seg.end.getDay()-1);
right = seg.isStart ? viewWidth : dayContentRight(seg.start.getDay());
}else{
if (seg.isStart) {
className += 'fc-corner-left ';
}
if (seg.isEnd) {
className += 'fc-corner-right ';
}
left = seg.isStart ? dayContentLeft(seg.start.getDay()) : 0;
right = seg.isEnd ? dayContentRight(seg.end.getDay()-1) : viewWidth;
}
eventLefts[l] = left;
eventRights[l] = right;
html +=
"<div class='" + className + event.className.join(' ') + "' style='position:absolute;z-index:8;left:"+left+"px'>" +
"<a" + (event.url ? " href='" + htmlEscape(event.url) + "'" : '') + ">" +
(!event.allDay && seg.isStart ?
"<span class='fc-event-time'>" +
htmlEscape(formatDates(event.start, event.end, view.option('timeFormat'), options)) +
"</span>"
:'') +
"<span class='fc-event-title'>" + htmlEscape(event.title) + "</span>" +
"</a>" +
"</div>";
l++;
}
}
}
segmentContainer.html(html);
_eventElements = segmentContainer[0].childNodes;
l = 0;
for (i=0; i<len; i++) {
levels = segRows[i];
for (j=0; j<levels.length; j++) {
segs = levels[j];
for (k=0; k<segs.length; k++) {
seg = segs[k];
event = seg.event;
eventElement = $(_eventElements[l]);
triggerRes = view.trigger('eventRender', event, event, eventElement);
if (triggerRes !== false) {
if (triggerRes && typeof triggerRes != 'boolean') {
eventElement = $(triggerRes).appendTo(segmentContainer);
}
eventOuterHeights[l] = eventElement.outerHeight(true);
eventHSides[l] = hsides(eventElement, true);
seg.element = eventElement;
bootstrapEventHandlers(event, seg, eventElement);
view.reportEventElement(event, eventElement);
}
l++;
}
}
}
l = 0;
for (i=0; i<len; i++) {
levels = segRows[i];
tr = tbody.find('tr:eq('+i+')');
td = tr.find('td:first');
innerDiv = td.find('div.fc-day-content div').css('position', 'relative');
top = safePosition(innerDiv, td, tr, tbody).top;
rowContentHeight = 0;
for (j=0; j<levels.length; j++) {
segs = levels[j];
levelHeight = 0;
for (k=0; k<segs.length; k++) {
seg = segs[k];
if (eventElement = seg.element) {
eventElement.css('top', top);
if (rtl && rtlLeftDiff == undefined) {
// bug in IE6 where offsets are miscalculated with direction:rtl
rtlLeftDiff = eventLefts[l] - eventElement.position().left;
if (rtlLeftDiff) {
eventElement.css('left', eventLefts[l] + rtlLeftDiff);
}
}
eventElement.width(eventRights[l] - eventLefts[l] - eventHSides[l]);
view.trigger('eventAfterRender', event, event, eventElement);
levelHeight = Math.max(levelHeight, eventOuterHeights[l]);
}
l++;
}
rowContentHeight += levelHeight;
top += levelHeight;
}
innerDiv.height(rowContentHeight);
}
}
/*
// the original function
function renderSegs2(segRows) {
var i, len = segRows.length, levels,
tr, td,
innerDiv,
top,
rowContentHeight,
j, segs,
levelHeight,
k, seg,
event,
className,
left, right,
eventElement, eventAnchor,
triggerRes;
for (i=0; i<len; i++) {
levels = segRows[i];
tr = tbody.find('tr:eq('+i+')');
td = tr.find('td:first');
innerDiv = td.find('div.fc-day-content div').css('position', 'relative');
top = safePosition(innerDiv, td, tr, tbody).top;
rowContentHeight = 0;
for (j=0; j<levels.length; j++) {
segs = levels[j];
levelHeight = 0;
for (k=0; k<segs.length; k++) {
seg = segs[k];
event = seg.event;
className = 'fc-event fc-event-hori ';
if (rtl) {
left = seg.isEnd ? 0 : dayContentLeft(seg.end.getDay()-1);
right = seg.isStart ? viewWidth : dayContentRight(seg.start.getDay());
if (seg.isStart) {
className += 'fc-corner-right ';
}
if (seg.isEnd) {
className += 'fc-corner-left ';
}
}else{
left = seg.isStart ? dayContentLeft(seg.start.getDay()) : 0;
right = seg.isEnd ? dayContentRight(seg.end.getDay()-1) : viewWidth;
if (seg.isStart) {
className += 'fc-corner-left ';
}
if (seg.isEnd) {
className += 'fc-corner-right ';
}
}
eventElement = $("<div class='" + className + event.className.join(' ') + "'/>")
.append(eventAnchor = $("<a/>")
.append(event.allDay || !seg.isStart ? null :
$("<span class='fc-event-time'/>")
.html(formatDates(event.start, event.end, view.option('timeFormat'), options)))
.append($("<span class='fc-event-title'/>")
.text(event.title)));
if (event.url) {
eventAnchor.attr('href', event.url);
}
triggerRes = view.trigger('eventRender', event, event, eventElement);
if (triggerRes !== false) {
if (triggerRes && typeof triggerRes != 'boolean') {
eventElement = $(triggerRes);
}
eventElement
.css({
position: 'absolute',
top: top,
left: left + (rtlLeftDiff||0),
zIndex: 8
})
.appendTo(element);
setOuterWidth(eventElement, right-left, true);
if (rtl && rtlLeftDiff == undefined) {
// bug in IE6 where offsets are miscalculated with direction:rtl
rtlLeftDiff = left - eventElement.position().left;
if (rtlLeftDiff) {
eventElement.css('left', left + rtlLeftDiff);
}
}
view.eventElementHandlers(event, eventElement);
if (event.editable || event.editable == undefined && options.editable) {
draggableEvent(event, eventElement);
if (seg.isEnd) {
view.resizableDayEvent(event, eventElement, colWidth);
}
}
view.reportEventElement(event, eventElement);
view.trigger('eventAfterRender', event, event, eventElement);
levelHeight = Math.max(levelHeight, eventElement.outerHeight(true));
}
}
rowContentHeight += levelHeight;
top += levelHeight;
}
innerDiv.height(rowContentHeight);
}
}
*/
@ -683,7 +439,7 @@ function Grid(element, options, methods) {
} }
} }
attached = true; attached = true;
view.trigger('eventMouseover', this, event, ev); view.trigger('eventMouseover', this, event, ev); // TODO: make sure this isn't being fired twice
} }
}); });
} }
@ -751,3 +507,131 @@ function Grid(element, options, methods) {
}; };
function _renderDaySegs(segRows, view, minLeft, maxLeft, getTr, dayContentLeft, dayContentRight, segmentContainer, bootstrapEventHandlers) {
var options=view.options,
rtl=options.isRTL,
event,
className,
left,
right,
eventLefts=[],
eventRights=[],
html='',
eventElements,
eventElement,
triggerRes,
eventOuterHeights=[],
eventHSides=[],
l=0,
i=0, len=segRows.length, levels,
tr,
td,
innerDiv,
top,
rowContentHeight,
j, segs,
k, seg;
// calculate desired position/dimensions, create html
eachLeaf(segRows, function(l, seg) {
event = seg.event;
className = 'fc-event fc-event-hori ';
if (rtl) {
if (seg.isStart) {
className += 'fc-corner-right ';
}
if (seg.isEnd) {
className += 'fc-corner-left ';
}
left = seg.isEnd ? dayContentLeft(seg.end.getDay()-1) : minLeft;
right = seg.isStart ? dayContentRight(seg.start.getDay()) : maxLeft;
}else{
if (seg.isStart) {
className += 'fc-corner-left ';
}
if (seg.isEnd) {
className += 'fc-corner-right ';
}
left = seg.isStart ? dayContentLeft(seg.start.getDay()) : minLeft;
right = seg.isEnd ? dayContentRight(seg.end.getDay()-1) : maxLeft;
}
eventLefts[l] = left;
eventRights[l] = right;
html +=
"<div class='" + className + event.className.join(' ') + "' style='position:absolute;z-index:8;left:"+left+"px'>" +
"<a" + (event.url ? " href='" + htmlEscape(event.url) + "'" : '') + ">" +
(!event.allDay && seg.isStart ?
"<span class='fc-event-time'>" +
htmlEscape(formatDates(event.start, event.end, view.option('timeFormat'), options)) +
"</span>"
:'') +
"<span class='fc-event-title'>" + htmlEscape(event.title) + "</span>" +
"</a>" +
"</div>";
});
segmentContainer.html(html);
eventElements = segmentContainer.children();
// retrieve elements, run through eventRender callback, record outer-edge dimensions
eachLeaf(segRows, function(l, seg) {
event = seg.event;
eventElement = eventElements.eq(l);
triggerRes = view.trigger('eventRender', event, event, eventElement);
if (triggerRes === false) {
eventElement.remove();
}else{
if (triggerRes && triggerRes !== true) {
eventElement.remove();
eventElement = $(triggerRes)
.css('left', eventLefts[l])
.appendTo(segmentContainer);
}
seg.element = eventElement;
eventOuterHeights[l] = eventElement.outerHeight(true);
eventHSides[l] = hsides(eventElement, true);
bootstrapEventHandlers(event, seg, eventElement);
view.reportEventElement(event, eventElement);
}
});
// set all positions/dimensions at once
for (; i<len; i++) {
levels = segRows[i];
tr = getTr(i);
td = tr.find('td:first');
innerDiv = td.find('div.fc-day-content div')
.css('position', 'relative')
.height(''); // this is needed for IE7 to get an accurate position
top = innerDiv.position().top + topCorrect(tr, td);
rowContentHeight = 0;
for (j=0; j<levels.length; j++) {
segs = levels[j];
levelHeight = 0;
for (k=0; k<segs.length; k++) {
seg = segs[k];
if (eventElement = seg.element) {
eventElement.css('top', top);
//if (rtl && rtlLeftDiff == undefined) {
// // bug in IE6 where offsets are miscalculated with direction:rtl
// rtlLeftDiff = eventLefts[l] - eventElement.position().left;
// if (rtlLeftDiff) {
// eventElement.css('left', eventLefts[l] + rtlLeftDiff);
// }
//}
eventElement.width(eventRights[l] - eventLefts[l] - eventHSides[l]);
event = seg.event;
view.trigger('eventAfterRender', event, event, eventElement);
levelHeight = Math.max(levelHeight, eventOuterHeights[l]);
}
l++;
}
rowContentHeight += levelHeight;
top += levelHeight;
}
innerDiv.height(rowContentHeight);
}
}

View file

@ -4,7 +4,8 @@
var DAY_MS = 86400000, var DAY_MS = 86400000,
HOUR_MS = 3600000, HOUR_MS = 3600000,
MINUTE_MS = 60000; MINUTE_MS = 60000,
arrayPop = Array.prototype.pop; // for eachLeaf
function addYears(d, n, keepTime) { function addYears(d, n, keepTime) {
d.setFullYear(d.getFullYear() + n); d.setFullYear(d.getFullYear() + n);
@ -296,7 +297,7 @@ var dateFormatters = {
/* Element Dimensions /* Element Dimensions
-----------------------------------------------------------------------------*/ -----------------------------------------------------------------------------*/
function setOuterWidth(element, width, includeMargins) { // TODO: probably eventually remove this function setOuterWidth(element, width, includeMargins) {
element.each(function() { element.each(function() {
var e = $(this); var e = $(this);
e.width(width - hsides(e, includeMargins)); e.width(width - hsides(e, includeMargins));
@ -313,7 +314,7 @@ function hsides(e, includeMargins) {
: 0); : 0);
} }
function setOuterHeight(element, height, includeMargins) { // TODO: probably eventually remove this function setOuterHeight(element, height, includeMargins) {
element.each(function() { element.each(function() {
var e = $(this); var e = $(this);
e.height(height - vsides(e, includeMargins)); e.height(height - vsides(e, includeMargins));
@ -335,22 +336,22 @@ function vsides(e, includeMargins) {
/* Position Calculation /* Position Calculation
-----------------------------------------------------------------------------*/ -----------------------------------------------------------------------------*/
// nasty bugs in opera 9.25 // nasty bugs in opera 9.25
// position() returning relative to direct parent // position()'s top returning incorrectly with TR/TD or elements within TD
var operaPositionBug; var topBug;
function reportTBody(tbody) { function topCorrect(tr, td) {
if (operaPositionBug == undefined) { if (topBug !== false && tr.is('tr')) {
operaPositionBug = tbody.position().top != tbody.find('tr').position().top; var tbody = tr.parent(),
trTop = tr.position().top;
if (topBug == undefined) {
topBug = trTop != tr.children().position().top;
}
if (topBug) {
return tbody.position().top + trTop - (td ? td.position().top : 0);
} }
} }
return 0;
function safePosition(element, td, tr, tbody) {
var position = element.position();
if (operaPositionBug) {
position.top += tbody.position().top + tr.position().top - td.position().top;
}
return position;
} }
@ -360,24 +361,23 @@ function safePosition(element, td, tr, tbody) {
function HoverMatrix(changeCallback) { function HoverMatrix(changeCallback) {
var tops=[], lefts=[], var t=this,
tops=[], lefts=[],
prevRowE, prevColE, prevRowE, prevColE,
origRow, origCol, origRow, origCol,
currRow, currCol; currRow, currCol;
this.row = function(e, topBug) { t.row = function(e) {
prevRowE = $(e); prevRowE = $(e);
tops.push(prevRowE.offset().top + ( tops.push(prevRowE.offset().top + topCorrect(prevRowE));
(operaPositionBug && prevRowE.is('tr')) ? prevRowE.parent().position().top : 0
));
}; };
this.col = function(e) { t.col = function(e) {
prevColE = $(e); prevColE = $(e);
lefts.push(prevColE.offset().left); lefts.push(prevColE.offset().left);
}; };
this.mouse = function(x, y) { t.mouse = function(x, y) {
if (origRow == undefined) { if (origRow == undefined) {
tops.push(tops[tops.length-1] + prevRowE.outerHeight()); tops.push(tops[tops.length-1] + prevRowE.outerHeight());
lefts.push(lefts[lefts.length-1] + prevColE.outerWidth()); lefts.push(lefts[lefts.length-1] + prevColE.outerWidth());
@ -392,13 +392,13 @@ function HoverMatrix(changeCallback) {
currRow = r; currRow = r;
currCol = c; currCol = c;
if (r == -1 || c == -1) { if (r == -1 || c == -1) {
this.cell = null; t.cell = null;
}else{ }else{
if (origRow == undefined) { if (origRow == undefined) {
origRow = r; origRow = r;
origCol = c; origCol = c;
} }
this.cell = { t.cell = {
row: r, row: r,
col: c, col: c,
top: tops[r], top: tops[r],
@ -410,7 +410,7 @@ function HoverMatrix(changeCallback) {
colDelta: c-origCol colDelta: c-origCol
}; };
} }
changeCallback(this.cell); changeCallback(t.cell);
} }
}; };
@ -452,4 +452,50 @@ function htmlEscape(s) {
.replace(/"/g, '&quot;') .replace(/"/g, '&quot;')
} }
function eachLeaf(node, callback, leafIndex, indexTrail) {
if (node.pop == arrayPop) { // is an array?
for (var i=0, len=node.length; i<len; i++) {
leafIndex = eachLeaf(node[i], callback, leafIndex||0, [i].concat(indexTrail||[]));
}
return leafIndex;
}
callback.apply(node, [leafIndex, node].concat(indexTrail));
return leafIndex + 1;
}
function HorizontalPositionCache(getElement) {
var t = this,
elements = {},
lefts = {},
rights = {};
function e(i) {
return elements[i] =
elements[i] || getElement(i);
}
t.left = function(i) {
return lefts[i] =
lefts[i] == undefined ? e(i).position().left : lefts[i];
};
t.right = function(i) {
return rights[i] =
rights[i] == undefined ? t.left(i) + e(i).width() : rights[i];
};
t.clear = function() {
elements = {};
lefts = {};
rights = {};
};
}

View file

@ -352,3 +352,5 @@ function segsCollide(seg1, seg2) {
return seg1.end > seg2.start && seg1.start < seg2.end; return seg1.end > seg2.start && seg1.start < seg2.end;
} }