Serializing all necessary place information

Avoids places service calls on every search result

Change-Id: I15092468fa627291263b0fb89013153f39fbf83f
diff --git a/src/components/timeline-client.js b/src/components/timeline-client.js
index fe5ced4..3b7e0f6 100644
--- a/src/components/timeline-client.js
+++ b/src/components/timeline-client.js
@@ -11,7 +11,7 @@
     var self = this;
     return this.outer.service.getDestinationPlace(this.outer.context, this.id)
       .then(function(place) {
-        return ifcx.toPlace(self.outer.dependencies, place);
+        return ifcx.toPlace(self.outer.maps, place);
       });
   },
 
@@ -67,7 +67,7 @@
     },
 
     remove: function(i) {
-      return this.service.get(this.context, ifcx.box(i))
+      return this.service.remove(this.context, ifcx.box(i))
         .then(this.getDestination);
     },
 
@@ -123,17 +123,15 @@
         this.bindEvent('onFocus', 'onDestinationFocus');
         this.bindEvent('onPlaceChange', 'onDestinationPlaceChange',
           function(e) {
-            return Promise.all([
-              ifcx.toPlace(self.outer.dependencies, e.place),
-              ifcx.toPlace(self.outer.dependencies, e.previous)
-            ]);
+            return [
+              ifcx.toPlace(self.outer.maps, e.place),
+              ifcx.toPlace(self.outer.maps, e.previous)
+            ];
           });
         this.bindEvent('onSearch', 'onDestinationSearch', function(e) {
-          return Promise.all(e.places.map(function(place) {
-            return ifcx.toPlace(self.outer.dependencies, place);
-          })).then(function(places) {
-            return [places];
-          });
+          return [e.places.map(function(place) {
+            return ifcx.toPlace(self.outer.maps, place);
+          })];
         });
         this.bindEvent('onSubmit', 'onDestinationSubmit', function(e) {
           return [e.value];
@@ -176,12 +174,12 @@
     onError: 'memory'
   },
 
-  init: function(context, service, dependencies) {
+  init: function(context, service, maps) {
     var self = this;
 
     this.context = context;
     this.service = service;
-    this.dependencies = dependencies;
+    this.maps = maps;
     this.destinations = {};
 
     this.bindEvent('onAddClick');
diff --git a/src/components/timeline-server.js b/src/components/timeline-server.js
index 638baf1..56c9435 100644
--- a/src/components/timeline-server.js
+++ b/src/components/timeline-server.js
@@ -63,11 +63,11 @@
  * function, and we'd have to new Function each one to preserve that.
  *
  * @param timeline the timeline control to serve.
- * @param dependencies {placesService, maps}
+ * @param maps
  */
-function TimelineService(timeline, dependencies) {
+function TimelineService(timeline, maps) {
   this.timeline = timeline;
-  this.dependencies = dependencies;
+  this.maps = maps;
   this.destinations = {};
   this.destinationIds = new Map();
 
@@ -118,7 +118,7 @@
 
   setDestinationPlace: function(ctx, serverCall, id, place) {
     var destination = this.destinations[id];
-    return ifcx.toPlace(this.dependencies, place).then(destination.setPlace);
+    return destination.setPlace(ifcx.toPlace(this.maps, place));
   },
 
   setDestinationPlaceholder: function(ctx, serverCall, id, placeholder) {
@@ -127,7 +127,7 @@
 
   setDestinationSearchBounds: function(ctx, serverCall, id, bounds) {
     this.destinations[id].setSearchBounds(
-      ifcx.toLatLngBounds(this.dependencies.maps, bounds));
+      ifcx.toLatLngBounds(this.maps, bounds));
   },
 
   getDestinationValue: function(ctx, serverCall, id) {
@@ -175,7 +175,7 @@
 
   setSearchBounds: function(ctx, serverCall, bounds) {
     this.timeline.setSearchBounds(
-      ifcx.toLatLngBounds(this.dependencies.maps, bounds));
+      ifcx.toLatLngBounds(this.maps, bounds));
   },
 
   onAddClick: event('onAddClick',
diff --git a/src/ifc/conversions.js b/src/ifc/conversions.js
index 6cc210d..f83c60c 100644
--- a/src/ifc/conversions.js
+++ b/src/ifc/conversions.js
@@ -8,7 +8,7 @@
 
 var Place = require('../place');
 
-module.exports = {
+var x = {
   box: function(i) {
     return i === undefined || i === null? i : new vdlTravel.Int16({ value: i });
   },
@@ -17,35 +17,58 @@
     return ifc && ifc.value;
   },
 
-  toPlace: function(dependencies, ifc) {
-    return ifc? Place.fromObject(dependencies, ifc) : Promise.resolve();
+  toPlace: function(maps, ifc) {
+    return ifc && new Place({
+      'place_id': ifc.placeId,
+      geometry: {
+        location: x.toLatLng(maps, ifc.location),
+        viewport: x.toLatLngBounds(maps, ifc.viewport)
+      },
+      'formatted_address': ifc.formattedAddress,
+      name: ifc.name
+    });
   },
 
   fromPlace: function(place) {
-    return place && new vdlTravel.Place(place.toObject());
+    if (!place) {
+      return place;
+    }
+
+    var placeObj = place.getPlaceObject();
+    var details = place.getDetails();
+    return new vdlTravel.Place({
+      placeId: placeObj.placeId,
+      location: x.fromLatLng(place.getLocation()),
+      viewport: place.getGeometry().viewport,
+      formattedAddress: details && details['formatted_address'] ||
+        placeObj.query,
+      name: details && details.name
+    });
   },
 
   toLatLng: function(maps, ifc) {
-    return new maps.LatLng(ifc.lat, ifc.lng);
+    return ifc && new maps.LatLng(ifc.lat, ifc.lng);
   },
 
   fromLatLng: function(latlng) {
-    return new vdlTravel.LatLng({
+    return latlng && new vdlTravel.LatLng({
       lat: latlng.lat(),
       lng: latlng.lng()
     });
   },
 
   toLatLngBounds: function(maps, ifc) {
-    return new maps.LatLngBounds(
-      module.exports.toLatLng(maps, ifc.sw),
-      module.exports.toLatLng(maps, ifc.ne));
+    return ifc && new maps.LatLngBounds(
+      x.toLatLng(maps, ifc.sw),
+      x.toLatLng(maps, ifc.ne));
   },
 
   fromLatLngBounds: function(bounds) {
-    return new vdlTravel.LatLngBounds({
-      sw: module.exports.fromLatLng(bounds.getSouthWest()),
-      ne: module.exports.fromLatLng(bounds.getNorthEast())
+    return bounds && new vdlTravel.LatLngBounds({
+      sw: x.fromLatLng(bounds.getSouthWest()),
+      ne: x.fromLatLng(bounds.getNorthEast())
     });
   }
-};
\ No newline at end of file
+};
+
+module.exports = x;
\ No newline at end of file
diff --git a/src/ifc/types.vdl b/src/ifc/types.vdl
index b171441..506d826 100644
--- a/src/ifc/types.vdl
+++ b/src/ifc/types.vdl
@@ -34,6 +34,12 @@
 
 type Place struct {
   PlaceId Id
+
+  Location LatLng
+  Viewport ?LatLngBounds
+
+  FormattedAddress string
+  Name string
 }
 
 type Event struct {
diff --git a/src/travel.js b/src/travel.js
index 820bab1..9ddbbdb 100644
--- a/src/travel.js
+++ b/src/travel.js
@@ -140,8 +140,17 @@
         /* Since these controls are 1:1 with destinations, we don't want to stay
          * in a state where the control has invalid text but the destination is
          * still valid; that would be confusing to the user (e.g. abandoned
-         * query string "restaurants" for destination 4 Privet Drive.) */
-        control.onPlaceChange.add(destination.setPlace);
+         * query string "restaurants" for destination 4 Privet Drive.)
+         *
+         * However, if the place is valid, don't bother updating the
+         * destination. The destination is authoritative, and any disparity will
+         * be due to update lag (especially bad for remote components), which
+         * will result in oscillation. */
+        control.onPlaceChange.add(function(place) {
+          if (!place) {
+            destination.setPlace(place);
+          }
+        });
       }
 
       asyncs.push(updateOrdinalAsync());
@@ -434,7 +443,7 @@
           targetOwner, targetDeviceName, 'timeline');
         return args.vanadiumWrapper.client(endpoint).then(function(ts) {
           var tc = new TimelineClient(args.vanadiumWrapper.context(),
-            ts, self.dependencies);
+            ts, self.map.maps);
           tc.onError.add(self.error);
           return self.adoptTimeline(tc);
         });
@@ -444,7 +453,7 @@
     receiveTimelineCast: function() {
       var self = this;
       var timeline = new Timeline(this.map.maps);
-      var ts = new TimelineService(timeline, this.dependencies);
+      var ts = new TimelineService(timeline, this.map.maps);
 
       this.vanadiumStartup.then(function(args) {
         return args.vanadiumWrapper.server(
@@ -574,13 +583,13 @@
       sbName = '/localhost:' + sbName;
     }
 
-    this.dependencies = {
+    var dependencies = {
       maps: maps,
       placesService: map.createPlacesService()
     };
 
     var sync = this.sync = new TravelSync(
-      vanadiumStartup, this.dependencies, sbName);
+      vanadiumStartup, dependencies, sbName);
     sync.bindDestinations(destinations);
 
     this.info(strings['Connecting...'], sync.startup