From b041deba05f4f41bec41e6274cd1680563d779b5 Mon Sep 17 00:00:00 2001
From: Piotr Gawron <piotr.gawron@uni.lu>
Date: Tue, 14 Nov 2017 11:10:58 +0100
Subject: [PATCH] refactor: operations on markers on map moved to separate
 class

---
 .../src/main/js/map/AbstractCustomMap.js      |  10 +-
 frontend-js/src/main/js/map/CustomMap.js      | 174 +---------
 .../js/map/marker/MarkerSurfaceCollection.js  | 302 ++++++++++++++++++
 .../main/js/map/surface/ReactionSurface.js    |   1 -
 frontend-js/src/test/js/helper.js             |   2 +-
 frontend-js/src/test/js/map/CustomMap-test.js | 215 +++++--------
 .../marker/MarkerSurfaceCollection-test.js    | 137 ++++++++
 .../test/js/plugin/MinervaPluginProxy-test.js |   1 +
 8 files changed, 551 insertions(+), 291 deletions(-)
 create mode 100644 frontend-js/src/main/js/map/marker/MarkerSurfaceCollection.js
 create mode 100644 frontend-js/src/test/js/map/marker/MarkerSurfaceCollection-test.js

diff --git a/frontend-js/src/main/js/map/AbstractCustomMap.js b/frontend-js/src/main/js/map/AbstractCustomMap.js
index c5c7167867..d67620f0bd 100644
--- a/frontend-js/src/main/js/map/AbstractCustomMap.js
+++ b/frontend-js/src/main/js/map/AbstractCustomMap.js
@@ -14,6 +14,8 @@ var PointInfoWindow = require('./window/PointInfoWindow');
 var ReactionInfoWindow = require('./window/ReactionInfoWindow');
 var ReactionSurface = require('./surface/ReactionSurface');
 
+var MarkerSurfaceCollection = require('./marker/MarkerSurfaceCollection');
+
 /**
  * Default constructor.
  */
@@ -53,6 +55,8 @@ function AbstractCustomMap(model, options) {
   // array with info windows for reactions
   this._reactionInfoWindow = [];
 
+  this._markerSurfaceCollection = new MarkerSurfaceCollection({map: this});
+
   // this is google.maps.drawing.DrawingManager that will allow user to draw
   // elements in the client
   this._drawingManager = null;
@@ -76,6 +80,10 @@ function AbstractCustomMap(model, options) {
 AbstractCustomMap.prototype = Object.create(ObjectWithListeners.prototype);
 AbstractCustomMap.prototype.constructor = AbstractCustomMap;
 
+
+AbstractCustomMap.prototype.getMarkerSurfaceCollection = function () {
+  return this._markerSurfaceCollection;
+};
 /**
  * Assigns layouts with images to the google map (which set of images should be
  * used by google maps api for which layout).
@@ -650,7 +658,7 @@ AbstractCustomMap.prototype._showSelectedLayout = function (layoutId, index, len
                 return self.getTopMap().callListeners("onBioEntityClick", element);
               }],
               customized: (length === 1)
-            }).then(function(result){
+            }).then(function (result) {
               surface = result;
               self.selectedLayoutOverlays[layoutId].push(surface);
               surface.show();
diff --git a/frontend-js/src/main/js/map/CustomMap.js b/frontend-js/src/main/js/map/CustomMap.js
index 61e027b521..218d45584c 100644
--- a/frontend-js/src/main/js/map/CustomMap.js
+++ b/frontend-js/src/main/js/map/CustomMap.js
@@ -6,10 +6,6 @@ var logger = require('../logger');
 var Functions = require('../Functions');
 
 var AbstractCustomMap = require('./AbstractCustomMap');
-var AbstractMarker = require('./marker/AbstractMarker');
-var AbstractSurfaceElement = require('./surface/AbstractSurfaceElement');
-var AliasMarker = require('./marker/AliasMarker');
-var AliasSurface = require('./surface/AliasSurface');
 var Alias = require('./data/Alias');
 var CommentDialog = require('../gui/CommentDialog');
 var ControlType = require('./ControlType');
@@ -18,10 +14,7 @@ var GuiConnector = require('../GuiConnector');
 var IdentifiedElement = require('./data/IdentifiedElement');
 var LayoutData = require('./data/LayoutData');
 var PointData = require('./data/PointData');
-var PointMarker = require('./marker/PointMarker');
 var Reaction = require('./data/Reaction');
-var ReactionMarker = require('./marker/ReactionMarker');
-var ReactionSurface = require('./surface/ReactionSurface');
 var ReferenceGenome = require('./data/ReferenceGenome');
 var SecurityError = require('../SecurityError');
 var Submap = require('./Submap');
@@ -243,11 +236,6 @@ CustomMap.prototype.registerDbOverlay = function (dbOverlay) {
 
   this.overlayCollections[dbOverlay.getName()] = dbOverlay;
 
-  dbOverlay.markers = {
-    ALIAS: [],
-    REACTION: [],
-    POINT: []
-  };
   dbOverlay.mapOverlays = {
     ALIAS: [],
     REACTION: [],
@@ -334,20 +322,14 @@ CustomMap.prototype.refreshMarkers = function (force) {
 };
 
 CustomMap.prototype.refreshOverlayMarkers = function (overlay, force) {
+  var self = this;
   logger.debug("Refresh overlay: " + overlay.name);
 
   if (!this.isMarkerOptimization() || force) {
-    for (var markerType in overlay.markers) {
-      if (overlay.markers.hasOwnProperty(markerType)) {
-        var markers = overlay.markers[markerType];
-        for (var key in markers) {
-          if (markers.hasOwnProperty(key)) {
-            var marker = markers[key];
-            marker.hide();
-            marker.show();
-          }
-        }
-      }
+    self.getMarkerSurfaceCollection().refreshOverlayMarkers(overlay);
+    var submaps = self.getSubmaps();
+    for (var i = 0; i < submaps.length; i++) {
+      submaps[i].getMarkerSurfaceCollection().refreshOverlayMarkers(overlay);
     }
   }
 };
@@ -702,18 +684,22 @@ CustomMap.prototype.renderOverlayCollection = function (params) {
     return Promise.each(elements, function (element) {
       var icon = element.getIcon();
       if (icon !== null && icon !== undefined) {
-        return self.createMarkerForDbOverlay(element, overlayCollection).then(function (marker) {
-
+        return self.getSubmapById(element.getModelId()).getMarkerSurfaceCollection().createMarkerForDbOverlay(element, overlayCollection).then(function (marker) {
           markers.push(marker);
         });
       } else {
-        return self.createSurfaceForDbOverlay(element, overlayCollection).then(function (mapOverlay) {
+        return self.getSubmapById(element.getModelId()).getMarkerSurfaceCollection().createSurfaceForDbOverlay(element, overlayCollection).then(function (mapOverlay) {
           markers.push(mapOverlay);
         });
       }
     });
   }).then(function () {
-    self.removeUnmodifiedMarkersAndSurfaces(markers, overlayCollection);
+
+    self.getMarkerSurfaceCollection().removeUnmodifiedMarkersAndSurfaces(markers, overlayCollection);
+    var submaps = self.getSubmaps();
+    for (var i = 0; i < submaps.length; i++) {
+      submaps[i].getMarkerSurfaceCollection().removeUnmodifiedMarkersAndSurfaces(markers, overlayCollection);
+    }
 
     return Promise.each(elements, function (element) {
       var infoWindow = self.getInfoWindowForIdentifiedElement(element);
@@ -733,56 +719,6 @@ CustomMap.prototype.renderOverlayCollection = function (params) {
   });
 };
 
-CustomMap.prototype.removeUnmodifiedMarkersAndSurfaces = function (modifiedMarkersAndSurfaces, dbOverlay) {
-  var modifiedMarkers = {
-    "ALIAS": [],
-    "REACTION": [],
-    "POINT": [],
-  };
-  var modifiedSurfaces = {
-    "ALIAS": [],
-    "REACTION": [],
-    "POINT": [],
-  };
-
-  for (var i = 0; i < modifiedMarkersAndSurfaces.length; i++) {
-    var object = modifiedMarkersAndSurfaces[i];
-    var identifiedElement = object.getIdentifiedElement();
-    if (object instanceof AbstractSurfaceElement) {
-      modifiedSurfaces[identifiedElement.getType()][identifiedElement.getId()] = true;
-    } else if (object instanceof AbstractMarker) {
-      modifiedMarkers[identifiedElement.getType()][identifiedElement.getId()] = true;
-    } else {
-      throw new Error("Unknown class type: " + object.prototype.name);
-    }
-  }
-
-  var markerType, key;
-  for (markerType in dbOverlay.markers) {
-    if (dbOverlay.markers.hasOwnProperty(markerType)) {
-      var markers = dbOverlay.markers[markerType];
-      for (key in markers) {
-        if (markers.hasOwnProperty(key) && !modifiedMarkers[markerType][key]) {
-          markers[key].setMap(null);
-          delete markers[key];
-        }
-      }
-    }
-  }
-
-  for (markerType in dbOverlay.mapOverlays) {
-    if (dbOverlay.mapOverlays.hasOwnProperty(markerType)) {
-      var mapOverlays = dbOverlay.mapOverlays[markerType];
-      for (key in mapOverlays) {
-        if (mapOverlays.hasOwnProperty(key) && !modifiedSurfaces[markerType][key]) {
-          mapOverlays[key].setMap(null);
-          delete mapOverlays[key];
-        }
-      }
-    }
-  }
-};
-
 /**
  * Opens {@link AbstractInfoWindow} for a marker.
  *
@@ -1206,90 +1142,6 @@ CustomMap.prototype.getVisibleDataOverlays = function () {
   return Promise.all(dataOverlayPromises);
 };
 
-CustomMap.prototype.createMarkerForDbOverlay = function (element, dbOverlay) {
-  var self = this;
-
-  var result = dbOverlay.markers[element.getType()][element.getId()];
-  if (result !== undefined) {
-    result.updateIdentifiedElement(element);
-    return Promise.resolve(result);
-  }
-
-  var submap = self.getSubmapById(element.getModelId());
-  var MarkerConstructor = null;
-  if (element.getType() === "ALIAS") {
-    MarkerConstructor = AliasMarker;
-  } else if (element.getType() === "REACTION") {
-    MarkerConstructor = ReactionMarker;
-  } else if (element.getType() === "POINT") {
-    MarkerConstructor = PointMarker;
-  } else {
-    throw new Error("Unknown type of the element in overlay: " + element.type);
-  }
-  result = new MarkerConstructor({
-    element: element,
-    map: submap,
-    onClick: [function () {
-      return self._openInfoWindowForIdentifiedElement(element, result.getGoogleMarker());
-    }, function () {
-      return self.callListeners("onBioEntityClick", element);
-    }]
-  });
-  return result.init().then(function () {
-    dbOverlay.markers[element.getType()][element.getId()] = result;
-    return result;
-  });
-};
-
-CustomMap.prototype.createSurfaceForDbOverlay = function (element, dbOverlay) {
-  var self = this;
-
-  var result = dbOverlay.mapOverlays[element.getType()][element.getId()];
-  if (result !== undefined) {
-    result.updateIdentifiedElement(element);
-    return Promise.resolve(result);
-  }
-
-  var map = self.getSubmapById(element.getModelId());
-  if (element.getType() === "ALIAS") {
-    return AliasSurface.createFromIdentifiedElement({
-      element: element,
-      map: self,
-      onClick: [function () {
-        return self.openInfoWindowForIdentifiedElement(element, result.getGoogleMarker());
-      }, function () {
-        return self.callListeners("onBioEntityClick", element);
-      }]
-    }).then(function (surface) {
-      result = surface;
-      dbOverlay.mapOverlays[element.getType()][element.getId()] = result;
-      return result;
-    });
-  } else if (element.getType() === "REACTION") {
-    return map.getModel().getReactionById(element.getId()).then(function (reactionData) {
-      return ReactionSurface.create({
-        reaction: reactionData,
-        map: map,
-        customized: true,
-        color: element.getColor(),
-        onClick: [function () {
-          return self.openInfoWindowForIdentifiedElement(element, result.getGoogleMarker());
-        }, function () {
-          return self.callListeners("onBioEntityClick", element);
-        }]
-      }).then(function (surface) {
-        result = surface;
-        result.show();
-        dbOverlay.mapOverlays[element.getType()][element.getId()] = result;
-        return result;
-      });
-    });
-  } else if (element.getType() === "POINT") {
-    throw new Error("Not implemented");
-  } else {
-    throw new Error("Unknown type of the element in overlay: " + element.type);
-  }
-};
 
 /**
  * Opens {@link AbstractInfoWindow} for a marker.
diff --git a/frontend-js/src/main/js/map/marker/MarkerSurfaceCollection.js b/frontend-js/src/main/js/map/marker/MarkerSurfaceCollection.js
new file mode 100644
index 0000000000..45c6a00b41
--- /dev/null
+++ b/frontend-js/src/main/js/map/marker/MarkerSurfaceCollection.js
@@ -0,0 +1,302 @@
+"use strict";
+
+var AbstractMarker = require('./AbstractMarker');
+var AliasMarker = require('./AliasMarker');
+var IdentifiedElement = require('../data/IdentifiedElement');
+var PointMarker = require('./PointMarker');
+var ReactionMarker = require('./ReactionMarker');
+
+var AbstractSurfaceElement = require('../surface/AbstractSurfaceElement');
+var AliasSurface = require('../surface/AliasSurface');
+var ReactionSurface = require('../surface/ReactionSurface');
+
+// noinspection JSUnusedLocalSymbols
+var logger = require('../../logger');
+var Promise = require("bluebird");
+
+var ObjectWithListeners = require('../../ObjectWithListeners');
+
+function MarkerSurfaceCollection(params) {
+  var self = this;
+  ObjectWithListeners.call(this);
+
+  self._markerOverlaysPerType = {
+    ALIAS: [],
+    REACTION: [],
+    POINT: []
+  };
+  self._surfaceOverlaysPerType = {
+    ALIAS: [],
+    REACTION: [],
+    POINT: []
+  };
+
+  self.setMap(params.map);
+}
+
+MarkerSurfaceCollection.prototype = Object.create(ObjectWithListeners.prototype);
+MarkerSurfaceCollection.prototype.constructor = MarkerSurfaceCollection;
+
+MarkerSurfaceCollection.prototype.setMap = function (map) {
+  if (map === undefined) {
+    throw new Error("Map must be defined");
+  }
+  this._map = map;
+};
+
+MarkerSurfaceCollection.prototype.getMap = function () {
+  return this._map;
+};
+
+MarkerSurfaceCollection.prototype.refreshOverlayMarkers = function (overlay) {
+  var self = this;
+  var markerOverlaysPerType = self._markerOverlaysPerType;
+  for (var markerType in markerOverlaysPerType) {
+    if (markerOverlaysPerType.hasOwnProperty(markerType)) {
+      var markers = markerOverlaysPerType[markerType][overlay.getName()];
+      if (markers !== undefined) {
+        for (var key in markers) {
+          if (markers.hasOwnProperty(key)) {
+            var marker = markers[key];
+            marker.hide();
+            marker.show();
+          }
+        }
+      }
+    }
+  }
+};
+
+MarkerSurfaceCollection.prototype.createMarkerForDbOverlay = function (element, dbOverlay) {
+  var self = this;
+
+  var result = self.getMarker({element: element, overlay: dbOverlay});
+  if (result !== undefined) {
+    result.updateIdentifiedElement(element);
+    return Promise.resolve(result);
+  }
+
+  var MarkerConstructor = null;
+  if (element.getType() === "ALIAS") {
+    MarkerConstructor = AliasMarker;
+  } else if (element.getType() === "REACTION") {
+    MarkerConstructor = ReactionMarker;
+  } else if (element.getType() === "POINT") {
+    MarkerConstructor = PointMarker;
+  } else {
+    throw new Error("Unknown type of the element in overlay: " + element.type);
+  }
+  result = new MarkerConstructor({
+    element: element,
+    map: self.getMap(),
+    onClick: [function () {
+      return self.getMap()._openInfoWindowForIdentifiedElement(element, result.getGoogleMarker());
+    }, function () {
+      return self.getMap().callListeners("onBioEntityClick", element);
+    }]
+  });
+  return result.init().then(function () {
+    self.putMarker({element: element, overlay: dbOverlay}, result);
+    return result;
+  });
+};
+
+MarkerSurfaceCollection.prototype.createSurfaceForDbOverlay = function (element, dbOverlay) {
+  var self = this;
+
+  var result = self.getSurface({element: element, overlay: dbOverlay});
+  if (result !== undefined) {
+    result.updateIdentifiedElement(element);
+    return Promise.resolve(result);
+  }
+
+  var map = self.getMap();
+  var onclickFunctions = [function () {
+    return self.getMap().openInfoWindowForIdentifiedElement(element, result.getGoogleMarker());
+  }, function () {
+    return self.getMap().callListeners("onBioEntityClick", element);
+  }];
+  if (element.getType() === "ALIAS") {
+    return AliasSurface.createFromIdentifiedElement({
+      element: element,
+      map: map,
+      onClick: onclickFunctions
+    }).then(function (surface) {
+      result = surface;
+      self.putSurface({element: element, overlay: dbOverlay}, result);
+      return result;
+    });
+  } else if (element.getType() === "REACTION") {
+    return map.getModel().getReactionById(element.getId()).then(function (reactionData) {
+      return ReactionSurface.create({
+        reaction: reactionData,
+        map: map,
+        customized: true,
+        color: element.getColor(),
+        onClick: onclickFunctions
+      }).then(function (surface) {
+        result = surface;
+        result.show();
+        self.putSurface({element: element, overlay: dbOverlay}, result);
+        return result;
+      });
+    });
+  } else if (element.getType() === "POINT") {
+    throw new Error("Not implemented");
+  } else {
+    throw new Error("Unknown type of the element in overlay: " + element.type);
+  }
+};
+
+
+MarkerSurfaceCollection.prototype.getMarker = function (params) {
+  var element = new IdentifiedElement(params.element);
+  var overlay = params.overlay;
+
+  var markerOverlaysPerType = this._markerOverlaysPerType;
+  var overlayMarkers = markerOverlaysPerType[element.getType()][overlay.getName()];
+  if (overlayMarkers === undefined) {
+    markerOverlaysPerType[element.getType()][overlay.getName()] = [];
+    overlayMarkers = markerOverlaysPerType[element.getType()][overlay.getName()];
+  }
+  return overlayMarkers[element.getId()];
+};
+
+MarkerSurfaceCollection.prototype.getMarkers = function () {
+  var result = [];
+
+  var markerOverlaysPerType = this._markerOverlaysPerType;
+  for (var markerType in markerOverlaysPerType) {
+    if (markerOverlaysPerType.hasOwnProperty(markerType)) {
+      var markersByType = markerOverlaysPerType[markerType];
+      for (var overlayName in markersByType) {
+        if (markersByType.hasOwnProperty(overlayName)) {
+          var markers = markersByType[overlayName];
+          for (var key in markers) {
+            if (markers.hasOwnProperty(key)) {
+              result.push(markers[key]);
+            }
+          }
+        }
+      }
+    }
+  }
+
+  return result;
+};
+MarkerSurfaceCollection.prototype.getSurfaces = function () {
+  var result = [];
+
+  var surfaceOverlaysPerType = this._surfaceOverlaysPerType;
+  for (var markerType in surfaceOverlaysPerType) {
+    if (surfaceOverlaysPerType.hasOwnProperty(markerType)) {
+      var markersByType = surfaceOverlaysPerType[markerType];
+      for (var overlayName in markersByType) {
+        if (markersByType.hasOwnProperty(overlayName)) {
+          var markers = markersByType[overlayName];
+          for (var key in markers) {
+            if (markers.hasOwnProperty(key)) {
+              result.push(markers[key]);
+            }
+          }
+        }
+      }
+    }
+  }
+
+  return result;
+};
+
+MarkerSurfaceCollection.prototype.putMarker = function (params, marker) {
+  var element = new IdentifiedElement(params.element);
+  var overlay = params.overlay;
+
+  var markerOverlaysPerType = this._markerOverlaysPerType;
+  var overlayMarkers = markerOverlaysPerType[element.getType()][overlay.getName()];
+  if (overlayMarkers === undefined) {
+    markerOverlaysPerType[element.getType()][overlay.getName()] = [];
+    overlayMarkers = markerOverlaysPerType[element.getType()][overlay.getName()];
+  }
+  overlayMarkers[element.getId()] = marker;
+};
+
+MarkerSurfaceCollection.prototype.getSurface = function (params) {
+  var element = new IdentifiedElement(params.element);
+  var overlay = params.overlay;
+
+  var surfaceOverlaysPerType = this._surfaceOverlaysPerType;
+  var overlaySurfaces = surfaceOverlaysPerType[element.getType()][overlay.getName()];
+  if (overlaySurfaces === undefined) {
+    surfaceOverlaysPerType[element.getType()][overlay.getName()] = [];
+    overlaySurfaces = surfaceOverlaysPerType[element.getType()][overlay.getName()];
+  }
+  return overlaySurfaces[element.getId()];
+};
+
+MarkerSurfaceCollection.prototype.putSurface = function (params, surface) {
+  var element = new IdentifiedElement(params.element);
+  var overlay = params.overlay;
+
+  var surfaceOverlaysPerType = this._surfaceOverlaysPerType;
+  var overlaySurfaces = surfaceOverlaysPerType[element.getType()][overlay.getName()];
+  if (overlaySurfaces === undefined) {
+    surfaceOverlaysPerType[element.getType()][overlay.getName()] = [];
+    overlaySurfaces = surfaceOverlaysPerType[element.getType()][overlay.getName()];
+  }
+  overlaySurfaces[element.getId()] = surface;
+};
+
+MarkerSurfaceCollection.prototype.removeUnmodifiedMarkersAndSurfaces = function (modifiedMarkersAndSurfaces, dbOverlay) {
+  var modifiedMarkers = {
+    "ALIAS": [],
+    "REACTION": [],
+    "POINT": []
+  };
+  var modifiedSurfaces = {
+    "ALIAS": [],
+    "REACTION": [],
+    "POINT": []
+  };
+
+  for (var i = 0; i < modifiedMarkersAndSurfaces.length; i++) {
+    var object = modifiedMarkersAndSurfaces[i];
+    var identifiedElement = object.getIdentifiedElement();
+    if (object instanceof AbstractSurfaceElement) {
+      modifiedSurfaces[identifiedElement.getType()][identifiedElement.getId()] = true;
+    } else if (object instanceof AbstractMarker) {
+      modifiedMarkers[identifiedElement.getType()][identifiedElement.getId()] = true;
+    } else {
+      throw new Error("Unknown class type: " + object.prototype.name);
+    }
+  }
+
+  var markerType, key;
+  var markerOverlaysPerType = this._markerOverlaysPerType;
+  for (markerType in markerOverlaysPerType) {
+    if (markerOverlaysPerType.hasOwnProperty(markerType)) {
+      var markers = markerOverlaysPerType[markerType][dbOverlay.getName()];
+      for (key in markers) {
+        if (markers.hasOwnProperty(key) && !modifiedMarkers[markerType][key]) {
+          markers[key].setMap(null);
+          delete markers[key];
+        }
+      }
+    }
+  }
+
+  var surfaceOverlaysPerType = this._surfaceOverlaysPerType;
+  for (markerType in surfaceOverlaysPerType) {
+    if (surfaceOverlaysPerType.hasOwnProperty(markerType)) {
+      var mapOverlays = surfaceOverlaysPerType[markerType][dbOverlay.getName()];
+      for (key in mapOverlays) {
+        if (mapOverlays.hasOwnProperty(key) && !modifiedSurfaces[markerType][key]) {
+          mapOverlays[key].setMap(null);
+          delete mapOverlays[key];
+        }
+      }
+    }
+  }
+};
+
+
+module.exports = MarkerSurfaceCollection;
diff --git a/frontend-js/src/main/js/map/surface/ReactionSurface.js b/frontend-js/src/main/js/map/surface/ReactionSurface.js
index c858a0a739..e57d7d8086 100644
--- a/frontend-js/src/main/js/map/surface/ReactionSurface.js
+++ b/frontend-js/src/main/js/map/surface/ReactionSurface.js
@@ -279,7 +279,6 @@ ReactionSurface.create = function (params) {
     promise = functions.overlayToColor(overlayData);
   }
   return promise.then(function (color) {
-    logger.debug(color);
     if (color === undefined) {
       color = "#0000FF";
     }
diff --git a/frontend-js/src/test/js/helper.js b/frontend-js/src/test/js/helper.js
index 4fa3e08f1e..6a4a9ecdb1 100644
--- a/frontend-js/src/test/js/helper.js
+++ b/frontend-js/src/test/js/helper.js
@@ -68,7 +68,7 @@ Helper.prototype.createSearchDbOverlay = function (map) {
 Helper.prototype.createDbOverlay = function (map) {
   var result = new AbstractDbOverlay({
     map: map,
-    name: "search",
+    name: "search"
   });
   result.clear = function () {
     logger.debug("Clear mock");
diff --git a/frontend-js/src/test/js/map/CustomMap-test.js b/frontend-js/src/test/js/map/CustomMap-test.js
index ff8df7c7cb..ce9d5f9ec9 100644
--- a/frontend-js/src/test/js/map/CustomMap-test.js
+++ b/frontend-js/src/test/js/map/CustomMap-test.js
@@ -16,6 +16,8 @@ var ReactionMarker = require('../../../main/js/map/marker/ReactionMarker');
 var ReactionSurface = require('../../../main/js/map/surface/ReactionSurface');
 var SelectionContextMenu = require('../../../main/js/gui/SelectionContextMenu');
 
+var ServerConnector = require('./../ServerConnector-mock');
+
 var logger = require('./../logger');
 
 var chai = require('chai');
@@ -250,10 +252,7 @@ describe('CustomMap', function () {
   describe("renderOverlayCollection", function () {
     it("for alias", function () {
       var map = helper.createCustomMap();
-      var reaction = helper.createReaction();
-      var alias = helper.createAlias();
-      map.getModel().addAlias(alias);
-      map.getModel().addReaction(reaction);
+      var alias = helper.createAlias(map);
 
       var oc = helper.createDbOverlay(map);
 
@@ -268,14 +267,8 @@ describe('CustomMap', function () {
       return map.renderOverlayCollection({
         overlayCollection: oc
       }).then(function () {
-        var aliasMarkerCount = 0;
-        var markers = oc.markers["ALIAS"];
-        for (var id in markers) {
-          if (markers.hasOwnProperty(id)) {
-            aliasMarkerCount++;
-          }
-        }
-        assert.equal(1, aliasMarkerCount);
+        var markers = map.getMarkerSurfaceCollection().getMarkers();
+        assert.equal(1, markers.length);
       });
     });
 
@@ -300,7 +293,7 @@ describe('CustomMap', function () {
       return map.renderOverlayCollection({
         overlayCollection: oc
       }).then(function () {
-        marker = oc.markers["ALIAS"][alias.getId()];
+        marker = map.getMarkerSurfaceCollection().getMarker({element: alias, overlay: oc});
         oc.getIdentifiedElements = function () {
           return Promise.resolve([new IdentifiedElement({
             objectId: alias.getId(),
@@ -309,7 +302,7 @@ describe('CustomMap', function () {
             type: "Alias"
           })]);
         };
-        assert.ok(oc.mapOverlays["REACTION"][reaction.getId()]);
+        assert.ok(map.getMarkerSurfaceCollection().getSurface({element: reaction, overlay: oc}));
 
         return map.renderOverlayCollection({
           overlayCollection: oc
@@ -342,15 +335,8 @@ describe('CustomMap', function () {
       return map.renderOverlayCollection({
         overlayCollection: oc
       }).then(function () {
-        var aliasMarkerCount = 0;
-        var markers = oc.markers["ALIAS"];
-        for (var id in markers) {
-          if (markers.hasOwnProperty(id)) {
-            aliasMarkerCount++;
-          }
-        }
-
-        assert.equal(aliasMarkerCount, 3);
+        var markers = map.getMarkerSurfaceCollection().getMarkers();
+        assert.equal(3, markers.length);
       });
     });
 
@@ -373,49 +359,54 @@ describe('CustomMap', function () {
       return map.renderOverlayCollection({
         overlayCollection: oc
       }).then(function () {
-        var markerCount = 0;
-        var markers = oc.markers["POINT"];
-        for (var id in markers) {
-          if (markers.hasOwnProperty(id)) {
-            markerCount++;
-          }
-        }
-        assert.equal(1, markerCount);
+        var markers = map.getMarkerSurfaceCollection().getMarkers();
+        assert.equal(1, markers.length);
       });
     });
 
     it("for reaction", function () {
       var map = helper.createCustomMap();
-      var reaction = helper.createReaction();
+      var reaction = helper.createReaction(map);
       map.getModel().addReaction(reaction);
 
       var oc = helper.createDbOverlay(map);
 
-      var javaObj = {
-        objectId: reaction.getId(),
-        modelId: map.getId(),
-        type: "Reaction",
-      };
-
       oc.getIdentifiedElements = function () {
-        return Promise.resolve([new IdentifiedElement(javaObj)]);
+        return Promise.resolve([new IdentifiedElement(reaction)]);
       };
 
       return map.renderOverlayCollection({
         overlayCollection: oc
       }).then(function () {
-        var markerCount = 0;
-        var markers = oc.mapOverlays["REACTION"];
-        for (var id in markers) {
-          if (markers.hasOwnProperty(id)) {
-            markerCount++;
-          }
-        }
-        assert.equal(1, markerCount);
+        var surfaces = map.getMarkerSurfaceCollection().getSurfaces();
+        assert.equal(1, surfaces.length);
       });
     });
   });
 
+  it("refreshMarkers", function () {
+    var map = helper.createCustomMap();
+    var alias = helper.createAlias(map);
+    map.getModel().addAlias(alias);
+
+    var oc = helper.createDbOverlay(map);
+
+    oc.getIdentifiedElements = function () {
+      var element = new IdentifiedElement(alias);
+      element.setIcon("icon");
+      return Promise.resolve([element]);
+    };
+
+    return map.renderOverlayCollection({
+      overlayCollection: oc
+    }).then(function () {
+      return map.refreshMarkers(true);
+    }).then(function () {
+      var markers = map.getMarkerSurfaceCollection().getMarkers();
+      assert.equal(1, markers.length);
+    });
+  });
+
   it("clearDbOverlays", function () {
     var map = helper.createCustomMap();
 
@@ -500,7 +491,7 @@ describe('CustomMap', function () {
 
       var mev = {
         stop: null,
-        latLng: new google.maps.LatLng(82.32061703407554, -167.25586206896548),
+        latLng: new google.maps.LatLng(82.32061703407554, -167.25586206896548)
       };
       map.getGoogleMap().setZoom(4);
       // latLng : new google.maps.LatLng(82.40238643645326, -148.44758620689652)
@@ -510,7 +501,8 @@ describe('CustomMap', function () {
 
     }).then(function () {
       assert.equal(map.getId(), map.getActiveSubmapId());
-      assert.ok(searchOverlay.markers["ALIAS"][329171]);
+      var element = new IdentifiedElement({id: 329171, type: "ALIAS", modelId: map.getId()});
+      assert.ok(map.getMarkerSurfaceCollection().getMarker({element: element, overlay: searchOverlay}));
     });
 
   });
@@ -532,9 +524,16 @@ describe('CustomMap', function () {
       return google.maps.event.trigger(map.getGoogleMap(), 'click', mev);
     }).then(function () {
       assert.equal(map.getId(), map.getActiveSubmapId());
-      assert.ok(searchOverlay.mapOverlays["REACTION"][153521]);
-      assert.ok(searchOverlay.mapOverlays["REACTION"][153521].isShown());
-      assert.ok(searchOverlay.markers["ALIAS"][329165]);
+
+      var reaction = new IdentifiedElement({id: 153521, type: "REACTION", modelId: map.getId()});
+      var surface = map.getMarkerSurfaceCollection().getSurface({element: reaction, overlay: searchOverlay});
+
+      assert.ok(surface);
+      assert.ok(surface.isShown());
+
+      var element = new IdentifiedElement({id: 329165, type: "ALIAS", modelId: map.getId()});
+      var marker = map.getMarkerSurfaceCollection().getMarker({element: element, overlay: searchOverlay});
+      assert.ok(marker);
     });
   });
 
@@ -549,7 +548,7 @@ describe('CustomMap', function () {
     map.turnOnDrawing();
 
     var triangleCoordinates = [new google.maps.LatLng(25.774, -80.190), new google.maps.LatLng(18.466, -66.118),
-      new google.maps.LatLng(32.321, -64.757), new google.maps.LatLng(25.774, -80.190),];
+      new google.maps.LatLng(32.321, -64.757), new google.maps.LatLng(25.774, -80.190)];
 
     // Construct the polygon.
     var bermudaTriangle = new google.maps.Polygon({
@@ -564,7 +563,7 @@ describe('CustomMap', function () {
 
     var eventParam = {
       type: google.maps.drawing.OverlayType.POLYGON,
-      overlay: bermudaTriangle,
+      overlay: bermudaTriangle
     };
 
     // check creation complete behaviour
@@ -629,10 +628,34 @@ describe('CustomMap', function () {
 
     ServerConnector.getSessionData().setShowComments(true);
     return map.refreshComments().then(function () {
-      assert.notOk(commentsOverlay.markers["POINT"]['(241.01,372.35)']);
-      assert.ok(commentsOverlay.markers["POINT"]['(643.96,144.09)']);
-      assert.notOk(commentsOverlay.markers["POINT"]['(216.65,370.00)']);
-      assert.notOk(commentsOverlay.markers["POINT"]['unkId']);
+      assert.notOk(map.getMarkerSurfaceCollection().getMarker({
+        element: new IdentifiedElement({
+          id: '(241.01,372.35)',
+          modelId: map.getId(),
+          type: "POINT"
+        }), overlay: commentsOverlay
+      }));
+      assert.ok(map.getMarkerSurfaceCollection().getMarker({
+        element: new IdentifiedElement({
+          id: '(643.96,144.09)',
+          modelId: map.getId(),
+          type: "POINT"
+        }), overlay: commentsOverlay
+      }));
+      assert.notOk(map.getMarkerSurfaceCollection().getMarker({
+        element: new IdentifiedElement({
+          id: '(216.65,370.00)',
+          modelId: map.getId(),
+          type: "POINT"
+        }), overlay: commentsOverlay
+      }));
+      assert.notOk(map.getMarkerSurfaceCollection().getMarker({
+        element: new IdentifiedElement({
+          id: 'unkId',
+          modelId: map.getId(),
+          type: "POINT"
+        }), overlay: commentsOverlay
+      }));
     });
   });
 
@@ -646,7 +669,13 @@ describe('CustomMap', function () {
       ServerConnector.getSessionData().setShowComments(false);
       return map.refreshComments();
     }).then(function () {
-      assert.notOk(commentsOverlay.markers["POINT"]['(241.01, 372.35)']);
+      assert.notOk(map.getMarkerSurfaceCollection().getMarker({
+        element: new IdentifiedElement({
+          id: '(241.01, 372.35)',
+          modelId: map.getId(),
+          type: "POINT"
+        }), overlay: commentsOverlay
+      }));
     });
   });
 
@@ -818,74 +847,6 @@ describe('CustomMap', function () {
     assert.equal(map.getSelectedArea(), null);
   });
 
-  describe("createMarkerForDbOverlay", function () {
-    it("update marker", function () {
-      var map;
-      var marker1, marker2;
-      var element = new IdentifiedElement({
-        id: 329159,
-        modelId: 15781,
-        type: "ALIAS"
-      });
-      var overlay;
-      return ServerConnector.getProject().then(function (project) {
-        var options = helper.createCustomMapOptions(project);
-        map = new CustomMap(options);
-        overlay = helper.createDbOverlay(map);
-        return map.createMarkerForDbOverlay(element, overlay);
-      }).then(function (result) {
-        marker1 = result;
-        element.setIcon("another.png");
-        return map.createMarkerForDbOverlay(element, overlay);
-      }).then(function (result) {
-        marker2 = result;
-        assert.equal(marker1, marker2);
-      });
-    });
-
-    it("update surface", function () {
-      var map;
-      var marker1, marker2;
-      var element = new IdentifiedElement({
-        id: 329159,
-        modelId: 15781,
-        type: "ALIAS"
-      });
-      var overlay;
-      return ServerConnector.getProject().then(function (project) {
-        var options = helper.createCustomMapOptions(project);
-        map = new CustomMap(options);
-        overlay = helper.createDbOverlay(map);
-        return map.createSurfaceForDbOverlay(element, overlay);
-      }).then(function (result) {
-        marker1 = result;
-        element.setColor("another.png");
-        return map.createSurfaceForDbOverlay(element, overlay);
-      }).then(function (result) {
-        marker2 = result;
-        assert.equal(marker1, marker2);
-      });
-    });
-
-    it("reaction marker", function () {
-      var map;
-      var element = new IdentifiedElement({
-        id: 153508,
-        modelId: 15781,
-        type: "REACTION"
-      });
-      var overlay;
-      return ServerConnector.getProject().then(function (project) {
-        var options = helper.createCustomMapOptions(project);
-        map = new CustomMap(options);
-        overlay = helper.createDbOverlay(map);
-        return map.createMarkerForDbOverlay(element, overlay);
-      }).then(function (result) {
-        assert.ok(result.getReactionData());
-      });
-    });
-
-  });
 
   describe("_openInfoWindowForIdentifiedElement", function () {
     it("for AliasMarker", function () {
diff --git a/frontend-js/src/test/js/map/marker/MarkerSurfaceCollection-test.js b/frontend-js/src/test/js/map/marker/MarkerSurfaceCollection-test.js
new file mode 100644
index 0000000000..8f6cc614d0
--- /dev/null
+++ b/frontend-js/src/test/js/map/marker/MarkerSurfaceCollection-test.js
@@ -0,0 +1,137 @@
+"use strict";
+
+require("../../mocha-config.js");
+
+var ServerConnector = require('../../ServerConnector-mock');
+
+/* exported logger */
+
+// noinspection JSUnusedLocalSymbols
+var logger = require('../../logger');
+
+var MarkerSurfaceCollection = require('../../../../main/js/map/marker/MarkerSurfaceCollection');
+var IdentifiedElement = require('../../../../main/js/map/data/IdentifiedElement');
+var assert = require('assert');
+
+describe('MarkerSurfaceCollection', function () {
+
+  describe("createMarkerForDbOverlay", function () {
+    it("update marker", function () {
+      var map, marker1, marker2, overlay, collection;
+      var element = new IdentifiedElement({
+        id: 329159,
+        modelId: 15781,
+        type: "ALIAS"
+      });
+      return ServerConnector.getProject().then(function (project) {
+        map = helper.createCustomMap(project);
+        overlay = helper.createDbOverlay(map);
+        collection = map.getMarkerSurfaceCollection();
+        return collection.createMarkerForDbOverlay(element, overlay);
+      }).then(function (result) {
+        marker1 = result;
+        element.setIcon("another.png");
+        return collection.createMarkerForDbOverlay(element, overlay);
+      }).then(function (result) {
+        marker2 = result;
+        assert.equal(marker1, marker2);
+      });
+    });
+
+    it("update surface", function () {
+      var map, collection, marker1, marker2, overlay;
+      var element = new IdentifiedElement({
+        id: 329159,
+        modelId: 15781,
+        type: "ALIAS"
+      });
+      return ServerConnector.getProject().then(function (project) {
+        map = helper.createCustomMap(project);
+        overlay = helper.createDbOverlay(map);
+        collection = map.getMarkerSurfaceCollection();
+        return collection.createSurfaceForDbOverlay(element, overlay);
+      }).then(function (result) {
+        marker1 = result;
+        element.setColor("another.png");
+        return collection.createSurfaceForDbOverlay(element, overlay);
+      }).then(function (result) {
+        marker2 = result;
+        assert.equal(marker1, marker2);
+      });
+    });
+
+    it("reaction marker", function () {
+      var map, overlay, collection;
+      var element = new IdentifiedElement({
+        id: 153508,
+        modelId: 15781,
+        type: "REACTION"
+      });
+      return ServerConnector.getProject().then(function (project) {
+        map = helper.createCustomMap(project);
+        overlay = helper.createDbOverlay(map);
+        collection = map.getMarkerSurfaceCollection();
+        return collection.createMarkerForDbOverlay(element, overlay);
+      }).then(function (result) {
+        assert.ok(result.getReactionData());
+      });
+    });
+
+    it("click on marker", function () {
+      var map, marker, overlay, collection;
+      var element = new IdentifiedElement({
+        id: 329159,
+        modelId: 15781,
+        type: "ALIAS"
+      });
+      return ServerConnector.getProject().then(function (project) {
+        map = helper.createCustomMap(project);
+        overlay = helper.createDbOverlay(map);
+        collection = map.getMarkerSurfaceCollection();
+        return collection.createMarkerForDbOverlay(element, overlay);
+      }).then(function (result) {
+        marker = result;
+        return marker.onClickHandler();
+      });
+    });
+
+  });
+
+  describe("createSurfaceForDbOverlay", function () {
+    it("click on element surface", function () {
+      var map, collection, marker, overlay;
+      var element = new IdentifiedElement({
+        id: 329159,
+        modelId: 15781,
+        type: "ALIAS"
+      });
+      return ServerConnector.getProject().then(function (project) {
+        map = helper.createCustomMap(project);
+        overlay = helper.createSearchDbOverlay(map);
+        collection = map.getMarkerSurfaceCollection();
+        return collection.createSurfaceForDbOverlay(element, overlay);
+      }).then(function (surface) {
+        return surface.onClickHandler();
+      });
+    });
+
+    it("click on reaction surface", function () {
+      var map, collection, marker, overlay;
+      var element = new IdentifiedElement({
+        id: 153508,
+        modelId: 15781,
+        type: "REACTION"
+      });
+      return ServerConnector.getProject().then(function (project) {
+        map = helper.createCustomMap(project);
+        overlay = helper.createSearchDbOverlay(map);
+        collection = map.getMarkerSurfaceCollection();
+        return collection.createSurfaceForDbOverlay(element, overlay);
+      }).then(function (surface) {
+        return surface.onClickHandler();
+      });
+    });
+
+  });
+
+});
diff --git a/frontend-js/src/test/js/plugin/MinervaPluginProxy-test.js b/frontend-js/src/test/js/plugin/MinervaPluginProxy-test.js
index 14143e1fab..6662e7e27e 100644
--- a/frontend-js/src/test/js/plugin/MinervaPluginProxy-test.js
+++ b/frontend-js/src/test/js/plugin/MinervaPluginProxy-test.js
@@ -4,6 +4,7 @@ require("../mocha-config");
 
 var Alias = require('../../../main/js/map/data/Alias');
 var MinervaPluginProxy = require('../../../main/js/plugin/MinervaPluginProxy');
+var ServerConnector = require('../ServerConnector-mock');
 
 var logger = require('../logger');
 
-- 
GitLab