refactor and simplify the way view switching/rerendering works

This commit is contained in:
Adam Shaw
2013-08-06 23:23:03 -07:00
parent 6da36c7bdf
commit d2aa9e9f4c
8 changed files with 184 additions and 217 deletions
+159 -144
View File
@@ -43,10 +43,8 @@ function Calendar(element, options, eventSources) {
var content;
var tm; // for making theme classes
var currentView;
var viewInstances = {};
var elementOuterWidth;
var suggestedViewHeight;
var absoluteViewElement;
var resizeUID = 0;
var ignoreWindowResize = 0;
var date = new Date();
@@ -65,10 +63,10 @@ function Calendar(element, options, eventSources) {
function render(inc) {
if (!content) {
initialRender();
}else{
}
else {
// mainly for the public API
calcSize();
markSizesDirty();
markEventsDirty();
renderView(inc);
}
}
@@ -86,13 +84,16 @@ function Calendar(element, options, eventSources) {
if (options.theme) {
element.addClass('ui-widget');
}
content = $("<div class='fc-content' style='position:relative'/>")
.prependTo(element);
header = new Header(t, options);
headerElement = header.render();
if (headerElement) {
element.prepend(headerElement);
}
changeView(options.defaultView);
if (options.handleWindowResize) {
@@ -140,132 +141,88 @@ function Calendar(element, options, eventSources) {
/* View Rendering
-----------------------------------------------------------------------------*/
// TODO: improve view switching (still weird transition in IE, and FF has whiteout problem)
function changeView(newViewName) {
if (!currentView || newViewName != currentView.name) {
ignoreWindowResize++; // because setMinHeight might change the height before render (and subsequently setSize) is reached
unselect();
var oldView = currentView;
var newViewElement;
if (oldView) {
(oldView.beforeHide || noop)(); // called before changing min-height. if called after, scroll state is reset (in Opera)
setMinHeight(content, content.height());
oldView.element.hide();
}else{
setMinHeight(content, 1); // needs to be 1 (not 0) for IE7, or else view dimensions miscalculated
}
content.css('overflow', 'hidden');
currentView = viewInstances[newViewName];
if (currentView) {
currentView.element.show();
}else{
currentView = viewInstances[newViewName] = new fcViews[newViewName](
newViewElement = absoluteViewElement =
$("<div class='fc-view fc-view-" + newViewName + "' style='position:absolute'/>")
.appendTo(content),
t // the calendar object
);
}
if (oldView) {
header.deactivateButton(oldView.name);
}
header.activateButton(newViewName);
renderView(); // after height has been set, will make absoluteViewElement's position=relative, then set to null
content.css('overflow', '');
if (oldView) {
setMinHeight(content, 1);
}
if (!newViewElement) {
(currentView.afterShow || noop)(); // called after setting min-height/overflow, so in final scroll state (for Opera)
}
ignoreWindowResize++;
_changeView(newViewName);
ignoreWindowResize--;
}
}
function _changeView(newViewName) {
if (currentView) {
freezeContentHeight();
currentView.element.remove();
header.deactivateButton(currentView.name);
}
header.activateButton(newViewName);
currentView = new fcViews[newViewName](
$("<div class='fc-view fc-view-" + newViewName + "' style='position:relative'/>")
.appendTo(content),
t // the calendar object
);
_renderView();
unfreezeContentHeight();
}
function renderView(inc) {
if (elementVisible()) {
ignoreWindowResize++; // because renderEvents might temporarily change the height before setSize is reached
unselect();
if (suggestedViewHeight === undefined) {
calcSize();
}
var forceEventRender = false;
if (!currentView.start || inc || date < currentView.start || date >= currentView.end) {
// view must render an entire new date range (and refetch/render events)
currentView.render(date, inc || 0); // responsible for clearing events
setSize(true);
forceEventRender = true;
}
else if (currentView.sizeDirty) {
// view must resize (and rerender events)
currentView.clearEvents();
setSize();
forceEventRender = true;
}
else if (currentView.eventsDirty) {
currentView.clearEvents();
forceEventRender = true;
}
currentView.sizeDirty = false;
currentView.eventsDirty = false;
updateEvents(forceEventRender);
elementOuterWidth = element.outerWidth();
header.updateTitle(currentView.title);
var today = new Date();
if (today >= currentView.start && today < currentView.end) {
header.disableButton('today');
}else{
header.enableButton('today');
}
ignoreWindowResize++;
_renderView(inc);
ignoreWindowResize--;
currentView.trigger('viewDisplay', _element);
}
}
function _renderView(inc) {
if (!currentView.start) { // has not been rendered before
renderViewDateRange();
getAndRenderEvents();
}
else if (inc || date < currentView.start || date >= currentView.end) {
unselect();
clearEvents();
renderViewDateRange(inc);
getAndRenderEvents();
}
currentView.trigger('viewDisplay', _element); // deprecated
}
function renderViewDateRange(inc) {
freezeContentHeight();
currentView.render(date, inc || 0); // the view's render method ONLY renders the skeleton, nothing else
setSize();
unfreezeContentHeight();
(currentView.afterRender || noop)();
updateTitle();
updateTodayButton();
}
/* Resizing
-----------------------------------------------------------------------------*/
function updateSize() {
markSizesDirty();
if (elementVisible()) {
unselect();
clearEvents();
calcSize();
setSize();
unselect();
currentView.clearEvents();
currentView.renderEvents(events);
currentView.sizeDirty = false;
renderEvents();
}
}
function markSizesDirty() {
$.each(viewInstances, function(i, inst) {
inst.sizeDirty = true;
});
}
function calcSize() {
if (options.contentHeight) {
suggestedViewHeight = options.contentHeight;
@@ -279,15 +236,18 @@ function Calendar(element, options, eventSources) {
}
function setSize(dateChanged) { // todo: dateChanged?
ignoreWindowResize++;
currentView.setHeight(suggestedViewHeight, dateChanged);
if (absoluteViewElement) {
absoluteViewElement.css('position', 'relative');
absoluteViewElement = null;
function setSize() {
if (suggestedViewHeight === undefined) {
calcSize(); // for first time
}
currentView.setWidth(content.width(), dateChanged);
ignoreWindowResize++;
currentView.setHeight(suggestedViewHeight);
currentView.setWidth(content.width());
ignoreWindowResize--;
elementOuterWidth = element.outerWidth();
}
@@ -316,52 +276,84 @@ function Calendar(element, options, eventSources) {
/* Event Fetching/Rendering
-----------------------------------------------------------------------------*/
// TODO: going forward, most of this stuff should be directly handled by the view
function refetchEvents() { // can be called as an API method
clearEvents();
fetchAndRenderEvents();
}
function rerenderEvents(modifiedEventID) { // can be called as an API method
clearEvents();
renderEvents(modifiedEventID);
}
function renderEvents(modifiedEventID) { // TODO: remove modifiedEventID hack
if (elementVisible()) {
currentView.setEventData(events); // for View.js, TODO: unify with renderEvents
currentView.renderEvents(events, modifiedEventID); // actually render the DOM elements
currentView.trigger('eventAfterAllRender');
}
}
function clearEvents() {
currentView.clearEvents(); // actually remove the DOM elements
currentView.clearEventData(); // for View.js, TODO: unify with clearEvents
}
// fetches events if necessary, rerenders events if necessary (or if forced)
function updateEvents(forceRender) {
function getAndRenderEvents() {
if (!options.lazyFetching || isFetchNeeded(currentView.visStart, currentView.visEnd)) {
refetchEvents();
fetchAndRenderEvents();
}
else if (forceRender) {
rerenderEvents();
else {
renderEvents();
}
}
function refetchEvents() {
fetchEvents(currentView.visStart, currentView.visEnd); // will call reportEvents
function fetchAndRenderEvents() {
fetchEvents(currentView.visStart, currentView.visEnd);
// ... will call reportEvents
// ... which will call renderEvents
}
// called when event data arrives
function reportEvents(_events) {
events = _events;
rerenderEvents();
renderEvents();
}
// called when a single event's data has been changed
function reportEventChange(eventID) {
rerenderEvents(eventID);
}
// attempts to rerenderEvents
function rerenderEvents(modifiedEventID) {
markEventsDirty();
if (elementVisible()) {
currentView.clearEvents();
currentView.renderEvents(events, modifiedEventID);
currentView.eventsDirty = false;
}
/* Header Updating
-----------------------------------------------------------------------------*/
function updateTitle() {
header.updateTitle(currentView.title);
}
function markEventsDirty() {
$.each(viewInstances, function(i, inst) {
inst.eventsDirty = true;
});
function updateTodayButton() {
var today = new Date();
if (today >= currentView.start && today < currentView.end) {
header.disableButton('today');
}
else {
header.enableButton('today');
}
}
@@ -442,6 +434,29 @@ function Calendar(element, options, eventSources) {
function getDate() {
return cloneDate(date);
}
/* Height "Freezing"
-----------------------------------------------------------------------------*/
function freezeContentHeight() {
content.css({
width: '100%',
height: content.height(),
overflow: 'hidden'
});
}
function unfreezeContentHeight() {
content.css({
width: '',
height: '',
overflow: ''
});
}
-6
View File
@@ -16,8 +16,6 @@ function AgendaEventRenderer() {
var isEventDraggable = t.isEventDraggable;
var isEventResizable = t.isEventResizable;
var eventEnd = t.eventEnd;
var reportEvents = t.reportEvents;
var reportEventClear = t.reportEventClear;
var eventElementHandlers = t.eventElementHandlers;
var setHeight = t.setHeight;
var getDaySegmentContainer = t.getDaySegmentContainer;
@@ -59,7 +57,6 @@ function AgendaEventRenderer() {
function renderEvents(events, modifiedEventId) {
reportEvents(events);
var i, len=events.length,
dayEvents=[],
slotEvents=[];
@@ -77,13 +74,10 @@ function AgendaEventRenderer() {
}
renderSlotSegs(compileSlotSegs(slotEvents), modifiedEventId);
trigger('eventAfterAllRender');
}
function clearEvents() {
reportEventClear();
getDaySegmentContainer().empty();
getSlotSegmentContainer().empty();
}
+10 -21
View File
@@ -29,8 +29,7 @@ function AgendaView(element, calendar, viewName) {
t.renderAgenda = renderAgenda;
t.setWidth = setWidth;
t.setHeight = setHeight;
t.beforeHide = beforeHide;
t.afterShow = afterShow;
t.afterRender = afterRender;
t.defaultEventEnd = defaultEventEnd;
t.timePosition = timePosition;
t.getIsCellAllDay = getIsCellAllDay;
@@ -67,7 +66,6 @@ function AgendaView(element, calendar, viewName) {
AgendaEventRenderer.call(t);
var opt = t.opt;
var trigger = t.trigger;
var clearEvents = t.clearEvents;
var renderOverlay = t.renderOverlay;
var clearOverlays = t.clearOverlays;
var reportSelection = t.reportSelection;
@@ -120,7 +118,6 @@ function AgendaView(element, calendar, viewName) {
var colPositions;
var colContentPositions;
var slotTopCache = {};
var savedScrollTop;
var tm;
var rtl;
@@ -142,11 +139,12 @@ function AgendaView(element, calendar, viewName) {
function renderAgenda(c) {
colCnt = c;
updateOptions();
if (!dayTable) {
if (!dayTable) { // first time rendering?
buildSkeleton(); // builds day table, slot area, events containers
}else{
}
else {
buildDayTable(); // rebuilds day table
clearEvents();
}
}
@@ -426,7 +424,7 @@ function AgendaView(element, calendar, viewName) {
-----------------------------------------------------------------------*/
function setHeight(height, dateChanged) {
function setHeight(height) {
if (height === undefined) {
height = viewHeight;
}
@@ -451,10 +449,6 @@ function AgendaView(element, calendar, viewName) {
snapRatio = opt('slotMinutes') / snapMinutes;
snapHeight = slotHeight / snapRatio;
if (dateChanged) {
resetScroll();
}
}
@@ -521,15 +515,10 @@ function AgendaView(element, calendar, viewName) {
scroll();
setTimeout(scroll, 0); // overrides any previous scroll state made by the browser
}
function beforeHide() {
savedScrollTop = slotScroller.scrollTop();
}
function afterShow() {
slotScroller.scrollTop(savedScrollTop);
function afterRender() { // after the view has been freshly rendered and sized
resetScroll();
}
-3
View File
@@ -13,14 +13,11 @@ function BasicEventRenderer() {
function renderEvents(events, modifiedEventId) {
t.reportEvents(events);
t.renderDayEvents(events, modifiedEventId);
t.trigger('eventAfterAllRender');
}
function clearEvents() {
t.reportEventClear();
t.getDaySegmentContainer().empty();
}
+5 -23
View File
@@ -40,7 +40,6 @@ function BasicView(element, calendar, viewName) {
BasicEventRenderer.call(t);
var opt = t.opt;
var trigger = t.trigger;
var clearEvents = t.clearEvents;
var renderOverlay = t.renderOverlay;
var clearOverlays = t.clearOverlays;
var daySelectionMousedown = t.daySelectionMousedown;
@@ -95,12 +94,11 @@ function BasicView(element, calendar, viewName) {
colCnt = _colCnt;
showNumbers = _showNumbers;
updateOptions();
var firstTime = !body;
if (firstTime) {
if (!body) {
buildEventContainer();
}else{
clearEvents();
}
buildTable();
}
@@ -131,7 +129,6 @@ function BasicView(element, calendar, viewName) {
function buildTable() {
var html = buildTableHTML();
lockHeight(); // the unlock happens later, in setHeight()...
if (table) {
table.remove();
}
@@ -318,14 +315,13 @@ function BasicView(element, calendar, viewName) {
bodyFirstCells.each(function(i, _cell) {
if (i < rowCnt) {
cell = $(_cell);
setMinHeight(
cell.find('> div'),
cell.find('> div').css(
'min-height',
(i==rowCnt-1 ? rowHeightLast : rowHeight) - vsides(cell)
);
}
});
unlockHeight();
}
@@ -520,19 +516,5 @@ function BasicView(element, calendar, viewName) {
function allDayRow(i) {
return bodyRows.eq(i);
}
// makes sure height doesn't collapse while we destroy/render new cells
// (this causes a bad end-user scrollbar jump)
// TODO: generalize this for all view rendering. (also in Calendar.js)
function lockHeight() {
setMinHeight(element, element.height());
}
function unlockHeight() {
setMinHeight(element, 1);
}
}
+9 -10
View File
@@ -12,10 +12,10 @@ function View(element, calendar, viewName) {
t.trigger = trigger;
t.isEventDraggable = isEventDraggable;
t.isEventResizable = isEventResizable;
t.reportEvents = reportEvents;
t.setEventData = setEventData;
t.clearEventData = clearEventData;
t.eventEnd = eventEnd;
t.reportEventElement = reportEventElement;
t.reportEventClear = reportEventClear;
t.eventElementHandlers = eventElementHandlers;
t.showEvents = showEvents;
t.hideEvents = hideEvents;
@@ -95,8 +95,7 @@ function View(element, calendar, viewName) {
------------------------------------------------------------------------------*/
// report when view receives new events
function reportEvents(events) { // events are already normalized at this point
function setEventData(events) { // events are already normalized at this point
eventsByID = {};
var i, len=events.length, event;
for (i=0; i<len; i++) {
@@ -108,6 +107,12 @@ function View(element, calendar, viewName) {
}
}
}
function clearEventData() {
eventElements = [];
eventElementsByID = {};
}
// returns a Date object for an event's end
@@ -132,12 +137,6 @@ function View(element, calendar, viewName) {
}
function reportEventClear() {
eventElements = [];
eventElementsByID = {};
}
// attaches eventClick, eventMouseover, eventMouseout
function eventElementHandlers(event, eventElement) {
eventElement
+1 -1
View File
@@ -105,7 +105,7 @@ html .fc,
}
.fc-view {
width: 100%; /* needed for view switching (when view is absolute) */
width: 100%;
overflow: hidden;
}
-9
View File
@@ -113,15 +113,6 @@ function vborders(element) {
}
function setMinHeight(element, height) {
height = (typeof height == 'number' ? height + 'px' : height);
element.each(function(i, _element) {
_element.style.cssText += ';min-height:' + height + ';_height:' + height;
// why can't we just use .css() ? i forget
});
}
/* Misc Utils
-----------------------------------------------------------------------------*/