diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 07f72610a0165bb0c221671c922a0ce9232bfbe3..548f86457c3216f5eed7b75b4b81d7f048351248 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -21,9 +21,10 @@
 # along with this program. If not, see <https://www.gnu.org/licenses/>.
 
 variables:
-   CI_REGISTRY_IMAGE: $CI_REGISTRY/caosdb/caosdb-webui/testenv
-   # When using dind, it's wise to use the overlayfs driver for
-   # improved performance.
+  DEPLOY_REF: dev
+  CI_REGISTRY_IMAGE: $CI_REGISTRY/caosdb/caosdb-webui/testenv
+  # When using dind, it's wise to use the overlayfs driver for
+  # improved performance.
 
 image: $CI_REGISTRY_IMAGE:latest
 
@@ -66,10 +67,11 @@ trigger_build:
     - echo $TOKEN
     - /usr/bin/curl -X POST
        -F token=$DEPLOY_TRIGGER_TOKEN
+       -F "variables[F_BRANCH]=$CI_COMMIT_REF_NAME"
        -F "variables[WEBUI]=$CI_COMMIT_REF_NAME"
        -F "variables[TriggerdBy]=WEBUI"
        -F "variables[TriggerdByHash]=$CI_COMMIT_SHORT_SHA"
-       -F ref=dev https://gitlab.indiscale.com/api/v4/projects/14/trigger/pipeline
+       -F ref=$DEPLOY_REF https://gitlab.indiscale.com/api/v4/projects/14/trigger/pipeline
 
 # Build a docker image in which tests for this repository can run
 build-testenv:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 18052e718ec41d79b7d95c71ce296d83d50856f2..cdc0bbfb39e53824ff52ea968b8926f4eeb413b3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,12 +16,71 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ### Fixed
 
+### Security (in case of vulnerabilities)
+
+## [v0.2.1] - 2020-09-07
+
+### Added (for new features, dependecies etc.)
+
+* `ext_jupyterdrag` (v0.1) module for dragging entities into jupyter notebooks.
+
+### Changed (for changes in existing functionality)
+
+### Deprecated (for soon-to-be removed features) 
+
+### Removed (for now removed features)
+
+### Fixed
+
+### Security (in case of vulnerabilities)
+
+## [v0.2] - 2020-09-02
+
+### Added (for new features, dependecies etc.)
+
+* Build variable `EXT_REFERENCES_CUSTOM_REFERENCE_RESOLVER`. The value of this
+  variable must be module which has at least a `resolve(id)` function, which
+  returns a `reference_info` object for further processing by the
+  `resolve_references` module.
+* `ext_sss_markdown` module for pretty display of server-side scripting stdout.
+  See module docstring for more information.
+* `ext_trigger_crawler_form` module which generates a form for triggering the
+  crawler. See module docstring for more info.
+* Support for deeply nested selectors in SELECT queries.
+* edit_mode supports reference properties when inserting/updating properties
+  (See [#83](https://gitlab.com/caosdb/caosdb-webui/-/issues/83) and
+  [#55](https://gitlab.com/caosdb/caosdb-webui/-/issues/55)).
+* new `navbar.add_tool` method which adds a button to submenu of the navbar.
+* new CSS classes for the styling of the default image and video preview of the
+  `ext_bottom_line` module: `caosdb-v-bottom-line-image-preview` and
+  `caosdb-v-bottom-line-video-preview`.
+* basic support for entity versioning:
+    * entity.xsl generates a versioning button which opens a versioning info modal.
+    * `preview` also works for versioned references
+    * `edit_mode` prevents the user from editing old versions of an entity.
+
+### Changed (for changes in existing functionality)
+- The `navbar.add_button` signatur changed to `add_button(button, options)`.
+  See the doc string of that method for more information.
+- added a layout argument to the create_plot function of ext_bottom_line
+
+### Deprecated (for soon-to-be removed features) 
+
+* css class `caosdb-property-text-value` is deprecated because different
+  functionality interpreted it differently and most of the uses of this class
+  have already been removed and replaced by specialized classes.
+
+### Removed (for now removed features)
+
+### Fixed
+
 * #101 - loading of A LOT of references in `ext_references` slows down the
   webui or even freezes the brower.
+* Fixed a bug where the Tour would lose its state upon page reload.
 
 ### Security (in case of vulnerabilities)
 
-## v0.2-rc.1 (2020-04-10)
+## [v0.2-rc.1] - 2020-04-10
 
 ### Added (for new features, dependecies etc.)
 
diff --git a/build.properties.d/00_default.properties b/build.properties.d/00_default.properties
index 6185725aefd266b45ef5024bd3e47a78801fef1f..23b1e9ff929b030750f6cab0014f22cd0d6b7cf4 100644
--- a/build.properties.d/00_default.properties
+++ b/build.properties.d/00_default.properties
@@ -39,10 +39,12 @@
 # overridden in the makefile in any case.
 
 ##############################################################################
-# Modules enabled by default
+# Modules enabled/disabled by default
 ##############################################################################
 BUILD_MODULE_EXT_PREVIEW=ENABLED
 BUILD_MODULE_EXT_RESOLVE_REFERENCES=ENABLED
+BUILD_MODULE_EXT_SSS_MARKDOWN=DISABLED
+BUILD_MODULE_EXT_TRIGGER_CRAWLER_FORM=DISABLED
 
 ##############################################################################
 # Navbar properties
@@ -76,4 +78,7 @@ BUILD_FOOTER_CUSTOM_ELEMENT_TWO=
 # BUILD_FOOTER_CUSTOM_ELEMENT_TWO=$(cat footer_element_2.html)
 BUILD_CUSTOM_IMPRINT='<p> Put an imprint note here </p>'
 
-
+##############################################################################
+# ext_trigger_crawler_form properties
+##############################################################################
+BUILD_MODULE_EXT_TRIGGER_CRAWLER_FORM_TOOLBOX="Tools"
diff --git a/makefile b/makefile
index d9091f32004cfe9c25e28c9e75a3cc18471ac73b..50dd72b500724bbe40801292ab39c3c5cf7ce70f 100644
--- a/makefile
+++ b/makefile
@@ -92,7 +92,7 @@ run-test-server: test
 	$(MISC_DIR)/unit_test_http_server.py $(PORT) $(TIMEOUT) False $(PUBLIC_DIR)
 
 keep-test-server: test
-	$(MISC_DIR)/unit_test_http_server.py $(PORT) $(TIMEOUT) True $(PUBLIC_DIR)
+	$(MISC_DIR)/unit_test_http_server.py $(PORT) -1 True $(PUBLIC_DIR)
 
 run-qunit: test
 	$(foreach exec, firefox Xvfb xwd,\
diff --git a/misc/select_query_test_data.py b/misc/select_query_test_data.py
new file mode 100755
index 0000000000000000000000000000000000000000..3ecf0a47a56641c1c2a05ea3346e9a4ef2ff0e35
--- /dev/null
+++ b/misc/select_query_test_data.py
@@ -0,0 +1,61 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#
+# ** header v3.0
+# This file is a part of the CaosDB Project.
+#
+# Copyright (C) 2020 IndiScale GmbH <info@indiscale.com>
+# Copyright (C) 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
+
+import caosdb
+
+
+d = caosdb.execute_query("FIND Test*")
+if len(d) > 0:
+    d.delete()
+
+rt_house = caosdb.RecordType("TestHouse").insert()
+rt_house.description = "A House"
+caosdb.RecordType("TestWindow").insert()
+rt_person = caosdb.RecordType("TestPerson").insert()
+caosdb.RecordType("TestParty").insert()
+caosdb.Property("TestHeight", datatype=caosdb.DOUBLE, unit="ft").insert()
+caosdb.Property("TestDate", datatype=caosdb.DATETIME).insert()
+
+window = caosdb.Record().add_parent("TestWindow")
+window.add_property("TestHeight", 20.5, unit="ft")
+window.insert()
+
+owner = caosdb.Record("The Queen").add_parent("TestPerson").insert()
+
+house = caosdb.Record("Buckingham Palace")
+house.description = "A rather large house"
+house.add_parent("TestHouse")
+house.add_property(rt_person, name="TestOwner", value=owner)
+house.add_property("TestWindow", window).insert()
+
+g1 = caosdb.Record().add_parent("TestPerson").insert()
+g2 = caosdb.Record().add_parent("TestPerson").insert()
+g3 = caosdb.Record().add_parent("TestPerson").insert()
+
+party = caosdb.Record("Diamond Jubilee of Elizabeth II").add_parent("TestParty")
+party.add_property(rt_house, name="Location", value=house)
+party.add_property("TestDate", "2012-02-06")
+party.add_property(rt_person, datatype=caosdb.LIST(rt_person), name="Guests",
+                   value = [g1, g2, g3])
+party.insert()
diff --git a/misc/unit_test_http_server.py b/misc/unit_test_http_server.py
index c52f84e4ced6da2289dc853c6b65c3c9dc6ed202..68e6a7434a584a26a242e5891329f3fb6c6d159f 100755
--- a/misc/unit_test_http_server.py
+++ b/misc/unit_test_http_server.py
@@ -115,7 +115,7 @@ class UnitTestHTTPServer(HTTPServer):
     def __init__(self, server_address, timeout, ignore_done):
         super(UnitTestHTTPServer, self).__init__(server_address,
                                                  UnitTestsHandler)
-        self.timeout = timeout
+        self.timeout = timeout if timeout > 0 else None
         self.ignore_done = ignore_done
         self._keep_running = True
         self._exit_message = None
@@ -139,8 +139,12 @@ class UnitTestHTTPServer(HTTPServer):
         Start the server and handle request until the `done` resource is being
         called or a timeout occurs.
         """
-        print(("starting UnitTestHTTPServer on {address} with {t}s "
-               "timeout.").format(address=self.server_address, t=self.timeout))
+        timeout_str = "."
+        if self.timeout is not None:
+            timeout_str = " with {}s timeout.".format(self.timeout)
+        print(("starting UnitTestHTTPServer on {address}"
+               "{timeout_str}").format(address=self.server_address,
+                                       timeout_str=timeout_str))
         self._keep_running = True
         while self._keep_running:
             self.handle_request()
diff --git a/misc/versioning_test_data.py b/misc/versioning_test_data.py
new file mode 100755
index 0000000000000000000000000000000000000000..eaa83e46f61ea2f20263b487e4bb42c37678c94f
--- /dev/null
+++ b/misc/versioning_test_data.py
@@ -0,0 +1,93 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#
+# ** header v3.0
+# This file is a part of the CaosDB Project.
+#
+# Copyright (C) 2020 IndiScale GmbH <info@indiscale.com>
+# Copyright (C) 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
+
+""" Insert test data for manually testing the versioning feature in the
+webinterface.
+"""
+# pylint: disable=no-member
+
+import caosdb
+
+# clean
+old = caosdb.execute_query("FIND Test*")
+if len(old) > 0:
+    old.delete()
+
+# data model
+
+rt = caosdb.RecordType("TestRT")
+rt.insert()
+
+
+# test data
+## record with several versions
+rec1 = caosdb.Record("TestRecord1-firstVersion").add_parent("TestRT")
+rec1.description = "This is the first version."
+rec1.insert()
+
+rec1.name = "TestRecord1-secondVersion"
+rec1.description = "This is the second version."
+rec1.update()
+if rec1.version:
+    ref = str(rec1.id) + "@" + rec1.version.id
+else:
+    ref = rec1.id
+
+rec1.name = "TestRecord1-thirdVersion"
+rec1.description = "This is the third version."
+rec1.update()
+
+rec2 = caosdb.Record("TestRecord2").add_parent("TestRT")
+rec2.description = ("This record references the TestRecord1 in the second "
+                    "version where the name and the description of the record "
+                    "should indicate the referenced version.")
+rec2.add_property("TestRT", ref)
+rec2.insert()
+
+rec3 = caosdb.Record("TestRecord3").add_parent("TestRT")
+rec3.description = ("This record references the TestRecord1 without "
+                    "specifying a version. Therefore the latest (at least the "
+                    "third version) is being openend when you click on the "
+                    "reference link.")
+rec3.add_property("TestRT", rec1.id)
+rec3.insert()
+
+rec4 = caosdb.Record("TestRecord4").add_parent("TestRT")
+rec4.description = ("This record has a list of references to several versions "
+                    "of TestRecord1. The first references the record without "
+                    "specifying the version, the other each reference a "
+                    "different version of that record.")
+if rec1.version:
+    rec4.add_property("TestRT", datatype=caosdb.LIST("TestRT"),
+                      value=[rec1.id,
+                             str(rec1.id) + "@HEAD",
+                             str(rec1.id) + "@HEAD~1",
+                             str(rec1.id) + "@HEAD~2"])
+else:
+    rec4.add_property("TestRT", datatype=caosdb.LIST("TestRT"),
+                      value=[rec1.id,
+                             str(rec1.id),
+                             str(rec1.id),
+                             str(rec1.id)])
+rec4.insert()
diff --git a/src/core/css/webcaosdb.css b/src/core/css/webcaosdb.css
index d598e8175c249fc1457e04498ffa1af6c3f0654d..05e36429b7dfe6144915b522de2fa6c36375c484 100644
--- a/src/core/css/webcaosdb.css
+++ b/src/core/css/webcaosdb.css
@@ -27,6 +27,47 @@ body {
     flex-direction: column;
 }
 
+.caosdb-v-navbar-toolbox li a:hover,
+.caosdb-v-navbar-toolbox li input:hover,
+.caosdb-v-navbar-toolbox li button:hover {
+    background-color: #f5f5f5;
+}
+
+.caosdb-v-navbar-toolbox li a,
+.caosdb-v-navbar-toolbox li input,
+.caosdb-v-navbar-toolbox li button {
+    display: block;
+    padding: 3px 20px;
+    border: none;
+    width: 100%;
+    text-align: left;
+    margin-top: 0px;
+    margin-bottom: 0px;
+    text-decoration: none;
+    background-color: transparent;
+    white-space: nowrap;
+}
+
+.caosdb-v-bottom-line-image-preview > img {
+    max-width: 400px;
+    max-height: 280px;
+}
+
+.caosdb-v-bottom-line-image-preview {
+    text-align: center;
+}
+
+button.caosdb-v-entity-version-button {
+    height: 15px;
+}
+
+.caosdb-v-entity-header-buttons-list > * {
+    margin: 0;
+    margin-left: 8px;
+    padding: 0;
+    vertical-align: middle;
+}
+
 .caosdb-v-main-col {
     flex-grow: 1;
     max-width: 90vw;
@@ -49,6 +90,9 @@ body {
     display: initial;
 }
 
+/* DEPRECATED css class .caosdb-property-text-value - Use
+* .caosdb-f-property-single-raw-value or introduce new
+* .caosdb-v-property-text-value */
 .caosdb-property-text-value {
     white-space: pre-line;
 }
diff --git a/src/core/js/autocomplete.js b/src/core/js/autocomplete.js
index 542849c5e77f764717bad759ca6076550cd85b2f..345d06b37bc6456645ef02947c70b3f2f5d6c44f 100644
--- a/src/core/js/autocomplete.js
+++ b/src/core/js/autocomplete.js
@@ -101,6 +101,9 @@ var autocomplete = new function() {
 	};
 };
 
-//$(document).ready(function () {
-//   caosdb_modules.register(autocomplete);
-//});
+// this will be replaced by require.js in the future.
+$(document).ready(function() {
+    if ("${BUILD_MODULE_EXT_AUTOCOMPLETE}" === "ENABLED") {
+        caosdb_modules.register(autocomplete);
+    }
+});
diff --git a/src/core/js/caosdb.js b/src/core/js/caosdb.js
index 648e8249a258a9f723325ec34c230e07a5900514..7fe4e9441822bf6c706a09c95aa65f0a2fef06a8 100644
--- a/src/core/js/caosdb.js
+++ b/src/core/js/caosdb.js
@@ -135,17 +135,19 @@ function getEntityRole(element) {
 }
 
 /**
- * Return the unit of element.
- * If the corresponding data-attribute can not be found undefined is returned.
- * @return A string containing the datatype of the element.
+ * Return the unit of entity or undefined.
+ *
+ * @param {HTMLElement} entity
+ * @return {String}
  */
-function getEntityUnit(element) {
-    var res = $(element).find("input.caosdb-f-entity-unit");
+function getEntityUnit(entity) {
+    var res = $(entity).find("input.caosdb-f-entity-unit");
     if (res.length == 1) {
         var x = res.val();
         return (x == '' ? undefined : x);
     }
-    return undefined;
+
+    return getEntityHeadingAttribute(entity, "unit");
 }
 
 /**
@@ -245,6 +247,35 @@ function getEntityID(element) {
     throw new Error("id is ambigous for this element!");
 }
 
+
+/**
+ * Get the version string of an entity.
+ *
+ * @param {HTMLElement} an Entity in HTML representation
+ *     (div.caosdb-entity-panel)
+ * @return {string} the version
+ */
+var getEntityVersion = function (entity) {
+    return entity.getAttribute("data-version-id");
+}
+
+
+/**
+ * Get the id and, if present, the version of an entity.
+ *
+ * @param {HTMLElement} an Entity in HTML representation
+ *     (div.caosdb-entity-panel)
+ * @return {string} <id>[@<version>]
+ */
+var getEntityIdVersion = function (entity) {
+    const id = getEntityID(entity);
+    const version = getEntityVersion(entity);
+    if (version) {
+        return `${id}@${version}`;
+    }
+    return id;
+}
+
 /**
  * Take a date and a time and format it into a CaosDB compatible representation.
  * @param date A date
@@ -631,6 +662,9 @@ function setPropertySafe(valueelement, property, propold) {
             preview.init();
         }
     } else {
+        /* DEPRECATED css class .caosdb-property-text-value - Use
+        * .caosdb-f-property-single-raw-value or introduce new
+        * .caosdb-v-property-text-value */
         valueelement.innerHTML = "<span class='caosdb-property-text-value'>" + property.value + "</span>";
     }
 }
@@ -798,7 +832,7 @@ function _createDocument(root) {
  * @param id The id of the entity. Can be undefined.
  * @param properties A list of properties.
  * @param parents A list of parents.
- * @return {Document|DocumentFragement} - An xml document holding the newly
+ * @return {Document|DocumentFragment} - An xml document holding the newly
  *         created entity.
  *
  */
diff --git a/src/core/js/edit_mode.js b/src/core/js/edit_mode.js
index b68d8ca4d9d5d309f3374c3717fe001806241173..0c52f403d5d9b2f0b2feb60a9dc21403c12dc0b2 100644
--- a/src/core/js/edit_mode.js
+++ b/src/core/js/edit_mode.js
@@ -188,6 +188,10 @@ var edit_mode = new function() {
 
 
     this.set_entity_dropable = function(entity, dragover, dragleave, parent_drop, property_drop) {
+        if (getEntityRole(entity) === "Property") {
+            // currently no parents and subproperties for properties.
+            return;
+        }
         var rts = entity.getElementsByClassName("caosdb-entity-panel-body");
         for (var rel of rts) {
             rel.addEventListener("dragleave", dragleave);
@@ -313,6 +317,71 @@ var edit_mode = new function() {
     }
 
 
+    /**
+     * Pure converter from a datatype in string representation to a json
+     * objects.
+     *
+     * E.g. passing "LIST<Person>" results in
+     *     { atomic_datatype: "REFERENCE",
+     *       reference_scope: "Person",
+     *       is_list: true 
+     *     }
+     *
+     * @param {str} datatype
+     * @returns {object}
+     */
+    this.get_datatype_from_string = function (datatype) {
+
+        var atomic = datatype;
+        const is_list = edit_mode.isListDatatype(datatype);
+        if (is_list) {
+            // unwrap the datatype from LIST<...>
+            atomic = edit_mode.unListDatatype(datatype);
+        }
+
+        var ref = undefined;
+        if (edit_mode._known_atomic_datatypes.indexOf(atomic) == -1) {
+            // `atomic` not in `known_atomic_datatypes` (e.g. "Person") -> this
+            // is a reference and the `atomic` is actually the reference's
+            // scope entity.
+            ref = atomic;
+            atomic = "REFERENCE";
+        }
+
+        return { "atomic_datatype": atomic, "reference_scope": ref, "is_list": is_list};
+    }
+
+    /**
+     * Pure converter from a datatype json objects to a string representation
+     * of the datatype.
+     *
+     * E.g. passing
+     *     { atomic_datatype: "REFERENCE",
+     *       reference_scope: "Person",
+     *       is_list: true 
+     *     }
+     * results in "LIST<Person>".
+     *
+     * @param {object} datatype
+     * @returns {str}
+     */
+    this.get_datatype_str = function (datatype) {
+        var result = datatype["atomic_datatype"];
+
+        const ref = datatype["reference_scope"];
+        if (ref !== null && typeof ref !== "undefined") {
+            result = ref;
+        }
+
+        const is_list = datatype["is_list"];
+        if (is_list === "on" || is_list === true) {
+            result = `LIST<${result}>`;
+        }
+
+        return result;
+    }
+
+
     /**
      * Insert entities.
      *
@@ -324,21 +393,32 @@ var edit_mode = new function() {
         ent_elements = caosdb_utils.assert_array(ent_elements, "param `ent_elements`", true);
         var xmls = [];
         for ( const ent_element of ent_elements ) {
-            xmls.push(createEntityXML(
-                getEntityRole(ent_element),
-                getEntityName(ent_element),
-                undefined,
-                edit_mode.getProperties(ent_element),
-                getParents(ent_element),
-                true,
-                getEntityDatatype(ent_element),
-                getEntityDescription(ent_element),
-                getEntityUnit(ent_element),
-            ));
+            xmls.push(edit_mode.form_to_xml(ent_element));
         }
         return await insert(xmls);
     }
 
+    /**
+     * Generate an XML from the form of the edited/newly created entity.
+     *
+     * @param {HTMLElement} entity_form - FORM of new/changed entity.
+     * @returns {Document|DocumentFragment} - An xml document containing the
+     *     entity in XML representation.
+     */
+    this.form_to_xml = function(entity_form) {
+        const obj = form_elements.form_to_object($(entity_form).find("form")[0]);
+        return createEntityXML(
+            getEntityRole(entity_form),
+            getEntityName(entity_form),
+            getEntityID(entity_form),
+            edit_mode.getProperties(entity_form),
+            getParents(entity_form),
+            true,
+            edit_mode.get_datatype_str(obj),
+            getEntityDescription(entity_form),
+            obj.unit,
+        );
+    }
 
     /**
      * TODO merge with getPropertyFromElement in caosdb.js
@@ -415,17 +495,7 @@ var edit_mode = new function() {
     }
 
     this.update_entity = async function(ent_element) {
-        var xml = createEntityXML(
-            getEntityRole(ent_element),
-            getEntityName(ent_element),
-            getEntityID(ent_element),
-            edit_mode.getProperties(ent_element),
-            getParents(ent_element),
-            true,
-            getEntityDatatype(ent_element),
-            getEntityDescription(ent_element),
-            getEntityUnit(ent_element),
-        );
+        var xml = edit_mode.form_to_xml(ent_element);
         return await edit_mode.update(xml);
     }
 
@@ -530,8 +600,8 @@ var edit_mode = new function() {
         roleElem.detach();
         var parentsElem = $(header).find('.caosdb-f-parent-list');
         parentsElem.detach();
-        var temp = $('<div class="form-group"><label class="col-sm-2 control-label">parents</label><div class="col-sm-10"></div></div>');
-        temp.find("div.col-sm-10").append(parentsElem);
+        const parentsSection = $('<div class="form-group"><label class="col-sm-2 control-label">parents</label><div class="col-sm-10"></div></div>');
+        parentsSection.find("div.col-sm-10").append(parentsElem);
 
         header.attr("title", "Drop parents from the right panel here.");
         header.data("toggle", "tooltip");
@@ -539,25 +609,29 @@ var edit_mode = new function() {
         // create inputs
         var inputs = [
             roleElem,
-            temp,
+            parentsSection,
             this.make_input("name", getEntityName(entity)),
             this.make_input("description", getEntityDescription(entity)),
         ];
         if (getEntityRole(roleElem[0]) == "Property") {
-            // TODO refactor getEntityUnit and use that
-            var unit = getEntityHeadingAttribute(entity, "unit");
-            for (const input of this.make_datatype_input(getEntityDatatype(entity), unit)) {
-                inputs.push(input);
-            }
-            temp.hide();
+            const current_datatype = getEntityDatatype(entity);
+            inputs.push(edit_mode.make_datatype_input(current_datatype));
+
+            const current_unit = getEntityUnit(entity);
+            inputs.push(edit_mode.make_unit_input(current_unit));
+
+            // TODO currently no parents for properties. Why not?
+            parentsSection.hide();
+            header.attr("title", "");
         } else if (getEntityRole(roleElem[0]) == "File") {
             inputs.push(this.make_input("path", getEntityPath(entity)));
         }
         // remove other stuff
         header.children().remove();
-        header.append($('<form class="form-horizontal"></form>').append(inputs));
-        edit_mode.make_dataype_input_logic(header);
+        const form = $('<form class="form-horizontal"></form>').append(inputs);
+        header.append(form);
 
+        edit_mode.make_datatype_input_logic(form[0]);
         edit_mode.add_parent_delete_buttons(header[0]);
     }
 
@@ -569,46 +643,155 @@ var edit_mode = new function() {
         return datatype.substring(5, datatype.length - 1);
     }
 
-    this.make_dataype_input_logic = function(header) {
-        var unitLabel = $(header).find(".caosdb-f-entity-unit-label");
-        var unitInput = $(header).find(".caosdb-f-entity-unit");
-        var isListLabel = $(header).find(".caosdb-f-entity-is-list-label");
-        var isListInput = $(header).find(".caosdb-f-entity-is-list");
-        var referenceLabel = $(header).find(".caosdb-f-entity-reference-label");
-        var referenceInput = $(header).find(".caosdb-f-entity-reference");
+    /**
+     * Append listeners to all input elements depending on the datatype.
+     *
+     * The relevant input elements are those for the unit and the
+     * reference_scope.
+     *
+     * Only when the atomic_datatype is either "DOUBLE" or "INTEGER" the unit
+     * field should be visible and enabled. Only when the atomic_datatype is
+     * "REFERENCE", the reference_scope field shoudl be visible and enabled.
+     *
+     * The listener `on_datatype_change` is added to the datatype field and
+     * triggered right away for the first time.
+     *
+     * @param {HTMLElement} form - The form containing the input fields.
+     */
+    this.make_datatype_input_logic = function(form) {
+        const datatype = form_elements.get_fields(form, "atomic_datatype");
 
-        // TODO show on reference
-        referenceInput.hide();
-        referenceLabel.hide();
+        $(datatype).find("select").change(function () {
+            const new_type = $(this).val();
+            logger.debug(`datatype changed to ${new_type}.`);
+            edit_mode.on_datatype_change(form, new_type);
+        });
 
-        // TODO show unit for double and integer
+        // trigger for the first time
+        edit_mode.on_datatype_change(form, $(datatype).find("select").val());
     }
 
-    this.make_datatype_input = function(datatype, unit) {
-        var is_list = edit_mode.isListDatatype(datatype);
-        if (is_list) {
-            datatype = edit_mode.unListDatatype(datatype);
-        }
+    /**
+     * The listener which is added by `make_datatype_input_logic`.
+     *
+     * @param {HTMLElement} form - The form containing the datatype and unit
+     *     input elements.
+     * @param {string} new_type - the new datatype which is used by this
+     *     listener to determine which fields need to be enabled or disabled.
+     */
+    this.on_datatype_change = function (form, new_type) {
+        logger.trace('enter on_datatype_change', form, new_type);
 
-        // datatypes {name: has_unit?}
-        var datatypes = {
-            "TEXT": false,
-            "DOUBLE": true,
-            "INTEGER": true,
-            "DATETIME": false,
-            "BOOLEAN": false,
-            "FILE": false,
-            /*TODO "REFERENCE":false*/
+        if (new_type === "REFERENCE") {
+            form_elements.enable_name(form, "reference_scope");
+        } else {
+            form_elements.disable_name(form, "reference_scope");
         }
-        var select = $('<select></select>');
-        for (const dt of Object.keys(datatypes)) {
-            select.append('<option data-has-refid="' + (dt == "REFERENCE") + '" data-has-unit="' + datatypes[dt] + '"  value="' + dt + '" ' + (dt == datatype ? 'selected="true"' : '') + '>' + dt + '</option>');
+
+        if (new_type === "DOUBLE" || new_type === "INTEGER") {
+            form_elements.enable_name(form, "unit");
+        } else {
+            form_elements.disable_name(form, "unit");
         }
+    }
+
 
-        return [
-            $('<div class="form-group"><label class="col-sm-2 control-label caosdb-f-entity-datatype-label">datatype</label><div class="col-sm-3"><select class="form-control caosdb-f-entity-datatype">' + select.html() + '</select></div><label class="col-sm-2 control-label caosdb-f-entity-reference-label">reference</label><div class="col-sm-3"><input readonly="true" class="form-control caosdb-f-entity-reference" value="" placeholder="Drop a RT"></input></div><label class="col-sm-1 control-label caosdb-f-entity-is-list-label">list</label><div class="col-sm-1"><input class="caosdb-f-entity-is-list" type="checkbox" ' + (is_list ? 'checked="true" ' : "") + '/></div>')[0],
-            $('<div class="form-group"><label class="col-sm-2 control-label caosdb-f-entity-unit-label">unit</label><div class="col-sm-2"><input type="text" class="form-control caosdb-f-entity-unit" value="' + (typeof unit == 'undefined' ? "" : unit) + '"></input></div></div>')[0],
+    /**
+     * Generate a text input element for the unit of an abstract property.
+     *
+     * @param {string} unit - the initial value of the input element.
+     * @returns {HTMLElement} - a labeled form field.
+     */
+    this.make_unit_input = function(unit) {
+        const unit_input = $(form_elements
+                .make_text_input({
+                    name: "unit",
+                    label: "unit",
+                    value: unit,
+                }));
+        unit_input.toggleClass("form-group", true);
+        unit_input.find(".col-sm-3").toggleClass("col-sm-2", true).toggleClass("col-sm-3", false);
+        unit_input.find(".col-sm-9").toggleClass("col-sm-2", true).toggleClass("col-sm-9", false);
+        return unit_input[0];
+    }
+
+    this._known_atomic_datatypes = [
+            "TEXT",
+            "DOUBLE",
+            "INTEGER",
+            "DATETIME",
+            "BOOLEAN",
+            "FILE",
+            "REFERENCE",
         ];
+
+    /**
+     * Make three input elements which contain all necessary parts of a datatype.
+     *
+     * The three input elements are wrapped in a single DIV.form-group.
+     *
+     * @param {string} [datatype] - defaults to TEXT if undefined.
+     * @returns {HTMLElement}
+     */
+    this.make_datatype_input = function(datatype) {
+        var _datatype = datatype || "TEXT";
+
+        // split/convert datatype string into more practical variables.
+        const datatype_obj = edit_mode.get_datatype_from_string(_datatype);
+        const atomic_datatype = datatype_obj["atomic_datatype"];
+        const reference_scope = datatype_obj["reference_scope"];
+        const is_list = datatype_obj["is_list"];
+
+
+        // generate select input for the atomic_datatype
+        const select = $('<select name="atomic_datatype" class="form-control"></select>');
+        for (const dt of edit_mode._known_atomic_datatypes) {
+            select.append(`<option value="${dt}">${dt}</option>`);
+        }
+        select.val(atomic_datatype)
+
+        const datatype_config = {
+            name: "atomic_datatype",
+            label: "datatype",
+        };
+        const datatype_selector = $(form_elements
+                ._make_field_wrapper(datatype_config.name))
+            .append(form_elements._make_input_label_str(datatype_config))
+            .append($('<div class="col-sm-3"/>').append(select));
+
+        // generate select input for the RecordTypes which are the reference's
+        // scope.
+        const reference_config = {
+            name: "reference_scope",
+            label: "reference to",
+            value: reference_scope,
+            query: "SELECT name FROM RECORDTYPE",
+            make_desc: getEntityName,
+            make_value: getEntityName,
+        };
+        const ref_selector = form_elements
+                .make_reference_drop_down(reference_config);
+
+
+        // generate the checkbox ([ ] list)
+        const list_checkbox_config = {
+            name: "is_list",
+            label: "list",
+            checked: is_list,
+        }
+        const list_checkbox = form_elements
+                .make_checkbox_input(list_checkbox_config);
+
+        // styling
+        $(list_checkbox).children().toggleClass("col-sm-3",false).toggleClass("col-sm-9", false).toggleClass("col-sm-1", true);
+
+        const form_group = $('<div class="form-group">').append([datatype_selector, ref_selector, list_checkbox]);
+        form_group.find(".form-group").toggleClass("form-group", false);
+        form_group.find(".col-sm-3").toggleClass("col-sm-2", true).toggleClass("col-sm-3", false);
+        form_group.find(".col-sm-9").toggleClass("col-sm-3", true).toggleClass("col-sm-9", false);
+
+
+        return form_group[0];
     }
 
     this.make_input = function(label, value) {
@@ -1168,6 +1351,10 @@ var edit_mode = new function() {
             const state = app.state;
             $('.caosdb-entity-panel').each(function(index) {
                 let entity = this;
+                if ($(entity).is("[data-version-successor]")) {
+                    // not the latest version -> ignore
+                    return;
+                }
 
                 // remove listeners from all entities
                 edit_mode.unset_entity_dropable(entity, edit_mode.dragover, edit_mode.dragleave, edit_mode.parent_drop_listener, edit_mode.property_drop_listener);
@@ -1202,6 +1389,10 @@ var edit_mode = new function() {
                 edit_mode.enable_new_buttons();
                 $('.caosdb-entity-panel').each(function(index) {
                     let entity = this;
+                    if ($(entity).is("[data-version-successor]")) {
+                        // not the latest version -> ignore
+                        return;
+                    }
                     if (typeof getEntityID(entity) == "undefined" || getEntityID(entity) == '') {
                         // no id -> insert
                         edit_mode.init_actions_panels(entity);
@@ -1400,8 +1591,8 @@ var edit_mode = new function() {
     /**
      * Fill the reference drop down menu of an entity property with options.
      *
-     * The options are candidates for reference targets (i.e. values of
-     * reference properties).
+     * The options are the entities which are within the scope of the reference
+     * property.
      *
      * @param {HTMLElement} drop_down - A SELECT element which will be filled.
      * @param {HTMLElement[]} options - array of option elements, ready for
diff --git a/src/core/js/ext_bottom_line.js b/src/core/js/ext_bottom_line.js
index 900263366e456c8f7a2945af6a24c7a10b345c0e..1bcf73dea17889bda9858ed78a2f6848e1dc21ee 100644
--- a/src/core/js/ext_bottom_line.js
+++ b/src/core/js/ext_bottom_line.js
@@ -43,7 +43,7 @@
  * @requires getEntityPath (function from caosdb.js)
  * @requires connection (module from webcaosdb.js)
  */
-var ext_bottom_line = function ($, logger, is_in_view_port, load_config, getEntityPath, connection) {
+var ext_bottom_line = function($, logger, is_in_view_port, load_config, getEntityPath, connection) {
 
     /**
      * @property {string|function} create - a function with one parameter
@@ -83,11 +83,11 @@ var ext_bottom_line = function ($, logger, is_in_view_port, load_config, getEnti
      * @return {boolean} true iff the entity has a path with one of the
      *     extensionss.
      */
-    const _path_has_file_extension = function (entity, extensions) {
+    const _path_has_file_extension = function(entity, extensions) {
         const path = getEntityPath(entity);
         if (path) {
             for (var ext of extensions) {
-                if(path.toLowerCase().endsWith(ext)) {
+                if (path.toLowerCase().endsWith(ext)) {
                     return true;
                 }
             }
@@ -101,9 +101,9 @@ var ext_bottom_line = function ($, logger, is_in_view_port, load_config, getEnti
      * @param {HTMLElement} entity
      * @return {HTMLElement} a VIDEO element.
      */
-    const _create_video_preview = function (entity) {
+    const _create_video_preview = function(entity) {
         var path = connection.getFileSystemPath() + getEntityPath(entity);
-        return $(`<video controls="controls"><source src="${path}"/></video>`)[0];
+        return $(`<div class="caosdb-v-bottom-line-video-preview"><video controls="controls"><source src="${path}"/></video></div>`)[0];
     }
 
     /**
@@ -112,9 +112,9 @@ var ext_bottom_line = function ($, logger, is_in_view_port, load_config, getEnti
      * @param {HTMLElement} entity
      * @return {HTMLElement} an IMG element.
      */
-    const _create_picture_preview = function (entity) {
+    const _create_picture_preview = function(entity) {
         var path = connection.getFileSystemPath() + getEntityPath(entity);
-        return $(`<img class="entity-image-preview" style="max-width: 200px; max-height=140px;" src="${path}"/>`)[0];
+        return $(`<div class="caosdb-v-bottom-line-image-preview"><img src="${path}"/></div>`)[0];
     }
 
     var fallback_preview = undefined;
@@ -124,20 +124,17 @@ var ext_bottom_line = function ($, logger, is_in_view_port, load_config, getEnti
      *
      * @member {Creator[]}
      */
-    const _default_creators = [
-        { // pictures
+    const _default_creators = [{ // pictures
             id: "_default_creators.pictures",
             is_applicable: (entity) => _path_has_file_extension(
                 entity, ["jpg", "png", "gif", "svg"]),
             create: _create_picture_preview
-        },
-        { // videos
+        }, { // videos
             id: "_default_creators.videos",
             is_applicable: (entity) => _path_has_file_extension(
                 entity, ["mp4", "mov", "webm"]),
             create: _create_video_preview,
-        },
-        { // fallback
+        }, { // fallback
             id: "_default_creators.fallback",
             is_applicable: (entity) => true,
             create: (entity) => fallback_preview,
@@ -167,7 +164,7 @@ var ext_bottom_line = function ($, logger, is_in_view_port, load_config, getEnti
      * @returns {HTMLElement} the preview container or `undefined` if the entity
      *     doesn't have any.
      */
-    const get_preview_container = function (entity) {
+    const get_preview_container = function(entity) {
         return $(entity).children(`.${_css_class_preview_container}`)[0];
     }
 
@@ -188,16 +185,20 @@ var ext_bottom_line = function ($, logger, is_in_view_port, load_config, getEnti
      * @param {HTMLElement} entity - An entity in HTML Representation which
      *     must have a (deep) child with class `caosdb-f-ext_bottom_line-container`.
      */
-    const set_preview_container = function (entity, element) {
+    const set_preview_container = function(entity, element) {
         const preview_container = $(get_preview_container(entity));
         if (preview_container[0]) {
             preview_container.empty();
             var buttons = preview_container.siblings(`.${_css_class_preview_container_button}`);
             if (element) {
-                buttons.css({"visibility": "initial"});
+                buttons.css({
+                    "visibility": "initial"
+                });
                 preview_container.append(element);
             } else {
-                buttons.css({"visibility": "hidden"});
+                buttons.css({
+                    "visibility": "hidden"
+                });
             }
         } else {
             logger.error(new Error("Could not find the preview container."));
@@ -219,7 +220,7 @@ var ext_bottom_line = function ($, logger, is_in_view_port, load_config, getEnti
      * @param {string|HTMLElement|Promise} preview - A preview for an entity or
      *     a Promise for a preview (which resolves as a string or an HTMLElement as well).
      */
-    var set_preview = async function (entity, preview) {
+    var set_preview = async function(entity, preview) {
         try {
             const wait = "Please wait...";
             set_preview_container(entity, wait);
@@ -252,7 +253,7 @@ var ext_bottom_line = function ($, logger, is_in_view_port, load_config, getEnti
      * @returns {String|HTMLElement|Promise} A preview which can be added to
      *     the entity DOM representation or a Promise for such a preview.
      */
-    var root_preview_creator = async function (entity) {
+    var root_preview_creator = async function(entity) {
         for (let c of _creators) {
             try {
                 if (await c.is_applicable(entity)) {
@@ -283,15 +284,21 @@ var ext_bottom_line = function ($, logger, is_in_view_port, load_config, getEnti
      * @param {HTMLElement} entity - An entity in HTML representation.
      * @return {HTMLElement} the newly created container.
      */
-    var add_preview_container = function (entity) {
+    var add_preview_container = function(entity) {
         const button_show = $('<button class="btn btn-xs"><span class="glyphicon glyphicon-menu-down"/> Show Preview</button>')
-            .css({width: "100%"})
+            .css({
+                width: "100%"
+            })
             .addClass(_css_class_preview_container_button);
         const button_hide = $('<button class="btn btn-xs"><span class="glyphicon glyphicon-menu-up"/> Hide Preview</button>')
-            .css({width: "100%"})
+            .css({
+                width: "100%"
+            })
             .addClass(_css_class_preview_container_button)
             .hide();
-        const style = { padding: "0px 10px" };
+        const style = {
+            padding: "0px 10px"
+        };
         const container = $(`<div class="collapse"/>`)
             .addClass(_css_class_preview_container)
             .addClass(_css_class_preview_container_resolvable)
@@ -308,7 +315,9 @@ var ext_bottom_line = function ($, logger, is_in_view_port, load_config, getEnti
         }
         button_show.click(show);
         button_hide.click(hide);
-        container.on("shown.bs.collapse", () => { container[0].dispatchEvent(previewShownEvent); });
+        container.on("shown.bs.collapse", () => {
+            container[0].dispatchEvent(previewShownEvent);
+        });
         $(entity).append(container);
         $(entity).append(button_show);
         $(entity).append(button_hide);
@@ -326,7 +335,7 @@ var ext_bottom_line = function ($, logger, is_in_view_port, load_config, getEnti
      * @param {HTMLElement} entity - the entity for which the preview is to
      *     created.
      */
-    var root_preview_handler = async function (entity) {
+    var root_preview_handler = async function(entity) {
         var container = $(get_preview_container(entity) || add_preview_container(entity));
         if (container.hasClass(_css_class_preview_container_resolvable)) {
             container.removeClass(_css_class_preview_container_resolvable);
@@ -339,7 +348,7 @@ var ext_bottom_line = function ($, logger, is_in_view_port, load_config, getEnti
      * Trigger the root_preview_handler for all entities within the view port
      * when the view port.
      */
-    var root_preview_handler_trigger = function () {
+    var root_preview_handler_trigger = function() {
         var entities = $(".caosdb-entity-panel,.caosdb-entity-preview");
         for (let entity of entities) {
 
@@ -359,7 +368,7 @@ var ext_bottom_line = function ($, logger, is_in_view_port, load_config, getEnti
      *     event. After this timeout the trigger is called.
      * @param {function} trigger - the callback which is called.
      */
-    var init_watcher = function (delay, trigger) {
+    var init_watcher = function(delay, trigger) {
         var scroll_timeout = undefined;
         $(window).scroll(() => {
             if (!scroll_timeout) {
@@ -387,9 +396,9 @@ var ext_bottom_line = function ($, logger, is_in_view_port, load_config, getEnti
      *
      * @param {BottomLineConfig} config
      */
-    var configure = async function (config) {
+    var configure = async function(config) {
         logger.debug("configure", config);
-        if(config.version != "0.1") {
+        if (config.version != "0.1") {
             throw new Error("Wrong version in config.");
         }
 
@@ -421,7 +430,7 @@ var ext_bottom_line = function ($, logger, is_in_view_port, load_config, getEnti
      * @property {BottomLineConfig} [config] - an optional config. Per default, the
      *     configuration is fetched from the server.
      */
-    const init = async function (config) {
+    const init = async function(config) {
         logger.info("ext_bottom_line initialized");
 
         try {
@@ -458,14 +467,14 @@ var ext_bottom_line = function ($, logger, is_in_view_port, load_config, getEnti
 /**
  * Helper for plotly
  */
-var plotly_preview = function (logger, ext_bottom_line, plotly) {
+var plotly_preview = function(logger, ext_bottom_line, plotly) {
 
     /**
      * Create a plot with plotly.
      *
      * The layout and any other plotly options are set by this function. The
-     * only parameter `data` is the `data` parameter of the `Plotly.newPlot`
-     * factory.
+     * only parameters are `data` and `layout` which are the respective
+     * parameters of the `Plotly.newPlot` factory.
      *
      * Hence the full documentation of the `data` parameter is available at
      * https://plotly.com/javascript/plotlyjs-function-reference/#plotlynewplot
@@ -473,22 +482,33 @@ var plotly_preview = function (logger, ext_bottom_line, plotly) {
      *
      * @param {object[]} data - array of objects containing the data which is
      *     to be plotted.
+     * @param {object[]} layout - dictionary of settings defining the layout of
+     *     the plot.
      * @returns {HTMLElement} the element which contains the plot.
      */
-    const create_plot = function (data) {
+    const create_plot = function(data,
+        layout = {
+            margin: {
+                t: 0
+            },
+            height: 400,
+            widht: 400
+        }) {
         var div = $('<div/>')[0];
-        plotly.newPlot(div, data, { margin: { t: 0}, height: 400, widht: 400 }, {responsive: true});
+        plotly.newPlot(div, data, layout, {
+            responsive: true
+        });
         return div;
     }
 
-    const resize_plots_event_handler = function (e) {
+    const resize_plots_event_handler = function(e) {
         var plots = $(e.target).find(".js-plotly-plot");
         for (let plot of plots) {
             plotly.Plots.resize(plot);
         }
     }
 
-    const init = function () {
+    const init = function() {
         window.addEventListener(ext_bottom_line.previewReadyEvent.type,
             resize_plots_event_handler, true);
         window.addEventListener(ext_bottom_line.previewShownEvent.type,
@@ -504,7 +524,7 @@ var plotly_preview = function (logger, ext_bottom_line, plotly) {
 
 
 // this will be replaced by require.js in the future.
-$(document).ready(function () {
+$(document).ready(function() {
     if ("${BUILD_MODULE_EXT_BOTTOM_LINE}" === "ENABLED") {
         caosdb_modules.register(plotly_preview);
         caosdb_modules.register(ext_bottom_line);
diff --git a/src/core/js/ext_cosmetics.js b/src/core/js/ext_cosmetics.js
index fe1819ed1be69fa390fbf87ae34362efacf24c83..3913a37b6645e88d3a357b9c4c124cf296ccfcad 100644
--- a/src/core/js/ext_cosmetics.js
+++ b/src/core/js/ext_cosmetics.js
@@ -4,6 +4,9 @@ var cosmetics = new function() {
     }
 
     this.linkify = function() {
+        /* DEPRECATED css class .caosdb-property-text-value - Use
+        * .caosdb-f-property-single-raw-value or introduce new
+        * .caosdb-v-property-text-value */
         $('.caosdb-property-text-value').each(function(index) {
             if (/^https?:\/\//.test(this.innerText)) {
                 var uri = this.innerText;
@@ -20,4 +23,4 @@ var cosmetics = new function() {
 
 $(document).ready(function() {
     cosmetics.init();
-});
\ No newline at end of file
+});
diff --git a/src/core/js/ext_jupyterdrag.js b/src/core/js/ext_jupyterdrag.js
new file mode 100644
index 0000000000000000000000000000000000000000..87d2f3a964ced1411df66da04ff4ddd4d7db2f15
--- /dev/null
+++ b/src/core/js/ext_jupyterdrag.js
@@ -0,0 +1,76 @@
+/*
+ * ** header v3.0
+ * This file is a part of the CaosDB Project.
+ *
+ * Copyright (C) 2020 IndiScale GmbH <info@indiscale.com>
+ * Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com>
+ * Copyright (C) 2020 Alexander Schlemmer <alexander.schlemmer@ds.mpg.de>
+ *
+ * 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
+ */
+
+'use strict';
+
+/**
+ * The ext_jupyterdrag module adds dragstart listeners to the WebUI which allow
+ * dragging single entities into Jupyter notebooks and other text editors (for
+ * python code) which implement drop gestures for text data.
+ *
+ * @module ext_jupyterdrag
+ * @version 0.1
+ *
+ * @requires jQuery
+ * @requires log
+ * @requires getEntityRole
+ * @requires getEntityID
+ */
+var ext_jupyterdrag = function($, logger, getEntityRole, getEntityID) {
+
+
+    /**
+     * Initialize the ext_jupyterdrag module.
+     *
+     */
+    var init = async function() {
+        $(".caosdb-entity-panel").find(
+            ".caosdb-entity-panel-heading").attr("draggable",
+            "true");
+        $(".caosdb-entity-panel").find(
+            ".caosdb-entity-panel-heading").on("dragstart",
+            function(ev) {
+                logger.trace("dragstart", ev);
+                var eel = this.parentElement;
+                var role = getEntityRole(eel);
+                var entid = getEntityID(eel);
+                ev.originalEvent.dataTransfer.setData(
+                    "text/plain",
+                    "db." + role + "(id=" + entid + ")");
+            });
+    }
+
+
+
+    return {
+        // public members, part of the API
+        init: init,
+        _logger: logger,
+    }
+}($, log.getLogger("ext_jupyterdrag"), getEntityRole, getEntityID);
+
+
+$(document).ready(function() {
+    caosdb_modules.register(ext_jupyterdrag);
+});
diff --git a/src/core/js/ext_references.js b/src/core/js/ext_references.js
index 8676bed637fdf8c8134cc2a3a2aba1e914e2807b..8a3bc50852381e5e9e9c6221b97a54f2f6841cfb 100644
--- a/src/core/js/ext_references.js
+++ b/src/core/js/ext_references.js
@@ -54,144 +54,6 @@ var isOutOfViewport = function (elem) {
 };
 
 
-/**
- * @module awi_references
- * @version 0.1
- *
- * Special functionality for AWI. Should be removed from the main repository in
- * the future.
- *
- * @author Timm Fitschen
- */
-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);
-    }
-}
-
 /**
  * @module reference_list_summary
  * @version 0.1
@@ -459,6 +321,15 @@ var resolve_references = new function () {
      * the entity which is to be resolved.  @return {reference_info}
      */
     this.resolve_reference = async function (id) {
+        const custom_reference_resolver = window["${BUILD_EXT_REFERENCES_CUSTOM_RESOLVER}"];
+        if (custom_reference_resolver && typeof custom_reference_resolver.resolve === "function") {
+            // try custom_reference_resolver and fall-back to standard implementation
+            var ret = await custom_reference_resolver.resolve(id);
+            if (ret) {
+              return ret;
+            }
+        }
+
         const entity = (await resolve_references.retrieve(id))[0];
 
         // TODO handle multiple parents
@@ -475,25 +346,6 @@ var resolve_references = new function () {
             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 === "TestReferenced" && typeof resolve_references.test_resolver === "function") {
             // this is a test case, initialized by the test suite.
             ret = resolve_references.test_resolver(entity);
@@ -651,7 +503,7 @@ var resolve_references = new function () {
                                     summary_field.dispatchEvent(
                                         resolve_references
                                             .summary_ready_event
-                                    ); logger.error("here");})
+                                    );})
                                 .catch((err) => {
                                     logger.error(err);
                                 })
diff --git a/src/core/js/ext_sss_markdown.js b/src/core/js/ext_sss_markdown.js
new file mode 100644
index 0000000000000000000000000000000000000000..21c7d3261faeb7052dbe8aa0b9fba29d88ed56d9
--- /dev/null
+++ b/src/core/js/ext_sss_markdown.js
@@ -0,0 +1,55 @@
+/**
+ * @module ext_sss_markdown
+ * @version 0.1
+ *
+ * Transforms the STDOUT of server-side scripting responses from plain text to
+ * HTML. The STDOUT is interpreted as Markdown.
+ *
+ * Module has to be enabled via setting the build property
+ * `BUILD_MODULE_EXT_SSS_MARKDOWN=ENABLED`.
+ */
+var ext_sss_markdown = function() {
+
+    var logger = log.getLogger("ext_sss_markdown");
+
+    var init = function() {
+        logger.trace("enter init");
+        const stdout_container = $("#caosdb-stdout");
+        if (stdout_container.length == 0) {
+            // nothing to do
+            return;
+        }
+
+        // get text content of the STDOUT container
+        const text = stdout_container[0].textContent;
+        logger.debug("transform", text);
+        // interpret as markdown.
+        const html = markdown.textToHtml(text);
+
+        // a little styling
+        stdout_container.css({padding: 10});
+
+        // replace plain markdown text with formatted html
+        stdout_container.empty();
+        stdout_container.append(html);
+
+        // hide error output container if the STDERR was empty.
+        var errortext = $("#caosdb-stderr").text();
+        if (errortext.length == 0) {
+            $("#caosdb-container-stderr").hide();
+        }
+    }
+
+    return {
+        init: init,
+        logger: logger,
+    };
+
+}();
+
+
+$(document).ready(function() {
+    if ("${BUILD_MODULE_EXT_SSS_MARKDOWN}" == "ENABLED") {
+        caosdb_modules.register(ext_sss_markdown);
+    }
+});
diff --git a/src/core/js/ext_trigger_crawler_form.js b/src/core/js/ext_trigger_crawler_form.js
new file mode 100644
index 0000000000000000000000000000000000000000..94ea6feb672cc3a2be8c87cb6bd6f5c8a5087d3f
--- /dev/null
+++ b/src/core/js/ext_trigger_crawler_form.js
@@ -0,0 +1,132 @@
+/*
+ * ** header v3.0
+ * This file is a part of the CaosDB Project.
+ *
+ * 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
+ * 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
+ */
+
+'use strict';
+
+/**
+ * @module ext_trigger_crawler_form
+ * @version 0.1
+ *
+ * Adds a button to a configurable navbar toolbox which opens a form for
+ * triggering the crawler as a server-side scripting job.
+ *
+ * The form has two fields. The text field `Path` sets the directory tree which
+ * is searched by the crawler. The checkbox `Suppress Warnings` can be checked
+ * to suppress the crawlers warnings.
+ *
+ * The module has to be enabled via the
+ * `BUILD_MODULE_EXT_TRIGGER_CRAWLER_FORM=ENABLED` build variable.
+ *
+ * The toolbox where the button is added can be configured via the build
+ * variable `BUILD_MODULE_EXT_TRIGGER_CRAWLER_FORM_TOOLBOX`. The default is
+ * `Tools`.
+ */
+var ext_trigger_crawler_form = function () {
+
+    var init = function (toolbox) {
+        const _toolbox = toolbox || "${BUILD_MODULE_EXT_TRIGGER_CRAWLER_FORM_TOOLBOX}";
+
+        const script = "crawl.py"
+        const button_name = "Trigger Crawler";
+        const title = "The LinkAhead-Crawler will run over the filesystem and make necessary updates.";
+
+        const crawler_form = make_scripting_caller_form(
+            script, button_name);
+        const modal = make_form_modal(crawler_form);
+
+
+        navbar.add_tool(button_name, _toolbox, {
+            title: title,
+            callback: () => {
+                $(modal).modal("toggle");
+            }
+        });
+    }
+
+    /**
+     * Wrap the form into a Bootstrap modal.
+     */
+    var make_form_modal = function (form) {
+        const title = "Trigger the Crawler";
+        const modal = $(`
+          <div class="modal fade" tabindex="-1" role="dialog">
+            <div class="modal-dialog" role="document">
+              <div class="modal-content">
+                <div class="modal-header">
+                  <button type="button"
+                    class="close"
+                    data-dismiss="modal"
+                    aria-label="Close">
+                    <span aria-hidden="true">&times;</span>
+                  </button>
+                  <h4 class="modal-title">${title}</h4>
+                </div>
+                <div class="modal-body">
+                </div>
+              </div>
+            </div>`);
+        modal.find(".modal-body").append(form);
+        return modal[0];
+    }
+
+    /**
+     * Create the trigger crawler form.
+     */
+    var make_scripting_caller_form = function (script, button_name) {
+        const path_field = form_elements.make_text_input({
+            name: "-p1",
+            value: "/",
+            label: "Path"
+        });
+        const warning_checkbox = form_elements.make_checkbox_input({
+            name: "-Osuppress",
+            label: "Suppress Warnings"
+        });
+        $(warning_checkbox).find("input").attr("value", "TRUE");
+
+        const scripting_caller = $(`
+        <form method="POST" action="/scripting">
+          <input type="hidden" name="call" value="${script}"/>
+          <input type="hidden" name="-p0" value=""/>
+          <div class="form-group">
+            <input type="submit"
+              class="form-control btn btn-primary" value="${button_name}"/>
+          </div>
+        </form>`);
+
+        scripting_caller.prepend(warning_checkbox).prepend(path_field);
+
+        return scripting_caller[0];
+    }
+
+    return {
+        init: init,
+    };
+
+}();
+
+$(document).ready(function () {
+    if ("${BUILD_MODULE_EXT_TRIGGER_CRAWLER_FORM}" === "ENABLED") {
+        caosdb_modules.register(ext_trigger_crawler_form);
+    }
+});
diff --git a/src/core/js/form_elements.js b/src/core/js/form_elements.js
index 5c29058f532dc30daa98b6354b60e3caab88a7a6..0014f4db452a55839edaa860d7fa49f98529434e 100644
--- a/src/core/js/form_elements.js
+++ b/src/core/js/form_elements.js
@@ -23,7 +23,10 @@
 'use strict';
 
 /**
- * caosdb_map module for reusable form elemenst which already have a basic css styling.
+ * form_elements module for reusable form elemenst which already have a basic
+ * css styling.
+ *
+ * @version 0.2
  *
  * IMPORTANCE CONCEPTS
  *
@@ -196,21 +199,32 @@ var form_elements = new function () {
         /**
          * Return SELECT form element with entity references.
          *
-         * The OPTIONS have the entities' ids as values and a description which
-         * is generated by a `make_desc` call-back function. If `make_desc` is
-         * undefined, the ids are shown instead.
+         * The OPTIONS' values are generated by the `make_value` call-back
+         * function from the entities. If `make_value` is undefined the
+         * entities' ids are used as values. The description which is generated
+         * by a `make_desc` call-back function. If `make_desc` is undefined,
+         * the ids are shown instead.
          *
          * @param {HTMLElement[]} entities - an array with entity elements.
          * @param {function} [make_desc] - a call-back function with one
-         *      parameter.
+         *      parameter which is an entity in HTML representation.
+         * @param {function} [make_value] - a call-back function with one
+         *      parameter which is an entity in HTML representation.
+         * @param {boolean} [multiple] - whether the select allows multiple
+         *      options to be selected.
          * @returns {HTMLElement} SELECT element with entity options.
          */
-        this.make_reference_select = async function (entities, make_desc, multiple = false) {
+        this.make_reference_select = async function (entities, make_desc,
+                make_value, multiple) {
             caosdb_utils.assert_array(entities, "param `entities`", false);
             if (typeof make_desc !== "undefined") {
                 caosdb_utils.assert_type(make_desc, "function",
                     "param `make_desc`");
             }
+            if (typeof make_value !== "undefined") {
+                caosdb_utils.assert_type(make_value, "function",
+                    "param `make_value`");
+            }
             const ret = $('<select class="selectpicker form-control" title="Nothing selected"/>');
             if (multiple) {
                 ret.attr("multiple", "");
@@ -222,26 +236,57 @@ var form_elements = new function () {
                 let entity_id = getEntityID(entity);
                 let desc = typeof make_desc == "function" ? await make_desc(entity) :
                     entity_id;
-                let option = this.make_reference_option(entity_id, desc);
+                let value = typeof make_value == "function" ? await make_value(entity) : entity_id;
+                let option = this.make_reference_option(value, desc);
                 ret.append(option);
             }
             return ret[0];
         }
 
+        /**
+         * @typedef {option} ReferenceDropDownConfig
+         *
+         * Configuration object for a drop down menu for selecting references.
+         * `make_reference_drop_down` generates such a drop down menu using a
+         * SELECT input with the references as its OPTION elements.
+         *
+         * The `query` parameter contains a query which is executed. The
+         * resulting entities are used to generate the OPTIONs.
+         *
+         * The OPTIONs' values are generated by the `make_value` call-back
+         * function from the entities. If `make_value` is undefined the
+         * entities' ids are used as values. The description which is generated
+         * by a `make_desc` call-back function. If `make_desc` is undefined,
+         * the ids are shown instead.
+         *
+         * The generated HTMLElements also contain a LABEL tag showing the text
+         * defined by `label`. If the `label` property is undefined, the `name`
+         * is shown instead.
+         *
+         * @property {string} name - The name of the select input.
+         * @property {string} query - Query for entities.
+         * @property {function} [make_value] - Call-back for the generation of
+         *     the OPTIONs' values from the entities.
+         * @property {function} [make_desc] - Call-back for the generation of
+         *     the OPTIONs' description from the entities.
+         * @property {boolean} [multiple] - Whether it is possible to select
+         *     multiple options at once.
+         * @property {string} [value] - Pre-selected value of the SELECT.
+         * @property {string} [label] - LABEL text.
+         * @property {string} [type] - This should be "reference_drop_down" or
+         *     undefined. This property is used by `make_form_field` to decide
+         *     which type of field is to be generated.
+         *
+         */
 
         /**
          * Search and retrieve entities and create a SELECT from element.
          *
-         * The OPTIONS have the entities' ids as values and a description which
-         * is generated by a `make_desc` call-back function. If `make_desc` is
-         * undefined, the ids are shown instead.
-         *
-         * @param {object} config
+         * @param {ReferenceDropDownConfig} config - all necessary parameters
+         *     for the configuration.
          * @returns {HTMLElement} SELECT element.
-         *
-         * TODO make syncronous
          */
-        this.make_reference_drop_down = async function (config) {
+        this.make_reference_drop_down = function (config) {
             let ret = $(this._make_field_wrapper(config.name));
             let label = this._make_input_label_str(config);
             let loading = $('<div class="caosdb-f-field-not-ready">loading...</div>');
@@ -250,11 +295,12 @@ var form_elements = new function () {
             input_col.append(loading);
             this._query(config.query).then(async function (entities) {
                 let select = $(await form_elements.make_reference_select(
-                    entities, config.make_desc, config.multiple));
+                    entities, config.make_desc, config.make_value, config.multiple,
+                    config.value));
                 select.attr("name", config.name);
                 loading.remove();
                 input_col.append(select);
-                form_elements.init_select_picker(ret[0]);
+                form_elements.init_select_picker(ret[0], config.value);
                 ret[0].dispatchEvent(form_elements.field_ready_event);
                 select.change(function () {
                     ret[0].dispatchEvent(form_elements.field_changed_event);
@@ -270,7 +316,7 @@ var form_elements = new function () {
         }
 
 
-        this.init_select_picker = function (field) {
+        this.init_select_picker = function (field, value) {
             caosdb_utils.assert_html_element(field, "parameter `field`");
             const select = $(field).find("select")[0];
             const select_picker_options = {};
@@ -283,6 +329,7 @@ var form_elements = new function () {
                 select_picker_options["liveSearchPlaceholder"] = "search...";
             }
             $(select).selectpicker(select_picker_options);
+            $(select).selectpicker("val", value);
             this.init_actions_box(field);
         }
 
@@ -486,7 +533,7 @@ var form_elements = new function () {
             } else if (type === "range") {
                 field = await this.make_range_input(config);
             } else if (type === "reference_drop_down") {
-                field = await this.make_reference_drop_down(config);
+                field = this.make_reference_drop_down(config);
             } else if (type === "subform") {
                 // TODO handle cache and required for subforms
                 return await this.make_subform(config);
@@ -664,11 +711,11 @@ var form_elements = new function () {
         }
 
         this.enable_name = function (form, name) {
-            this.enable_fields(form.find(".caosdb-f-field[data-field-name='" + name + "']").toArray());
+            this.enable_fields($(form).find(".caosdb-f-field[data-field-name='" + name + "']").toArray());
         }
 
         this.disable_name = function (form, name) {
-            this.disable_fields(form.find(".caosdb-f-field[data-field-name='" + name + "']").toArray());
+            this.disable_fields($(form).find(".caosdb-f-field[data-field-name='" + name + "']").toArray());
         }
 
         this.make_script_form = async function (config, script) {
@@ -963,7 +1010,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-f-entity-property" data-field-name="' + name + '" />')
+            return $('<div class="form-group caosdb-f-field" data-field-name="' + name + '" />')
                 .css({"padding": "0"})[0];
         }
 
@@ -1133,7 +1180,7 @@ var form_elements = new function () {
             let ret = $(this._make_field_wrapper(config.name));
             let name = config.name;
             let label = this._make_input_label_str(config);
-            let type = config.type;
+            let type = config.type || "text";
             let value = config.value;
             let input = $('<input class="form-control caosdb-f-property-single-raw-value" type="' + type +
                 '" name="' + name +
diff --git a/src/core/js/preview.js b/src/core/js/preview.js
index 9af9bb0366097f6d1017a46ff077874ab34bf773..f74fd13ffd2fe373543c025866e91cfe6b6fd9eb 100644
--- a/src/core/js/preview.js
+++ b/src/core/js/preview.js
@@ -30,9 +30,25 @@ var preview = new function() {
         return props;
     }
 
+    // import from global name space.
+    this.getEntityID = getEntityID;
+    this.getEntityVersion = getEntityVersion;
+    this.getEntityIdVersion = getEntityIdVersion;
+
+    /**
+     * Get the id and, if present, the version of an entity from a link or a
+     * displayed reference.
+     *
+     * @param {HTMLElement} a link to an entity.
+     * @return {string} <id>[@<version>]
+     */
+    this.getEntityRef = function (link) {
+        return link.getElementsByClassName("caosdb-id")[0].textContent;
+    }
+
     /**
      * Initialize the preview feature for all reference properties which belong to certain entity.
-     *  
+     *
      * @param {HTMLElement} entity
      * @return {HTMLElement[]} The initialized properties. 
      */
@@ -113,7 +129,7 @@ var preview = new function() {
         app.onEnterWaiting = function(e) {
             executeFailSave(function() {
                 preview.addWaitingNotification(ref_property_elem, preview.createWaitingNotification());
-                let entityIds = preview.getEntityIds(refLinksContainer);
+                let entityIds = preview.getAllEntityRefs(refLinksContainer);
                 preview.retrievePreviewEntities(entityIds).then(entities => {
                     app.receivePreview(entities);
                 }, err => {
@@ -391,9 +407,9 @@ var preview = new function() {
         let selectorButtons = preview.getSelectorButtons(nav);
         selectorButtons.each((index, button) => {
             let slide_id = button.getAttribute("data-slide-to");
-            let entity_id = getEntityId(button);
-            let entity = preview.getEntityById(entities, entity_id);
-            if (entity == null) throw new Error("Entity with ID " + entity_id + " could not be found!");
+            let entity_id_version = preview.getEntityRef(button);
+            let entity = preview.getEntityByIdVersion(entities, entity_id_version);
+            if (entity == null) throw new Error("Entity with ID " + entity_id_version + " could not be found!");
             inner.children[slide_id].appendChild(preview.preparePreviewEntity(entity));
         });
 
@@ -443,8 +459,8 @@ var preview = new function() {
      *
      */
     this.createSinglePreview = function(entities, refLinksContainer) {
-        let entityId = getEntityId(preview.getReferenceLinks(refLinksContainer)[0]);
-        let entity = preview.preparePreviewEntity(preview.getEntityById(entities, entityId));
+        const entityRef = preview.getEntityRef(preview.getReferenceLinks(refLinksContainer)[0]);
+        const entity = preview.preparePreviewEntity(preview.getEntityByIdVersion(entities, entityRef));
         return entity;
     }
 
@@ -456,15 +472,25 @@ var preview = new function() {
      * @return {HTMLElement} The prepared entity.
      */
     this.preparePreviewEntity = function(entity) {
+        // move version modal into body because otherwise it would be displayed
+        // inside the caroussel. That would make sense but there is simply not
+        // enough space.
+        $(entity).find(".caosdb-f-entity-version-info").appendTo(document.body);
+
+
+        // make backref button smaller
+        $(entity).find(".caosdb-backref-link > .hidden-xs").hide();
+
         var preparedEntity = entity.cloneNode(true);
 
         // header is clickable:
-        let href = connection.getBasePath() + transaction.generateEntitiesUri([getEntityId(entity)]);
+        let href = connection.getBasePath() + transaction.generateEntitiesUri([preview.getEntityRef(entity)]);
         let link = $('<a title="Load this entity in a new window." href="' + href + '" class="label caosdb-id caosdb-id-button" target="_blank"></a>');
         let entityIdElem = $(preparedEntity).find('.label.caosdb-id');
         link.insertAfter(entityIdElem);
         link.append(entityIdElem.text() + " ");
         link.append('<span class="glyphicon glyphicon-new-window"/>');
+        // TODO this link is not visible due to webcaosdb.css (caosdb-id)
         entityIdElem.remove();
 
         return preparedEntity;
@@ -529,22 +555,34 @@ var preview = new function() {
     }
 
     /**
-     * Get the entity with a certain ID from an array of entities. Returns null if no such entity
-     * is in the array.
+     * Get the entity with a certain ID and Version (if applicable) from an
+     * array of entities. Returns null if no such entity is in the array.
+     *
      * @param {HTMLElement[]} entities
-     * @param {Number} entity_id
-     * @return {HTMLElement} The entity with id=entity_id or null.
+     * @param {String} entity_id_version
+     * @return {HTMLElement} Matching entity or null.
      */
-    this.getEntityById = function(entities, entity_id) {
+    this.getEntityByIdVersion = function(entities, entity_id_version) {
         if (entities == null) {
             throw new Error("entities must not be null");
         }
-        if (entity_id == null || isNaN(entity_id)) {
-            throw new Error("entity_id is to be a number");
+        if (entity_id_version == null) {
+            throw new Error("entity_id_version must not be null");
+        }
+
+        // if the entity_id_version contains an "@" it is actually a reference
+        // to a versioned entity. Otherwise, it is just an id an thus only the
+        // id has to be matched.
+        const is_versioned = entity_id_version.indexOf("@") !== -1;
+        var matches;
+        if (is_versioned) {
+            matches = (e) => preview.getEntityIdVersion(e) === entity_id_version;
+        } else {
+            matches = (e) => preview.getEntityID(e) === entity_id_version;
         }
         for (let i = 0; i < entities.length; i++) {
-            let e = entities[i];
-            if (getEntityId(e) === entity_id) {
+            const e = entities[i];
+            if (matches(e)) {
                 return e;
             }
         }
@@ -694,26 +732,27 @@ var preview = new function() {
      * @param {HTMLElement} refLinksContainer
      * @return {String[]} An array of entity ids.
      */
-    this.getEntityIds = function(refLinksContainer) {
+    this.getAllEntityRefs = function(refLinksContainer) {
         if (refLinksContainer == null) {
             throw new Error("parameter refLinksContainer must not be null.");
         }
 
-        let entityIds = [];
-        preview.getReferenceLinks(refLinksContainer).each((index, link) => {
-            entityIds.push(getEntityId(link));
-        });
-        return entityIds;
+        let entityRefs = [];
+        for (let link of preview.getReferenceLinks(refLinksContainer)) {
+            entityRefs.push(preview.getEntityRef(link));
+        };
+        return entityRefs;
     }
 
     /**
      * Get an array of all reference links.
      * 
      * @param {HTMLElement} refLinksContainer - The original reference links.
-     * @return {jQuery} A collection of links.
+     * @return {HTMLElement[]} A collection of links.
      */
     this.getReferenceLinks = function(refLinksContainer) {
-        return $(refLinksContainer).find('a').addBack('a').has('.caosdb-id');
+        return $(refLinksContainer)
+            .find('a').addBack('a').has('.caosdb-id').toArray();
     }
 };
 
diff --git a/src/core/js/query_shortcuts.js b/src/core/js/query_shortcuts.js
index c12e2b47b7c8b970b2a8177ebdb529dd5cb47efa..426a6db4ea3b25d10e0bb6c0289caa664b34e71f 100644
--- a/src/core/js/query_shortcuts.js
+++ b/src/core/js/query_shortcuts.js
@@ -824,20 +824,38 @@ var query_shortcuts = new function() {
             fields: this.make_create_fields(entity[0]),
         };
         var form = form_elements.make_form(form_config);
+        this._toggle_entity_property_class(form);
+
 
         this.logger.trace("leave make_create_form", form);
         return form;
     }
 
+    /**
+     * Add the "caosdb-f-entity-property" class to the form fields. Thus the
+     * fields are findable by the `getEntityXML` method which is used in
+     * `get_shortcut_entities` to generate the entity xml from the shortcut
+     * form.
+     *
+     * @param {HTMLElement} form - form which contains the fields where the
+     * class is to be added.
+     */
+    this._toggle_entity_property_class = function(form) {
+        form.addEventListener("caosdb.form.ready", () => {
+            $(form).find(".caosdb-f-field").toggleClass("caosdb-f-entity-property", true);
+        });
+        $(form).find(".caosdb-f-field").toggleClass("caosdb-f-entity-property", true);
+    }
+
     this.make_create_fields = function(include) {
         return [
             include,
             {
-                type: "text", name: query_shortcuts._shortcuts_property_description_name, required: true,
+                type: "text", name: query_shortcuts._shortcuts_property_description_name,
                 label: "Description", required: true, cached: true,
                 //help: query_shortcuts._description_help, TODO
             }, {
-                type: "text", name: query_shortcuts._shortcuts_property_query_name, required: true,
+                type: "text", name: query_shortcuts._shortcuts_property_query_name,
                 label: "Query", required: true, cached: true,
                 //help: query_shortcuts._query_help, TODO
             }
@@ -933,6 +951,7 @@ var query_shortcuts = new function() {
             fields: this.make_update_fields(entity[0], olddef.attr("data-shortcut-description"), olddef.attr("data-query-string")),
         };
         var form = form_elements.make_form(form_config);
+        this._toggle_entity_property_class(form);
 
         this.logger.trace("leave make_update_form", form);
         return form;
diff --git a/src/core/js/tour.js b/src/core/js/tour.js
index 57b426818abf2da0d58b46db3ce5bd893721bcc4..7b47dd37239c29b5c5e7edb67f1d16fe43bf8ad6 100644
--- a/src/core/js/tour.js
+++ b/src/core/js/tour.js
@@ -698,11 +698,14 @@ var tour = new function() {
 
     /**
      * Initialize the tour.
+     *
+     * The `refresh` argument is currently only used interactively on the debugging console.
      */
-    this.init = async function _in(refresh=false) {
+    this.init = async function _in(refresh) {
         try {
-            tour.debug("initializing tour module");
-            if (refresh) {
+            tour.debug("initializing tour module, refresh: " + refresh);
+            if (refresh === true) {
+                tour.info("Refreshing tour state.");
                 localStorage.removeItem("tour_state");
             }
             await tour.load_tour();
diff --git a/src/core/js/webcaosdb.js b/src/core/js/webcaosdb.js
index b496c3b7a176828cf99a3efd017d591dde2e937b..df93703d83fafde15908efb40c3ae578824980d9 100644
--- a/src/core/js/webcaosdb.js
+++ b/src/core/js/webcaosdb.js
@@ -26,7 +26,7 @@
 
 window.addEventListener('error', (e) => globalError(e.error));
 
-var globalError = function(error) {
+var globalError = function (error) {
     var stack = error.stack;
     var message = "Error! Please help to make CaosDB better! Copy this message and send it via email to info@indiscale.com.\n\n";
     message += error.toString();
@@ -40,7 +40,7 @@ var globalError = function(error) {
     throw error;
 }
 
-var globalClassNames = new function() {
+var globalClassNames = new function () {
     this.NotificationArea = "caosdb-preview-notification-area";
     this.WaitingNotification = "caosdb-preview-waiting-notification";
     this.ErrorNotification = "caosdb-preview-error-notification";
@@ -49,62 +49,192 @@ var globalClassNames = new function() {
 /**
  * navbar module contains convenience functions for the navbar.
  */
-this.navbar = new function() {
+this.navbar = new function () {
 
     var logger = log.getLogger("navbar");
     this.logger = logger;
     /**
-     * Add a button to the navbar (left navbar, append right), wrapped in a LI
-     * element.
+     * Add a button to the navbar.
      *
      * If the param `button` is a string then a suitable BUTTON element will be
-     * created. If the button is otherwise an HTMLElement it will just be
-     * wrapped and appended to the navbar.
+     * created. If the button is otherwise an HTMLElement or an array of
+     * HTMLElements it will just be wrapped, styled and appended to the navbar.
      *
-     * The optional `click_callback` function is bound to the click event.
+     * The optional `options` object knows the following optional keys:
+     *     @property {function} callback - a function which will be bound to the
+     *         click event of the button.
+     *     @property {string} title - the title attribute (a kind of tooltip)
+     *         of the button.
+     *     @property {HTMLElement} menu - where to append the button. Defaults
+     *         to the navbar itself.
      *
-     * @param {String|HTMLElement} button - add this button to navbar
-     * @param {function} [click_callback] - callback function for click
+     * If the `options["menu"] parameter is not set, the button is appended
+     * to the navbar directly and appears left of all previously appended
+     * children.
+     *
+     *
+     * @param {String|HTMLElement|HTMLElement[]} button - the button
+     * @param {object} [options] - further options
      * @return {HTMLElement} wrapper of the new button
      */
-    this.add_button = function(button, click_callback) {
+    this.add_button = function (button, options) {
+        logger.trace("enter add_button", button, options);
+
+        // assure that button parameter is {String|HTMLElement|HTMLElement[]}
+        if (typeof button === "undefined") {
+            throw new TypeError("button is expected to be a string, a single HTMLElement or HTMLElement[], was " + typeof button);
+        } else if (Array.isArray(button)) {
+            for (const element of button) {
+                if (!(element instanceof HTMLElement)) {
+                    throw new TypeError("button is expected to be a string, a single HTMLElement or HTMLElement[], element was " + typeof element);
+                }
+            }
+        } else if (!(typeof button === "string" || button instanceof String || button instanceof HTMLElement)) {
+            throw new TypeError("button is expected to be a string, a single HTMLElement or HTMLElement[], was " + typeof button);
+        }
+
+        // default: empty options
+        const _options = options || {};
 
         var button_elem = button;
         if (typeof button === "string" || button instanceof String) {
             // create button element from string
             button_elem = $('<button>' + button + '</button>');
         }
-        $(button_elem).toggleClass("navbar-btn", true);
-        $(button_elem).toggleClass("btn", true);
-        $(button_elem).toggleClass("btn-link", true);
+
+        // set title
+        const title = _options["title"];
+        if (typeof _options !== "undefined") {
+            $(button_elem).attr("title", title);
+        }
+
 
         // bind click
-        if(typeof click_callback === "function") {
+        const click_callback = _options["callback"]
+        if (typeof click_callback === "function") {
             $(button_elem).click(click_callback);
         }
 
         // wrapp button
         let wrapper = $("<li></li>").append(button_elem);
-        $('#top-navbar').find("ul.caosdb-navbar").first().append(wrapper);
 
+
+        // menu defaults to the navbar
+        const menu = _options["menu"] || this.get_navbar();
+
+        if ($(menu).is("ul.caosdb-navbar")) {
+            // special styling for buttons which are added directly to the
+            // navbar
+            $(button_elem)
+                .toggleClass("navbar-btn", true)
+                .toggleClass("btn", true)
+                .toggleClass("btn-link", true);
+        }
+
+        logger.debug("add", wrapper, "to", menu);
+        $(menu).append(wrapper);
+
+        logger.trace("leave add_button", wrapper[0]);
         return wrapper[0];
     }
 
-    this.init = function() {
+    this.init = function () {
         $("nav.navbar-fixed-top")
-            .on("shown.bs.collapse", function(e) {
+            .on("shown.bs.collapse", function (e) {
                 logger.trace("navbar expands", e);
             })
-            .on("hidden.bs.collapse", function(e) {
+            .on("hidden.bs.collapse", function (e) {
                 logger.trace("navbar shrinks", e);
             });
     }
 
+
+    /**
+     * Create initially empty tool box dropdown and append to navbar.
+     *
+     * The returned element is the drop-down menu which will eventually contain
+     * the tool buttons. That means, the buttons can be added directly to the
+     * returned element to appear in the drop-down menu.
+     *
+     * @return {HTMLElement} the dropdown-menu.
+     */
+    this.init_toolbox = function (name) {
+        var button = $(`<a class="dropdown-toggle"
+            data-toggle="dropdown" href="#">${name}
+            <span class="caret"></span></a>`)[0];
+
+        var menu = $(`<ul
+            class="caosdb-v-navbar-toolbox
+                caosdb-f-navbar-toolbox
+                dropdown-menu"
+            data-toolbox-name="${name}"/>`)[0];
+
+        const wrapper = this.add_button([button, menu]);
+        $(wrapper).toggleClass("dropdown", true)
+            .children()
+            .toggleClass("btn", false)
+            .toggleClass("btn-link", false)
+            .toggleClass("navbar-btn", false);
+
+        return menu;
+    }
+
+    /**
+     * Add a tool to a toolbox.
+     *
+     * If the button is a string a new button element is created showing the
+     * string as its label. Otherwise the button should be an HTMLElement which
+     * is directly added to the toolbox.
+     * toolbox.
+     *
+     * The `callback` is a function which will be bound to the click event of
+     * the button.
+     *
+     * The passed or created button is wrapped in a LI element and added to the
+     * toolbox. The wrapper of the button is returned
+     *
+     * The optional `options` object knows the following optional keys:
+     *     @property {function} callback - a function which will be bound to the
+     *         click event of the button.
+     *     @property {string} title - the title attribute (a kind of tooltip)
+     *         of the button.
+     *
+     * @param {string|HTMLElement} button
+     * @param {string} [toolbox] - the name of the toolbox
+     * @param {object} [options] further options.
+     * @return {HTMLElement} the button wrapper.
+     */
+    this.add_tool = function (button, toolbox, options) {
+        const toolbox_element = this.get_toolbox(toolbox);
+
+        // put toolbox_element as menu into the options for `add_button`
+        const _options = $.extend({
+            menu: toolbox_element
+        }, options);
+
+        const wrapper = this.add_button(button, _options);
+        return wrapper;
+    }
+
+    this.get_navbar = function () {
+        return $('#top-navbar')
+            .find("ul.caosdb-navbar")[0];
+    }
+
+    this.get_toolbox = function (name) {
+        var toolbox = $(this.get_navbar()).find(".caosdb-f-navbar-toolbox[data-toolbox-name='" + name + "']");
+        if (toolbox.length > 0) {
+            return toolbox[0];
+        } else {
+            return this.init_toolbox(name);
+        }
+    }
+
 }
 
 
-this.caosdb_utils = new function() {
-    this.assert_string = function(obj, name, optional=false) {
+this.caosdb_utils = new function () {
+    this.assert_string = function (obj, name, optional = false) {
         if (typeof obj === "undefined" && optional) {
             return obj;
         }
@@ -114,7 +244,7 @@ this.caosdb_utils = new function() {
         throw new TypeError(name + " is expected to be a string, was " + typeof obj);
     }
 
-    this.assert_type = function(obj, type, name, optional=false) {
+    this.assert_type = function (obj, type, name, optional = false) {
         if (typeof obj === "undefined" && optional) {
             return obj;
         }
@@ -124,14 +254,14 @@ this.caosdb_utils = new function() {
         return obj;
     }
 
-    this.assert_html_element = function(obj, name) {
+    this.assert_html_element = function (obj, name) {
         if (typeof obj === "undefined" || !(obj instanceof HTMLElement)) {
             throw new TypeError(name + " is expected to be an HTMLElement, was " + typeof obj);
         }
         return obj;
     }
 
-    this.assert_array = function(obj, name, wrap_if_not_array) {
+    this.assert_array = function (obj, name, wrap_if_not_array) {
         if (Array.isArray(obj)) {
             return obj;
         } else if (wrap_if_not_array) {
@@ -144,8 +274,8 @@ this.caosdb_utils = new function() {
 /**
  * connection module contains all ajax calls.
  */
-this.connection = new function() {
-    this._init = function() {
+this.connection = new function () {
+    this._init = function () {
         /**
          * Send a get request.
          */
@@ -224,8 +354,8 @@ this.connection = new function() {
                     console.log(error);
                 } else if (error.status != null) {
                     throw new Error(
-                        "POST scripting returned with HTTP status " + error.status
-                            + " - " + error.statusText);
+                        "POST scripting returned with HTTP status " + error.status +
+                        " - " + error.statusText);
                 } else {
                     throw error;
                 }
@@ -284,7 +414,7 @@ this.connection = new function() {
         /**
          * Return the base path of the server.
          */
-        this.getBasePath = function() {
+        this.getBasePath = function () {
             var base = window.location.origin + "/";
             if (typeof window.sessionStorage.caosdbBasePath !== "undefined") {
                 base = window.sessionStorage.caosdbBasePath;
@@ -305,7 +435,7 @@ this.connection = new function() {
          * @param {string[]|number[]} ids
          * @return {string} the URI.
          */
-        this.getEntityUri = function(ids) {
+        this.getEntityUri = function (ids) {
             return this.getBasePath() + transaction.generateEntitiesUri(ids);
         }
     }
@@ -316,14 +446,14 @@ this.connection = new function() {
  * transformation module contains all code for tranforming xml into html via
  * xslt.
  */
-this.transformation = new function() {
+this.transformation = new function () {
     /**
      * remove all permission information from a server's response.
      *
      * @param {XMLDocument} xml
      * @return {XMLDocument} without <Permissions> tags.
      */
-    this.removePermissions = function(xml) {
+    this.removePermissions = function (xml) {
         $(xml).find('Permissions').remove();
         return xml
     }
@@ -424,10 +554,10 @@ this.transformation = new function() {
      *          be included.
      * @return {XMLDocument} a new style sheets with all template rules;
      */
-    this.mergeXsltScripts = function(xslMain, xslIncludes) {
+    this.mergeXsltScripts = function (xslMain, xslIncludes) {
         let ret = getXSLScriptClone(xslMain);
         for (var i = 0; i < xslIncludes.length; i++) {
-            $(xslIncludes[i].firstElementChild).find('xsl\\:template').each(function(index) {
+            $(xslIncludes[i].firstElementChild).find('xsl\\:template').each(function (index) {
                 $(ret.firstElementChild).append(this);
             });
         }
@@ -439,7 +569,7 @@ this.transformation = new function() {
  * transaction module contains all code for insertion, update and deletion of 
  * entities. Currently, only updates are implemented.
  */
-this.transaction = new function() {
+this.transaction = new function () {
     this.classNameUpdateForm = "caosdb-update-entity-form";
 
     /**
@@ -499,14 +629,14 @@ this.transaction = new function() {
      * @param {String[]} entityIds - An array of entity ids..
      * @return {String} The uri.
      */
-    this.generateEntitiesUri = function(entityIds) {
+    this.generateEntitiesUri = function (entityIds) {
         return "Entity/" + entityIds.join("&");
     }
 
     /**
      * Submodule for update transactions.
      */
-    this.update = new function() {
+    this.update = new function () {
         /**
          * Create a form for updating entities. It has only a textarea and a
          * submit button.
@@ -520,7 +650,7 @@ this.transaction = new function() {
          * @param {String} entityXmlStr, the old entity
          * @param {function} putCallback, the function which sends a put request.
          */
-        this.createUpdateForm = function(entityXmlStr, putCallback) {
+        this.createUpdateForm = function (entityXmlStr, putCallback) {
             // check the parameters
             if (putCallback == null) {
                 throw new Error("putCallback function must not be null.");
@@ -544,13 +674,13 @@ this.transaction = new function() {
             form.append(resetButton);
 
             // reset restores the original xmlStr
-            form.on('reset', function(e) {
+            form.on('reset', function (e) {
                 textarea.find('textarea').val(entityXmlStr);
                 return false;
             });
 
             // submit calls the putCallback
-            form.submit(function(e) {
+            form.submit(function (e) {
                 putCallback(e.target.updateXml.value);
                 return false;
             });
@@ -568,7 +698,7 @@ this.transaction = new function() {
          * @param {HTMLElement} entity, the div which represent the entity.
          * @return {Object} a state machine.
          */
-        this.updateSingleEntity = function(entity) {
+        this.updateSingleEntity = function (entity) {
             let updatePanel = transaction.update.createUpdateEntityPanel(transaction.update.createUpdateEntityHeading($(entity).find('.caosdb-entity-panel-heading')[0]));
             var app = new StateMachine({
                 transitions: [{
@@ -593,14 +723,14 @@ this.transaction = new function() {
                     to: 'final'
                 }, ],
             });
-            app.errorHandler = function(fn) {
+            app.errorHandler = function (fn) {
                 try {
                     fn();
                 } catch (e) {
                     setTimeout(() => app.resetApp(e), 1000);
                 }
             }
-            app.onInit = function(e, entity) {
+            app.onInit = function (e, entity) {
                 // remove entity
                 $(entity).hide();
                 app.errorHandler(() => {
@@ -617,7 +747,7 @@ this.transaction = new function() {
                 });
                 // retrieve old xml, trigger state change when response is ready
             };
-            app.onOpenForm = function(e, entityXmlStr) {
+            app.onOpenForm = function (e, entityXmlStr) {
                 app.errorHandler(() => {
                     // create and show Form
                     let form = transaction.update.createUpdateForm(entityXmlStr, (xmlstr) => {
@@ -626,14 +756,14 @@ this.transaction = new function() {
                     updatePanel.append(form);
                 });
             };
-            app.onResetApp = function(e, error) {
+            app.onResetApp = function (e, error) {
                 $(entity).show();
                 $(updatePanel).remove();
                 if (error != null) {
                     globalError(error);
                 }
             };
-            app.onShowUpdatedEntity = function(e, newentity) {
+            app.onShowUpdatedEntity = function (e, newentity) {
                 // remove updatePanel
                 updatePanel.remove();
                 // show new version of entity
@@ -641,7 +771,7 @@ this.transaction = new function() {
                 // remove old version
                 $(entity).remove();
             };
-            app.onSubmitForm = function(e, xmlstr) {
+            app.onSubmitForm = function (e, xmlstr) {
                 // remove form
                 $(updatePanel).find('form').remove();
 
@@ -673,7 +803,7 @@ this.transaction = new function() {
                     );
                 });
             };
-            app.onLeaveWaitPutEntity = function() {
+            app.onLeaveWaitPutEntity = function () {
                 // remove waiting notifications
                 removeAllWaitingNotifications(updatePanel);
             };
@@ -700,19 +830,19 @@ this.transaction = new function() {
             return xml2str(transformation.removePermissions(xml));
         }
 
-        this.createWaitRetrieveNotification = function() {
+        this.createWaitRetrieveNotification = function () {
             return createWaitingNotification("Retrieving xml and loading form. Please wait.");
         }
 
-        this.createWaitUpdateNotification = function() {
+        this.createWaitUpdateNotification = function () {
             return createWaitingNotification("Sending update to the server. Please wait.");
         }
 
-        this.createErrorInUpdatedEntityNotification = function() {
+        this.createErrorInUpdatedEntityNotification = function () {
             return createErrorNotification("The update was not successful.");
         }
 
-        this.addErrorNotification = function(elem, err) {
+        this.addErrorNotification = function (elem, err) {
             $(elem).append(err);
             return elem;
         }
@@ -724,7 +854,7 @@ this.transaction = new function() {
          * @param {HTMLElement} heading, the heading of the panel.
          * @return {HTMLElement} A div.
          */
-        this.createUpdateEntityPanel = function(heading) {
+        this.createUpdateEntityPanel = function (heading) {
             let panel = $('<div class="panel panel-default" style="border-color: blue;"/>');
             panel.append(heading);
             return panel[0];
@@ -739,7 +869,7 @@ this.transaction = new function() {
          * @param {HTMLElement} entityHeading, the heading of the entity.
          * @return {HTMLElement} the heading for the update panel.
          */
-        this.createUpdateEntityHeading = function(entityHeading) {
+        this.createUpdateEntityHeading = function (entityHeading) {
             let heading = entityHeading.cloneNode(true);
             let update = $('<span><h3>Update</h3></span>')[0];
             $(heading).children().slice(1).remove();
@@ -754,19 +884,19 @@ this.transaction = new function() {
          * @param {HTMLElement} entityPanel, the entity panel.
          * @return {HTMLElement} the heading.
          */
-        this.getEntityHeading = function(entityPanel) {
+        this.getEntityHeading = function (entityPanel) {
             return $(entityPanel).find('.caosdb-entity-panel-heading')[0];
         }
 
-        this.initUpdate = function(button) {
+        this.initUpdate = function (button) {
             transaction.update.updateSingleEntity(
                 $(button).closest('.caosdb-entity-panel')[0]
             );
         }
 
-        this.createCloseButton = function(close, callback) {
+        this.createCloseButton = function (close, callback) {
             let button = $('<button title="Cancel update" class="btn btn-link close" aria-label="Cancel update">&times;</button>');
-            button.bind('click', function() {
+            button.bind('click', function () {
                 $(this).closest(close).hide();
                 callback();
             });
@@ -776,7 +906,7 @@ this.transaction = new function() {
 }
 
 
-var paging = new function() {
+var paging = new function () {
 
     this.defaultPageLen = 10;
     /**
@@ -791,7 +921,7 @@ var paging = new function() {
      *            the number of entities which are currently shown on this
      *            page.
      */
-    this.initPaging = function(href, totalEntities) {
+    this.initPaging = function (href, totalEntities) {
 
         if (totalEntities == null) {
             return false;
@@ -841,7 +971,7 @@ var paging = new function() {
      *            the page the new uri shall point to
      * @return a string uri which points to the page denotes by the parameter
      */
-    this.getPageHref = function(uri_old, page) {
+    this.getPageHref = function (uri_old, page) {
         if (uri_old == null) {
             throw new Error("uri was null.");
         }
@@ -869,7 +999,7 @@ var paging = new function() {
      * @param uri An arbitrary URI, usually the current URI of the window.
      * @return The first part of the query segment which begins with 'P='
      */
-    this.getPSegmentFromUri = function(uri) {
+    this.getPSegmentFromUri = function (uri) {
         if (uri == null) {
             throw new Error("uri was null.");
         }
@@ -886,7 +1016,7 @@ var paging = new function() {
      *            a paging string
      * @return a String
      */
-    this.getPrevPage = function(P) {
+    this.getPrevPage = function (P) {
         if (P == null) {
             throw new Error("P was null");
         }
@@ -920,7 +1050,7 @@ var paging = new function() {
      *            total numbers of entities.
      * @return a String
      */
-    this.getNextPage = function(P, n) {
+    this.getNextPage = function (P, n) {
         // check n and P for null values and correct formatting
         if (n == null) {
             throw new Error("n was null");
@@ -950,8 +1080,8 @@ var paging = new function() {
     }
 };
 
-var queryForm = new function() {
-    this.init = function(form) {
+var queryForm = new function () {
+    this.init = function (form) {
         this.restoreLastQuery(form, () => window.sessionStorage.lastQuery);
         this.bindOnClick(form, (set) => {
             window.sessionStorage.lastQuery = set;
@@ -959,7 +1089,7 @@ var queryForm = new function() {
         });
     };
 
-    this.restoreLastQuery = function(form, getter) {
+    this.restoreLastQuery = function (form, getter) {
         if (form == null) {
             throw new Error("form was null");
         }
@@ -972,16 +1102,16 @@ var queryForm = new function() {
      * @value {string} query - the query string.
      * @param {string} paging - the paging string, e.g. 0L10.
      */
-    this.redirect = function(query, paging) {
+    this.redirect = function (query, paging) {
         var pagingparam = ""
-        if(paging && paging.length > 0) {
+        if (paging && paging.length > 0) {
             pagingparam = "P=" + paging + "&";
         }
         location.href = connection.getBasePath() + "Entity/?" + pagingparam + "query=" + query;
     }
 
-    this.bindOnClick = function(form, setter) {
-        if (setter == null || typeof(setter) !== 'function' || setter.length !== 1) {
+    this.bindOnClick = function (form, setter) {
+        if (setter == null || typeof (setter) !== 'function' || setter.length !== 1) {
             throw new Error("setter must be a function with one param");
         }
 
@@ -990,7 +1120,7 @@ var queryForm = new function() {
           and the click handler of the button.
           See https://developer.mozilla.org/en-US/docs/Web/Events/submit why this is necessary.
           */
-        var submithandler = function() {
+        var submithandler = function () {
 
             // store current query
             var queryField = form.query;
@@ -1004,7 +1134,7 @@ var queryForm = new function() {
             setter(queryField.value);
 
             var paging = "";
-            if(form.P && !queryForm.isSelectQuery(queryField.value)) {
+            if (form.P && !queryForm.isSelectQuery(queryField.value)) {
                 paging = form.P.value
             }
 
@@ -1013,15 +1143,15 @@ var queryForm = new function() {
 
 
         // handler for the form
-	    form.onsubmit = function(e) {
-	        e.preventDefault();
-	        submithandler();
+        form.onsubmit = function (e) {
+            e.preventDefault();
+            submithandler();
             return false;
-	    };
+        };
 
         // same handler for the button
-        form.getElementsByClassName("caosdb-search-btn")[0].onclick = function() {
-	        submithandler();
+        form.getElementsByClassName("caosdb-search-btn")[0].onclick = function () {
+            submithandler();
         };
     };
 
@@ -1031,7 +1161,7 @@ var queryForm = new function() {
      * @param {HTMLElement} query, the query to be tested.
      * @return {Boolean}
      */
-    this.isSelectQuery = function(query) {
+    this.isSelectQuery = function (query) {
         return query.toUpperCase().startsWith("SELECT");
     }
 
@@ -1042,7 +1172,7 @@ var queryForm = new function() {
      * @param {HTMLElement} form, the query form.
      * @return {HTMLElement} the form without the paging input.
      */
-    this.removePagingField = function(form) {
+    this.removePagingField = function (form) {
         $(form.P).remove();
         return form;
     }
@@ -1052,9 +1182,9 @@ var queryForm = new function() {
 /**
  * Small module containing only a converter from markdown to html.
  */
-this.markdown = new function() {
+this.markdown = new function () {
     this.dependencies = ["showdown", "caosdb_utils"];
-    this.init = function() {
+    this.init = function () {
         this.converter = new showdown.Converter();
     };
 
@@ -1065,7 +1195,7 @@ this.markdown = new function() {
      * @param {HTMLElement} textElement - an element with text which is to be
      * converted to html.
      */
-    this.toHtml = function(textElement) {
+    this.toHtml = function (textElement) {
         let text = $(textElement).html();
         let html = this.textToHtml(text);
         $(textElement).html(html);
@@ -1074,28 +1204,28 @@ this.markdown = new function() {
     /**
      * Convert a markdown text to HTML
      */
-    this.textToHtml = function(text) {
+    this.textToHtml = function (text) {
         caosdb_utils.assert_string(text, "param `text`");
         return this.converter.makeHtml(text.trim());
     };
 
-    $(document).ready(function() {
+    $(document).ready(function () {
         caosdb_modules.register(markdown);
     });
 }
 
-var hintMessages = new function() {
-    this.init = function() {
+var hintMessages = new function () {
+    this.init = function () {
         for (var entity of $('.caosdb-entity-panel')) {
             this.hintMessages(entity);
         }
     }
 
-    this.removeMessages = function(entity) {
+    this.removeMessages = function (entity) {
         $(entity).find(".alert").remove();
     }
 
-    this.unhintMessages = function(entity) {
+    this.unhintMessages = function (entity) {
         $(entity).find(".caosdb-f-message-badge").remove();
         $(entity).find(".alert").show();
     }
@@ -1115,7 +1245,7 @@ var hintMessages = new function() {
      * @param {HTMLElement} entity - the element where to replace the
      *     messages.
      */
-    this.hintMessages = function(entity) {
+    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
@@ -1131,7 +1261,7 @@ var hintMessages = new function() {
         for (let alrt in messageType) {
 
             // find all message divs
-            $(entity).find(".alert.alert-" + alrt).each(function(index) {
+            $(entity).find(".alert.alert-" + alrt).each(function (index) {
                 var messageElem = $(this);
 
                 // this way only one badge is shown, even if there are more
@@ -1140,7 +1270,7 @@ var hintMessages = new function() {
 
                     // 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) {
+                    messageElem.parent().find(".caosdb-f-message-badge.alert-" + alrt).on("click", function (e) {
 
                         // TODO use remove here instead of hide?
                         $(this).hide();
@@ -1235,7 +1365,7 @@ function postXml(xml, basepath, querySegment, timeout) {
         dataType: 'xml',
         timeout: timeout,
         statusCode: {
-            401: function() {
+            401: function () {
                 throw new Error("unauthorized");
             },
         },
@@ -1273,7 +1403,7 @@ async function load_config(filename) {
         } else if (error.status == 404) {
             return [];
         } else {
-            throw new Error("loading '"+ uri + "' failed.", error);
+            throw new Error("loading '" + uri + "' failed.", error);
         }
     }
     return data;
@@ -1391,13 +1521,13 @@ function initOnDocumentReady() {
     }
 
     // show image 100% width
-    $(".entity-image-preview").click(function() {
+    $(".entity-image-preview").click(function () {
         $(this).css('width', '100%');
         $(this).css('max-width', "");
         $(this).css('max-height', "");
     });
 
-    if(typeof caosdb_modules.auto_init === "undefined") {
+    if (typeof caosdb_modules.auto_init === "undefined") {
         // the test index.html sets this to false, 
         // unset -> no tests
         caosdb_modules.auto_init = true;
@@ -1429,12 +1559,12 @@ class _CaosDBModules {
      * @throws TypeError - if module has no `init` method.
      */
     register(module) {
-        if(!(typeof module.init === "function")) {
+        if (!(typeof module.init === "function")) {
             throw new TypeError("modules must define an init function");
         }
 
         this.modules.push(module);
-        if(this.auto_init) {
+        if (this.auto_init) {
             this._init_module(module);
         }
     }
diff --git a/src/core/xsl/entity.xsl b/src/core/xsl/entity.xsl
index 6b709d0e25647807d852bf9d6b778b54ff543d08..299b1dac37a1029e5bc7c48008efa3735d61037b 100644
--- a/src/core/xsl/entity.xsl
+++ b/src/core/xsl/entity.xsl
@@ -46,9 +46,9 @@
       <xsl:attribute name="href">
         <xsl:value-of select="concat($entitypath, '?P=0L10&amp;query=FIND+Entity+which+references+', current())"/>
       </xsl:attribute>
-      <span class="glyphicon glyphicon-share-alt flipped-horiz-icon"/> References
+      <span class="glyphicon glyphicon-share-alt flipped-horiz-icon"/>
+      <span class="hidden-xs"> References</span>
     </a>
-    <span class="spacer"/>
   </xsl:template>
   <!-- special entity properties like type, checksum, path... -->
   <xsl:template match="@datatype" mode="entity-heading-attributes-datatype">
@@ -87,11 +87,16 @@
     </p>
   </xsl:template>
   <xsl:template match="*" mode="entity-action-panel">
-    <div class="caosdb-entity-actions-panel text-right btn-group-xs"></div>
+    <div class="caosdb-entity-actions-panel text-right btn-group-xs">
+        <xsl:apply-templates select="Version/Successor" mode="entity-action-panel-version">
+          <xsl:with-param name="entityId" select="@id"/>
+        </xsl:apply-templates>
+    </div>
   </xsl:template>
   <!-- Main entry for ENTITIES -->
   <xsl:template match="Property|Record|RecordType|File" mode="entities">
     <div class="panel panel-default caosdb-entity-panel">
+      <xsl:apply-templates select="Version" mode="entity-version-marker"/>
       <xsl:attribute name="id">
         <xsl:value-of select="@id"/>
       </xsl:attribute>
@@ -138,17 +143,22 @@
             </h5>
           </div>
           <div class="col-sm-4 text-right">
-            <h5>
+            <h5 class="caosdb-v-entity-header-buttons-list">
               <!-- Button for expanding/collapsing the comments section-->
-              <span class="caosdb-clickable glyphicon glyphicon-comment" data-toggle="collapse" title="Comments" style="margin-right: 10px;">
+              <span class="caosdb-clickable glyphicon glyphicon-comment" data-toggle="collapse" title="Toggle the comments section at the bottom of this entity.">
                 <xsl:attribute name="data-target">
                   <xsl:value-of select="concat('#', 'comment_', $entityid)"/>
                 </xsl:attribute>
               </span>
+              <span>
               <xsl:apply-templates mode="backreference-link" select="@id"/>
+              </span>
               <span class="label caosdb-id caosdb-id-button hidden">
                 <xsl:value-of select="@id"/>
               </span>
+              <xsl:apply-templates mode="entity-heading-attributes-version" select="Version">
+                <xsl:with-param name="entityId" select="@id"/>
+              </xsl:apply-templates>
             </h5>
           </div>
         </div>
@@ -285,6 +295,9 @@
           <xsl:otherwise>
             <xsl:element name="span">
               <xsl:attribute name="class">
+                <!-- DEPRECATED css class .caosdb-property-text-value - Use
+                     .caosdb-f-property-single-raw-value or introduce new 
+                     .caosdb-v-property-text-value -->
                 <xsl:value-of select="'caosdb-f-property-single-raw-value caosdb-property-text-value'"/>
               </xsl:attribute>
               <xsl:call-template name="trim">
@@ -297,6 +310,9 @@
         </xsl:choose>
       </xsl:when>
       <xsl:otherwise>
+        <!-- DEPRECATED css class .caosdb-property-text-value - Use
+             .caosdb-f-property-single-raw-value or introduce new 
+             .caosdb-v-property-text-value -->
         <span class="caosdb-f-property-single-raw-value caosdb-property-text-value"/>
       </xsl:otherwise>
     </xsl:choose>
@@ -318,6 +334,19 @@
             </xsl:with-param>
           </xsl:call-template>
         </xsl:for-each>
+        <xsl:for-each select="Record|RecordType|File|Property">
+          <xsl:call-template name="single-value">
+            <xsl:with-param name="reference">
+              <xsl:value-of select="'true'"/>
+            </xsl:with-param>
+            <xsl:with-param name="value">
+              <xsl:value-of select="@id"/>
+            </xsl:with-param>
+            <xsl:with-param name="boolean">
+              <xsl:value-of select="'false'"/>
+            </xsl:with-param>
+          </xsl:call-template>
+        </xsl:for-each>
       </xsl:element>
     </div>
   </xsl:template>
@@ -351,7 +380,7 @@
       <xsl:when test="contains(concat('&lt;',@datatype),'&lt;LIST&lt;')">
         <!-- list -->
         <xsl:choose>
-          <xsl:when test="translate(normalize-space(text()),'0123456789','')='' and not(contains('+LIST&lt;INTEGER>+LIST&lt;DOUBLE>+LIST&lt;TEXT>+LIST&lt;BOOLEAN>+LIST&lt;DATETIME>+',concat('+',@datatype,'+')))">
+          <xsl:when test="not(contains('+LIST&lt;INTEGER>+LIST&lt;DOUBLE>+LIST&lt;TEXT>+LIST&lt;BOOLEAN>+LIST&lt;DATETIME>+',concat('+',@datatype,'+')))">
             <xsl:apply-templates mode="property-reference-value-list" select="."/>
           </xsl:when>
           <xsl:otherwise>
@@ -361,17 +390,38 @@
       </xsl:when>
       <!-- hence, this is no collection -->
       <xsl:otherwise>
-        <xsl:call-template name="single-value">
-          <xsl:with-param name="value">
-            <xsl:value-of select="text()"/>
-          </xsl:with-param>
-          <xsl:with-param name="reference">
-            <xsl:value-of select="translate(normalize-space(text()),'0123456789','')='' and not(contains('+INTEGER+DOUBLE+TEXT+BOOLEAN+DATETIME+',concat('+',@datatype,'+')))"/>
-          </xsl:with-param>
-          <xsl:with-param name="boolean">
-            <xsl:value-of select="@datatype='BOOLEAN'"/>
-          </xsl:with-param>
-        </xsl:call-template>
+        <xsl:choose>
+          <!-- the referenced entities have been returned. -->
+          <xsl:when test="*[@id]">
+            <xsl:for-each select="*[@id]">
+              <xsl:call-template name="single-value">
+                <xsl:with-param name="reference">
+                  <xsl:value-of select="'true'"/>
+                </xsl:with-param>
+                <xsl:with-param name="value">
+                  <xsl:value-of select="@id"/>
+                </xsl:with-param>
+                <xsl:with-param name="boolean">
+                  <xsl:value-of select="'false'"/>
+                </xsl:with-param>
+              </xsl:call-template>
+            </xsl:for-each>
+          </xsl:when>
+          <xsl:otherwise>
+            <!-- only the ids are available -->
+            <xsl:call-template name="single-value">
+              <xsl:with-param name="value">
+                <xsl:value-of select="text()"/>
+              </xsl:with-param>
+              <xsl:with-param name="reference">
+                <xsl:value-of select="not(contains('+INTEGER+DOUBLE+TEXT+BOOLEAN+DATETIME+',concat('+',@datatype,'+')))"/>
+              </xsl:with-param>
+              <xsl:with-param name="boolean">
+                <xsl:value-of select="@datatype='BOOLEAN'"/>
+              </xsl:with-param>
+            </xsl:call-template>
+          </xsl:otherwise>
+        </xsl:choose>
       </xsl:otherwise>
     </xsl:choose>
     <!-- unit -->
@@ -452,4 +502,115 @@
       </li>
     </ul>
   </xsl:template>
+  <!--VERSIONING-->
+  <xsl:template match="Version" mode="entity-heading-attributes-version">
+    <xsl:param name="entityId"/>
+    <xsl:param name="versionModalId">version-modal-<xsl:value-of select="generate-id()"/></xsl:param>
+    <!-- the clock button which opens the window with the versioning info -->
+    <button title="Versioning Info" type="button" data-toggle="modal">
+      <xsl:attribute name="data-target">#<xsl:value-of select="$versionModalId"/></xsl:attribute>
+      <xsl:attribute name="class">
+        caosdb-f-entity-version-button caosdb-v-entity-version-button btn
+        <xsl:if test="Successor">
+          <!-- indicate old version by color and symbol -->
+          <xsl:value-of select="' text-danger'"/>
+        </xsl:if>
+      </xsl:attribute>
+      <span class="glyphicon glyphicon-time"/>
+    </button>
+    <!-- the following div.modal is the window that pops up when the user clicks on the clock button -->
+    <div class="caosdb-f-entity-version-info modal fade" tabindex="-1" role="dialog">
+      <xsl:attribute name="id"><xsl:value-of select="$versionModalId"/></xsl:attribute>
+      <div class="modal-dialog modal-lg" role="document">
+        <div class="modal-content text-left">
+          <div>
+            <xsl:attribute name="class">
+              modal-header
+              <xsl:if test="Successor">
+                <!-- indicate old version by color -->
+                <xsl:value-of select="' bg-danger'"/>
+              </xsl:if>
+            </xsl:attribute>
+            <button type="button" class="close" data-dismiss="modal" aria-label="Close" title="Close"><span aria-hidden="true">×</span></button>
+            <h4 class="modal-title">Version Info</h4>
+            <p class="caosdb-entity-heading-attr">
+              <em class="caosdb-entity-heading-attr-name">
+              This is
+              <xsl:if test="Successor"><b>not</b></xsl:if>
+              the latest version of this entity.
+              </em>
+            </p>
+          </div>
+          <div class="modal-body">
+            <xsl:apply-templates mode="entity-version-modal-head" select="Successor">
+              <xsl:with-param name="entityId" select="$entityId"/>
+            </xsl:apply-templates>
+            <xsl:apply-templates mode="entity-version-modal-successor" select="Successor">
+              <xsl:with-param name="entityId" select="$entityId"/>
+            </xsl:apply-templates>
+            <p class="caosdb-entity-heading-attr">
+              <em class="caosdb-entity-heading-attr-name">This version:</em>
+              <xsl:value-of select="@id"/> (<xsl:value-of select="@date"/>)
+            </p>
+            <xsl:apply-templates mode="entity-version-modal-predecessor" select="Predecessor">
+              <xsl:with-param name="entityId" select="$entityId"/>
+            </xsl:apply-templates>
+          </div>
+        </div>
+      </div>
+    </div>
+  </xsl:template>
+  <xsl:template match="Predecessor" mode="entity-version-modal-predecessor">
+    <!-- content of the versioning window -->
+    <xsl:param name="entityId"/>
+    <p class="caosdb-entity-heading-attr">
+      <em class="caosdb-entity-heading-attr-name">Previous version:</em>
+      <a><xsl:attribute name="href"><xsl:value-of select="$entityId"/>@<xsl:value-of select="@id"/></xsl:attribute>
+        <xsl:value-of select="@id"/> (<xsl:value-of select="@date"/>)
+      </a>
+    </p>
+  </xsl:template>
+  <xsl:template match="Successor" mode="entity-version-modal-head">
+    <!-- content of the versioning window -->
+    <xsl:param name="entityId"/>
+    <p class="caosdb-entity-heading-attr">
+      <em class="caosdb-entity-heading-attr-name">Newest version:</em>
+      <a><xsl:attribute name="href"><xsl:value-of select="$entityId"/>@HEAD</xsl:attribute>
+        <xsl:value-of select="$entityId"/>@HEAD
+      </a>
+    </p>
+  </xsl:template>
+  <xsl:template match="Successor" mode="entity-version-modal-successor">
+    <!-- content of the versioning window -->
+    <xsl:param name="entityId"/>
+    <p class="caosdb-entity-heading-attr">
+      <em class="caosdb-entity-heading-attr-name">Next version:</em>
+      <a><xsl:attribute name="href"><xsl:value-of select="$entityId"/>@<xsl:value-of select="@id"/></xsl:attribute>
+        <xsl:value-of select="@id"/> (<xsl:value-of select="@date"/>)
+      </a>
+    </p>
+  </xsl:template>
+  <xsl:template match="Version/Successor" mode="entity-action-panel-version">
+    <!-- clickable warning message in the entity actions panel when there exists a newer version -->
+    <xsl:param name="entityId"/>
+    <a class="caosdb-f-entity-version-old-warning alert-warning btn btn-link" title="Go to the latest version of this entity.">
+      <xsl:attribute name="href"><xsl:value-of select="$entityId"/>@HEAD</xsl:attribute>
+      <strong>Warning</strong> A newer version exists!
+    </a>
+  </xsl:template>
+  <xsl:template match="Version" mode="entity-version-marker">
+    <!-- content of the data-version-id attribute -->
+    <xsl:attribute name="data-version-id">
+        <xsl:value-of select="@id"/>
+    </xsl:attribute>
+    <xsl:apply-templates select="Successor" mode="entity-version-marker"/>
+  </xsl:template>
+  <xsl:template match="Successor" mode="entity-version-marker">
+    <!-- content of the data-version-successor attribute
+         This data-attribute marks entities which have a newer version.
+    -->
+    <xsl:attribute name="data-version-successor">
+      <xsl:value-of select="@id"/>
+    </xsl:attribute>
+  </xsl:template>
 </xsl:stylesheet>
diff --git a/src/core/xsl/entity_palette.xsl b/src/core/xsl/entity_palette.xsl
index aea42a8750a74f509557bcf842e4d1f8dc97bef9..961a51dc51584c3fe87496e54f9837fcec9de0b0 100644
--- a/src/core/xsl/entity_palette.xsl
+++ b/src/core/xsl/entity_palette.xsl
@@ -51,9 +51,22 @@
   </xsl:template>
 
   <xsl:template match="Property">
-    <li class="caosdb-f-edit-drag list-group-item caosdb-v-edit-drag">
-      <xsl:attribute name="id">caosdb-f-edit-p-<xsl:value-of select="@id"/></xsl:attribute>
-      <xsl:value-of select="@name"/>
-    </li>
+    <xsl:choose>
+      <xsl:when test="@name='name'">
+          <!-- ignore name property -->
+      </xsl:when>
+      <xsl:when test="@name='description'">
+          <!-- ignore description property -->
+      </xsl:when>
+      <xsl:when test="@name='unit'">
+          <!-- ignore unit property -->
+      </xsl:when>
+      <xsl:otherwise>
+        <li class="caosdb-f-edit-drag list-group-item caosdb-v-edit-drag">
+          <xsl:attribute name="id">caosdb-f-edit-p-<xsl:value-of select="@id"/></xsl:attribute>
+          <xsl:value-of select="@name"/>
+        </li>
+      </xsl:otherwise>
+    </xsl:choose>
   </xsl:template>
 </xsl:stylesheet>
diff --git a/src/core/xsl/main.xsl b/src/core/xsl/main.xsl
index f4a128f63f52488657b32e8ff03043c276d4aaa3..efbf2e6b6e4db0ad7b14cb1c2483157bc79001f1 100644
--- a/src/core/xsl/main.xsl
+++ b/src/core/xsl/main.xsl
@@ -180,6 +180,11 @@
         <xsl:value-of select="concat($basepath,'webinterface/${BUILD_NUMBER}/js/query_shortcuts.js')"/>
       </xsl:attribute>
     </xsl:element>
+    <xsl:element name="script">
+      <xsl:attribute name="src">
+        <xsl:value-of select="concat($basepath,'webinterface/${BUILD_NUMBER}/js/ext_jupyterdrag.js')"/>
+      </xsl:attribute>
+    </xsl:element>
     <xsl:element name="script">
       <xsl:attribute name="src">
         <xsl:value-of select="concat($basepath,'webinterface/${BUILD_NUMBER}/js/annotation.js')"/>
@@ -240,6 +245,16 @@
         <xsl:value-of select="concat($basepath,'webinterface/${BUILD_NUMBER}/js/ext_revisions.js')"/>
       </xsl:attribute>
     </xsl:element>
+    <xsl:element name="script">
+      <xsl:attribute name="src">
+        <xsl:value-of select="concat($basepath,'webinterface/${BUILD_NUMBER}/js/ext_sss_markdown.js')"/>
+      </xsl:attribute>
+    </xsl:element>
+    <xsl:element name="script">
+      <xsl:attribute name="src">
+        <xsl:value-of select="concat($basepath,'webinterface/${BUILD_NUMBER}/js/ext_trigger_crawler_form.js')"/>
+      </xsl:attribute>
+    </xsl:element>
     <!--JS_EXTENSIONS-->
   </xsl:template>
   <xsl:template name="caosdb-data-container">
diff --git a/src/core/xsl/query.xsl b/src/core/xsl/query.xsl
index 8b6ae132b7b98118b03882c6bdeec259019c190b..be49ed7d889e920cfee87e723c4c5c3b8efb27b2 100644
--- a/src/core/xsl/query.xsl
+++ b/src/core/xsl/query.xsl
@@ -169,6 +169,7 @@
       </span>
     </a>
   </xsl:template>
+
   <xsl:template name="select-table-row">
     <xsl:param name="entity-id"/>
     <tr>
@@ -180,7 +181,7 @@
           <xsl:with-param name="entity-id" select="$entity-id"/>
         </xsl:call-template>
       </td>
-      <xsl:for-each select="/Response/Query/Selection/Selector[@name!='id']">
+      <xsl:for-each select="/Response/Query/Selection/Selector">
         <xsl:call-template name="select-table-cell">
           <xsl:with-param name="entity-id" select="$entity-id"/>
           <xsl:with-param name="field-name" select="translate(@name, $uppercase, $lowercase)"/>
@@ -188,6 +189,7 @@
       </xsl:for-each>
     </tr>
   </xsl:template>
+
   <xsl:template name="select-table-cell">
     <xsl:param name="entity-id"/>
     <xsl:param name="field-name"/>
@@ -196,18 +198,90 @@
         <xsl:value-of select="$field-name"/>
       </xsl:attribute>
       <div class="caosdb-f-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]"/>
-          </xsl:when>
-          <xsl:when test="/Response/*[@id=$entity-id]/Property[translate(@name, $uppercase, $lowercase)=$field-name]">
-            <xsl:apply-templates mode="property-value" select="/Response/*[@id=$entity-id]/Property[translate(@name, $uppercase, $lowercase)=$field-name]"/>
-          </xsl:when>
-        </xsl:choose>
+        <xsl:apply-templates select="/Response/*[@id=$entity-id]" mode="walk-select-segments">
+          <xsl:with-param name="first-segment">
+            <xsl:value-of select="substring-before(concat($field-name, '.'), '.')"/>
+          </xsl:with-param>
+          <xsl:with-param name="next-segments">
+            <xsl:value-of select="substring-after($field-name, '.')"/>
+          </xsl:with-param>
+        </xsl:apply-templates>
       </div>
     </td>
   </xsl:template>
+
+  <xsl:template match="Property" mode="walk-select-segments">
+    <!-- handle properties -->
+    <xsl:param name="next-segments"/>
+
+    <xsl:choose>
+      <xsl:when test="$next-segments='value'">
+        <!--handle value-->
+        <xsl:apply-templates mode="property-value" select="."/>
+      </xsl:when>
+
+      <xsl:when test="translate($next-segments, $uppercase, $lowercase)='unit'">
+        <!--handle unit-->
+        <xsl:call-template name="single-value">
+          <xsl:with-param name="value">
+            <xsl:value-of select="@unit"/>
+          </xsl:with-param>
+        </xsl:call-template>
+      </xsl:when>
+
+      <xsl:when test="$next-segments!=''">
+        <!--walk to next level of nested entities-->
+        <xsl:apply-templates select="*[@id]" mode="walk-select-segments">
+          <xsl:with-param name="first-segment">
+            <xsl:value-of select="substring-before(concat($next-segments, '.'), '.')"/>
+          </xsl:with-param>
+          <xsl:with-param name="next-segments">
+            <xsl:value-of select="substring-after($next-segments, '.')"/>
+          </xsl:with-param>
+        </xsl:apply-templates>
+      </xsl:when>
+
+      <xsl:otherwise>
+        <!--next is empty. handle complete property-->
+        <xsl:apply-templates mode="property-value" select="."/>
+      </xsl:otherwise>
+    </xsl:choose>
+  </xsl:template>
+
+  <xsl:template match="*" mode="walk-select-segments">
+    <!-- handle anything but attributes and properties -->
+    <xsl:param name="first-segment"/>
+    <xsl:param name="next-segments"/>
+
+    <xsl:choose>
+      <xsl:when test="@*[translate($first-segment, $uppercase, $lowercase)=name()]">
+        <!--handle attributes-->
+        <xsl:call-template name="single-value">
+          <xsl:with-param name="value">
+            <xsl:value-of select="@*[translate(name(), $uppercase, $lowercase)=$first-segment]"/>
+          </xsl:with-param>
+        </xsl:call-template>
+      </xsl:when>
+
+      <xsl:when test="$next-segments">
+        <!-- when there is a next-segmenst -->
+        <xsl:apply-templates select="Property[translate(@name, $uppercase, $lowercase)=$first-segment]" mode="walk-select-segments">
+          <xsl:with-param name="next-segments">
+            <xsl:value-of select="$next-segments"/>
+          </xsl:with-param>
+        </xsl:apply-templates>
+      </xsl:when>
+
+
+      <xsl:otherwise>
+        <!-- otherwise, this is the final segment and the reference can be printed. -->
+        <xsl:value-of select="@id"/>
+      </xsl:otherwise>
+    </xsl:choose>
+  </xsl:template>
+
   <xsl:template name="caosdb-query-panel">
+    <!-- query panel, this is the area which contains the query form and other related stuff (e.g. query short cuts). -->
     <div class="container caosdb-query-panel">
       <form class="panel" id="caosdb-query-form" method="GET">
         <xsl:attribute name="action">
diff --git a/test/core/html/form_elements_example_1.html b/test/core/html/form_elements_example_1.html
index aa10ac557cb9e015d08490bf8047a0e79a244407..9977e5d91c02cc0e4dd08e9f0939c85f98c6c1be 100644
--- a/test/core/html/form_elements_example_1.html
+++ b/test/core/html/form_elements_example_1.html
@@ -1,4 +1,7 @@
 <div class="caosdb-f-form-wrapper">
+  <!-- DEPRECATED css class .caosdb-property-text-value - Use
+       .caosdb-f-property-single-raw-value or introduce new 
+       .caosdb-v-property-text-value -->
   <form action="#" class="form-horizontal" method="post" name="sample_creation.py">
     <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>
diff --git a/test/core/index.html b/test/core/index.html
index 0d97a415334fd441319eec7e4db262c34d64ef07..50d8cbef8003d6fb9ab383f94405bcfa07270774 100644
--- a/test/core/index.html
+++ b/test/core/index.html
@@ -67,6 +67,8 @@
   <script src="js/ext_bottom_line.js"></script>
   <script src="js/ext_revisions.js"></script>
   <script src="js/autocomplete.js"></script>
+  <script src="js/ext_sss_markdown.js"></script>
+  <script src="js/ext_trigger_crawler_form.js"></script>
   <!--EXTENSIONS-->
   <script src="js/modules/webcaosdb.js.js"></script>
   <script src="js/modules/caosdb.js.js"></script>
@@ -85,5 +87,7 @@
   <script src="js/modules/ext_bottom_line.js.js"></script>
   <script src="js/modules/ext_revisions.js.js"></script>
   <script src="js/modules/autocomplete.js.js"></script>
+  <script src="js/modules/ext_sss_markdown.js.js"></script>
+  <script src="js/modules/ext_trigger_crawler_form.js.js"></script>
 </body>
 </html>
diff --git a/test/core/js/modules/caosdb.js.js b/test/core/js/modules/caosdb.js.js
index 35a1834083d0b849a62b5f9552a3b7f1259c56c4..76141117ffc845917c0e5ff50a165e7718663a31 100644
--- a/test/core/js/modules/caosdb.js.js
+++ b/test/core/js/modules/caosdb.js.js
@@ -167,6 +167,17 @@ QUnit.test("getProperties", function(assert) {
     assert.equal(ps[0].datatype, "TEXT");
 });
 
+QUnit.test("getEntityIdVersion", function(assert) {
+    // without version
+    var html = $('<div data-entity-id="1234"/>')[0];
+    assert.equal(getEntityIdVersion(html), "1234", "id extracted");
+
+    // with version
+    html = $('<div data-entity-id="1234" data-version-id="abcd"/>')[0];
+    assert.equal(getEntityIdVersion(html), "1234@abcd", "<id>@<version> extracted");
+
+});
+
 /**
   * @author Alexander Schlemmer
   * Test whether parents are retrieved correctly.
@@ -442,6 +453,17 @@ QUnit.test("getEntityRole", function(assert) {
 });
 
 
+QUnit.test("getEntityUnit", function(assert) {
+    var property1 = $(`<div><p class="caosdb-entity-heading-attr"><em
+        class="caosdb-entity-heading-attr-name">unit:</em>m</p></div>`);
+    assert.equal(getEntityUnit(property1[0]), "m");
+
+    var property2 = $(`<div><input type="text" class="caosdb-f-entity-unit"
+        value="m/s"/><div>`);
+    assert.equal(getEntityUnit(property2[0]), "m/s");
+});
+
+
 // Test for bug #53
 // https://gitlab.com/caosdb/caosdb-webui/issues/53
 QUnit.test("unset_entity_references", function(assert) {
diff --git a/test/core/js/modules/edit_mode.js.js b/test/core/js/modules/edit_mode.js.js
index c6326e4400750f75ef0b939990a941c2f706da2e..ae11b04a380f162018de70a53409e34b4e6990c6 100644
--- a/test/core/js/modules/edit_mode.js.js
+++ b/test/core/js/modules/edit_mode.js.js
@@ -24,12 +24,10 @@
 
 /* SETUP ext_references module */
 QUnit.module("edit_mode.js", {
-    before: function(assert) {
+    before: function (assert) {
+        this.form_elements_query = form_elements._query;
+        this.edit_mode_query = edit_mode.query;
         var done = assert.async();
-        // overwrite query
-        edit_mode.query = async function(str) {
-            return [];
-        }
         retrieveTestEntities("edit_mode/getProperties_1.xml").then(entities => {
             this.testEntity_getProperties_1 = entities[0];
             this.testEntity_make_property_editable_1 = entities[0];
@@ -39,9 +37,14 @@ QUnit.module("edit_mode.js", {
             done();
         });
     },
-    after: function(assert) {
+    after: function (assert) {
         $('.modal.fade').has(".dropzone").remove();
-    }
+    },
+    afterEach: function (assert) {
+        // remove mock up functions
+        edit_mode.query = this.edit_mode_query;
+        form_elements._query = this.form_elements_query;
+    },
 });
 
 /**
@@ -50,28 +53,28 @@ QUnit.module("edit_mode.js", {
  *
  * @return {HTMLElement[]}
  */
-async function retrieveTestEntities(testCase, transform=true) {
-    entities = await connection.get("xml/"+ testCase);
+async function retrieveTestEntities(testCase, transform = true) {
+    entities = await connection.get("xml/" + testCase);
     return transformation.transformEntities(entities);
 }
 
-QUnit.test("available", function(assert) {
+QUnit.test("available", function (assert) {
     assert.ok(edit_mode);
 });
 
-QUnit.test("init", function(assert){
+QUnit.test("init", function (assert) {
     assert.ok(edit_mode.init);
 });
 
-QUnit.test("dragstart", function(assert){
+QUnit.test("dragstart", function (assert) {
     assert.ok(edit_mode.dragstart);
 });
 
-QUnit.test("dragleave", function(assert){
+QUnit.test("dragleave", function (assert) {
     assert.ok(edit_mode.dragleave);
 });
 
-QUnit.test("dragover", function(assert){
+QUnit.test("dragover", function (assert) {
     assert.ok(edit_mode.dragover);
 });
 
@@ -85,7 +88,7 @@ function assert_throws(assert, cb, message, name, info) {
     }
 }
 
-QUnit.test("add_new_property", function(assert){
+QUnit.test("add_new_property", function (assert) {
     assert.ok(edit_mode.add_new_property);
     var done = assert.async(2);
 
@@ -111,7 +114,11 @@ QUnit.test("add_new_property", function(assert){
 
     // test good cases
     assert.equal($(entity).find("#test_new_prop").length, 0, "no property");
-    entity.addEventListener("caosdb.edit_mode.property_added", function(e){assert.ok(e.target === new_prop, "event fired on newprop"); assert.ok(this === entity, "event detected on entity"); done();}, true);
+    entity.addEventListener("caosdb.edit_mode.property_added", function (e) {
+        assert.ok(e.target === new_prop, "event fired on newprop");
+        assert.ok(this === entity, "event detected on entity");
+        done();
+    }, true);
     edit_mode.add_new_property(entity, new_prop, (x) => {
         assert.ok(x === new_prop,
             "make_property_editable_cb called");
@@ -124,56 +131,56 @@ QUnit.test("add_new_property", function(assert){
 
 });
 
-QUnit.test("property_added", function(assert){
+QUnit.test("property_added", function (assert) {
     assert.ok(edit_mode.property_added, "available");
     assert.ok(edit_mode.property_added instanceof Event, "is event");
 });
 
-QUnit.test("add_dropped_property", function(assert){
+QUnit.test("add_dropped_property", function (assert) {
     assert.ok(edit_mode.add_dropped_property);
 });
 
-QUnit.test("add_dropped_parent", function(assert){
+QUnit.test("add_dropped_parent", function (assert) {
     assert.ok(edit_mode.add_dropped_parent);
 });
 
-QUnit.test("set_entity_dropable", function(assert){
+QUnit.test("set_entity_dropable", function (assert) {
     assert.ok(edit_mode.set_entity_dropable);
 });
 
-QUnit.test("unset_entity_dropable", function(assert){
+QUnit.test("unset_entity_dropable", function (assert) {
     assert.ok(edit_mode.unset_entity_dropable);
 });
 
-QUnit.test("remove_save_button", function(assert){
+QUnit.test("remove_save_button", function (assert) {
     assert.ok(edit_mode.remove_save_button);
 });
 
-QUnit.test("add_save_button", function(assert){
+QUnit.test("add_save_button", function (assert) {
     assert.ok(edit_mode.add_save_button);
 });
 
-QUnit.test("add_trash_button", function(assert){
+QUnit.test("add_trash_button", function (assert) {
     assert.ok(edit_mode.add_trash_button);
 });
 
-QUnit.test("add_parent_trash_button", function(assert){
+QUnit.test("add_parent_trash_button", function (assert) {
     assert.ok(edit_mode.add_parent_trash_button);
 });
 
-QUnit.test("add_parent_delete_buttons", function(assert){
+QUnit.test("add_parent_delete_buttons", function (assert) {
     assert.ok(edit_mode.add_parent_delete_buttons);
 });
 
-QUnit.test("add_property_trash_button", function(assert){
+QUnit.test("add_property_trash_button", function (assert) {
     assert.ok(edit_mode.add_property_trash_button);
 });
 
-QUnit.test("insert_entity", function(assert){
+QUnit.test("insert_entity", function (assert) {
     assert.ok(edit_mode.insert_entity);
 });
 
-QUnit.test("getProperties", function(assert){
+QUnit.test("getProperties", function (assert) {
     assert.ok(edit_mode.getProperties);
 
     assert.equal(edit_mode.getProperties(undefined).length, 0, "undefined returns empty list");
@@ -194,11 +201,11 @@ QUnit.test("getProperties", function(assert){
 
 });
 
-QUnit.test("update_entity", function(assert){
+QUnit.test("update_entity", function (assert) {
     assert.ok(edit_mode.update_entity);
 });
 
-QUnit.test("add_edit_mode_button", function(assert){
+QUnit.test("add_edit_mode_button", function (assert) {
     assert.ok(edit_mode.add_edit_mode_button);
 
     var target = $(document.body)[0];
@@ -209,43 +216,156 @@ QUnit.test("add_edit_mode_button", function(assert){
     $(button).remove();
 });
 
-QUnit.test("toggle_edit_mode", function(assert){
+QUnit.test("toggle_edit_mode", function (assert) {
     assert.ok(edit_mode.toggle_edit_mode);
 });
 
-QUnit.test("leave_edit_mode", function(assert){
+QUnit.test("leave_edit_mode", function (assert) {
     assert.ok(edit_mode.leave_edit_mode);
 });
 
-QUnit.test("enter_edit_mode", function(assert){
+QUnit.test("enter_edit_mode", function (assert) {
     assert.ok(edit_mode.enter_edit_mode);
 });
 
-QUnit.test("make_header_editable", function(assert){
+QUnit.test("make_header_editable", function (assert) {
     assert.ok(edit_mode.make_header_editable);
 });
 
-QUnit.test("isListDatatype", function(assert){
+QUnit.test("isListDatatype", function (assert) {
     assert.ok(edit_mode.unListDatatype);
 });
 
-QUnit.test("make_dataype_input_logic", function(assert){
-    assert.ok(edit_mode.make_dataype_input_logic);
+QUnit.test("make_datatype_input_logic", function (assert) {
+    assert.ok(edit_mode.make_datatype_input_logic);
 });
 
-QUnit.test("make_datatype_input", function(assert){
-    assert.ok(edit_mode.make_datatype_input);
+QUnit.test("make_datatype_input", function (assert) {
+    var done = assert.async(9);
+
+    // mock query response of server
+    form_elements._query = async function (q) {
+        log.getLogger("edit_mode").trace(q);
+        const entities = str2xml(`<Response>
+            <RecordType name="Person"/></Response>`);
+        return transformation.transformEntities(entities);
+    };
+    const form_wrapper = "<form/>";
+
+    const no_dt_input = edit_mode.make_datatype_input(undefined);
+    no_dt_input.addEventListener("caosdb.field.ready", function (e) {
+        var obj = form_elements
+            .form_to_object($(form_wrapper).append(no_dt_input)[0]);
+        assert.propEqual(obj, {
+            "atomic_datatype": "TEXT",
+            "reference_scope": null,
+        }, "No datatype (defaults to TEXT)");
+        done();
+    }, true);
+
+    const text_dt_input = edit_mode.make_datatype_input("TEXT");
+    text_dt_input.addEventListener("caosdb.field.ready", function (e) {
+        var obj = form_elements
+            .form_to_object($(form_wrapper).append(text_dt_input)[0]);
+        assert.propEqual(obj, {
+            "atomic_datatype": "TEXT",
+            "reference_scope": null,
+        }, "TEXT");
+        done();
+    }, true);
+
+    const ref_dt_input = edit_mode.make_datatype_input("REFERENCE");
+    ref_dt_input.addEventListener("caosdb.field.ready", function (e) {
+        var obj = form_elements
+            .form_to_object($(form_wrapper).append(ref_dt_input)[0]);
+        assert.propEqual(obj, {
+            "atomic_datatype": "REFERENCE",
+            "reference_scope": null,
+        }, "REF");
+        done();
+    }, true);
+
+    const file_dt_input = edit_mode.make_datatype_input("FILE");
+    file_dt_input.addEventListener("caosdb.field.ready", function (e) {
+        var obj = form_elements
+            .form_to_object($(form_wrapper).append(file_dt_input)[0]);
+        assert.propEqual(obj, {
+            "atomic_datatype": "FILE",
+            "reference_scope": null,
+        }, "FILE");
+        done();
+    }, true);
+
+    const person_dt_input = edit_mode.make_datatype_input("Person");
+    person_dt_input.addEventListener("caosdb.field.ready", function (e) {
+        var obj = form_elements
+            .form_to_object($(form_wrapper).append(person_dt_input)[0]);
+        assert.propEqual(obj, {
+            "atomic_datatype": "REFERENCE",
+            "reference_scope": "Person",
+        }, "Person");
+        done();
+    }, true);
+
+    const list_text_dt_input = edit_mode.make_datatype_input("LIST<TEXT>");
+    list_text_dt_input.addEventListener("caosdb.field.ready", function (e) {
+        var obj = form_elements
+            .form_to_object($(form_wrapper).append(list_text_dt_input)[0]);
+        assert.propEqual(obj, {
+            "atomic_datatype": "TEXT",
+            "reference_scope": null,
+            "is_list": "on",
+        }, "LIST<TEXT>");
+        done();
+    }, true);
+
+    const list_ref_dt_input = edit_mode.make_datatype_input("LIST<REFERENCE>");
+    list_ref_dt_input.addEventListener("caosdb.field.ready", function (e) {
+        var obj = form_elements
+            .form_to_object($(form_wrapper).append(list_ref_dt_input)[0]);
+        assert.propEqual(obj, {
+            "atomic_datatype": "REFERENCE",
+            "reference_scope": null,
+            "is_list": "on",
+        }, "LIST<REFERENCE>");
+        done();
+    }, true);
+
+    const list_file_dt_input = edit_mode.make_datatype_input("LIST<FILE>");
+    list_file_dt_input.addEventListener("caosdb.field.ready", function (e) {
+        var obj = form_elements
+            .form_to_object($(form_wrapper).append(list_file_dt_input)[0]);
+        assert.propEqual(obj, {
+            "atomic_datatype": "FILE",
+            "reference_scope": null,
+            "is_list": "on",
+        }, "LIST<FILE>");
+        done();
+    }, true);
+
+    const list_per_dt_input = edit_mode.make_datatype_input("LIST<Person>");
+    list_per_dt_input.addEventListener("caosdb.field.ready", function (e) {
+        var obj = form_elements
+            .form_to_object($(form_wrapper).append(list_per_dt_input)[0]);
+        assert.propEqual(obj, {
+            "atomic_datatype": "REFERENCE",
+            "reference_scope": "Person",
+            "is_list": "on",
+        }, "LIST<Person>");
+        done();
+    }, true);
+
 });
 
-QUnit.test("make_input", function(assert){
+QUnit.test("make_input", function (assert) {
     assert.ok(edit_mode.make_input);
 });
 
-QUnit.test("smooth_replace", function(assert){
+QUnit.test("smooth_replace", function (assert) {
     assert.ok(edit_mode.smooth_replace);
 });
 
-QUnit.test("make_property_editable", function(assert) {
+QUnit.test("make_property_editable", function (assert) {
     assert.ok(edit_mode.make_property_editable);
 
     assert.throws(() => edit_mode.make_property_editable(undefined), /param 'element' is expected to be an HTMLElement, was undefined/, "undefined");
@@ -260,212 +380,214 @@ QUnit.test("make_property_editable", function(assert) {
 
 });
 
-QUnit.test("create_new_record", function(assert){
+QUnit.test("create_new_record", function (assert) {
     assert.ok(edit_mode.create_new_record);
 });
 
-QUnit.test("init_edit_app", function(assert){
+QUnit.test("init_edit_app", function (assert) {
     assert.ok(edit_mode.init_edit_app);
 });
 
-QUnit.test("has_errors", function(assert){
+QUnit.test("has_errors", function (assert) {
     assert.ok(edit_mode.has_errors);
 });
 
-QUnit.test("freeze_but", function(assert){
+QUnit.test("freeze_but", function (assert) {
     assert.ok(edit_mode.freeze_but);
 });
 
-QUnit.test("unfreeze", function(assert){
+QUnit.test("unfreeze", function (assert) {
     assert.ok(edit_mode.unfreeze);
 });
 
-QUnit.test("retrieve_datatype_list", function(assert){
+QUnit.test("retrieve_datatype_list", function (assert) {
     assert.ok(edit_mode.retrieve_datatype_list);
 });
 
-QUnit.test("highlight", function(assert){
+QUnit.test("highlight", function (assert) {
     assert.ok(edit_mode.highlight);
 });
 
-QUnit.test("unhighlight", function(assert){
+QUnit.test("unhighlight", function (assert) {
     assert.ok(edit_mode.unhighlight);
 });
 
-QUnit.test("handle_error", function(assert){
+QUnit.test("handle_error", function (assert) {
     assert.ok(edit_mode.handle_error);
 });
 
-QUnit.test("get_edit_panel", function(assert){
+QUnit.test("get_edit_panel", function (assert) {
     assert.ok(edit_mode.get_edit_panel);
 });
 
-QUnit.test("add_wait_datamodel_info", function(assert){
+QUnit.test("add_wait_datamodel_info", function (assert) {
     assert.ok(edit_mode.add_wait_datamodel_info);
 });
 
-QUnit.test("toggle_edit_panel", function(assert){
+QUnit.test("toggle_edit_panel", function (assert) {
     assert.ok(edit_mode.toggle_edit_panel);
 });
 
-QUnit.test("leave_edit_mode_template", function(assert){
+QUnit.test("leave_edit_mode_template", function (assert) {
     assert.ok(edit_mode.leave_edit_mode_template);
 });
 
-QUnit.test("is_edit_mode", function(assert){
+QUnit.test("is_edit_mode", function (assert) {
     assert.ok(edit_mode.is_edit_mode);
 });
 
-QUnit.test("add_cancel_button", function(assert){
+QUnit.test("add_cancel_button", function (assert) {
     assert.ok(edit_mode.add_cancel_button);
 });
 
-QUnit.test("create_new_entity", function(assert){
+QUnit.test("create_new_entity", function (assert) {
     assert.ok(edit_mode.create_new_entity);
 });
 
-QUnit.test("remove_cancel_button", function(assert){
+QUnit.test("remove_cancel_button", function (assert) {
     assert.ok(edit_mode.remove_cancel_button);
 });
 
-QUnit.test("freeze_entity", function(assert){
+QUnit.test("freeze_entity", function (assert) {
     assert.ok(edit_mode.freeze_entity);
 });
 
-QUnit.test("unfreeze_entity", function(assert){
+QUnit.test("unfreeze_entity", function (assert) {
     assert.ok(edit_mode.unfreeze_entity);
 });
 
-QUnit.test("filter", function(assert){
+QUnit.test("filter", function (assert) {
     assert.ok(edit_mode.filter);
 });
 
-QUnit.test("add_start_edit_button", function(assert){
+QUnit.test("add_start_edit_button", function (assert) {
     assert.ok(edit_mode.add_start_edit_button);
 });
 
-QUnit.test("remove_start_edit_button", function(assert){
+QUnit.test("remove_start_edit_button", function (assert) {
     assert.ok(edit_mode.remove_start_edit_button);
 });
 
-QUnit.test("add_new_record_button", function(assert){
+QUnit.test("add_new_record_button", function (assert) {
     assert.ok(edit_mode.add_new_record_button);
 });
 
-QUnit.test("remove_new_record_button", function(assert){
+QUnit.test("remove_new_record_button", function (assert) {
     assert.ok(edit_mode.remove_new_record_button);
 });
 
-QUnit.test("add_delete_button", function(assert){
+QUnit.test("add_delete_button", function (assert) {
     assert.ok(edit_mode.add_delete_button);
 });
 
-QUnit.test("remove_delete_button", function(assert){
+QUnit.test("remove_delete_button", function (assert) {
     assert.ok(edit_mode.remove_delete_button);
 });
 
 
 {
-  const sleep = function sleep(ms) {
-    return new Promise(resolve => setTimeout(resolve, ms));
-  }
+    const sleep = function sleep(ms) {
+        return new Promise(resolve => setTimeout(resolve, ms));
+    }
 
-  const datamodel = `
+    const datamodel = `
 <div><div class=\"btn-group-vertical\"><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 class=\"panel-heading\"><h5>Existing Properties</h5></div><div class=\"panel-body\"><div class=\"input-group\" style=\"width: 100%;\"><input class=\"form-control\" placeholder=\"filter...\" title=\"Type a name (full or partial).\" oninput=\"edit_mode.filter('properties');\" id=\"caosdb-f-filter-properties\" type=\"text\" /><span class=\"input-group-btn\"><button class=\"btn btn-default caosdb-f-edit-panel-new-button new-property caosdb-f-hide-on-empty-input\" title=\"Create this Property.\"><span class=\"glyphicon glyphicon-plus\"></span></button></span></div><ul class=\"caosdb-v-edit-list\"><li class=\"caosdb-f-edit-drag list-group-item caosdb-v-edit-drag\" id=\"caosdb-f-edit-p-20\">name</li><li class=\"caosdb-f-edit-drag list-group-item caosdb-v-edit-drag\" id=\"caosdb-f-edit-p-21\">unit</li><li class=\"caosdb-f-edit-drag list-group-item caosdb-v-edit-drag\" id=\"caosdb-f-edit-p-24\">description</li></ul></div></div><div title=\"Drag and drop RecordTypes from this panel to the Entities on the left.\" class=\"panel panel-default\"><div class=\"panel-heading\"><h5>Existing RecordTypes</h5></div><div class=\"panel-body\"><div class=\"input-group\" style=\"width: 100%;\"><input class=\"form-control\" placeholder=\"filter...\" title=\"Type a name (full or partial).\" oninput=\"edit_mode.filter('recordtypes');\" id=\"caosdb-f-filter-recordtypes\" type=\"text\" /><span class=\"input-group-btn\"><button class=\"btn btn-default caosdb-f-edit-panel-new-button new-recordtype caosdb-f-hide-on-empty-input\" title=\"Create this RecordType\"><span class=\"glyphicon glyphicon-plus\"></span></button></span></div><ul class=\"caosdb-v-edit-list\"><li class=\"caosdb-f-edit-drag list-group-item caosdb-v-edit-drag\" id=\"caosdb-f-edit-rt-30992\">Test</li><li class=\"caosdb-f-edit-drag list-group-item caosdb-v-edit-drag\" id=\"caosdb-f-edit-rt-31015\">Test2</li></ul></div></div></div>`;
 
+    edit_mode.query = async function(q) {
+        return [];
+    }
 
+    QUnit.test("test case 1 - insert property", async function (assert) {
 
-  QUnit.test("test case 1 - insert property", async function(assert) {
-
-    // here lives the test tool box
-    const test_tool_box = $('<div class="caosdb-f-edit-panel-body" />');
+        // here lives the test tool box
+        const test_tool_box = $('<div class="caosdb-f-edit-panel-body" />');
 
-    // here live the entities
-    const main_panel = $('<div class="caosdb-f-main-entities"/>');
-    assert.equal($(".caosdb-f-main-entities").length,0);
+        // here live the entities
+        const main_panel = $('<div class="caosdb-f-main-entities"/>');
+        assert.equal($(".caosdb-f-main-entities").length, 0);
 
-    $(document.body).append(test_tool_box).append(main_panel);
+        $(document.body).append(test_tool_box).append(main_panel);
 
 
-    // ENTER EDIT MODE
-    assert.equal(edit_mode.is_edit_mode(), false, "edit_mode should not be active");
-    // fake server response
-    edit_mode.retrieve_data_model = async function() {
-        return str2xml(datamodel);
-    }
-    var app = await edit_mode.enter_edit_mode();
-    assert.equal(edit_mode.is_edit_mode(), true, "now, edit_mode should be active");
+        // ENTER EDIT MODE
+        assert.equal(edit_mode.is_edit_mode(), false, "edit_mode should not be active");
+        // fake server response
+        edit_mode.retrieve_data_model = async function () {
+            return str2xml(datamodel);
+        }
+        var app = await edit_mode.enter_edit_mode();
+        assert.equal(edit_mode.is_edit_mode(), true, "now, edit_mode should be active");
 
 
-    // NEW PROPERTY
-    assert.equal($(".caosdb-f-edit-panel-new-button.new-property").length, 2, "two new-property buttons should be present");
-    assert.equal($(".caosdb-entity-panel").length, 0, "no entities");
-    assert.equal(app.state, "initial", "initial state");
-    // click on "new property"
-    $(".caosdb-f-edit-panel-new-button.new-property").first().click();
+        // NEW PROPERTY
+        assert.equal($(".caosdb-f-edit-panel-new-button.new-property").length, 2, "two new-property buttons should be present");
+        assert.equal($(".caosdb-entity-panel").length, 0, "no entities");
+        assert.equal(app.state, "initial", "initial state");
+        // click on "new property"
+        $(".caosdb-f-edit-panel-new-button.new-property").first().click();
 
-    while(app.state === "initial") {
-        await sleep(500);
-    }
+        while (app.state === "initial") {
+            await sleep(500);
+        }
 
-    // EDIT PROPERTY
-    assert.equal(app.state, "changed", "changed state");
-    var entity = $(".caosdb-entity-panel");
-    assert.equal(entity.length, 1, "entity added");
-    // set name
-    $(".caosdb-entity-panel .caosdb-f-entity-name").val("TestProperty");
-
-    // SAVE
-    var save_button = $(".caosdb-f-entity-save-button");
-    assert.equal(save_button.length, 1, "save button available");
-    // fake server response
-    connection.post = async function(uri, data) {
-        await sleep(500);
-        assert.equal(xml2str(data), "<Request><Property name=\"TestProperty\" datatype=\"TEXT\"/></Request>");
-        assert.equal(app.state, "wait", "in wait state");
-        return str2xml("<Response><Property id=\"newId\" name=\"TestProperty\" datatype=\"TEXT\"/></Response>");
-    }
-    // click save button
-    var updated_entity = main_panel.find(".caosdb-entity-panel .caosdb-id:contains('newId')");
-    assert.equal(updated_entity.length, 0, "entity with id not yet in main panel");
-    save_button.click();
+        // EDIT PROPERTY
+        assert.equal(app.state, "changed", "changed state");
+        var entity = $(".caosdb-entity-panel");
+        assert.equal(entity.length, 1, "entity added");
+        // set name
+        $(".caosdb-entity-panel .caosdb-f-entity-name").val("TestProperty");
+
+        // SAVE
+        var save_button = $(".caosdb-f-entity-save-button");
+        assert.equal(save_button.length, 1, "save button available");
+        // fake server response
+        connection.post = async function (uri, data) {
+            await sleep(500);
+            assert.equal(xml2str(data), "<Request><Property name=\"TestProperty\" datatype=\"TEXT\"/></Request>");
+            assert.equal(app.state, "wait", "in wait state");
+            return str2xml("<Response><Property id=\"newId\" name=\"TestProperty\" datatype=\"TEXT\"/></Response>");
+        }
+        // click save button
+        var updated_entity = main_panel.find(".caosdb-entity-panel .caosdb-id:contains('newId')");
+        assert.equal(updated_entity.length, 0, "entity with id not yet in main panel");
+        save_button.click();
 
-    while(app.state === "changed" || app.state === "wait" ) {
-        await sleep(500);
-    }
+        while (app.state === "changed" || app.state === "wait") {
+            await sleep(500);
+        }
 
-    // SEE RESPONSE
-    assert.equal(app.state, "initial", "initial state");
+        // SEE RESPONSE
+        assert.equal(app.state, "initial", "initial state");
 
-    var response = $("#newId");
-    assert.equal(response.length, 1, "entity added");
+        var response = $("#newId");
+        assert.equal(response.length, 1, "entity added");
 
-    // entity has been added to main panel
-    updated_entity = main_panel.find(".caosdb-entity-panel .caosdb-id:contains('newId')");
-    assert.equal(updated_entity.length, 1, "entity with new id now in main panel");
+        // entity has been added to main panel
+        updated_entity = main_panel.find(".caosdb-entity-panel .caosdb-id:contains('newId')");
+        assert.equal(updated_entity.length, 1, "entity with new id now in main panel");
 
-    //https://gitlab.com/caosdb/caosdb-webui/issues/47
-    assert.equal(main_panel.find(".caosdb-entity-panel .caosdb-entity-actions-panel").length, 1, "general actions panel there");
-    assert.equal(main_panel.find(".caosdb-entity-panel .caosdb-f-edit-mode-entity-actions-panel").length, 1, "edit_mode actions panel there (BUG caosdb-webui#47)");
+        // tests for closed issue https://gitlab.com/caosdb/caosdb-webui/issues/47
+        assert.equal(main_panel.find(".caosdb-entity-panel .caosdb-entity-actions-panel").length, 1, "general actions panel there");
+        assert.equal(main_panel.find(".caosdb-entity-panel .caosdb-f-edit-mode-entity-actions-panel").length, 1, "edit_mode actions panel there (BUG caosdb-webui#47)");
 
-    main_panel.remove();
-    test_tool_box.remove();
+        main_panel.remove();
+        test_tool_box.remove();
 
-    edit_mode.leave_edit_mode();
-    assert.equal(edit_mode.is_edit_mode(), false, "edit_mode should not be active");
+        edit_mode.leave_edit_mode();
+        assert.equal(edit_mode.is_edit_mode(), false, "edit_mode should not be active");
 
-  });
+    });
 
 }
 
 
-var transformProperty = async function(xml_str) {
+var transformProperty = async function (xml_str) {
     var xml = str2xml(`<Response>${xml_str}</Response>`);
     return (await transformation.transformProperty(xml)).firstElementChild;
 }
 
 
-QUnit.test("createElementForProperty", async function(assert) {
+QUnit.test("createElementForProperty", async function (assert) {
     assert.timeout(100000);
     // unique dummy values for each data type
     var data = {
@@ -493,7 +615,7 @@ QUnit.test("createElementForProperty", async function(assert) {
     }
 });
 
-QUnit.test("getPropertyFromElement", async function(assert) {
+QUnit.test("getPropertyFromElement", async function (assert) {
     assert.timeout(100000);
     // unique dummy values for each data type
     var data = {
@@ -560,7 +682,7 @@ QUnit.test("getPropertyFromElement", async function(assert) {
  *
  * This test case performs a large set of different data types.
  */
-QUnit.test("_toggle_list_property_object", async function(assert) {
+QUnit.test("_toggle_list_property_object", async function (assert) {
 
     // unique dummy values for each data type
     var data = {
@@ -616,7 +738,9 @@ QUnit.test("_toggle_list_property_object", async function(assert) {
         assert.equal(list_inputs_property.listDatatype, dt, `single->list has correct listDatatype (${dt}).`);
 
 
-        assert.throws(() => {edit_mode._toggle_list_property_object(long_list_prop_html, false)}, /Could not toggle to list=false with value=/, "long list throws on list->single");
+        assert.throws(() => {
+            edit_mode._toggle_list_property_object(long_list_prop_html, false)
+        }, /Could not toggle to list=false with value=/, "long list throws on list->single");
 
     }
 });
@@ -628,7 +752,7 @@ QUnit.test("_toggle_list_property_object", async function(assert) {
  * _toggle_list_property_object: HTML -> JS Object -> Converted JS Object ->
  * Converted HTML.
  */
-QUnit.test("_toggle_list_property", async function(assert) {
+QUnit.test("_toggle_list_property", async function (assert) {
 
     // unique dummy values for each data type
     var data = {
@@ -670,13 +794,15 @@ QUnit.test("_toggle_list_property", async function(assert) {
         var list_inputs = edit_mode._toggle_list_property(non_list_prop_html, true);
         var single_input = edit_mode._toggle_list_property(list_prop_html, false);
 
-        assert.throws(() => {edit_mode._toggle_list_property(long_list_prop_html, false)}, /Could not toggle to list=false with value=/, "long list throws on list->single");
+        assert.throws(() => {
+            edit_mode._toggle_list_property(long_list_prop_html, false)
+        }, /Could not toggle to list=false with value=/, "long list throws on list->single");
 
         assert.equal($(list_inputs[0].parentElement).find("ol li :input:not(button)").is.length, 1, "one list input");
         assert.equal($(list_inputs[0].parentElement).find("ol li :input").val(), data[dt], `list ${dt} input has correct value`);
         assert.equal($(single_input.parentElement).find("ol :input").length, 0, "no list input");
         var val = $(single_input).val();
-        if(dt=="DATETIME") {
+        if (dt == "DATETIME") {
             val = $(single_input).find("[type='date']").val() + "T" + $(single_input).find("[type='time']").val();
         }
         assert.equal(val, data[`LIST<${dt}>`], `single ${dt} input has correct value`);
@@ -699,7 +825,7 @@ QUnit.test("_toggle_list_property", async function(assert) {
  * (from which the user would normally choose a reference and the old reference
  * was pre-selected), effectively deleting the old value of the reference.
  */
-QUnit.test("Bug #95", async function(assert) {
+QUnit.test("Bug #95", async function (assert) {
     var query_done;
     edit_mode.query = function (query) {
         var re = /^FIND (Record|File)\s*$/g;
@@ -716,7 +842,7 @@ QUnit.test("Bug #95", async function(assert) {
 
     // old option not deleted when options are empty
     var resolve_function;
-    var empty_options = new Promise(function(res, err) {
+    var empty_options = new Promise(function (res, err) {
         resolve_function = res;
     });
     var property = $("<div/>")
@@ -725,7 +851,7 @@ QUnit.test("Bug #95", async function(assert) {
             datatype: "REFERENCE",
             list: false,
             value: "1234"
-    }, empty_options));
+        }, empty_options));
     assert.equal($(property).find("select").val(), "1234", "old value before");
     assert.equal($(property).find("option").length, 1, "one option before");
     assert.equal($(property).find(":selected").text(), "1234", "old text before");
@@ -751,7 +877,7 @@ QUnit.test("Bug #95", async function(assert) {
     assert.equal(options.length, 3, "3 entities returned");
     var fill_method_done = assert.async();
     var proxied = edit_mode.fill_reference_drop_down;
-    edit_mode.fill_reference_drop_down = async function(arg1, arg2) {
+    edit_mode.fill_reference_drop_down = async function (arg1, arg2) {
         await proxied(arg1, arg2);
 
         assert.equal($(property).find("select").val(), "1234", "still old value after");
@@ -766,7 +892,7 @@ QUnit.test("Bug #95", async function(assert) {
             datatype: "REFERENCE",
             list: false,
             value: "1234"
-    }, options));
+        }, options));
 
 
     edit_mode.fill_reference_drop_down = proxied;
@@ -774,7 +900,7 @@ QUnit.test("Bug #95", async function(assert) {
 });
 
 
-QUnit.test("fill_reference_drop_down", async function(assert) {
+QUnit.test("fill_reference_drop_down", async function (assert) {
     var options = edit_mode._create_reference_options(await transformation
         .transformEntities(str2xml(`
         <Response>
@@ -803,9 +929,9 @@ QUnit.test("fill_reference_drop_down", async function(assert) {
 /**
  * Test the inner logic of retrieve_datatype_list.
  */
-QUnit.test("_create_reference_options", async function(assert) {
-  var entities = await transformation
-    .transformEntities(str2xml(`
+QUnit.test("_create_reference_options", async function (assert) {
+    var entities = await transformation
+        .transformEntities(str2xml(`
     <Response>
       <Record name="RName" id="RID"/>
     </Response>
diff --git a/test/core/js/modules/entity.xsl.js b/test/core/js/modules/entity.xsl.js
index 9b790a007435d05d1e9410774bc8c898fe287a16..c607ee28bf7888f94e3086abf8e033e1532d0d09 100644
--- a/test/core/js/modules/entity.xsl.js
+++ b/test/core/js/modules/entity.xsl.js
@@ -162,6 +162,9 @@ QUnit.test("LIST Property", function(assert) {
 });
 
 QUnit.test("single-value template with reference property.", function(assert) {
+    /* DEPRECATED css class .caosdb-property-text-value - Use
+    * .caosdb-f-property-single-raw-value or introduce new
+    * .caosdb-v-property-text-value */
     assert.equal(xml2str(callTemplate(this.entityXSL, 'single-value', {
         'value': '',
         'reference': 'true',
@@ -177,6 +180,90 @@ QUnit.test("single-value template with reference property.", function(assert) {
     assert.equal($(link).find('.caosdb-id').length, 1, 'has caosdb-id span');
 })
 
+QUnit.test("old version warning", function(assert) {
+    // with successor tag
+    var xmlstr = '<Record id="2345"><Version id="abcd1234"><Successor id="bcde2345"/></Version></Record>';
+    var xml = str2xml(xmlstr);
+    var html = applyTemplates(xml, this.entityXSL, "entities", "*");
+    assert.equal($(html).find(".caosdb-entity-panel .caosdb-f-entity-version-old-warning").length, 1, "warning present");
+
+    // with version tag, without successor
+    xmlstr = '<Record id="2345"><Version id="abcd1234"/></Record>';
+    xml = str2xml(xmlstr);
+    html = applyTemplates(xml, this.entityXSL, "entities", "*");
+    assert.equal($(html).find(".caosdb-f-entity-version-old-warning").length, 0, "warning not present");
+
+    // without version tag
+    xmlstr = '<Record id="2345"></Record>';
+    xml = str2xml(xmlstr);
+    html = applyTemplates(xml, this.entityXSL, "entities", "*");
+    assert.equal($(html).find(".caosdb-f-entity-version-old-warning").length, 0, "warning not present");
+});
+
+QUnit.test("version button", function(assert) {
+    // with version tag
+    var xmlstr = '<Record id="2345"><Version id="abcd1234"/></Record>';
+    var xml = str2xml(xmlstr);
+    var html = applyTemplates(xml, this.entityXSL, "entities", "*");
+
+    assert.equal($(html).find("div.caosdb-entity-panel button.caosdb-f-entity-version-button").length, 1, "button present");
+
+    // without version tag
+    xmlstr = '<Record id="2345"></Record>';
+    xml = str2xml(xmlstr);
+    html = applyTemplates(xml, this.entityXSL, "entities", "*");
+    assert.equal($(html).find(".caosdb-f-entity-version-button").length, 0, "button not present");
+});
+
+QUnit.test("version info modal", function(assert) {
+    // with version tag
+    var xmlstr = '<Record id="2345"><Version id="abcd1234"/></Record>';
+    var xml = str2xml(xmlstr);
+    var html = applyTemplates(xml, this.entityXSL, "entities", "*");
+
+    assert.equal($(html).find("div.caosdb-entity-panel div.caosdb-f-entity-version-info").length, 1, "info present");
+
+    // without version tag
+    xmlstr = '<Record id="2345"></Record>';
+    xml = str2xml(xmlstr);
+    html = applyTemplates(xml, this.entityXSL, "entities", "*");
+    assert.equal($(html).find(".caosdb-f-entity-version-info").length, 0, "info not present");
+});
+
+QUnit.test("data-version-id attribute", function(assert) {
+    // with version tag
+    var xmlstr = '<Record id="2345"><Version id="abcd1234"/></Record>';
+    var xml = str2xml(xmlstr);
+    var html = applyTemplates(xml, this.entityXSL, "entities", "*");
+    assert.equal($(html).find("div.caosdb-entity-panel[data-version-id='abcd1234']").length, 1, "data-version-id attribute present");
+
+    // without version tag
+    xmlstr = '<Record id="2345"></Record>';
+    xml = str2xml(xmlstr);
+    html = applyTemplates(xml, this.entityXSL, "entities", "*");
+    assert.equal($(html).find("div.caosdb-entity-panel[data-version-id]").length, 0, "data-version-id attribute not present");
+});
+
+QUnit.test("data-version-successor attribute", function(assert) {
+    // with successor tag
+    var xmlstr = '<Record id="2345"><Version id="abcd1234"><Successor id="bcde2345"/></Version></Record>';
+    var xml = str2xml(xmlstr);
+    var html = applyTemplates(xml, this.entityXSL, "entities", "*");
+    assert.equal($(html).find("div.caosdb-entity-panel[data-version-successor='bcde2345']").length, 1, "data-version-successor attribute present");
+
+    // with version tag, without successor
+    xmlstr = '<Record id="2345"><Version id="abcd1234"/></Record>';
+    xml = str2xml(xmlstr);
+    html = applyTemplates(xml, this.entityXSL, "entities", "*");
+    assert.equal($(html).find("div.caosdb-entity-panel[data-version-successor]").length, 0, "data-version-successor attribute not present");
+
+    // without version tag
+    xmlstr = '<Record id="2345"></Record>';
+    xml = str2xml(xmlstr);
+    html = applyTemplates(xml, this.entityXSL, "entities", "*");
+    assert.equal($(html).find("div.caosdb-entity-panel[data-version-successor]").length, 0, "data-version-successor attribute not present");
+});
+
 /* MISC FUNCTIONS */
 function applyTemplates(xml, xsl, mode, select = "*") {
     let entryRule = '<xsl:template priority="9" match="/"><xsl:apply-templates select="' + select + '" mode="' + mode + '"/></xsl:template>';
diff --git a/test/core/js/modules/ext_bottom_line.js b/test/core/js/modules/ext_bottom_line.js
index 46903ead3226c1cb6038e9320548284ce76b0672..21c92167271f87554a9af881554d33db686d9194 100644
--- a/test/core/js/modules/ext_bottom_line.js
+++ b/test/core/js/modules/ext_bottom_line.js
@@ -45,7 +45,7 @@ var ext_bottom_line_test_suite = function ($, ext_bottom_line, QUnit) {
         },
         { "id": "test.success-2",
           "is_applicable": "(entity) => getParents(entity).map(par => par.name).includes('TestPreviewRecordType') && getEntityName(entity) !== 'TestPreviewRecord-fall-back'",
-          "create": "(entity) => { return plotly_preview.create_plot([{x: [1,2,3,4,5], y: [1,2,4,8,16]}]); }"
+          "create": "(entity) => { return plotly_preview.create_plot([{x: [1,2,3,4,5], y: [1,2,4,8,16]}], { 'xaxis': {'title': 'time [samples]'}}); }"
         }
       ]
     };
diff --git a/test/core/js/modules/ext_sss_markdown.js.js b/test/core/js/modules/ext_sss_markdown.js.js
new file mode 100644
index 0000000000000000000000000000000000000000..07199e869f56c7042a78dba6938de55e4a36e727
--- /dev/null
+++ b/test/core/js/modules/ext_sss_markdown.js.js
@@ -0,0 +1,77 @@
+/*
+ * ** header v3.0
+ * This file is a part of the CaosDB Project.
+ *
+ * Copyright (C) 2020 IndiScale GmbH <info@indiscale.com>
+ * Copyright (C) 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
+ */
+
+
+/* MODULE ext_sss_markdown */
+QUnit.module("ext_sss_markdown", {
+    before: function (assert) {
+        markdown.init();
+        ext_sss_markdown.logger.setLevel("trace");
+
+        const qunit_obj = this;
+        const done = assert.async();
+
+        // load test case
+        $.ajax({
+            cache: true,
+            dataType: 'xml',
+            url: "xml/test_sss_output.xml",
+        }).done(function(data, textStatus, jdXHR) {
+            // append the test case to the QUnit module object.
+            qunit_obj.testCase1 = data;
+        }).always(function() {
+            done();
+        });
+    },
+    after: function (assert) {
+        // clean up
+        $("#caosdb-stdout").remove();
+    }
+});
+
+
+QUnit.test("availability", function(assert) {
+    assert.ok(ext_sss_markdown, "available");
+});
+
+QUnit.test("test case 1", function(assert) {
+    // setup
+    const testCase1 = this.testCase1;
+    const plainStdout = testCase1.evaluate("/Response/script/stdout", testCase1, null, XPathResult.STRING_TYPE, null).stringValue;
+
+    const stdout = $('<div id="caosdb-stdout"/>').text(plainStdout);
+    $("body").append(stdout);
+
+    assert.equal($("#caosdb-stdout").length, 1);
+    assert.equal($("#caosdb-stdout div.alert").length, 0, "no bootstrap alert");
+    assert.equal($("#caosdb-stdout p").length, 0, "no html paragraphs");
+    assert.equal($("#caosdb-stdout").text(), plainStdout, "only plain text");
+
+    ext_sss_markdown.init();
+
+    assert.equal($("#caosdb-stdout p").length, 3, "3 html paragraphs transformed");
+    assert.equal($("#caosdb-stdout code").length, 1, "html code tag transformed");
+    assert.equal($("#caosdb-stdout div.alert").text(),
+        "this is a bootstrap alert", "bootstrap alert transformed");
+
+});
diff --git a/test/core/js/modules/ext_trigger_crawler_form.js.js b/test/core/js/modules/ext_trigger_crawler_form.js.js
new file mode 100644
index 0000000000000000000000000000000000000000..e2a4b0c8a494e43234547de4740ea62dde77beac
--- /dev/null
+++ b/test/core/js/modules/ext_trigger_crawler_form.js.js
@@ -0,0 +1,55 @@
+/*
+ * ** header v3.0
+ * This file is a part of the CaosDB Project.
+ *
+ * Copyright (C) 2020 IndiScale GmbH <info@indiscale.com>
+ * Copyright (C) 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
+ */
+
+'use strict';
+
+QUnit.module("ext_trigger_crawler_form", {
+    before: () => {
+        $(document.body).append('<div id="top-navbar"><ul class="caosdb-navbar"/></div>');
+    },
+    beforeEach: () => {
+        $(".caosdb-f-navbar-toolbox").remove();
+    },
+    after: () => {
+        $("#top-navbar").remove();
+    },
+});
+
+QUnit.test("test build variables and availability", function(assert) {
+    assert.ok(ext_trigger_crawler_form, "availble");
+    assert.equal("${BUILD_MODULE_EXT_TRIGGER_CRAWLER_FORM}", "DISABLED", "BUILD_MODULE_EXT_TRIGGER_CRAWLER_FORM disabled by default.");
+    assert.equal("${BUILD_MODULE_EXT_TRIGGER_CRAWLER_FORM_TOOLBOX}", "Tools", "BUILD_MODULE_EXT_TRIGGER_CRAWLER_FORM_TOOLBOX defaults to Tools");
+});
+
+QUnit.test("test init", function(assert) {
+    var tools = navbar.get_toolbox("Tools");
+
+    assert.equal($(tools).length, 1);
+    assert.equal($(tools).find("button").length, 0);
+    ext_trigger_crawler_form.init()
+    tools = navbar.get_toolbox("Tools");
+    assert.equal($(tools).find("button").length, 1);
+
+});
+
+
diff --git a/test/core/js/modules/webcaosdb.js.js b/test/core/js/modules/webcaosdb.js.js
index 3b615e11e4bd96c26ecc0d8e1cb9d8220a4cba8e..239758161b8419a8a5e15799c67c909977f1fbf0 100644
--- a/test/core/js/modules/webcaosdb.js.js
+++ b/test/core/js/modules/webcaosdb.js.js
@@ -164,7 +164,6 @@ QUnit.test("get", function(assert) {
     });
 });
 
-
 /* MODULE transformation */
 QUnit.module("webcaosdb.js - transformation", {
     before: function(assert) {
@@ -782,34 +781,29 @@ QUnit.test("getActiveSlideItemIndex", function(assert) {
     assert.equal(2, preview.getActiveSlideItemIndex(okElem2));
 });
 
-QUnit.test("getEntityById", function(assert) {
-    assert.ok(preview.getEntityById, "function available");
+QUnit.test("getEntityByIdVersion", function(assert) {
+    assert.ok(preview.getEntityByIdVersion, "function available");
     let e1 = $('<div><div class="caosdb-id">1</div></div>')[0];
     let e2 = $('<div><div class="caosdb-id">2</div></div>')[0];
-    let e3 = $('<div><div class="caosdb-id">3</div><div><div class="caosdb-id">1</div></div></div>')[0];
 
-    let es = [e1, e2, e3];
+    let es = [e1, e2];
 
     assert.throws(() => {
-        preview.getEntityById()
+        preview.getEntityByIdVersion()
     }, "no param throws.");
     assert.throws(() => {
-        preview.getEntityById(null, 1)
+        preview.getEntityByIdVersion(null, 1)
     }, "null first param throws.");
     assert.throws(() => {
-        preview.getEntityById("asdf", 1)
+        preview.getEntityByIdVersion("asdf", 1)
     }, "string first param throws.");
     assert.throws(() => {
-        preview.getEntityById(es, null)
+        preview.getEntityByIdVersion(es, null)
     }, "null second param throws.");
-    assert.throws(() => {
-        preview.getEntityById(es, "asdf")
-    }, "string second param throws.");
 
-    assert.equal(e1, preview.getEntityById(es, 1), "find 1");
-    assert.equal(e2, preview.getEntityById(es, 2), "find 2");
-    assert.equal(e3, preview.getEntityById(es, 3), "find 3");
-    assert.equal(null, preview.getEntityById(es, 4), "find 4 -> null");
+    assert.equal(e1, preview.getEntityByIdVersion(es, "1"), "find 1");
+    assert.equal(e2, preview.getEntityByIdVersion(es, "2"), "find 2");
+    assert.equal(null, preview.getEntityByIdVersion(es, "3"), "find 3 -> null");
 });
 
 QUnit.test("createEmptyInner", function(assert) {
@@ -870,7 +864,7 @@ QUnit.test("createCarouselNav", function(assert) {
     let refLinks = $('<div class="caosdb-value-list"><a><span class="caosdb-id">1234</span></a><a><span class="caosdb-id">2345</span></a><a><span class="caosdb-id">3456</span></a><a><span class="caosdb-id">4567</span></a></div>')[0];
     let e1 = $('<div><div class="caosdb-id">1234</div></div>')[0];
     let e2 = $('<div><div class="caosdb-id">2345</div></div>')[0];
-    let e3 = $('<div><div class="caosdb-id">3456</div><div><div class="caosdb-id">1234</div></div></div>')[0];
+    let e3 = $('<div><div class="caosdb-id">3456</div></div>')[0];
     let e4 = $('<div><div class="caosdb-id">4567</div></div>')[0];
     let entities = [e1, e3, e4, e2];
     let carousel = preview.createPreviewCarousel(entities, refLinks);
@@ -1040,8 +1034,41 @@ QUnit.test("preparePreviewEntity", function(assert){
     assert.equal($(prepared).find('a.caosdb-id')[0].href, connection.getBasePath() + "Entity/1234", "link is correct.");
 });
 
-QUnit.test("getEntitiyIds", function(assert) {
-    assert.ok(preview.getEntityIds, 'function available');
+QUnit.test("getEntityRef", function(assert) {
+    assert.ok(preview.getEntityRef, 'function available');
+
+    var html = $('<div><div class="caosdb-id">sdfg</div></div>')[0];
+    assert.equal(preview.getEntityRef(html), "sdfg", "id extracted");
+
+    html = $('<div><div class="caosdb-id"></div></div>')[0];
+    assert.equal(preview.getEntityRef(html), "", "empty string extracted");
+
+    html = $('<div></div>')[0];
+    assert.throws(()=>{preview.getEntityRef(html);}, "missing .caosdb-id throws");
+});
+
+
+QUnit.test("getAllEntityRefs", function(assert) {
+    assert.ok(preview.getAllEntityRefs, 'function available');
+    assert.throws(preview.getAllEntityRefs, "null param throws");
+
+    // overwrite called methods
+    const oldGetReferenceLinks = preview.getReferenceLinks;
+    preview.getReferenceLinks = function(links) {
+        assert.propEqual(links, ["bla"], "array is passed to getReferenceLinks");
+        return links;
+    }
+    const oldGetEntityRef = preview.getEntityRef;
+    preview.getEntityRef = function(link) {
+        assert.equal(link, "bla", "array elements are passed to getEntityRef");
+        return "asdf";
+    }
+
+    assert.propEqual(preview.getAllEntityRefs(["bla"]), ["asdf"], "returns array with refs");
+
+
+    // cleanup
+    preview.getReferenceLinks = oldGetReferenceLinks;
 });
 
 QUnit.test("retrievePreviewEntities", function(assert) {
@@ -1847,6 +1874,25 @@ QUnit.test("annotation module", function(assert) {
 
 /* MODULE navbar */
 QUnit.module("webcaosdb.js - navbar", {
+    before: () => {
+        $(document.body).append('<div id="top-navbar"><ul class="caosdb-navbar"/></div>');
+    },
+    beforeEach: () => {
+        $(".caosdb-f-navbar-toolbox").remove();
+    },
+    after: () => {
+        $("#top-navbar").remove();
+    },
+});
+
+QUnit.test("get_navbar", function(assert) {
+    assert.equal(navbar.get_navbar().className, "caosdb-navbar");
+});
+
+QUnit.test("add_button wrong parameters", function(assert) {
+    assert.throws(()=>{navbar.add_button(undefined)}, /button is expected/, "undefined throws");
+    assert.throws(()=>{navbar.add_button({"test": "an object"})}, "object throws");
+    assert.throws(()=>{navbar.add_button(["array of strings"])}, "array of string throws");
 });
 
 QUnit.test("test button classes", function(assert) {
@@ -1856,3 +1902,40 @@ QUnit.test("test button classes", function(assert) {
     assert.ok(result.hasClass("btn-link"), "has class btn-link");
     assert.equal(result.text(), "TestButton", "text is correct");
 });
+
+QUnit.test("add_tool", function(assert) {
+    assert.equal($(".caosdb-f-navbar-toolbox").length, 0, "no toolbox");
+    navbar.add_tool("TestButton", "TestMenu");
+
+    var toolbox = $("ul.caosdb-f-navbar-toolbox");
+    assert.equal(toolbox.length, 1, "new toolbox");
+    assert.equal(toolbox.find("button").length, 1, "new button");
+    assert.equal(toolbox.find("button").text(), "TestButton", "Name correct")
+
+    assert.notOk(toolbox.hasClass("btn"));
+    assert.notOk(toolbox.hasClass("btn-link"));
+    assert.notOk(toolbox.hasClass("navbar-btn"));
+
+    assert.notOk(toolbox.siblings("a.dropdown-toggle").hasClass("btn"));
+    assert.notOk(toolbox.siblings("a.dropdown-toggle").hasClass("btn-link"));
+    assert.notOk(toolbox.siblings("a.dropdown-toggle").hasClass("navbar-btn"));
+});
+
+QUnit.test("toolbox example", function(assert) {
+    // this is a kind of integration test and it uses the toolbox_example
+    // module from toolbox_example.js. That example is also usefull for manual
+    // testing.
+    assert.equal($(".caosdb-f-navbar-toolbox").length, 0, "no toolbox");
+    toolbox_example.init();
+    assert.equal($(".caosdb-f-navbar-toolbox").length, 3, "three toolboxes");
+
+    assert.equal($('.caosdb-f-navbar-toolbox[data-toolbox-name="Useful Links"]').length, 1, "one 'Useful Links' toolbox");
+    assert.equal($('.caosdb-f-navbar-toolbox[data-toolbox-name="Useful Links"] a[href="https://indiscale.com"]').length, 1, "one external link");
+
+    assert.equal($('.caosdb-f-navbar-toolbox[data-toolbox-name="Server-side Scripts"]').length, 1, "one 'Server-side Scripts' toolbox");
+    assert.equal($('.caosdb-f-navbar-toolbox[data-toolbox-name="Server-side Scripts"] form input[type="submit"]').attr("value"), "Trigger Crawler", "one crawler trigger button");
+    assert.equal($('.caosdb-f-navbar-toolbox[data-toolbox-name="Server-side Scripts"] form').attr("title"), "Trigger the crawler.", "form has tooltip");
+
+    assert.equal($('.caosdb-f-navbar-toolbox[data-toolbox-name="Tools"]').length, 1, "one 'Tools' toolbox");
+    assert.equal($('.caosdb-f-navbar-toolbox[data-toolbox-name="Tools"] button').length, 3, "three 'Tools' buttons");
+});
diff --git a/test/core/xml/test_sss_output.xml b/test/core/xml/test_sss_output.xml
new file mode 100644
index 0000000000000000000000000000000000000000..6fad9d3edcc3436b32c0efc2a015fe5ce60a29df
--- /dev/null
+++ b/test/core/xml/test_sss_output.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?xml-stylesheet type="text/xsl" href="../webcaosdb.xsl"?>
+<Response>
+    <script code="0">
+        <stdout>here are new lines and stuff
+
+&lt;code>this is a code environment&lt;/code>
+
+
+&lt;div class=&quot;alert alert-info&quot;>this is a bootstrap alert&lt;/div>
+
+last line ---</stdout>
+    </script>
+</Response>
diff --git a/test/ext/js/toolbox_example.js b/test/ext/js/toolbox_example.js
new file mode 100644
index 0000000000000000000000000000000000000000..9b9ef6548353c248376c3c3183835d03698366da
--- /dev/null
+++ b/test/ext/js/toolbox_example.js
@@ -0,0 +1,49 @@
+var toolbox_example = function() {
+
+    var init = function() {
+        navbar.add_button("Single Button", {callback: ()=>{alert("Single Button");}, title: "Click me!"});
+        navbar.add_tool("Tool 1", "Tools", {callback: ()=>{alert("Tool 1");}, title: "Tooltip 1"});
+        navbar.add_tool("Tool 2", "Tools", {callback: ()=>{alert("Tool 2");}, title: "Tooltip 2"});
+        navbar.add_tool("Tool 3", "Tools", {callback: ()=>{alert("Tool 3");}, title: "Tooltip 3"});
+
+        navbar.add_tool($('<a href="https://indiscale.com">Link1</a>')[0], "Useful Links", {title: "Browse to indiscale.com"});
+
+
+        const script = "crawler.py"
+        const args = {
+            "-p0": "positional argument 1",
+            "-p1": "positional argument 2",
+            "-Ooption1": "option value 1",
+            "-Ooption2": "option value 2",
+        };
+        const button_name = "Trigger Crawler";
+        const title = "Trigger the crawler.";
+
+        const crawler_form = make_scripting_caller_form(
+            script, args, button_name);
+
+        navbar.add_tool(crawler_form, "Server-side Scripts", {title: title});
+    }
+
+    var make_scripting_caller_form = function (script, args, button_name) {
+        const scripting_caller = $(`
+        <form method="POST" action="/scripting">
+          <input type="hidden" name="call" value="${script}"/>
+          <input type="submit"
+              class="btn btn-link" value="${button_name}"/>
+        </form>`);
+
+        // add arguements
+        for (const arg in args) {
+            scripting_caller.append(`<input type="hidden" name="${arg}"
+                value="${args[arg]}"/>`);
+        }
+
+        return scripting_caller[0];
+    }
+
+    return {
+        init: init
+    };
+
+}();