diff --git a/CHANGELOG.md b/CHANGELOG.md
index 59dcd6135e7c87c4880813073631706d16427a41..c44fc8d33280a8106715348a05031774c28c745b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,6 +12,7 @@
 .. --------------------------------------------used to the update the records specified ------
 .. Best Practice T3 reST: https://docs.typo3.org/m/typo3/docs-how-to-document/master/en-us/WritingReST/CheatSheet.html
 .. Reference: https://docs.typo3.org/m/typo3/docs-how-to-document/master/en-us/WritingReST/Index.html
+..
 .. Italic *italic*
 .. Bold **bold**
 .. Code ``text``
@@ -19,7 +20,6 @@
 .. Internal Link: :ref:`downloadButton` (default url text) or :ref:`download Button<downloadButton>` (explicit url text)
 .. Add Images:    .. image:: ./Images/a4.jpg
 ..
-..
 .. Admonitions
 .. .. note::   .. important::     .. tip::     .. warning::
 .. Color:   (blue)       (orange)           (green)      (red)
@@ -63,6 +63,7 @@ Notes
 Features
 ^^^^^^^^
 
+* #15682 / Subrecord hide please save record first if there is no table title.
 * #15098 / Implemented qfqFunction in QFQ variable for usage in forms.
 * Doc: Replace many places single back tick by double back tick. Add hint for 'Row size too large'. Added Enis & Jan
   as Developer. Add hint use mysqldump with one row per record.
@@ -393,9 +394,9 @@ Notes
   per QFQ installation and/or per form.
 * Subrecord:
 
-    * Dynamically computed 'new' button in subrecord: FE.parameter.new. Use regular `... AS _link` syntax.
-    * Per row customizeable edit and delete button. Use special column name `_rowEdit` and `_rowDelete`. Use
-      regular `... AS _link` syntax.
+  * Dynamically computed 'new' button in subrecord: FE.parameter.new. Use regular `... AS _link` syntax.
+  * Per row customizeable edit and delete button. Use special column name `_rowEdit` and `_rowDelete`. Use
+    regular `... AS _link` syntax.
 
 * Link/Tablesorter: New link qualifier `|Y:...` which is invisible to the user, but will be respected by tablesorter and
   filter.
@@ -3748,11 +3749,10 @@ Changes
 
 * Table `FormElement`:
 
-    * Modified column: `checkType` - new value `numerical`.
+  * Modified column: `checkType` - new value `numerical`.
 
-      ALTER TABLE FormElement MODIFY COLUMN checkType ENUM('alnumx','digit','numerical','email','min|max','min|max
-      date',
-      'pattern','allbut','all') NOT NULL DEFAULT 'alnumx'
+    ALTER TABLE FormElement MODIFY COLUMN checkType ENUM('alnumx','digit','numerical','email','min|max','min|max date',
+    'pattern','allbut','all') NOT NULL DEFAULT 'alnumx'
 
 * Example Report for `forms` extended by a delete button per row.
 
@@ -3799,22 +3799,22 @@ Changes
 ^^^^^^^
 
 * Table 'FormElement'
-    * New column: rowLabelInputNote:
+  * New column: rowLabelInputNote:
 
-      ALTER TABLE  `FormElement` ADD `rowLabelInputNote` set('row','label','/label','input','/input','note','/note','
-      /row')
-      NOT NULL DEFAULT 'row,label,/label,input,/input,note,/note,/row' AFTER  `bsNoteColumns` ;
+    ALTER TABLE  `FormElement` ADD `rowLabelInputNote` set('row','label','/label','input','/input','note','/note','
+    /row')
+    NOT NULL DEFAULT 'row,label,/label,input,/input,note,/note,/row' AFTER  `bsNoteColumns` ;
 
-    * Modified column: 'type' - new value 'templateGroup':
+  * Modified column: 'type' - new value 'templateGroup':
 
-      ALTER TABLE  `FormElement` CHANGE  `type`  `type` ENUM(  'checkbox',  'date',  'datetime',  'dateJQW',  '
-      datetimeJQW',  'extra',
-      'gridJQW',  'text',  'editor',  'time',  'note',  'password',  'radio',  'select',  'subrecord',  'upload',  '
-      fieldset', 'pill',
-      'templateGroup',  'beforeLoad',  'beforeSave',  'beforeInsert',  'beforeUpdate',  'beforeDelete',  'afterLoad',  '
-      afterSave',
-      'afterInsert',  'afterUpdate',  'afterDelete',  'sendMail' ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL
-      DEFAULT  'text'
+    ALTER TABLE  `FormElement` CHANGE  `type`  `type` ENUM(  'checkbox',  'date',  'datetime',  'dateJQW',  '
+    datetimeJQW',  'extra',
+    'gridJQW',  'text',  'editor',  'time',  'note',  'password',  'radio',  'select',  'subrecord',  'upload',  '
+    fieldset', 'pill',
+    'templateGroup',  'beforeLoad',  'beforeSave',  'beforeInsert',  'beforeUpdate',  'beforeDelete',  'afterLoad',  '
+    afterSave',
+    'afterInsert',  'afterUpdate',  'afterDelete',  'sendMail' ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL
+    DEFAULT  'text'
 
 * formEditor.sql: Added HTML 'placeholder' in FormEditor for bs*Columns.
 
@@ -3895,9 +3895,9 @@ Bug fixes
 * Skip unwanted parameter expansion during save.
 * Fixed bug with uninitialized FE_SLAVE_ID.
 * formEditor.sql:
-    * The defintion as 'editor' (not text) for FormElement 'note' has been lost - reinserted.
-    * Fixed problem while playing SQL query - deleting old FormElements of Formeditor deleted also FormElements of other
-      forms.
+  * The defintion as 'editor' (not text) for FormElement 'note' has been lost - reinserted.
+  * Fixed problem while playing SQL query - deleting old FormElements of Formeditor deleted also FormElements of other
+    forms.
 * #3066 / help-text with-error - CSS class 'hidden' will be rendered by default (as long there is no error).
 * Labels are skipped, if FormElement.bsLabelColumns=0.
 * Respect attribute `data-class-on-change` on save buttons.
diff --git a/Documentation-develop/NewVersion.md b/Documentation-develop/NewVersion.md
index b6dc5c863603932fa60735c2eb696348eab4dcd4..6d054813b09ae6158e4b4a51c86d7279dfe0787b 100644
--- a/Documentation-develop/NewVersion.md
+++ b/Documentation-develop/NewVersion.md
@@ -44,7 +44,7 @@ Neue Versionsnummer
    **Achtung**: die Release Minor darf KEINE fuehrenden Nullen enthalten!!! Ansonsten funktioniert die Verteilung vie
    TER nicht.
 
-   **Auto**: ./setVersion.sh 23.10.0
+   **Auto**: ./setVersion.sh 23.10.1
 
    Manuell:
 
@@ -56,7 +56,7 @@ Neue Versionsnummer
 
     * **Commit & Push** to develop branch:
 
-   New version v23.10.0
+   New version v23.10.1
 
 6)
     * Merge 'Develop' to **Master**: git.math.uzh.ch > QFQ > Merge Requests > New merge request > 'Develop >> Master'
@@ -64,12 +64,12 @@ Neue Versionsnummer
 
 7) **New Tag**:
 
-    * Neuen tag via Browser auf dem **master** branch setzen: git.math.uzh.ch > QFQ > Repository > Tags > New tag
+    * Neuen tag via Browser auf dem **master** branch setzen: git.math.uzh.ch > QFQ > Repository > Code > Tags > New tag
 
-   Tag: v23.10.0
+   Tag: v23.10.1
 
    # Den tag mit diesem Command zu setzen scheint den Build Prozess nicht zu triggern.
-   git tag -a v23.10.0 -m 'New version v23.10.0' git push
+   git tag -a v23.10.1 -m 'New version v23.10.1' git push
 
 
 7) **Merge 'master' into 'develop'**
diff --git a/Documentation/Form.rst b/Documentation/Form.rst
index 6169c970a9c3a784dba9abe2d71a8b3c88ce7bda..ccec65a7c93a1830402c45c5807eb4cf29d6f593 100644
--- a/Documentation/Form.rst
+++ b/Documentation/Form.rst
@@ -1989,6 +1989,10 @@ will be rendered inside the form as a HTML table.
 
         subrecordAppendExtraDeleteForm = address2
 
+  * *subrecordEmptyText*: Optional. Define the text displayed when subrecord has no records::
+
+        subrecordEmptyText = my custom text
+
 **Subrecord DragAndDrop**
 
 Subrecords inherently support drag-and-drop, see also :ref:`drag_and_drop`.
diff --git a/Documentation/Release.rst b/Documentation/Release.rst
index 0198e9d89b144fe5b520097a11efd0ddb2e77f80..0203f61e86520496b7c6ce1f6d5a35f4d3d468ba 100644
--- a/Documentation/Release.rst
+++ b/Documentation/Release.rst
@@ -20,7 +20,6 @@
 .. Internal Link: :ref:`downloadButton` (default url text) or :ref:`download Button<downloadButton>` (explicit url text)
 .. Add Images:    .. image:: ./Images/a4.jpg
 ..
-..
 .. Admonitions
 ..           .. note::   .. important::     .. tip::     .. warning::
 .. Color:   (blue)       (orange)           (green)      (red)
@@ -64,9 +63,11 @@ Notes
 Features
 ^^^^^^^^
 
+* #15682 / Subrecord hide please save record first if there is no table title.
 * #15098 / Implemented qfqFunction in QFQ variable for usage in forms.
-* Doc: Replace many places single back tick by double back tick. Add hint for 'Row size too large'.  Added Enis & Jan
-  as Developer. Add hint use mysqldump with one row per record.
+* Doc: Replace many places single back tick by double back tick. Add hint for 'Row size too large'.  Add hint how to use
+  mysqldump to export one row per record.
+* index.rst: Added Enis & Jan as Developer.
 
 Bug Fixes
 ^^^^^^^^^
@@ -74,7 +75,7 @@ Bug Fixes
 * #17003 / inline edit - dark mode has wrong css path.
 * #17075 / Fix broken '... AS _restClient'.
 * #17091 / upload_Incorrect_integer_value_fileSize.
-* RTD: Fix broken readthedocs rendering.
+* #17148 / RTD: Fix broken readthedocs rendering.
 
 Version 23.10.0
 ---------------
@@ -253,6 +254,7 @@ Bug Fixes
 ^^^^^^^^^
 
 * #14754 / Using double quotes in tableview config caused sql error.
+* #15474 / Form save button activated after clicking in TinyMCE editor. Should only activate after change.
 * #15483 / Protected folder check fixed. Changed default of wget, preventing errors. Changed handling from protected
   folder check, new once a day.
 * #15521 / FormEditor assigns always container, even none is selected. Change handling of form variables from type select,
diff --git a/Documentation/Report.rst b/Documentation/Report.rst
index 72dd4a5195931a9c47ae9a9fb6209cd4083d4d85..e4cf107554758fb7c7d76377d573c7b6858d5d73 100644
--- a/Documentation/Report.rst
+++ b/Documentation/Report.rst
@@ -2168,6 +2168,21 @@ Output::
 
     12-345-678
 
+.. _qifprepend:
+
+QIFPREPEND: if not empty show input with prepend separator
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The SQL function QIFPREPEND(separator, input) returns 'input' with prepend separator if 'input' is not 'empty string' / '0'.
+
+Example::
+
+    10.sql = SELECT 'lastName', QIFPREPEND(', ','title'), QIFPREPEND(', ','')
+
+Output::
+
+    lastName, title
+
 .. _strip_tags:
 
 strip_tags: strip html tags
diff --git a/Documentation/Settings.cfg b/Documentation/Settings.cfg
index d2a1ef89395dacf293401146d8384dab9ec5ac36..b048b0981e9ad6f184c88bda9afb10ee6834ce44 100644
--- a/Documentation/Settings.cfg
+++ b/Documentation/Settings.cfg
@@ -22,7 +22,7 @@
 
 project = QFQ - Quick Form Query
 version = 23.10
-release = 23.10.0
+release = 23.10.1
 t3author = Carsten Rose
 copyright = since 2017 by the author
 
diff --git a/Documentation/conf.py b/Documentation/conf.py
index 591b4a236a87b3e9014149e8789e0209ce487819..e4e23b99d032572ae41843981085b1d9459848a6 100644
--- a/Documentation/conf.py
+++ b/Documentation/conf.py
@@ -64,7 +64,7 @@ author = 'Carsten Rose, Benjamin Baer, Enis Nuredini, Jan Haller'
 # The short X.Y version.
 version = '23.10'
 # The full version, including alpha/beta/rc tags.
-release = '23.10.0'
+release = '23.10.1'
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.
diff --git a/Documentation/index.rst b/Documentation/index.rst
index 5729e5c29241c24fa9a845a1ab755a15f887e94c..9257f36f174572e4431e13703327a31e0774bb5d 100644
--- a/Documentation/index.rst
+++ b/Documentation/index.rst
@@ -25,13 +25,13 @@ Quick Form Query Extension
    2017-2023
 
 :Authors:
-   Carsten Rose, Benjamin Baer, Enis Nuredini
+   Carsten Rose, Benjamin Baer, Enis Nuredini, Jan Haller
 
 :Further Contributors:
    Nicola Chiapolini, Marc Egger, Rafael Ostertag, Elias Villiger
 
 :Email:
-   carsten.rose@math.uzh.ch, benjamin.baer@math.uzh.ch, enis.nuredini@math.uzh.ch
+   carsten.rose@math.uzh.ch, benjamin.baer@math.uzh.ch, enis.nuredini@math.uzh.ch, jan.haller@math.uzh.ch
 
 :License:
     This document is published under the Open Publication License
diff --git a/Gruntfile.js b/Gruntfile.js
index d65c613472645ab489b08678db00c5a4c8234683..6398f40238b95fb4343fd54b67131951088e6003 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -587,9 +587,9 @@ module.exports = function (grunt) {
                 ]
             },
         },
-        uglify: {
+        terser: {
             options: {
-                banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n',
+                //banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n',
             },
             build: {
                 src: ['js/<%= pkg.name %>.debug.js'],
@@ -613,7 +613,10 @@ module.exports = function (grunt) {
             }
         },
         jshint: {
-            all: js_sources
+            all: js_sources,
+            options: {
+                'esversion': 6,
+            }
         },
         concat_in_order: {
             debug_standalone: {
@@ -714,7 +717,8 @@ module.exports = function (grunt) {
     });
 
     // Load the plugin that provides the "uglify" task.
-    grunt.loadNpmTasks('grunt-contrib-uglify');
+    grunt.loadNpmTasks('grunt-terser');
+    //grunt.loadNpmTasks('grunt-contrib-uglify');
     grunt.loadNpmTasks('grunt-contrib-copy');
     grunt.loadNpmTasks('grunt-contrib-concat');
     grunt.loadNpmTasks('grunt-concat-in-order');
@@ -724,9 +728,9 @@ module.exports = function (grunt) {
     grunt.loadNpmTasks('grunt-contrib-jasmine');
 
     // Default task(s).
-    grunt.registerTask('default', ['jshint', 'concat_in_order', 'uglify', 'copy', 'less']);
+    grunt.registerTask('default', ['jshint', 'concat_in_order', 'terser', 'copy', 'less']);
 
-    grunt.registerTask('only-js', ['jshint', 'concat_in_order', 'uglify', 'copy:qfqPlugins', 'copy:worker']);
+    grunt.registerTask('only-js', ['jshint', 'concat_in_order', 'terser', 'copy:qfqPlugins', 'copy:worker']);
 
     grunt.registerTask('run-jasmine', ['jshint', 'concat_in_order', 'jasmine']);
 
diff --git a/extension/Classes/Core/AbstractBuildForm.php b/extension/Classes/Core/AbstractBuildForm.php
index 8aedd3f4b49fc18fbd6031ae8b6864b60b63a30f..968c5bb4204dde4e8e9140bbc8704b8ebaa70c94 100644
--- a/extension/Classes/Core/AbstractBuildForm.php
+++ b/extension/Classes/Core/AbstractBuildForm.php
@@ -640,7 +640,9 @@ abstract class AbstractBuildForm {
      */
     public function getFormId() {
         if ($this->formId === null) {
-            $this->formId = uniqid('qfq-form-');
+//            $this->formId = uniqid('qfq-form-');
+            $this->formId = 'qfq-form-' . $this->store->getVar(TYPO3_TT_CONTENT_UID, STORE_TYPO3) . '-'
+                . $this->formSpec[F_ID] . '-' . $this->store->getVar(CLIENT_RECORD_ID, STORE_TYPO3 . STORE_SIP . STORE_RECORD . STORE_ZERO);
         }
 
         return $this->formId;
@@ -904,7 +906,7 @@ abstract class AbstractBuildForm {
             // Typically: $htmlElementNameIdZero = true
             // After Saving a record, staying on the form, the FormElements on the Client are still known as '<feName>:0'.
             $htmlFormElementName = HelperFormElement::buildFormElementName($formElement, ($htmlElementNameIdZero) ? 0 : $recordId);
-            $formElement[FE_HTML_ID] = HelperFormElement::buildFormElementId($this->formSpec[F_ID], $formElement[FE_ID],
+            $formElement[FE_HTML_ID] = HelperFormElement::buildFormElementId($this->getFormId(), $formElement[FE_ID],
                 ($htmlElementNameIdZero) ? 0 : $recordId,
                 $formElement[FE_TG_INDEX]);
 
@@ -2446,6 +2448,7 @@ abstract class AbstractBuildForm {
      */
     private function buildSubrecordRowsHtml(array $formElement, array $rowSettings, $additionalRow = false): string {
         $htmlBody = '';
+        $rowHtml = '';
         $subrecordToken = FE_SQL1;
 
         // Configure formElement array for additional rows.
@@ -2456,7 +2459,6 @@ abstract class AbstractBuildForm {
 
         foreach ($formElement[$subrecordToken] as $row) {
             $rowHtml = '';
-
             if ($formElement[FE_MODE] == FE_MODE_READONLY) {
                 $editToolTip = 'Show';
                 $editSymbol = SYMBOL_SHOW;
@@ -2518,6 +2520,10 @@ abstract class AbstractBuildForm {
             }
             $htmlBody .= Support::wrapTag("<tr $rowAttribute>", $rowHtml, true);
         }
+        if(empty($formElement[$subrecordToken])){
+            $rowHtml .= Support::wrapTag("<td>", $formElement[SUBRECORD_EMPTY_TEXT] ?? SUBRECORD_DEFAULT_EMPTY_TEXT);
+            $htmlBody .= Support::wrapTag("<tr>", $rowHtml, true);
+        }
         return $htmlBody;
     }
 
diff --git a/extension/Classes/Core/BuildFormBootstrap.php b/extension/Classes/Core/BuildFormBootstrap.php
index a15737c31026c42fe1201f254774bd3f2625c6df..75e81f72e6b11c04d976c8651c1cee51cd916edf 100644
--- a/extension/Classes/Core/BuildFormBootstrap.php
+++ b/extension/Classes/Core/BuildFormBootstrap.php
@@ -448,8 +448,9 @@ class BuildFormBootstrap extends AbstractBuildForm {
         $class = Support::doAttribute('class', $class);
         $dataClassOnChange = Support::doAttribute('data-class-on-change', $buttonOnChangeClass);
         $tooltip = Support::doAttribute('title', $tooltip);
+        $formId = $this->getFormId();
 
-        return "<button id='$buttonHtmlId' type='button' $class $dataClassOnChange $tooltip $disabled>$element</button>";
+        return "<button id='$buttonHtmlId-$formId' type='button' $class $dataClassOnChange $tooltip $disabled>$element</button>";
     }
 
     /**
@@ -510,7 +511,7 @@ class BuildFormBootstrap extends AbstractBuildForm {
 //            $a = '<a ' . Support::doAttribute('href', '#' . $this->createAnker($formElement[FE_ID])) . ' data-toggle="tab">' . $formElement[FE_LABEL] . '</a>';
 
             $attributeLiA = 'data-toggle="tab" ';
-            $hrefTarget = '#' . $this->createAnker($formElement[FE_ID]);
+            $hrefTarget = '#' . $this->createAnker($formElement[FE_ID], $recordId);
 
             $htmlFormElementName = HelperFormElement::buildFormElementName($formElement, $recordId);
             switch ($formElement[FE_MODE]) {
@@ -582,15 +583,16 @@ class BuildFormBootstrap extends AbstractBuildForm {
      *
      * @return string
      */
-    private function createAnker($id) {
-        return $this->formSpec[FE_NAME] . '_' . $id;
+    private function createAnker($id, $recordId) {
+        return $this->formSpec[FE_NAME] . '_' . $id . '_' . $recordId;
     }
 
     /**
      * @return string
      */
     private function getTabId() {
-        return 'qfqTabs';
+        return 'qfqTabs-' . $this->store->getVar(TYPO3_TT_CONTENT_UID, STORE_TYPO3) . '-'
+        . $this->formSpec[F_ID] . '-' . $this->store->getVar(CLIENT_RECORD_ID, STORE_TYPO3 . STORE_SIP . STORE_RECORD . STORE_ZERO);
     }
 
     /**
@@ -606,7 +608,7 @@ class BuildFormBootstrap extends AbstractBuildForm {
 
         $attribute = $this->getFormTagAttributes();
 
-        $attribute['class'] = 'form-horizontal';
+        $attribute['class'] = 'form-horizontal qfq-form';
         $attribute['data-toggle'] = 'validator';
 
         $formModeGlobal = Support::getFormModeGlobal($this->formSpec[F_MODE_GLOBAL] ?? '');
@@ -622,6 +624,27 @@ class BuildFormBootstrap extends AbstractBuildForm {
         $honeypot = $this->getHoneypotVars();
         $md5 = $this->buildInputRecordHashMd5();
 
+        $actionUpload = FILE_ACTION . '=' . FILE_ACTION_UPLOAD;
+        $actionDelete = FILE_ACTION . '=' . FILE_ACTION_DELETE;
+
+        # Replacing the attributes set via tail / javascript
+        $attribute["data-form-id"] = $this->getFormId();
+        $attribute["data-tabs-id"] = $this->getTabId();
+        if (0 < ($recordId = $this->store->getVar(SIP_RECORD_ID, STORE_SIP))) {
+            $attribute["data-delete-url"] = $this->createDeleteUrl($this->formSpec[F_FINAL_DELETE_FORM], $recordId);
+        }
+        if ($this->formSpec[F_DIRTY_MODE] === DIRTY_MODE_NONE) {
+            $attribute["data-dirty-url"] = Path::urlApi(API_DIRTY_PHP);
+        }
+        $attribute["data-submit-to"] = Path::urlApi(API_SAVE_PHP);
+        $attribute["data-type-ahead-url"] = Path::urlApi(API_TYPEAHEAD_PHP);
+        $attribute["data-refresh-url"] = Path::urlApi(API_LOAD_PHP);
+        $attribute["data-file-upload-to"] = Path::urlApi(API_FILE_PHP) . '?' . $actionUpload;
+        $attribute["data-file-delete-url"] = Path::urlApi(API_FILE_PHP) . '?' . $actionDelete;
+        $attribute["data-log-level"] = 0; # Not sure if raos did implement this fully, but it was hardcoded 0 before.
+        $attribute["data-api-delete-url"] = Path::urlApi(API_DELETE_PHP);
+        #$attribute["data-sip"] = 
+
         return '<form ' . OnArray::toString($attribute, '=', ' ', "'") . '>' . $honeypot . $md5;
     }
 
@@ -634,58 +657,10 @@ class BuildFormBootstrap extends AbstractBuildForm {
     public function tail() {
 
         $html = '';
-        $deleteUrl = '';
-
-        $formId = $this->getFormId();
 
         $html .= '</div> <!--class="tab-content" -->';  //  <div class="tab-content">
 //        $html .= '<input type="submit" value="Submit">';
 
-        $formId = $this->getFormId();
-        $tabId = $this->getTabId();
-
-        if (0 < ($recordId = $this->store->getVar(SIP_RECORD_ID, STORE_SIP))) {
-            $deleteUrl = $this->createDeleteUrl($this->formSpec[F_FINAL_DELETE_FORM], $recordId);
-        }
-
-        $actionUpload = FILE_ACTION . '=' . FILE_ACTION_UPLOAD;
-        $actionDelete = FILE_ACTION . '=' . FILE_ACTION_DELETE;
-
-        $apiDeletePhp = Path::urlApi(API_DELETE_PHP);
-
-        $dirtyAction = ($this->formSpec[F_DIRTY_MODE] == DIRTY_MODE_NONE) ? '' : "dirtyUrl: '" . Path::urlApi(API_DIRTY_PHP) . "',";
-
-        $submitTo = Path::urlApi(API_SAVE_PHP);
-        $typeAheadUrl = Path::urlApi(API_TYPEAHEAD_PHP);
-        $refreshUrl = Path::urlApi(API_LOAD_PHP);
-        $fileUploadTo = Path::urlApi(API_FILE_PHP) . '?' . $actionUpload;
-        $fileDeleteUrl = Path::urlApi(API_FILE_PHP) . '?' . $actionDelete;
-
-        $html .= '</form>';  //  <form class="form-horizontal" ...
-        $html .= <<<EOF
-        <script type="text/javascript">
-            $(function () {
-                'use strict';
-                QfqNS.Log.level = 0;
-
-                var qfqPage = new QfqNS.QfqPage({
-                    tabsId: '$tabId',
-                    formId: '$formId',
-                    submitTo: '$submitTo',
-                    $dirtyAction
-                    deleteUrl: '$deleteUrl',
-                    typeAheadUrl: '$typeAheadUrl',
-                    refreshUrl: '$refreshUrl',
-                    fileUploadTo: '$fileUploadTo',
-                    fileDeleteUrl: '$fileDeleteUrl'
-                });
-
-               
-                var qfqRecordList = new QfqNS.QfqRecordList('$apiDeletePhp');
-            })
-         </script>
-EOF;
-
         // Button Save at bottom of form - only if there is a button text given.
         if ($this->formSpec[F_SUBMIT_BUTTON_TEXT] !== '') {
 
@@ -710,9 +685,9 @@ EOF;
             $html .= $this->wrapItem(WRAP_SETUP_INPUT, $htmlElement);
             $html .= $this->wrapItem(WRAP_SETUP_NOTE, '');
             $html .= $lastDiv;
-
         }
 
+        $html .= '</form>';
 
         $html .= '</div>';  //  <div class="container-fluid"> === main <div class=...> around everything
 
@@ -892,8 +867,9 @@ EOF;
             $class = $this->isFirstPill ? 'active ' : '';
             $this->isFirstPill = false;
         }
+        $recordId = $this->store->getVar(COLUMN_ID, STORE_RECORD . STORE_ZERO);
 
-        $html = Support::wrapTag('<div role="tabpanel" class="tab-pane ' . $class . '" id="' . $this->createAnker($formElement['id']) . '">', $html);
+        $html = Support::wrapTag('<div role="tabpanel" class="tab-pane ' . $class . '" id="' . $this->createAnker($formElement['id'], $recordId) . '">', $html);
 
 
         return $html;
diff --git a/extension/Classes/Core/Constants.php b/extension/Classes/Core/Constants.php
index 5abf369038a7fb6cf8904cd92badcda57cea046c..45db92060a4cb19d934f559f00758e9ebc86b1c1 100644
--- a/extension/Classes/Core/Constants.php
+++ b/extension/Classes/Core/Constants.php
@@ -1005,6 +1005,8 @@ const QUESTION_DELETE = 'Do you really want to delete the record?';
 const SUBRECORD_COLUMN_DEFAULT_MAX_LENGTH = 20;
 const SUBRECORD_COLUMN_TITLE_EDIT = 'subrecordColumnTitleEdit';
 const SUBRECORD_COLUMN_TITLE_DELETE = 'subrecordColumnTitleDelete';
+const SUBRECORD_EMPTY_TEXT = 'subrecordEmptyText';
+const SUBRECORD_DEFAULT_EMPTY_TEXT = 'Currently no records exist.';
 const FORM_ELEMENTS_NATIVE = 'native';
 const FORM_ELEMENTS_SUBRECORD = 'subrecord';
 const FORM_ELEMENTS_NATIVE_SUBRECORD = 'native_subrecord';
diff --git a/extension/Classes/Sql/function.sql b/extension/Classes/Sql/function.sql
index 171aafc9ce8e984c131e863b63e7558b8422b77b..7dd39a0b024030bf0c35b381f04aebd3dac9418c 100644
--- a/extension/Classes/Sql/function.sql
+++ b/extension/Classes/Sql/function.sql
@@ -401,3 +401,21 @@ BEGIN
     RETURN @highlighted_text;
 END;
 
+###
+#
+# QIFPREPEND(separator, input)
+# If 'input' is not empty|0, prepend separator to given input
+#
+DROP FUNCTION IF EXISTS QIFPREPEND;
+CREATE FUNCTION QIFPREPEND(`separator` VARCHAR(128), `input` TEXT)
+    RETURNS TEXT
+    DETERMINISTIC
+    SQL SECURITY INVOKER
+BEGIN
+    DECLARE output TEXT;
+    SET output =
+            IF(ISNULL(`input`) OR `input` = '' OR `input` = '0',
+               '',
+               CONCAT(`separator`, `input`));
+    RETURN output;
+END;
\ No newline at end of file
diff --git a/extension/RELEASE.txt b/extension/RELEASE.txt
index 3ff16b0271c3f96e36e497186c8ca7e7e7d35055..01282d98b4f2c4ce9a510762d6d7960bd3a668db 100644
--- a/extension/RELEASE.txt
+++ b/extension/RELEASE.txt
@@ -12,6 +12,7 @@
 .. --------------------------------------------used to the update the records specified ------
 .. Best Practice T3 reST: https://docs.typo3.org/m/typo3/docs-how-to-document/master/en-us/WritingReST/CheatSheet.html
 ..             Reference: https://docs.typo3.org/m/typo3/docs-how-to-document/master/en-us/WritingReST/Index.html
+..
 .. Italic *italic*
 .. Bold **bold**
 .. Code ``text``
@@ -19,7 +20,6 @@
 .. Internal Link: :ref:`downloadButton` (default url text) or :ref:`download Button<downloadButton>` (explicit url text)
 .. Add Images:    .. image:: ./Images/a4.jpg
 ..
-..
 .. Admonitions
 ..           .. note::   .. important::     .. tip::     .. warning::
 .. Color:   (blue)       (orange)           (green)      (red)
@@ -63,9 +63,11 @@ Notes
 Features
 ^^^^^^^^
 
+* #15682 / Subrecord hide please save record first if there is no table title.
 * #15098 / Implemented qfqFunction in QFQ variable for usage in forms.
-* Doc: Replace many places single back tick by double back tick. Add hint for 'Row size too large'.  Added Enis & Jan
-  as Developer. Add hint use mysqldump with one row per record.
+* Doc: Replace many places single back tick by double back tick. Add hint for 'Row size too large'. Add hint how to use
+  mysqldump to export one row per record.
+* Index.rst: Add Enis & Jan as developer.
 
 Bug Fixes
 ^^^^^^^^^
diff --git a/extension/Tests/Unit/Core/BuildFormPlainTest.php b/extension/Tests/Unit/Core/BuildFormPlainTest.php
index 0aab8fdfb1cab35134867d1e4cf55e7ec8479e19..c9733c71eb9ff035a61f1418bf3ae3162307faf3 100644
--- a/extension/Tests/Unit/Core/BuildFormPlainTest.php
+++ b/extension/Tests/Unit/Core/BuildFormPlainTest.php
@@ -40,11 +40,17 @@ class BuildFormPlainTest extends AbstractDatabaseTest {
         $build = new BuildFormPlain([F_DB_INDEX => DB_INDEX_DEFAULT], array(), array(), $this->dbArray);
 
         $formId1 = $build->getFormId();
-        $this->assertMatchesRegularExpression('/qfq-form-[0-9a-f]{13}/', $formId1);
+        $this->assertMatchesRegularExpression('/qfq-form-[0-9]*-[0-9]*-[0-9]*/', $formId1);
 
         $formId2 = $build->getFormId();
         $this->assertEquals($formId1, $formId2);
 
+        /*
+         * 1) IMATHUZH\Qfq\Tests\Unit\Core\BuildFormPlainTest::testGetFormId
+Failed asserting that 'qfq-form-1234--3' matches PCRE pattern "/qfq-form-[0-9a-f]{13}/".
+/home/gitlab-runner/builds/_jtKSCsb/0/typo3/qfq/typo3conf/ext/qfq/Tests/Unit/Core/BuildFormPlainTest.php:43
+FAILURES!
+         */
     }
 
     /**
diff --git a/extension/ext_emconf.php b/extension/ext_emconf.php
index f0fdd0d5f5de098026d190cd7861e5c1bb7ac78a..776d94d9d8f8f282026da472b5127926cf71f4b5 100644
--- a/extension/ext_emconf.php
+++ b/extension/ext_emconf.php
@@ -12,7 +12,7 @@ $EM_CONF['qfq'] = array(
     'dependencies' => 'fluid,extbase',
     'clearcacheonload' => true,
     'state' => 'stable',
-    'version' => '23.10.0',
+    'version' => '23.10.1',
     'constraints' => [
         'depends' => [
             'typo3' => '8.0.0-11.9.99',
diff --git a/javascript/src/BSTabs.js b/javascript/src/BSTabs.js
index 7aef8f3a3175018046d4a58ba5e7246ac6d3333e..347754af0ff2534f6e7fcd92785483d4202b7295 100644
--- a/javascript/src/BSTabs.js
+++ b/javascript/src/BSTabs.js
@@ -37,7 +37,7 @@ var QfqNS = QfqNS || {};
         this.eventEmitter = new EventEmitter();
         this.currentFormName = $('#' + this.tabId + ' .active a[data-toggle="tab"]')[0].hash.slice(1).split("_")[0];
         this.currentRecordId = $('#' + this.tabId + ' a[data-toggle="tab"]')[0].id.split("-")[2];
-        this.currentActiveLastPill = document.getElementById('qfqTabs').getAttribute('data-active-last-pill');
+        this.currentActiveLastPill = document.getElementById(this.tabId).getAttribute('data-active-last-pill');
 
         // Fill this.tabs
         this.fillTabInformation();
diff --git a/javascript/src/Element/FormGroup.js b/javascript/src/Element/FormGroup.js
index 3ea9086fc3ce286f18cc1112be99c5f7b5669689..7d707635e76093717934139a3555e683d3ce72c5 100644
--- a/javascript/src/Element/FormGroup.js
+++ b/javascript/src/Element/FormGroup.js
@@ -92,14 +92,21 @@ QfqNS.Element = QfqNS.Element || {};
      * @private
      */
     n.FormGroup.prototype.$findFormGroup = function ($enclosedElement) {
-        var $formGroup = $('#' + $enclosedElement.attr('id') + '-i');
+        var idArray = $enclosedElement.attr('id').split("-");
+        var searchString = "#";
+        for(var i = 0; i < 8 && i < idArray.length; i++) {
+            searchString += idArray[i] + "-";
+        }
+        var $formGroup = $(searchString + 'i');
         
         if (!$formGroup || $formGroup.length === 0) {
-            throw new Error("Unable to find Form Group");
+            console.log("Unable to find Form Group for", $enclosedElement);
+            console.log("trying with: " + '#' + $enclosedElement.attr('id') + '-i');
+            throw new Error("Unable to find Form Group for", $enclosedElement);
         }
 
         if ($formGroup.length > 1) {
-            $formGroup = $('#' + $enclosedElement.attr('id') + '-i');
+            $formGroup = $(searchString + 'i');
             console.log("Enclosed Element Id: " + $enclosedElement.attr('id'));
             if ($formGroup.length !== 1) {
                 throw new Error("enclosed element yields ambiguous form group");
diff --git a/javascript/src/Element/NameSpaceFunctions.js b/javascript/src/Element/NameSpaceFunctions.js
index e30998c3fee2087c1bc6aa42e96250edbf096d87..c19cd8bbaff44da7f10b0b982e8e25c0bb1a56c6 100644
--- a/javascript/src/Element/NameSpaceFunctions.js
+++ b/javascript/src/Element/NameSpaceFunctions.js
@@ -68,6 +68,7 @@ QfqNS.Element = QfqNS.Element || {};
             case 'checkbox':
                 return new n.Checkbox($element);
             case 'nonFormGroupCheckbox':
+            case "file":
                 return $element;
             case 'radio':
                 return new n.Radio($element);
diff --git a/javascript/src/Form.js b/javascript/src/Form.js
index 19958e805b3efa4104053b407e55f19824f41d07..ba2a1a1f1ab864a23dad9e51b747c544558f6054 100644
--- a/javascript/src/Form.js
+++ b/javascript/src/Form.js
@@ -91,7 +91,7 @@ var QfqNS = QfqNS || {};
         document.addEventListener('keydown', function(event) {
             if (event.ctrlKey && event.altKey && event.key === 's') {
                 console.log("submit");
-                $("#save-button:not([disabled=disabled])").click();
+                $("#save-button-" + this.formId + ":not([disabled=disabled])").click();
             }
         });
 
@@ -181,6 +181,7 @@ var QfqNS = QfqNS || {};
         // Reset disabled inputs
         disabled.attr('disabled','disabled');
 
+        console.log("Serialized form", serializedForm);
         $.post(submitUrl, serializedForm)
             .done(this.ajaxSuccessHandler.bind(this))
             .fail(this.submitFailureHandler.bind(this));
@@ -230,8 +231,7 @@ var QfqNS = QfqNS || {};
                 textStatus: textStatus,
                 jqXHR: jqXHR
             }));
-        $("#save-button").removeClass('btn-warning active disabled');
-        $("#save-button").addClass('btn-default');
+        
         this.saveInProgress = false;
     };
 
diff --git a/javascript/src/Main.js b/javascript/src/Main.js
index b4660c404f6f6d4d28c34e28fbb58032a5870c49..5fef921a534b7aefe762c2e82103402ad0e8bdce 100644
--- a/javascript/src/Main.js
+++ b/javascript/src/Main.js
@@ -28,10 +28,20 @@ $(document).ready( function () {
         $('button.qfq-column-selector').click(function () {
             $('.tablesorter-column-selector>label>input').addClass('qfq-skip-dirty');
         });
+
+
+        var collection = document.getElementsByClassName("qfq-form");
+        console.log(collection);
+        var qfqPages = [];
+        for (const form of collection) {
+            const page = new n.QfqPage(form.dataset);
+            qfqPages.push(page);
+        }
+
     } catch (e) {
         console.log(e);
     }
-
+    
         $('.qfq-auto-grow').each(function() {
             var minHeight = $(this).attr("rows") * 14 + 18;
             var newHeight = $(this).prop('scrollHeight');
@@ -194,6 +204,7 @@ $(document).ready( function () {
         };
 
         n.initializeQfqClearMe();
+        n.initializeDatetimepicker();
         n.Helper.calendar();
 
     })(QfqNS);
diff --git a/javascript/src/Plugins/qfq.fabric.js b/javascript/src/Plugins/qfq.fabric.js
index f87b882f1c1033c0eb479213fd99671bcfeba6f8..efc5ffa087f5330ce4e3b8332a27d8d1fa57f4d4 100644
--- a/javascript/src/Plugins/qfq.fabric.js
+++ b/javascript/src/Plugins/qfq.fabric.js
@@ -181,13 +181,13 @@ $(function (n) {
         };
 
         ModeSettings.prototype.getButtonById = function (needle) {
-            var needleInHaystack = {};
+            var needleInHaystack = false;
             this.myButtons.forEach(function (haystack) {
                 if (haystack[0].id === needle) {
                     needleInHaystack = haystack;
                 }
             });
-            if (needleInHaystack === {}) {
+            if (needleInHaystack) {
                 console.error("Button not found, id: " + string);
             } else {
                 return needleInHaystack;
diff --git a/javascript/src/QfqForm.js b/javascript/src/QfqForm.js
index 147144d1c92e9e22c5f4361a3a3b977b634a7464..0a26cb5d6cee905a14460516281f87bd95973989 100644
--- a/javascript/src/QfqForm.js
+++ b/javascript/src/QfqForm.js
@@ -146,9 +146,9 @@ var QfqNS = QfqNS || {};
         this.applyFormConfiguration(configurationData);
 
         // Initialize jqxDateTimeInput elements.
-        n.Helper.jqxDateTimeInput();
+        //n.Helper.jqxDateTimeInput();
         // Initialize jqxComboBox elements.
-        n.Helper.jqxComboBox();
+        //n.Helper.jqxComboBox();
         // Deprecated
         //n.Helper.jqxEditor();
         n.Helper.tinyMce();
@@ -638,6 +638,7 @@ var QfqNS = QfqNS || {};
 
     n.QfqForm.prototype.submit = function (queryParameters) {
         var submitQueryParameters;
+        console.log("Save in progress", submitQueryParameters);
         var alert;
         var submitReason;
 
@@ -652,7 +653,7 @@ var QfqNS = QfqNS || {};
                 }
 
 
-                var form = document.getElementById(this.form.formId);
+                var form = document.getElementById(this.formId);
                 var inputs = form.elements;
 
                 for (var i = 0; i < inputs.length; i++) {
@@ -699,6 +700,7 @@ var QfqNS = QfqNS || {};
 
         submitQueryParameters = $.extend({}, queryParameters, submitReason);
         this.form.submitTo(this.submitTo, submitQueryParameters);
+        console.log("Submitting with", submitQueryParameters);
         this.form.saveInProgress = true;
     };
 
@@ -942,7 +944,7 @@ var QfqNS = QfqNS || {};
      * @private
      */
     n.QfqForm.prototype.getSaveButton = function () {
-        return $("#save-button");
+        return $("#save-button-" + this.formId);
     };
 
     /**
@@ -952,7 +954,7 @@ var QfqNS = QfqNS || {};
      * @private
      */
     n.QfqForm.prototype.getCloseButton = function () {
-        return $("#close-button");
+        return $("#close-button-" + this.formId);
     };
 
     /**
@@ -962,7 +964,7 @@ var QfqNS = QfqNS || {};
      * @private
      */
     n.QfqForm.prototype.getDeleteButton = function () {
-        return $("#delete-button");
+        return $("#delete-button-" + this.formId);
     };
 
     /**
@@ -972,7 +974,7 @@ var QfqNS = QfqNS || {};
      * @private
      */
     n.QfqForm.prototype.getNewButton = function () {
-        return $("#form-new-button");
+        return $("#form-new-button-" + this.formId);
     };
 
 
@@ -1073,6 +1075,8 @@ var QfqNS = QfqNS || {};
      */
     n.QfqForm.prototype.handleSubmitSuccess = function (form, data) {
         n.Log.debug('Reset form state');
+        this.getSaveButton().removeClass('btn-warning active disabled');
+        this.getSaveButton().addClass('btn-default');
         form.resetFormChanged();
         this.resetLockState();
 
@@ -1237,9 +1241,8 @@ var QfqNS = QfqNS || {};
          * confusing.
          */
         var $formGroup = this.getFormGroupByControlName(formControlName);
-        if (!$formGroup) {
-            return;
-        }
+        if (!$formGroup) return;
+
         var $helpBlockColumn;
         var $formGroupSubDivs = $formGroup.find("div");
         if ($formGroupSubDivs.length < 3) {
diff --git a/javascript/src/QfqPage.js b/javascript/src/QfqPage.js
index 02791409a413d0762d60b746eafd5e74133181e4..0b9dae588e107cbf2610f6c93218f937bb087e0d 100644
--- a/javascript/src/QfqPage.js
+++ b/javascript/src/QfqPage.js
@@ -24,6 +24,7 @@ var QfqNS = QfqNS || {};
      * @name QfqNS.QfqPage
      */
     n.QfqPage = function (settings) {
+        console.log("Creating QFQPage", settings);
         this.qfqForm = {};
         this.settings = $.extend(
             {
@@ -40,6 +41,8 @@ var QfqNS = QfqNS || {};
             }, settings
         );
 
+        n.Log.level = settings.logLevel;
+
         this.intentionalClose = false;
 
         try {
@@ -117,6 +120,7 @@ var QfqNS = QfqNS || {};
                     that.qfqForm.releaseLock(true);
                 };
             })(this));
+            this.recordList = new n.QfqRecordList(settings.apiDeleteUrl);
         } catch (e) {
             n.Log.error(e.message);
             this.qfqForm = null;
@@ -125,7 +129,7 @@ var QfqNS = QfqNS || {};
         var page = this;
         // Initialize Fabric to access form events
         try {
-            $(".annotate-graphic").each(function() {
+            $("#" + this.formId + " .annotate-graphic").each(function() {
                 var qfqFabric = new QfqNS.Fabric();
                 qfqFabric.initialize($(this), page);
             });
@@ -134,7 +138,7 @@ var QfqNS = QfqNS || {};
         }
 
         try {
-            $(".annotate-text").each(function() {
+            $("#" + this.formId + " .annotate-text").each(function() {
                 var codeCorrection = new QfqNS.CodeCorrection();
                 codeCorrection.initialize($(this), page);
             });
@@ -144,7 +148,7 @@ var QfqNS = QfqNS || {};
 
         QfqNS.TypeAhead.install(this.settings.typeAheadUrl);
         QfqNS.CharacterCount.initialize();
-        n.initializeDatetimepicker(false);
+        //n.initializeDatetimepicker(false);
     };
 
     /**
diff --git a/javascript/src/QfqRecordList.js b/javascript/src/QfqRecordList.js
index 364b19537a9ebeb8b0ee50bdaee23e13507e7f93..15c9853466604a5bc6539149e52b8cd112fd8a88 100644
--- a/javascript/src/QfqRecordList.js
+++ b/javascript/src/QfqRecordList.js
@@ -22,6 +22,7 @@ var QfqNS = QfqNS || {};
      * @name QfqNS.QfqRecordList
      */
     n.QfqRecordList = function (deleteUrl) {
+        console.log("initialized with this url", deleteUrl);
         this.deleteUrl = deleteUrl;
         this.deleteButtonClass = 'record-delete';
         this.recordClass = 'record';
diff --git a/package.json b/package.json
index 27a62141a9c00a87574c1e8500294efb2ba53b9c..a1a8a5676f48fa2458b340a82f41d0cf3c8af4a9 100644
--- a/package.json
+++ b/package.json
@@ -3,12 +3,11 @@
   "version": "1.0.0",
   "dependencies": {
     "@fortawesome/fontawesome-free": "^5.15.3",
-    "bootlint": "^0.14.2",
     "bootstrap": "^3.3.6",
     "bootstrap-datetimepicker": "0.0.7",
     "bootstrap-validator": "^0.11.5",
     "chart.js": "^2.9.4",
-    "codemirror": "^5.65.12",
+    "codemirror": "^5.65.15",
     "corejs-typeahead": "^1.3.1",
     "eonasdan-bootstrap-datetimepicker": "^4.17.49",
     "fullcalendar": "^3.10.2",
@@ -19,25 +18,25 @@
     "grunt-contrib-jasmine": "^1.1.0",
     "grunt-contrib-jshint": "^1.1.0",
     "grunt-contrib-less": "^1.2.0",
-    "grunt-contrib-uglify": "^2.2.0",
     "grunt-contrib-watch": "^1.0.0",
-    "http-server": "^14.1.1",
+    "grunt-terser": "^2.0.0",
     "jquery": "latest",
     "jqwidgets-framework": "4.2.1",
-    "jsdoc": "^3.6.11",
-    "mocha": "^3.2.0",
     "moment": "^2.29.4",
     "popper.js": "^1.16.1",
-    "selenium-webdriver": "^3.3.0",
+    "selenium-webdriver": "^4.14.0",
     "should": "^11.2.1",
     "tablesorter": "^2.31.3",
+    "terser": "^5.21.0",
     "tinymce": "^4.9.11",
     "wolfy87-eventemitter": "^4.3.0"
   },
-  "devDependencies": {},
   "scripts": {
     "test": "mocha tests/selenium/test*.js"
   },
   "license": "ISC",
-  "repository": "https://git.math.uzh.ch/typo3/qfq"
+  "repository": "https://git.math.uzh.ch/typo3/qfq",
+  "devDependencies": {
+    "mocha": "^10.2.0"
+  }
 }
diff --git a/version b/version
index 7d7a0702435d3d1f633fc95f064cdd3df32f6caa..16e4d40238a8232ecfa8ee3e49f874f4637fd3ad 100644
--- a/version
+++ b/version
@@ -1 +1 @@
-23.10.0
+23.10.1