Commit 2e0e3a4d authored by Carsten  Rose's avatar Carsten Rose
Browse files

Merge branch 'F9617-SaveRequired' into 'master'

F9617 save required

See merge request !201
parents 8c5a4d37 5c6bbe72
Pipeline #2734 passed with stages
in 2 minutes and 59 seconds
......@@ -2436,11 +2436,11 @@ After the user presses *Save*, *Close*, *Delete* or *New*, different actions are
to a previous page. The decision depends on:
* *Close* goes back (feels like close) to the previous page. Note: if there is no history, QFQ won't close the tab,
instead a message is shown.
instead a message is shown.
* *Save* stays on the current page.
* `close` - goes back (feels like close) to the previous page. Note: if there is no history, QFQ won't close the tab,
instead a message is shown.
instead a message is shown.
* `no` - no change, the browser remains on the current side. Close does not close the page. It just triggers a save if
there are modified data.
* `url` - the browser redirects to the URL or T3 page named in `Forward URL / Page`. Independent if the user presses `save` or `close`.
......@@ -2481,7 +2481,7 @@ The CLIENT `submit_reason` shows the user action:
Example forwardPage
^^^^^^^^^^^^^^^^^^^
* `{{SELECT IF('{{formModeGlobal:S}}'='requiredOff', 'no', 'auto') }}`
* `{{SELECT IF('{{formModeGlobal:S}}'='skipRequiredCheck', 'no', 'auto') }}`
* `{{SELECT IF('{{submit_reason:CE:alnumx}}'='save', 'no', 'url'), '|http://example.com' }}`
Type: combined dynamic mode & URL/page
......@@ -2690,27 +2690,31 @@ The Form global mode `mode` is given by default with `{{formModeGlobal:SE:alnumx
Optional it might be defined via *Form.parameter* ::
mode=readonly|requiredOff
mode=readonly|skipRequiredCheck
* `readonly`: all `FormElement` of the whole form are temporarily in `readonly` mode. This is a fast way to use an
existing *Form* just to display the form data, without a possibility for the user to change any data of the form.
* `requiredOff`: all `FormElement` of the whole, with `mode=required`, will temporarily switch to `mode=show`. In this
mode, the user might save the form without providing all necessary data. Later on, when application logic requires a
final submit, this mode is not used any longer (call the form as regular without the 'formModeGlobal' parameter) and
the form can only be saved with all data given.
Than, e.g. an action-FormElement 'afterSave' can be used to detect the final submit situation and do some extra stuff,
necessary for the final submit.
* `skipRequiredCheck`:
The following shows the same *Form* in the `regular`, `readonly` and `requiredOff` mode::
* All `FormElement` with `mode=required`, will switch to `mode=show`.
* The user might save the form without providing all necessary data.
* Calling the form without this mode (regular), the form can only be saved with all data given. Than, e.g. an
action-FormElement 'afterSave' can be used to detect the final submit situation and do some extra stuff,
necessary for a final submit.
* `requiredOff` is a deprecated synonym of `skipRequiredCheck`.
The following shows the same *Form* in the `regular`, `readonly` and `skipRequiredCheck` mode::
10.sql = SELECT CONCAT('p:{{pageAlias:T}}&form=person&r=', p.id, '|Regular') as _pagee,
CONCAT('p:{{pageAlias:T}}&form=person&formModeGlobal=readonly&r=', p.id, '|Readonly') as _pagee,
CONCAT('p:{{pageAlias:T}}&form=person&formModeGlobal=requiredOff&r=', p.id, '|Required off') as _pagee
CONCAT('p:{{pageAlias:T}}&form=person&formModeGlobal=skipRequiredCheck&r=', p.id, '|Required off') as _pagee
FROM Person AS p
..
FormElements
------------
......
......@@ -987,8 +987,7 @@ abstract class AbstractBuildForm {
for ($i = 0; $i < count($this->feSpecNative); ++$i) {
// Only check native elements which will be shown
if ($this->feSpecNative[$i][FE_CLASS] == FE_CLASS_NATIVE &&
($this->feSpecNative[$i][FE_MODE] == FE_MODE_SHOW || $this->feSpecNative[$i][FE_MODE] == FE_MODE_REQUIRED ||
$this->feSpecNative[$i][FE_MODE] == FE_MODE_SHOW_REQUIRED)
($this->feSpecNative[$i][FE_MODE] == FE_MODE_SHOW || $this->feSpecNative[$i][FE_MODE] == FE_MODE_REQUIRED)
) {
// Check if there is an explicit definition.
if (isset($this->feSpecNative[$i][FE_AUTOFOCUS])) {
......@@ -1144,7 +1143,7 @@ abstract class AbstractBuildForm {
// }
}
if ($formElement[FE_MODE] == FE_MODE_REQUIRED || $formElement[FE_MODE] == FE_MODE_SHOW_REQUIRED) {
if ($formElement[FE_MODE] == FE_MODE_REQUIRED) {
$addClassRequired = HelperFormElement::getRequiredPositionClass($formElement[F_FE_REQUIRED_POSITION]);
}
......@@ -1250,7 +1249,6 @@ abstract class AbstractBuildForm {
switch ($feMode) {
case FE_MODE_SHOW:
case FE_MODE_SHOW_REQUIRED:
break;
case FE_MODE_REQUIRED:
$required = 'yes';
......@@ -1679,7 +1677,6 @@ abstract class AbstractBuildForm {
switch ($feMode) {
case FE_MODE_HIDDEN:
case FE_MODE_SHOW:
case FE_MODE_SHOW_REQUIRED:
break;
case FE_MODE_REQUIRED:
case FE_MODE_READONLY:
......
......@@ -593,6 +593,15 @@ class BuildFormBootstrap extends AbstractBuildForm {
$attribute['class'] = 'form-horizontal';
$attribute['data-toggle'] = 'validator';
$formModeGlobal = $this->store::getVar(F_MODE_GLOBAL, STORE_SIP . STORE_EMPTY);
if ($formModeGlobal == '') {
$formModeGlobal = $this->formSpec[F_MODE_GLOBAL] ?? '';
}
if ($formModeGlobal == F_MODE_SKIP_REQUIRED_CHECK || $formModeGlobal == F_MODE_REQUIRED_OFF) {
$attribute[DATA_SKIP_REQUIRED_CHECK] = 'true';
}
if (isset($this->formSpec[F_SAVE_BUTTON_ACTIVE]) && $this->formSpec[F_SAVE_BUTTON_ACTIVE] != '0') {
$attribute[DATA_ENABLE_SAVE_BUTTON] = 'true';
}
......@@ -794,7 +803,7 @@ EOF;
}
}
if ($formElement[FE_MODE] == FE_MODE_REQUIRED || $formElement[FE_MODE] == FE_MODE_SHOW_REQUIRED) {
if ($formElement[FE_MODE] == FE_MODE_REQUIRED) {
$addClassRequired = HelperFormElement::getRequiredPositionClass($formElement[F_FE_REQUIRED_POSITION]);
}
......
......@@ -723,6 +723,7 @@ const VAR_FILENAME_BASE = 'filenameBase'; // Original filename of an uploaded fi
const VAR_FILENAME_EXT = 'filenameExt'; // Extension of the original filename of an uploaded file, .
const VAR_FILE_MIME_TYPE = 'mimeType';
const VAR_FILE_SIZE = 'fileSize';
const VAR_ALL_REQUIRED_GIVEN = 'allRequiredGiven'; // 0 if at least one FE is required and value is missing, else 1.
// PHP class Typeahead
......@@ -835,6 +836,8 @@ const API_ANSWER_REDIRECT_URL_SKIP_HISTORY = 'url-skip-history';
const API_TYPEAHEAD_KEY = 'key';
const API_TYPEAHEAD_VALUE = 'value';
const DATA_SKIP_REQUIRED_CHECK = 'data-skip-required-check';
const DATA_HIDDEN = 'data-hidden';
const DATA_DISABLED = 'data-disabled';
const DATA_REQUIRED = 'data-required';
......@@ -1007,7 +1010,8 @@ const F_TYPEAHEAD_LDAP_SEARCH_PER_TOKEN = 'typeAheadLdapSearchPerToken';
const F_MODE = 'mode';
const F_MODE_READONLY = 'readonly';
const F_MODE_REQUIRED_OFF = 'requiredOff';
const F_MODE_REQUIRED_OFF = 'requiredOff'; // deprecated, #9617
const F_MODE_SKIP_REQUIRED_CHECK = 'skipRequiredCheck';
const F_MODE_GLOBAL = 'formModeGlobal';
const F_SAVE_BUTTON_ACTIVE = 'saveButtonActive';
......@@ -1059,7 +1063,6 @@ const CLIENT_REST_FORM = '_form';
const FE_MODE_SHOW = 'show';
const FE_MODE_READONLY = 'readonly';
const FE_MODE_REQUIRED = 'required';
const FE_MODE_SHOW_REQUIRED = 'showRequired';
const FE_MODE_HIDDEN = 'hidden';
const FE_CLASS_NATIVE = 'native';
......
......@@ -408,7 +408,7 @@ EOF;
EOF;
}
$skip = (!($formElement[FE_MODE] == FE_MODE_SHOW || $formElement[FE_MODE] == FE_MODE_REQUIRED || $formElement[FE_MODE] == FE_MODE_SHOW_REQUIRED));
$skip = (!($formElement[FE_MODE] == FE_MODE_SHOW || $formElement[FE_MODE] == FE_MODE_REQUIRED));
// LOCK
if (!$skip && HelperFormElement::booleParameter($formElement[FE_INPUT_EXTRA_BUTTON_LOCK] ?? '-')) {
......@@ -532,4 +532,5 @@ EOF;
public static function booleParameter($data) {
return $data == '' || $data == '1';
}
}
\ No newline at end of file
......@@ -1171,8 +1171,6 @@ class Support {
case FE_MODE_REQUIRED:
if ($formMode == F_MODE_READONLY) {
$feMode = FE_MODE_READONLY;
} elseif ($formMode == F_MODE_REQUIRED_OFF && $feMode == FE_MODE_REQUIRED) {
$feMode = FE_MODE_SHOW_REQUIRED;
}
break;
......
......@@ -544,10 +544,17 @@ class Save {
*/
public function checkRequiredHidden() {
$requiredOff = ($this->store->getVar(F_MODE_GLOBAL, STORE_SIP) == F_MODE_REQUIRED_OFF);
$formModeGlobal = $this->store->getVar(F_MODE_GLOBAL, STORE_SIP . STORE_EMPTY);
if ($formModeGlobal == '') {
$formModeGlobal = $this->formSpec[F_MODE_GLOBAL] ?? '';
}
$reportRequiredFailed = ($formModeGlobal != F_MODE_REQUIRED_OFF && $formModeGlobal != F_MODE_SKIP_REQUIRED_CHECK);
$clientValues = $this->store::getStore(STORE_FORM);
$flagAllRequiredGiven = 1;
foreach ($this->feSpecNative AS $key => $formElement) {
// Do not check retype slave FE.
......@@ -569,8 +576,11 @@ class Save {
$mode = 'fake'; // The next if() should never be true.
}
if (!$requiredOff && $mode == FE_MODE_REQUIRED && empty($clientValues[$formElement[FE_NAME]])) {
throw new \UserFormException("Missing required value: " . $formElement[FE_LABEL], ERROR_REQUIRED_VALUE_EMPTY);
if ($mode == FE_MODE_REQUIRED && empty($clientValues[$formElement[FE_NAME]])) {
$flagAllRequiredGiven = 0;
if ($reportRequiredFailed) {
throw new \UserFormException("Missing required value: " . $formElement[FE_LABEL], ERROR_REQUIRED_VALUE_EMPTY);
}
}
if ($mode == FE_MODE_HIDDEN) {
......@@ -578,6 +588,9 @@ class Save {
$this->store::unsetVar($formElement[FE_NAME], STORE_FORM);
}
}
// Save 'allRequiredGiven in STORE_VAR
$this->store::setVar(VAR_ALL_REQUIRED_GIVEN, $flagAllRequiredGiven, STORE_VAR, true);
}
/**
......
......@@ -184,7 +184,7 @@ class FillStoreForm {
if ($formMode == FORM_UPDATE && $formModeGlobal == '') {
# During 'update': fake all elements to be not 'required'.
$formModeGlobal = F_MODE_REQUIRED_OFF;
$formModeGlobal = F_MODE_SKIP_REQUIRED_CHECK;
}
// If called through 'api/...': get STORE_TYPO3 via SIP parameter.
......@@ -289,7 +289,6 @@ class FillStoreForm {
if ($formElement[FE_DYNAMIC_UPDATE] === 'yes' ||
$formElement[FE_MODE] === FE_MODE_REQUIRED ||
$formElement[FE_MODE] === FE_MODE_SHOW_REQUIRED ||
$formElement[FE_MODE] === FE_MODE_SHOW ||
(isset($formElement[FE_PROCESS_READ_ONLY]) && $formElement[FE_PROCESS_READ_ONLY] != '0')) {
......
......@@ -67,6 +67,12 @@ var QfqNS = QfqNS || {};
this.form = new n.Form(this.formId, false);
}
if ($('#' + QfqNS.escapeJqueryIdSelector(this.formId)).data('skip-required-check')) {
this.skipRequiredCheck = true;
} else {
this.skipRequiredCheck = false;
}
this.infoLockedButton = this.infoLockedButton.bind(this);
// This is required when displaying validation messages, in order to activate the tab, which has validation
......@@ -622,12 +628,17 @@ var QfqNS = QfqNS || {};
alert = new n.Alert("Form is incomplete.", "warning");
alert.timeout = 3000;
alert.show();
return;
if (!this.skipRequiredCheck) {
return;
}
}
// First, remove all validation states, in case a previous submit has set a validation state, thus we're not
// stockpiling them.
this.clearAllValidationStates();
if (!this.skipRequiredCheck) {
this.clearAllValidationStates();
}
submitReason = {
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment