From fe52bed04d064a2b85a9ebdef9b1c3a397a8063c Mon Sep 17 00:00:00 2001
From: Piotr Gawron <piotr.gawron@uni.lu>
Date: Fri, 6 Jan 2017 19:15:55 +0100
Subject: [PATCH] first approach to search Db Ovelay

clicking on map access appropriate elements vi api
---
 frontend-js/src/main/js/ServerConnector.js    |  70 +-------
 .../src/main/js/map/AbstractCustomMap.js      |  10 +-
 frontend-js/src/main/js/map/CustomMap.js      | 159 +++++++++---------
 .../src/main/js/map/data/IdentifiedElement.js |  33 ++--
 frontend-js/src/main/js/map/data/MapModel.js  |   3 +-
 frontend-js/src/main/js/map/data/Reaction.js  |  18 +-
 .../main/js/map/overlay/CommentDbOverlay.js   |  17 +-
 .../main/js/map/overlay/OverlayCollection.js  |  30 +++-
 .../main/js/map/overlay/SearchDbOverlay.js    | 106 ++++++++++++
 frontend-js/src/main/js/minerva.js            |   3 +
 .../src/test/js/ServerConnector-mock.js       |  12 --
 frontend-js/src/test/js/google-map-mock.js    |  40 ++++-
 frontend-js/src/test/js/helper.js             |  11 ++
 frontend-js/src/test/js/map/CustomMap-test.js |  31 +++-
 frontend-js/src/test/js/minerva-test.js       |  18 ++
 ...lse&projectId=sample&token=MOCK_TOKEN_ID&} |   0
 ...102&projectId=sample&token=MOCK_TOKEN_ID&} |   0
 ...5781&projectId=sample&token=MOCK_TOKEN_ID& |   1 +
 ...5781&projectId=sample&token=MOCK_TOKEN_ID& |   1 +
 ...9180&projectId=sample&token=MOCK_TOKEN_ID& |   1 +
 ...9173&projectId=sample&token=MOCK_TOKEN_ID& |   1 +
 ...3515&projectId=sample&token=MOCK_TOKEN_ID& |   1 +
 22 files changed, 375 insertions(+), 191 deletions(-)
 create mode 100644 frontend-js/src/main/js/map/overlay/SearchDbOverlay.js
 rename frontend-js/testFiles/apiCalls/comment/addComment/{content=&coordinates=2,12&elementId=&elementType=POINT&email=&modelId=102&name=&pinned=false&projectId=sample&token=MOCK_TOKEN_ID& => content=&coordinates=2.00,12.00&elementId=&elementType=POINT&email=&modelId=102&name=&pinned=false&projectId=sample&token=MOCK_TOKEN_ID&} (100%)
 rename frontend-js/testFiles/apiCalls/project/getClosestElementsByCoordinates/{coordinates=2,12&modelId=102&projectId=sample&token=MOCK_TOKEN_ID& => coordinates=2.00,12.00&modelId=102&projectId=sample&token=MOCK_TOKEN_ID&} (100%)
 create mode 100644 frontend-js/testFiles/apiCalls/project/getClosestElementsByCoordinates/coordinates=26547.33,39419.29&count=1&modelId=15781&projectId=sample&token=MOCK_TOKEN_ID&
 create mode 100644 frontend-js/testFiles/apiCalls/project/getClosestElementsByCoordinates/coordinates=26547.33,40201.07&count=1&modelId=15781&projectId=sample&token=MOCK_TOKEN_ID&
 create mode 100644 frontend-js/testFiles/apiCalls/project/getElements/columns=&id=329156,329162,329174,329180&projectId=sample&token=MOCK_TOKEN_ID&
 create mode 100644 frontend-js/testFiles/apiCalls/project/getElements/columns=id,bounds,modelId&id=329173&projectId=sample&token=MOCK_TOKEN_ID&
 create mode 100644 frontend-js/testFiles/apiCalls/project/getReactions/columns=&id=153515&projectId=sample&token=MOCK_TOKEN_ID&

diff --git a/frontend-js/src/main/js/ServerConnector.js b/frontend-js/src/main/js/ServerConnector.js
index 70b703de56..1de30ae567 100644
--- a/frontend-js/src/main/js/ServerConnector.js
+++ b/frontend-js/src/main/js/ServerConnector.js
@@ -43,13 +43,6 @@ ServerConnector.formIdentifier = "_gmapForm";
 
 ServerConnector._customMap = null;
 
-/**
- * Sets search query that will be handled by server.
- */
-ServerConnector.setSearchQuery = function(value) {
-  document.getElementById(ServerConnector.formIdentifier + ':mapParam').value = "" + value;
-};
-
 /**
  * Sets data mining query that will be handled by server.
  */
@@ -153,11 +146,6 @@ ServerConnector.updateOverlayCollection = function(overlayName, data, fitBounds)
   }
 };
 
-/**
- * Name of the overlay for 'search' overlay.
- */
-ServerConnector.SEARCH_OVERLAY_NAME = 'search';
-
 /**
  * Name of the overlay for 'data mining' overlay.
  */
@@ -178,30 +166,6 @@ ServerConnector.CHEMICAL_OVERLAY_NAME = 'chemical';
  */
 ServerConnector.MI_RNA_OVERLAY_NAME = 'mirna';
 
-/**
- * Register 'search' overlay on the server.
- */
-ServerConnector.registerSearchOverlay = function() {
-  _registerSearchOverlayCollection([ {
-    name : "overlayName",
-    value : ServerConnector.SEARCH_OVERLAY_NAME,
-  } ]);
-};
-
-/**
- * Sends request to the server to refresh data in 'search' overlay.
- */
-ServerConnector.refreshSearchOverlay = function() {
-  _refreshSearchOverlayCollection();
-};
-
-/**
- * Clear data related to 'search' overlay..
- */
-ServerConnector.clearSearchOverlay = function() {
-  _clearSearchOverlayCollection();
-};
-
 /**
  * Register 'data mining' overlay on the server.
  */
@@ -351,15 +315,6 @@ ServerConnector.clearMiRnaOverlay = function() {
   _clearMiRnaOverlayCollection();
 };
 
-/**
- * Define pack of methods for 'search' overlay.
- */
-ServerConnector._overlayMethods[ServerConnector.SEARCH_OVERLAY_NAME] = {
-    initFunction : ServerConnector.registerSearchOverlay,
-    refreshFunction : ServerConnector.refreshSearchOverlay,
-    clearFunction : ServerConnector.clearSearchOverlay,
-};
-
 /**
  * Define pack of methods for 'data minig' overlay.
  */
@@ -495,19 +450,6 @@ ServerConnector.addReactions = function(jsonReactions) {
   this.getCustomMap().addReactions(jsonReactions);
 };
 
-ServerConnector.searchByCoord = function(modelId, latLngCoordinates) {
-  _searchByCoord([ {
-    name : 'submodelId',
-    value : modelId
-  }, {
-    name : 'latCoord',
-    value : latLngCoordinates.lat()
-  }, {
-    name : 'lngCoord',
-    value : latLngCoordinates.lng()
-  } ]);
-};
-
 ServerConnector.sendReferenceGenomeDetailRequest = function(type, version) {
   logger.debug("Send request", type, version);
   _sendReferenceGenomeDetailRequest([ {
@@ -726,7 +668,7 @@ ServerConnector.idsToString = function (ids) {
 };
 
 ServerConnector.pointToString = function (point) {
-  return point.x+","+point.y;
+  return point.x.toFixed(2)+","+point.y.toFixed(2);
 };
 
 ServerConnector.columnsToString = function (columns) {
@@ -781,13 +723,15 @@ ServerConnector.getClosestElementsByCoordinatesUrl = function(params) {
   var projectId = params.projectId;
   var modelId = params.modelId;
   var token = params.token;
+  var count = params.count;
 
   return this.getApiUrl({type:"project",
     method:"getClosestElementsByCoordinates",
     params: {
       projectId: projectId, 
       coordinates: coordinates, 
-      modelId: modelId, 
+      modelId: modelId,
+      count: count,
       token: token},
   });
 };
@@ -1020,13 +964,13 @@ ServerConnector.getSessionData = function(project) {
 
 ServerConnector.getClosestElementsByCoordinates = function(params) {
   var self = this;
-  var projectId;
   return new Promise(function(resolve, reject) {
     return self.getProjectId(params.projectId).then(function(result) {
-      projectId = result;
+      params.projectId = result;
       return self.getToken();
     }).then(function(token) {
-      return self.readFile(self.getClosestElementsByCoordinatesUrl({projectId:projectId, token:token, modelId:params.modelId, coordinates: params.coordinates}));
+      params.token = token;
+      return self.readFile(self.getClosestElementsByCoordinatesUrl(params));
     }).then(function(content) {
       var array=JSON.parse(content);
       var result = [];
diff --git a/frontend-js/src/main/js/map/AbstractCustomMap.js b/frontend-js/src/main/js/map/AbstractCustomMap.js
index 5263499926..7a70a061cc 100644
--- a/frontend-js/src/main/js/map/AbstractCustomMap.js
+++ b/frontend-js/src/main/js/map/AbstractCustomMap.js
@@ -313,7 +313,11 @@ AbstractCustomMap.prototype.registerMapClickEvents = function() {
 
   // search event
   google.maps.event.addListener(this.getGoogleMap(), 'click', function(mouseEvent) {
-    ServerConnector.searchByCoord(self.getId(), mouseEvent.latLng);
+    var point = self.fromLatLngToPoint(mouseEvent.latLng);
+    var searchDb = customMap.getOverlayByName('search');
+    return searchDb.searchByCoordinates(self.getModel(), point).then(function() {
+      return customMap.updateOverlayCollection(searchDb, false);
+    }).catch(GuiConnector.alert);
   });
 
   // select last clicked map
@@ -685,8 +689,8 @@ AbstractCustomMap.prototype.getAliasVisibleLayoutsData = function(aliasId) {
   for (var i = 0; i < layoutIds.length; i++) {
     promises.push(this.getModel().getLayoutDataById(layoutIds[i]));
   }
-  return new Promise(function(resolve){
-    return Promise.all(promises).then(function(layouts){
+  return new Promise(function(resolve) {
+    return Promise.all(promises).then(function(layouts) {
       var result = [];
       for (var i = 0; i < layouts.length; i++) {
         var layout = layouts[i];
diff --git a/frontend-js/src/main/js/map/CustomMap.js b/frontend-js/src/main/js/map/CustomMap.js
index 9e3d6a61b5..689281d9de 100644
--- a/frontend-js/src/main/js/map/CustomMap.js
+++ b/frontend-js/src/main/js/map/CustomMap.js
@@ -16,6 +16,7 @@ var PointMarker = require('./marker/PointMarker');
 var ReactionMarker = require('./marker/ReactionMarker');
 var ReactionOverlay = require('./overlay/ReactionOverlay');
 var ReferenceGenome = require('./data/ReferenceGenome');
+var SearchDbCollection = require('./overlay/SearchDbOverlay');
 var Submap = require('./Submap');
 var TouchMap = require('./TouchMap');
 
@@ -214,7 +215,9 @@ CustomMap.prototype.refreshOverlays = function() {
   for ( var overlayName in this.overlayCollections) {
     if (this.overlayCollections.hasOwnProperty(overlayName)) {
       var collection = this.overlayCollections[overlayName];
-      promises.push(collection.refresh());
+      if (!collection instanceof SearchDbCollection) {
+        promises.push(collection.refresh());
+      }
     }
   }
   return Promise.all(promises);
@@ -549,25 +552,16 @@ CustomMap.prototype.refreshOverlayMarkers = function(overlay) {
     if (overlay.reactionMarkers.hasOwnProperty(reactionKey) && overlay.reactionMarkers[reactionKey] !== undefined
         && overlay.reactionMarkers[reactionKey] !== null) {
       var reactionOverlay = overlay.reactionMarkers[reactionKey];
-      if (reactionOverlay.getReactionData() === null || reactionOverlay.getReactionData() === undefined) {
-        reactionOverlay.getCustomMap().getModel().getReactionById(reactionOverlay.getId()).then(function(reactionData) {
-          reactionOverlay.setReactionData(reactionData);
-          reactionOverlay.init();
-          reactionOverlay.show();
-          updated = true;
-          bounds = reactionOverlay.getBounds();
-          boundsArray[reactionOverlay.getCustomMap().getId()].extend(bounds.getNorthEast());
-          boundsArray[reactionOverlay.getCustomMap().getId()].extend(bounds.getSouthWest());
-        });
-      } else {
-        bounds = reactionOverlay.getBounds();
-        if (!this.isMarkerOptimization()) {
-          reactionOverlay.hide();
-          reactionOverlay.show();
-        }
-        boundsArray[reactionOverlay.getCustomMap().getId()].extend(bounds.getNorthEast());
-        boundsArray[reactionOverlay.getCustomMap().getId()].extend(bounds.getSouthWest());
+      if (!reactionOverlay.isShown()) {
+        reactionOverlay.show();
+      }
+      bounds = reactionOverlay.getBounds();
+      if (!this.isMarkerOptimization()) {
+        reactionOverlay.hide();
+        reactionOverlay.show();
       }
+      boundsArray[reactionOverlay.getCustomMap().getId()].extend(bounds.getNorthEast());
+      boundsArray[reactionOverlay.getCustomMap().getId()].extend(bounds.getSouthWest());
     }
   }
 
@@ -1356,70 +1350,75 @@ CustomMap.prototype.renderOverlayCollection = function(overlayCollection, fitBou
     fitBounds = overlayCollection.fitBounds;
     overlayCollection = overlayCollection.overlayCollection;
   }
+  
+  var elements;
+
+  return overlayCollection.getIdentifiedElements().then(function(identifiedElements){
+    elements = identifiedElements;
+    var boundsArray = [];
+    boundsArray[self.getId()] = new google.maps.LatLngBounds();
+    for (var j = 0; j < self.submaps.length; j++) {
+      boundsArray[self.submaps[j].getId()] = new google.maps.LatLngBounds();
+    }
 
-  var elements = overlayCollection.getIdentifiedElements();
-
-  var boundsArray = [];
-  boundsArray[this.getId()] = new google.maps.LatLngBounds();
-  for (var j = 0; j < this.submaps.length; j++) {
-    boundsArray[this.submaps[j].getId()] = new google.maps.LatLngBounds();
-  }
-
-  var bounds;
-
-  return Promise.each(
-      elements,
-      function(element) {
-        var model = self.getSubmodelById(element.modelId);
-        if (element.getType() === "ALIAS") {
-          if (overlayCollection.aliasMarkers[element.getId()] !== undefined) {
-            logger.warn("More than one marker in " + overlayCollection.name + " for alias " + element.getId()
-                + ". Skipping duplicates.");
-            return null;
-          } else {
-            return model.getModel().getAliasById(element.getId()).then(function(aliasData) {
-              var aliasMarker = new AliasMarker(element.getId(), element.icon, aliasData, model);
-              overlayCollection.aliasMarkers[element.getId()] = aliasMarker;
-              bounds = aliasMarker.getBounds();
+    var bounds;
+
+    return Promise.each(
+        elements,
+        function(element) {
+          var model = self.getSubmodelById(element.modelId);
+          if (element.getType() === "ALIAS") {
+            if (overlayCollection.aliasMarkers[element.getId()] !== undefined) {
+              logger.warn("More than one marker in " + overlayCollection.name + " for alias " + element.getId()
+                  + ". Skipping duplicates.");
+              return null;
+            } else {
+              return model.getModel().getAliasById(element.getId()).then(function(aliasData) {
+                var aliasMarker = new AliasMarker(element.getId(), element.icon, aliasData, model);
+                overlayCollection.aliasMarkers[element.getId()] = aliasMarker;
+                bounds = aliasMarker.getBounds();
+                boundsArray[element.getModelId()].extend(bounds.getNorthEast());
+                boundsArray[element.getModelId()].extend(bounds.getSouthWest());
+                return aliasMarker;
+              });
+            }
+          } else if (element.getType() === "REACTION") {
+            return model.getModel().getReactionById(element.getId()).then(function(reactionData) {
+              var marker = null;
+              var icon = element.getIcon();
+
+              if (icon === null || icon === undefined) {
+                // this should happen when we visualize search data (there is
+                // no marker, but only flat overlay of the reaction lines)
+                //
+                marker = new ReactionOverlay(null, reactionData, model, false, element.getId());
+              } else {
+                // when we have icon defined (for instance when it comes from
+                // comment) then we don't want to have overlayed reaction lines
+                // but icon marker
+                marker = new ReactionMarker(element.getId(), icon, reactionData, model);
+              }
+              marker.show();
+              overlayCollection.reactionMarkers[element.getId()] = marker;
+              bounds = marker.getBounds();
               boundsArray[element.getModelId()].extend(bounds.getNorthEast());
               boundsArray[element.getModelId()].extend(bounds.getSouthWest());
-              return aliasMarker;
+              return marker;
             });
-          }
-        } else if (element.getType() === "REACTION") {
-          return model.getModel().getReactionById(element.getId()).then(function(reactionData) {
-            var marker = null;
-            var icon = element.getIcon();
-
-            if (icon === null || icon === undefined) {
-              // this should happen when we visualize search data (there is
-              // no marker, but only flat overlay of the reaction lines)
-              //
-              marker = new ReactionOverlay(null, reactionData, model, false, element.getId());
-            } else {
-              // when we have icon defined (for instance when it comes from
-              // comment) then we don't want to have overlayed reaction lines
-              // but icon marker
-              marker = new ReactionMarker(element.getId(), icon, reactionData, model);
-            }
-            overlayCollection.reactionMarkers[element.getId()] = marker;
-            bounds = marker.getBounds();
+          } else if (element.getType() === "POINT") {
+            var pointData = model.getModel().getPointDataByPoint(element.getPoint());
+            var pointMarker = new PointMarker(pointData, element.icon, model);
+            overlayCollection.pointMarkers[pointData.getId()] = pointMarker;
+            bounds = pointMarker.getBounds();
             boundsArray[element.getModelId()].extend(bounds.getNorthEast());
             boundsArray[element.getModelId()].extend(bounds.getSouthWest());
-            return marker;
-          });
-        } else if (element.getType() === "POINT") {
-          var pointData = model.getModel().getPointDataByPoint(element.getPoint());
-          var pointMarker = new PointMarker(pointData, element.icon, model);
-          overlayCollection.pointMarkers[pointData.getId()] = pointMarker;
-          bounds = pointMarker.getBounds();
-          boundsArray[element.getModelId()].extend(bounds.getNorthEast());
-          boundsArray[element.getModelId()].extend(bounds.getSouthWest());
-          return pointMarker;
-        } else {
-          throw new Error("Unknown type of the element in overlay: " + element.type);
-        }
-      }).then(function() {
+            return pointMarker;
+          } else {
+            throw new Error("Unknown type of the element in overlay: " + element.type);
+          }
+        });
+  }).then(function() {
+
     for (var i = 0; i < elements.length; i++) {
       var element = elements[i];
       var infoWindow = self.getInfoWindowForIdentifiedElement(element);
@@ -1447,6 +1446,8 @@ CustomMap.prototype.renderOverlayCollection = function(overlayCollection, fitBou
 
   });
 
+
+
 };
 
 /**
@@ -1646,6 +1647,10 @@ CustomMap.prototype.getOverlayDataForIdentifiedElement = function(identifiedElem
   });
 };
 
+CustomMap.prototype.getOverlayByName = function(name) {
+  return this.overlayCollections[name];
+};
+
 /**
  * Returns {@link AbstractInfoWindow} for element identified by the parameter.
  * 
diff --git a/frontend-js/src/main/js/map/data/IdentifiedElement.js b/frontend-js/src/main/js/map/data/IdentifiedElement.js
index 4b849f949b..5f4730bad2 100644
--- a/frontend-js/src/main/js/map/data/IdentifiedElement.js
+++ b/frontend-js/src/main/js/map/data/IdentifiedElement.js
@@ -21,15 +21,15 @@ function IdentifiedElement(javaObject) {
   if (javaObject instanceof Alias) {
     this.setId(javaObject.getId());
     this.setModelId(javaObject.getModelId());
-    this.type = "ALIAS";
+    this.setType("ALIAS");
   } else if (javaObject instanceof Reaction) {
     this.setId(javaObject.getId());
     this.setModelId(javaObject.getModelId());
-    this.type = "REACTION";
+    this.setType("REACTION");
   } else if (javaObject instanceof PointData) {
     this.setId(javaObject.getId());
     this.setModelId(javaObject.getModelId());
-    this.type = "POINT";
+    this.setType("POINT");
   } else {
     // identifier of the object to visualize
     if (javaObject.objectId === undefined) {
@@ -38,19 +38,14 @@ function IdentifiedElement(javaObject) {
       this.setId(javaObject.objectId);
     }
     // which marker should be used to show this object
-    this.icon = javaObject.icon;
+    this.setIcon(javaObject.icon);
     // on which model the element is located
     this.setModelId(javaObject.modelId);
     // what kind of object we are talking about
-    this.type = javaObject.type;
+    this.setType(javaObject.type);
   }
 
-  if (this.type === undefined || this.type === null) {
-    throw new Error("Type not defined for element: " + javaObject);
-  }
-
-  this.type = this.type.toUpperCase();
-  if (this.type === "POINT") {
+  if (this.getType() === "POINT") {
     var tmp = this.getId();
     if (tmp.indexOf("Point2D.Double") >= 0) {
       tmp = tmp.replace("Point2D.Double", "");
@@ -63,8 +58,8 @@ function IdentifiedElement(javaObject) {
     var x = parseFloat(tmp[0]).toFixed(2);
     var y = parseFloat(tmp[1]).toFixed(2);
     this._point = new google.maps.Point(x, y);
-  } else if (this.type !== "ALIAS" && this.type !== "REACTION") {
-    throw new Error("Unknown type of identified element: " + this.type);
+  } else if (this.getType() !== "ALIAS" && this.getType() !== "REACTION") {
+    throw new Error("Unknown type of identified element: " + this.getType());
   }
 
   if (this.getId() === undefined || this.getId() === null) {
@@ -137,6 +132,14 @@ IdentifiedElement.prototype.getType = function() {
   return this.type;
 };
 
+IdentifiedElement.prototype.setType = function(type) {
+  if (type === undefined || type === null) {
+    throw new Error("Type not defined");
+  }
+
+  this.type = type.toUpperCase();
+};
+
 /**
  * Returns icon that should be used for visualization.
  * 
@@ -146,4 +149,8 @@ IdentifiedElement.prototype.getIcon = function() {
   return this.icon;
 };
 
+IdentifiedElement.prototype.setIcon = function(icon) {
+  this.icon = icon;
+};
+
 module.exports = IdentifiedElement;
diff --git a/frontend-js/src/main/js/map/data/MapModel.js b/frontend-js/src/main/js/map/data/MapModel.js
index 6201f0a402..c7370e2973 100644
--- a/frontend-js/src/main/js/map/data/MapModel.js
+++ b/frontend-js/src/main/js/map/data/MapModel.js
@@ -177,7 +177,7 @@ MapModel.prototype.getReactionById = function(id, complete) {
 MapModel.prototype.getCompleteReactionById = function(id) {
   var self = this;
   return new Promise(function(resolve) {
-    if (self._reactions[id]!==undefined && self._reactions[id].isComplete()) {
+    if (self._reactions[id] instanceof Reaction && self._reactions[id].isComplete()) {
       resolve(self._reactions[id]);
     } else {
       var result;
@@ -296,7 +296,6 @@ MapModel.prototype.getMissingElements = function(elements) {
     }
   }
 
-  
   return new Promise(function(resolve, reject) {
     Promise.all([reactionPromise, aliasPromise]).then(function(values) {
       var result = [], i;      
diff --git a/frontend-js/src/main/js/map/data/Reaction.js b/frontend-js/src/main/js/map/data/Reaction.js
index 8b6aae5848..395353c819 100644
--- a/frontend-js/src/main/js/map/data/Reaction.js
+++ b/frontend-js/src/main/js/map/data/Reaction.js
@@ -39,7 +39,7 @@ function Reaction(javaObject) {
     }
     this.setCenter(javaObject.centerPoint);
     this.setModelId(javaObject.modelId);
-    this.setCompletness(false);
+    this.setIsComplete(false);
     this.update(javaObject);
   }
 }
@@ -112,12 +112,12 @@ Reaction.prototype.update = function(javaObject) {
   }
 };
 
-Reaction.prototype.getCompletness = function() {
-  return this._completness;
+Reaction.prototype.isComplete = function() {
+  return this._complete;
 };
 
-Reaction.prototype.setCompletness = function(completness) {
-  this._completness = completness;
+Reaction.prototype.setIsComplete = function(complete) {
+  this._complete = complete;
 };
 
 Reaction.prototype.getReactionId = function() {
@@ -144,6 +144,14 @@ Reaction.prototype.getProducts = function() {
   return this._products;
 };
 
+Reaction.prototype.getElements = function() {
+  var result = [];
+  result = result.concat(this.getReactants());
+  result = result.concat(this.getProducts());
+  result = result.concat(this.getModifiers());
+  return result;
+};
+
 Reaction.prototype.setModifiers = function(modifiers) {
   this._modifiers = modifiers;
 };
diff --git a/frontend-js/src/main/js/map/overlay/CommentDbOverlay.js b/frontend-js/src/main/js/map/overlay/CommentDbOverlay.js
index 41df64f1cc..abbbec7715 100644
--- a/frontend-js/src/main/js/map/overlay/CommentDbOverlay.js
+++ b/frontend-js/src/main/js/map/overlay/CommentDbOverlay.js
@@ -43,14 +43,17 @@ CommentDbOverlay.prototype._getDetailArrayByIdentifiedElement = function(element
 
 
 CommentDbOverlay.prototype.getIdentifiedElements = function(){
-  var result = [];
-  for (var i=0;i<this.elements.length;i++) {
-    //we return only elements that are pinned to the map and weren't removed
-    if (!this.elements[i].isRemoved()) {
-      result.push(this.elements[i].getIdentifiedElement());
+  var self = this;
+  return new Promise(function(resolve){
+    var result = [];
+    for (var i=0;i<self.elements.length;i++) {
+      // we return only elements that are pinned to the map and weren't removed
+      if (!self.elements[i].isRemoved()) {
+        result.push(self.elements[i].getIdentifiedElement());
+      }
     }
-  }
-  return result;
+    resolve(result);
+  });
 };
 
 
diff --git a/frontend-js/src/main/js/map/overlay/OverlayCollection.js b/frontend-js/src/main/js/map/overlay/OverlayCollection.js
index c1fff1bcb6..0679d67318 100644
--- a/frontend-js/src/main/js/map/overlay/OverlayCollection.js
+++ b/frontend-js/src/main/js/map/overlay/OverlayCollection.js
@@ -236,8 +236,34 @@ OverlayCollection.prototype.setAllowGeneralSearch = function(allowGeneralSearch)
   }
 };
 
-OverlayCollection.prototype.getIdentifiedElements = function(){
-  return this.elements;
+OverlayCollection.prototype.getIdentifiedElements = function() {
+  var self = this;
+  return new Promise(function(resolve) {
+    resolve(self.elements);
+  });
+};
+
+OverlayCollection.prototype.setIconType = function(iconType) {
+  return this._iconType = iconType;
+};
+OverlayCollection.prototype.setIconStart = function(iconStart) {
+  return this._iconStart = iconStart;
+};
+
+OverlayCollection.IconColors = [ "red", "blue", "green", "purple", "yellow", "pink", "paleblue", "brown", "orange" ];
+
+OverlayCollection.prototype.getColor = function(colorId) {
+  var id = colorId + this._iconStart;
+  id %= OverlayCollection.IconColors.length;
+  return OverlayCollection.IconColors[id];
+}
+
+OverlayCollection.prototype.getIcon = function(colorId, id) {
+  if (id >= 100) {
+    id = 1;
+  }
+  var color = this.getColor(colorId);
+  return "marker/" + this._iconType + "/" + this._iconType + "_" + color + "_" + id + ".png";
 };
 
 module.exports = OverlayCollection;
diff --git a/frontend-js/src/main/js/map/overlay/SearchDbOverlay.js b/frontend-js/src/main/js/map/overlay/SearchDbOverlay.js
new file mode 100644
index 0000000000..be7537e826
--- /dev/null
+++ b/frontend-js/src/main/js/map/overlay/SearchDbOverlay.js
@@ -0,0 +1,106 @@
+"use strict";
+
+var Promise = require("bluebird");
+
+var Alias = require('../data/Alias');
+var IdentifiedElement = require('../data/IdentifiedElement');
+var OverlayCollection = require('./OverlayCollection');
+
+var logger = require('../../logger');
+
+function SearchDbOverlay(params) {
+  // call super constructor
+  OverlayCollection.call(this, params);
+  
+  this.setIconType("marker");
+  this.setIconStart(0);
+  this._elementsByQuery = [];
+}
+
+SearchDbOverlay.prototype = Object.create(OverlayCollection.prototype);
+SearchDbOverlay.prototype.constructor = SearchDbOverlay;
+
+SearchDbOverlay.QueryType = {
+    SEARCH_BY_COORDINATES : "SEARCH_BY_COORDINATES",
+    SEARCH_BY_QUERY : "SEARCH_BY_QUERY",
+};
+
+function encodeQuery(type, model, arg){
+  if (type === SearchDbOverlay.QueryType.SEARCH_BY_COORDINATES) {
+    return type + "_"+model.getId()+"_"+arg.x+","+arg.y;
+  } else {
+    throw new Error("Unknown query type: "+type);
+  }
+}
+
+SearchDbOverlay.prototype.searchByCoordinates = function(model, coordinates) {
+  var self = this;
+  return new Promise(function(resolve, reject) {
+    var query = encodeQuery(SearchDbOverlay.QueryType.SEARCH_BY_COORDINATES, model, coordinates);
+    self.setQueries([query]);
+    
+    if (self._elementsByQuery[query] !== undefined) {
+      resolve(self._elementsByQuery[query]);
+    } else {
+      return ServerConnector.getClosestElementsByCoordinates({
+        modelId:model.getId(), coordinates: coordinates, count: 1
+      }).then(function(elements) {
+        self._elementsByQuery[query] = elements;
+        if (elements[0].getType()==="REACTION") {
+          var model = self.getMap().getSubmodelById(elements[0].getModelId()).getModel(); 
+          return model.getReactionById(elements[0].getId(), true).then(function(reaction){
+            var i=0;
+            var reactionElements = reaction.getElements(); 
+            for (i=0;i<reactionElements.length;i++) {
+              self._elementsByQuery[query].push(new IdentifiedElement(reactionElements[i]));
+            }
+            resolve(self._elementsByQuery[query]);
+          }).catch(reject);
+        } else {
+          resolve(self._elementsByQuery[query]);
+        }
+      }).catch(reject);
+    }
+  });
+};
+
+SearchDbOverlay.prototype.setQueries = function(queries){
+  this._queries = queries;
+};
+
+SearchDbOverlay.prototype.getQueries = function(){
+  return this._queries ;
+};
+
+SearchDbOverlay.prototype.getIdentifiedElements = function(){
+  var self = this;
+
+  return new Promise(function(resolve){
+    var queries = self.getQueries();
+    var result = [];
+    for (var i=0;i<queries.length;i++) {
+      var query = queries[i];
+      var elements = self._elementsByQuery[query];
+
+      var iconCounter =1;
+      for (var j=0;j<elements.length;j++) {
+        var element = elements[j];
+        var ie = new IdentifiedElement(element);
+        if (element.getType() === "ALIAS") {
+          ie.setIcon(self.getIcon(i, iconCounter++));
+        } else if (element.getType() !== "REACTION") {
+          throw new Error("Unknown element type: "+element.getType());
+        }
+        result.push(ie);
+      }
+    }
+    resolve(result);
+  });
+};
+
+SearchDbOverlay.prototype.refresh = function(){
+  throw new Error("Refreshing shouldn't be called");
+};
+
+
+module.exports = SearchDbOverlay;
diff --git a/frontend-js/src/main/js/minerva.js b/frontend-js/src/main/js/minerva.js
index 3aab0b86ef..daf8c324d7 100644
--- a/frontend-js/src/main/js/minerva.js
+++ b/frontend-js/src/main/js/minerva.js
@@ -5,6 +5,7 @@ var functions = require('./Functions');
 var CommentDbOverlay = require('./map/overlay/CommentDbOverlay');
 var CustomMap = require('./map/CustomMap');
 var OverlayCollection = require('./map/overlay/OverlayCollection');
+var SearchDbOverlay = require('./map/overlay/SearchDbOverlay');
 
 var OriginalGuiConnector = require('./GuiConnector');
 var OriginalServerConnector = require('./ServerConnector');
@@ -75,6 +76,8 @@ function create(params) {
       var collection = null;
       if (collectionParams.name === "comment") {
         collection = new CommentDbOverlay(collectionParams);
+      } else if (collectionParams.name === "search") {
+        collection = new SearchDbOverlay(collectionParams);
       } else {
         collection = new OverlayCollection(collectionParams);
       }
diff --git a/frontend-js/src/test/js/ServerConnector-mock.js b/frontend-js/src/test/js/ServerConnector-mock.js
index 0f84b28d5a..1819d39311 100644
--- a/frontend-js/src/test/js/ServerConnector-mock.js
+++ b/frontend-js/src/test/js/ServerConnector-mock.js
@@ -108,18 +108,6 @@ ServerConnectorMock.sendOverlayDetailDataRequest = function(overlayName, identif
   ServerConnectorMock.callListeners("onSendOverlayDetailDataRequest", overlayName, identifiedElement);
 };
 
-ServerConnectorMock.searchByCoord = function(modelId, latLngCoordinates) {
-  ServerConnectorMock.callListeners("onSearchByCoord", [ modelId, latLngCoordinates ]);
-};
-
-ServerConnectorMock.setCustomMap = function(customMap) {
-  this._customMap = customMap;
-};
-
-ServerConnectorMock.getCustomMap = function() {
-  return this._customMap;
-};
-
 ServerConnectorMock.addOverlayCollection = function(overlay) {
   logger.debug("Adding overlay: " + overlay);
 };
diff --git a/frontend-js/src/test/js/google-map-mock.js b/frontend-js/src/test/js/google-map-mock.js
index 949e0a8474..da21005981 100644
--- a/frontend-js/src/test/js/google-map-mock.js
+++ b/frontend-js/src/test/js/google-map-mock.js
@@ -15,12 +15,14 @@ var google = {
         object.addEventListener(type, fun);
       },
       trigger : function(object, type, param) {
+        var promises = [];
         for (var i = 0; i < google.maps.event._data.length; i++) {
           var e = google.maps.event._data[i];
           if (e.object === object && e.type === type) {
-            e.fun(param);
+            promises.push(e.fun(param));
           }
         }
+        return Promise.all(promises);
       },
     },
     drawing : {
@@ -61,15 +63,44 @@ var google = {
       };
     },
     LatLngBounds : function(ne, sw) {
+      var data = {
+        ne : ne,
+        sw : sw
+      };
       return {
         getSouthWest : function() {
-          return sw;
+          return data.sw;
         },
         getNorthEast : function() {
-          return ne;
+          return data.ne;
+        },
+        isEmpty : function() {
+          return ne === sw || (data.ne.lat() === sw.lat() && data.ne.lng() === sw.lng());
         },
-        extend : function() {
+        extend : function(arg) {
+          if (data.sw === undefined) {
+            data.sw = arg;
+          } else {
+            if (arg.lng() < data.sw.lng()) {
+              data.sw.longitude = arg.lng();
+            }
+            if (arg.lat() < data.sw.lat()) {
+              data.sw.longitude = arg.lng();
+            }
+          }
+          if (data.ne === undefined) {
+            data.ne = arg;
+          } else {
+            if (arg.lng() > data.ne.lng()) {
+              data.ne.longitude = arg.lng();
+            }
+
+            if (arg.lat() > data.ne.lat()) {
+              data.ne.longitude = arg.lng();
+            }
+          }
         },
+
       };
     },
     OverlayView : function() {
@@ -97,6 +128,7 @@ var google = {
     },
     Marker : function(options) {
       this.options = options;
+      this.position = options.position;
       this.getMap = function() {
         return this.options.map;
       };
diff --git a/frontend-js/src/test/js/helper.js b/frontend-js/src/test/js/helper.js
index 7a61d23de1..b7001fdb64 100644
--- a/frontend-js/src/test/js/helper.js
+++ b/frontend-js/src/test/js/helper.js
@@ -16,6 +16,7 @@ var Model = require("../../main/js/map/data/MapModel");
 var OverlayCollection = require("../../main/js/map/overlay/OverlayCollection");
 var Project = require("../../main/js/map/data/Project");
 var Reaction = require("../../main/js/map/data/Reaction");
+var SearchDbOverlay = require("../../main/js/map/overlay/SearchDbOverlay");
 
 function Helper() {
   this.idCounter = 1000000;
@@ -30,6 +31,16 @@ Helper.prototype.createCommentDbOverlay = function(map) {
   return result;
 };
 
+Helper.prototype.createSearchDbOverlay = function(map) {
+  var result = new SearchDbOverlay({
+    map : map,
+    name : "search",
+  });
+  return result;
+};
+
+
+
 Helper.prototype.createDrugDbOverlay = function(map) {
   var result = this.createDbOverlay(map);
   result.setName('drug');
diff --git a/frontend-js/src/test/js/map/CustomMap-test.js b/frontend-js/src/test/js/map/CustomMap-test.js
index c7d46c095a..3eebf279e5 100644
--- a/frontend-js/src/test/js/map/CustomMap-test.js
+++ b/frontend-js/src/test/js/map/CustomMap-test.js
@@ -467,18 +467,43 @@ describe('CustomMap', function() {
     assert.equal(map.getId(), map.getActiveSubmapId());
 
   });
+  
   it("left click on map", function() {
     var map = helper.createCustomMap();
-
+    map.getModel().setId(15781);
+    
+    var searchOverlay = helper.createSearchDbOverlay(map);
+    
     var mev = {
       stop : null,
       latLng : new google.maps.LatLng(40.0, -90.0)
     };
 
     assert.notOk(map.getActiveSubmapId());
-    google.maps.event.trigger(map.getGoogleMap(), 'click', mev);
-    assert.equal(map.getId(), map.getActiveSubmapId());
+    return google.maps.event.trigger(map.getGoogleMap(), 'click', mev).then(function(){
+      assert.equal(map.getId(), map.getActiveSubmapId());
+      assert.ok(searchOverlay.aliasMarkers[329173]);
+    });
+  });
 
+  it("left click on reaction", function() {
+    var map = helper.createCustomMap();
+    map.getModel().setId(15781);
+    
+    var searchOverlay = helper.createSearchDbOverlay(map);
+    
+    var mev = {
+      stop : null,
+      latLng : new google.maps.LatLng(42.0, -90.0)
+    };
+
+    assert.notOk(map.getActiveSubmapId());
+    return google.maps.event.trigger(map.getGoogleMap(), 'click', mev).then(function(){
+      assert.equal(map.getId(), map.getActiveSubmapId());
+      assert.ok(searchOverlay.reactionMarkers[153515]);
+      assert.ok(searchOverlay.reactionMarkers[153515].isShown());
+      assert.ok(searchOverlay.aliasMarkers[329162]);
+    });
   });
 
   it("create polygon through drawingManager", function() {
diff --git a/frontend-js/src/test/js/minerva-test.js b/frontend-js/src/test/js/minerva-test.js
index f2252c102d..7dc361355f 100644
--- a/frontend-js/src/test/js/minerva-test.js
+++ b/frontend-js/src/test/js/minerva-test.js
@@ -126,4 +126,22 @@ describe('minerva global', function() {
       assert.ok(result);
     });
   });
+  
+  it('create with search overlay', function() {
+    var project = helper.createProject();
+    project.getModel().setId(15781);
+    var map = helper.createGoogleMap();
+
+    var options = {
+      map : map,
+      project : project,
+      dataCollections : [ {
+        name : "search"
+      } ],
+    };
+
+    return minerva.create(options).then(function(result) {
+      assert.ok(result);
+    });
+  });
 });
diff --git a/frontend-js/testFiles/apiCalls/comment/addComment/content=&coordinates=2,12&elementId=&elementType=POINT&email=&modelId=102&name=&pinned=false&projectId=sample&token=MOCK_TOKEN_ID& b/frontend-js/testFiles/apiCalls/comment/addComment/content=&coordinates=2.00,12.00&elementId=&elementType=POINT&email=&modelId=102&name=&pinned=false&projectId=sample&token=MOCK_TOKEN_ID&
similarity index 100%
rename from frontend-js/testFiles/apiCalls/comment/addComment/content=&coordinates=2,12&elementId=&elementType=POINT&email=&modelId=102&name=&pinned=false&projectId=sample&token=MOCK_TOKEN_ID&
rename to frontend-js/testFiles/apiCalls/comment/addComment/content=&coordinates=2.00,12.00&elementId=&elementType=POINT&email=&modelId=102&name=&pinned=false&projectId=sample&token=MOCK_TOKEN_ID&
diff --git a/frontend-js/testFiles/apiCalls/project/getClosestElementsByCoordinates/coordinates=2,12&modelId=102&projectId=sample&token=MOCK_TOKEN_ID& b/frontend-js/testFiles/apiCalls/project/getClosestElementsByCoordinates/coordinates=2.00,12.00&modelId=102&projectId=sample&token=MOCK_TOKEN_ID&
similarity index 100%
rename from frontend-js/testFiles/apiCalls/project/getClosestElementsByCoordinates/coordinates=2,12&modelId=102&projectId=sample&token=MOCK_TOKEN_ID&
rename to frontend-js/testFiles/apiCalls/project/getClosestElementsByCoordinates/coordinates=2.00,12.00&modelId=102&projectId=sample&token=MOCK_TOKEN_ID&
diff --git a/frontend-js/testFiles/apiCalls/project/getClosestElementsByCoordinates/coordinates=26547.33,39419.29&count=1&modelId=15781&projectId=sample&token=MOCK_TOKEN_ID& b/frontend-js/testFiles/apiCalls/project/getClosestElementsByCoordinates/coordinates=26547.33,39419.29&count=1&modelId=15781&projectId=sample&token=MOCK_TOKEN_ID&
new file mode 100644
index 0000000000..189815b72b
--- /dev/null
+++ b/frontend-js/testFiles/apiCalls/project/getClosestElementsByCoordinates/coordinates=26547.33,39419.29&count=1&modelId=15781&projectId=sample&token=MOCK_TOKEN_ID&
@@ -0,0 +1 @@
+[{"modelId":15781,"id":153515,"type":"REACTION"}]
\ No newline at end of file
diff --git a/frontend-js/testFiles/apiCalls/project/getClosestElementsByCoordinates/coordinates=26547.33,40201.07&count=1&modelId=15781&projectId=sample&token=MOCK_TOKEN_ID& b/frontend-js/testFiles/apiCalls/project/getClosestElementsByCoordinates/coordinates=26547.33,40201.07&count=1&modelId=15781&projectId=sample&token=MOCK_TOKEN_ID&
new file mode 100644
index 0000000000..410f869e59
--- /dev/null
+++ b/frontend-js/testFiles/apiCalls/project/getClosestElementsByCoordinates/coordinates=26547.33,40201.07&count=1&modelId=15781&projectId=sample&token=MOCK_TOKEN_ID&
@@ -0,0 +1 @@
+[{"modelId":15781,"id":329173,"type":"ALIAS"}]
\ No newline at end of file
diff --git a/frontend-js/testFiles/apiCalls/project/getElements/columns=&id=329156,329162,329174,329180&projectId=sample&token=MOCK_TOKEN_ID& b/frontend-js/testFiles/apiCalls/project/getElements/columns=&id=329156,329162,329174,329180&projectId=sample&token=MOCK_TOKEN_ID&
new file mode 100644
index 0000000000..50f4f50347
--- /dev/null
+++ b/frontend-js/testFiles/apiCalls/project/getElements/columns=&id=329156,329162,329174,329180&projectId=sample&token=MOCK_TOKEN_ID&
@@ -0,0 +1 @@
+[{"formerSymbols":[],"references":[],"modelId":15781,"synonyms":[],"description":"","type":"Protein","name":"s22","bounds":{"x":918.0,"y":427.0,"width":80.0,"height":40.0},"id":329162},{"formerSymbols":[],"references":[],"modelId":15781,"synonyms":[],"description":"","type":"Protein","name":"s22","bounds":{"x":712.0,"y":384.0,"width":80.0,"height":40.0},"id":329174},{"formerSymbols":[],"references":[],"modelId":15781,"synonyms":[],"description":"","type":"Simple molecule","name":"GDP","bounds":{"x":959.0,"y":271.0,"width":70.0,"height":25.0},"id":329156},{"formerSymbols":[],"references":[],"modelId":15781,"synonyms":[],"description":"","type":"Simple molecule","name":"GTP","bounds":{"x":849.0,"y":309.0,"width":70.0,"height":25.0},"id":329180}]
\ No newline at end of file
diff --git a/frontend-js/testFiles/apiCalls/project/getElements/columns=id,bounds,modelId&id=329173&projectId=sample&token=MOCK_TOKEN_ID& b/frontend-js/testFiles/apiCalls/project/getElements/columns=id,bounds,modelId&id=329173&projectId=sample&token=MOCK_TOKEN_ID&
new file mode 100644
index 0000000000..3cd9b0d710
--- /dev/null
+++ b/frontend-js/testFiles/apiCalls/project/getElements/columns=id,bounds,modelId&id=329173&projectId=sample&token=MOCK_TOKEN_ID&
@@ -0,0 +1 @@
+[{"modelId":15781,"bounds":{"x":12.0,"y":6.0,"width":80.0,"height":40.0},"id":329173}]
\ No newline at end of file
diff --git a/frontend-js/testFiles/apiCalls/project/getReactions/columns=&id=153515&projectId=sample&token=MOCK_TOKEN_ID& b/frontend-js/testFiles/apiCalls/project/getReactions/columns=&id=153515&projectId=sample&token=MOCK_TOKEN_ID&
new file mode 100644
index 0000000000..35ab17dc8b
--- /dev/null
+++ b/frontend-js/testFiles/apiCalls/project/getReactions/columns=&id=153515&projectId=sample&token=MOCK_TOKEN_ID&
@@ -0,0 +1 @@
+[{"modelId":15781,"reactants":"329174,329180","reactionId":"re26","id":153515,"type":"State transition","lines":[{"start":{"x":792.0,"y":404.0},"end":{"x":868.3333333333338,"y":377.9999999999997},"type":"START"},{"start":{"x":868.3333333333338,"y":377.9999999999997},"end":{"x":893.6666666666671,"y":378.00000000000017},"type":"MIDDLE"},{"start":{"x":884.0,"y":334.0},"end":{"x":893.6666666666671,"y":378.00000000000017},"type":"START"},{"start":{"x":931.666666666667,"y":378.0000000000009},"end":{"x":918.0,"y":447.0},"type":"END"},{"start":{"x":906.3333333333337,"y":378.00000000000045},"end":{"x":931.666666666667,"y":378.0000000000009},"type":"MIDDLE"},{"start":{"x":906.3333333333337,"y":378.00000000000045},"end":{"x":994.0,"y":296.0},"type":"END"},{"start":{"x":893.6666666666671,"y":378.00000000000017},"end":{"x":896.0000000000005,"y":378.0000000000003},"type":"MIDDLE"},{"start":{"x":906.3333333333337,"y":378.00000000000045},"end":{"x":904.0000000000005,"y":378.0000000000004},"type":"MIDDLE"}],"modifiers":"","centerPoint":{"x":900.0000000000005,"y":378.00000000000034},"products":"329162,329156"}]
\ No newline at end of file
-- 
GitLab