Commit 84b36b73 authored by Carsten  Rose's avatar Carsten Rose
Browse files

Merge branch 'F5695-Multiform' into 'master'

F5695 multiform

See merge request !186
parents 57330aac 9bc3c9c1
Pipeline #2507 passed with stages
in 2 minutes and 35 seconds
Neuer Build Neuer Build
=========== ===========
* release: Wird ein *Tag* vergeben (egal welcher Branch) der mit 'v' beginnt, erzeugt das automatisch einen Build - https://w3.math.uzh.ch/qfq/release. * release: Wird ein *Tag* vergeben (egal welcher Branch) der mit 'v' beginnt, erzeugt das automatisch einen Build - https://w3.math.uzh.ch/qfq/release.
* snapshot: Jeder Commit (egal welcher Branch) erzeugt einen Snapshot - https://w3.math.uzh.ch/qfq/snapshot. * snapshot: Jeder Commit (egal welcher Branch) erzeugt einen Snapshot - https://w3.math.uzh.ch/qfq/snapshot.
* nightly: Nach einem Commit auf Branch 'master' tagsueber, wird um 23:55 ein 'nightly' Build erstellt - https://w3.math.uzh.ch/qfq/nightly. * nightly: Nach einem Commit auf Branch 'master' tagsueber, wird um 23:55 ein 'nightly' Build erstellt - https://w3.math.uzh.ch/qfq/nightly.
......
...@@ -4136,10 +4136,14 @@ Parameter: slaveId ...@@ -4136,10 +4136,14 @@ Parameter: slaveId
Note: Note:
* `{{slaveId}}` can be used in any query of the current *FormElement*. * `{{slaveId:V}}` can be used in any query of the current *FormElement*.
* If the `action`-*FormElement* name exist as a column in the master record: Update that column *automatically* with the * If the `action`-*FormElement* name exist as a column in the master record: Update that column *automatically* with the
recent slaveId recent slaveId
* After an INSERT the `last_insert_id()` becomes the *slaveId*). * After an INSERT the `last_insert_id()` becomes the *{{slaveId:V}}*.
* `fillStoreVar` is fired first, than `slaveId`.
* If `slaveId` is known in `fillStoreVar`, set: `slaveId={{someId:V}}`.
Parameter: sqlBefore / sqlInsert / sqlUpdate / sqlDelete / sqlAfter Parameter: sqlBefore / sqlInsert / sqlUpdate / sqlDelete / sqlAfter
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
...@@ -4348,10 +4352,63 @@ Action ...@@ -4348,10 +4352,63 @@ Action
in one of the specified required FEs. in one of the specified required FEs.
.. _multi-language-form: .. _multi-form:
Multi Language Form Multi Form
------------------- ----------
`Multi Forms` are like a regular form with the difference that the shown FormElements are repeated for *each* selected record
(defined by `multiSql`).
+------------------+----------------------------------+------------------------------------------------+
| Name | | |
+==================+==================================+================================================+
| multiSql | {{!SELECT id, name FROM Person}} | Query to select MulitForm records |
+------------------+----------------------------------+------------------------------------------------+
| multiMgsNoRecord | Default: No data | Message shown if `multiSql` selects no records |
+------------------+----------------------------------+------------------------------------------------+
The Form is shown as a HTML table.
* `multiSql`: Selects the records where the defined FormElements will work on each.
* A uniq column 'id' or '_id' (not shown) is mandatory and has to reflect an existing record id in table `primary table`.
* Additional columns, defined in `multiSql`, will be shown on the form in the same line, before the FormElements.
`
Simple
======
General:
* It's not possible to create new records in simple mode, only existing records can be used.
Form:
* Per row, the STORE_RECORD is filled with the whole record of the primary table, referenced
by `multiSql.id`.
FormElement:
* The FormElement.name represents a column of the defined primary table.
* The existing values of such FormElements are automatically loaded.
* No further definition is required.
Advanced
========
* The `FormElement.name` do not have to be a column of the primary table.
* If `FormElement.name` is not a column of the primary table, the insert/update/delete SQL statement has to be
extra defined.
.. _multiple-languages:
Multiple languages
------------------
QFQ Forms might be configured for up to 5 different languages. Per language there is one extra field in the *Form editor*. QFQ Forms might be configured for up to 5 different languages. Per language there is one extra field in the *Form editor*.
Which field represents which language is configured in configuration_. Which field represents which language is configured in configuration_.
......
...@@ -21,10 +21,13 @@ class QfqController extends \TYPO3\CMS\Extbase\Mvc\Controller\ActionController { ...@@ -21,10 +21,13 @@ class QfqController extends \TYPO3\CMS\Extbase\Mvc\Controller\ActionController {
/** /**
* @return string * @return string
* @throws \CodeException * @throws \CodeException
* @throws \UserFormException
* @throws \PhpOffice\PhpSpreadsheet\Exception * @throws \PhpOffice\PhpSpreadsheet\Exception
* @throws \PhpOffice\PhpSpreadsheet\Reader\Exception * @throws \PhpOffice\PhpSpreadsheet\Reader\Exception
* @throws \PhpOffice\PhpSpreadsheet\Writer\Exception * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception
* @throws \Twig\Error\LoaderError
* @throws \Twig\Error\RuntimeError
* @throws \Twig\Error\SyntaxError
* @throws \UserFormException
*/ */
public function showAction() { public function showAction() {
......
...@@ -194,6 +194,140 @@ abstract class AbstractBuildForm { ...@@ -194,6 +194,140 @@ abstract class AbstractBuildForm {
abstract public function fillWrap(); abstract public function fillWrap();
/**
* @param $filter
* @param $modeCollectFe
* @param array $rcJson
* @return string
* @throws \CodeException
* @throws \DbException
* @throws \DownloadException
* @throws \PhpOffice\PhpSpreadsheet\Exception
* @throws \PhpOffice\PhpSpreadsheet\Reader\Exception
* @throws \PhpOffice\PhpSpreadsheet\Writer\Exception
* @throws \Twig\Error\LoaderError
* @throws \Twig\Error\RuntimeError
* @throws \Twig\Error\SyntaxError
* @throws \UserFormException
* @throws \UserReportException
*/
private function buildMultiForm($filter, $modeCollectFe, array &$rcJson) {
$htmlElements = '';
$rcJson = array();
$parentRecords = $this->evaluate->parse($this->formSpec[F_MULTI_SQL], ROW_REGULAR);
// No rows: nothing to do.
if (empty($parentRecords)) {
return $this->formSpec[F_MULTI_MSG_NO_RECORD];
}
// Check for 'id' or '_id' as column name
$idName = isset($parentRecords[0]['_' . F_MULTI_COL_ID]) ? '_' . F_MULTI_COL_ID : F_MULTI_COL_ID;
// Check that an column 'id' is given
if (!isset($parentRecords[0][$idName])) {
throw new \UserFormException(
json_encode([ERROR_MESSAGE_TO_USER => 'Missing column "_' . F_MULTI_COL_ID . '"', ERROR_MESSAGE_TO_DEVELOPER => $this->formSpec[F_MULTI_SQL]]),
ERROR_INVALID_OR_MISSING_PARAMETER);
}
// This is a dirty workaround for formSave: clear FORM STORE (already outdated values).
// Otherwise those outdated values will be taken to fill non primary FE in multiForm (which are garbage).
// Better solution would be to have FORM_STORE in sync.
$this->store::unsetStore(STORE_FORM);
$storeVarBase = $this->evaluate->parse($this->formSpec[FE_FILL_STORE_VAR]);
if (!is_array($storeVarBase)) {
$storeVarBase = array();
}
// Per row, iterate over all form elements
foreach ($parentRecords as $row) {
// Always start with a clean STORE_VAR
$this->store->setStore($storeVarBase, STORE_VAR, true);
$this->store->setStore($row, STORE_PARENT_RECORD, true);
$this->store->setVar(F_MULTI_COL_ID, $row[$idName], STORE_PARENT_RECORD); // In case '_id' is used, both '_id' and 'id' should be accessible.
$record = $this->dbArray[$this->dbIndexData]->sql('SELECT * FROM `' . $this->formSpec[F_TABLE_NAME] . '` WHERE id=' . $row[F_MULTI_COL_ID], ROW_EXPECT_1);
$this->store->setStore($record, STORE_RECORD, true);
$jsonTmp = array();
$feTmp = $this->feSpecNative;
$leftColumns = $this->buildMultiFormLeftColumns($row);
$rightInputs = $this->elements($row[$idName], $filter, 0, $jsonTmp, $modeCollectFe,
false, STORE_USE_DEFAULT, FORM_LOAD, true);
$htmlElements .= Support::wrapTag('<tr>', $leftColumns . $rightInputs);
// Clean for the next round
$this->feSpecNative = $feTmp;
$this->store::unsetStore(STORE_RECORD);
$rcJson = array_merge($rcJson, $jsonTmp);
}
$tableHead = Support::wrapTag('<tr>', $this->buildMultiFormTableHead($parentRecords[0]));
return '<table class="table"><thead>' . $tableHead . '</thead><tbody>' . $htmlElements . '</tbody></table>';
}
/**
* @param array $row
* @return string
*/
private function buildMultiFormLeftColumns(array $row) {
$line = '';
// Collect columns
foreach ($row as $key => $value) {
if (($key[0] ?? '') != '_') {
$line .= "<td>$value</td>";
}
}
return $line;
}
/**
* @param array $row
* @return string
* @throws \CodeException
* @throws \UserFormException
*/
private function buildMultiFormTableHead(array $row) {
$line = '';
// Collect columns
foreach ($row as $key => $value) {
if (($key[0] ?? '') != '_') {
$line .= "<th>$key</th>";
}
}
// Collect label from FormElements
foreach ($this->feSpecNative as $formElement) {
$editFeHtml = '';
// debugStack as Tooltip
if ($this->showDebugInfoFlag) {
// Build 'FormElement' Edit symbol
$feEditUrl = $this->createFormEditorUrl(FORM_NAME_FORM_ELEMENT, $formElement[FE_ID], ['formId' => $formElement[FE_FORM_ID]]);
$titleAttr = Support::doAttribute('title', $this->formSpec[FE_NAME] . ' / ' . $formElement[FE_NAME] . ' [' . $formElement[FE_ID] . ']');
$icon = Support::wrapTag('<span class="' . GLYPH_ICON . ' ' . GLYPH_ICON_EDIT . '">', '');
$editFeHtml = ' ' . Support::wrapTag("<a class='hidden " . CLASS_FORM_ELEMENT_EDIT . "' href='$feEditUrl' $titleAttr>", $icon);
}
$line .= '<th>' . $formElement[FE_LABEL] . $editFeHtml . '</th>';
}
return $line;
}
/** /**
* Builds complete 'form'. Depending of form specification, the layout will be 'plain' / 'table' / 'bootstrap'. * Builds complete 'form'. Depending of form specification, the layout will be 'plain' / 'table' / 'bootstrap'.
* *
...@@ -206,17 +340,19 @@ abstract class AbstractBuildForm { ...@@ -206,17 +340,19 @@ abstract class AbstractBuildForm {
* @throws \CodeException * @throws \CodeException
* @throws \DbException * @throws \DbException
* @throws \DownloadException * @throws \DownloadException
* @throws \UserFormException
* @throws \UserReportException
* @throws \PhpOffice\PhpSpreadsheet\Exception * @throws \PhpOffice\PhpSpreadsheet\Exception
* @throws \PhpOffice\PhpSpreadsheet\Reader\Exception * @throws \PhpOffice\PhpSpreadsheet\Reader\Exception
* @throws \PhpOffice\PhpSpreadsheet\Writer\Exception * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception
* @throws \Twig\Error\LoaderError
* @throws \Twig\Error\RuntimeError
* @throws \Twig\Error\SyntaxError
* @throws \UserFormException
* @throws \UserReportException
*/ */
public function process($mode, $htmlElementNameIdZero = false, $latestFeSpecNative = array()) { public function process($mode, $htmlElementNameIdZero = false, $latestFeSpecNative = array()) {
$htmlHead = ''; $htmlHead = '';
$htmlTail = ''; $htmlTail = '';
$htmlT3vars = ''; $htmlT3vars = '';
$htmlSubrecords = '';
$htmlElements = ''; $htmlElements = '';
$json = array(); $json = array();
...@@ -240,18 +376,10 @@ abstract class AbstractBuildForm { ...@@ -240,18 +376,10 @@ abstract class AbstractBuildForm {
$filter = $this->getProcessFilter(); $filter = $this->getProcessFilter();
if ($this->formSpec['multiMode'] !== 'none') { // Form type
if ($this->formSpec[F_MULTI_SQL] === '') {
$parentRecords = $this->dbArray[$this->dbIndexQfq]->sql($this->formSpec['multiSql']);
foreach ($parentRecords as $row) {
$this->store->setStore($row, STORE_PARENT_RECORD, true);
$jsonTmp = array();
$htmlElements = $this->elements($row['_id'], $filter, 0, $jsonTmp, $modeCollectFe);
$json[] = $jsonTmp;
}
} else {
// Regular Form
$recordId = $this->store->getVar(SIP_RECORD_ID, STORE_SIP); $recordId = $this->store->getVar(SIP_RECORD_ID, STORE_SIP);
if (!($recordId == '' || is_numeric($recordId))) { if (!($recordId == '' || is_numeric($recordId))) {
throw new \UserFormException( throw new \UserFormException(
...@@ -271,23 +399,28 @@ abstract class AbstractBuildForm { ...@@ -271,23 +399,28 @@ abstract class AbstractBuildForm {
// Via 'element-update' // Via 'element-update'
$json[][API_ELEMENT_UPDATE][DIRTY_RECORD_HASH_MD5][API_ELEMENT_ATTRIBUTE]['value'] = $md5; $json[][API_ELEMENT_UPDATE][DIRTY_RECORD_HASH_MD5][API_ELEMENT_ATTRIBUTE]['value'] = $md5;
} }
} else {
// Multi Form
if ($mode === FORM_LOAD || $mode === FORM_SAVE) {
$htmlElements = $this->buildMultiForm($filter, $modeCollectFe, $json);
}
} }
// <form> // <form>
if ($mode === FORM_LOAD) { if ($mode === FORM_LOAD) {
$htmlT3vars = $this->prepareT3VarsForSave(); $htmlT3vars = $this->prepareT3VarsForSave();
$htmlTail = $this->tail(); $htmlTail = $this->tail();
$htmlSubrecords = $this->doSubrecords();
} }
$htmlHidden = $this->buildAdditionalFormElements(); $htmlHidden = $this->buildAdditionalFormElements();
$htmlSip = $this->buildHiddenSip($json); $htmlSip = $this->buildHiddenSip($json);
return ($mode === FORM_LOAD) ? $htmlHead . $htmlHidden . $htmlElements . $htmlSip . $htmlT3vars . $htmlTail . $htmlSubrecords : $json; return ($mode === FORM_LOAD) ? $htmlHead . $htmlHidden . $htmlElements . $htmlSip . $htmlT3vars . $htmlTail : $json;
} }
/** /**
* Builds the head area of the form. * Build the head area of the form.
* *
* @param string $mode * @param string $mode
* @return string * @return string
...@@ -527,11 +660,14 @@ abstract class AbstractBuildForm { ...@@ -527,11 +660,14 @@ abstract class AbstractBuildForm {
* @throws \CodeException * @throws \CodeException
* @throws \DbException * @throws \DbException
* @throws \DownloadException * @throws \DownloadException
* @throws \UserFormException
* @throws \UserReportException
* @throws \PhpOffice\PhpSpreadsheet\Exception * @throws \PhpOffice\PhpSpreadsheet\Exception
* @throws \PhpOffice\PhpSpreadsheet\Reader\Exception * @throws \PhpOffice\PhpSpreadsheet\Reader\Exception
* @throws \PhpOffice\PhpSpreadsheet\Writer\Exception * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception
* @throws \Twig\Error\LoaderError
* @throws \Twig\Error\RuntimeError
* @throws \Twig\Error\SyntaxError
* @throws \UserFormException
* @throws \UserReportException
*/ */
private function processReportSyntax($value) { private function processReportSyntax($value) {
...@@ -565,6 +701,7 @@ abstract class AbstractBuildForm { ...@@ -565,6 +701,7 @@ abstract class AbstractBuildForm {
return $value; return $value;
} }
/** /**
* Process all FormElements in $this->feSpecNative: Collect and return all HTML code & JSON. * Process all FormElements in $this->feSpecNative: Collect and return all HTML code & JSON.
* *
...@@ -576,20 +713,24 @@ abstract class AbstractBuildForm { ...@@ -576,20 +713,24 @@ abstract class AbstractBuildForm {
* @param bool $htmlElementNameIdZero * @param bool $htmlElementNameIdZero
* @param string $storeUseDefault * @param string $storeUseDefault
* @param string $mode FORM_LOAD | FORM_UPDATE | FORM_SAVE * @param string $mode FORM_LOAD | FORM_UPDATE | FORM_SAVE
* @param bool $flagMulti
* *
* @return string * @return string
* @throws \CodeException * @throws \CodeException
* @throws \DbException * @throws \DbException
* @throws \DownloadException * @throws \DownloadException
* @throws \UserFormException
* @throws \UserReportException
* @throws \PhpOffice\PhpSpreadsheet\Exception * @throws \PhpOffice\PhpSpreadsheet\Exception
* @throws \PhpOffice\PhpSpreadsheet\Reader\Exception * @throws \PhpOffice\PhpSpreadsheet\Reader\Exception
* @throws \PhpOffice\PhpSpreadsheet\Writer\Exception * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception
* @throws \Twig\Error\LoaderError
* @throws \Twig\Error\RuntimeError
* @throws \Twig\Error\SyntaxError
* @throws \UserFormException
* @throws \UserReportException
*/ */
public function elements($recordId, $filter, $feIdContainer, array &$json, public function elements($recordId, $filter, $feIdContainer, array &$json,
$modeCollectFe = FLAG_DYNAMIC_UPDATE, $htmlElementNameIdZero = false, $modeCollectFe = FLAG_DYNAMIC_UPDATE, $htmlElementNameIdZero = false,
$storeUseDefault = STORE_USE_DEFAULT, $mode = FORM_LOAD) { $storeUseDefault = STORE_USE_DEFAULT, $mode = FORM_LOAD, $flagMulti = false) {
$html = ''; $html = '';
// The following 'FormElement.parameter' will never be used during load (fe.type='upload'). FE_PARAMETER has been already expanded. // The following 'FormElement.parameter' will never be used during load (fe.type='upload'). FE_PARAMETER has been already expanded.
...@@ -637,9 +778,8 @@ abstract class AbstractBuildForm { ...@@ -637,9 +778,8 @@ abstract class AbstractBuildForm {
$this->store->appendToStore($fe[FE_FILL_STORE_VAR], STORE_VAR); $this->store->appendToStore($fe[FE_FILL_STORE_VAR], STORE_VAR);
} }
// for Upload FormElements, it's necessary to pre-calculate an optional given 'slaveId'. // If given, slaveId will be copied to STORE_VAR
if ($fe[FE_TYPE] === FE_TYPE_UPLOAD) { if (!empty($fe[FE_SLAVE_ID])) {
Support::setIfNotSet($fe, FE_SLAVE_ID);
$this->store->setVar(SYSTEM_FORM_ELEMENT_COLUMN, FE_SLAVE_ID, STORE_SYSTEM); // debug $this->store->setVar(SYSTEM_FORM_ELEMENT_COLUMN, FE_SLAVE_ID, STORE_SYSTEM); // debug
$slaveId = Support::falseEmptyToZero($this->evaluate->parse($fe[FE_SLAVE_ID])); $slaveId = Support::falseEmptyToZero($this->evaluate->parse($fe[FE_SLAVE_ID]));
$this->store->setVar(VAR_SLAVE_ID, $slaveId, STORE_VAR); $this->store->setVar(VAR_SLAVE_ID, $slaveId, STORE_VAR);
...@@ -666,11 +806,10 @@ abstract class AbstractBuildForm { ...@@ -666,11 +806,10 @@ abstract class AbstractBuildForm {
// $fe[FE_MODE_SQL] = ''; // $fe[FE_MODE_SQL] = '';
// } // }
if ($flagOutput === true) { if ($flagOutput === true && !$flagMulti) {
$this->fillWrapLabelInputNote($formElement[FE_BS_LABEL_COLUMNS], $formElement[FE_BS_INPUT_COLUMNS], $formElement[FE_BS_NOTE_COLUMNS]); $this->fillWrapLabelInputNote($formElement[FE_BS_LABEL_COLUMNS], $formElement[FE_BS_INPUT_COLUMNS], $formElement[FE_BS_NOTE_COLUMNS]);
} }
$value = '';
Support::setIfNotSet($formElement, FE_VALUE); Support::setIfNotSet($formElement, FE_VALUE);
if (is_array($formElement[FE_VALUE])) { if (is_array($formElement[FE_VALUE])) {
...@@ -734,19 +873,23 @@ abstract class AbstractBuildForm { ...@@ -734,19 +873,23 @@ abstract class AbstractBuildForm {
if ($flagOutput) { if ($flagOutput) {
// debugStack as Tooltip // debugStack as Tooltip
if ($this->showDebugInfoFlag) { if ($this->showDebugInfoFlag) {
if (count($debugStack) > 0) { if (count($debugStack) > 0) {
$elementHtml .= Support::doTooltip($formElement[FE_HTML_ID] . HTML_ID_EXTENSION_TOOLTIP, implode("\n", $debugStack)); $elementHtml .= Support::doTooltip($formElement[FE_HTML_ID] . HTML_ID_EXTENSION_TOOLTIP, implode("\n", $debugStack));
} }
// Build 'FormElement' Edit symbol if (!$flagMulti) {
$feEditUrl = $this->createFormEditorUrl(FORM_NAME_FORM_ELEMENT, $formElement[FE_ID], ['formId' => $formElement[FE_FORM_ID]]); // Build 'FormElement' Edit symbol. MultiForms: Edit symbol is in thead.
$titleAttr = Support::doAttribute('title', $this->formSpec[FE_NAME] . ' / ' . $formElement[FE_NAME] . ' [' . $formElement[FE_ID] . ']'); $feEditUrl = $this->createFormEditorUrl(FORM_NAME_FORM_ELEMENT, $formElement[FE_ID], ['formId' => $formElement[FE_FORM_ID]]);
$icon = Support::wrapTag('<span class="' . GLYPH_ICON . ' ' . GLYPH_ICON_EDIT . '">', ''); $titleAttr = Support::doAttribute('title', $this->formSpec[FE_NAME] . ' / ' . $formElement[FE_NAME] . ' [' . $formElement[FE_ID] . ']');
$elementHtml .= Support::wrapTag("<a class='hidden " . CLASS_FORM_ELEMENT_EDIT . "' href='$feEditUrl' $titleAttr>", $icon); $icon = Support::wrapTag('<span class="' . GLYPH_ICON . ' ' . GLYPH_ICON_EDIT . '">', '');
$elementHtml .= Support::wrapTag("<a class='hidden " . CLASS_FORM_ELEMENT_EDIT . "' href='$feEditUrl' $titleAttr>", $icon);
}
} }
// Construct Marshaller Name: buildRow // Construct Marshaller Name: buildRow...
$buildRowName = 'buildRow' . $this->buildRowName[$formElement[FE_TYPE]]; $tmpName = $flagMulti ? 'MultiElement' : $this->buildRowName[$formElement[FE_TYPE]];
$buildRowName = 'buildRow' . $tmpName;
$html .= $formElement[FE_HTML_BEFORE] . $this->$buildRowName($formElement, $elementHtml, $htmlFormElementName) . $formElement[FE_HTML_AFTER]; $html .= $formElement[FE_HTML_BEFORE] . $this->$buildRowName($formElement, $elementHtml, $htmlFormElementName) . $formElement[FE_HTML_AFTER];
} }
...@@ -760,6 +903,21 @@ abstract class AbstractBuildForm { ...@@ -760,6 +903,21 @@ abstract class AbstractBuildForm {
return $html; return $html;
} }
/**
* @param array $formElement
* @param $elementHtml
*
* @return string
*/
public function buildRowMultiElement(array $formElement, $elementHtml) {
$before = ($formElement[FE_HTML_BEFORE] == '') ? '<td>' : $formElement[FE_HTML_BEFORE];
$after = ($formElement[FE_HTML_AFTER] == '') ? '</td>' : $formElement[FE_HTML_AFTER];
return $before . $elementHtml . $after;
}
/** /**
* Checks if LDAP search is requested. * Checks if LDAP search is requested.
* Yes: prepare configuration and fire the query. * Yes: prepare configuration and fire the query.
...@@ -2870,7 +3028,7 @@ abstract class AbstractBuildForm { ...@@ -2870,7 +3028,7 @@ abstract class AbstractBuildForm {
$flagWidthLimit = true; $flagWidthLimit = true;
$control[SUBRECORD_COLUMN_MAX_LENGTH][$columnName] = SUBRECORD_COLUMN_DEFAULT_MAX_LENGTH; $control[SUBRECORD_COLUMN_MAX_LENGTH][$columnName] = SUBRECORD_COLUMN_DEFAULT_MAX_LENGTH;
// a) 'City@maxLength=40', b) 'Status@icon', c) 'Mailto@maxLength=80@nostrip' // a) 'City|maxLength=40', b) 'Status|icon', c) 'Mailto@maxLength=80|nostrip'
$arr = KeyValueStringParser::parse($columnName, '=', '|', KVP_IF_VALUE_EMPTY_COPY_KEY); $arr = KeyValueStringParser::parse($columnName, '=', '|', KVP_IF_VALUE_EMPTY_COPY_KEY);
foreach ($arr as $attribute => $value) { foreach ($arr as $attribute => $value) {
switch ($attribute) { switch ($attribute) {
......
...@@ -149,6 +149,11 @@ class BuildFormBootstrap extends AbstractBuildForm { ...@@ -149,6 +149,11 @@ class BuildFormBootstrap extends AbstractBuildForm {
$class = ['tab-content', $this->formSpec[F_CLASS_BODY]]; $class = ['tab-content', $this->formSpec[F_CLASS_BODY]];
if ($pill == '') { if ($pill == '') {
$class[] = 'col-md-12'; $class[] = 'col-md-12';
$class[] = 'qfq-form-body'; // Make an outline on form body
if ($title == '') {
$class[] = 'qfq-form-no-title';
}
} }
$html .= "<div " . Support::doAttribute('class', $class) . ">"; $html .= "<div " . Support::doAttribute('class', $class) . ">";
...@@ -714,6 +719,54 @@ EOF; ...@@ -714,6 +719,54 @@ EOF;
return $html; return $html;
} }
/**
* Wrap content with $wrapArray or, if specified use $formElement[$wrapName]. Inject $htmlId in wrap.
*
* Result:
* - if $bsColumns==0 and empty $formElement[$wrapName]: no wrap