From a00104b10b7708de849f5b607a68b376a0363d7c Mon Sep 17 00:00:00 2001 From: Adam Shaw Date: Sun, 24 Jan 2010 22:56:57 -0800 Subject: [PATCH] event rendering optimizations for agenda view w/ refactoring --- src/agenda.js | 582 ++++++++++++++++---------------------------------- src/grid.js | 414 +++++++++++++---------------------- src/util.js | 98 ++++++--- src/view.js | 2 + 4 files changed, 411 insertions(+), 685 deletions(-) diff --git a/src/agenda.js b/src/agenda.js index a359309..f2d380b 100644 --- a/src/agenda.js +++ b/src/agenda.js @@ -88,15 +88,16 @@ function Agenda(element, options, methods) { nwe, // no weekends (int) rtl, dis, dit, // day index sign / translate minMinute, maxMinute, - dayContentElements=[], - dayContentLefts=[], - dayContentRights=[], + colContentPositions = new HorizontalPositionCache(function(col) { + return bg.find('td:eq(' + col + ') div div'); + }), // ... view = $.extend(this, viewMethods, methods, { renderAgenda: renderAgenda, renderEvents: renderEvents, rerenderEvents: rerenderEvents, + clearEvents: clearEvents, updateSize: updateSize, shown: resetScroll, defaultEventEnd: function(event) { @@ -287,19 +288,16 @@ function Agenda(element, options, methods) { function updateSize(width, height) { viewWidth = width; viewHeight = height; - dayContentLefts = []; - dayContentRights = []; + colContentPositions.clear(); - bodyTable.width(''); + body.width(width); body.height(height - head.height()); - - // need this for IE6/7. triggers clientWidth to be calculated for - // later user in this function. this is ridiculous - body[0].clientWidth; + bodyTable.width(''); var topTDs = head.find('tr:first th'), stripeTDs = bg.find('td'), - contentWidth = body[0].clientWidth; + contentWidth = slotSegmentContainer.width(); // body[0].clientWidth isn't reliable here in IE6 + bodyTable.width(contentWidth); // time-axis width @@ -327,10 +325,6 @@ function Agenda(element, options, methods) { }); 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) { @@ -402,397 +396,153 @@ 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 function renderDaySegs(segRow) { if (options.allDaySlot) { - var html='', - 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, - left, right, - triggerRes, - l=0, - _eventElements, - eventLefts=[], eventRights=[], - eventHSides=[], - eventOuterHeights=[]; - for (i=0; i" + - "" + - (!event.allDay && seg.isStart ? - "" + - htmlEscape(formatDates(event.start, event.end, view.option('timeFormat'), options)) + - "" - :'') + - "" + htmlEscape(event.title) + "" + - "" + - ""; - l++; - } - } - daySegmentContainer.html(html); - _eventElements = daySegmentContainer[0].childNodes; - l = 0; - for (i=0; i") - .append(anchorElement = $("") - .append($("") - .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 function renderSlotSegs(segCols) { - renderSlotSegs2(segCols); - /* - var html='', - colI, colLen=segCols.length, col, - levelI, level, - segI, seg, - forward, - event, - top, bottom, - tdInner, - width, left, - eventElement, + + var event, className, - l=0, + top, + bottom, + leftmost, + availWidth, + forward, + width, + left, + eventTops=[], eventLefts=[], eventOuterWidths=[], - triggerRes; - for (colI=0; colI") - .append(anchorElement = $("") - .append(timeElement = $("") - .text(formatDates(event.start, event.end, view.option('timeFormat')))) - .append(titleElement = $("") - .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', - zIndex: 8, - top: top, - left: left - }) - .appendTo(bodyContent); - 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); - } - } - } - view.reportEventElement(event, eventElement); - view.trigger('eventAfterRender', event, event, eventElement); + 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 += + "
" + + "" + + "" + htmlEscape(formatDates(event.start, event.end, view.option('timeFormat'))) + "" + + "" + htmlEscape(event.title) + "" + + "" + + "" + + ((event.editable || event.editable == undefined && options.editable) && !options.disableResizing && $.fn.resizable ? + "
=
" + : '') + + "
"; + }); + 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); + if (triggerRes === false) { + eventElement.remove(); + }else{ + if (triggerRes && triggerRes !== true) { + eventElement.remove(); + eventElement = $(triggerRes) + .css({ + top: eventTops[l], + left: eventLefts[l] + }) + .appendTo(slotSegmentContainer); } + 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); } - } + }); + + // 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); + } + }); + } @@ -814,6 +564,45 @@ function Agenda(element, options, methods) { return addMinutes(cloneDate(event.start), options.defaultEventMinutes); } } + + + + 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); + } + }); + } @@ -1038,7 +827,9 @@ function Agenda(element, options, methods) { var slotDelta, prevSlotDelta; eventElement .resizable({ - handles: 's', + handles: { + s: 'div.ui-resizable-s' + }, grid: slotHeight, start: function(ev, ui) { 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 } } - }) - .find('div.ui-resizable-s').text('='); + }); } } - // ALL-DAY event resizing w/ 'view' methods... - - /* Misc @@ -1105,9 +892,16 @@ function Agenda(element, options, methods) { td = tr.find('td'), innerDiv = td.find('div'); 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; + } + } diff --git a/src/grid.js b/src/grid.js index 5c37549..1ef4d71 100644 --- a/src/grid.js +++ b/src/grid.js @@ -110,7 +110,7 @@ views.basicDay = function(element, options) { // rendering bugs -var tdHeightBug, rtlLeftDiff; +var tdHeightBug; function Grid(element, options, methods) { @@ -125,9 +125,9 @@ function Grid(element, options, methods) { cachedEvents=[], segments=[], segmentContainer, - dayContentElements=[], - dayContentLefts=[], - dayContentRights=[], + dayContentPositions = new HorizontalPositionCache(function(dayOfWeek) { + return tbody.find('td:eq(' + ((dayOfWeek - Math.max(firstDay,nwe)+colCnt) % colCnt) + ') div div') + }), // ... // initialize superclass @@ -135,6 +135,7 @@ function Grid(element, options, methods) { renderGrid: renderGrid, renderEvents: renderEvents, rerenderEvents: rerenderEvents, + clearEvents: clearEvents, updateSize: updateSize, defaultEventEnd: function(event) { // calculates an end if event doesnt have one, mostly for resizing return cloneDate(event.start); @@ -322,8 +323,7 @@ function Grid(element, options, methods) { function updateSize(width, height) { // does not render/position the events viewWidth = width; viewHeight = height; - dayContentLefts = []; - dayContentRights = []; + dayContentPositions.clear(); var leftTDs = tbody.find('tr td:first-child'), tbodyHeight = viewHeight - thead.height(), @@ -336,8 +336,6 @@ function Grid(element, options, methods) { rowHeight2 = tbodyHeight - rowHeight1*(rowCnt-1); } - reportTBody(tbody); - if (tdHeightBug == undefined) { // bug in firefox where cell height includes padding var tr = tbody.find('tr:first'), @@ -361,36 +359,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 @@ -430,235 +398,23 @@ function Grid(element, options, methods) { - function renderSegs(segRows) { - //renderSegs2(segRows); - //return; - var html='', - i, len = segRows.length, levels, - tr, td, - innerDiv, - top, - rowContentHeight, - j, segs, - levelHeight, - k, seg, - event, - className, - left, right, - eventElement, - triggerRes, - l=0, - _eventElements, - eventLefts=[], eventRights=[], - eventHSides=[], - eventOuterHeights=[]; - for (i=0; i" + - "" + - (!event.allDay && seg.isStart ? - "" + - htmlEscape(formatDates(event.start, event.end, view.option('timeFormat'), options)) + - "" - :'') + - "" + htmlEscape(event.title) + "" + - "" + - ""; - l++; - } - } - } - segmentContainer.html(html); - _eventElements = segmentContainer[0].childNodes; - l = 0; - for (i=0; i") - .append(eventAnchor = $("") - .append(event.allDay || !seg.isStart ? null : - $("") - .html(formatDates(event.start, event.end, view.option('timeFormat'), options))) - .append($("") - .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); - } - } - */ - - function visEventEnd(event) { // returns exclusive 'visible' end, for rendering if (event.end) { @@ -683,7 +439,7 @@ function Grid(element, options, methods) { } } 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 += + ""; + }); + 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 seg2.start && seg1.start < seg2.end; } + +