diff --git a/CHANGELOG.md b/CHANGELOG.md
index f3fcda0ed89756011e24232c5c0009b1168b9c4c..6ae4be1462bb86c43cfa3a48782cb3910a30fc29 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,16 +8,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ### Added (for new features, dependecies etc.)
 
-* Map (v0.1)
-    * adds a button to the navbar which toggles a map in the top of the main
-      panel. The map currently shows all entities on the current page and can be used to select an area in the map and generate a query filter for this area which is can be openend in the query panel.
-    * see the caosdb_map module in  [ext_map.js](./src/core/js/ext_map.js)
-    * the default configuration is located in the module itself.
-    * the module needs an additional [ext.json](./conf/ext/json/ext_map.json)
-      defines at least the tiling server. The tiling server is not configured
-      in the default config because this would require the caosdb maintainers
-      to enforce the respective usage policies of the tiling server providers.
-    * test data for your server can be generated with [map_test_data.py](./misc/map_test_data.py).
+* Map (v0.3)
+    * Adds a button to the navbar which toggles a map in the top of the main
+      panel. The map currently shows all known entities which have geolocation
+      properties and highlights those which are on the current page. Users can
+      select an area in the map and generate a query filter for this area which
+      is can be openend in the query panel.
+    * See the caosdb_map module in  [ext_map.js](./src/core/js/ext_map.js)
+    * The default configuration is located in the module itself.
+    * The module needs an additional [ext_map.json](./conf/ext/json/ext_map.json)
+      which defines at least the tiling server. The tiling server is not
+      configured in the default config because this would require the caosdb
+      maintainers to enforce the respective usage policies of the tiling server
+      providers.
+    * Test data for your server can be generated with
+      [map_test_data.py](./misc/map_test_data.py).
+    * The map module supports different map projections, e.g. Spherical
+      Mercartor and projections for the polar regions. The active projection
+      can changed by the user with a button.
 * navbar module (v0.1)
     * A collection of helper functions for adding stuff to the navbar.
 * caosdb_utils module (v0.1)
@@ -26,12 +34,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 * New Dependency: leaflet-1.5-1
     * for the map
 * New Dependency: loglevel-1.6.4
-    * our new logging framework. Any new logging should be done with this framework.
+    * Our new logging framework. Any new logging should be done with this
+      framework.
 
 ### Changed (for changes in existing functionality)
 
-* in [entity.xsl](./src/core/xsl/entity.xsl): an emtpy property value (a `NULL` property) did not produce any `<span>` element with class `caosdb-property-text-value`. This caused the current implementation of `getPropertyFromElement` in [caosdb.js](./src/core/js/caosdb.js) to return the unit of the property as the value. The new implementation produces an empty `<span>` (no child text node) which is more appropriate and also fixes the buggy `getPropertyFromElement` without touching it.
-* in [webcaosdb.js](./src/core/js/webcaosdb.js), `markdown` module: The markdown module is very generell and small now. The logic for converting comments (aka CommentAnnotations) to markdown is implemented in the `annotation` module now (which uses the `markdown` module as back-end, tho).
+* The old `caosdb-property-row` CSS class has been replaced by
+  `caosdb-v-property-row` for styling and `caosdb-f-property` for functional
+  needs.
+* In [ext_xls_download.js](./src/core/js/ext_xls_download.js): Complete rewrite
+  of the module. The generation of the TSV table is done in this module now,
+  instead of generating it with xsl (in [query.xsl](./src/core/xsl/query.xsl)).
+  Also it is generated on demand.
+* In [ext_references.js](./src/core/js/ext_references.js): Updated
+  ext_references to v0.2. The new version can also generate and show summaries
+  of `LIST` properties.
+* In [entity.xsl](./src/core/xsl/entity.xsl): an emtpy property value (a `NULL`
+  property) did not produce any `<span>` element with class
+  `caosdb-property-text-value`. This caused the current implementation of
+  `getPropertyFromElement` in [caosdb.js](./src/core/js/caosdb.js) to return
+  the unit of the property as the value. The new implementation produces an
+  empty `<span>` (no child text node) which is more appropriate and also fixes
+  the buggy `getPropertyFromElement` without touching it.
+* In [webcaosdb.js](./src/core/js/webcaosdb.js), `markdown` module: The
+  markdown module is very generell and small now. The logic for converting
+  comments (aka CommentAnnotations) to markdown is implemented in the
+  `annotation` module now (which uses the `markdown` module as back-end, tho).
 * updated QUnit test framework to 2.9.2
 
 ### Deprecated (for soon-to-be removed features) 
diff --git a/misc/list_references_test_data.py b/misc/list_references_test_data.py
new file mode 100755
index 0000000000000000000000000000000000000000..68371df2f3a5db4d0da5e26abba432ef846b5898
--- /dev/null
+++ b/misc/list_references_test_data.py
@@ -0,0 +1,40 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+(C) Copyright IndiScale GmbH 2019
+"""
+
+import caosdb
+import random
+
+caosdb.execute_query("FIND Referenc*").delete()
+
+# data model
+datamodel = caosdb.Container()
+datamodel.extend([
+    caosdb.RecordType("Referenced"),
+    caosdb.RecordType(
+        "Referencing"
+        ).add_property("Referenced", datatype=caosdb.LIST("Referenced")),
+])
+
+datamodel.insert()
+
+
+# test data
+testdata = caosdb.Container()
+
+for i in range(100):
+    testdata.append(
+        caosdb.Record("ReferenceObject-{}".format(i)
+            ).add_parent("Referenced")
+    )
+
+testdata.insert();
+caosdb.Record().add_parent(
+    "Referencing"
+    ).add_property("Referenced",
+                   datatype=caosdb.LIST("Referenced"),
+                   value=testdata
+    ).insert()
+
diff --git a/src/core/css/webcaosdb.css b/src/core/css/webcaosdb.css
index cc704be6cdd816cfe3214116a07275b3118bc363..0c8a1a5eb7b01efa947bafd7d17955582eceec44 100644
--- a/src/core/css/webcaosdb.css
+++ b/src/core/css/webcaosdb.css
@@ -269,11 +269,24 @@ h5 {
 .caosdb-v-edit-list {
     padding-left: 0px;
 }
+
+.caosdb-v-editmode-existing {
+    height: 320.7px;
+    overflow-y: auto;
+}
+
 .caosdb-v-edit-panel {
     position: sticky;
-    top: 20px;
+    top: 57px;
     padding: 0px;
+    margin-top: 5px;
+    margin-left: 5px;
     width: unset;
+    height: 800px;
+}
+
+.caosdb-v-editmode-btngroup {
+    padding-bottom: 15px;
 }
 
 .caosdb-prop-list-group>.list-group-item {
@@ -444,6 +457,11 @@ h5 {
     border-left: 0px solid #7c7c7c;
 }
 
+.caosdb-paging-panel {
+    padding-left: 0px;
+    padding-right: 0px;
+}
+
 .caosdb-pagination {
     margin: 5px 15px;
 }
@@ -478,7 +496,7 @@ h5 {
     100% { transform: rotate(360deg); }
 }
 
-.caosdb-property-row {
+.caosdb-v-property-row {
     animation: appear 0.5s 1;
     padding: 0.3ex 1em;
 }
@@ -514,11 +532,6 @@ input[type="file"] {
     min-height: 22px;
 }
 
-.caosdb-v-edit-panel {
-    max-height: 80vh;
-    overflow-y: auto;
-}
-
 footer {
     background-color: lightgrey;
     padding: 0.5em;
diff --git a/src/core/js/caosdb.js b/src/core/js/caosdb.js
index 85032d8e973f64f94565fff2e1b3187f735d9021..c96881e230ef16ff929cc8fdffdf36e01a9df722 100644
--- a/src/core/js/caosdb.js
+++ b/src/core/js/caosdb.js
@@ -348,12 +348,17 @@ function getParents(element) {
 /**
  * Find all elements that fulfill a condition.
  * Don't traverse elements if except condition is matched.
- * @param element The start node.
- * @param condition The condition.
- * @param except The stop condition.
+ *
+ * @param {HTMLElement} element - The start node.
+ * @param {function} condition - A filter callback which returns `true` for
+ *     each element which should be included.
+ * @param {function} except - A filter callback which returns `true` for an
+ *     element when the traversing should stop.
+ *
+ * @returns {HTMLElement[]} an array of HTMLElements
  */
 function findElementByConditions(element, condition, except) {
-    let found = []
+    let found = [];
     let echild = element.children;
 
     for (var i = 0; i < echild.length; i++) {
@@ -400,20 +405,43 @@ function getPropertyName(element) {
 }
 
 /**
- *  Return a list of property objects from the dom property element.
- *  @param propertyelement: A HTMLElement identifying the property element.
- *  @param names: a map of names tracking the count of usage for each name (optional)
+ * A JSON representation of an entities property.
+ *
+ * TODO description, importance
+ *
+ * @type {EntityProperty}
+ * @property {string} id
+ * @property {string} name
+ * @property {number} duplicateIndex - Number of properties in the entity with
+ *     the same name.
+ * @property {HTMLElement} html - An HTML representation of the property.
+ * @property {string} datatype
+ * @property {boolean} reference - has a reference datatype?
+ * @property {string|string[]} value
+ * @property {string} unit
+ * @property {boolean} list - has a list datatype?
+ * @property {string} listDatatype - the datatype of the list elements
+ */
+
+/**
+ * Return a json property object.
+ *
+ * @param {HTMLElement} propertyelement - A HTMLElement representing the property.
+ *     property element.
+ * @param {object} [names] - a map of names tracking the count of usage for
+ *     each name (Default: undefined)
  *
- * TODO: Retrieval when using list element preview is currently broken.
+ * @return {Property}
  **/
 function getPropertyFromElement(propertyelement, names = undefined) {
 
-    let property = new Object({});
+    let property = {};
     let valel = propertyelement.getElementsByClassName("caosdb-property-value")[0];
     let dtel = propertyelement.getElementsByClassName("caosdb-property-datatype")[0];
     let idel = propertyelement.getElementsByClassName("caosdb-property-id")[0];
     let unitel = valel.getElementsByClassName("caosdb-unit")[0];
 
+    property.html = propertyelement;
     // name
     property.name = getPropertyName(propertyelement);
 
@@ -484,15 +512,13 @@ function getPropertyFromElement(propertyelement, names = undefined) {
             let listel;
             if (property.reference) {
                 // list of referernces
-                // TODO: Fix list preivew here. Fixed, but untested.
-                listel = findElementByConditions(valel, x => x.classList.contains("caosdb-resolvable-reference"),
+                listel = findElementByConditions(valel, x => x.classList.contains("caosdb-f-reference-value"),
                     x => x.classList.contains("caosdb-preview-container"));
                 for (var j = 0; j < listel.length; j++) {
                     property.value.push(getIDfromHREF(listel[j]));
                 }
             } else {
                 // list of anything but references
-                // TODO: Fix list preivew here. Fixed, but untested.
                 listel = findElementByConditions(valel, x => x.classList.contains("list-group-item"),
                     x => x.classList.contains("caosdb-preview-container"));
                 for (var j = 0; j < listel.length; j++) {
@@ -501,8 +527,6 @@ function getPropertyFromElement(propertyelement, names = undefined) {
             }
         } else if (property.reference) {
             // reference datatypes
-            // let el = findElementByConditions(valel, x => x.classList.contains("caosdb-id"),
-            //                                  x => x.classList.contains("caosdb-preview-container"));
             property.value = getIDfromHREF(valel.getElementsByTagName("a")[0]);
         } else {
             // all other datatypes
@@ -515,13 +539,15 @@ function getPropertyFromElement(propertyelement, names = undefined) {
 }
 
 /**
- * Get the properties from an entity.
- * @param element The element holding the entity.
- * @return a list of dom elements containing the properties.
+ * Get all the properties from an entity.
+ *
+ * @param {HTMLElement} element The element holding the entity.
+ *
+ * @return {HTMLElement[]} a list of properties in HTML representation.
  */
 function getPropertyElements(element) {
     return findElementByConditions(element,
-        x => x.classList.contains("caosdb-property-row"),
+        x => x.classList.contains("caosdb-f-entity-property"),
         x => x.classList.contains("caosdb-preview-container"));
 }
 
@@ -564,8 +590,6 @@ function getProperties(element) {
  * @param valueelement The dom element where the text content is to be set.
  * @param property The new property.
  * @param propold The old property belonging to valueelement.
- *
- * TODO: Server string is hardcoded.
  */
 function setPropertySafe(valueelement, property, propold) {
     const serverstring = connection.getBasePath() + "Entity/";
diff --git a/src/core/js/edit_mode.js b/src/core/js/edit_mode.js
index 5b11866a4a3d50ee6dc2c3ae133612a4b20836ff..692ae79ab9847b62ef06a3349e2ac9bf2bebda93 100644
--- a/src/core/js/edit_mode.js
+++ b/src/core/js/edit_mode.js
@@ -330,54 +330,57 @@ var edit_mode = new function() {
      * ent_element : {HTMLElement} entity in view mode
      */
     this.getProperties = function(ent_element) {
-        var properties = [];
-        for (var element of $(ent_element).find('.caosdb-property-row')) {
-
-            var valfield = $(element).find(".caosdb-property-value");
-            var editfield = $(element).find(".caosdb-property-edit-value");
-            var property = getPropertyFromElement(element);
-
-            // LISTs need to be handled here
-            if (property.list == true) {
-                // TODO: unit missing
-                property.value = [];
-                if (this.checkForDatatypeList(property.listDatatype,
-                                              ["TEXT","DATE","DOUBLE","INTEGER","BOOLEAN","FILE"])) {
-                    // LOOP over elements of editfield.find(":input")
-                    for (var singleelement of $(editfield).find(":not(.caosdb-f-list-item-button):input")) {
-                        property.value.push($(singleelement).val());
-                    }
-                } else if (property.datatype == "DATETIME") {
-                    throw ("Lists of DATETIME currently not supported.");
-                } else if (property.reference) {
-                    // LOOP over elements of editfield.find("select")
-                    for (var singleelement of $(editfield).find(":not(.caosdb-f-list-item-button):input")) {
-                        property.value.push(singleelement.selectedOptions[0].value);
+        const properties = [];
+        if (ent_element) {
+            const prop_elements = getPropertyElements(ent_element);
+            for (var element of prop_elements) {
+
+                var valfield = $(element).find(".caosdb-property-value");
+                var editfield = $(element).find(".caosdb-property-edit-value");
+                var property = getPropertyFromElement(element);
+
+                // LISTs need to be handled here
+                if (property.list == true) {
+                    // TODO: unit missing
+                    property.value = [];
+                    if (this.checkForDatatypeList(property.listDatatype,
+                                                  ["TEXT","DATE","DOUBLE","INTEGER","BOOLEAN","FILE"])) {
+                        // LOOP over elements of editfield.find(":input")
+                        for (var singleelement of $(editfield).find(":not(.caosdb-f-list-item-button):input")) {
+                            property.value.push($(singleelement).val());
+                        }
+                    } else if (property.datatype == "DATETIME") {
+                        throw ("Lists of DATETIME currently not supported.");
+                    } else if (property.reference) {
+                        // LOOP over elements of editfield.find("select")
+                        for (var singleelement of $(editfield).find(":not(.caosdb-f-list-item-button):input")) {
+                            property.value.push(singleelement.selectedOptions[0].value);
+                        }
+                    } else {
+                        throw ("This property's data type is not supported by the webui. Please issue a feature request for support for `" + property.datatype + "`.");
                     }
                 } else {
-                    throw ("This property's data type is not supported by the webui. Please issue a feature request for support for `" + property.datatype + "`.");
-                }
-            } else {
-                property.unit = editfield.find(".caosdb-unit").val();
-                if (this.checkForDatatypeList(property.datatype,
-                                              ["TEXT","DATE","DOUBLE","INTEGER","BOOLEAN","FILE"])) {
-                    property.value = editfield.find(":input").val()
-                } else if (property.datatype == "DATETIME") {
-                    let es = editfield.find(":input");
-                    if (es.length == 2) {
-                        property.value = input2caosdbDate(
-                            es[0].value,
-                            es[1].value);
-                    } else if (es[0]) {
-                        property.value = es[0].value;
+                    property.unit = editfield.find(".caosdb-unit").val();
+                    if (this.checkForDatatypeList(property.datatype,
+                                                  ["TEXT","DATE","DOUBLE","INTEGER","BOOLEAN","FILE"])) {
+                        property.value = editfield.find(":input").val()
+                    } else if (property.datatype == "DATETIME") {
+                        let es = editfield.find(":input");
+                        if (es.length == 2) {
+                            property.value = input2caosdbDate(
+                                es[0].value,
+                                es[1].value);
+                        } else if (es[0]) {
+                            property.value = es[0].value;
+                        }
+                    } else if (property.reference) {
+                        property.value = $(editfield).find("select").first()[0].selectedOptions[0].value;
+                    } else {
+                        throw ("This property's data type is not supported by the webui. Please issue a feature request for support for `" + property.datatype + "`.");
                     }
-                } else if (property.reference) {
-                    property.value = $(editfield).find("select").first()[0].selectedOptions[0].value;
-                } else {
-                    throw ("This property's data type is not supported by the webui. Please issue a feature request for support for `" + property.datatype + "`.");
                 }
+                properties.push(property);
             }
-            properties.push(property);
         }
         return properties;
 
@@ -1022,7 +1025,9 @@ var edit_mode = new function() {
             // set listener for editable entity.
             hintMessages.hintMessages(app.entity);
             $(app.entity).find('.caosdb-annotation-section').remove();
-            for (var element of $(app.entity).find('.caosdb-property-row')) {
+
+            const prop_elements = getPropertyElements(app.entity);
+            for (var element of prop_elements) {
                 edit_mode.make_property_editable(element);
             }
             app.entity.dispatchEvent(edit_mode.start_edit);
diff --git a/src/core/js/ext_map.js b/src/core/js/ext_map.js
index aba52042292df7d2ead3b42b104db29855ca275a..67c7bfe799c8c96eb945ec9334ad87fa3f7d70f9 100644
--- a/src/core/js/ext_map.js
+++ b/src/core/js/ext_map.js
@@ -24,8 +24,11 @@
 'use strict';
 
 /**
- * caosdb_map module for displaying a geographical map which shows entities at
- * their associated geo location.
+ * @module caosdb_map
+ * @version 0.3
+ *
+ * For displaying a geographical map which shows entities at their associated
+ * geolocation.
  *
  * The configuration for this module has to be stored in
  * `conf/ext/json/ext_map.json` and comply with the {@link MapConfig} type
diff --git a/src/core/js/ext_references.js b/src/core/js/ext_references.js
index 68a52a023e49d8e9f092883817ad82b1da1c87cd..b62fcaac7a7b503fa8d96ef4fb046887efd111b1 100644
--- a/src/core/js/ext_references.js
+++ b/src/core/js/ext_references.js
@@ -1,17 +1,38 @@
-/**
- * Resolve References
- * Alexander Schlemmer, 11/2018
+/*
+ * ** header v3.0
+ * This file is a part of the CaosDB Project.
+ *
+ * Copyright (C) 2018 Alexander Schlemmer
+ * Copyright (C) 2019-2020 IndiScale GmbH (info@indiscale.com)
+ * Copyright (C) 2019-2020 Timm Fitschen (t.fitschen@indiscale.com)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * ** end header
  */
 
 
-/*!
+/*
  * Check if an element is out of the viewport
+ *
  * (c) 2018 Chris Ferdinandi, MIT License, https://gomakethings.com
  *
- * @param  {Node}  elem The element to check
- * @return {Object}     A set of booleans for each side of the element
+ * https://gomakethings.com/how-to-check-if-any-part-of-an-element-is-out-of-the-viewport-with-vanilla-js/
+ *
+ * @param {Node} elem - The element to check
+ * @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) {
 
     // Get element's bounding
@@ -21,85 +42,390 @@ var isOutOfViewport = function (elem) {
     var out = {};
     out.top = bounding.top < 0;
     out.left = bounding.left < 0;
-    out.bottom = bounding.bottom > (window.innerHeight || document.documentElement.clientHeight);
-    out.right = bounding.right > (window.innerWidth || document.documentElement.clientWidth);
-    out.any = out.top || out.left || out.bottom || out.right;
-    out.all = out.top && out.left && out.bottom && out.right;
+    out.bottom = bounding.bottom > (window.innerHeight ||
+        document.documentElement.clientHeight);
+    out.right = bounding.right >
+        (window.innerWidth || document.documentElement.clientWidth);
+    out.any =
+        out.top || out.left || out.bottom || out.right;
+    out.all = out.top &&
+        out.left && out.bottom && out.right;
     return out;
 };
 
+
 /**
- * Check if an element is inside of the viewport on the vertical axis.
- *
- * Returns true if any of the element's bounding box's top or bottom edges are in the
- * viewport.
+ * @module awi_references
+ * @version 0.1
  *
- * @param {HTMLElement} elem - the element to check
- * @return {boolean}
+ * Special functionality for AWI. Should be removed from the main repository in
+ * the future.
  *
+ * @author Timm Fitschen
  */
-var is_in_viewport_vertically = function (elem) {
-    var out = isOutOfViewport(elem);
-    return !(out.top || out.bottom);
+var awi_references = new function () {
+
+    var logger = log.getLogger("awi_references");
+
+
+    this.find_bag_of_sample = async function (id) {
+        return await
+        this._find_ice_sample_back_ref(id, "Bag");
+    }
+
+
+    this.find_ice_core_of_sample = async function (id) {
+        return await
+        this._find_ice_sample_back_ref(id, "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 (resolve_references.is_child(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)
+    }
+
+
+    this.get_icecore = async function (bag) {
+        var id = getEntityID(bag);
+        var icecore = (await query(
+                "SELECT name FROM IceCore WHICH REFERENCES " +
+                id))[0];
+        var bag_number = getProperty(bag, "Number", false);
+        var ret = {
+                "data": {
+                    "bag": bag_number
+                }
+            };
+        if (!icecore) {
+            ret["text"] =
+                `${id} (Bag ${bag_number}, no Ice Core)`;
+        } else {
+            ret["text"] = `${id} (Ice Core ${getEntityName(icecore)}, Bag ${bag_number})`;
+            ret["data"]["icecore"] = getEntityName(icecore);
+        }
+        return ret;
+    }
+
+
+    this.get_bag_and_icecore = async function (sample) {
+        var id =
+            getEntityID(sample);
+        var bag = await awi_references.find_bag_of_sample(id);
+        var ret = {};
+        if (!bag) {
+            var icecore = await awi_references.find_ice_core_of_sample(id);
+            if (!icecore) {
+                ret["text"] = `${id} (Sample w/o Bag or Ice Core)`;
+            } else {
+                ret["text"] = `${id} (Ice Core ${getEntityName(icecore)}, no Bag)`;
+                ret["data"] = {
+                    "icecore": getEntityName(icecore)
+                };
+            }
+        } else {
+            return await awi_references.get_icecore(bag);
+        }
+        return ret;
+    }
+
+
+    this.summarize_subsamples = function (ref_infos) {
+        logger.trace("enter summarize_subsamples ", ref_infos);
+        var icecores = {};
+        for (const ref_info of ref_infos) {
+            const icecore_name = ref_info.data.icecore || "none";
+            if (!icecores[icecore_name]) {
+                icecores[icecore_name] = [];
+            }
+            const bagnumber = parseInt(ref_info.data.bag, 10);
+            icecores[icecore_name].push(bagnumber);
+        }
+        var ret = "";
+        var last = "";
+        const pretty_bag_numbers = reference_list_summary
+            .simplify_integer_numbers;
+        for (const icecore_name of
+                Object.keys(icecores)) {
+            if (icecore_name === "none") {
+                last =
+                    `<div class="casodb-f-resolve-reference-summary-plain">Bags without IceCore: ${pretty_bag_numbers(icecores[icecore_name])}</div>`;
+            } else {
+                ret +=
+                    `<div class="caosdb-f-resolve-reference-summary-plain">IceCore: ${icecore_name} (Bags: ${pretty_bag_numbers(icecores[icecore_name])})</div>`;
+            }
+        }
+        return ret.length + last.length > 0 ?
+            '<b>Summary</b>' + ret + last : "";
+    }
+
+
+    this.summarize_box_content = function (ref_infos) {
+        logger.trace("enter summarize_box_content ", ref_infos);
+        return awi_references.summarize_subsamples(ref_infos);
+    }
 }
 
 /**
- * Check if an element is inside of the viewport on the horizontal axis.
- *
- * This criterion is very much different from the vertical correspondent:
- * It looks for the parent which contains the list scroll bar for caosdb values.
- * Then it is checked whether the element's bounding box is visible inside the scroll box.
+ * @module reference_list_summary
+ * @version 0.1
  *
- * @param {HTMLElement} elem - the element to check
- * @return {boolean}
+ * For generating short summaries of LIST properties. This module is used by
+ * the resolve_references module.
  *
+ * @author Timm Fitschen
  */
-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) {
-        var boundel = elem.getBoundingClientRect();
-        var boundscroll = scrollbox.getBoundingClientRect();
-        var leftcrit = boundel.right > boundscroll.left;
-        var rightcrit = boundel.left < boundscroll.right;
-        return leftcrit && rightcrit;
-    } else {
-        return true;
+var reference_list_summary = new function () {
+
+    var logger = log.getLogger("reference_list_summary");
+
+    /** Return a condensed string representation of an array of integers.
+     *
+     * @example simplify_integer_numbers([1,2,3,5,8,9,10]) returns "1-3, 5,
+     * 8-10"
+     *
+     * @example simplify_integer_numbers([]) returns ""
+     *
+     * @example simplify_integer_numbers([1]) returns "1"
+     *
+     * @example simplify_integer_numbers([1,2]) returns "1, 2" because an array
+     * with two elements gets a special treatment.
+     *
+     * The array may be unsorted.  simplify_integer_numbers([1,2,3]) returns
+     * "1-3" simplify_integer_numbers([2,1,3]) returns "1-3" as well
+     *
+     * The array may contain duplicates.  simplify_integer_numbers([1,2,2,3])
+     * returns "1-3"
+     *
+     * @param {numbers} array - unsorted array of integers, possibly with
+     * duplicates.  @return {string} a condensed string representation of the
+     * array.
+     */
+    this.simplify_integer_numbers = function (array) {
+        logger.trace("enter simplify_integer_numbers", array);
+        var set = Array.from(new Set(array));
+
+        if (set.length === 0) {
+            return ""
+        } else if (set.length === 1) {
+            return `${set[0]}`;
+        }
+
+        // sort numerically
+        set.sort((a, b) => a - b);
+
+        if (set.length === 2) {
+            return `${set[0]}, ${set[1]}`;
+        }
+
+
+        var ret = `${set[0]}`;
+        var last = undefined;
+        // set[0];
+
+        // e.g. [1,2,3,4,5,8,9,10];
+        for (const next of set) {
+            // append '-' to summarize consecutive numbers
+            if (next - last === 1 && !ret.endsWith("-")) {
+                ret += "-";
+            }
+
+            if (next - last > 1) {
+
+                if (ret.endsWith("-")) {
+                    // close previous interval and start new
+                    ret += `${last}, ${next}`;
+                } else {
+                    // no previous interval, start interval.
+                    ret += `, ${next}`;
+                }
+            } else if (next === set[set.length - 1]) {
+                // finish interval if next is last item
+                ret += next;
+                break;
+            }
+
+
+            last = next;
+
+        }
+
+        // e.g. "1-5, 8-10"
+        return ret;
+    }
+
+    /**
+     * Generate a summary of all reference_infos by calling the callback
+     * function of the first reference_info in the array.
+     *
+     * The summary is appended to the summary_container if available and
+     * returned (for testing purposes).
+     *
+     * Returns undefined, if ref_infos is empty or does not come with a
+     * callback function.
+     *
+     * @param {reference_info[]} ref_infos - array of reference_infos.
+     * @param {HTMLElement} summary_container - the summary is appended to this
+     *     element.
+     * @return {HTMLElement|string} generated summary
+     */
+    this.generate = function (ref_infos, summary_container) {
+        logger.trace("enter generate", ref_infos);
+        if (ref_infos.length > 0 &&
+            typeof ref_infos[0].callback === "function") {
+            const summary =
+                ref_infos[0].callback(ref_infos);
+            if (summary && summary_container) {
+                $(summary_container).append(summary);
+            }
+            logger.trace("leave generate", summary);
+            return summary;
+        }
+        logger.trace("leave generate, return undefined");
+        return undefined;
     }
 }
 
 
 /**
- * The resolve_references module.
+ * @module resolve_references
+ * @version 0.2
+ *
+ * Original implementation by Alexander Schlemmer, 11/2018
+ *
+ * @author Timm Fitschen
+ * @author Alexander Schlemmer
  */
 var resolve_references = new function () {
 
+    var logger = log.getLogger("resolve_references");
+
+    var _scroll_timeout = undefined;
+
+    /**
+     * Scroll listener calls {@link resolve_references.update_visible_references} 500 milliseconds after the
+     * last scroll event.
+     */
+    var scroll_listener = () => {
+        if (_scroll_timeout) {
+            clearTimeout(_scroll_timeout);
+        }
+        _scroll_timeout = setTimeout(function () {
+            resolve_references.update_visible_references();
+        }, 500);
+    };
+
+
+    /**
+     * Initilize the scroll listener which triggers the resolution of the
+     * entity ids and trigger it for the first time in order to resolve all
+     * visible references.
+     */
     this.init = function () {
-        this.update_visible_references();
+        scroll_listener();
+
+        // mainly for vertical scrolling
+        $(window).scroll(scroll_listener);
+
+        // for horizontal scrolling. is this still necessary when lists are
+        // loaded in one go?
+        $(".caosdb-value-list").scroll(scroll_listener);
+    }
+
+    /**
+     * Check if an element is inside of the viewport on the vertical axis.
+     *
+     * Returns true if any of the element's bounding box's top or bottom edges are
+     * in the viewport.
+     *
+     * @param {HTMLElement} elem - the element to check @return {boolean}
+     *
+     */
+    this.is_in_viewport_vertically = function (elem) {
+        var out =
+            isOutOfViewport(elem);
+        return !(out.top || out.bottom);
+    }
+
+    /** Check if an element is inside of the viewport on the horizontal axis.
+     *
+     * This criterion is very much different from the vertical correspondent: It
+     * looks for the parent which contains the list scroll bar for caosdb values.
+     * Then it is checked whether the element's bounding box is visible inside the
+     * scroll box.
+     *
+     * @param {HTMLElement} elem - the element to check @return {boolean}
+     *
+     */
+    this.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) {
+            var boundel = elem.getBoundingClientRect();
+            var boundscroll = scrollbox.getBoundingClientRect();
+            var leftcrit = boundel.right > boundscroll.left;
+            var rightcrit = boundel.left < boundscroll.right;
+            return leftcrit && rightcrit;
+        } else {
+            return true;
+        }
     }
 
 
+    /**
+     * Return the name of a person as firstname + lastname
+     */
     this.get_person_str = function (el) {
         var valpr = getProperties(el);
         if (valpr == undefined) {
             return;
         }
-        return valpr.filter(valprel => valprel.name.toLowerCase() == "firstname")[0].value +
-            " " + valpr.filter(valprel => valprel.name.toLowerCase() == "lastname")[0].value;
+        return valpr.filter(valprel =>
+                valprel.name.toLowerCase() == "firstname")[0].value +
+            " " +
+            valpr.filter(valprel => valprel.name.toLowerCase() ==
+                "lastname")[0].value;
     }
 
 
     /**
-     * Return true iff the entity has at least one parent named `rt`.
+     * Return true iff the entity has at least one direct parent named `par`.
      *
-     * @param {HTMLElement} el - entity in HTML representation.
-     * @param {string} rt - parent name.
-     * @returns {boolean}
+     * @param {HTMLElement} entity - entity in HTML representation.  @param
+     * {string} par - parent name.  @return {boolean}
      */
-    this.isChild = function (el, rt) {
-        var pars = getParents(el);
+    this.is_child = function (entity, par) {
+        var pars = getParents(entity);
         for (const par of pars) {
-            if (par.name === rt) {
+            if (par.name === par) {
                 return true;
             }
         }
@@ -107,137 +433,240 @@ var resolve_references = new function () {
     }
 
 
-    this.find_bag_of_sample = async function (el) {
-        return await this._find_ice_sample_back_ref(getEntityID(el), "Bag");
+    /**
+     * Example implementation of a function which returns a reference_info for
+     * referenced `ReferenceObject` entities.  `ReferenceObject` is a mock-up
+     * entity which is used to test this module.
+     */
+    this.get_referenced = function (entity) {
+        return {
+            "text": getEntityName(entity),
+            "data": {
+                "name": getEntityName(entity),
+                "number": getEntityName(entity).replace(
+                    "ReferenceObject-", "")
+            },
+            "callback": function (ref_infos) {
+                var ret = $('<div/>').append("Summary: ");
+                var
+                    array = []
+                for (const ref_info of ref_infos) {
+                    array.push(parseInt(ref_info["data"]["number"],
+                        10));
+                }
+                ret.append(reference_list_summary
+                    .simplify_integer_numbers(array));
+                return ret[0];
+            }
+        };
     }
 
 
-    this.find_ice_core_of_sample = async function (el) {
-        return await this._find_ice_sample_back_ref(getEntityID(el), "IceCore");
-    }
+    this.retrieve = retrieve;
 
+    /**
+     * @typedef {reference_info}
+     * @property {string} text
+     * @property {function} callback - a callback function with one parameter
+     *     (`data`) which generates a summary from the array of data objects
+     *     which are passed to this function.
+     * @property {object} data - an object with properties which can be
+     *     understood and used by the `callback` function.
+     */
 
-    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 a reference_info for an entity.
+     *
+     * TODO refactor to be configurable.  @async @param {string} id - the id of
+     * the entity which is to be resolved.  @return {reference_info}
+     */
+    this.resolve_reference = async function (id) {
+        const entity = (await resolve_references.retrieve(id))[0];
+
+        // TODO handle multiple parents
+        const par = getParents(entity)[0] || {};
+
+        var ret = {
+            "text": id
+        };
+        if (getEntityHeadingAttribute(entity, "path") !==
+            undefined || par.name == "Image") {
+            // show file name
+            var pths = getEntityHeadingAttribute(entity, "path")
+                .split("/");
+            ret["text"] = pths[pths.length - 1];
+        } else if (par.name === "Person") {
+            ret["text"] = this.get_person_str(entity);
+        } else if (par.name === "ExperimentSeries") {
+            ret["text"] =
+                getEntityName(entity);
+        } else if (par.name === "BoxType") {
+            ret["text"] = getEntityName(entity);
+        } else if (par.name === "Loan") {
+            var borrower = await this.retrieve(getProperty(entity, "Borrower"));
+            var loan_state = awi_demo.get_loan_state_string(getProperties(entity));
+            ret["text"] = "Borrowed by " + this.get_person_str(borrower[0]) + " (" + loan_state.replace("_", " ") + ")";
+        } else if (par.name === "SubSample" || par.name === "BagMean" || awi_references.isStripe(par)) {
+            ret = await awi_references.get_bag_and_icecore(entity);
+            ret["callback"] = awi_references.summarize_subsamples;
+        } else if (par.name === "Bag") {
+            ret = await awi_references.get_icecore(entity);
+            ret["callback"] = awi_references.summarize_box_content;
+        } else if (par.name === "Box") {
+            ret["text"] = getProperty(entity, "Number");
+        } else if (par.name === "Palette") {
+            ret["text"] = getProperty(entity, "Number"); } else if (par.name === "Referenced") {
+            ret = this.get_referenced(entity);
+        } else {
+            var name = getEntityName(entity);
+            if (typeof name !== "undefined" && name.length > 0) {
+                ret["text"] = name;
             }
         }
-        return undefined;
-    }
 
-    const _stripe_re = /Stripe$/i;
-    this.isStripe = function(el) {
-        return _stripe_re.test(el.name)
+        return ret;
     }
 
 
-    /*
-     * Function that retrieves meaningful information for a single element.
+    this._target_class = "caosdb-resolve-reference-target";
+
+    /**
+     * Add a target span where the resolved reference information can be shown.
      *
-     * This function needs to be customized for specific implementations.
+     * If the element has a target yet, the existing one is returned.
      *
-     * rs: Element having the class caosdb-resolvable-reference and including a caosdb-resolve-reference target.
+     * @param {HTMLElement} element - where to append the target.
+     * @return {HTMLElement} the new/existing target element.
      */
-    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") {
-            rseditable.textContent = this.get_person_str(el[0]);
-        } else if (pr[0].name === "ExperimentSeries") {
-            rseditable.textContent = getEntityName(el[0]);
-        } else if (pr[0].name === "BoxType") {
-            rseditable.textContent = getEntityName(el[0]);
-        } 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 === "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") {
-            rseditable.textContent = getProperty(el[0], "Number");
+    this.add_target = function (element) {
+        if(element.getElementsByClassName(this._target_class).length > 0){
+            return element.getElementsByClassName(this._target_class);
         } else {
-            if (typeof el[0].name !== "undefined" && el[0].length > 0) {
-                rseditable.textContent = el[0].name;
-            }
+            return $(`<span class="${this._target_class}"/>`)
+                .appendTo(element)[0];
         }
+    }
 
+    /**
+     * Retrieve meaningful information for a single caosdb-f-reference-value
+     * element.
+     *
+     * @param {HTMLElement} rs - resolvable reference link
+     * @return {reference_info} the resolved reference information
+     */
+    this.update_single_resolvable_reference = async function (rs) {
+        const target = this.add_target(rs);
+        const id = getIDfromHREF(rs);
+        target.textContent = id;
+        const resolved_entity_info = await this.resolve_reference(id);
+        target.textContent = resolved_entity_info.text;
+        return resolved_entity_info;
+    }
+
+
+    /**
+     * Add a summary field to the the list_values element.
+     *
+     * A summary field is a DIV element with class
+     * `caosdb-resolve-reference-summary`. The summary field is used to display
+     * a condensed string representation of the referenced entities in the
+     * list-property.
+     *
+     * @param {HTMLElement} list_values - where to add the summary field.
+     * @return {HTMLElement} a summary field.
+     */
+    this.add_summary_field = function (list_values) {
+        const summary = $(
+            '<div class="caosdb-resolve-reference-summary"/>');
+        $(list_values).prepend(summary);
+        return summary[0];
+    }
+
+    this._unresolved_class_name = "caosdb-resolvable-reference";
+
+    this.get_resolvable_properties = function (container) {
+        const _magic_class_name = this._unresolved_class_name;
+        return $(container).find(".caosdb-property-value").has(
+            `.${_magic_class_name}`).toArray();
     }
 
 
     /*
-     * This function updates all references that are inside of the current viewport.
+     * This function updates all references in the body which are inside of the
+     * current viewport.
+     *
+     * If the optional container parameter is given, only elements inside the
+     * container are being processed.
      *
+     * @param {HTMLElement} container
+     * @return {Promise[]} array of promises for ref_infos.
      */
-    this.update_visible_references = async function () {
-        var rs = $(".caosdb-resolvable-reference");
+    this.update_visible_references = function (container) {
+        const property_values = resolve_references
+            .get_resolvable_properties(container || document.body);
+
+        const _magic_class_name = resolve_references
+            ._unresolved_class_name;
 
-        for (var i = 0; i < rs.length; i++) {
-            if (is_in_viewport_vertically(rs[i]) &&
-                is_in_viewport_horizontally(rs[i])) {
-                this.update_single_resolvable_reference(rs[i]);
+        var all_ref_infos = [];
+        for (const property_value of property_values) {
+            const lists = $(property_value).find(
+                ".caosdb-value-list").has(
+                `.${_magic_class_name}`);
+
+            if (lists.length > 0) {
+                const summary_field = resolve_references
+                    .add_summary_field(property_value);
+
+                logger.debug("processing lists of references", lists);
+                for (var i = 0; i < lists.length; i++) {
+                    const list = lists[i];
+                    if (resolve_references
+                            .is_in_viewport_vertically(list)) {
+                        const rs = $(list).find(
+                                `.${_magic_class_name}`)
+                            .toggleClass(_magic_class_name, false);
+                        const ref_infos = [];
+                        for (var j = 0; j < rs.length; j++) {
+                            const ref_info = resolve_references
+                                .update_single_resolvable_reference(rs[j]);
+                            ref_info["index"] = j;
+                            ref_infos.push(ref_info);
+                        }
+
+                        // wait for resolution of references,
+                        // then generate the summary.
+                        Promise.all(ref_infos)
+                            .then(_ref_infos => reference_list_summary
+                                .generate(_ref_infos, summary_field))
+                            .catch(logger.error);
+
+                        all_ref_infos = all_ref_infos.concat(ref_infos);
+                    }
+                }
+            } else {
+                // TODO merge code with the above?
+                const rs = $(property_value).find(
+                    `.${_magic_class_name}`);
+                logger.debug("processing single references", rs);
+                for (var i = 0; i < rs.length; i++) {
+                    if (resolve_references.is_in_viewport_vertically(
+                            rs[i]) &&
+                        resolve_references.is_in_viewport_horizontally(
+                            rs[i])) {
+                        $(rs).toggleClass(_magic_class_name, false);
+                        all_ref_infos.push(resolve_references
+                            .update_single_resolvable_reference(rs[i]));
+                    }
+                }
             }
         }
+
+        return all_ref_infos;
     }
 }
 
 
 $(document).ready(function () {
-    resolve_references.init();
-    var scrollTimeout = undefined;
-    var updatefunc = () => {
-        if (scrollTimeout) {
-            clearTimeout(scrollTimeout);
-        }
-        scrollTimeout = setTimeout(function () {
-            resolve_references.update_visible_references();
-        }, 500);
-    };
-    $(window).scroll(updatefunc);
-    $(".caosdb-value-list").scroll(updatefunc);
+    caosdb_modules.register(resolve_references);
 });
diff --git a/src/core/js/ext_xls_download.js b/src/core/js/ext_xls_download.js
index 37349b35a609c57b3cddd371b3b37ebc9a93fdb2..d8953840c580fa742ef35f6fdd3981748c1e0d35 100644
--- a/src/core/js/ext_xls_download.js
+++ b/src/core/js/ext_xls_download.js
@@ -2,7 +2,8 @@
  * ** header v3.0
  * This file is a part of the CaosDB Project.
  *
- * Copyright (C) 2019 IndiScale GmbH
+ * Copyright (C) 2019 IndiScale GmbH <info@indiscale.com>
+ * Copyright (C) 2019 Timm Fitschen <t.fitschen@indiscale.com>
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Affero General Public License as
@@ -22,28 +23,263 @@
 'use strict';
 
 /**
- * Functions which depend on server-side executable scripts.
+ * @module caosdb_table_export
+ * @version 0.1
  *
- * Dependency: webcaosdb
+ * Convert Entities for TSV and XLS export.
+ *
+ * The XLS part depends on a server-side executable script.
  *
+ * Dependency: webcaosdb
  */
+var caosdb_table_export = new function () {
+
+    var logger = log.getLogger("caosdb_table_export");
+    var TAB = "%09";
+    var NEWLINE = "%0A";
+
+    /**
+     * Hide "Download XLS File" link if the user is not authenticted (i.e.
+     * has the `anonymous` role).
+     */
+    this.init = function() {
+        logger.info("init caosdb_table_export");
+        // TODO with AMD, use userIsAnonymous()
+        if (Array.from(
+            document.getElementsByClassName("caosdb-user-role")).map(
+                el => el.innerText
+            ).filter(el => el == "anonymous").length > 0) {
+            $(".caosdb-v-query-select-data-xsl").parent().hide();
+        }
+    }
+
+
+    /**
+     * Convert the entities of the select-table to a tsv string.
+     *
+     * @param {boolean} raw - if true, the raw entity ids are put into the
+     *     cells. Otherwise, the displayed data is used instead.
+     * @return {string}
+     */
+    this.get_tsv_string = function(raw) {
+        const table = $('.caosdb-select-table');
+        const columns = table.find("th").toArray()
+            .map(e => e.textContent)
+            .filter(e => e.length > 0);
+        // TODO use entity-panel class in table as well (change in query.xsl
+        // and then here)
+        const entities = table.find("tbody tr").toArray();
+        const csv_string = this._get_tsv_string(entities, columns, raw);
+        return csv_string;
+    }
+
+    /**
+     * Convert all entities to a tsv string with the given columns.
+     *
+     * @param {HTMLElement[]} entities - entities which are converted to rows
+     *     of the tsv string.
+     * @param {string[]} columns - array of property names.
+     * @param {boolean} raw - if true, the raw entity ids are put into the
+     *     cells. Otherwise, the displayed data is used instead.
+     * @return {string}
+     */
+    this._get_tsv_string = function (entities, columns, raw) {
+        logger.trace("enter get_tsv_string", entities, columns);
+        var preamble = "data:text/csv;charset=utf-8,";
+        var header = "ID" + TAB + columns.join(TAB) + NEWLINE
+        var rows = caosdb_table_export._get_tsv_rows(entities, columns, raw).join(NEWLINE);
+
+        const ret = `${preamble}${header}${rows}`;
+        logger.trace("leave get_tsv_string", ret);
+        return ret;
+    }
+
+    /**
+     * Return an array of rows with the given columns of the tsv table, one per
+     * entity.
+     *
+     * @param {HTMLElement[]} entities - entities which are converted to rows
+     *     of the tsv string.
+     * @param {string[]} columns - array of property names.
+     * @param {boolean} raw - if true, the raw entity ids are put into the
+     *     cells. Otherwise, the displayed data is used instead.
+     * @return {string[]}
+     */
+    this._get_tsv_rows = function (entities, columns, raw) {
+        var rows = [];
+        for (const entity of entities) {
+            rows.push(this._get_entity_row(entity, columns, raw));
+        }
+        return rows;
+    }
+
+
+    /**
+     * Return different string representations of the property's value.
+     *
+     * Returns an object with three string properties: raw, pretty, summary
+     *
+     * `raw` is just the raw property value string representation which is
+     * returned by the server. In case of list properties, this is a
+     * comma-separated list of the raw strings.
+     *
+     * `pretty` is used only for references and lists of references. It is the
+     * string which is generated by the ext-references module as a replacement
+     * for the entity id. If there is no such replacement, `pretty` is
+     * undefined.
+     *
+     * `summary` is used only for lists of references. It is the string summary
+     * of lists of references which is generated by the ext-references module.
+     * If there is no such replacement, `summary` is undefined.
+     *
+     * @param {HTMLElement} property
+     * @return {object}
+     */
+    this._get_property_value = function(property) {
+        const value_element = $(property)
+            .find(".caosdb-property-value")
+            .first();
+        const raw_value = value_element
+            .find(".caosdb-f-property-single-raw-value")
+            .toArray();
+        const pretty_value = value_element
+            .find(".caosdb-resolve-reference-target")
+            .toArray();
+        var summary_value = value_element
+            .find(".caosdb-resolve-reference-summary")
+            .find(".caosdb-f-resolve-reference-summary-plain")
+            .toArray();
+        if (summary_value.length === 0) {
+            summary_value = value_element
+                .find(".caosdb-resolve-reference-summary").toArray();
+        }
+
+        return {
+            "raw": caosdb_table_export._to_string_value(raw_value),
+            "pretty": caosdb_table_export._to_string_value(pretty_value),
+            "summary": caosdb_table_export._to_string_value(summary_value),
+        };
+    }
+
+    /**
+     * Convert an array of property value elements to string.
+     *
+     * Empty arrays result in an emtpy string.
+     *
+     * One-element-arrays result in the text content of the element.
+     *
+     * N-element-arrays result in the a comma-separated list of the text
+     * content of the elements.
+     *
+     * @param {HTMLElement[]} value_elements
+     * @return {string}
+     */
+    this._to_string_value = function(value_elements) {
+        if (value_elements.length === 0) {
+            return "";
+        } else if (value_elements.length === 1) {
+            return $(value_elements[0])
+                .text();
+        } else {
+            return value_elements
+                .map(e => $(e).text())
+                .join(", ");
+        }
+    }
 
-function _get_csv_string(){
-    const csv_string = document.getElementById("caosdb-f-query-select-data-tsv").getAttribute(
-        "href");
-    if (!csv_string) {
-        return undefined;
+
+    /**
+     * Return an array of cells, one per column, which contain a string
+     * representation of the value of the properties with the same name (as the
+     * column).
+     *
+     * @param {HTMLElement} entity - entity from which the cells are extracted.
+     * @param {string[]} columns - array of property names.
+     * @param {boolean} raw - if true, the raw entity ids are put into the
+     *     cells. Otherwise, the displayed data is used instead.
+     * @return {string[]}
+     */
+    this._get_entity_row = function (entity, columns, raw) {
+        var cells = [getEntityID(entity)];
+        var properties = getProperties(entity);
+
+        for (const column of columns) {
+            var cell = "";
+            for (const property of properties) {
+                if(property.name.toLowerCase() === column.toLowerCase()) {
+                    var value = caosdb_table_export
+                        ._get_property_value(property.html);
+                    if (raw) {
+                        cell = value.raw;
+                    } else if (value.summary) {
+                        cell = value.summary;
+                    } else if (value.pretty) {
+                        cell = value.pretty;
+                    } else {
+                        cell = value.raw;
+                    }
+                }
+            }
+            cells.push(cell);
+        }
+
+        logger.trace("leave _get_entity_row", cells);
+        return cells.join(TAB);
+    }
+
+
+    /**
+     * Open the resulting xls file by setting href to the location of the resulting
+     * file in the server's `Shared` resource and imitate a click.
+     */
+    this._go_to_script_results = function (xls_link, filename) {
+        xls_link.setAttribute(
+            "href",
+            location.protocol + "//" +location.host + "/Shared/" + filename);
+        xls_link.click();
+    }
+
+
+    this._get_csv_string = function (){
+        const raw = $("input#caosdb-table-export-raw-flag-xls").is(":checked");
+        const csv_string = caosdb_table_export.get_tsv_string(raw);
+        //const csv_string = document.getElementById("caosdb-f-query-select-data-tsv").getAttribute(
+            //"href");
+        if (!csv_string) {
+            return undefined;
+        }
+
+        return decodeURIComponent(csv_string.replace(/^data.*utf-8,/, ""));
     }
+}
+
+
+/**
+ * This function is called on click by the link button which says "Download TSV
+ * File".
+ *
+ * It sets the href attribute of the link to a string which gerenates a
+ * downloadable file. All entities of the select-table are included in the
+ * resulting file.
+ */
+function downloadTSV(tsv_link) {
+    const raw = $("input#caosdb-table-export-raw-flag-tsv").is(":checked");
+    const tsv_string = caosdb_table_export.get_tsv_string(raw);
 
-    return decodeURIComponent(csv_string.replace(/^data.*utf-8,/, ""));
+    $(tsv_link).attr("href", tsv_string);
+    return true;
 }
 
 /**
- * Call the server-side script `xls_from_csv.py` and generate a XLS file from
- * TSV string content.
+ * This function is called on click by the link button which says "Download XLS
+ * File".
+ *
+ * It calls the server-side script `xls_from_csv.py` and generate a XLS file
+ * from TSV string content. All entities of the select-table are included in the
+ * resulting file.
  */
 async function downloadXLS(xls_link) {
-    const csv_string = _get_csv_string();
+    const csv_string = caosdb_table_export._get_csv_string();
 
     // remove click event handler which called this function in the first place
     const onClickValue = xls_link.getAttribute("onClick");
@@ -64,18 +300,18 @@ async function downloadXLS(xls_link) {
         }
 
         // set the href in order to download the file and simulate a click.
-        _go_to_script_results(xls_link, filename);
+        caosdb_table_export._go_to_script_results(xls_link, filename);
     } catch (e) {
         globalError(e);
-        // restore the old click handler
+    } finally {
+        // restore the old click handler - hence a new file is generated with each click.
         xls_link.setAttribute("onClick", onClickValue);
-    };
+    }
+
     return true;
 }
 
-function _go_to_script_results(xls_link, filename) {
-    xls_link.setAttribute(
-        "href",
-        location.protocol + "//" +location.host + "/Shared/" + filename);
-    xls_link.click();
-}
+
+$(document).ready(function () {
+    caosdb_modules.register(caosdb_table_export);
+});
diff --git a/src/core/js/form_elements.js b/src/core/js/form_elements.js
index 3c4b195410abc8b58571c99bd4b1673f46368594..28226e80abe4bd392563e03d2d301c3589b669d8 100644
--- a/src/core/js/form_elements.js
+++ b/src/core/js/form_elements.js
@@ -963,7 +963,7 @@ var form_elements = new function () {
          */
         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 + '" />')
+            return $('<div class="form-group caosdb-f-field caosdb-f-entity-property" data-field-name="' + name + '" />')
                 .css({"padding": "0"})[0];
         }
 
diff --git a/src/core/js/webcaosdb.js b/src/core/js/webcaosdb.js
index c213a0ebcd766a1d1c2502cc8aaae2fc014ff840..56f00429e63b8454a88c952e39bb5666a57ca583 100644
--- a/src/core/js/webcaosdb.js
+++ b/src/core/js/webcaosdb.js
@@ -76,6 +76,7 @@ this.navbar = new function() {
         }
         $(button_elem).toggleClass("navbar-btn", true);
         $(button_elem).toggleClass("btn", true);
+        $(button_elem).toggleClass("btn-link", true);
 
         // bind click
         if(typeof click_callback === "function") {
@@ -331,6 +332,7 @@ this.transformation = new function() {
      * Transform the server's response with multiple entities into their
      * html representation. The parameter `xml` may also be a Promise.
      *
+     * @async
      * @param {XMLDocument} xml
      * @return {HTMLElement[]} an array of HTMLElements.
      */
@@ -384,9 +386,14 @@ this.transformation = new function() {
      * Retrieve the entity.xsl script and modify it such that we can use it
      * without the context of the main.xsl.
      *
+     * @param {string} [root_template] a string to be injected as a root
+     *     template. Optional. If none is given a default template is injected
+     *     which calls the entity templates which perform the transformation of
+     *     xml entities to the canocical HTML representation.
      * @return {XMLDocument} xslt script
      */
-    this.retrieveEntityXsl = async function _rEX() {
+    this.retrieveEntityXsl = async function _rEX(root_template) {
+        const _root = root_template || '<xsl:template match="/"><div class="root"><xsl:apply-templates select="Response/*" mode="entities"/></div></xsl:template>';
         var entityXsl = await transformation.retrieveXsltScript("entity.xsl");
         var commonXsl = await transformation.retrieveXsltScript("common.xsl");
         var errorXsl = await transformation.retrieveXsltScript('messages.xsl');
@@ -394,7 +401,7 @@ this.transformation = new function() {
         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>');
+        xslt = injectTemplate(xslt, _root);
         return xslt;
     }
 
@@ -1083,29 +1090,65 @@ var hintMessages = new function() {
         $(entity).find(".alert").show();
     }
 
+    /**
+     * Replace all (too-big) message divs with (tiny) badges.
+     *
+     * When an entity has many errors, warnings and info messages they will
+     * likely litter up the entity panel (and the property rows). This function
+     * replaces all message divs with tiny, clickable badges showing just the
+     * message type (error, warning, info). On click they are replaced with the
+     * original message div again.
+     *
+     * This method can be called on an entity panel, but also on any element
+     * which contains message divs at any depth in the DOM tree.
+     *
+     * @param {HTMLElement} entity - the element where to replace the
+     *     messages.
+     */
     this.hintMessages = function(entity) {
+
+        // TODO refactor such that the function can detect whether a message is
+        // replaced yet instead of "unhintMessage"ing all of them first and do
+        // all the work again.
         this.unhintMessages(entity);
-        var messageType = {
+
+        // dictionary for mapping bootstrap classes to message types
+        const messageType = {
             "info": "info",
             "warning": "warning",
             "danger": "error"
         };
         for (let alrt in messageType) {
+
+            // find all message divs
             $(entity).find(".alert.alert-" + alrt).each(function(index) {
                 var messageElem = $(this);
+
+                // this way only one badge is shown, even if there are more
+                // than one message of the given type.
                 if (messageElem.parent().find(".caosdb-f-message-badge.alert-" + alrt).length == 0) {
-                    messageElem.parent('.caosdb-messages, .caosdb-property-row').prepend('<button title="Click here to show the ' + messageType[alrt] + ' messages of the last transaction." class="btn caosdb-f-message-badge badge alert-' + alrt + '">' + messageType[alrt] + '</button>');
+
+                    // TODO why is the message badge added to the .caosdb-v-property-row here? shouldn't .caosdb-messages suffice?
+                    messageElem.parent('.caosdb-messages, .caosdb-v-property-row').prepend('<button title="Click here to show the ' + messageType[alrt] + ' messages of the last transaction." class="btn caosdb-v-message-badge caosdb-f-message-badge badge alert-' + alrt + '">' + messageType[alrt] + '</button>');
                     messageElem.parent().find(".caosdb-f-message-badge.alert-" + alrt).on("click", function(e) {
+
+                        // TODO use remove here instead of hide?
                         $(this).hide();
-                        console.log(this);
-                        console.log(alrt);
+
+                        // TODO use messageElem.show() here? or even append
+                        // here and remove in the next statement?
                         messageElem.parent().find('.alert.alert-' + alrt).show()
                     });
+                } else {
+                    // badge found
+                    // TODO add counter to the badge for each message type
                 }
+
                 messageElem.hide();
             });
         }
 
+        // moves all badges into one div with text-right
         if ($(entity).find(".caosdb-messages > .caosdb-f-message-badge").length > 0) {
             var div = $('<div class="text-right" style="padding: 5px 16px;"/>');
             div.prependTo($(entity).find(".caosdb-messages"));
@@ -1113,7 +1156,9 @@ var hintMessages = new function() {
             messageBadges.detach();
             div.append(messageBadges);
         }
-        $(entity).find(".caosdb-property-row > .caosdb-f-message-badge").addClass("pull-right");
+
+        // see the other TODO above: why is there a .caosdb-v-message-badge inside the caosdb-v-property-row without a div.caosdb-message
+        $(entity).find(".caosdb-v-property-row > .caosdb-v-message-badge").addClass("pull-right");
     }
 }
 
diff --git a/src/core/xsl/entity.xsl b/src/core/xsl/entity.xsl
index 621755604a87b9bb7fbb9d8125ce414a11757d32..26f0c11677e328c22eb70440772b3cb53bc4c146 100644
--- a/src/core/xsl/entity.xsl
+++ b/src/core/xsl/entity.xsl
@@ -232,7 +232,7 @@
   </xsl:template>
   <!-- PROPERTIES -->
   <xsl:template match="Property" mode="entity-body">
-    <li class="list-group-item caosdb-property-row">
+    <li class="list-group-item caosdb-v-property-row caosdb-f-entity-property">
       <xsl:attribute name="id">
         <xsl:value-of select="generate-id()"/>
       </xsl:attribute>
@@ -304,42 +304,46 @@
         <xsl:choose>
           <xsl:when test="$reference='true' and normalize-space($value)!=''">
             <!-- this is a reference -->
-            <a class="btn btn-default btn-sm caosdb-resolvable-reference">
+            <a class="btn btn-default btn-sm caosdb-f-reference-value caosdb-resolvable-reference">
               <xsl:attribute name="href">
                 <xsl:value-of select="concat($entitypath,normalize-space($value))"/>
               </xsl:attribute>
-              <span class="caosdb-id caosdb-id-button">
+              <xsl:element name="span">
+              <xsl:attribute name="class">
+                <xsl:value-of select="'caosdb-f-property-single-raw-value caosdb-id caosdb-id-button'"/>
+                </xsl:attribute>
                 <xsl:value-of select="normalize-space($value)"/>
-              </span>
-              <span class="caosdb-resolve-reference-target"/>
+              </xsl:element>
             </a>
           </xsl:when>
           <xsl:when test="$boolean='true'">
             <xsl:element name="span">
               <xsl:attribute name="class">
-                <xsl:value-of select="concat('caosdb-boolean-',normalize-space($value)='TRUE')"/>
+                <xsl:value-of select="concat('caosdb-f-property-single-raw-value caosdb-boolean-',normalize-space($value)='TRUE')"/>
               </xsl:attribute>
               <xsl:value-of select="normalize-space($value)"/>
             </xsl:element>
           </xsl:when>
           <xsl:otherwise>
-            <span class="caosdb-property-text-value">
+            <xsl:element name="span">
+              <xsl:attribute name="class">
+                <xsl:value-of select="'caosdb-f-property-single-raw-value caosdb-property-text-value'"/>
+              </xsl:attribute>
               <xsl:call-template name="trim">
                 <xsl:with-param name="str">
                   <xsl:value-of select="$value"/>
                 </xsl:with-param>
               </xsl:call-template>
-            </span>
+            </xsl:element>
           </xsl:otherwise>
         </xsl:choose>
       </xsl:when>
       <xsl:otherwise>
-        <span class="caosdb-property-text-value"/>
+        <span class="caosdb-f-property-single-raw-value caosdb-property-text-value"/>
       </xsl:otherwise>
     </xsl:choose>
   </xsl:template>
   <xsl:template match="Property" mode="property-reference-value-list">
-    <xsl:param name="reference"/>
     <div class="caosdb-value-list">
       <xsl:element name="div">
         <xsl:attribute name="class">btn-group btn-group-sm caosdb-overflow-content</xsl:attribute>
diff --git a/src/core/xsl/entity_palette.xsl b/src/core/xsl/entity_palette.xsl
index bbe3a7dc7e5c2dff2cd0442a0e5702434a6d6e83..aea42a8750a74f509557bcf842e4d1f8dc97bef9 100644
--- a/src/core/xsl/entity_palette.xsl
+++ b/src/core/xsl/entity_palette.xsl
@@ -3,11 +3,11 @@
   <xsl:output method="html"/>
 
   <xsl:template match="/Response">
-    <div class="btn-group-vertical">
+    <div class="btn-group-vertical caosdb-v-editmode-btngroup">
       <button type="button" class="btn btn-default caosdb-f-edit-panel-new-button new-property">Create new Property</button>
       <button type="button" class="btn btn-default caosdb-f-edit-panel-new-button new-recordtype">Create new RecordType</button>
     </div>
-    <div title="Drag and drop Properties from this panel to the Entities on the left." class="panel panel-default">
+    <div title="Drag and drop Properties from this panel to the Entities on the left." class="panel panel-default caosdb-v-editmode-existing">
       <div class="panel-heading">
         <h5>Existing Properties</h5>
       </div>
@@ -23,7 +23,7 @@
         </ul>
       </div>
     </div>
-    <div title="Drag and drop RecordTypes from this panel to the Entities on the left." class="panel panel-default">
+    <div title="Drag and drop RecordTypes from this panel to the Entities on the left." class="panel panel-default caosdb-v-editmode-existing">
       <div class="panel-heading">
         <h5>Existing RecordTypes</h5>
       </div>
diff --git a/src/core/xsl/navbar.xsl b/src/core/xsl/navbar.xsl
index a0b299e7668ca87153761396f7b93a61e9169978..3f4b268cb8c210012567196d1a6b2e25b2259940 100644
--- a/src/core/xsl/navbar.xsl
+++ b/src/core/xsl/navbar.xsl
@@ -150,6 +150,7 @@
         <xsl:with-param name="class" select="'alert-info'"/>
       </xsl:apply-templates>
     </nav>
+    <div class="container" id="subnav"/>
   </xsl:template>
   <xsl:template match="Role" name="caosdb-user-roles">
     <div class="caosdb-user-role">
diff --git a/src/core/xsl/query.xsl b/src/core/xsl/query.xsl
index 7aa9a80cf425767b9d49d9ed52170618f66317f7..0015fe8d5beef5f1fc67c894b5b1421e2aaab4bb 100644
--- a/src/core/xsl/query.xsl
+++ b/src/core/xsl/query.xsl
@@ -101,16 +101,16 @@
                   </div>
                   <div class="modal-body">
                     <p>
-                      <a id="caosdb-f-query-select-data-tsv" download="selected_data.tsv">
-                        <xsl:attribute name="href">
-                            <xsl:value-of select="'data:text/csv;charset=utf-8,'"/><xsl:for-each select="Selector"><xsl:value-of select="@name"/><xsl:if test="position()!=last()"><xsl:value-of select="'%09'"/></xsl:if></xsl:for-each><xsl:for-each select="/Response/*[@id]"><xsl:call-template name="select-table-row-plain"><xsl:with-param name="entity-id" select="@id"/></xsl:call-template></xsl:for-each></xsl:attribute>
+                      <a id="caosdb-f-query-select-data-tsv" onclick="downloadTSV(this)" href="#selected_data.tsv" download="selected_data.tsv">
                         Download TSV File
                       </a>
+                      <span class="checkbox" style="margin-top: 0; display: inline; position: absolute; right: 10px"><label><input type="checkbox" name="raw" id="caosdb-table-export-raw-flag-tsv" title="Export raw entity ids instead of the visible page content."/>raw</label></span>
                     </p>
                     <p>
-                    <a class="caosdb-v-query-select-data-xsl" onclick="downloadXLS(this)" href="#selected_data.xsl" download="">
-                      Download XLS File
+                      <a class="caosdb-v-query-select-data-xsl" onclick="downloadXLS(this)" href="#selected_data.xsl" download="">
+                        Download XLS File
                       </a>
+                      <span class="checkbox" style="margin-top: 0; display: inline; position: absolute; right: 10px"><label><input type="checkbox" name="raw" id="caosdb-table-export-raw-flag-xls" title="Export raw entity ids instead of the visible page content."/>raw</label></span>
                     </p>
                     <hr/>
                     <p>
@@ -172,6 +172,9 @@
   <xsl:template name="select-table-row">
     <xsl:param name="entity-id"/>
     <tr>
+      <xsl:attribute name="data-entity-id">
+        <xsl:value-of select="$entity-id"/>
+      </xsl:attribute>
       <td>
         <xsl:call-template name="entity-link">
           <xsl:with-param name="entity-id" select="$entity-id"/>
@@ -188,8 +191,11 @@
   <xsl:template name="select-table-cell">
     <xsl:param name="entity-id"/>
     <xsl:param name="field-name"/>
-    <td>
-      <div class="caosdb-v-property-value">
+    <td class="caosdb-f-entity-property">
+      <xsl:attribute name="data-property-name">
+        <xsl:value-of select="$field-name"/>
+      </xsl:attribute>
+      <div class="caosdb-property-value caosdb-v-property-value">
         <xsl:choose>
           <xsl:when test="/Response/*[@id=$entity-id]/@*[translate(name(),$uppercase, $lowercase)=$field-name]">
             <xsl:value-of select="/Response/*[@id=$entity-id]/@*[translate(name(), $uppercase, $lowercase)=$field-name]"/>
@@ -201,23 +207,6 @@
       </div>
     </td>
   </xsl:template>
-  <!-- For CSV download -->
-    <!-- This block is responsible for generating the downloadable TSV. -->
-  <xsl:template name="select-table-row-plain">
-    <xsl:param name="entity-id"/>
-    <xsl:value-of select="'%0A'"/><xsl:for-each select="/Response/Query/Selection/Selector"><xsl:call-template name="select-table-cell-plain"><xsl:with-param name="entity-id" select="$entity-id"/><xsl:with-param name="field-name" select="translate(@name, $uppercase, $lowercase)"/></xsl:call-template><xsl:if test="position()!=last()"><xsl:value-of select="'%09'"/></xsl:if></xsl:for-each></xsl:template>
-  <xsl:template name="select-table-cell-plain">
-    <xsl:param name="entity-id"/>
-    <xsl:param name="field-name"/>
-    <xsl:choose>
-      <xsl:when test="/Response/*[@id=$entity-id]/@*[translate(name(),$uppercase, $lowercase)=$field-name]">
-        <xsl:value-of select="/Response/*[@id=$entity-id]/@*[translate(name(), $uppercase, $lowercase)=$field-name]"/>
-      </xsl:when>
-      <xsl:when test="/Response/*[@id=$entity-id]/Property[translate(@name, $uppercase, $lowercase)=$field-name]">
-        <xsl:apply-templates mode="property-value-plain" select="/Response/*[@id=$entity-id]/Property[translate(@name, $uppercase, $lowercase)=$field-name]"></xsl:apply-templates>
-      </xsl:when>
-    </xsl:choose>
-  </xsl:template>
   <xsl:template name="caosdb-query-panel">
     <div class="container caosdb-query-panel">
       <form class="panel" id="caosdb-query-form" method="GET">
diff --git a/src/ext/js/fileupload.js b/src/ext/js/fileupload.js
index 4d223116e7d771e56404076dbb2e64e48832b000..103af1914f2786ef6d8edd5987b04d6de215e2fc 100644
--- a/src/ext/js/fileupload.js
+++ b/src/ext/js/fileupload.js
@@ -233,7 +233,7 @@ var fileupload = new function() {
 
         // add global listener for start_edit event
         document.body.addEventListener(edit_mode.start_edit.type, function(e) {
-            $(e.target).find(".caosdb-properties .caosdb-property-row").each(function(idx) {
+            $(e.target).find(".caosdb-properties .caosdb-f-entity-property").each(function(idx) {
                 fileupload.create_upload_app(this);
             });
         }, true);
diff --git a/test/core/html/form_elements_example_1.html b/test/core/html/form_elements_example_1.html
index eacc71f5346b1f5469d84fc409ce21ed0aff459f..d2d6fd34cd8e11f139d87f1cad6263fe482d72a8 100644
--- a/test/core/html/form_elements_example_1.html
+++ b/test/core/html/form_elements_example_1.html
@@ -1,6 +1,6 @@
 <div class="caosdb-f-form-wrapper">
   <form action="#" class="form-horizontal" method="post" name="sample_creation.py">
-    <div class="form-group caosdb-f-field caosdb-property-row caosdb-f-form-field-required caosdb-f-form-field-cached" data-field-name="ice_core" data-groups="(part1)">
+    <div class="form-group caosdb-f-field caosdb-f-entity-property caosdb-f-form-field-required caosdb-f-form-field-cached" data-field-name="ice_core" data-groups="(part1)">
       <label class="control-label col-sm-3" data-property-name="ice_core" for="ice_core">Ice Core</label>
       <div class="col-sm-9">
         <div class="dropdown bootstrap-select form-control bs3">
@@ -61,7 +61,7 @@
         </div>
       </div>
     </div>
-    <div class="form-group caosdb-f-field caosdb-property-row" data-field-name="orig_sample_type" data-groups="(part1)">
+    <div class="form-group caosdb-f-field caosdb-f-entity-property" data-field-name="orig_sample_type" data-groups="(part1)">
       <label class="control-label col-sm-3" data-property-name="orig_sample_type" for="orig_sample_type">Original Sample Type</label>
       <div class="col-sm-9">
         <div class="dropdown bootstrap-select form-control bs3">
@@ -128,7 +128,7 @@
         </div>
       </div>
     </div>
-    <div class="form-group caosdb-f-field caosdb-property-row caosdb-f-field-disabled" data-field-name="logging_protocol" data-groups="(part2)" style="display: none;">
+    <div class="form-group caosdb-f-field caosdb-f-entity-property caosdb-f-field-disabled" data-field-name="logging_protocol" data-groups="(part2)" style="display: none;">
       <label class="control-label col-sm-3" data-property-name="logging_protocol" for="logging_protocol">Logging Protocol</label>
       <div class="col-sm-9">
         <div class="dropdown bootstrap-select form-control bs3">
@@ -154,7 +154,7 @@
         </div>
       </div>
     </div>
-    <div class="form-group caosdb-f-field caosdb-property-row" data-field-name="cutting_protocol" data-groups="(part3)" style="">
+    <div class="form-group caosdb-f-field caosdb-f-entity-property" data-field-name="cutting_protocol" data-groups="(part3)" style="">
       <label class="control-label col-sm-3" data-property-name="cutting_protocol" for="cutting_protocol">Cutting Protocol</label>
       <div class="col-sm-9">
         <div class="dropdown bootstrap-select form-control bs3">
@@ -191,28 +191,28 @@
         </div>
       </div>
     </div>
-    <div class="form-group caosdb-f-field caosdb-property-row caosdb-f-form-field-required" data-field-name="cutting_date" data-groups="(part2)(part3)" style="">
+    <div class="form-group caosdb-f-field caosdb-f-entity-property caosdb-f-form-field-required" data-field-name="cutting_date" data-groups="(part2)(part3)" style="">
       <label class="control-label col-sm-3" data-property-name="cutting_date" for="cutting_date">Cutting Date</label>
       <div class="caosdb-property-value col-sm-9">
         <input class="form-control caosdb-property-text-value" name="cutting_date" type="date"/>
       </div>
     </div>
-    <div class="form-group caosdb-f-field caosdb-property-row" data-field-name="bag_numbers" data-groups="(part2)(part3)" style="">
+    <div class="form-group caosdb-f-field caosdb-f-entity-property" data-field-name="bag_numbers" data-groups="(part2)(part3)" style="">
       <label class="control-label col-sm-3" data-property-name="bag_numbers" for="bag_numbers">Bag Numbers</label>
-      <div class="caosdb-f-field caosdb-property-row" data-field-name="bag_numbers_from">
+      <div class="caosdb-f-field caosdb-f-entity-property" data-field-name="bag_numbers_from">
         <label class="control-label col-sm-1" data-property-name="bag_numbers_from" for="bag_numbers_from">from</label>
         <div class="caosdb-property-value col-sm-3">
           <input class="form-control caosdb-property-text-value" name="bag_numbers_from" step="1" type="number"/>
         </div>
       </div>
-      <div class="caosdb-f-field caosdb-property-row" data-field-name="bag_numbers_to">
+      <div class="caosdb-f-field caosdb-f-entity-property" data-field-name="bag_numbers_to">
         <label class="control-label col-sm-1 col-sm-offset-1" data-property-name="bag_numbers_to" for="bag_numbers_to">to</label>
         <div class="caosdb-property-value col-sm-3">
           <input class="form-control caosdb-property-text-value" name="bag_numbers_to" step="1" type="number"/>
         </div>
       </div>
     </div>
-    <div class="form-group caosdb-f-field caosdb-property-row" data-field-name="box_of_new_samples" data-groups="(part2)(part3)" style="">
+    <div class="form-group caosdb-f-field caosdb-f-entity-property" data-field-name="box_of_new_samples" data-groups="(part2)(part3)" style="">
       <label class="control-label col-sm-3" data-property-name="box_of_new_samples" for="box_of_new_samples">Box of New Samples</label>
       <div class="col-sm-9">
         <div class="dropdown bootstrap-select form-control bs3">
@@ -285,7 +285,7 @@
         </div>
       </div>
     </div>
-    <div class="form-group caosdb-f-field caosdb-property-row" data-field-name="new_subsamples_selector" data-groups="(part3)" style="">
+    <div class="form-group caosdb-f-field caosdb-f-entity-property" data-field-name="new_subsamples_selector" data-groups="(part3)" style="">
       <label class="control-label col-sm-3" data-property-name="new_subsamples_selector" for="new_subsamples_selector">New Subsamples</label>
       <div class="col-sm-9">
         <div class="dropdown bootstrap-select show-tick form-control bs3">
@@ -333,24 +333,24 @@
     <div class="col-sm-9 col-sm-offset-3 row" style="background-color: rgb(255, 255, 255);">
       <fieldset class="form-inline caosdb-f-form-elements-subform" data-subform-name="new_subsamples" id="new_subsamples_6335" style="padding-left: 15px; padding-right: 15px;">
         <legend>Subsample</legend>
-        <div class="form-group caosdb-f-field caosdb-property-row" data-field-name="type">
+        <div class="form-group caosdb-f-field caosdb-f-entity-property" data-field-name="type">
           <div class="caosdb-property-value col-sm-9">
             <input class="form-control caosdb-property-text-value" name="type" type="hidden" value="6335"/>
           </div>
         </div>
-        <div class="form-group caosdb-f-field caosdb-property-row" data-field-name="width" style="margin-left: 15px; margin-right: 15px;">
+        <div class="form-group caosdb-f-field caosdb-f-entity-property" data-field-name="width" style="margin-left: 15px; margin-right: 15px;">
           <label class="control-label" data-property-name="width" for="width">width (cm)</label>
           <div class="caosdb-property-value">
             <input class="form-control caosdb-property-text-value" name="width" step="any" type="number"/>
           </div>
         </div>
-        <div class="form-group caosdb-f-field caosdb-property-row" data-field-name="height" style="margin-left: 15px; margin-right: 15px;">
+        <div class="form-group caosdb-f-field caosdb-f-entity-property" data-field-name="height" style="margin-left: 15px; margin-right: 15px;">
           <label class="control-label" data-property-name="height" for="height">height (cm)</label>
           <div class="caosdb-property-value">
             <input class="form-control caosdb-property-text-value" name="height" step="any" type="number"/>
           </div>
         </div>
-        <div class="form-group caosdb-f-field caosdb-property-row" data-field-name="rectangular" style="margin-left: 15px; margin-right: 15px;">
+        <div class="form-group caosdb-f-field caosdb-f-entity-property" data-field-name="rectangular" style="margin-left: 15px; margin-right: 15px;">
           <label class="control-label" data-property-name="rectangular" for="rectangular">rectangular</label>
           <div class="caosdb-property-value">
             <input class="caosdb-property-text-value" name="rectangular" type="checkbox"/>
@@ -359,24 +359,24 @@
       </fieldset>
       <fieldset class="form-inline caosdb-f-form-elements-subform" data-subform-name="new_subsamples" id="new_subsamples_6338" style="padding-left: 15px; padding-right: 15px;">
         <legend>PP_Sample</legend>
-        <div class="form-group caosdb-f-field caosdb-property-row" data-field-name="type">
+        <div class="form-group caosdb-f-field caosdb-f-entity-property" data-field-name="type">
           <div class="caosdb-property-value col-sm-9">
             <input class="form-control caosdb-property-text-value" name="type" type="hidden" value="6338"/>
           </div>
         </div>
-        <div class="form-group caosdb-f-field caosdb-property-row" data-field-name="width" style="margin-left: 15px; margin-right: 15px;">
+        <div class="form-group caosdb-f-field caosdb-f-entity-property" data-field-name="width" style="margin-left: 15px; margin-right: 15px;">
           <label class="control-label" data-property-name="width" for="width">width (cm)</label>
           <div class="caosdb-property-value">
             <input class="form-control caosdb-property-text-value" name="width" step="any" type="number"/>
           </div>
         </div>
-        <div class="form-group caosdb-f-field caosdb-property-row" data-field-name="height" style="margin-left: 15px; margin-right: 15px;">
+        <div class="form-group caosdb-f-field caosdb-f-entity-property" data-field-name="height" style="margin-left: 15px; margin-right: 15px;">
           <label class="control-label" data-property-name="height" for="height">height (cm)</label>
           <div class="caosdb-property-value">
             <input class="form-control caosdb-property-text-value" name="height" step="any" type="number"/>
           </div>
         </div>
-        <div class="form-group caosdb-f-field caosdb-property-row" data-field-name="rectangular" style="margin-left: 15px; margin-right: 15px;">
+        <div class="form-group caosdb-f-field caosdb-f-entity-property" data-field-name="rectangular" style="margin-left: 15px; margin-right: 15px;">
           <label class="control-label" data-property-name="rectangular" for="rectangular">rectangular</label>
           <div class="caosdb-property-value">
             <input class="caosdb-property-text-value" name="rectangular" type="checkbox"/>
diff --git a/test/core/js/modules/edit_mode.js.js b/test/core/js/modules/edit_mode.js.js
index 98fd3b2f9aac4f23f52edc6a57d6740d41600914..1ec57f31d7da949e6310958ab4516206e073d811 100644
--- a/test/core/js/modules/edit_mode.js.js
+++ b/test/core/js/modules/edit_mode.js.js
@@ -258,7 +258,7 @@ QUnit.test("make_property_editable", function(assert) {
     // test for correct parsing of datatypes
     var testEntity = this.testEntity_make_property_editable_1;
 
-    for (var element of $(testEntity).find('.caosdb-property-row')) {
+    for (var element of $(testEntity).find('.caosdb-f-entity-property')) {
         edit_mode.make_property_editable(element);
     }
 
diff --git a/test/core/js/modules/entity.xsl.js b/test/core/js/modules/entity.xsl.js
index b6d461ce9461ca8d9dd2f9d82c5b580a268aa7c9..a853c36556c5482e0bd0b1f751755e88b871d25e 100644
--- a/test/core/js/modules/entity.xsl.js
+++ b/test/core/js/modules/entity.xsl.js
@@ -187,7 +187,7 @@ QUnit.test("single-value template with reference property.", function(assert) {
         'value': '',
         'reference': 'true',
         'boolean': 'false'
-    })), "<span xmlns=\"http://www.w3.org/1999/xhtml\" class=\"caosdb-property-text-value\"></span>", "empty value produces empty span.");
+    })), "<span xmlns=\"http://www.w3.org/1999/xhtml\" class=\"caosdb-f-property-single-raw-value caosdb-property-text-value\"></span>", "empty value produces empty span.");
     let link = callTemplate(this.entityXSL, 'single-value', {
         'value': '1234',
         'reference': 'true',
diff --git a/test/core/js/modules/ext_map.js.js b/test/core/js/modules/ext_map.js.js
index 88a66a1c11c6691d7f4db53a502a5734ab4cbb3f..972691364c5bfd402313e6b3ad8fd29ed572b4ed 100644
--- a/test/core/js/modules/ext_map.js.js
+++ b/test/core/js/modules/ext_map.js.js
@@ -30,12 +30,12 @@ QUnit.module("ext_map.js", {
         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="list-group-item caosdb-f-entity-property">
     <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="list-group-item caosdb-f-entity-property">
     <div class="caosdb-property-name">` +
             lng + `</div>
     <div class="caosdb-property-value">5.23</div>
diff --git a/test/core/js/modules/ext_references.js.js b/test/core/js/modules/ext_references.js.js
index 1789f31676590ff9293bdaa26cc3bd133f89824e..b45a3deb99000dd173c755f7d5f0c9e32f4e24f1 100644
--- a/test/core/js/modules/ext_references.js.js
+++ b/test/core/js/modules/ext_references.js.js
@@ -25,6 +25,23 @@
 /* SETUP ext_references module */
 QUnit.module("ext_references.js", {
     before: function(assert) {
+        // dummy test functions
+        var logger = log.getLogger("ext_references.js")
+        logger.setLevel("debug");
+
+        resolve_references.retrieve = async function (id) {
+            logger.debug("retrieve", id);
+            return transformation.transformEntities(str2xml(
+                `<Response>
+                  <Record id="${id}" name="ReferenceObject-${id}">
+                    <Parent name="Referenced"/>
+                  </Record>
+                </Response>`
+            ));
+        }
+
+        resolve_references.is_in_viewport_horizontally = () => true;
+        resolve_references.is_in_viewport_vertically = () => true;
     }
 });
 
@@ -40,8 +57,43 @@ QUnit.test("get_person_str", function(assert){
     assert.ok(resolve_references.get_person_str);
 });
 
-QUnit.test("update_single_resolvable_reference", function(assert){
-    assert.ok(resolve_references.update_single_resolvable_reference);
+QUnit.test("update_visible_references", async function(assert){
+    const f = resolve_references.update_visible_references;
+
+    const test_property = $(`<div class="caosdb-property-value">
+        <div data-entity-id="15"
+            class="${resolve_references._unresolved_class_name}">
+          <span class="${resolve_references._target_class}"/></span>
+        </div>
+    </div><div class="caosdb-property-value">
+        <div class="caosdb-value-list">
+            <div data-entity-id="16"
+                class="${resolve_references._unresolved_class_name}">
+              <span class="${resolve_references._target_class}"/></span>
+            </div>
+            <div data-entity-id="17"
+                class="${resolve_references._unresolved_class_name}">
+              <span class="${resolve_references._target_class}"/></span>
+            </div>
+        </div>
+    </div>`);
+
+    assert.equal(test_property.find(`.${resolve_references._unresolved_class_name}`).length, 3, "is unresolved");
+
+    const ref_infos = f($("<div/>").append(test_property));
+
+    assert.equal(test_property.find(`.${resolve_references._unresolved_class_name}`).length, 0, "is resolved");
+
+
+    assert.equal(ref_infos.length, 3, "three ref_infos");
+    var done = assert.async();
+    Promise.all(ref_infos).then(ref_infos => {
+        assert.equal(ref_infos[1].data.name.replace(/[0-9]*/g, ""), "ReferenceObject-", "has data");
+        assert.ok(typeof ref_infos[1].callback === "function",
+            "has callback");
+        done();
+    });
+
 });
 
 
@@ -81,7 +133,62 @@ QUnit.test("check_structure_html", function(assert){
         }
         // but the first element does NOT:
         assert.equal(elms[0].parentElement.parentElement.classList.contains("caosdb-value-list"), false);
-        
+
         done();
     }, err => {console.log(err);});
 });
+
+QUnit.module("reference_list_summary");
+
+QUnit.test("simplify_integer_numbers", function(assert) {
+    var f = reference_list_summary.simplify_integer_numbers;
+    assert.equal(f([1]), "1");
+    assert.equal(f([1,1,1]), "1");
+    assert.equal(f([1,2]), "1, 2");
+    assert.equal(f([2,1]), "1, 2");
+    assert.equal(f([2,1,2]), "1, 2");
+    assert.equal(f([2,1,2]), "1, 2");
+    assert.equal(f([1,2,4]), "1-2, 4");
+    assert.equal(f([1,3,4,5]), "1, 3-5");
+    assert.equal(f([1,2,3,5,8,9,10]), "1-3, 5, 8-10");
+    assert.equal(f([1,2,3,5,7,8,10]), "1-3, 5, 7-8, 10");
+    assert.equal(f([1,3,5,7,8,10]), "1, 3, 5, 7-8, 10");
+    assert.equal(f([1,2,4,5,7,8,10]), "1-2, 4-5, 7-8, 10");
+    assert.equal(f([1,2,4,5,7,9,10]), "1-2, 4-5, 7, 9-10");
+});
+
+
+QUnit.test("generate", function(assert) {
+    const f = reference_list_summary.generate;
+    const summary_container = $("<div/>");
+
+
+    // without callback function
+    const ref_infos_no_cb = [ {
+        "data": { "dummy": "dummy_data1" },
+    }, {
+        "data": { "dummy": "dummy_data2" },
+    }, ];
+
+    assert.ok(typeof f(ref_infos_no_cb) === "undefined", "no cb returns undefined");
+    f(ref_infos_no_cb, summary_container);
+    assert.equal(summary_container.children().length, 0, "no cb doesn't append to summary container.");
+    assert.equal(summary_container.text(), "", "no text in container");
+
+    // with callback function
+    const ref_infos_with_cb = [ {
+        "callback": function (ref_infos) { 
+            return ref_infos.map(i => i.data.dummy).join(",");
+        },
+        "data": { "dummy": "dummy_data3" },
+    }, {
+        "data": { "dummy": "dummy_data4" },
+    }, ];
+
+    const summary = "dummy_data3,dummy_data4";
+
+    assert.equal(f(ref_infos_with_cb, summary_container), summary, "callback returns summary");
+    assert.equal(summary_container.text(), summary, "summary in container");
+
+
+});
diff --git a/test/core/js/modules/ext_xls_download.js.js b/test/core/js/modules/ext_xls_download.js.js
index 360b389725eec2bc6a4e4115e9cc46cd91e33d69..4f9dc6c59b156f1f2265acb4b315887536667194 100644
--- a/test/core/js/modules/ext_xls_download.js.js
+++ b/test/core/js/modules/ext_xls_download.js.js
@@ -21,7 +21,48 @@
  */
 'use strict';
 
-QUnit.module("ext_xls_download");
+/**
+ * Transform the server's response of a select query request into the html
+ * tabular representation. The parameter `xml` may also be a Promise.
+ *
+ * Only used for testing purposes.
+ *
+ * @async
+ * @param {XMLDocument} xml
+ * @return {HTMLElement} DIV.caosdb-query-response
+ */
+transformation.transformSelectTable = async function _tST (xml) {
+    var root_template = '<xsl:template match="/"><div class="root"><xsl:apply-templates select="Response/Query" mode="query-results"/></div></xsl:template>';
+    var queryXsl = await transformation.retrieveXsltScript("query.xsl");
+    var entityXsl = await transformation.retrieveEntityXsl(root_template);
+    insertParam(entityXsl, "uppercase", 'abcdefghijklmnopqrstuvwxyz');
+    insertParam(entityXsl, "lowercase", 'ABCDEFGHIJKLMNOPQRSTUVWXYZ');
+    var xsl = transformation.mergeXsltScripts(entityXsl, [queryXsl]);
+    var html = await asyncXslt(xml, xsl);
+    return $(html).find('div.root')[0];
+}
+
+QUnit.module("ext_xls_download", {
+    before: function (assert) {
+        var done = assert.async();
+        var testCase = "table_export/test_case_select_table_1.xml";
+        connection
+            .get("xml/"+ testCase)
+            .then(xml => {
+                return transformation.
+                    //transformEntities(xml);
+                    transformSelectTable(xml);
+            }).then(entities => {
+                this.test_case_1 = entities;
+                done();
+            }).catch(err => {
+                console.error(err);
+                done();
+            });
+
+    }
+});
+
 
 {
   const sleep = function sleep(ms) {
@@ -31,16 +72,18 @@ QUnit.module("ext_xls_download");
   QUnit.test("call downloadXLS", async function(assert) {
     var done = assert.async(2);
 
+    // mock server response (successful)
     connection.runScript = async function(exec, param){
         assert.equal(exec, "xls_from_csv.py", "call xls_from_csv.py");
         done();
         return str2xml('<response><script code="0" /><stdout>bla</stdout></response>');
     }
 
-    _go_to_script_results = function(xls_link, filename) {
+    caosdb_table_export._go_to_script_results = function(xls_link, filename) {
         xls_link.setAttribute(
             "href",
             location.protocol + "//" +location.host + "/Shared/" + filename);
+        assert.equal(filename, "bla", "filename correct");
         done();
     }
 
@@ -58,3 +101,61 @@ QUnit.module("ext_xls_download");
     tsv_data.remove();
   });
 }
+
+QUnit.test("_get_property_value", function(assert) {
+    var f = caosdb_table_export._get_property_value;
+
+    assert.equal(f().pretty, "", "No pretty content");
+    assert.equal(f().raw, "", "No raw content");
+    assert.equal(f().summary, "", "No summary");
+});
+
+
+QUnit.test("_get_tsv_string", function(assert) {
+    const table = this.test_case_1;
+    const entities = $(table).find("tbody tr").toArray();
+    assert.equal(entities.length, 2, "two example entities");
+
+    var f = caosdb_table_export._get_tsv_string
+    var tsv_string = f(entities, ["Bag", "Number"], true);
+    assert.equal(tsv_string, "data:text/csv;charset=utf-8,ID%09Bag%09Number%0A242%096366, 6406, 6407, 6408, 6409, 6410, 6411, 6412, 6413%090284%0A2112%09%091101", "tsv generated");
+});
+
+QUnit.test("_get_property_value", function (assert) {
+    const table = this.test_case_1;
+    const entity = $(table).find("tbody tr")[0];
+
+    const property = getProperties(entity)[0].html;
+
+    var f = caosdb_table_export._get_property_value;
+    var ret = f(property);
+    assert.equal(ret.pretty, "", "pretty is empty");
+    assert.equal(ret.summary, "", "summary is empty");
+    assert.equal(ret.raw, "6366, 6406, 6407, 6408, 6409, 6410, 6411, 6412, 6413", "raw contains ids");
+
+    var unresolved = property
+        .getElementsByClassName(
+            resolve_references
+                ._unresolved_class_name);
+
+    for (const el of unresolved) {
+        var target = resolve_references
+            .add_target(el);
+        $(target).append("bla");
+    }
+
+    ret = f(property);
+    assert.equal(ret.pretty, "bla, bla, bla, bla, bla, bla, bla, bla, bla", "pretty is empty");
+    assert.equal(ret.summary, "", "summary is empty");
+    assert.equal(ret.raw, "6366, 6406, 6407, 6408, 6409, 6410, 6411, 6412, 6413", "raw contains ids");
+
+
+    var summary = resolve_references.add_summary_field(property);
+    $(summary).find(".coasdb-property-value").append("summary bla");
+
+    ret = f(property);
+    assert.equal(ret.pretty, "bla, bla, bla, bla, bla, bla, bla, bla, bla", "pretty shows list of reference info text");
+    assert.equal(ret.summary, "", "summary is empty");
+    assert.equal(ret.raw, "6366, 6406, 6407, 6408, 6409, 6410, 6411, 6412, 6413", "raw contains ids");
+
+});
diff --git a/test/core/js/modules/webcaosdb.js.js b/test/core/js/modules/webcaosdb.js.js
index 7e02d1d98270f0c5f696bcbe39651fe0b3064c03..874b68e137121ac886082212cda564ec53da3005 100644
--- a/test/core/js/modules/webcaosdb.js.js
+++ b/test/core/js/modules/webcaosdb.js.js
@@ -1860,4 +1860,15 @@ QUnit.test("annotation module", function(assert) {
     assert.ok(annotation.initNewCommentApp, "initNewCommentApp exists.");
 });
 
-/* MISC FUNCTIONS */
+
+/* MODULE navbar */
+QUnit.module("webcaosdb.js - navbar", {
+});
+
+QUnit.test("test button classes", function(assert) {
+    var result = $(navbar.add_button("TestButton")).children().first()
+    assert.ok(result.hasClass("navbar-btn"), "has class navbar-btn");
+    assert.ok(result.hasClass("btn"), "has class btn");
+    assert.ok(result.hasClass("btn-link"), "has class btn-link");
+    assert.equal(result.text(), "TestButton", "text is correct");
+});
diff --git a/test/core/xml/table_export/test_case_select_table_1.xml b/test/core/xml/table_export/test_case_select_table_1.xml
new file mode 100644
index 0000000000000000000000000000000000000000..12f26180b89bdf3f2ba3280d4546783f54bfa8ea
--- /dev/null
+++ b/test/core/xml/table_export/test_case_select_table_1.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Response>
+  <Query string="Select Bag, Number from Box with Bag" results="2">
+    <Role />
+    <Entity>Box</Entity>
+    <Selection>
+      <Selector name="Bag" />
+      <Selector name="Number" />
+    </Selection>
+  </Query>
+  <Record id="242">
+    <Property id="117" name="Number" datatype="TEXT" importance="FIX">
+      0284
+    </Property>
+    <Property id="104" name="Bag" datatype="Bag" importance="FIX">
+      6366
+    </Property>
+    <Property id="104" name="Bag" datatype="LIST&lt;Bag&gt;" importance="FIX">
+      <Value>6406</Value>
+      <Value>6407</Value>
+    </Property>
+    <Property id="104" name="Bag" datatype="LIST&lt;Bag&gt;" importance="FIX">
+      <Value>6408</Value>
+      <Value>6409</Value>
+    </Property>
+    <Property id="104" name="Bag" datatype="LIST&lt;Bag&gt;" importance="FIX">
+      <Value>6410</Value>
+      <Value>6411</Value>
+    </Property>
+    <Property id="104" name="Bag" datatype="LIST&lt;Bag&gt;" importance="FIX">
+      <Value>6412</Value>
+      <Value>6413</Value>
+    </Property>
+    <Property id="104" name="Bag" datatype="LIST&lt;Bag&gt;" importance="FIX">
+    </Property>
+  </Record>
+  <Record id="2112">
+    <Property id="117" name="Number" datatype="TEXT" importance="FIX">
+      1101
+    </Property>
+    <Property id="104" name="Bag" datatype="LIST&lt;Bag&gt;" importance="FIX">
+    </Property>
+  </Record>
+</Response>