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/js/ext_map.js b/src/core/js/ext_map.js
index 44e50df23936debadbc1c03265d23ecc61d8bb55..5ef5c5dc2a2a8f336764f6f083defb26f444cad9 100644
--- a/src/core/js/ext_map.js
+++ b/src/core/js/ext_map.js
@@ -52,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.
@@ -73,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.
      */
@@ -272,6 +272,7 @@ var caosdb_map = new function () {
      *       }
      *     }
      *
+     * @typedef {object} GraticuleConfig
      * @property {string} type - either "simple" or "latlngGraticule".
      * @property {object} options - the options for the graticule
      *     implementation.
@@ -305,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 
@@ -330,6 +332,140 @@ var caosdb_map = new function () {
         },
     }
 
+    /**
+     * 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.
      *
@@ -606,7 +742,7 @@ var caosdb_map = new function () {
             var toggle_button = undefined;
             try {
                 const config = await this.load_config();
-                if(!config.views || config.views.length === 0) {
+                if (!config.views || config.views.length === 0) {
                     logger.warn("no views in config");
                     return;
                 }
@@ -649,7 +785,7 @@ var caosdb_map = new function () {
                         nextview + ".zoom"];
                     local_conf["center"] = sessionStorage["caosdb_map.view." +
                         nextview + ".center"];
-                    if(local_conf["center"]) {
+                    if (local_conf["center"]) {
                         local_conf["center"] =
                             JSON.parse(local_conf["center"]);
                     }
@@ -664,13 +800,14 @@ var caosdb_map = new function () {
                     this._map = this.create_map_view(panel,
                         view_config);
 
-                    // initialize layer groups
-                    this.init_layer_groups(this._map);
-                    var current_page_entities_layer =
-                        this.get_layer_group("current_page_entities");
-                    this.add_current_page_entities(
-                        config.datamodel,
-                        current_page_entities_layer);
+                    // 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);
@@ -697,6 +834,79 @@ 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.
          *
@@ -824,8 +1034,8 @@ var caosdb_map = new function () {
          * area to be searched in.
          *
          * @param {number} north
-         * @param {number} west
          * @param {number} south
+         * @param {number} west
          * @param {number} east
          * @return {string} a query string.
          */
@@ -870,7 +1080,7 @@ var caosdb_map = new function () {
             var result = $.extend(true, {}, this
                 ._default_config, conf);
 
-            if(!result.views || result.views.length === 0){
+            if (!result.views || result.views.length === 0) {
                 logger.debug(
                     "Could not find any view config. using a dummy tiling server."
                 );
@@ -932,14 +1142,14 @@ var caosdb_map = new function () {
          * @param {DataModelConfig} datamodel
          * @returns {HTMLElement[]} entities with coordinate properties.
          */
-        this.get_map_objects = function (container, datamodel) {
-            var map_objects = $(container)
+        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_objects.toArray();
+                    ".caosdb-property-name:contains('" + datamodel
+                    .lng + "')").has(
+                    ".caosdb-property-name:contains('" + datamodel
+                    .lat + "')");
+            return map_entities.toArray();
         }
 
 
@@ -980,74 +1190,35 @@ var caosdb_map = new function () {
          * @returns {HTMLElement} a label with the entities name.
          */
         this.make_entity_name_label = function (entity) {
-            var name = getEntityName(entity);
-            var id = getEntityId(entity);
-            var name_label = $('<div/>')
+            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];
         }
 
 
         /**
-         * Create a div element which shows the most relevant information of an
-         * entity and is shown as a pop-up when the user clicks on a marker.
+         * Retrieve entities from the server and return a container.
          *
-         * @param {HTMLElement} entity - the entity in HTML representation.
-         * @returns {HTMLElement} the div element.
+         * @param {string} query_str - a CQL query.
+         * @returns {HTMLElement[]} an array of entities in HTML representation.
          */
-        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];
-        }
-
-        /**
-         * Collect all entities with coordinates on the current page and create
-         * markers ({@link L.Marker}).
-         *
-         * The markers are not added to any map by this method. The are only
-         * added to the {@link L.LayerGroup}.
-         *
-         * The entities must have the properties for latitude and longitude as
-         * configured by the {@link DataModelConfig}.
-         *
-         * @param {DataModelConfig} datamodel - the datamodel which specifies
-         *     the properties for coordinates.
-         * @param {L.LayerGroup} layergroup - where to add the newly created
-         *     markers.
-         */
-        this.add_current_page_entities = function (datamodel,
-            layergroup) {
-            logger.trace(
-                "enter add_current_page_entities",
-                datamodel, layergroup);
-            var container = $(".caosdb-f-main-entities")[0];
-            var map_objects = this.get_map_objects(container, datamodel);
-
-            var markers = this.create_entitiy_markers(map_objects, datamodel);
-            for (const marker of markers) {
-                layergroup.addLayer(marker)
-            };
-
-            logger.trace(
-                "leave add_current_page_entities");
-        }
+        this.query = query;
 
 
         /**
@@ -1057,99 +1228,41 @@ var caosdb_map = new function () {
          *     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) {
-            logger.trace("enter create_entitiy_markers", entities, datamodel);
+        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_object of entities) {
-                var lat = getProperty(map_object, datamodel.lat);
-                var lng = getProperty(map_object, datamodel.lng);
+            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));
+                    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);
                 }
             }
             return ret;
         }
 
-        /**
-         * Return the standard layer groups.
-         *
-         * Currently only one standard layer group is known:
-         * "current_page_layer", which contains markers of the position of all
-         * entities which are on the current page.
-         *
-         * @returns {object} a mapping from string name to a L.LayerGroup.
-         */
-        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;
-        }
-
-        /**
-         * Initialize the layer groups 
-         *
-         *
-         * Layer groups are sets of Layers identified by a string name,
-         * i.e. a mapping from a string name to a {@link L.LayerGroup}.
-         *
-         * Layer groups are used to organize different types of layers.
-         *
-         * This method adds standard layer groups the map (without creating any
-         * layers tho).
-         *
-         * Especially the markers that are used to show the location of those
-         * entities which are shown on the current page are grouped together in
-         * the "current_page_entities" group.
-         *
-         * @param {L.Map} map - the map of the layers.
-         * @returns {object} a mapping from names to L.LayerGroups.
-         */
-        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 of Object.keys(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");
-            return this._layer_groups;
-        }
-
-        /**
-         * Get a layer group identified by a name.
-         *
-         * The layer group must already be in the _layer_groups list.
-         *
-         * @param {string} name - name of the layer group.
-         * @returns {L.LayerGroup}
-         */
-        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.
diff --git a/src/core/js/ext_references.js b/src/core/js/ext_references.js
index 943eadad4dd1bc50cd1df96deb93caa153678887..68a52a023e49d8e9f092883817ad82b1da1c87cd 100644
--- a/src/core/js/ext_references.js
+++ b/src/core/js/ext_references.js
@@ -12,7 +12,7 @@
  * @return {Object}     A set of booleans for each side of the element
  */
 // Source address: https://gomakethings.com/how-to-check-if-any-part-of-an-element-is-out-of-the-viewport-with-vanilla-js/
-var isOutOfViewport = function(elem) {
+var isOutOfViewport = function (elem) {
 
     // Get element's bounding
     var bounding = elem.getBoundingClientRect();
@@ -38,7 +38,7 @@ var isOutOfViewport = function(elem) {
  * @return {boolean}
  *
  */
-var is_in_viewport_vertically = function(elem) {
+var is_in_viewport_vertically = function (elem) {
     var out = isOutOfViewport(elem);
     return !(out.top || out.bottom);
 }
@@ -54,7 +54,7 @@ var is_in_viewport_vertically = function(elem) {
  * @return {boolean}
  *
  */
-var is_in_viewport_horizontally = function(elem) {
+var is_in_viewport_horizontally = function (elem) {
     var scrollbox = elem.parentElement.parentElement;
     // Check this condition only if the grand parent is a list and return true otherwise.
     if (scrollbox.classList.contains("caosdb-value-list") == true) {
@@ -72,13 +72,14 @@ var is_in_viewport_horizontally = function(elem) {
 /**
  * The resolve_references module.
  */
-var resolve_references = new function() {
-    this.init = function() {
-        // this.references();
+var resolve_references = new function () {
+
+    this.init = function () {
         this.update_visible_references();
     }
 
-    this.get_person_str = function(el) {
+
+    this.get_person_str = function (el) {
         var valpr = getProperties(el);
         if (valpr == undefined) {
             return;
@@ -87,6 +88,60 @@ var resolve_references = new function() {
             " " + valpr.filter(valprel => valprel.name.toLowerCase() == "lastname")[0].value;
     }
 
+
+    /**
+     * Return true iff the entity has at least one parent named `rt`.
+     *
+     * @param {HTMLElement} el - entity in HTML representation.
+     * @param {string} rt - parent name.
+     * @returns {boolean}
+     */
+    this.isChild = function (el, rt) {
+        var pars = getParents(el);
+        for (const par of pars) {
+            if (par.name === rt) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+
+    this.find_bag_of_sample = async function (el) {
+        return await this._find_ice_sample_back_ref(getEntityID(el), "Bag");
+    }
+
+
+    this.find_ice_core_of_sample = async function (el) {
+        return await this._find_ice_sample_back_ref(getEntityID(el), "IceCore");
+    }
+
+
+    this._find_ice_sample_back_ref = async function (id, rt, oldcounter) {
+        var counter = oldcounter + 1 || 1
+        if (counter > 5) {
+            return null;
+        }
+        var referencing_samples = await query("FIND IceSample WHICH REFERENCES " + id);
+        for (const sample of referencing_samples) {
+            if (this.isChild(sample, rt)) {
+                return sample;
+            } else {
+                var ret = await this._find_ice_sample_back_ref(getEntityID(sample), rt, counter);
+                if (ret) {
+                    return ret;
+                }
+            }
+        }
+        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.
      *
@@ -94,42 +149,73 @@ var resolve_references = new function() {
      *
      * rs: Element having the class caosdb-resolvable-reference and including a caosdb-resolve-reference target.
      */
-    this.update_single_resolvable_reference = async function(rs) {
+    this.update_single_resolvable_reference = async function (rs) {
+        // remove caosdb-resolvable-reference class because this reference is
+        // being resolved right now.
+        $(rs).toggleClass("caosdb-resolvable-reference", false);
+
         var rseditable = rs.getElementsByClassName("caosdb-resolve-reference-target")[0];
+        var id = getIDfromHREF(rs);
+        rseditable.textContent = id;
         var el = await retrieve(getIDfromHREF(rs));
         var pr = getParents(el[0]);
         if (getEntityHeadingAttribute(el[0], "path") !== undefined || pr[0].name == "Image") {
             var pths = getEntityHeadingAttribute(el[0], "path").split("/");
             rseditable.textContent = pths[pths.length - 1];
-        } else if (pr[0].name == "Person") {
+        } else if (pr[0].name === "Person") {
             rseditable.textContent = this.get_person_str(el[0]);
-        } else if (pr[0].name == "ExperimentSeries") {
+        } else if (pr[0].name === "ExperimentSeries") {
             rseditable.textContent = getEntityName(el[0]);
-        } else if (pr[0].name == "BoxType") {
+        } else if (pr[0].name === "BoxType") {
             rseditable.textContent = getEntityName(el[0]);
-        } else if (pr[0].name == "Loan") {
+        } else if (pr[0].name === "Loan") {
             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 == "Box") {
-            rseditable.textContent = getProperty(el[0], "Number");
-        } else if (pr[0].name == "Bag") {
+        } 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]);
+                if (!icecore) {
+                    rseditable.textContent = `${id} (Sample w/o Bag or Ice Core)`;
+                } else {
+                    rseditable.textContent = `${id} (Ice Core ${getEntityName(icecore)}, no Bag)`;
+                }
+            } else {
+                var icecore = (await query("SELECT name FROM IceCore WHICH REFERENCES " + getEntityID(bag)))[0];
+                if (!icecore) {
+                    rseditable.textContent = `${id} (Bag ${getProperty(bag, "Number", false)}, no Ice Core)`;
+                } else {
+                    rseditable.textContent = `${id} (Ice Core ${getEntityName(icecore)}, Bag ${getProperty(bag, "Number", false)})`;
+                }
+            }
+        } 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") {
+        } else if (pr[0].name === "Palette") {
             rseditable.textContent = getProperty(el[0], "Number");
-        } else if (pr[0].name.includes("Model")) {
-            rseditable.textContent = pr[0].name;
         } else {
-            rseditable.textContent = el[0].id;
+            if (typeof el[0].name !== "undefined" && el[0].length > 0) {
+                rseditable.textContent = el[0].name;
+            }
         }
+
     }
 
+
     /*
      * This function updates all references that are inside of the current viewport.
      *
      */
-    this.update_visible_references = async function() {
-        var rs = document.getElementsByClassName("caosdb-resolvable-reference");
+    this.update_visible_references = async function () {
+        var rs = $(".caosdb-resolvable-reference");
 
         for (var i = 0; i < rs.length; i++) {
             if (is_in_viewport_vertically(rs[i]) &&
@@ -138,25 +224,17 @@ var resolve_references = new function() {
             }
         }
     }
-
-    this.references = async function() {
-        var rs = document.getElementsByClassName("caosdb-resolvable-reference");
-
-        for (var i = 0; i < rs.length; i++) {
-            this.update_single_resolvable_reference(rs[i]);
-        }
-    }
 }
 
 
-$(document).ready(function() {
+$(document).ready(function () {
     resolve_references.init();
     var scrollTimeout = undefined;
     var updatefunc = () => {
         if (scrollTimeout) {
             clearTimeout(scrollTimeout);
         }
-        scrollTimeout = setTimeout(function() {
+        scrollTimeout = setTimeout(function () {
             resolve_references.update_visible_references();
         }, 500);
     };
diff --git a/src/core/js/form_elements.js b/src/core/js/form_elements.js
index cb08ac7083b352883fa4bafb806711eccb850b27..8e0f6c82deba0ed55122eb0451bde6080d5e7280 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;
     }
 
-    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/test/core/js/modules/edit_mode.js.js b/test/core/js/modules/edit_mode.js.js
index 24c0a59580a4b0f346bd12b80ff52e049b79feb6..98fd3b2f9aac4f23f52edc6a57d6740d41600914 100644
--- a/test/core/js/modules/edit_mode.js.js
+++ b/test/core/js/modules/edit_mode.js.js
@@ -63,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 7fb9c69dcd7b9ee2f2e4c7254ee534321cce2c6f..88a66a1c11c6691d7f4db53a502a5734ab4cbb3f 100644
--- a/test/core/js/modules/ext_map.js.js
+++ b/test/core/js/modules/ext_map.js.js
@@ -163,39 +163,73 @@ QUnit.test("create_map_view", function(assert) {
 
 });
 
-
-QUnit.test("get_map_objects", function(assert) {
+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_objects(container[0], datamodel);
+    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.ok(markers[0]._popupHandlersAdded, "has popup");
+    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) {
+
+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");
-    caosdb_map.add_current_page_entities(datamodel, layerGroup);
-
-    assert.equal(layerGroup.getLayers().length, 1, "has one layer");
-    assert.ok(layerGroup.getLayers()[0] instanceof L.Marker,
-        "layergroup has entity marker");
+    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");
+});