Adam Ahmed avatar Adam Ahmed committed 6d889b8

Make iPads take advantage of bulk update and update more often. Fix pluralization in UI messages. Fix bug where Impromptu Meetings were being order after existing meetings and thus didn't show up in the UI.

Comments (0)

Files changed (5)

 
     app.get('/data/rooms', checkSecret, function(req, res, next) {
         var rooms = datasource.rooms().map(toJSON);
-        if (req.query.expand) {
+        if (req.query.expand !== undefined) {
             rooms.forEach(function(room) {
                 room.events = datasource.events(room.key).map(toJSON);
             });

lib/models/room.js

 };
 Room.prototype.addEvent = function(event) {
 	this._events.push(event);
+	this._events.sort(function(a, b) {
+		return (a._start - b._start) || (a._end - b._end);
+	});
 };
 Room.prototype.toJSON = function(expand) {
 	return expand ? {

public/js/eventManager.js

 				0;
 	};
 
-	function CalendarEvent(eventJson, room) {
-		TimeRange.call(this, new Date(eventJson.start), new Date(eventJson.end));
+	function CalendarEvent(jsonOrEvent) {
+		TimeRange.call(this, new Date(jsonOrEvent.start), new Date(jsonOrEvent.end));
 
-		this._title = eventJson.title || 'Unnamed Event';
-		this._organizer = eventJson.organizer;
-		this._room = room;
+		this._title = jsonOrEvent._title || jsonOrEvent.title || 'Unnamed Event';
+		this._organizer = jsonOrEvent._organizer || jsonOrEvent.organizer;
 	}
 	$.extend(CalendarEvent.prototype, TimeRange.prototype, {
 		conflictsWith : function (date) {
 			return this.intersects(date);
 		},
-		includesRoom : function(roomName) {
-			return roomName === this._room.name();
-		},
 		nextNonConflict : function (afterDate) {
 			if (this.intersects(afterDate)) {
 				return this.end;
 	var Room;
 	(function() {
 		Room = function (roomJson) {
-			var _events;
-			var _loaded = false;
+			var _events = CalendarEvent.getEvents(roomJson.events);
 
+			delete roomJson.events;
+			
 			this._meta = roomJson;
 
-			this.loaded = function() { return _loaded; };
+			this.events = function(events) {
+				if (events) {
+					_events = CalendarEvent.getEvents(events);
 
-			this.events = function() { return _events; };
-
-			this.load = function(callback) {
-				if (!_loaded) {
-					this.reload(callback);
+					this._nextFreeTimeCache = {};
+					this._nextEventCache = {};
 				} else {
-					callback && callback();
+					return _events;
 				}
-			};
-			var thisRoom = this;
-			this.reload = function(callback) {
-				EventManager.getRoomEvents(this, function(res) {
-					try {
-						_events = CalendarEvent.getEvents(res.events);
-						_events.sort(CalendarEvent.sorter);
-						thisRoom._nextFreeTimeCache = {};
-						thisRoom._nextEventCache = {};
-						_loaded = true;
-						callback && callback();
-					} catch (e) {
-						Logger.log("Error during reload.", e);
-					}
-				}, function (ret) { Logger.log('Error loading room', ret); });
-			};
+			}.bind(this);
 
 			this._nextFreeTimeCache = {};
 			this._nextEventCache = {};
 				return room._nextEventCache[dateObj];
 			}
 
-			if (!room.loaded()) {
-				Logger.log('Room not loaded.');
-			}
-
 			var minDate = null, minDateEvent = null;
 			for (var i = 0, events = room.events(), l = events.length; i < l; i++) {
 				var event = events[i];
 			isBooked : function (dateToCheck) {
 				var dateObj = dateToCheck || DebugSettings.now() || new Date();
 
-				if (!this.loaded()) {
-					Logger.log('Room not loaded.');
-				}
-
 				for (var i = 0; i < this.events().length; i++) {
 					if(this.events()[i].conflictsWith(dateObj)) {
 						return true;
 					return this._nextFreeTimeCache[dateObj];
 				}
 
-				if (!this.loaded()) {
-					Logger.log('Room not loaded.');
-				}
-
 				var maxofMinFreeDates = new Date(dateObj);
 				for (var i = 0; i < this.events().length; i++) {
 					var minFreeDate = this.events()[i].nextNonConflict(dateObj);
 		var roomsByName;
 		var roomsById;
 		
+		function addNewRoom(room) {
+			EventManager.rooms.push(room);
+			roomsByName[room.name()] = room;
+			roomsById[room.id()] = room;
+		}
+
 		this.init = function(rootUrl, secret, callback) {
 			url = rootUrl;
 			secretKey = secret;
-			getRooms(function(res){
-				EventManager.rooms = Room.getRooms(res.rooms);
-				roomsByName = {};
-				roomsById = {};
+			this.rooms = [];
+			roomsByName = {};
+			roomsById = {};
+
+			this.update(callback);
+		};
+
+		this.update = function(callback) {
+			getRooms(function(res) {
+				var newRooms = [];
+				_.each(Room.getRooms(res.rooms), function(room) {
+					if (room.id() in roomsById) {
+						roomsById[room.id()].events(room.events());
+					} else {
+						newRooms.push(room);
+						addNewRoom(room);
+					}
+				});
 				
-				for(var i = 0; i < EventManager.rooms.length; i++) {
-					var currRoom = EventManager.rooms[i];
-					roomsByName[currRoom.name()] = currRoom;
-					roomsById[currRoom.id()] = currRoom;
-				}
-				
-				callback();
+				callback(newRooms);
 			}, function(err) { Logger.log('GetAvailableCalendars error', err); });
 		};
 
 		
 		function getRooms(onSuccess, onError) {
 			return $.ajax({
-				url : url + 'data/rooms' + (secretKey ? '?secret=' + secretKey : ''),
-				dataType : 'json',
-				success : onSuccess,
-				error : errorHandler(onError)
-			});
-		}
-
-		function getEvents(roomKey, onSuccess, onError) {
-			return $.ajax({
-				url : url + 'data/events?room=' + roomKey + (secretKey ? '&secret=' + secretKey : ''),
+				url : url + 'data/rooms?expand' + (secretKey ? '&secret=' + secretKey : ''),
 				dataType : 'json',
 				success : onSuccess,
 				error : errorHandler(onError)
 			return roomsById[roomId];
 		};
 		
-		this.getRoomEvents = function(room, onSuccess, onError) {
-			return getEvents(room.id(), onSuccess, onError);
-		};
-		
 		this.bookRoom = function(room, startTime, durationMinutes, onSuccess, onError) {
 			var endTime = new Date(startTime);
 			endTime.setMinutes(startTime.getMinutes() + durationMinutes);

public/js/main.js

 		}, firstDelaySec * 1000);
 	}
 	
-	function reloadRoom(room, callback) {
-		room.reload(function() {
-			GlobalEvents.trigger('roomUpdatedByServer', room);
-			callback && callback();
-		});
-	}
-	function beginReloadingRooms(thisRoom) {
-		var currIndex = 0;
-		function updateARoom() {
-			var otherRoom = EventManager.rooms[currIndex];
-			currIndex++;
-			currIndex %= EventManager.rooms.length;
-			
-			if (otherRoom == thisRoom) { // skip it, it has its own cycle.
-				updateARoom();
-			} else {
-				reloadRoom(otherRoom);
-			}
-		}
-	
-		runEveryNMinutesOnTheMthSecond(15, 20, updateARoom);
-	}
-	
 	function getRootUrl() {
 		var qsStart = window.location.href.indexOf('?');
 		var hashStart = window.location.href.indexOf('#');
 		return url;
 	}
 
+	function updateRooms() {
+		EventManager.update(function success(newRooms) {
+			_.each(newRooms, function(room) {
+				GlobalEvents.trigger('roomLoaded', room);
+			});
+			_.each(EventManager.rooms, function(room) {
+				GlobalEvents.trigger('roomUpdatedByServer', room);
+			});
+		}, function error() {
+			console.log('Error while updating rooms.');
+		});
+	}
+
 	DebugSettings.init(getRootUrl());
-	EventManager.init(getRootUrl(), params.secret, function() {
+	EventManager.init(getRootUrl(), params.secret, function(newRooms) {
 		var thisRoom = roomName ? EventManager.getRoom(roomName) : undefined;
 		
 		if (roomName && !thisRoom) {
 			$('#container').addClass('hidden');
 			return;
 		}
-		
-		//once all the "other" rooms are loaded, begin REloading them one at a time in a round robin to keep them up-to-date.
-		var afterAllRoomsLoaded = _.after(EventManager.rooms.length, function() { beginReloadingRooms(thisRoom); }),
-			loadOtherRooms = function(thisRoom) {
-				_.each(EventManager.rooms, function(room) {
-					thisRoom !== room && room.load(function() {
-						afterAllRoomsLoaded();
-						GlobalEvents.trigger('roomLoaded', room);
-					});
-				});
-			};
 			
 		initUi(thisRoom);
-		if (thisRoom) {
-			thisRoom.load(function() { // if we have a "this" room, we want to load it first without other loads getting in the way (not sure they actually will...)
-				GlobalEvents.trigger('roomLoaded', thisRoom);
-				afterAllRoomsLoaded();
-				
-				//begin reloading this room at regular intervals
-				runEveryNMinutesOnTheMthSecond(30, 40, function() { reloadRoom(thisRoom); });
-				
-				loadOtherRooms(thisRoom);
-			});
-		} else {
-			loadOtherRooms();
-		}
+		
+		_.each(newRooms, function(room) {
+			GlobalEvents.trigger('roomLoaded', room);
+		});
+
+		runEveryNMinutesOnTheMthSecond(1, 31, updateRooms);
 		
 		//update UI when the minute ticks over.
-		runEveryNMinutesOnTheMthSecond(1, 1, function() {
-			GlobalEvents.trigger('minuteChanged');
-		});
+		runEveryNMinutesOnTheMthSecond(1, 1, function() { GlobalEvents.trigger('minuteChanged'); });
 	});
 	
 	GlobalEvents.bind('bookingAddedByUser', function(event, booking) {
 		EventManager.bookRoom(booking.room, booking.time, booking.duration,
 			function success() {			
-				booking.room.reload(function() {
-					GlobalEvents.trigger('roomUpdatedByServer', booking.room);
-				});
+				updateRooms();
 			},
 			function failure() {
 				GlobalEvents.trigger('bookingFailure', booking);
 		);
 	});
 	
-	// update MEAT from server everyday at midnight.  gApps also seems to have some memory leaks.  This lets us avoid bad consequences from that...
+	// update MEAT from server everyday at midnight.
 	runAtMidnight(function() { window.location.reload(); });
 })();
 	
 	var ViewModels = (function() {
 
+		function pluralize(num, one, many) {
+			return num === 1 ? one : many;
+		}
+
 		function minutesBetween(a, b) {
 			return Math.ceil((b.getTime() - a.getTime()) / 60000);
 		}
 			if (minutes < 1) {
 				return "";
 			} else if (minutes < 60) {
-				return prefix + minutes + " minutes";
+				return prefix + minutes + pluralize(minutes, " minute", " minutes");
 			} else {
 				var hours = Math.floor(minutes / 60);
 				if (hours < 24) {
-					return prefix + hours + " hours";
+					return prefix + hours + pluralize(hours, " hour", " hours");
 				} else {
 					return prefix + "a long time";
 				}
 			}
 		}
+
+		function minutesLeftToday() {
+			var now = DebugSettings.now() || new Date();
+			var end = new Date(new Date().getTime() + (1000 * 60 * 60 * 24));
+			end.setHours(0);
+			end.setMinutes(0);
+			end.setSeconds(0);
+
+			return (end - now) / 1000 / 60;
+		}
+
+		function minutesToDurationString(mins, isOngoing) {
+			if (mins < 60) {
+				return isOngoing ?
+					'for ' + mins + pluralize(mins, ' more min', ' more mins') :
+					'in ' + mins + pluralize(mins, ' min', ' mins');
+			}
+			if (mins > minutesLeftToday()) {
+				return isOngoing ? 'for a long time' : 'tomorrow';
+			} else {
+				var hours = Math.floor(mins / 60);
+				return isOngoing ?
+					'for ' + hours + pluralize(hours, '+ more hour', '+ more hours') :
+					'in ' + hours + pluralize(hours, ' hour', ' hours');
+			}
+		}
 		
 		function getRoomAvailability(room) {
 			var now = DebugSettings.now() || new Date(),
 					getCurrentBooking : function() {
 						var currentBooking = getRoomAvailability(room).currentBooking;
 						if (currentBooking) {
-							currentBooking.when = "for " + currentBooking.minutesTilEnd + " more mins";
+							currentBooking.when =
+								minutesToDurationString(currentBooking.minutesTilEnd, true);
 						}
 						return currentBooking;
 					},
 					getNextBooking : function() {
 						var nextBooking = getRoomAvailability(room).nextBooking;
 						if (nextBooking && nextBooking.minutesTilStart) {
-							nextBooking.when = "in " + nextBooking.minutesTilStart + " mins";
+							nextBooking.when =
+								minutesToDurationString(nextBooking.minutesTilStart, false);
 						}
 						return nextBooking;
 					},
 										if (room === bookingRoom) {
 											onComplete();
 										}
-									}, onFailure = function(event, room) {
-										if (room === bookingRoom) {
+									}, onFailure = function(event, booking) {
+										if (booking.room === bookingRoom) {
 											$timeRequired.text('ERROR');
 											setTimeout(onComplete, 2000);
 										}
 				RoomList.init($('#rooms'));
 				if (thisRoom) {
 					Status.init($('#container'), thisRoom);
-					if (thisRoom.loaded()) {
-						switchTo(Status);
-					} else {
-						GlobalEvents.bind('roomLoaded', function(event, room) {
-							if (room === thisRoom) {
-								switchTo(Status);
-							}
-						});
-					}
+					switchTo(Status);
 				} else {
 					switchTo(RoomList);
 				}
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.