diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 803ffb5d5c39c60760e60e2fd30963f9f60779a4..4b04f0dbfa4ba199f3c2ea365287dd6cf0570caa 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -22,10 +22,6 @@ variables:
    CI_REGISTRY_IMAGE: $CI_REGISTRY/caosdb-webui-testenv
    # When using dind, it's wise to use the overlayfs driver for
    # improved performance.
-   DOCKER_DRIVER: overlay2
-
-services:
-   - docker:19.03-dind
 
 image: $CI_REGISTRY_IMAGE:latest
 
@@ -63,7 +59,7 @@ trigger_build:
   script:
     - echo $TOKEN     
     - /usr/bin/curl -X POST
-       -F token=$TOKEN
+       -F token=$DEPLOY_TRIGGER_TOKEN
        -F "variables[WEBUI]=$CI_COMMIT_REF_NAME"
        -F "variables[TriggerdBy]=WEBUI"
        -F "variables[TriggerdByHash]=$CI_COMMIT_SHORT_SHA"
@@ -71,12 +67,12 @@ trigger_build:
 
 # Build a docker image in which tests for this repository can run
 build-testenv:
-  tags: [ docker ]
+  tags: [ cached-dind ]
   image: docker:19.03
   stage: setup
   script: 
     - cd test/docker
-    - docker login -u testuser -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
+    - docker login -u indiscale -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
       # use here general latest or specific branch latest...
     - docker pull $CI_REGISTRY_IMAGE:latest || true
     - docker build 
diff --git a/build.properties.d/00_default.properties b/build.properties.d/00_default.properties
index d7e81457236d33995e0f21d72488c33198ce4caa..1e1df3997f414a027439225b6074d9d1ce9a4cb8 100644
--- a/build.properties.d/00_default.properties
+++ b/build.properties.d/00_default.properties
@@ -59,3 +59,6 @@ BUILD_FOOTER_DATA_POLICY_HREF=https://indiscale.com/?page_id=156
 # element).
 BUILD_FOOTER_CUSTOM_ELEMENT_ONE=
 BUILD_FOOTER_CUSTOM_ELEMENT_TWO=
+BUILD_CUSTOM_IMPRINT='<p> Put an imprint note here </p>'
+
+
diff --git a/conf/core/json/ext_map.json b/conf/core/json/ext_map.json
new file mode 100644
index 0000000000000000000000000000000000000000..33bcc2e53b6445f6262927bb941817dbe2e0f198
--- /dev/null
+++ b/conf/core/json/ext_map.json
@@ -0,0 +1,3 @@
+{
+    "disabled": true
+}
diff --git a/makefile b/makefile
index b1072187d2e34ffd4bb318110a04a0e7e700be87..22a274eb3f6836278791e4607f090af86e80bcb7 100644
--- a/makefile
+++ b/makefile
@@ -97,7 +97,7 @@ run-qunit:
 
 	# start firefox
 	screen -S caosdb-webui-test -X screen -t firefox $(XVFB-RUN) firefox \
-		"http://localhost:$(PORT)/webinterface/index.html?loggerPort=$(PORT)"
+		"http://localhost:$(PORT)/?loggerPort=$(PORT)"
 
 	# wait until server stops
 	while [ 1 -eq 1 ]; do \
diff --git a/misc/unit_test_http_server.py b/misc/unit_test_http_server.py
index 0667572361b35b47c65475b0af35955b650fa39f..a83d762ae69940d0eb43083ad9eff892e54f182b 100755
--- a/misc/unit_test_http_server.py
+++ b/misc/unit_test_http_server.py
@@ -30,6 +30,7 @@ import sys
 import os
 from http.server import SimpleHTTPRequestHandler, HTTPServer
 
+counter = 0
 class UnitTestsHandler(SimpleHTTPRequestHandler):
     """UnitTestsHandler
 
@@ -88,10 +89,12 @@ class UnitTestsHandler(SimpleHTTPRequestHandler):
 
         Print the body of a request.
         """
+        global counter
+        counter += 1
         content_length = int(self.headers['Content-Length'])
         post_data = self.rfile.read(content_length).decode("utf-8")
         with open("qunit.log", "a") as logfile:
-            logfile.write("[LOG] " + post_data + "\n")
+            logfile.write("[LOG {}] ".format(counter) + post_data + "\n")
         return post_data
 
 
diff --git a/src/core/css/webcaosdb.css b/src/core/css/webcaosdb.css
index beb8ab76b7922cd092f9467621199b57817866db..cc704be6cdd816cfe3214116a07275b3118bc363 100644
--- a/src/core/css/webcaosdb.css
+++ b/src/core/css/webcaosdb.css
@@ -29,6 +29,7 @@ body {
 
 .caosdb-v-main-col {
     flex-grow: 1;
+    max-width: 90vw;
 }
 
 .caosdb-v-show-only-child {
@@ -39,6 +40,11 @@ body {
     cursor: pointer;
 }
 
+/* Ugly workaround for https://gitlab.com/caosdb/caosdb-webui/issues/78 */
+.caosdb-select-table tbody > tr > td {
+    max-width: 30vw;
+}
+
 .caosdb-v-show-only-child:only-child {
     display: initial;
 }
@@ -69,9 +75,10 @@ body {
 
 .caosdb-f-main {
     display: flex;
+    width: unset;
 }
 .caosdb-f-main-entities {
-    width: unset;
+    width: calc(100% - 5px);
     min-width: 50vw;
 }
 
diff --git a/src/core/html/imprint.html b/src/core/html/imprint.html
index ec9ae6a397f79bfda8f89b0630c9b487f33b09c7..6ae3813e4d99ad3587ca7d3487fffeecf8ab3b37 100644
--- a/src/core/html/imprint.html
+++ b/src/core/html/imprint.html
@@ -17,9 +17,7 @@
   </head>
   <body style="width: 80%; margin: auto;">
     <h1>Imprint</h1>
-    <p>The entity responsible for running this service is:</p>
-    <p>Please contact the system administrator and ask them to enter the correct
-      information here.</p>
+	${BUILD_CUSTOM_IMPRINT}
     <h1>Liability notice</h1>
     <p>The authors of the software are not responsible for the way it is
       provided to the public or used otherwise.</p>
diff --git a/src/core/js/annotation.js b/src/core/js/annotation.js
index 25260abcaf076c0174af1455802e85fcaeeea825..685930da76d73980985210484a60f51690e45a46 100644
--- a/src/core/js/annotation.js
+++ b/src/core/js/annotation.js
@@ -234,7 +234,7 @@ this.annotation = new function() {
 
             // convert and send form
             var xml = annotation.convertNewCommentForm(form);
-            var xslPromise = annotation.loadAnnotationXsl(window.sessionStorage.caosdbBasePath);
+            var xslPromise = annotation.loadAnnotationXsl(connection.getBasePath());
             var responsePromise = annotation.postCommentXml(xml);
             var commentPromise = annotation.convertNewCommentResponse(responsePromise, xslPromise);
             commentPromise.then(function(resolve) {
@@ -306,7 +306,7 @@ this.annotation = new function() {
      * Shortcut for postXml. The renaming is also good to be able to replace the
      * function during unit tests with a dummy postXml function
      */
-    this.postCommentXml = (xml) => postXml(xml, window.sessionStorage.caosdbBasePath, "?H");
+    this.postCommentXml = (xml) => postXml(xml, connection.getBasePath(), "?H");
 
     /**
      * Convert a the response of a POST request for a new CommentAnnotation to
@@ -377,7 +377,7 @@ this.annotation = new function() {
         return $.ajax({
             cache: true,
             dataType: 'xml',
-            url: window.sessionStorage.caosdbBasePath + "Entity/?H&query=FIND+Annotation+WHICH+REFERENCES+" + referencedId + "+WITH+ID=" + referencedId,
+            url: connection.getBasePath() + "Entity/?H&query=FIND+Annotation+WHICH+REFERENCES+" + referencedId + "+WITH+ID=" + referencedId,
         });
     }
 
@@ -391,7 +391,7 @@ this.annotation = new function() {
 
     this.loadComments = async function(annotationSection) {
         var entityId = annotation.getEntityId(annotationSection);
-        var annotations = await annotation.getAnnotationsForEntity(entityId, annotation.queryAnnotation, annotation.loadAnnotationXsl(window.sessionStorage.caosdbBasePath));
+        var annotations = await annotation.getAnnotationsForEntity(entityId, annotation.queryAnnotation, annotation.loadAnnotationXsl(connection.getBasePath()));
         $(annotationSection).append(annotations);
     }
 
diff --git a/src/core/js/edit_mode.js b/src/core/js/edit_mode.js
index c9605dc2665ef4b7f3215b97d0950b296b16ff79..5b11866a4a3d50ee6dc2c3ae133612a4b20836ff 100644
--- a/src/core/js/edit_mode.js
+++ b/src/core/js/edit_mode.js
@@ -1147,9 +1147,7 @@ var edit_mode = new function() {
             for (var j = 0; j < prlist.length; j++) {
                 prdict.push(prlist[j].name + ": " + prlist[j].value);
             }
-            if (prdict.length == 0) {
-                prdict.push("ID: " + getEntityID(eli));
-            }
+            prdict.push("CaosDB ID: " + getEntityID(eli));
             $("select.caosdb-list-" + datatype).not('[data-resolved="true"]').append(
                 $("<option value=\"" + getEntityID(eli) + "\">" + prdict.join(", ") + "</option>"));
         }
diff --git a/src/core/js/ext_map.js b/src/core/js/ext_map.js
index 7340d4339ac4a6889a03563fafcee23948b7357c..aba52042292df7d2ead3b42b104db29855ca275a 100644
--- a/src/core/js/ext_map.js
+++ b/src/core/js/ext_map.js
@@ -24,15 +24,26 @@
 'use strict';
 
 /**
- * caosdb_map module for displaying a geographical map which shows entities at their associated geo location.
+ * caosdb_map module for displaying a geographical map which shows entities at
+ * their associated geo location.
  *
- * The configuration for this module has to be stored in `conf/ext/json/ext_map.json` and comply with the {@link MapConfig} type which is described below.
+ * The configuration for this module has to be stored in
+ * `conf/ext/json/ext_map.json` and comply with the {@link MapConfig} type
+ * which is described below.
+ *
+ * The current version is 0.3. It is not considered to be stable because
+ * implementation of the graticule still is not satisfactory.
+ *
+ * Apart from that the specification of the configuration and the
+ * implementation can be considered stable.
  */
-var caosdb_map = new function() {
+var caosdb_map = new function () {
 
     var logger = log.getLogger("caosdb_map");
-    this.version = "0.2";
-    this.dependencies = ["log", {"L": ["latlngGraticule", "Proj"]}, "navbar", "caosdb_utils"];
+    this.version = "0.3";
+    this.dependencies = ["log", {
+        "L": ["latlngGraticule", "Proj"]
+    }, "navbar", "caosdb_utils"];
     this.logger = logger;
 
     /**
@@ -41,7 +52,7 @@ var caosdb_map = new function() {
      * the configuration of the graticule(s) and the data model for the
      * integrated query generator.
      *
-     * @typedef {Object} MapConfig
+     * @typedef {object} MapConfig
      * @property {string} version - the version of the map
      * @property {string} default_view - the view which is shown when the user
      *     opens the map and has no stored view from prior visits to the map.
@@ -62,7 +73,7 @@ var caosdb_map = new function() {
      * decimal format. The latitude should not exceed [-90°,90°] ranges and the
      * longitude should not exceed [-180°, 180°] ranges.
      *
-     * @typedef {Object} DataModelConfig
+     * @typedef {object} DataModelConfig
      * @property {string} [lat=latitude] - the name of the latitude property.
      * @property {string} [lng=longitude] - the name of the longitude property.
      */
@@ -101,11 +112,11 @@ var caosdb_map = new function() {
      * {@link view_change_handler} plugin.
      *
      * Note: Leaflet comes with a few pre-defined coordinate reference systems
-     * (cf. {@link https://leafletjs.com/reference-1.5.0.html#crs Defined CRSs}). By
-     * default, the default CRS of leaflet will be used for the map (which is
-     * currently EPSG:3857, the Sperical Mercator). If {@link crs} is a string,
-     * e.g. "EPSG:3395" or "Simple" that matches the pre-defined CRS, the
-     * pre-defined CRS is used.
+     * (cf. {@link https://leafletjs.com/reference-1.5.0.html#crs Defined
+     * CRSs}). By default, the default CRS of leaflet will be used for the map
+     * (which is currently EPSG:3857, the Sperical Mercator). If {@link crs} is
+     * a string, e.g. "EPSG:3395" or "Simple" that matches the pre-defined CRS,
+     * the pre-defined CRS is used.
      *
      * @typedef {object} ViewConfig
      * @property {string} id - the unique view id, used by the 
@@ -212,11 +223,14 @@ var caosdb_map = new function() {
      * write our own or refactor one of these plug-ins.
      *
      * For the time being, both can be used and the implementation is set by
-     * the {@link type} property. If unset or "simple", the L.Graticule.js implementation is used.
+     * the {@link type} property. If unset or "simple", the L.Graticule.js
+     * implementation is used.
      *
-     * If {@link type} is "latlngGraticule" the leaflet.latlng-graticule implementation is used.
+     * If {@link type} is "latlngGraticule" the leaflet.latlng-graticule
+     * implementation is used.
      *
-     * The {@link options} must comply with the configuration options of the respective implementation.
+     * The {@link options} must comply with the configuration options of the
+     * respective implementation.
      *
      * @example <caption>Example for the `simple` graticule</caption>
      *     // this works for polar maps
@@ -258,8 +272,10 @@ var caosdb_map = new function() {
      *       }
      *     }
      *
+     * @typedef {object} GraticuleConfig
      * @property {string} type - either "simple" or "latlngGraticule".
-     * @property {object} options - the options for the graticule implementation.
+     * @property {object} options - the options for the graticule
+     *     implementation.
      */
 
     /**
@@ -290,6 +306,7 @@ var caosdb_map = new function() {
      *       }
      *     }
      *
+     * @typedef {object} CRSConfig
      * @property {string} code - the standardized CRS Code.
      * @property {string} proj4def - the proj4 definition of the CRS.
      * @property {object} options - options according to the 
@@ -302,7 +319,7 @@ var caosdb_map = new function() {
      * @type {MapConfig}
      */
     this._default_config = {
-        "version": "0.2",
+        "version": this.version,
         "datamodel": {
             "lat": "latitude",
             "lng": "longitude",
@@ -313,28 +330,199 @@ var caosdb_map = new function() {
                 "entity": "",
             },
         },
-        // TODO move to css
-        "panel": {
-            "css": {
-                "height": "500px",
-            },
-        },
     }
 
     /**
-     * (Re-)set this module's functions to standard implementation.
+     * A function which returns entities which are to be displayed on the map.
+     *
+     * The parameters may be used to filter the entities. The caller should not
+     * expect anything from the entities.
+     *
+     * @async
+     * @callback {mapEntityGetter}
+     * @param {DataModelConfig} datamodel - datamodel of the entities to be returned.
+     * @param {number} north - northern bounding latitude
+     * @param {number} south - southern bounding latitude
+     * @param {number} west - western bounding longitude
+     * @param {number} east - eastern bounding longitude
+     * @returns {HTMLElement[]} array of entities in HTML representation.
+     */
+
+    /**
+     * A function which generates a condensed HTML representation of an entity
+     * which can be shown as a popup in a map.
+     *
+     * @callback {mapEntityPopupGenerator}
+     * @param {HTMLElement} entity - in HTML representation
+     * @param {DataModelConfig} datamodel
+     * @return {HTMLElement} a popup element.
+     */
+
+    /**
+     * @typedef {object} EntityLayerConfig
+     * @property {string} id
+     * @property {string} name
+     * @property {string} description
+     * @property {DivIcon_options} icon_options
+     * @property {number} zIndexOffset
+     * @property {mapEntityGetter} get_entities
+     * @property {mapEntityPopupGenerator} make_popup
+     */
+
+    /**
+     * Implements {@link mapEntityGetter}.
+     */
+    this._get_current_page_entities = function (
+        datamodel, north, south, west, east) {
+        const container = $(".caosdb-f-main-entities")[0];
+        return caosdb_map.get_map_entities(container, datamodel);
+    }
+
+    /**
+     * Implements {@link mapEntityGetter}.
+     */
+    this._query_all_entities = async function (
+        datamodel, north, south, west, east) {
+        const results = await caosdb_map.query(`FIND ENTITY WITH ${datamodel.lat} AND ${datamodel.lng}`);
+        const container = $('<div>').append(results)[0];
+        return caosdb_map.get_map_entities(container, datamodel);
+    }
+
+    /**
+     * Create a HTML representation of an entity which is suitable for being
+     * displayed in the map as a marker's popup.
+     *
+     * Implements {@link mapEntityPopupGenerator}
+     * @param {HTMLElement} entity - an entity in HTML representation.
+     * @param {DataModelConfig} datamodel - configuration of the properties
+     *     used for the coordinates.
+     * @returns {HTMLElement} a popup element.
+     */
+    this._make_map_popup = function (entity, datamodel) {
+        const lat = getProperty(entity, datamodel.lat);
+        const lng = getProperty(entity, datamodel.lng);
+        const role_label = $(entity).find(
+            ".label.caosdb-f-entity-role").first().clone();
+        const parent_list = caosdb_map.make_parent_labels(entity);
+        const name = caosdb_map.make_entity_name_label(entity);
+        const dms_lat = L.NumberFormatter.toDMS(lat);
+        const dms_lng = L.NumberFormatter.toDMS(lng);
+        const loc = $(`<div class="small text-muted">
+            Lat: ${dms_lat} Lng: ${dms_lng}
+            </div>`);
+        const ret = $('<div/>')
+            .append(role_label)
+            .append(parent_list)
+            .append(name)
+            .append(loc);
+        return ret[0];
+    }
+
+
+    /**
+     * Default entities layers configuration with two layers:
+     * "current_page_entities" which shows the entities on the current page and
+     * "all_map_entities" which shows all entities in the database with
+     * coordinates.
+     *
+     * @type {EntityLayerConfig[]}
+     */
+    this._default_entity_layer_config = [{
+        "id": "current_page_entities",
+        "name": "Entities on the current page.",
+        "description": "Show all entities on the current page.",
+        "icon": {
+            html: '<span style="color: #00F; font-size: 20px" class="glyphicon glyphicon-map-marker"></span>',
+            iconAnchor: [10, 19],
+            className: "",
+        },
+        "zIndexOffset": 1000,
+        "datamodel": {
+            "lat": "latitude",
+            "lng": "longitude",
+            "role": "ENTITY",
+            "entity": "",
+        },
+        "get_entities": this._get_current_page_entities,
+        "make_popup": this._make_map_popup,
+    }, {
+        "id": "all_map_entities",
+        "name": "All entities",
+        "description": "Show all entities with coordinates.",
+        "icon": {
+            html: '<span style="color: #F00; font-size: 20px" class="glyphicon glyphicon-map-marker"></span>',
+            iconAnchor: [10, 19],
+            className: "",
+        },
+        "zIndexOffset": 0,
+        "datamodel": {
+            "lat": "latitude",
+            "lng": "longitude",
+            "role": "ENTITY",
+            "entity": "",
+        },
+        "get_entities": this._query_all_entities,
+        "make_popup": this._make_map_popup,
+    }, ];
+
+
+    /**
+     * If no views are configured this dummy view servers as a placeholder.
+     *
+     * @type {ViewConfig}
+     */
+    this._unconfigured_views = [{
+        "id": "UNCONFIGURED",
+        "zoom": 10,
+        "center": {
+            "lat": 0,
+            "lng": 0,
+        },
+        "select": false,
+        "view_change": false,
+        "tileLayer": {
+            "type": "wms",
+            "url": "/webinterface/${BUILD_NUMBER}/pics/map_tile_caosdb_logo.png"
+        }
+    }];
+
+    /**
+     * Default configuration for every map view.
+     *
+     * This configuration sets a default zoom level and center of the map.
+     *
+     * Furthermore, it enables the select handler and the view_change handler.
+     *
+     * Leaflets boxZoom handler is disabled, because the select handler uses
+     * the box for selection.
+     *
      */
-    this._init_functions = function() {
+    this._default_leaflet_config = {
+        "center": {
+            "lat": 53.5332554,
+            "lng": 8.5783268
+        },
+        "zoom": 5,
+        "boxZoom": false,
+        "select": true,
+        "view_change": true,
+    }
+
+    /** (Re-)set this module's functions to standard implementation.
+     */
+    this._init_functions = function () {
         logger.trace("enter _init_functions");
 
         /**
          * Create button with the caosdb-f-toggle-map-button class.
          *
-         * @param {string} content - the button content. Optional, defaults to "Map"
+         * @param {string} content - the button content. Optional, defaults to
+         *     "Map"
          */
-        this.create_toggle_map_button = function(content = "Map") {
+        this.create_toggle_map_button = function (content = "Map") {
             logger.trace("enter create_toggle_map_button");
-            let button = $(`<button class="navbar-btn btn btn-link"/>`);
+            let button = $(
+                `<button class="navbar-btn btn btn-link"/>`);
             button.toggleClass("caosdb-f-toggle-map-button", true);
             button.text(content);
             logger.trace("leave create_toggle_map_button");
@@ -344,12 +532,19 @@ var caosdb_map = new function() {
         /**
          * Create map panel (div.caosdb-f-map-panel).
          */
-        this.create_map_panel = function() {
+        this.create_map_panel = function () {
             let panel = $("<div>");
             panel.toggleClass("caosdb-f-map-panel", true);
 
             // for centered and responsive display
             panel.toggleClass("container", true);
+
+            // TODO move to css file
+            $(panel).css({
+                "height": "500px"
+            });
+
+
             return panel[0];
         }
 
@@ -362,12 +557,16 @@ var caosdb_map = new function() {
          *
          * @returns {L.Map} the map
          */
-        this.create_map_view = function(container, config) {
-            logger.trace("enter create_map_view", container, config);
+        this.create_map_view = function (container, config) {
+            logger.trace("enter create_map_view", container,
+                config);
+            caosdb_utils.assert_html_element(container, "param `container`");
 
             // crs
-            if (typeof config.crs === "string" || config.crs instanceof String) {
-                config.crs = L.CRS[crs.replace(":","")];
+            // TODO move to separate function
+            if (typeof config.crs === "string" || config
+                .crs instanceof String) {
+                config.crs = L.CRS[config.crs.replace(":", "")];
                 logger.debug("use pre-defined CRS ", config.crs);
             } else if (typeof config.crs === "object") {
                 config.crs = new L.Proj.CRS(config.crs.code,
@@ -376,25 +575,40 @@ var caosdb_map = new function() {
             }
 
             // set tiling server
+            // TODO move to separate function
             var tileLayer;
-            if (config.tileLayer.type && config.tileLayer.type === "wms") {
+            if (config.tileLayer.type && config.tileLayer.type ===
+                "wms") {
                 tileLayer = L.tileLayer
-                    .wms(config.tileLayer.url, config.tileLayer.options);
-            } else if (config.tileLayer.type === "osm") {
+                    .wms(config.tileLayer.url, config.tileLayer
+                        .options);
+            } else if (config.tileLayer.type === "osm" ||
+                typeof config.tileLayer.type === "undefined") {
                 tileLayer = L
-                    .tileLayer(config.tileLayer.url, config.tileLayer.options);
+                    .tileLayer(config.tileLayer.url, config
+                        .tileLayer.options);
             } else {
-                throw new Error("unknown tileLayer type: "
-                    + config.tileLayer.type);
+                throw new Error("unknown tileLayer type: " +
+                    config.tileLayer.type);
             }
 
-
             var map = L.map(container, config);
+            map._crs = config.crs;
             tileLayer.addTo(map);
 
+            // add listeners which store the current zoom and center
+            map.on("zoomend", (e) => {
+                sessionStorage["caosdb_map.view." + config.id + ".zoom"] = map.getZoom();
+            });
+
+            map.on("moveend", (e) => {
+                sessionStorage["caosdb_map.view." + config.id + ".center"] = JSON.stringify(map.getCenter());
+            });
+
             // add mouse coords
-            // TODO move to config
+            // TODO do this outside of this function (one level up)
             L.control.coordinates({
+                // TODO move to config
                 "position": "bottomleft",
                 "decimals": 2,
                 "enableUserInput": false,
@@ -407,7 +621,8 @@ var caosdb_map = new function() {
                 map.invalidateSize(true);
             };
 
-            if(config.graticule) {
+            // TODO move one level up
+            if (config.graticule) {
                 this.add_graticule(map, config.graticule);
             }
 
@@ -415,7 +630,13 @@ var caosdb_map = new function() {
         }
 
 
-        this.init_map_panel = function(config) {
+        /**
+         * Initialize the map panel.
+         *
+         * The map panel is the HTMLElement which contains the map. Is is
+         * hidden or shown when the user clicks on the toggle map button.
+         */
+        this.init_map_panel = function () {
             logger.trace("enter init_map_panel");
 
             // remove old
@@ -423,9 +644,6 @@ var caosdb_map = new function() {
 
             let panel = this.create_map_panel();
 
-            $(panel).css(config.css);
-
-
             $(panel).hide();
             $('nav').first().after(panel);
 
@@ -437,9 +655,10 @@ var caosdb_map = new function() {
         /**
          * Toggle the map (show/hide).
          */
-        this.toggle_map = function() {
+        this.toggle_map = function () {
             logger.trace("enter toggle_map");
-            $(".caosdb-f-map-panel").toggle(900, ()=>this._toggle_cb());
+            $(".caosdb-f-map-panel").toggle(900, () => this
+                ._toggle_cb());
         }
 
 
@@ -456,10 +675,12 @@ var caosdb_map = new function() {
          * @throws TypeError if the parameters have wrong types.
          * @returns {HTMLElement} the button
          */
-        this.bind_toggle_map = function(button, toggle_cb) {
+        this.bind_toggle_map = function (button, toggle_cb) {
             logger.trace("enter bind_toggle_map");
-            caosdb_utils.assert_html_element(button, "parameter 'button'");
-            caosdb_utils.assert_type(toggle_cb, "function", "parameter 'toggle_cb'");
+            caosdb_utils.assert_html_element(button,
+                "parameter 'button'");
+            caosdb_utils.assert_type(toggle_cb, "function",
+                "parameter 'toggle_cb'");
             $(button).on("click", toggle_cb);
             logger.trace("leave bind_toggle_map");
             return button;
@@ -471,12 +692,13 @@ var caosdb_map = new function() {
          * @throws Error - if a dependency is unmet.
          * @returns {boolean} true if all dependencies are defined.
          */
-        this.check_dependencies = function() {
+        this.check_dependencies = function () {
             logger.trace("enter check_dependencies");
             for (let dep of this.dependencies) {
                 if (typeof dep == "string") {
                     if (typeof window[dep] == "undefined") {
-                        throw new Error("Unmet dependency: " + dep);
+                        throw new Error("Unmet dependency: " +
+                            dep);
                     }
                 } else {
                     // TODO
@@ -489,7 +711,24 @@ var caosdb_map = new function() {
 
 
         /**
-         * Initialize the caosdb_map module.
+         * Return the view with the given id from an array of views.
+         *
+         * @param {ViewConfig[]} views - array of views
+         * @param {string} id - the id of the view to be returned.
+         * @throws {Error} if the view is not in the array.
+         * @return {ViewConfig} with the given id.
+         */
+        this.get_view_config = function (views, id) {
+            caosdb_utils.assert_string(id, "param `id`");
+            for (const view of views) {
+                if (view.id === id)
+                    return view;
+            }
+            throw new Error("Could not find view " + id);
+        }
+
+
+        /** Initialize the caosdb_map module.
          *
          * 1) load config
          * 2) initialize leaflet plug-ins (select_handler, change-view-handler)
@@ -497,60 +736,97 @@ var caosdb_map = new function() {
          * 4) inttialize the map itself
          * 5) add the map toggle button to the navbar
          */
-        this.init = async function() {
+        this.init = async function () {
             logger.trace("enter init");
             var panel = undefined;
             var toggle_button = undefined;
             try {
                 const config = await this.load_config();
+                if (config.disabled) {
+                    return;
+                }
+                if (!config.views || config.views.length === 0) {
+                    logger.warn("no views in config");
+                    return;
+                }
                 this.config = config;
                 this.init_select_handler();
                 this.init_view_change_handler();
-                panel = this.init_map_panel(config.panel);
+                panel = this.init_map_panel();
 
+                // TODO split in smaller pieces and move callback to separate function
                 this.change_map_view = (view) => {
-                    var local_conf = {};
-                    if(this._map) {
-                        // TODO it would be nice if a change of the view would not reset the center and zoom.
-                        // However, if the old center is not on the current
-                        // map, leaflet throws exceptions. Furthermore, the
-                        // zoom level is view-specific - level 2 in one view
-                        // does not necessarily have the same resolution as the
-                        // level 2 in another view. No idea, how to find a good
-                        // zoom approximation...
-                        // At least we know this - the following does not work:
-                        //local_conf["zoom"] = this._map.getZoom();
-                        //local_conf["center"] = this._map.getCenter();
+                    if (this._map) {
                         this._map.remove();
                     }
 
-                    var nextview = view || sessionStorage["caosdb_map.view"] || config.leaflet.default_view;
-                    sessionStorage["caosdb_map.view"] = nextview;
-                    var view_config = $.extend(true, {}, {
-                        // TODO move to defaults object
-                        "select": true,
-                        "boxZoom": false,
-                        "view_change": true,
-                        "zoom": 1,
-                        "center": { "lat": 0, "lng": 0 },
-                        }, config.leaflet.views[nextview], local_conf);
-                    this._map = this.create_map_view(panel, view_config);
-                    this.init_layer_groups(this._map);
-
-                    this.create_layer_current_page_entities();
+                    var nextview = view || sessionStorage[
+                            "caosdb_map.view"] || config
+                        .default_view || config.views[0].id;
+                    sessionStorage["caosdb_map.view"] =
+                        nextview;
+                    var nextview_config;
+
+                    // try to get the next view config.
+                    try {
+                        nextview_config = this
+                            .get_view_config(config.views,
+                                nextview);
+                    } catch (err) {
+                        logger.warn(err);
+
+                        // try the default
+                        nextview = config.default_view || config.views[0].id;
+                        nextview_config = this
+                            .get_view_config(config.views,
+                                nextview);
+                    }
+
+                    // remember zoom and center for each view:
+                    var local_conf = {};
+                    local_conf["zoom"] = sessionStorage["caosdb_map.view." +
+                        nextview + ".zoom"];
+                    local_conf["center"] = sessionStorage["caosdb_map.view." +
+                        nextview + ".center"];
+                    if (local_conf["center"]) {
+                        local_conf["center"] =
+                            JSON.parse(local_conf["center"]);
+                    }
+
+                    // merge the view config with default values
+                    var view_config = $.extend(true, {},
+                        this._default_leaflet_config,
+                        nextview_config,
+                        local_conf);
+
+                    // create map
+                    this._map = this.create_map_view(panel,
+                        view_config);
+
+                    // init entity layers
+                    var layers = this.init_entity_layers(this._default_entity_layer_config);
+                    var layerControl = L.control.layers();
+                    for (const layer of layers) {
+                        layerControl.addOverlay(layer.layer_group, layer.chooser_html.outerHTML);
+                        layer.layer_group.addTo(this._map);
+                    }
+                    layerControl.addTo(this._map);
+
+                    // initialize handlers
                     this.add_select_handler(this._map);
                     this.add_view_change_handler(
                         this._map,
-                        config.leaflet.views,
+                        config.views,
                         nextview,
                         this.change_map_view
                     );
-                }
+                };
                 this.change_map_view();
 
                 toggle_button = this.init_toggle_map_button();
             } catch (err) {
-                logger.error("Could not initialize the map.", err);
+                logger.error("Could not initialize the map.",
+                    err);
                 if (typeof toggle_button !== "undefined") {
                     $(toggle_button).remove();
                 }
@@ -561,17 +837,90 @@ var caosdb_map = new function() {
             logger.trace("leave init");
         }
 
+
+        /**
+         * The _EntityLayer object is used to pass around map overlay layers
+         * between functions. It is not part of the public API.
+         *
+         * @typedef {object} _EntityLayer
+         * @property {string} id
+         * @property {HTMLElement} chooser_html
+         * @property {L.LayerGroup} layer_group
+         * @property {boolean} active
+         */
+
+        /**
+         * @param {EntityLayerConfig[]} configs
+         * @returns {_EntityLayer[]}
+         */
+        this.init_entity_layers = function (configs) {
+            var ret = []
+            for (const conf of configs) {
+                ret.push(this.init_entity_layer(conf));
+            }
+            return ret;
+        }
+
+
+        /**
+         * Initialize an entity layer.
+         *
+         * @param {EntityLayerConfig} config
+         * @return {_EntityLayer}
+         */
+        this.init_entity_layer = function (config) {
+            logger.trace("enter init_entity_layer", config);
+
+            var layer_group = L.layerGroup();
+
+            // load all entities into layer group
+            var _load = async function (layer_group, config) {
+                var entities = await config.get_entities(config.datamodel);
+                var markers = caosdb_map.create_entitiy_markers(
+                    entities, config.datamodel, config.make_popup,
+                    config.zIndexOffset, config.icon);
+
+                for (const marker of markers) {
+                    layer_group.addLayer(marker);
+                }
+            };
+            _load(layer_group, config);
+
+            var ret = {
+                "id": config.id,
+                "active": typeof config.active === "undefined" || config.active,
+                "chooser_html": this.make_layer_chooser_html(config),
+                "layer_group": layer_group
+            };
+
+
+            return ret;
+        }
+
+
+        /**
+         * @param {EntityLayerConfig} config
+         * @return {HTMLElement}
+         */
+        this.make_layer_chooser_html = function (config) {
+            return $('<span/>')
+                .attr("title", config.description)
+                .append(config.icon.html)
+                .append(config.name)[0];
+        }
+
+
         /**
          * Add the graticule to the current map view.
          *
          * @parameter {L.Map} map - the leaflet map.
          * @parameter {GraticuleConfig} config - the graticule config.
          */
-        this.add_graticule = function(map, config) {
-            if(config.type === "latlngGraticule") {
+        this.add_graticule = function (map, config) {
+            if (config.type === "latlngGraticule") {
                 L.latlngGraticule(config.options).addTo(map);
-            } else if( typeof config.type === "undefined"
-                || config.type === "simple") {
+            } else if (typeof config.type === "undefined" ||
+                config.type === "simple") {
                 L.graticule(config.options).addTo(map);
             } else {
                 this.logger.warn("unknown graticule type: ", type);
@@ -583,7 +932,7 @@ var caosdb_map = new function() {
          *
          * @param {L.Map} map
          */
-        this.add_select_handler = function(map){
+        this.add_select_handler = function (map) {
             map.addHandler("select", L.SelectHandler);
         }
 
@@ -601,62 +950,147 @@ var caosdb_map = new function() {
          * @parameter {string} active - the id of the currently active view.
          * @parameter {ViewChangeCB} view_changer_cb - callback which changes the view.
          */
-        this.add_view_change_handler = function(map, views, active, view_changer_cb) {
+        this.add_view_change_handler = function (map, views, active,
+            view_changer_cb) {
             map.addHandler("view_change", L.ViewChangeHandler);
             map.view_change.init(views, active, view_changer_cb);
         }
 
 
-        this.init_view_change_handler = function(views) {
-            L.ViewChangeHandler = L.Handler.extend(this.view_change_handler);
+        /** Initialize the L.ViewChangeHandler as a leaflet extension of
+         * L.Handler.
+         */
+        this.init_view_change_handler = function () {
+            L.ViewChangeHandler =
+                L.Handler.extend(this.view_change_handler);
         }
 
-        this.init_select_handler = function() {
-            L.SelectHandler = L.Handler.extend(this.select_handler);
+        /** Initialize the L.SelectHandler as a leaflet extension of L.Handler.
+         */
+        this.init_select_handler = function () {
+            L.SelectHandler =
+                L.Handler.extend(this.select_handler);
         }
 
 
-        this.open_query_panel = function () {
+        /**
+         * Show the query panel if not visible, collapse the query shortcuts
+         * if visible and fill the query string into the text input of the
+         * query panel.
+         *
+         * Also, the query form's paging setting is set to return *all* results
+         * one page.
+         *
+         * The query shortcuts are collapsed because they would otherwise take
+         * up too much space of the view port and push the map outside of the
+         * view port.
+         *
+         * @param {string} query - the generated query.
+         */
+        this.open_query_panel = function (query) {
             var query_panel = $("#caosdb-query-panel");
 
-            // hide query shortcuts in available
-            query_panel.find(".caosdb-f-shortcuts-panel-toggle-button:not('.caosdb-f-shortcuts-panel-hidden')").click();
+            // hide query shortcuts if available and visible
+            query_panel.find(
+                ".caosdb-f-shortcuts-panel-toggle-button:not('.caosdb-f-shortcuts-panel-hidden')"
+            ).click();
 
             query_panel
                 .collapse("show");
-        }
 
+            // fill query into text field
+            query_panel.find("#caosdb-query-textarea").val(query);
 
-        this.fill_query_filter = function (query_filter) {
-            let role = this.config.select.query.role;
-            let entity = this.config.select.query.entity;
-            var query = "FIND " + role + " " + entity + " WITH " + query_filter;
-            $("#caosdb-query-textarea").val(query);
             // remove paging for queries generated by the map
-            $("#caosdb-query-paging-input").remove();
+            query_panel.find("#caosdb-query-paging-input")
+                .remove();
+        }
+
+
+
+        /**
+         * Generate a query to search inside a map area bounded by maximum and
+         * minimum latitudes and longitudes.
+         *
+         * This function uses the configuration from {@link SelectConfig} for
+         * the role and entity, the configuration from {@link DataModelConfig}
+         * for the latitude and longitude properties, and constructs a query
+         * filter from the rectangular bounding box such that all entities with
+         * coordinates inside that area are returned.
+         *
+         * The area is specified as follows:
+         *
+         * <code>
+         *                  north
+         *            +-----------------+
+         *            |                 |
+         *            |                 |
+         *        west|                 |east
+         *            |                 |
+         *            |                 |
+         *            +-----------------+
+         *                  south
+         * </code>
+         *
+         * The horizontal lines (-) mark the maximum and minimum latitude and
+         * the vertical lines (|) mark the maximum and minimum longitude of the
+         * area to be searched in.
+         *
+         * @param {number} north
+         * @param {number} south
+         * @param {number} west
+         * @param {number} east
+         * @return {string} a query string.
+         */
+        this.generate_query_from_bounds = function (north, south, west,
+            east) {
+            const role = this.config.select.query.role;
+            const entity = this.config.select.query.entity;
+            const lat = this.config.datamodel.lat;
+            const lng = this.config.datamodel.lng;
+
+            const query_filter = " ( " + lat + " < '" + north +
+                "' AND " + lat +
+                " > '" + south + "' AND " + lng + " > '" + west +
+                "' AND " +
+                lng + " < '" + east + "' ) ";
+
+            const query = "FIND " + role + " " + entity +
+                " WITH " + query_filter;
+            return query
         }
 
 
         /**
-         * Return a config.
+         * Retrieve the map config from the CaosDBServer.
          *
-         * @returns {Object} config
+         * @param {string} [resource=ext_map.json] - location of the config
+         *     file.
+         * @returns {MapConfig} config
          */
-        this.load_config = async function() {
+        this.load_config = async function (resource) {
             logger.trace("enter load_config");
 
             var conf = {};
             try {
-                conf = await load_config("ext_map.json");
+                resource = resource || "ext_map.json";
+                conf = await load_config(resource);
             } catch (err) {
                 logger.error(err);
             }
 
 
-            logger.debug("Could not find any config. returning default config.");
-            logger.trace("leave load_config");
-            var result = $.extend(true, {}, this._default_config, conf);
-            return result; 
+            var result = $.extend(true, {}, this
+                ._default_config, conf);
+
+            if (!result.views || result.views.length === 0) {
+                logger.debug(
+                    "Could not find any view config. using a dummy tiling server."
+                );
+                result.views = this._unconfigured_views;
+            }
+            logger.trace("leave load_config", result);
+            return result;
         }
 
 
@@ -668,11 +1102,14 @@ var caosdb_map = new function() {
          * @throws Error, if any assertion about the config fails.
          * @returns {boolean} true, iff everything is fine.
          */
-        this.check_config = function(config) {
+        this.check_config = function (config) {
             logger.trace("enter check_config");
             caosdb_utils.assert_type(config, "object", "config");
             if (config.version !== caosdb_map.version) {
-                throw new TypeError("The version of the configuration does not match the version of this implementation of the caosdb_map module. Should be '"+caosdb_map.version+"', was '"+config.version+"'.");
+                throw new TypeError(
+                    "The version of the configuration does not match the version of this implementation of the caosdb_map module. Should be '" +
+                    caosdb_map.version + "', was '" + config
+                    .version + "'.");
             }
 
             logger.trace("enter check_config");
@@ -681,9 +1118,10 @@ var caosdb_map = new function() {
 
 
         /**
-         * Create a button, bind the toggle_map function to the on-click event and append it to the navbar.
+         * Create a button, bind the toggle_map function to the on-click event
+         * and append it to the navbar.
          */
-        this.init_toggle_map_button = function() {
+        this.init_toggle_map_button = function () {
             logger.trace("enter init_toggle_map_button");
 
             // remove old
@@ -698,154 +1136,234 @@ var caosdb_map = new function() {
         }
 
 
-        this.get_map_objects = function() {
-            var datamodel = this.config.datamodel;
-            var map_objects = $(".caosdb-entity-panel").has(".caosdb-property-name:contains('"+ datamodel.lng + "')").has(".caosdb-property-name:contains('" + datamodel.lat + "')");
-            return map_objects.toArray();
+        /**
+         * Return all entities which have the properties specified in the
+         * datamodel configuration.
+         *
+         * @param {HTMLElement} container - where the entities in HTML
+         *     representation are kept.
+         * @param {DataModelConfig} datamodel
+         * @returns {HTMLElement[]} entities with coordinate properties.
+         */
+        this.get_map_entities = function (container, datamodel) {
+            var map_entities = $(container)
+                .find(".caosdb-entity-panel").has(
+                    ".caosdb-property-name:contains('" + datamodel
+                    .lng + "')").has(
+                    ".caosdb-property-name:contains('" + datamodel
+                    .lat + "')");
+            return map_entities.toArray();
         }
 
 
-        this.make_parent_labels = function(entity) {
+
+        /**
+         * Return an array containing spans with the entity's parents' names in
+         * the pop-up which is shown when the user clicks on an entity marker
+         * in the map.
+         *
+         * @param {HTMLElement} entity - the entity in HTML representation.
+         * @param {HTMLElement[]} array of spans
+         */
+        this.make_parent_labels = function (entity) {
             var parents = getParents(entity);
             var ret = [];
-            for(const par of parents) {
-                // TODO handle case where the parent is shown
-                var label = $('<span class="label">' + par.name + '</span>')
-                    .css({"color": "#333", "border": "1px solid #333"});
+            for (const par of parents) {
+                var label = $('<span class="label">' + par.name +
+                        '</span>')
+                    // TODO move to global css
+                    .css({
+                        "color": "#333",
+                        "border": "1px solid #333"
+                    });
                 ret.push(label[0]);
             }
             return ret;
         }
 
 
-        this.make_entity_name_label = function(entity) {
-            var name = getEntityName(entity);
-            var id = getEntityId(entity);
-            var name_label = $('<div/>')
-                .css({"margin-top": "4px", "margin-bottom":"4px"})
+        /**
+         * Create a div which shows the name of the entity and contains a link
+         * which points to the entity on the page.
+         *
+         * This is shown as a part of the pop-up when the user click on an
+         * entity marker in the map.
+         *
+         * @param {HTMLElement} entity - the entity in HTML representation.
+         * @returns {HTMLElement} a label with the entities name.
+         */
+        this.make_entity_name_label = function (entity) {
+            const name = getEntityName(entity);
+            const id = getEntityId(entity);
+
+            const entity_on_page = $(`#${id}`).length > 0;
+            const href = entity_on_page ? `#${id}` : connection.getBasePath() + `Entity/${id}`
+            const link_title = entity_on_page ? "Jump to this entitiy." : "Browse to this entity.";
+            const link = $(`<a title="${link_title}" href="${href}"/>`)
+                .addClass("pull-right")
+                .append(`<span class="glyphicon glyphicon-share-alt"/></a>`);
+
+            const name_label = $('<div/>')
+                // TODO move to global css
+                .css({
+                    "margin-top": "4px",
+                    "margin-bottom": "4px"
+                })
                 .text(name)
-                .append('<a title="Jump to this entity" class="pull-right" href="#' + id + '"><span class="glyphicon glyphicon-share-alt"/></a>');
+                .append(link);
             return name_label[0];
         }
 
 
-        this.make_map_popup_representation = function(entity) {
-            var role_label = $(entity).find(".label.caosdb-f-entity-role").first().clone();
-            var parent_list = this.make_parent_labels(entity);
-            var name = this.make_entity_name_label(entity);
-            var ret = $('<div/>')
-                .append(role_label)
-                .append(parent_list)
-                .append(name);
-            return ret[0];
-        }
+        /**
+         * Retrieve entities from the server and return a container.
+         *
+         * @param {string} query_str - a CQL query.
+         * @returns {HTMLElement[]} an array of entities in HTML representation.
+         */
+        this.query = query;
 
-        this.create_layer_current_page_entities = function() {
-            logger.trace("enter create_layer_current_page_entities");
-            var datamodel = this.config.datamodel;
-            var map_objects = this.get_map_objects();
 
-            for (const map_object of map_objects) {
-                var lat = getProperty(map_object, datamodel.lat);
-                var lng = getProperty(map_object, datamodel.lng);
+        /**
+         * Create markers for the map for an array of entities.
+         *
+         * @param {HTMLElement[]} entities - an array of entities in HTML
+         *     representation.
+         * @param {DataModelConfig} datamodel - specifies the properties for
+         *     coordinates.
+         * @param {mapEntityPopupGenerator} [make_popup] - creates popup content.
+         * @param {number} zIndexOffset - zIndexOffset of the marker.
+         * @param {DivIcon_options} icon_options
+         * @returns {L.Marker[]} an array of markers for the map.
+         */
+        this.create_entitiy_markers = function (entities, datamodel, make_popup, zIndexOffset, icon_options) {
+            logger.trace("enter create_entitiy_markers", entities, datamodel, zIndexOffset, icon_options);
+
+            var ret = []
+            for (const map_entity of entities) {
+                var lat = getProperty(map_entity, datamodel.lat);
+                var lng = getProperty(map_entity, datamodel.lng);
 
                 if (lat && lng) {
-                    logger.debug("found map-object", map_object, lat, lng);
-                    var marker = L.marker([lat, lng]);
-                    marker.bindPopup(this.make_map_popup_representation(map_object));
-                    this.add_to_layer_group("current_page_entities", marker);
+                    logger.debug(`create entity marker at [${lat}, ${lng}] for`,
+                        map_entity);
+                    var marker = L.marker([lat, lng], {
+                        icon: L.divIcon(icon_options)
+                    });
+
+                    if (zIndexOffset) {
+                        marker.setZIndexOffset(zIndexOffset);
+                    }
+                    if (make_popup) {
+                        marker.bindPopup(make_popup(map_entity, datamodel));
+                    }
+                    ret.push(marker);
                 } else {
-                    logger.debug("no valid latitude or longitude", map_object, lat, lng);
+                    logger.debug("undefined latitude or longitude",
+                        map_entity, lat, lng);
                 }
             }
-            logger.debug("current_page_layer", this.get_layer_group("current_page_layer"));
-            logger.trace("leave create_layer_current_page_entities");
-        }
-
-        this.get_standard_layer_groups = function() {
-            logger.trace("enter get_standard_layer_groups");
-            var new_groups = {};
-
-            // current page entities
-            var cpe_group = L.layerGroup();
-            new_groups["current_page_entities"] = cpe_group;
-
-            logger.trace("leave get_standard_layer_groups");
-            return new_groups;
-        }
-
-        this.init_layer_groups = function(map) {
-            logger.trace("enter init_layer_groups");
-            if (typeof this._layer_groups == "undefined") {
-                this._layer_groups = this.get_standard_layer_groups();
-            }
-
-            for (var name in this._layer_groups) {
-                let lg = this.get_layer_group(name);
-                logger.debug("add layer group to map:", name, lg);
-                lg.addTo(map);
-            }
-            logger.trace("leave init_layer_groups");
-        }
-
-        this.add_to_layer_group = function(group_name, layer) {
-            logger.trace("enter add_to_layer_group");
-            this.get_layer_group(group_name).addLayer(layer);
-            logger.trace("leave add_to_layer_group");
+            return ret;
         }
 
-        this.get_layer_group = function(name) {
-            return this._layer_groups[name];
-        }
 
+        /**
+         * Plug-in for leaflet which adds a menu to the map for changing the view.
+         *
+         * A view is a combination of a base map tile layer, a coordinate
+         * reference system and possibly other properties of the map.
+         *
+         * When a new view is activated the old map is being destroyed and a
+         * new map is contstructed.
+         */
         this.view_change_handler = {
-            init: function(views, active, view_changer) {
-                this._add_view_options(this._view_menu, views, active, view_changer);
+            init: function (views, active, view_changer) {
+                this._add_view_options(this._view_menu, views,
+                    active, view_changer);
             },
 
-            _add_view_options: function(menu, views, active, view_changer) {
-                const click = function(e) {
-                    logger.trace("click on view_menu option", e);
+            /**
+             * Add a all defined views to the menu.
+             *
+             * The menu is a set of checkboxes. The currently active view is
+             * checked initially and when another view is selected a callback
+             * function intiates change to the new view.
+             *
+             * @param {HTMLElement} menu - A form element where the options are
+             *     added.
+             * @param {ViewConfig[]} view - The view to select from.
+             * @param {string} active - The id of the currently active view.
+             * @param {function} view_changer - The callback which initiates
+             *     the reset of the map in the new view.
+             */
+            _add_view_options: function (menu, views, active,
+                view_changer) {
+                const click = function (e) {
+                    logger.trace(
+                        "click on view_menu option", e);
                     var next = e.target.value;
-                    if(next !== active) {
+                    if (next !== active) {
                         view_changer(e.target.value);
                     }
                 }
-                for( const key of Object.keys(views)) {
-                    const name = views[key].name || key;
-                    const description = views[key].description || key;
-                    const checked = key === active;
-                    const option = $('<div title="'
-                        + description
-                        + '"><input name="view" type="radio" value="' 
-                        + key + '"/><label style="margin-left: 8px">' 
-                        + name + '</label></div>')
-                        .css({"width": "max-content"});
+                for (const key of Object.keys(views)) {
+                    const id = views[key].id;
+                    const name = views[key].name || id;
+                    const description = views[key]
+                        .description || name;
+                    const checked = id === active;
+                    const option = $('<div title="' +
+                            description +
+                            '"><input name="view" type="radio" value="' +
+                            id +
+                            '"/><label style="margin-left: 8px">' +
+                            name + '</label></div>')
+                        .css({
+                            "width": "max-content"
+                        });
                     option
                         .find(":input")
-                        .on("click", click) // TODO
+                        .on("click", click)
                         .prop("checked", checked)
                         .prop("name", "nextview");
                     $(menu).append(option);
                 }
             },
 
-            addHooks: function() {
+            /**
+             * Initialize the handler after it has been added to the map.
+             *
+             * This function is called by the map after the handler has been
+             * added via {@link L.Map.addHandler} or the handler added itself
+             * to the map via {@link L.Handler.addTo}.
+             *
+             * This method adds the menu button which opens the menu for
+             * selecting views.
+             */
+            addHooks: function () {
                 this._view_menu = this._make_view_menu();
                 const toggle_view_menu_button = this.
-                    _make_toggle_view_menu_button(this._view_menu);
+                _make_toggle_view_menu_button(this._view_menu);
 
                 const ChangeViewControl = L.Control.extend({
-                    options: {position: "bottomleft"},
-                    onAdd: function() {
+                    options: {
+                        position: "bottomleft"
+                    },
+                    onAdd: function () {
                         return toggle_view_menu_button;
                     }
                 })
 
-                this._map.addControl(new ChangeViewControl()); 
+                this._map.addControl(new ChangeViewControl());
             },
 
-            _make_view_menu: function() {
+            /**
+             * Return an empty menu, i.e. a form element where later on the
+             * view options will be added to.
+             *
+             * @returns {HTMLElement} A form element.
+             */
+            _make_view_menu: function () {
                 var form = $('<form/>')
                     .hide()
                     .css({ // TODO move to css
@@ -854,40 +1372,53 @@ var caosdb_map = new function() {
                         "background-color": "white",
                         "position": "absolute",
                         "bottom": "0px",
-                        "left": "34px", 
+                        "left": "34px",
                         "border": "1px solid black",
                         "border-radius": "4px",
                         "min-width": "100px"
                     });
 
-                form[0].addEventListener("click", function(e) {
-                    logger.trace("click on view menu", e);
+                form[0].addEventListener("click", function (
+                    e) {
+                    // just stop the click here, such that it does not change
+                    // anything else in the map's state.
+                    logger.trace("click on view menu",
+                        e);
                     e.stopPropagation();
-                    // TODO
-                    //caosdb_map.set_map_view(views[this.nextview]);
-                    //caosdb_map._active_view = this.nextview;
                 });
 
                 return form[0];
             },
 
-            _make_toggle_view_menu_button: function(view_menu) {
+            /**
+             * Return a button which toggles the view menu.
+             *
+             * @param {HTMLElement} the view menu which is to be toggled.
+             * @returns {HTMLElement} the button.
+             */
+            _make_toggle_view_menu_button: function (view_menu) {
                 var click = (e) => {
                     e.stopPropagation();
-                    logger.trace("click on toggle_view_menu_button", e, view_menu);
+                    logger.trace(
+                        "click on toggle_view_menu_button",
+                        e, view_menu);
                     $(view_menu).toggle();
                 };
 
-                // TODO refactor and extract function for map controls
-                var button = L.DomUtil.create("div", "leaflet-bar leaflet-control leaflet-control-custom");
+                // TODO refactor and extract function for map controls and
+                // merge with similar code from the select_handler.
+                var button = L.DomUtil.create("div",
+                    "leaflet-bar leaflet-control leaflet-control-custom"
+                );
                 button.title = "Change the view";
                 // TODO move to css
                 button.style.backgroundColor = "white";
                 button.style.width = "34px";
                 button.style.height = "34px";
-                button.style.textAlign  = "center";
+                button.style.textAlign = "center";
                 button.style.marginTop = "2px";
-                button.innerHTML = '<span style="margin-top: 5px; font-size: 15px" class="glyphicon glyphicon-option-vertical"></span>';
+                button.innerHTML =
+                    '<span style="margin-top: 5px; font-size: 15px" class="glyphicon glyphicon-option-vertical"></span>';
 
                 button.addEventListener("click", click);
                 $(button).prepend(view_menu);
@@ -895,7 +1426,13 @@ var caosdb_map = new function() {
                 return button;
             },
 
-            removeHooks: function() {
+            /**
+             * Clean up after the view change handler has been removed from the
+             * map.
+             *
+             * This method removes the menu button from the map.
+             */
+            removeHooks: function () {
                 $(this._toggle_view_menu_button).remove();
             },
         }
@@ -904,212 +1441,451 @@ var caosdb_map = new function() {
         /**
          * Plug-in for leaflet which lets the user select an area in the map
          * and execute a query using a latitude/longitude filter for entities.
+         *
+         * This handler adds a select button as a control to the map. The
+         * select button toggles and indicates the select mode (on/off).
+         *
+         * The selection can be started by either clicking on the map when the
+         * select mode is on (after enabling it via the select button).
+         *
+         * The query button apears as soon as an selection has being finished.
+         *
+         * The query button generates a query from for searching inside the
+         * selected area, opens the query panel and fills the generated query
+         * into the query panel's text field.
          */
         this.select_handler = {
-            addHooks: function() {
-                const select_button = this._get_select_button((event)=>{
-                    logger.trace("select button clicked", this);
+
+
+            /**
+             * Initialize the handler after it has been added to the map.
+             *
+             * This function is called by the map after the handler has been
+             * added via {@link L.Map.addHandler} or the handler added itself
+             * to the map via {@link L.Handler.addTo}.
+             *
+             * This method
+             * 1) Adds the select button.
+             * 2) Adds a listener for the `mousedown` event.
+             *
+             * Both are means to start the process of selecting an area in the
+             * map.
+             */
+            addHooks: function () {
+                const select_button = this._get_select_button((
+                    event) => {
+                    logger.trace(
+                        "select button clicked",
+                        this);
                     event.preventDefault();
                     event.stopPropagation();
-                    this._toggle_select();
+                    this._toggle_select_mode();
                 });
                 this._select_button = select_button;
-                this._map.addControl(select_button); 
-                this._map.on('mousedown', this._startSelect);
+                this._map.addControl(select_button);
+                this._map.on('mousedown', this
+                    ._mousedown_listener);
             },
 
-            removeHooks: function() {
-                // TODO remove button
-                this._map.off('mousedown', this._startSelect);
+            /**
+             * Clean up after the select handler has been removed from the map.
+             *
+             * This method removes the select button and the `mousedown`
+             * listener.
+             */
+            removeHooks: function () {
+                this._select_button.remove();
+                this._map.off('mousedown', this
+                    ._mousedown_listener);
             },
 
-            _highlight_select_button: function() {
-                $(this._select_button.button).css({backgroundColor: "#90EE90"});
+            /**
+             * Change the color of the select button in order to highlight it.
+             *
+             * This is used to indicate that the select mode is on and that the
+             * user can select something by clicking and moving the mouse on
+             * the map.
+             */
+            _highlight_select_button: function () {
+                $(this._select_button.button).css({
+                    backgroundColor: "#90EE90"
+                });
             },
 
-            _unhighlight_select_button: function() {
-                $(this._select_button.button).css({backgroundColor: "white"});
+
+            /**
+             * Change the color of the select button back to normal.
+             */
+            _unhighlight_select_button: function () {
+                $(this._select_button.button).css({
+                    backgroundColor: "white"
+                });
             },
 
-            _toggle_select: function() {
-                logger.trace("toggle select", this);
-                if(this._select_active) {
+            /**
+             * Toggle the select mode (on/off).
+             *
+             * This includes setting the _select_mode_on to true/false,
+             * highlighting/unhighlighting the select button and
+             * disabling/enabling the moving of the map center by dragging.
+             */
+            _toggle_select_mode: function () {
+                logger.trace("toggle select mode", this);
+                if (this._select_mode_on) {
                     this._unhighlight_select_button();
                     this._map.dragging.enable();
-                    this._select_active = false;
+                    this._select_mode_on = false;
                 } else {
                     this._highlight_select_button();
                     this._map.dragging.disable();
-                    this._select_active = true;
+                    this._select_mode_on = true;
                 }
             },
 
-            _get_select_button: function(callback) {
+            /**
+             * Return a button for toggling and indicating the select mode.
+             *
+             * The select button shows a litte dashed square as its icon.
+             *
+             * @param {function} callback - a callback which toggles the select
+             *     mode.
+             * @returns {L.Control} the select button.
+             */
+            _get_select_button: function (callback) {
+
+                // TODO flatten the structure of the code and possibly merge it with the query_button code.
                 var select_button = L.Control.extend({
-                    options: {position: "topleft"},
+                    options: {
+                        position: "topleft"
+                    },
 
-                    onAdd: function(m) {
+                    onAdd: function (m) {
                         return this.button;
                     },
 
-                    button: function() {
+                    button: function () {
                         // TODO refactor to make_map_control function
-                        var button = L.DomUtil.create("div", "leaflet-bar leaflet-control leaflet-control-custom");
-                        button.title = "Select an area";
-                        button.style.backgroundColor = "white";
-                        button.style.width = "34px";
-                        button.style.height = "34px";
-                        button.style.textAlign  = "center";
+                        var button = L.DomUtil
+                            .create("div",
+                                "leaflet-bar leaflet-control leaflet-control-custom"
+                            );
+                        button.title =
+                            "Select an area";
+                        button.style
+                            .backgroundColor =
+                            "white";
+                        button.style.width =
+                            "34px";
+                        button.style.height =
+                            "34px";
+                        button.style.textAlign =
+                            "center";
                         // Distance to zoom buttons:
-                        button.style.marginTop = "10px";
+                        button.style.marginTop =
+                            "10px";
                         // TODO implement helper for pictures
-                        button.innerHTML = '<img width="20px" height="20px" style="margin-top: 5px;" src="/webinterface/${BUILD_NUMBER}/pics/select.svg.png">';
+                        button.innerHTML =
+                            '<img width="20px" height="20px" style="margin-top: 5px;" src="/webinterface/${BUILD_NUMBER}/pics/select.svg.png">';
                         button.onclick = callback;
 
-                        $(button).on("mousedown", (event) => { event.stopPropagation(); });
-                        $(button).on("mouseup", (event) => { event.stopPropagation(); });
+                        $(button).on("mousedown", (
+                            event) => {
+                            event
+                                .stopPropagation();
+                        });
+                        $(button).on("mouseup", (
+                            event) => {
+                            event
+                                .stopPropagation();
+                        });
                         return button;
                     }(),
                 });
                 return new select_button();
             },
 
-            _get_query_button: function(callback){
+            /**
+             * Return a button for opening the query panel with a pre-filled query.
+             *
+             * The query button has a loupe icon.
+             *
+             * The query button is added after an area has been selected and
+             * only visible as long an area is selected.
+             *
+             * @param {function} callback - a callback for opening the query
+             *     panel and fill in the query.
+             * @returns {L.Control} the query button.
+             */
+            _get_query_button: function (callback) {
+
+                // TODO flatten the structure of the code and possibly merge it with the select_button code.
                 var query_button = L.Control.extend({
-                    options: {position: "topleft"},
+                    options: {
+                        position: "topleft"
+                    },
 
-                    onAdd: function(m) {
+                    onAdd: function (m) {
                         return this.button;
                     },
 
-                    button: function() {
-                        var button = L.DomUtil.create("div", "leaflet-bar leaflet-control leaflet-control-custom");
-                        button.title = "Search within this area";
-                        button.style.backgroundColor = "#ff8700";
-                        button.style.width = "34px";
-                        button.style.height = "34px";
-                        button.style.textAlign  = "center";
-                        button.style.marginTop = "2px";
-                        button.innerHTML = '<span style="margin-top: 5px; font-size: 15px" class="glyphicon glyphicon-search"></span>';
+                    button: function () {
+                        var button = L.DomUtil
+                            .create("div",
+                                "leaflet-bar leaflet-control leaflet-control-custom"
+                            );
+                        button.title =
+                            "Search within this area";
+                        button.style
+                            .backgroundColor =
+                            "#ff8700";
+                        button.style.width =
+                            "34px";
+                        button.style.height =
+                            "34px";
+                        button.style.textAlign =
+                            "center";
+                        button.style.marginTop =
+                            "2px";
+                        button.innerHTML =
+                            '<span style="margin-top: 5px; font-size: 15px" class="glyphicon glyphicon-search"></span>';
                         button.onclick = callback;
 
-                        $(button).on("mousedown", (event) => { event.stopPropagation(); });
-                        $(button).on("mouseup", (event) => { event.stopPropagation(); });
+                        $(button).on("mousedown", (
+                            event) => {
+                            event
+                                .stopPropagation();
+                        });
+                        $(button).on("mouseup", (
+                            event) => {
+                            event
+                                .stopPropagation();
+                        });
                         return button;
                     }(),
                 });
                 return new query_button();
             },
 
-            _startSelect: function(event) {
+
+            /**
+             * Listens on the mousedown event of the map and calls the
+             * _startSelect method if either (1) the select mode is on or (2)
+             * the shift key is pressed during the click on the map.
+             */
+            _mousedown_listener: function (event) {
                 logger.trace("mousedown", event, "on", this);
-                if(!event.originalEvent.shiftKey && !this.select._select_active) {
+                if (!event.originalEvent.shiftKey && !this
+                    .select._select_mode_on) {
                     return;
                 }
                 event.originalEvent.preventDefault();
                 event.originalEvent.stopPropagation();
-                this.select._reset_selection();
-                this.select._point1 = event.latlng;
-                this.select._point2 = undefined;
-                logger.trace("point1", this.select._point1);
+                this.select._startSelect(event.latlng);
+            },
+
+            /**
+             * Remove a pre-existing selection and start the process of a new
+             * selection.
+             *
+             * When the user clicks on the map with shift key pressed or
+             * _select_mode this method is called with the coordinates of the
+             * click.
+             *
+             * This also adds listeners on `mousemove` events (for redrawing
+             * the selected area) and on `mouseup` events for finishing the
+             * process of selection.
+             *
+             * @param {L.LatLng} start_point - the coordinates where the
+             *     selection begins.
+             */
+            _startSelect: function (start_point) {
+                this._reset_selection();
+                this._point1 = start_point;
+                logger.trace("point1", this._point1);
 
-                this.on("mousemove", this.select._drawRect);
-                this.on("mouseup", this.select._endSelect);
+                this._map.on("mousemove", this._drawRect);
+                this._map.on("mouseup", this._endSelect);
             },
 
-            _reset_selection: function() {
+            /**
+             * If present, remove the selected area and the query button from
+             * the map.
+             */
+            _reset_selection: function () {
+                this._point1 = undefined;
                 if (this._rectangle) {
                     this._rectangle.remove();
                 }
                 this._remove_query_button();
             },
 
-            _drawRect: function(event) {
+            /**
+             * (Re-)draw the rectangle which indicates the currently selected
+             * area.
+             *
+             * This method is added as a listener on the `mousemove` event by
+             * the _startSelect method.
+             */
+            _drawRect: function (event) {
                 logger.trace("mousemove", event, "on", this);
                 event.originalEvent.preventDefault();
                 event.originalEvent.stopPropagation();
+
+                // remove old rectangle
                 if (this.select._rectangle) {
                     this.select._rectangle.remove();
                 }
-                this.select._point2 = event.latlng;
-                const area = this.select._get_area(this.select._point1, this.select._point2)
-                this.select._rectangle = this.select._get_select_rectangle(area);
+
+                // draw new rectangle
+                const point2 = event.latlng;
+                const area = this.select._get_area(this.select
+                    ._point1, point2)
+                this.select._rectangle = this.select
+                    ._get_select_rectangle(area);
                 this.select._rectangle.addTo(this);
             },
 
-            _get_select_rectangle: function(area) {
-                return L.rectangle(area, {color: "#ff7800", weight: 1});
 
+            /**
+             * Return a colored rectangle covering an area.
+             *
+             * @param {L.LatLngBounds} area.
+             * @returns {L.Rectangle} the colored rectangle.
+             */
+            _get_select_rectangle: function (area) {
+                return L.rectangle(area, {
+                    color: "#ff7800",
+                    weight: 1
+                });
             },
 
-            _endSelect: function(event) {
+            /**
+             * Finish the process of selection, add the query button to the map.
+             *
+             * This method is a listener on the `mouseup` event and is added by
+             * the _startSelect method.
+             *
+             * It removes itself as a listener and also the _drawRect listener
+             * on the `mousemove` event.
+             */
+            _endSelect: function (event) {
                 logger.trace("mouseup", event, "on", this);
                 event.originalEvent.preventDefault();
                 event.originalEvent.stopPropagation();
-                if(this.select._point2) {
-                    // was moved, update last time
-                    this.select._point2 = event.latlng;
-                    logger.trace("point1", this.select._point1);
-                    logger.trace("point2", this.select._point2);
 
-                    const area = this.select._get_area(this.select._point1, this.select._point2)
+                const point2 = event.latlng;
+                logger.trace("point1", this.select._point1);
+                logger.trace("point2", point2);
+
+                const area = this.select._get_area(this.select
+                    ._point1, point2)
+
+                this.select._add_query_button(area);
 
-                    var query_filter = this.select._generate_query_filter(area);
-                    logger.debug("generated query filter", query_filter);
-                    this.select._add_query_button(query_filter);
-                } else {
-                    // mouse has not been moved, was a click
-                }
                 this.select._point1 = undefined;
-                this.select._point2 = undefined;
                 this.off("mouseup", this.select._endSelect);
                 this.off("mousemove", this.select._drawRect);
-
             },
 
-            _add_query_button: function(query_filter) {
-                logger.trace("_add_query_button", query_filter, this);
+            /**
+             * Add a `query` button to the map (showing a loupe icon) which
+             * opens the query panel with a pre-filled query.
+             *
+             * The generated query searches for entities inside the selected
+             * area `a`.
+             *
+             * A pre-existing query button is removed and the new query button
+             * is stored into this._query_button for later references.
+             *
+             * @param {L.LatLngBounds} a - the selected area.
+             * @return {L.Control} the new query button.
+             */
+            _add_query_button: function (a) {
+                logger.trace("_add_query_button", a, this);
+
+                // remove older query button
                 this._remove_query_button();
-                var callback = (event) => {
-                    logger.trace("click query_button", this);
+
+                const north = this._round(a.getNorth());
+                const south = this._round(a.getSouth());
+                const east = this._round(a.getEast());
+                const west = this._round(a.getWest());
+                const query = caosdb_map
+                    .generate_query_from_bounds(north, south,
+                        west, east)
+
+
+                // generate a call-back which opens the query panel with the
+                // generated query
+                const callback = (event) => {
+                    logger.trace("click query_button",
+                        this);
                     event.stopPropagation();
-                    caosdb_map.open_query_panel();
-                    caosdb_map.fill_query_filter(query_filter);
-                }
-                this._query_button = this._get_query_button(callback);
+                    caosdb_map.open_query_panel(query);
+                };
+
+                this._query_button = this._get_query_button(
+                    callback);
                 this._map.addControl(this._query_button);
+                return this._query_button;
             },
 
-            _remove_query_button: function() {
-                if(this._query_button) {
-                    this._query_button.remove();
+            /**
+             * Remove a `query` button if present.
+             *
+             * @return {L.Control} the old query button if present, `undefined`
+             *     otherwise.
+             */
+            _remove_query_button: function () {
+                const old = this._query_button;
+                if (old) {
+                    old.remove();
                     this._query_button = undefined;
+                    return old;
                 }
             },
 
-            _get_area: function(point1, point2) {
+            /**
+             * Return the area specified by two coordinates.
+             *
+             * The edges are parallel to the latitude and longitude of the two
+             * points.
+             *
+             * @param {L.LatLng} point1
+             * @param {L.LatLng} point2
+             * @returns {L.LatLngBounds} the area.
+             */
+            _get_area: function (point1, point2) {
                 return L.latLngBounds(point1, point2);
             },
 
             /**
-             * @param {LatLngBounds} a - the area.
+             * Round the double value to 3 decimal places.
+             *
+             * Note: This function is used to round map coordinates to
+             * meaningful values.
+             *
+             * @param {number} d - a double value
+             * @returns {number} a rounded double value.
              */
-            _generate_query_filter: function (a) {
-                let lat = caosdb_map.config.datamodel.lat;
-                let lng = caosdb_map.config.datamodel.lng;
-                let no = this._round(a.getNorth());
-                let so = this._round(a.getSouth());
-                let ea = this._round(a.getEast());
-                let we = this._round(a.getWest());
-                return " ( " + lat + " < '" + no + "' AND " + lat + " > '" + so + "' AND " + lng + " > '" + we + "' AND " + lng + " < '" + ea + "' ) ";
-            },
-
-            _round: function(d) {
-                return Math.round(d*1000)/1000;
+            _round: function (d) {
+                return Math.round(d * 1000) / 1000;
             },
 
+            /**
+             * The first point of the selected area.
+             *
+             * It is stored by the _startSelect method is used by subsequent
+             * execution of the _drawRect method and eventually the _endSelect
+             * method.
+             */
             _point1: undefined,
-            _point2: undefined,
+
+            /**
+             * _select_mode_on indicates whether clicks on the map without
+             * pressing shift will start/end/reset selection.
+             */
+            _select_mode_on: false,
         };
 
         logger.trace("leave _init_functions");
@@ -1117,6 +1893,6 @@ var caosdb_map = new function() {
     this._init_functions();
 }
 
-$(document).ready(function() {
+$(document).ready(function () {
     caosdb_modules.register(caosdb_map);
 });
diff --git a/src/core/js/ext_references.js b/src/core/js/ext_references.js
index 0e87d0ddb07c3a2db18aad6efe66e2551cde984e..68a52a023e49d8e9f092883817ad82b1da1c87cd 100644
--- a/src/core/js/ext_references.js
+++ b/src/core/js/ext_references.js
@@ -136,6 +136,11 @@ var resolve_references = new function () {
         return undefined;
     }
 
+    const _stripe_re = /Stripe$/i;
+    this.isStripe = function(el) {
+        return _stripe_re.test(el.name)
+    }
+
 
     /*
      * Function that retrieves meaningful information for a single element.
@@ -167,7 +172,7 @@ var resolve_references = new function () {
             var persel = await retrieve(getProperty(el[0], "Borrower"));
             var loan_state = awi_demo.get_loan_state_string(getProperties(el[0]));
             rseditable.textContent = "Borrowed by " + this.get_person_str(persel[0]) + " (" + loan_state.replace("_", " ") + ")";
-        } else if (pr[0].name === "ArchiveStripe") { // TODO *Stripe, Bag, *Sample
+        } else if (pr[0].name === "SubSample" || this.isStripe(pr[0])) {
             var bag = await this.find_bag_of_sample(el[0]);
             if (!bag) {
                 var icecore = await this.find_ice_core_of_sample(el[0]);
@@ -181,12 +186,18 @@ var resolve_references = new function () {
                 if (!icecore) {
                     rseditable.textContent = `${id} (Bag ${getProperty(bag, "Number", false)}, no Ice Core)`;
                 } else {
-                    rseditable.textContent = `${id} (Bag ${getProperty(bag, "Number", false)}, Ice Core ${getEntityName(icecore)})`;
+                    rseditable.textContent = `${id} (Ice Core ${getEntityName(icecore)}, Bag ${getProperty(bag, "Number", false)})`;
                 }
             }
-        } else if (pr[0].name === "Box") {
-            rseditable.textContent = getProperty(el[0], "Number");
         } else if (pr[0].name === "Bag") {
+            var bag = el[0];
+            var icecore = (await query("SELECT name FROM IceCore WHICH REFERENCES " + getEntityID(bag)))[0];
+            if (!icecore) {
+                rseditable.textContent = `${id} (Number ${getProperty(bag, "Number", false)}, no Ice Core)`;
+            } else {
+                rseditable.textContent = `${id} (Ice Core ${getEntityName(icecore)}, Number ${getProperty(bag, "Number", false)})`;
+            }
+        } else if (pr[0].name === "Box") {
             rseditable.textContent = getProperty(el[0], "Number");
         } else if (pr[0].name === "Palette") {
             rseditable.textContent = getProperty(el[0], "Number");
diff --git a/src/core/js/form_elements.js b/src/core/js/form_elements.js
index cb08ac7083b352883fa4bafb806711eccb850b27..3c4b195410abc8b58571c99bd4b1673f46368594 100644
--- a/src/core/js/form_elements.js
+++ b/src/core/js/form_elements.js
@@ -63,7 +63,7 @@
  * TODO
  *
  */
-var form_elements = new function() {
+var form_elements = new function () {
 
     this.version = "0.1";
     this.dependencies = ["log", "caosdb_utils", "markdown"];
@@ -80,13 +80,13 @@ var form_elements = new function() {
     this.form_error_event = new Event("caosdb.form.error");
 
 
-    this.get_cache_key = function(form, field) {
+    this.get_cache_key = function (form, field) {
         var form_key = $(form).prop("name");
         var field_key = $(field).attr("data-field-name");
-        return "form_elements." + form_key + "." + field_key;
+        return "form_elements.cache." + form_key + "." + field_key;
     }
 
-    this.get_cache_value = function(field) {
+    this.get_cache_value = function (field) {
         var ret = $(field)
             .find(":input")
             .val();
@@ -97,11 +97,11 @@ var form_elements = new function() {
         }
     }
 
-    this.cache_form = function(cache, form) {
+    this.cache_form = function (cache, form) {
         this.logger.trace("enter cache_form", cache, form);
         $(form)
             .find(".caosdb-f-field.caosdb-f-form-field-cached")
-            .each(function(index, field) {
+            .each(function (index, field) {
                 var value = form_elements.get_cache_value(field);
                 const key = form_elements.get_cache_key(form, field);
                 if (value !== null) {
@@ -114,7 +114,7 @@ var form_elements = new function() {
     }
 
 
-    this.set_cached = function(field) {
+    this.set_cached = function (field) {
         $(field).toggleClass("caosdb-f-form-field-cached", true);
     }
 
@@ -126,9 +126,9 @@ var form_elements = new function() {
         await form_elements.field_ready(field);
         const old_value = form_elements.get_cache_value(field);
 
-        if(old_value !== value) {
+        if (old_value !== value) {
             form_elements.logger.trace("loaded from cache", field, value);
-            if(typeof $().selectpicker === "function" &&
+            if (typeof $().selectpicker === "function" &&
                 $(field).find(".selectpicker").length > 0) {
                 $(field).find(".selectpicker").selectpicker("val", value);
             } else {
@@ -138,16 +138,16 @@ var form_elements = new function() {
         }
     }
 
-    this.is_set = function(field) {
+    this.is_set = function (field) {
         var value = $(field).find(":input").val();
         return value && value.length > 0;
     }
 
-    this.load_cached = function(cache, form) {
+    this.load_cached = function (cache, form) {
         this.logger.trace("enter load_cached", cache, form);
         $(form)
             .find(".caosdb-f-field.caosdb-f-form-field-cached")
-            .each(function(index, field) {
+            .each(function (index, field) {
                 var key = form_elements.get_cache_key(form, field);
                 var value = cache[key] || null;
                 if (value !== null) {
@@ -164,9 +164,9 @@ var form_elements = new function() {
     /**
      * (Re-)set this module's functions to standard implementation.
      */
-    this._init_functions = function() {
+    this._init_functions = function () {
 
-        this.init = function() {
+        this.init = function () {
             this.logger.trace("enter init");
         }
 
@@ -183,13 +183,13 @@ var form_elements = new function() {
          * @param {string} [desc] - the description for the entity.
          * @returns {HTMLElement} OPTION element.
          */
-        this.make_reference_option = function(entity_id, desc) {
+        this.make_reference_option = function (entity_id, desc) {
             caosdb_utils.assert_string(entity_id, "param `entity_id`");
-            if(typeof desc == "undefined") {
+            if (typeof desc == "undefined") {
                 desc = entity_id;
             }
-            var opt_str = '<option value="' + entity_id + '">' + desc
-                + "</option>";
+            var opt_str = '<option value="' + entity_id + '">' + desc +
+                "</option>";
             return $(opt_str)[0];
         }
 
@@ -205,9 +205,9 @@ var form_elements = new function() {
          *      parameter.
          * @returns {HTMLElement} SELECT element with entity options.
          */
-        this.make_reference_select = async function(entities, make_desc, multiple=false) {
+        this.make_reference_select = async function (entities, make_desc, multiple = false) {
             caosdb_utils.assert_array(entities, "param `entities`", false);
-            if ( typeof make_desc !== "undefined" ) {
+            if (typeof make_desc !== "undefined") {
                 caosdb_utils.assert_type(make_desc, "function",
                     "param `make_desc`");
             }
@@ -217,7 +217,7 @@ var form_elements = new function() {
             } else {
                 ret.append('<option style="display: none" selected="selected" value="" disabled="disabled"></option>');
             }
-            for ( let entity of entities ) {
+            for (let entity of entities) {
                 this.logger.trace("add option", entity);
                 let entity_id = getEntityID(entity);
                 let desc = typeof make_desc == "function" ? await make_desc(entity) :
@@ -238,15 +238,17 @@ var form_elements = new function() {
          *
          * @param {object} config
          * @returns {HTMLElement} SELECT element.
+         *
+         * TODO make syncronous
          */
-        this.make_reference_drop_down = async function(config) {
+        this.make_reference_drop_down = async function (config) {
             let ret = $(this._make_field_wrapper(config.name));
             let label = this._make_input_label_str(config);
             let loading = $('<div class="caosdb-f-field-not-ready">loading...</div>');
             let input_col = $('<div class="col-sm-9"/>');
 
             input_col.append(loading);
-            this._query(config.query).then(async function(entities){
+            this._query(config.query).then(async function (entities) {
                 let select = $(await form_elements.make_reference_select(
                     entities, config.make_desc, config.multiple));
                 select.attr("name", config.name);
@@ -254,7 +256,7 @@ var form_elements = new function() {
                 input_col.append(select);
                 form_elements.init_select_picker(ret[0]);
                 ret[0].dispatchEvent(form_elements.field_ready_event);
-                select.change(function() {
+                select.change(function () {
                     ret[0].dispatchEvent(form_elements.field_changed_event);
                 });
             }).catch(err => {
@@ -268,7 +270,7 @@ var form_elements = new function() {
         }
 
 
-        this.init_select_picker = function(field) {
+        this.init_select_picker = function (field) {
             caosdb_utils.assert_html_element(field, "parameter `field`");
             const select = $(field).find("select")[0];
             const select_picker_options = {};
@@ -285,12 +287,12 @@ var form_elements = new function() {
         }
 
 
-        this.init_actions_box = function(field) {
+        this.init_actions_box = function (field) {
             this.logger.trace("enter init_actions_box", field);
             caosdb_utils.assert_html_element(field, "parameter `field`");
             const select = $(field).find("select");
             var actions_box = select.siblings().find(".bs-actionsbox");
-            if(actions_box.length === 0) {
+            if (actions_box.length === 0) {
                 actions_box = $(`<div class="bs-actionsbox">
                             <div class="btn-group btn-group-sm btn-block">
                                 <button type="button" class="actions-btn btn-default bs-deselect-all btn btn-light">None</button>
@@ -305,7 +307,7 @@ var form_elements = new function() {
                 field.addEventListener(
                     form_elements.field_changed_event.type,
                     (e) => {
-                        if(form_elements.is_set(field)) {
+                        if (form_elements.is_set(field)) {
                             actions_box.show();
                         } else {
                             actions_box.hide();
@@ -314,7 +316,7 @@ var form_elements = new function() {
 
                 actions_box
                     .find(".bs-deselect-all")
-                    .click((e)=>{
+                    .click((e) => {
                         select.val(null)
                             .selectpicker("render")
                             .parent().toggleClass("open", false);
@@ -333,13 +335,13 @@ var form_elements = new function() {
          * @param {HTMLElement} field
          * @return {Promise} the field-ready promise
          */
-        this.field_ready = function(field) {
+        this.field_ready = function (field) {
             // TODO add support for field name (string) as field parameter
             // TODO check type of param field (not an array!)
             caosdb_utils.assert_html_element(field, "parameter `field`");
-            return new Promise(function(resolve, reject) {
+            return new Promise(function (resolve, reject) {
                 try {
-                    if(!$(field).hasClass("caosdb-f-field-not-ready") && $(field).find(".caosdb-f-field-not-ready").length === 0) {
+                    if (!$(field).hasClass("caosdb-f-field-not-ready") && $(field).find(".caosdb-f-field-not-ready").length === 0) {
                         resolve(field);
                     } else {
                         field.addEventListener(form_elements.field_ready_event.type,
@@ -359,13 +361,20 @@ var form_elements = new function() {
 
         this._run_script = async function (script, form) {
             const json_str = JSON.stringify(form_elements.form_to_object(form[0]));
-            const params = {"-p0": {"filename": "form.json", "blob": new Blob([json_str], {type: "application/json"})}};
+            const params = {
+                "-p0": {
+                    "filename": "form.json",
+                    "blob": new Blob([json_str], {
+                        type: "application/json"
+                    })
+                }
+            };
             const result = await connection.runScript(script, params);
             this.logger.debug("server-side script returned", result);
             return this.parse_script_result(result);
         }
 
-        this.parse_script_result = function(result) {
+        this.parse_script_result = function (result) {
             console.log(result);
             const scriptNode = result.evaluate("/Response/script", result, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
             const code = result.evaluate("@code", scriptNode, null, XPathResult.STRING_TYPE, null).stringValue;
@@ -374,13 +383,18 @@ var form_elements = new function() {
             const stderr = result.evaluate("stderr", scriptNode, null, XPathResult.STRING_TYPE, null).stringValue;
             const stdout = result.evaluate("stdout", scriptNode, null, XPathResult.STRING_TYPE, null).stringValue;
 
-            return { "code": code, "call": call, "stdout": stdout, "stderr": stderr };
+            return {
+                "code": code,
+                "call": call,
+                "stdout": stdout,
+                "stderr": stderr
+            };
         }
 
         /**
          * generate a java script object representation of a form
          */
-        this.form_to_object = function(form) {
+        this.form_to_object = function (form) {
             this.logger.trace("entity form_to_json", form);
             caosdb_utils.assert_html_element(form, "parameter `form`");
 
@@ -389,7 +403,7 @@ var form_elements = new function() {
 
                 for (const child of element.children) {
                     // ignore disabled fields and subforms
-                    if ($(child).hasClass("caosdb-f-field-disabled")){
+                    if ($(child).hasClass("caosdb-f-field-disabled")) {
                         continue;
                     }
                     const name = $(child).attr("name");
@@ -400,7 +414,7 @@ var form_elements = new function() {
                         var subform_obj = _to_json(child, {});
                         if (typeof data[subform] === "undefined") {
                             data[subform] = subform_obj;
-                        } else if (Array.isArray(data[subform])){
+                        } else if (Array.isArray(data[subform])) {
                             data[subform].push(subform_obj);
                         } else {
                             data[subform] = [data[subform], subform_obj]
@@ -408,12 +422,12 @@ var form_elements = new function() {
                     } else if (name && name !== "") {
                         // input elements
                         const not_checkbox = !$(child).is(":checkbox");
-                        if ( not_checkbox || $(child).is(":checked")) {
+                        if (not_checkbox || $(child).is(":checked")) {
                             // checked or not a checkbox
                             var value = $(child).val();
                             if (typeof data[name] === "undefined") {
                                 data[name] = value;
-                            } else if (Array.isArray(data[name])){
+                            } else if (Array.isArray(data[name])) {
                                 data[name].push(value);
                             } else {
                                 data[name] = [data[name], value]
@@ -436,12 +450,12 @@ var form_elements = new function() {
             return ret;
         }
 
-        this.make_submit_button = function() {
+        this.make_submit_button = function () {
             var ret = $('<button class="caosdb-f-form-elements-submit-button btn btn-primary" type="submit">Submit</button>');
             return ret[0];
         }
 
-        this.make_cancel_button = function(form) {
+        this.make_cancel_button = function (form) {
             var ret = $('<button class="caosdb-f-form-elements-cancel-button btn btn-primary" type="button">Cancel</button>');
             ret.on("click", e => {
                 this.logger.debug("cancel form", e, form);
@@ -450,22 +464,25 @@ var form_elements = new function() {
             return ret[0];
         }
 
-        this.make_form_field = async function(config) {
+        /**
+         * TODO make syncronous
+         */
+        this.make_form_field = async function (config) {
             caosdb_utils.assert_type(config, "object", "param `config`");
             caosdb_utils.assert_string(config.type, "`config.type` of param `config`");
 
             var field = undefined;
             const type = config.type;
             if (type === "date") {
-                field = await this.make_date_input(config);
+                field = this.make_date_input(config);
             } else if (type === "checkbox") {
-                field = await this.make_checkbox_input(config);
+                field = this.make_checkbox_input(config);
             } else if (type === "text") {
-                field = await this.make_text_input(config);
+                field = this.make_text_input(config);
             } else if (type === "double") {
-                field = await this.make_double_input(config);
+                field = this.make_double_input(config);
             } else if (type === "integer") {
-                field = await this.make_integer_input(config);
+                field = this.make_integer_input(config);
             } else if (type === "range") {
                 field = await this.make_range_input(config);
             } else if (type === "reference_drop_down") {
@@ -491,9 +508,11 @@ var form_elements = new function() {
         }
 
 
-        this.add_help = function(field, config) {
+        this.add_help = function (field, config) {
             var help_button = $('<span data-trigger="click focus" data-toggle="popover" class="caosdb-f-form-help pull-right glyphicon glyphicon-info-sign"/>')
-                .css({"cursor": "pointer"});
+                .css({
+                    "cursor": "pointer"
+                });
 
             if (typeof config === "string" || config instanceof String) {
                 help_button.attr("data-content", config);
@@ -504,16 +523,18 @@ var form_elements = new function() {
 
 
             var label = $(field).children("label");
-            if(label.length > 0) {
-                help_button.css({"margin-left": "4px"});
+            if (label.length > 0) {
+                help_button.css({
+                    "margin-left": "4px"
+                });
                 label.first().append(help_button);
             } else {
                 $(field).append(help_button);
             }
         }
 
-        this.make_heading = function(config) {
-            if(typeof config.header === "undefined") {
+        this.make_heading = function (config) {
+            if (typeof config.header === "undefined") {
                 return;
             } else if (typeof config.header === "string" || config.header instanceof String) {
                 return $('<div class="caosdb-f-form-header h3">' + config.header + '</div>')[0];
@@ -522,7 +543,7 @@ var form_elements = new function() {
             return config.header;
         }
 
-        this.make_form_wrapper = function(form, config) {
+        this.make_form_wrapper = function (form, config) {
             var wrapper = $('<div class="caosdb-f-form-wrapper"/>');
 
             var header = this.make_heading(config);
@@ -554,10 +575,10 @@ var form_elements = new function() {
             return wrapper[0];
         }
 
-        this.make_form = function(config) {
+        this.make_form = function (config) {
             var form = undefined;
 
-            if(config.script) {
+            if (config.script) {
                 form = this.make_script_form(config, config.script);
             } else {
                 form = this.make_generic_form(config);
@@ -566,7 +587,10 @@ var form_elements = new function() {
             return wrapper;
         }
 
-        this.make_subform = async function(config) {
+        /**
+         * TODO make syncronous
+         */
+        this.make_subform = async function (config) {
             this.logger.trace("enter make_subform");
             caosdb_utils.assert_type(config, "object", "param `config`");
             caosdb_utils.assert_string(config.name, "`config.name` of param `config`");
@@ -575,7 +599,7 @@ var form_elements = new function() {
             const name = config.name;
             var form = $('<div data-subform-name="' + name + '" class="caosdb-f-form-elements-subform"/>');
 
-            for ( let field of config.fields ) {
+            for (let field of config.fields) {
                 this.logger.trace("add subform field", field);
                 let elem = await this.make_form_field(field);
                 form.append(elem);
@@ -585,26 +609,26 @@ var form_elements = new function() {
             return form[0];
         }
 
-        this.dismiss_form = function(form) {
-            if(form.tagName === "FORM") {
+        this.dismiss_form = function (form) {
+            if (form.tagName === "FORM") {
                 form.dispatchEvent(this.cancel_form_event);
             }
             var _form = $(form).find("form");
-            if (_form.length > 0){
+            if (_form.length > 0) {
                 _form[0].dispatchEvent(this.cancel_form_event);
             }
         }
 
-        this.enable_group = function(form, group) {
+        this.enable_group = function (form, group) {
             this.enable_fields(this.get_group_fields(form, group));
         }
 
-        this.disable_group = function(form, group) {
+        this.disable_group = function (form, group) {
             this.disable_fields(this.get_group_fields(form, group));
         }
 
-        this.get_group_fields = function(form, group) {
-            return $(form).find(".caosdb-f-field[data-groups*='("+group+")']").toArray();
+        this.get_group_fields = function (form, group) {
+            return $(form).find(".caosdb-f-field[data-groups*='(" + group + ")']").toArray();
         }
 
         /**
@@ -613,44 +637,44 @@ var form_elements = new function() {
          * @param {string} name - the field name
          * @return {HTMLElement[]} array of fields
          */
-        this.get_fields = function(form, name) {
+        this.get_fields = function (form, name) {
             caosdb_utils.assert_html_element(form, "parameter `form`");
             caosdb_utils.assert_string(name, "parameter `name`");
             return $(form).find(".caosdb-f-field[data-field-name='" + name + "']").toArray();
         }
 
-        this.add_field_to_group = function(field, group) {
+        this.add_field_to_group = function (field, group) {
             this.logger.trace("enter add_field_to_group", field, group);
-            var groups = ($(field).attr("data-groups")?$(field).attr("data-groups"):"") + "(" + group + ")";
+            var groups = ($(field).attr("data-groups") ? $(field).attr("data-groups") : "") + "(" + group + ")";
             $(field).attr("data-groups", groups);
         }
 
-        this.disable_fields = function(fields) {
+        this.disable_fields = function (fields) {
             $(fields).toggleClass("caosdb-f-field-disabled", true).hide();
             for (const field of $(fields)) {
                 field.dispatchEvent(this.field_disabled_event);
             }
         }
 
-        this.enable_fields = function(fields) {
+        this.enable_fields = function (fields) {
             $(fields).toggleClass("caosdb-f-field-disabled", false).show();
             for (const field of $(fields)) {
                 field.dispatchEvent(this.field_enabled_event);
             }
         }
 
-        this.enable_name = function(form, name) {
+        this.enable_name = function (form, name) {
             this.enable_fields(form.find(".caosdb-f-field[data-field-name='" + name + "']").toArray());
         }
 
-        this.disable_name = function(form, name) {
+        this.disable_name = function (form, name) {
             this.disable_fields(form.find(".caosdb-f-field[data-field-name='" + name + "']").toArray());
         }
 
-        this.make_script_form = async function(config, script) {
+        this.make_script_form = async function (config, script) {
             this.logger.trace("enter make_script_form");
 
-            const submit_callback = async function(form) {
+            const submit_callback = async function (form) {
                 form = $(form);
 
 
@@ -672,7 +696,10 @@ var form_elements = new function() {
             };
 
             this.logger.trace("leave make_script_form");
-            const new_config = $.extend({}, {name: script, submit:submit_callback}, config);
+            const new_config = $.extend({}, {
+                name: script,
+                submit: submit_callback
+            }, config);
             return await this.make_generic_form(new_config);
         }
 
@@ -685,7 +712,7 @@ var form_elements = new function() {
          *
          * TODO
          */
-        this.make_generic_form = async function(config) {
+        this.make_generic_form = async function (config) {
             this.logger.trace("enter make_generic_form");
 
             caosdb_utils.assert_type(config, "object", "param `config`");
@@ -695,14 +722,14 @@ var form_elements = new function() {
             const form = $('<form class="form-horizontal" action="#" method="post" />');
 
             // set name
-            if(config.name) {
+            if (config.name) {
                 form.attr("name", config.name);
             }
 
             // add fields
-            for ( let field of config.fields ) {
+            for (let field of config.fields) {
                 this.logger.trace("add field", field);
-                if ( field instanceof HTMLElement ) {
+                if (field instanceof HTMLElement) {
                     form.append(field);
                 } else {
                     let elem = await this.make_form_field(field);
@@ -712,16 +739,16 @@ var form_elements = new function() {
 
             // set groups
             if (config.groups) {
-                for ( let group of config.groups ) {
+                for (let group of config.groups) {
                     this.logger.trace("add group", group);
-                    for ( let fieldname of group.fields ) {
+                    for (let fieldname of group.fields) {
                         let field = form.find(".caosdb-f-field[data-field-name='" + fieldname + "']");
                         this.logger.trace("set group", field, group);
                         this.add_field_to_group(field, group.name)
 
                     }
                     // disable if necessary
-                    if (typeof group.enabled === "undefined" || group.enabled ) {
+                    if (typeof group.enabled === "undefined" || group.enabled) {
                         this.enable_group(form, group.name);
                     } else {
                         this.disable_group(form, group.name);
@@ -732,14 +759,14 @@ var form_elements = new function() {
             const footer = this.make_footer();
             form.append(footer);
 
-            if(!(typeof config.submit === 'boolean' && config.submit === false)) {
+            if (!(typeof config.submit === 'boolean' && config.submit === false)) {
                 // add submit button unless config.submit is false
                 footer.append(this.make_submit_button());
             }
             form[0].addEventListener("submit", (e) => {
                 e.preventDefault();
                 e.stopPropagation();
-                if(form.find(".caosdb-f-form-submitting").length > 0) {
+                if (form.find(".caosdb-f-form-submitting").length > 0) {
                     // do not submit twice
                     return;
                 }
@@ -766,9 +793,9 @@ var form_elements = new function() {
                 const success_handler = config.success;
                 const submit_callback = config.submit;
                 form.find(".caosdb-f-form-elements-message").remove();
-                if(typeof config.submit === "function") {
+                if (typeof config.submit === "function") {
                     // wrap callback in async function
-                    const _wrap_callback = async function() {
+                    const _wrap_callback = async function () {
                         try {
                             var results = await submit_callback(form[0]);
 
@@ -805,12 +832,12 @@ var form_elements = new function() {
 
             }, true);
 
-            form[0].addEventListener(this.form_success_event.type, function(e) {
+            form[0].addEventListener(this.form_success_event.type, function (e) {
                 // remove submit button, show ok button
                 form.find("button[type='submit']").remove();
                 form.find("button:contains('Cancel')").text("Ok").prop("disabled", false);
             }, true);
-            form[0].addEventListener(this.form_error_event.type, function(e) {
+            form[0].addEventListener(this.form_error_event.type, function (e) {
                 // reenable inputs
                 form.find(":input").prop("disabled", false);
             }, true);
@@ -828,7 +855,7 @@ var form_elements = new function() {
             return form[0];
         }
 
-        this.init_form_caching = function(config, form) {
+        this.init_form_caching = function (config, form) {
             var default_config = {
                 "cache_event": form_elements.submit_form_event.type,
                 "cache_storage": localStorage
@@ -843,50 +870,65 @@ var form_elements = new function() {
             form_elements.load_cached(lconfig.cache_storage, form);
         }
 
-        this.show_results = function(form, results) {
+        this.show_results = function (form, results) {
             $(form).append(results);
         }
 
-        this.show_errors = function(form, errors) {
+        this.show_errors = function (form, errors) {
             $(form).append(errors);
         }
 
-        this.make_footer = function() {
-            return $('<div class="text-right caosdb-f-form-elements-footer"/>')[0];
+        this.make_footer = function () {
+            return $('<div class="text-right caosdb-f-form-elements-footer"/>')
+                .css({
+                    "margin": "20px",
+                }).append(this.make_required_marker())
+                .append('<span style="margin-right: 4px; font-size: 11px">required field</span>')[0];
         }
 
-        this.make_error_message = function(message) {
+        this.make_error_message = function (message) {
             return this.make_message(message, "error");
         }
 
-        this.make_success_message = function(message) {
+        this.make_success_message = function (message) {
             return this.make_message(message, "success");
         }
 
-        this.make_submitting_info = function() {
+        this.make_submitting_info = function () {
             // TODO styling
             return $(this.make_message("Submitting... please wait. This might take some time.", "info"))
-                .toggleClass("h3",true)
-                .toggleClass("caosdb-f-form-submitting",true)
-                .toggleClass("text-right",true)[0];
+                .toggleClass("h3", true)
+                .toggleClass("caosdb-f-form-submitting", true)
+                .toggleClass("text-right", true)[0];
         }
 
-        this.make_message = function(message, type) {
+        this.make_message = function (message, type) {
             var ret = $('<div class="caosdb-f-form-elements-message"/>');
-            if(type) {
+            if (type) {
                 ret.addClass("caosdb-f-form-elements-message-" + type);
             }
             return ret.append(markdown.textToHtml(message))[0];
         }
 
-        this.make_range_input = async function(config) {
+        /**
+         * TODO make syncronous
+         */
+        this.make_range_input = async function (config) {
 
             // TODO 
             // 1. wrapp both inputs to separate it from the label into a container
             // 2. make two rows for each input
             // 3. make inline-block for all included elements
-            const from_config = $.extend({}, {cached: config.cached, required: config.required, type: "double"}, config.from);
-            const to_config = $.extend({}, {cached: config.cached, required: config.required, type: "double"}, config.to);
+            const from_config = $.extend({}, {
+                cached: config.cached,
+                required: config.required,
+                type: "double"
+            }, config.from);
+            const to_config = $.extend({}, {
+                cached: config.cached,
+                required: config.required,
+                type: "double"
+            }, config.to);
 
             const from_input = await this.make_form_field(from_config);
             const to_input = await this.make_form_field(to_config);
@@ -919,16 +961,17 @@ var form_elements = new function() {
          * @param {string} name - the name of the field.
          * @returns {HTMLElement} a DIV.
          */
-        this._make_field_wrapper = function(name) {
+        this._make_field_wrapper = function (name) {
             caosdb_utils.assert_string(name, "param `name`");
-            return $('<div class="form-group caosdb-f-field caosdb-property-row" data-field-name="'+name+'" />')[0];
+            return $('<div class="form-group caosdb-f-field caosdb-property-row" data-field-name="' + name + '" />')
+                .css({"padding": "0"})[0];
         }
 
-        this.make_date_input = function(config) {
+        this.make_date_input = function (config) {
             return this._make_input(config);
         }
 
-        this.make_text_input = function(config) {
+        this.make_text_input = function (config) {
             return this._make_input(config);
         }
 
@@ -941,8 +984,10 @@ var form_elements = new function() {
          * @param {form_elements.input_config} config.
          * @returns {HTMLElement} a double form field.
          */
-        this.make_double_input = function(config) {
-            var clone = $.extend({}, config, {type: "number"});
+        this.make_double_input = function (config) {
+            var clone = $.extend({}, config, {
+                type: "number"
+            });
             var ret = $(this._make_input(clone))
             ret.find("input").attr("step", "any");
             return ret[0];
@@ -957,7 +1002,7 @@ var form_elements = new function() {
          * @param {form_elements.input_config} config.
          * @returns {HTMLElement} an integer form field.
          */
-        this.make_integer_input = function(config) {
+        this.make_integer_input = function (config) {
             var ret = $(this.make_double_input(config));
             ret.find("input").attr("step", "1");
             return ret[0];
@@ -970,16 +1015,18 @@ var form_elements = new function() {
          * @param {form_elements.checkbox_config} config.
          * @returns {HTMLElement} a checkbox form field.
          */
-        this.make_checkbox_input = function(config) {
-            var clone = $.extend({}, config, {type: "checkbox"});
+        this.make_checkbox_input = function (config) {
+            var clone = $.extend({}, config, {
+                type: "checkbox"
+            });
             var ret = $(this._make_input(clone));
             ret.find("input:checkbox").prop("checked", false);
             ret.find("input:checkbox").toggleClass("form-control", false);
-            if(config.checked ) {
+            if (config.checked) {
                 ret.find("input:checkbox").prop("checked", true);
                 ret.find("input:checkbox").attr("checked", "checked");
             }
-            if(config.value) {
+            if (config.value) {
                 ret.find("input:checkbox").attr("value", config.value);
             }
             return ret[0];
@@ -991,22 +1038,41 @@ var form_elements = new function() {
          *
          * @param {HTMLElement} field - the required form field.
          */
-        this.set_required = function(field) {
+        this.set_required = function (field) {
             $(field).toggleClass("caosdb-f-form-field-required", true);
+            $(field).find(":input").prop("required", true);
+            $(field).find("label").prepend(this.make_required_marker());
+        }
+
+        /**
+         * Return a span which is to be inserted before a field's label text
+         * and which marks that field as required.
+         *
+         * @returns {HTMLElement} span element.
+         */
+        this.make_required_marker = function () {
+            // TODO create class and move to css file
+            return $('<span>*</span>')
+                .css({
+                    "font-size": "10px",
+                    "color": "red",
+                    "margin-right": "4px",
+                    "font-weight": "100",
+                })[0];
         }
 
 
-        this.get_enabled_required_fields = function(form) {
+        this.get_enabled_required_fields = function (form) {
             return $(this.get_enabled_fields(form))
                 .filter(".caosdb-f-form-field-required")
                 .toArray();
         }
 
 
-        this.get_enabled_fields = function(form) {
+        this.get_enabled_fields = function (form) {
             return $(form)
                 .find(".caosdb-f-field")
-                .filter(function(idx){
+                .filter(function (idx) {
                     // remove disabled fields from results
                     return !$(this).hasClass("caosdb-f-field-disabled");
                 })
@@ -1014,9 +1080,9 @@ var form_elements = new function() {
         }
 
 
-        this.all_required_fields_set = function(form) {
+        this.all_required_fields_set = function (form) {
             const req = form_elements.get_enabled_required_fields(form);
-            for ( const field of req ) {
+            for (const field of req) {
                 if (!form_elements.is_set(field)) {
                     return false;
                 }
@@ -1027,16 +1093,16 @@ var form_elements = new function() {
         /**
          * @param {HTMLElement} form - the form be validated.
          */
-        this.is_valid = function(form) {
+        this.is_valid = function (form) {
             return form_elements.all_required_fields_set(form);
         }
 
 
-        this.toggle_submit_button_form_valid = function(form, submit) {
+        this.toggle_submit_button_form_valid = function (form, submit) {
             // TODO do not change the submit button directly. change the
             // `submittable` state of the form and handle the case where a form
             // is submitting when this function is called.
-            if(form_elements.is_valid(form)){
+            if (form_elements.is_valid(form)) {
                 $(submit).prop("disabled", false);
             } else {
                 $(submit).prop("disabled", true);
@@ -1044,9 +1110,9 @@ var form_elements = new function() {
         }
 
 
-        this.init_validator = function(form) {
+        this.init_validator = function (form) {
             const submit = $(form).find(":input[type='submit']")[0];
-            if(submit) {
+            if (submit) {
                 form.addEventListener("caosdb.field.changed", (e) => form_elements.toggle_submit_button_form_valid(form, submit), true);
                 form.addEventListener("caosdb.field.enabled", (e) => form_elements.toggle_submit_button_form_valid(form, submit), true);
                 form.addEventListener("input", (e) => form_elements.toggle_submit_button_form_valid(form, submit), true);
@@ -1062,17 +1128,17 @@ var form_elements = new function() {
          *      optional `label`
          * @returns {HTMLElement} a form field.
          */
-        this._make_input = function(config) {
+        this._make_input = function (config) {
             caosdb_utils.assert_string(config.name, "the name of a form field");
             let ret = $(this._make_field_wrapper(config.name));
             let name = config.name;
             let label = this._make_input_label_str(config);
             let type = config.type;
             let value = config.value;
-            let input = $('<input class="form-control caosdb-property-text-value" type="' + type
-                + '" name="' + name
-                + '" />');
-            input.change(function() {
+            let input = $('<input class="form-control caosdb-property-text-value" type="' + type +
+                '" name="' + name +
+                '" />');
+            input.change(function () {
                 ret[0].dispatchEvent(form_elements.field_changed_event);
             });
             let input_col = $('<div class="caosdb-property-value col-sm-9"/>');
@@ -1094,19 +1160,19 @@ var form_elements = new function() {
          * @param {object} config - a config object with `name` and `label`.
          * @returns {string} a html string for a LABEL element.
          */
-        this._make_input_label_str = function(config) {
+        this._make_input_label_str = function (config) {
             let name = config.name;
             let label = config.label;
-            return label ? '<label for="' + name
-                + '" data-property-name="' + name
-                + '" class="control-label col-sm-3">' + label
-                + '</label>' : "";
+            return label ? '<label for="' + name +
+                '" data-property-name="' + name +
+                '" class="control-label col-sm-3">' + label +
+                '</label>' : "";
         }
 
     }
     this._init_functions();
 }
 
-$(document).ready(function() {
+$(document).ready(function () {
     caosdb_modules.register(form_elements);
 });
diff --git a/src/core/js/query_shortcuts.js b/src/core/js/query_shortcuts.js
index 1b4765cebfd3d95faf2beb9b5e948fba4456de2b..c12e2b47b7c8b970b2a8177ebdb529dd5cb47efa 100644
--- a/src/core/js/query_shortcuts.js
+++ b/src/core/js/query_shortcuts.js
@@ -38,7 +38,7 @@
  * [
  *   {
  *     "description": "Find all geological data sets with temperature above {temp} K.",
- *     "query": "FIND Record Geodata with temperature > \"$1\""
+ *     "query": "FIND Record Geodata with temperature > \"{temp}\""
  *   }, {
  *     ...
  *   }
diff --git a/src/core/js/tour.js b/src/core/js/tour.js
index 9e4be98a80ea72fb2dd49d124b4b8536c5ecc8d6..d798eb558266179483c7be7ebf37f5f083e78a1c 100644
--- a/src/core/js/tour.js
+++ b/src/core/js/tour.js
@@ -242,7 +242,7 @@ var tour = new function() {
             if(this.config.deactivate_other) {
                 for (const element of this.elements) {
                     if(element instanceof tour.PageSet && element !== trigger) {
-                        element._deactivate();
+                        element.deactivate();
                     }
                 }
             }
@@ -565,17 +565,18 @@ var tour = new function() {
                 return;
             }
             const ids = Object.keys(highlighters)
+            var highlightable = {}
             for (const id of ids) {
-                var highlightable = $(highlighters[id]);
+                highlightable[id] = $(highlighters[id]);
                 if (id == "button") {
                     // special case, don't look for the highlighter in the content
-                    this._apply_highlighter(button, highlightable);
+                    this._apply_highlighter(button, highlightable[id]);
                 } else {
                     $(button).on('shown.bs.popover', (e) => {
                         console.log("Highlighting:")
                         console.log(highlighters[id]);
-                        console.log(highlightable);
-                        this._apply_highlighter("#" + id, highlightable);
+                        console.log(highlightable[id]);
+                        this._apply_highlighter("#" + id, highlightable[id]);
                     });
                 }
             }
@@ -900,7 +901,7 @@ var tour = new function() {
             if(this.config.deactivate_other) {
                 for (const element of this.elements) {
                     if(element instanceof tour.PageSet && element !== trigger) {
-                        element._deactivate();
+                        element.deactivate();
                     }
                 }
             }
diff --git a/src/core/js/webcaosdb.js b/src/core/js/webcaosdb.js
index a66e699605844d6b1160b25bb68913de9722354c..c213a0ebcd766a1d1c2502cc8aaae2fc014ff840 100644
--- a/src/core/js/webcaosdb.js
+++ b/src/core/js/webcaosdb.js
@@ -151,7 +151,7 @@ this.connection = new function() {
         this.get = async function _get(uri) {
             try {
                 return await $.ajax({
-                    url: window.sessionStorage.caosdbBasePath + uri,
+                    url: connection.getBasePath() + uri,
                     dataType: "xml",
                 });
             } catch (error) {
@@ -173,7 +173,7 @@ this.connection = new function() {
         this.put = async function _put(uri, data) {
             try {
                 return await $.ajax({
-                    url: window.sessionStorage.caosdbBasePath + uri,
+                    url: connection.getBasePath() + uri,
                     method: 'PUT',
                     dataType: "xml",
                     processData: false,
@@ -208,7 +208,7 @@ this.connection = new function() {
             }
             try {
                 return await $.ajax({
-                    url: window.sessionStorage.caosdbBasePath + "scripting",
+                    url: connection.getBasePath() + "scripting",
                     method: 'POST',
                     dataType: "xml",
                     contentType: false,
@@ -234,7 +234,7 @@ this.connection = new function() {
         this.post = async function _post(uri, data) {
             try {
                 return await $.ajax({
-                    url: window.sessionStorage.caosdbBasePath + uri,
+                    url: connection.getBasePath() + uri,
                     method: 'POST',
                     dataType: "xml",
                     processData: false,
@@ -259,7 +259,7 @@ this.connection = new function() {
         this.deleteEntities = async function _deleteEntities(idline) {
             try {
                 return await $.ajax({
-                    url: window.sessionStorage.caosdbBasePath + "Entity/" + idline,
+                    url: connection.getBasePath() + "Entity/" + idline,
                     method: 'DELETE',
                     dataType: "xml",
                     processData: false,
@@ -281,9 +281,11 @@ this.connection = new function() {
          * Return the base path of the server.
          */
         this.getBasePath = function() {
-            let a = document.createElement('a');
-            a.href = window.sessionStorage.caosdbBasePath;
-            return a.href;
+            var base = window.location.origin + "/";
+            if (typeof window.sessionStorage.caosdbBasePath !== "undefined") {
+                base = window.sessionStorage.caosdbBasePath;
+            }
+            return base;
         }
 
         /**
@@ -344,7 +346,7 @@ this.transformation = new function() {
      */
     this.transformParent = async function _tME(xml) {
         var xsl = await transformation.retrieveXsltScript("parent.xsl");
-        insertParam(xsl, "entitypath", window.sessionStorage.caosdbBasePath + "Entity/");
+        insertParam(xsl, "entitypath", connection.getBasePath() + "Entity/");
         // TODO the following line should not have any effect. nevertheless: it
         // does not work without
         xsl = str2xml(xml2str(xsl));
@@ -358,8 +360,8 @@ this.transformation = new function() {
      */
     this.transformProperty = async function _tME(xml) {
         var xsl = await transformation.retrieveXsltScript("property.xsl");
-        insertParam(xsl, "filesystempath", window.sessionStorage.caosdbBasePath + "FileSystem/");
-        insertParam(xsl, "entitypath", window.sessionStorage.caosdbBasePath + "Entity/");
+        insertParam(xsl, "filesystempath", connection.getBasePath() + "FileSystem/");
+        insertParam(xsl, "entitypath", connection.getBasePath() + "Entity/");
         insertParam(xsl, "close-char", '×');
         var entityXsl = await transformation.retrieveXsltScript('entity.xsl');
         var messageXsl = await transformation.retrieveXsltScript('messages.xsl');
@@ -389,8 +391,8 @@ this.transformation = new function() {
         var commonXsl = await transformation.retrieveXsltScript("common.xsl");
         var errorXsl = await transformation.retrieveXsltScript('messages.xsl');
         var xslt = transformation.mergeXsltScripts(entityXsl, [errorXsl, commonXsl]);
-        insertParam(xslt, "filesystempath", window.sessionStorage.caosdbBasePath + "FileSystem/");
-        insertParam(xslt, "entitypath", window.sessionStorage.caosdbBasePath + "Entity/");
+        insertParam(xslt, "filesystempath", connection.getBasePath() + "FileSystem/");
+        insertParam(xslt, "entitypath", connection.getBasePath() + "Entity/");
         insertParam(xslt, "close-char", '×');
         xslt = injectTemplate(xslt, '<xsl:template match="/"><div class="root"><xsl:apply-templates select="Response/*" mode="entities"/></div></xsl:template>');
         return xslt;
@@ -474,7 +476,9 @@ this.transaction = new function() {
 
     /**
      * Generate the URI for the retrieval of a list of entities.
-     * 
+     *
+     * TODO merge this with connection.getEntityUri
+     *
      * @param {String[]} entityIds - An array of entity ids..
      * @return {String} The uri.
      */
@@ -956,7 +960,7 @@ var queryForm = new function() {
         if(paging && paging.length > 0) {
             pagingparam = "P=" + paging + "&";
         }
-        location.href = window.sessionStorage.caosdbBasePath + "Entity/?" + pagingparam + "query=" + query;
+        location.href = connection.getBasePath() + "Entity/?" + pagingparam + "query=" + query;
     }
 
     this.bindOnClick = function(form, setter) {
@@ -1202,10 +1206,10 @@ function xml2str(xml) {
  * @return object containing the configuration
  */
 async function load_config(filename) {
-    var uri = connection.getBasePath() + "webinterface/conf/" + filename;
+    const uri = connection.getBasePath() + "webinterface/${BUILD_NUMBER}/conf/" + filename;
     try {
         var data = await $.ajax({
-            url: connection.getBasePath() + "webinterface/${BUILD_NUMBER}/conf/" + filename,
+            url: uri,
             dataType: "json",
         });
     } catch (error) {
diff --git a/src/core/pics/map_tile_caosdb_logo.png b/src/core/pics/map_tile_caosdb_logo.png
new file mode 100644
index 0000000000000000000000000000000000000000..10d930f630c4bd3b863be98d0c13e860686145d4
Binary files /dev/null and b/src/core/pics/map_tile_caosdb_logo.png differ
diff --git a/src/core/webcaosdb.xsl b/src/core/webcaosdb.xsl
index de4bdc9a5728da61799b05b23f5030ed25dfbc4a..ff12c424743d5b7cb9294f3284b22b147fb94942 100644
--- a/src/core/webcaosdb.xsl
+++ b/src/core/webcaosdb.xsl
@@ -46,9 +46,7 @@
       </head>
       <body>
         <xsl:call-template name="caosdb-top-navbar" />
-        <xsl:call-template name="paging-panel"/>
         <xsl:call-template name="caosdb-data-container" />
-        <xsl:call-template name="paging-panel"/>
         <footer>
           <xsl:call-template name="caosdb-footer"/>
         </footer>
diff --git a/src/core/xsl/main.xsl b/src/core/xsl/main.xsl
index cd0ff0d770802793779a7cc836e144a7122ca111..038b9765bcf263dca7aeafa91a6f2f28c11401f5 100644
--- a/src/core/xsl/main.xsl
+++ b/src/core/xsl/main.xsl
@@ -70,7 +70,7 @@
     </xsl:element>
     <xsl:element name="link">
       <xsl:attribute name="rel">stylesheet</xsl:attribute>
-      <xsl:attribute name="href">n
+      <xsl:attribute name="href">
         <xsl:value-of select="concat($basepath,'webinterface/${BUILD_NUMBER}/css/leaflet.css')"/>
       </xsl:attribute>
     </xsl:element>
@@ -208,12 +208,12 @@
     </xsl:element>
     <xsl:element name="script">
       <xsl:attribute name="src">
-        <xsl:value-of select="concat($basepath,'webinterface/${BUILD_NUMBER}/js/tour.js')"/>
+        <xsl:value-of select="concat($basepath,'webinterface/${BUILD_NUMBER}/js/ext_map.js')"/>
       </xsl:attribute>
     </xsl:element>
     <xsl:element name="script">
       <xsl:attribute name="src">
-        <xsl:value-of select="concat($basepath,'webinterface/${BUILD_NUMBER}/js/ext_map.js')"/>
+        <xsl:value-of select="concat($basepath,'webinterface/${BUILD_NUMBER}/js/tour.js')"/>
       </xsl:attribute>
     </xsl:element>
     <!--JS_EXTENSIONS-->
@@ -222,6 +222,7 @@
     <div class="container caosdb-f-main">
       <div class="row caosdb-v-main-col">
         <div class="panel-group caosdb-f-main-entities">
+          <xsl:call-template name="paging-panel"/>
           <xsl:apply-templates select="/Response/UserInfo"/>
           <xsl:apply-templates mode="top-level-data" select="/Response/*"/>
           <xsl:apply-templates mode="query-results" select="/Response/Query"/>
@@ -231,6 +232,7 @@
           <xsl:if test="count(/Response/*)&lt;2 and not(/Response/Error|/Response/Info|/Response/Warning)">
             <xsl:call-template name="welcome"/>
           </xsl:if>
+          <xsl:call-template name="paging-panel"/>
         </div>
       </div>
       <div class="panel panel-warning caosdb-f-edit caosdb-v-edit-panel caosdb-v-edit-panel hidden">
diff --git a/test/core/index.html b/test/core/index.html
index f964f8f8388c2c4ff8c16f29dbd6f77185a58dc0..c91a1261e0b7c305fc135666e2d5fff4addfd6c0 100644
--- a/test/core/index.html
+++ b/test/core/index.html
@@ -58,6 +58,9 @@
   <script src="js/leaflet.js"></script>
   <script src="js/leaflet-graticule.js"></script>
   <script src="js/leaflet-latlng-graticule.js"></script>
+  <script src="js/leaflet-coordinates.js"></script>
+  <script src="js/proj4.js"></script>
+  <script src="js/proj4leaflet.js"></script>
   <script src="js/ext_map.js"></script>
   <!--EXTENSIONS-->
   <script src="js/modules/webcaosdb.js.js"></script>
diff --git a/test/core/js/modules/edit_mode.js.js b/test/core/js/modules/edit_mode.js.js
index 20ad9e0edecbae7c23883d2d1609764c22691879..98fd3b2f9aac4f23f52edc6a57d6740d41600914 100644
--- a/test/core/js/modules/edit_mode.js.js
+++ b/test/core/js/modules/edit_mode.js.js
@@ -38,6 +38,9 @@ QUnit.module("edit_mode.js", {
             globalError(err);
             done();
         });
+    },
+    after: function(assert) {
+        $('.modal.fade').has(".dropzone").remove();
     }
 });
 
@@ -60,10 +63,6 @@ QUnit.test("init", function(assert){
     assert.ok(edit_mode.init);
 });
 
-QUnit.test("scroll_edit_panel", function(assert){
-    assert.ok(edit_mode.scroll_edit_panel);
-});
-
 QUnit.test("dragstart", function(assert){
     assert.ok(edit_mode.dragstart);
 });
diff --git a/test/core/js/modules/ext_map.js.js b/test/core/js/modules/ext_map.js.js
index 74573b522926cc4093411989821c00771095eea7..88a66a1c11c6691d7f4db53a502a5734ab4cbb3f 100644
--- a/test/core/js/modules/ext_map.js.js
+++ b/test/core/js/modules/ext_map.js.js
@@ -22,10 +22,33 @@
 
 'use strict';
 
-QUnit.module("ext_map.js");
+QUnit.module("ext_map.js", {
+    before: function(assert) {
+        var lat = "latitude";
+        var lng = "longitude";
+        this.datamodel = { lat: lat, lng: lng };
+        this.test_map_entity = `
+<div class="caosdb-entity-panel caosdb-properties">
+  <div class="caosdb-id">1234</div>
+  <div class="list-group-item caosdb-property-row">
+    <div class="caosdb-property-name">` +
+            lat + `</div>
+    <div class="caosdb-property-value">1.23</div>
+  </div>
+  <div class="list-group-item caosdb-property-row">
+    <div class="caosdb-property-name">` +
+            lng + `</div>
+    <div class="caosdb-property-value">5.23</div>
+  </div>
+</div>`;
+    },
+    beforeEach: function(assert) {
+        sessionStorage.removeItem("caosdb_map.view");
+    }
+});
 
 QUnit.test("availability", function(assert) {
-    assert.equal(caosdb_map.version, "0.2", "test version");
+    assert.equal(caosdb_map.version, "0.3", "test version");
     assert.ok(caosdb_map.init, "init available");
 });
 
@@ -36,7 +59,10 @@ QUnit.test("default config", function(assert) {
 
 QUnit.test("load_config", async function(assert) {
     assert.ok(caosdb_map.load_config, "available");
-    assert.ok(await caosdb_map.load_config(), "returns something");
+    var config = await caosdb_map.load_config("non_existing.json");
+    assert.ok(config, "returns something");
+    assert.equal(config.views.length, 1, "one view in default");
+    assert.equal(config.views[0].id, "UNCONFIGURED", "view has id 'UNCONFIGURED'.");
 });
 
 QUnit.test("check_config", function(assert) {
@@ -92,4 +118,118 @@ QUnit.test("create_map_panel", function(assert) {
     assert.ok($(panel).hasClass("container"), "has class container");
 });
 
+QUnit.test("create_map_view", function(assert) {
+    var view_config = $.extend(true, {}, caosdb_map._unconfigured_views[0],
+        {"select": true, "view_change": true});
+    var map_panel = $("<div/>");
+
+    var map = caosdb_map.create_map_view(map_panel[0], view_config);
+
+    console.log(map_panel[0]);
+    assert.ok(map instanceof L.Map, "map instance created");
+    assert.ok(map_panel.hasClass("leaflet-container"), "map_panel has .leaflet-container child");
+
+    assert.notOk(map._crs, "no special crs");
+    map.remove();
+
+    // test with pre-defined crs
+    view_config["crs"] = "Simple";
+    map = caosdb_map.create_map_view(map_panel[0], view_config);
+
+    console.log(map_panel[0]);
+    assert.ok(map instanceof L.Map, "map instance created");
+    assert.ok(map_panel.hasClass("leaflet-container"), "map_panel has .leaflet-container child");
+    assert.equal(map._crs, L.CRS.Simple, "map has SIMPLE crs");
+
+    map.remove();
+
+    // test with special crs:
+    view_config["crs"] =  {
+        "code": "EPSG:3995",
+        "proj4def": "+proj=stere +lat_0=90 +lat_ts=71 +lon_0=0 +k=1 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs",
+        "options": {
+            "resolutions": ["16384", "8192", "4096", "2048", "1024", "512", "256", "128", "64", "32", "16", "8", "4", "2", "1", "0.5"]
+        }
+    };
+
+    map = caosdb_map.create_map_view(map_panel[0], view_config);
+
+    console.log(map_panel[0]);
+    assert.ok(map instanceof L.Map, "map instance created");
+    assert.ok(map_panel.hasClass("leaflet-container"), "map_panel has .leaflet-container child");
+    assert.ok(map._crs instanceof L.Proj.CRS, "map has special crs");
+
+    map.remove();
+
+});
+
+QUnit.test("get_map_entities", function(assert) {
+    var datamodel = this.datamodel;
+    var container = $('<div/>').append(this.test_map_entity);
+    var map_objects = caosdb_map.get_map_entities(container[0], datamodel);
+    assert.equal(map_objects.length, 1, "has one map object");
+});
+
+
+QUnit.test("create_entitiy_markers", function(assert) {
+    var datamodel = this.datamodel;
+    var entities = $(this.test_map_entity).toArray();
+
+    // w/o popup
+    var markers = caosdb_map.create_entitiy_markers(entities, datamodel);
+    assert.equal(markers.length, 1, "has one marker");
+    assert.ok(markers[0] instanceof L.Marker, "is marker");
+    var latlng = markers[0]._latlng;
+    assert.equal(latlng.lat, "1.23", "latitude set");
+    assert.equal(latlng.lng, "5.23", "longitude set");
+    assert.notOk(markers[0].getPopup(), "no popup");
+
+    // with popup
+    var markers = caosdb_map.create_entitiy_markers(entities, datamodel, ()=>"popup");
+    assert.ok(markers[0].getPopup(), "has popup");
+});
+
+
+QUnit.test("_add_current_page_entities", function(assert) {
+    var datamodel = this.datamodel;
+    var layerGroup = L.layerGroup();
+    var container = $('<div class="caosdb-f-main-entities"/>').append(this.test_map_entity);
+    $("body").append(container);
+
+    assert.equal(layerGroup.getLayers().length, 0, "no layer");
+    var cpe = caosdb_map._get_current_page_entities(datamodel, undefined, undefined, undefined, undefined);
 
+    assert.equal(cpe.length, 1, "has one entity");
+    container.remove();
+});
+
+
+QUnit.test("make_layer_chooser_html", function(assert) {
+    var test_conf = { "id": "test_id",
+        "name": "test name",
+        "description": "test description",
+        "icon": { "html": "<span>ICON</span>",
+        },
+    };
+
+    var layer_chooser = caosdb_map.make_layer_chooser_html(test_conf);
+    assert.ok(layer_chooser, "available");
+    assert.equal($(layer_chooser).attr("title"), "test description", "description set as title");
+});
+
+QUnit.test("init_entity_layer", function(assert) {
+    var done = assert.async();
+    var test_conf = { "id": "test_id",
+        "name": "test name",
+        "description": "test description",
+        "get_entities": async function() {done(); return []},
+        "icon": { "html": "<span>ICON</span>",
+        },
+    }
+
+    var entityLayer= caosdb_map.init_entity_layer(test_conf);
+    assert.equal(entityLayer.id, test_conf.id, "id");
+    assert.equal(entityLayer.active, true, "is active");
+    assert.ok(entityLayer.chooser_html instanceof HTMLElement, "chooser_html is HTMLElement");
+    assert.equal(entityLayer.layer_group.getLayers().length, 0 , "empty layergroup");
+});
diff --git a/test/core/js/modules/ext_references.js.js b/test/core/js/modules/ext_references.js.js
index a82af8e2b7da861404abd03e4f7bfd131ff3a32e..1789f31676590ff9293bdaa26cc3bd133f89824e 100644
--- a/test/core/js/modules/ext_references.js.js
+++ b/test/core/js/modules/ext_references.js.js
@@ -44,11 +44,6 @@ QUnit.test("update_single_resolvable_reference", function(assert){
     assert.ok(resolve_references.update_single_resolvable_reference);
 });
 
-QUnit.test("references", function(assert){
-    assert.ok(resolve_references.references);
-});
-
-
 
 /*
  * This test checks whether all required
diff --git a/test/core/js/modules/ext_xsl_download.js.js b/test/core/js/modules/ext_xsl_download.js.js
index 360b389725eec2bc6a4e4115e9cc46cd91e33d69..12ba18b9da3bf393d8249cb5456c7016f13456d6 100644
--- a/test/core/js/modules/ext_xsl_download.js.js
+++ b/test/core/js/modules/ext_xsl_download.js.js
@@ -37,7 +37,7 @@ QUnit.module("ext_xls_download");
         return str2xml('<response><script code="0" /><stdout>bla</stdout></response>');
     }
 
-    _go_to_script_results = function(xls_link, filename) {
+    var _go_to_script_results = function(xls_link, filename) {
         xls_link.setAttribute(
             "href",
             location.protocol + "//" +location.host + "/Shared/" + filename);
diff --git a/test/core/js/modules/navbar.xsl.js b/test/core/js/modules/navbar.xsl.js
index 3649c1908939400d0b7a77b4de90cf29dfde638b..0b43cae70cbd4694290885560c69594a5d4a555e 100644
--- a/test/core/js/modules/navbar.xsl.js
+++ b/test/core/js/modules/navbar.xsl.js
@@ -35,7 +35,7 @@ QUnit.module("navbar.xsl", {
 		}).done(function(data, textStatus, jdXHR) {
 			insertParam(data, "entitypath", "/entitypath/");
 			insertParam(data, "filesystempath", "/filesystempath/");
-			insertParam(data, "basepath", "/basepath/");	
+			insertParam(data, "basepath", window.location.origin);	
  			qunit_obj.navbarXsl = injectTemplate(injectTemplate(data, '<xsl:template name="make-filesystem-link"><filesystemlink/></xsl:template>'), '<xsl:template name="caosdb-query-panel"><query/></xsl:template>');
  		}).always(function() {
 			done();
diff --git a/test/core/js/modules/webcaosdb.js.js b/test/core/js/modules/webcaosdb.js.js
index aba5d845d4032514f17bcddac0916cd73d421018..7e02d1d98270f0c5f696bcbe39651fe0b3064c03 100644
--- a/test/core/js/modules/webcaosdb.js.js
+++ b/test/core/js/modules/webcaosdb.js.js
@@ -146,7 +146,6 @@ QUnit.test("createErrorNotification", function(assert) {
 /* MODULE connection */
 QUnit.module("webcaosdb.js - connection", {
     before: function(assert) {
-        window.sessionStorage.caosdbBasePath = "../../";
         assert.ok(connection, "connection module is defined");
     }
 });
@@ -460,7 +459,6 @@ QUnit.module("webcaosdb.js - preview", {
             done();
         });
 
-        window.sessionStorage.caosdbBasePath = "../../"
         assert.ok(preview, "preview module is defined");
     },
     afterEach: function(assert) {
@@ -1444,7 +1442,6 @@ QUnit.module("webcaosdb.js - annotation", {
         annotation.postCommentXml = function(xml) {
             return new Promise(resolve => setTimeout(resolve, 1000, str2xml("<Response/>")));
         }
-        window.sessionStorage.caosdbBasePath = "../../";
     }
 });
 
diff --git a/test/core/js/setup.js b/test/core/js/setup.js
index 701195ce7cef08190a1dbf066f240a9ed52a5dce..f2bca4e928b8d5ef01b19ef6cd2d4425bae0ee24 100644
--- a/test/core/js/setup.js
+++ b/test/core/js/setup.js
@@ -33,19 +33,19 @@ function getQueryValue(key) {
     if (loggerPort) {
         console.log("logging QUnit results to http://127.0.0.1:" + loggerPort);
 
+        QUnit.done(function( details ) {
+            console.log("done");
+            let report = (details.failed === 0 ? "SUCCESS\n" : "FAILURE\n") + "Total: " + details.total + "\nFailed: " + details.failed + "\nPassed: " + details.passed + "\nRuntime: " + details.runtime + "\n";
+            console.log(report);
+            return $.post("http://127.0.0.1:" + loggerPort + "/done", report);
+        });
+
+        QUnit.config.testTimeout = 30000;
 
         QUnit.log(function(obj) {
-            // TODO
             if(!obj.result) {
-                var failed_assertion = {
-                    "Actual": obj.actual,
-                    "Expected": obj.expected,
-                    "Message": obj.message,
-                    "Source": obj.source,
-                    "Module": obj.module,
-                    "Name": obj.name,
-                }
-                $.post("http://127.0.0.1:" + loggerPort + "/log", JSON.stringify(failed_assertion, null, 2));
+                var failed_assertion = JSON.stringify(obj, null, 2);
+                $.post("http://127.0.0.1:" + loggerPort + "/log", "FAILURE\n" + failed_assertion);
             }
         });
 
@@ -63,15 +63,8 @@ function getQueryValue(key) {
             "Runtime": details.runtime
           };
 
-          $.post("http://127.0.0.1:" + loggerPort + "/log", JSON.stringify( result, null, 2 ));
+          return $.post("http://127.0.0.1:" + loggerPort + "/log", JSON.stringify( result, null, 2 ));
 
-        } );
-
-        QUnit.done(function( details ) {
-            console.log("done");
-            let report = (details.failed === 0 ? "SUCCESS\n" : "") + "Total: " + details.total + "\nFailed: " + details.failed + "\nPassed: " + details.passed + "\nRuntime: " + details.runtime + "\n";
-            console.log(report);
-            $.post("http://127.0.0.1:" + loggerPort + "/done", report);
         });