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