Browse Source

revive event rendering

Adam Shaw 8 năm trước cách đây
mục cha
commit
6cd2191a91

+ 1 - 6
src.json

@@ -56,12 +56,7 @@
     "Header.js",
     "models/UnzonedRange.js",
     "models/ComponentFootprint.js",
-    "models/EventInstanceRepo.js",
-    "models/EventInstanceDataSource.js",
-    "models/EventInstanceDataSourceSplitter.js",
-    "models/EventInstanceChangeset.js",
-    "models/EventDataSource.js",
-    "models/RequestableEventDataSource.js",
+    "models/EventPeriod.js",
     "models/EventManager.js",
     "models/BusinessHourGenerator.js",
     "models/event/EventDefParser.js",

+ 4 - 4
src/Calendar.constraints.js

@@ -49,12 +49,12 @@ Calendar.prototype.isEventInstanceGroupAllowed = function(eventInstanceGroup) {
 
 
 Calendar.prototype.getPeerEventInstances = function(eventDef) {
-	return this.eventManager.instanceRepo.getEventInstancesWithoutId(eventDef.id);
+	return this.eventManager.getEventInstancesWithoutId(eventDef.id);
 };
 
 
 Calendar.prototype.isSelectionFootprintAllowed = function(componentFootprint) {
-	var peerEventInstances = this.eventManager.instanceRepo.getEventInstances();
+	var peerEventInstances = this.eventManager.getEventInstances();
 	var peerEventRanges = peerEventInstances.map(eventInstanceToEventRange);
 	var peerEventFootprints = this.eventRangesToEventFootprints(peerEventRanges);
 
@@ -157,7 +157,7 @@ Calendar.prototype.constraintValToFootprints = function(constraintVal, isAllDay)
 		}
 	}
 	else if (constraintVal != null) { // an ID
-		eventInstances = this.eventManager.instanceRepo.getEventInstancesWithId(constraintVal);
+		eventInstances = this.eventManager.getEventInstancesWithId(constraintVal);
 
 		return this.eventInstancesToFootprints(eventInstances);
 	}
@@ -285,7 +285,7 @@ Calendar.prototype.parseEventDefToInstances = function(eventInput) {
 		return false;
 	}
 
-	return eventDef.buildInstances(eventManager.currentUnzonedRange);
+	return eventDef.buildInstances(eventManager.currentPeriod.unzonedRange);
 };
 
 

+ 2 - 2
src/Calendar.events-api.js

@@ -123,7 +123,7 @@ Calendar.mixin({
 			eventManager.removeAllEventDefs(true); // persist=true
 		}
 		else {
-			eventManager.instanceRepo.iterEventInstances(function(eventInstance) {
+			eventManager.getEventInstances().forEach(function(eventInstance) {
 				legacyInstances.push(eventInstance.toLegacy());
 			});
 
@@ -150,7 +150,7 @@ Calendar.mixin({
 	clientEvents: function(legacyQuery) {
 		var legacyEventInstances = [];
 
-		this.eventManager.instanceRepo.iterEventInstances(function(eventInstance) {
+		this.eventManager.getEventInstances().forEach(function(eventInstance) {
 			legacyEventInstances.push(eventInstance.toLegacy());
 		});
 

+ 10 - 5
src/Calendar.js

@@ -292,7 +292,7 @@ var Calendar = FC.Calendar = Class.extend(EmitterMixin, ListenerMixin, {
 
 	rerenderEvents: function() { // API method. destroys old events if previously rendered.
 		if (this.elementVisible()) {
-			this.eventManager.tryReset();
+			this.view.flash('displayingEvents');
 		}
 	},
 
@@ -309,6 +309,12 @@ var Calendar = FC.Calendar = Class.extend(EmitterMixin, ListenerMixin, {
 			rawSources.unshift(singleRawSource);
 		}
 
+		eventManager.on('release', function(eventsPayload) {
+			_this.trigger('eventsReset', eventsPayload);
+		});
+
+		eventManager.freeze();
+
 		rawSources.forEach(function(rawSource) {
 			var source = EventSourceParser.parse(rawSource, _this);
 
@@ -316,19 +322,18 @@ var Calendar = FC.Calendar = Class.extend(EmitterMixin, ListenerMixin, {
 				eventManager.addSource(source);
 			}
 		});
+
+		eventManager.thaw();
 	},
 
 
-	// returns an EventInstanceDataSource
 	requestEvents: function(start, end) {
-		this.eventManager.request(
+		return this.eventManager.requestEvents(
 			start,
 			end,
 			this.opt('timezone'),
 			!this.opt('lazyFetching')
 		);
-
-		return this.eventManager;
 	}
 
 });

+ 2 - 2
src/Calendar.render.js

@@ -157,13 +157,13 @@ Calendar.mixin({
 	onRenderQueueStart: function() {
 		this.freezeContentHeight();
 
-		this.view.addScroll(this.view.queryScroll()); // TODO: move to Calendar
+		this.view.addScroll(this.view.queryScroll());
 	},
 
 
 	onRenderQueueStop: function() {
 		if (this.updateViewSize()) { // success?
-			this.view.popScroll(); // TODO: move to Calendar
+			this.view.popScroll();
 		}
 
 		this.thawContentHeight();

+ 5 - 5
src/View.js

@@ -89,12 +89,12 @@ var View = FC.View = InteractiveDateComponent.extend({
 
 
 	startBatchRender: function() {
-		this.calendar.renderQueue.startBatchRender()
+		this.calendar.startBatchRender()
 	},
 
 
 	stopBatchRender: function() {
-		this.calendar.renderQueue.stopBatchRender()
+		this.calendar.stopBatchRender()
 	},
 
 
@@ -291,7 +291,7 @@ var View = FC.View = InteractiveDateComponent.extend({
 	// -----------------------------------------------------------------------------------------------------------------
 
 
-	executeEventRender: function() {
+	executeEventRender: function(eventsPayload) {
 		DateComponent.prototype.executeEventRender.apply(this, arguments);
 
 		this.whenSizeUpdated(
@@ -879,13 +879,13 @@ View.watch('displayingEvents', [ 'displayingDates', 'hasEvents' ], function() {
 	var _this = this;
 
 	this.requestRender(function() {
-		////_this.executeEventRender(_this.get('currentEvents')); // TODO: audit payload
+		_this.executeEventRender(_this.get('currentEvents'));
 	}, 'event', 'init');
 }, function() {
 	var _this = this;
 
 	this.requestRender(function() {
-		////_this.executeEventUnrender();
+		_this.executeEventUnrender();
 	}, 'event', 'destroy');
 });
 

+ 20 - 6
src/agenda/AgendaView.js

@@ -274,15 +274,29 @@ var AgendaView = FC.AgendaView = View.extend({
 	------------------------------------------------------------------------------------------------------------------*/
 
 
-	executeEventRender: function() {
-		// TODO: route
-	},
+	executeEventRender: function(eventsPayload) {
+		var dayEventsPayload = {};
+		var timedEventsPayload = {};
+		var id, eventInstanceGroup;
 
+		// separate the events into all-day and timed
+		for (id in eventsPayload) {
+			eventInstanceGroup = eventsPayload[id];
 
-	executeEventUnrender: function() {
-		// TODO: route
-	},
+			if (eventInstanceGroup.getEventDef().isAllDay()) {
+				dayEventsPayload[id] = eventInstanceGroup;
+			}
+			else {
+				timedEventsPayload[id] = eventInstanceGroup;
+			}
+		}
 
+		this.timeGrid.executeEventRender(timedEventsPayload);
+
+		if (this.dayGrid) {
+			this.dayGrid.executeEventRender(dayEventsPayload);
+		}
+	},
 
 
 	/* Dragging/Resizing Routing

+ 1 - 1
src/basic/DayGrid.js

@@ -336,7 +336,7 @@ var DayGrid = FC.DayGrid = InteractiveDateComponent.extend(StandardInteractionsM
 
 	// Retrieves all rendered segment objects currently rendered on the grid
 	getOwnEventSegs: function() {
-		return InteractiveDateComponent.prototype.getEventSegs.call(this) // get the segments from the super-method
+		return InteractiveDateComponent.prototype.getOwnEventSegs.apply(this, arguments) // get the segments from the super-method
 			.concat(this.popoverSegs || []); // append the segments from the "more..." popover
 	},
 

+ 3 - 3
src/component/DateComponent.js

@@ -225,13 +225,13 @@ var DateComponent = FC.DateComponent = Component.extend({
 	// -----------------------------------------------------------------------------------------------------------------
 
 
-	executeEventRender: function(eventInstanceRepo) { // TODO: review paypload
+	executeEventRender: function(eventsPayload) {
 		if (this.eventRenderer) {
 			this.eventRenderer.rangeUpdated(); // poorly named now
-			this.eventRenderer.renderInstanceHash(eventInstanceRepo.byDefId);
+			this.eventRenderer.render(eventsPayload);
 		}
 		else if (this.renderEvents) { // legacy
-			this.renderEvents(convertEventInstanceHashToLegacyArray(eventInstanceRepo.byDefId));
+			this.renderEvents(convertEventInstanceHashToLegacyArray(eventsPayload));
 		}
 
 		this.callChildren('executeEventRender', arguments);

+ 3 - 5
src/component/renderers/EventRenderer.js

@@ -51,17 +51,15 @@ var EventRenderer = FC.EventRenderer = Class.extend({
 	},
 
 
-	renderInstanceHash: function(instanceHash) {
+	render: function(eventsPayload) {
 		var eventDefId;
 		var instanceGroup;
 		var eventRanges;
 		var bgRanges = [];
 		var fgRanges = [];
 
-		for (eventDefId in instanceHash) {
-
-			// TODO: eventually kill EventInstanceGroup and do slicing in the renderer
-			instanceGroup = new EventInstanceGroup(instanceHash[eventDefId]);
+		for (eventDefId in eventsPayload) {
+			instanceGroup = eventsPayload[eventDefId];
 
 			eventRanges = instanceGroup.sliceRenderRanges(this.component.dateProfile.activeUnzonedRange);
 

+ 0 - 143
src/models/EventDataSource.js

@@ -1,143 +0,0 @@
-
-/*
-Stores EventDefs AND EventInstances
-*/
-var EventDataSource = EventInstanceDataSource.extend({
-
-	currentUnzonedRange: null, // for creating EventInstances
-
-	eventDefsByUid: null,
-	eventDefsById: null,
-
-
-	constructor: function() {
-		EventInstanceDataSource.call(this);
-
-		this.eventDefsByUid = {};
-		this.eventDefsById = {};
-	},
-
-
-	getEventDefByUid: function(eventDefUid) {
-		return this.eventDefsByUid[eventDefUid];
-	},
-
-
-	getEventDefsById: function(eventDefId) {
-		var bucket = this.eventDefsById[eventDefId];
-
-		if (bucket) {
-			return bucket.slice(); // clone
-		}
-
-		return [];
-	},
-
-
-	addEventDefs: function(eventDefs) {
-		for (var i = 0; i < eventDefs.length; i++) {
-			this.addEventDef(eventDefs[i]);
-		}
-	},
-
-
-	// generates and stores instances as well
-	addEventDef: function(eventDef) {
-		this.storeEventDef(eventDef);
-
-		this.addChangeset(
-			new EventInstanceChangeset(
-				null, // removals
-				new EventInstanceRepo( // additions
-					eventDef.buildInstances(this.currentUnzonedRange)
-				)
-			)
-		);
-	},
-
-
-	// does NOT add any instances
-	storeEventDef: function(eventDef) {
-		var eventDefsById = this.eventDefsById;
-		var id = eventDef.id;
-
-		(eventDefsById[id] || (eventDefsById[id] = []))
-			.push(eventDef);
-
-		this.eventDefsByUid[eventDef.uid] = eventDef;
-	},
-
-
-	removeEventDefsById: function(eventDefId) {
-		var _this = this;
-
-		this.getEventDefsById(eventDefId).forEach(function(eventDef) {
-			_this.removeEventDef(eventDef);
-		});
-	},
-
-
-	removeAllEventDefs: function() {
-		this.freeze();
-
-		Object.values(this.eventDefsByUid).forEach(
-			this.removeEventDef.bind(this)
-		);
-
-		this.thaw();
-	},
-
-
-	removeEventDef: function(eventDef) {
-		var eventDefsById = this.eventDefsById;
-		var bucket = eventDefsById[eventDef.id];
-
-		delete this.eventDefsByUid[eventDef.uid];
-
-		if (bucket) {
-			removeExact(bucket, eventDef);
-
-			if (!bucket.length) {
-				delete eventDefsById[eventDef.id];
-			}
-
-			this.addChangeset(
-				new EventInstanceChangeset(
-					new EventInstanceRepo( // removals
-						this.instanceRepo.getEventInstancesForDef(eventDef)
-					)
-				)
-			);
-		}
-	},
-
-
-	/*
-	Will emit TWO SEPARATE CHANGESETS. This is due to EventDef's being mutable.
-	Returns an undo function.
-	*/
-	mutateEventsWithId: function(eventDefId, eventDefMutation) {
-		var _this = this;
-		var eventDefs = this.getEventDefsById(eventDefId);
-		var undoFuncs;
-
-		eventDefs.forEach(this.removeEventDef.bind(this));
-
-		undoFuncs = eventDefs.map(function(eventDef) {
-			return eventDefMutation.mutateSingle(eventDef);
-		});
-
-		eventDefs.forEach(this.addEventDef.bind(this));
-
-		return function() {
-			eventDefs.forEach(_this.removeEventDef.bind(_this));
-
-			undoFuncs.forEach(function(undoFunc) {
-				undoFunc();
-			});
-
-			eventDefs.forEach(_this.addEventDef.bind(_this));
-		};
-	}
-
-});

+ 0 - 63
src/models/EventInstanceChangeset.js

@@ -1,63 +0,0 @@
-
-var EventInstanceChangeset = Class.extend({
-
-	removalsRepo: null,
-	additionsRepo: null,
-
-
-	constructor: function(removalsRepo, additionsRepo) {
-		this.removalsRepo = removalsRepo || new EventInstanceRepo();
-		this.additionsRepo = additionsRepo || new EventInstanceRepo();
-	},
-
-
-	applyToRepo: function(repo) {
-		var removalsHash = this.removalsRepo.byDefId;
-		var additionsHash = this.additionsRepo.byDefId;
-		var id, instances;
-		var i;
-
-		for (id in removalsHash) {
-			instances = removalsHash[id];
-
-			for (i = 0; i < instances.length; i++) {
-				repo._removeEventInstance(id, instances[i]);
-			}
-		}
-
-		for (id in additionsHash) {
-			instances = additionsHash[id];
-
-			for (i = 0; i < instances.length; i++) {
-				repo._addEventInstance(id, instances[i]);
-			}
-		}
-	},
-
-
-	applyToChangeset: function(changeset) {
-		var removalsHash = this.removalsRepo.byDefId;
-		var additionsHash = this.additionsRepo.byDefId;
-		var id, instances;
-		var i;
-
-		for (id in removalsHash) {
-			instances = removalsHash[id];
-
-			for (i = 0; i < instances.length; i++) {
-				if (!changeset.additionsRepo._removeEventInstance(id, instances[i])) {
-					changeset.removalsRepo._addEventInstance(id, instances[i]);
-				}
-			}
-		}
-
-		for (id in additionsHash) {
-			instances = additionsHash[id];
-
-			for (i = 0; i < instances.length; i++) {
-				changeset.additionsRepo._addEventInstance(id, instances[i]);
-			}
-		}
-	}
-
-});

+ 0 - 82
src/models/EventInstanceDataSource.js

@@ -1,82 +0,0 @@
-
-var EventInstanceDataSource = Class.extend(EmitterMixin, ListenerMixin, {
-
-	instanceRepo: null,
-	freezeDepth: 0,
-	outboundChangeset: null,
-	isResolved: false, // for eventAfterAllRender
-
-
-	constructor: function() {
-		this.instanceRepo = new EventInstanceRepo();
-	},
-
-
-	tryReset: function() {
-		if (this.isResolved && this.canTrigger()) {
-			this.triggerChangeset(new EventInstanceChangeset(
-				this.instanceRepo, // removals
-				this.instanceRepo // additions
-			));
-			this.trigger('resolved');
-		}
-	},
-
-
-	// Reporting and Triggering
-	// -----------------------------------------------------------------------------------------------------------------
-
-
-	addChangeset: function(changeset) {
-		if (!this.outboundChangeset) {
-			this.outboundChangeset = new EventInstanceChangeset();
-		}
-
-		changeset.applyToChangeset(this.outboundChangeset);
-
-		this.trySendOutbound();
-	},
-
-
-	freeze: function() {
-		this.freezeDepth++;
-	},
-
-
-	thaw: function() {
-		this.freezeDepth--;
-		this.trySendOutbound();
-	},
-
-
-	trySendOutbound: function() { // also might apply outbound changes to INTERNAL data
-		var outboundChangeset = this.outboundChangeset;
-
-		if (this.canTrigger()) {
-
-			if (outboundChangeset) {
-				outboundChangeset.applyToRepo(this.instanceRepo); // finally internally record
-
-				this.outboundChangeset = null;
-				this.triggerChangeset(outboundChangeset);
-			}
-
-			// for eventAfterAllRender
-			this.isResolved = true;
-			this.trigger('resolved');
-		}
-	},
-
-
-	canTrigger: function() {
-		return !this.freezeDepth;
-	},
-
-
-	triggerChangeset: function(changeset) {
-		this.trigger('before:receive');
-		this.trigger('receive', changeset);
-		this.trigger('after:receive');
-	}
-
-});

+ 0 - 86
src/models/EventInstanceDataSourceSplitter.js

@@ -1,86 +0,0 @@
-
-var EventInstanceDataSourceSplitter = FC.EventInstanceDataSourceSplitter = Class.extend(EmitterMixin, ListenerMixin, {
-
-	keysFunc: null,
-	repoHash: null,
-
-
-	constructor: function(keysFunc) {
-		this.keysFunc = keysFunc;
-		this.repoHash = {};
-	},
-
-
-	buildSubSource: function(key) {
-		var subDataSource = new EventInstanceDataSource();
-		var initialRepo = this.repoHash[key];
-
-		if (initialRepo) {
-			subDataSource.addChangeset(new EventInstanceChangeset(null, initialRepo));
-		}
-
-		subDataSource.listenTo(this, 'receive:' + key, function(changeset) {
-			subDataSource.addChangeset(changeset);
-		});
-
-		return subDataSource;
-	},
-
-
-	releaseSubResource: function(subDataSource) {
-		subDataSource.stopListeningTo(this);
-	},
-
-
-	addSource: function(dataSource) {
-
-		if (dataSource.instanceRepo.cnt) {
-			this.processChangeset(new EventInstanceChangeset(null, dataSource.instanceRepo)); // add all
-		}
-
-		this.listenTo(dataSource, 'receive', this.processChangeset);
-	},
-
-
-	removeSource: function(dataSource) {
-		this.stopListeningTo(dataSource);
-
-		if (dataSource.instanceRepo.cnt) {
-			this.processChangeset(new EventInstanceChangeset(dataSource.instanceRepo)); // remove all
-		}
-	},
-
-
-	processChangeset: function(changeset) {
-		var keysFunc = this.keysFunc;
-		var changesetsByKey = {};
-		var key;
-		var getChangeset = function(key) {
-			return (changesetsByKey[key] || (changesetsByKey[key] = new EventInstanceChangeset()));
-		};
-
-		changeset.removalsRepo.iterEventInstances(function(eventInstance) {
-			keysFunc(eventInstance).forEach(function(key) {
-				getChangeset(key).removalsRepo.addEventInstance(eventInstance);
-			});
-		});
-
-		changeset.additionsRepo.iterEventInstances(function(eventInstance) {
-			keysFunc(eventInstance).forEach(function(key) {
-				getChangeset(key).additionsRepo.addEventInstance(eventInstance);
-			});
-		});
-
-		for (key in changesetsByKey) {
-			changesetsByKey[key].applyToRepo(this.ensureRepo(key));
-
-			this.trigger('receive:' + key, changesetsByKey[key]);
-		}
-	},
-
-
-	ensureRepo: function(key) {
-		return (this.repoHash[key] || (this.repoHash[key] = new EventInstanceRepo()));
-	}
-
-});

+ 0 - 106
src/models/EventInstanceRepo.js

@@ -1,106 +0,0 @@
-
-var EventInstanceRepo = Class.extend({
-
-	byDefId: null,
-	cnt: 0,
-
-
-	constructor: function(eventInstances) {
-		this.byDefId = {};
-
-		(eventInstances || []).forEach(this.addEventInstance.bind(this));
-	},
-
-
-	getEventInstancesForDef: function(eventDef) {
-		return (this.byDefId[eventDef.id] || []).filter(function(eventInstance) {
-			return eventInstance.def === eventDef;
-		});
-	},
-
-
-	getEventInstances: function() {
-		var byDefId = this.byDefId;
-		var a = [];
-		var id;
-
-		for (id in byDefId) {
-			a.push.apply(a, byDefId[id]);
-		}
-
-		return a;
-	},
-
-
-	iterEventInstances: function(func) {
-		var byDefId = this.byDefId;
-		var defId;
-
-		for (defId in byDefId) {
-			byDefId[defId].forEach(func);
-		}
-	},
-
-
-	getEventInstancesWithId: function(eventDefId) {
-		var bucket = this.byDefId[eventDefId];
-
-		if (bucket) {
-			return bucket.slice(); // clone
-		}
-
-		return [];
-	},
-
-
-	getEventInstancesWithoutId: function(eventDefId) {
-		var byDefId = this.byDefId;
-		var a = [];
-		var id;
-
-		for (id in byDefId) {
-			if (id !== eventDefId) {
-				a.push.apply(a, byDefId[id]);
-			}
-		}
-
-		return a;
-	},
-
-
-	addEventInstance: function(eventInstance) {
-		this._addEventInstance(eventInstance.def.id, eventInstance);
-	},
-
-
-	removeEventInstance: function(eventInstance) {
-		return this._removeEventInstance(eventInstance.def.id, eventInstance);
-	},
-
-
-	_addEventInstance: function(id, eventInstance) {
-		(this.byDefId[id] || (this.byDefId[id] = []))
-			.push(eventInstance);
-
-		this.cnt++;
-	},
-
-
-	_removeEventInstance: function(id, eventInstance) {
-		var bucket = this.byDefId[id];
-
-		if (bucket && removeExact(bucket, eventInstance)) {
-
-			if (!bucket.length) {
-				delete this.byDefId[id];
-			}
-
-			this.cnt--;
-
-			return true;
-		}
-
-		return false;
-	}
-
-});

+ 179 - 41
src/models/EventManager.js

@@ -1,5 +1,7 @@
 
-var EventManager = RequestableEventDataSource.extend({
+var EventManager = Class.extend(EmitterMixin, ListenerMixin, {
+
+	currentPeriod: null,
 
 	calendar: null,
 	stickySource: null,
@@ -7,18 +9,25 @@ var EventManager = RequestableEventDataSource.extend({
 
 
 	constructor: function(calendar) {
-		RequestableEventDataSource.call(this);
-
 		this.calendar = calendar;
 		this.stickySource = new ArrayEventSource(calendar);
 		this.otherSources = [];
+	},
 
-		this.on('before:receive', function() {
-			calendar.startBatchRender();
-		});
-		this.on('after:receive', function() {
-			calendar.stopBatchRender();
-		});
+
+	requestEvents: function(start, end, timezone, force) {
+		if (
+			force ||
+			!this.currentPeriod ||
+			!this.currentPeriod.isWithinRange(start, end) ||
+			timezone !== this.currentPeriod.timezone
+		) {
+			this.setPeriod( // will change this.currentPeriod
+				new EventPeriod(start, end, timezone)
+			);
+		}
+
+		return this.currentPeriod.whenReleased();
 	},
 
 
@@ -29,21 +38,55 @@ var EventManager = RequestableEventDataSource.extend({
 	addSource: function(eventSource) {
 		this.otherSources.push(eventSource);
 
-		if (this.currentUnzonedRange) {
-			this.requestSource(eventSource);
+		if (this.currentPeriod) {
+			this.currentPeriod.requestSource(eventSource); // might release
 		}
 	},
 
 
 	removeSource: function(doomedSource) {
 		removeExact(this.otherSources, doomedSource);
-		this.purgeSource(doomedSource);
+
+		if (this.currentPeriod) {
+			this.currentPeriod.purgeSource(doomedSource); // might release
+		}
 	},
 
 
 	removeAllSources: function() {
 		this.otherSources = [];
-		this.purgeAllSources();
+
+		if (this.currentPeriod) {
+			this.currentPeriod.purgeAllSources(); // might release
+		}
+	},
+
+
+	// Source Refetching
+	// -----------------------------------------------------------------------------------------------------------------
+
+
+	refetchSource: function(eventSource) {
+		var currentPeriod = this.currentPeriod;
+
+		if (currentPeriod) {
+			currentPeriod.freeze();
+			currentPeriod.purgeSource(eventSource);
+			currentPeriod.requestSource(eventSource);
+			currentPeriod.thaw();
+		}
+	},
+
+
+	refetchAllSources: function() {
+		var currentPeriod = this.currentPeriod;
+
+		if (currentPeriod) {
+			currentPeriod.freeze();
+			currentPeriod.purgeAllSources();
+			currentPeriod.requestSources(this.getSources());
+			currentPeriod.thaw();
+		}
 	},
 
 
@@ -124,38 +167,76 @@ var EventManager = RequestableEventDataSource.extend({
 	},
 
 
-	// Event Adding/Removing needs to have side-effects in the sources
+	// Event-Period
 	// -----------------------------------------------------------------------------------------------------------------
 
 
-	addEventDef: function(eventDef, persist) {
-		if (persist) {
-			this.stickySource.addEventDef(eventDef);
+	setPeriod: function(eventPeriod) {
+		if (this.currentPeriod) {
+			this.unbindPeriod(this.currentPeriod);
+			this.currentPeriod = null;
 		}
 
-		RequestableEventDataSource.prototype.addEventDef.apply(this, arguments);
+		this.currentPeriod = eventPeriod;
+		this.bindPeriod(eventPeriod);
+
+		eventPeriod.requestSources(this.getSources());
 	},
 
 
-	removeEventDefsById: function(eventId, persist) {
-		if (persist) {
-			this.getSources().forEach(function(eventSource) {
-				eventSource.removeEventDefsById(eventId);
-			});
+	bindPeriod: function(eventPeriod) {
+		this.listenTo(eventPeriod, 'release', function(eventsPayload) {
+			this.trigger('release', eventsPayload);
+		});
+	},
+
+
+	unbindPeriod: function(eventPeriod) {
+		this.stopListeningTo(eventPeriod);
+	},
+
+
+	// Event Getting/Adding/Removing
+	// -----------------------------------------------------------------------------------------------------------------
+
+
+	getEventDefByUid: function(uid) {
+		if (this.currentPeriod) {
+			return this.currentPeriod.getEventDefByUid(uid);
 		}
+	},
 
-		RequestableEventDataSource.prototype.removeEventDefsById.apply(this, arguments);
+
+	addEventDef: function(eventDef, isSticky) {
+		if (isSticky) {
+			this.stickySource.addEventDef(eventDef);
+		}
+
+		if (this.currentPeriod) {
+			this.currentPeriod.addEventDef(eventDef); // might release
+		}
 	},
 
 
-	removeAllEventDefs: function(persist) {
-		if (persist) {
-			this.getSources().forEach(function(eventSource) {
-				eventSource.removeAllEventDefs();
-			});
+	removeEventDefsById: function(eventId) {
+		this.getSources().forEach(function(eventSource) {
+			eventSource.removeEventDefsById(eventId);
+		});
+
+		if (this.currentPeriod) {
+			this.currentPeriod.removeEventDefsById(eventId); // might release
 		}
+	},
+
 
-		RequestableEventDataSource.prototype.removeAllEventDefs.apply(this, arguments);
+	removeAllEventDefs: function() {
+		this.getSources().forEach(function(eventSource) {
+			eventSource.removeAllEventDefs();
+		});
+
+		if (this.currentPeriod) {
+			this.currentPeriod.removeAllEventDefs();
+		}
 	},
 
 
@@ -167,19 +248,38 @@ var EventManager = RequestableEventDataSource.extend({
 	Returns an undo function.
 	*/
 	mutateEventsWithId: function(eventDefId, eventDefMutation) {
-		var calendar = this.calendar;
-		var undoFunc;
+		var currentPeriod = this.currentPeriod;
+		var eventDefs;
+		var undoFuncs = [];
+
+		if (currentPeriod) {
+
+			currentPeriod.freeze();
 
-		// emits two separate changesets, so make sure rendering happens only once
-		calendar.startBatchRender();
-		undoFunc = RequestableEventDataSource.prototype.mutateEventsWithId.apply(this, arguments);
-		calendar.stopBatchRender();
+			eventDefs = currentPeriod.getEventDefsById(eventDefId);
+			eventDefs.forEach(function(eventDef) {
+				// add/remove esp because id might change
+				currentPeriod.removeEventDef(eventDef);
+				undoFuncs.push(eventDefMutation.mutateSingle(eventDef));
+				currentPeriod.addEventDef(eventDef);
+			});
+
+			currentPeriod.thaw();
+
+			return function() {
+				currentPeriod.freeze();
 
-		return function() {
-			calendar.startBatchRender();
-			undoFunc();
-			calendar.stopBatchRender();
-		};
+				for (var i = 0; i < eventDefs.length; i++) {
+					currentPeriod.removeEventDef(eventDefs[i]);
+					undoFuncs[i]();
+					currentPeriod.addEventDef(eventDefs[i]);
+				}
+
+				currentPeriod.thaw();
+			};
+		}
+
+		return function() { };
 	},
 
 
@@ -205,11 +305,49 @@ var EventManager = RequestableEventDataSource.extend({
 		}
 
 		return new EventInstanceGroup(allInstances);
+	},
+
+
+	// Freezing
+	// -----------------------------------------------------------------------------------------------------------------
+
+
+	freeze: function() {
+		if (this.currentPeriod) {
+			this.currentPeriod.freeze();
+		}
+	},
+
+
+	thaw: function() {
+		if (this.currentPeriod) {
+			this.currentPeriod.thaw();
+		}
 	}
 
 });
 
 
+// Methods that straight-up query the current EventPeriod for an array of results.
+[
+	'getEventDefsById',
+	'getEventInstances',
+	'getEventInstancesWithId',
+	'getEventInstancesWithoutId'
+].forEach(function(methodName) {
+
+	EventManager.prototype[methodName] = function() {
+		var currentPeriod = this.currentPeriod;
+
+		if (currentPeriod) {
+			return currentPeriod[methodName].apply(currentPeriod, arguments);
+		}
+
+		return [];
+	};
+});
+
+
 function isSourcesEquivalent(source0, source1) {
 	return source0.getPrimitive() == source1.getPrimitive();
 }

+ 0 - 159
src/models/RequestableEventDataSource.js

@@ -1,159 +0,0 @@
-
-var RequestableEventDataSource = EventDataSource.extend({
-
-	currentStart: null,
-	currentEnd: null,
-	currentTimezone: null,
-
-	requestsByUid: null,
-	pendingSourceCnt: 0,
-
-
-	constructor: function() {
-		EventDataSource.call(this);
-
-		this.requestsByUid = {};
-	},
-
-
-	request: function(start, end, timezone, force) {
-		if (
-			force ||
-			!this.currentStart || // first fetch?
-			this.currentTimezone !== timezone || // different timezone?
-			start.isBefore(this.currentStart) || // out of bounds?
-			end.isAfter(this.currentEnd)         // "
-		) {
-			this.currentTimezone = timezone;
-			this.currentStart = start;
-			this.currentEnd = end;
-			this.currentUnzonedRange = new UnzonedRange(
-				start.clone().stripZone(),
-				end.clone().stripZone()
-			);
-
-			this.refetchAllSources();
-		}
-	},
-
-
-	refetchSource: function(eventSource) {
-		if (this.currentUnzonedRange) {
-			this.freeze();
-			this.purgeSource(eventSource);
-			this.requestSource(eventSource);
-			this.thaw();
-		}
-	},
-
-
-	refetchAllSources: function() {
-		if (this.currentUnzonedRange) {
-			this.freeze();
-			this.purgeAllSources();
-			this.requestSources(this.getSources());
-			this.thaw();
-		}
-	},
-
-
-	getSources: function() {
-		return [];
-	},
-
-
-	requestSources: function(sources) {
-		this.freeze();
-
-		for (var i = 0; i < sources.length; i++) {
-			this.requestSource(sources[i]);
-		}
-
-		this.thaw();
-	},
-
-
-	requestSource: function(source) {
-		var _this = this;
-		var request = { source: source, status: 'pending' };
-
-		this.requestsByUid[source.uid] = request;
-		this.pendingSourceCnt += 1;
-
-		source.fetch(this.currentStart, this.currentEnd, this.currentTimezone).then(function(eventDefs) {
-			if (request.status !== 'cancelled') {
-				request.status = 'completed';
-				request.eventDefs = eventDefs;
-
-				_this.addEventDefs(eventDefs);
-				_this.reportSourceDone();
-			}
-		}, function() { // failure
-			if (request.status !== 'cancelled') {
-				request.status = 'failed';
-
-				_this.reportSourceDone();
-			}
-		});
-	},
-
-
-	purgeSource: function(source) {
-		var request = this.requestsByUid[source.uid];
-
-		if (request) {
-			delete this.requestsByUid[source.uid];
-
-			if (request.status === 'pending') {
-				request.status = 'cancelled';
-
-				this.reportSourceDone();
-			}
-			else if (request.status === 'completed') {
-				this.freeze();
-
-				request.eventDefs.forEach(this.removeEventDef.bind(this));
-
-				this.thaw();
-			}
-		}
-	},
-
-
-	purgeAllSources: function() {
-		var requestsByUid = this.requestsByUid;
-		var uid, request;
-		var completedCnt = 0;
-
-		for (uid in requestsByUid) {
-			request = requestsByUid[uid];
-
-			if (request.status === 'pending') {
-				request.status = 'cancelled';
-			}
-			else if (request.status === 'completed') {
-				completedCnt++;
-			}
-		}
-
-		this.pendingSourceCnt = 0;
-		this.requestsByUid = {};
-
-		if (completedCnt) {
-			this.removeAllEventDefs();
-		}
-	},
-
-
-	reportSourceDone: function() {
-		this.pendingSourceCnt--;
-		this.trySendOutbound();
-	},
-
-
-	canTrigger: function() {
-		return EventDataSource.prototype.canTrigger.apply(this, arguments) &&
-			!this.pendingSourceCnt;
-	}
-
-});