Bläddra i källkod

refactor and simplify the way view switching/rerendering works

Adam Shaw 12 år sedan
förälder
incheckning
d2aa9e9f4c
8 ändrade filer med 183 tillägg och 216 borttagningar
  1. 158 143
      src/Calendar.js
  2. 0 6
      src/agenda/AgendaEventRenderer.js
  3. 10 21
      src/agenda/AgendaView.js
  4. 0 3
      src/basic/BasicEventRenderer.js
  5. 5 23
      src/basic/BasicView.js
  6. 9 10
      src/common/View.js
  7. 1 1
      src/main.css
  8. 0 9
      src/util.js

+ 158 - 143
src/Calendar.js

@@ -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
+			ignoreWindowResize++;
+			_renderView(inc);
+			ignoreWindowResize--;
+		}
+	}
+
 
+	function _renderView(inc) {
+		if (!currentView.start) { // has not been rendered before
+			renderViewDateRange();
+			getAndRenderEvents();
+		}
+		else if (inc || date < currentView.start || date >= currentView.end) {
 			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--;
-			currentView.trigger('viewDisplay', _element);
+			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: ''
+		});
+	}
 	
 	
 	

+ 0 - 6
src/agenda/AgendaEventRenderer.js

@@ -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
src/agenda/AgendaView.js

@@ -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();
 	}
 	
 	

+ 0 - 3
src/basic/BasicEventRenderer.js

@@ -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
src/basic/BasicView.js

@@ -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
src/common/View.js

@@ -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
src/main.css

@@ -105,7 +105,7 @@ html .fc,
 	}
 	
 .fc-view {
-	width: 100%; /* needed for view switching (when view is absolute) */
+	width: 100%;
 	overflow: hidden;
 	}
 	

+ 0 - 9
src/util.js

@@ -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
 -----------------------------------------------------------------------------*/