diff --git a/doc/CODING.md b/doc/CODING.md index 7fba0b2de5450f91888bf9fb56783ba7998dba94..4345a7e60eaaf60fc1d8d5884149d085de48ea92 100644 --- a/doc/CODING.md +++ b/doc/CODING.md @@ -43,13 +43,25 @@ LOAD * If a form is called without a SIP (form.permitNew='always'), than a SIP is created on the fly (as a parameter in the form). -* Depending on `r=0` or `r>0` a form submit will do an MySQL `insert` or `update` later during save. -* For new records (r=0), clicking on 'save' without closing the form is a tricky situation. Additionally the user might have open multiple - tabs (same form, all r=0) and after saving the record (wihtout closing the form) the user expects that it's ok to edit - the record again and again. Unfortunately, the initial created SIP (before 'form load') is not uniqe anymore (multiple - tabs might contain a saved 'new record'). To guarantee correct saving of r=0 records, a unique on the fly generated SIP - is creatd during form load - individually per browser tab. - +* Uniq SIP for mutliple tabs with r=0 + * Depending on `r=0` or `r>0` a form submit will do an MySQL `insert` or `update` later during save. + * For new records (r=0), clicking on 'save' without closing the form is a tricky situation. Additionally the user might have open multiple + tabs (same form, all r=0) and after saving the record (wihtout closing the form) the user expects that it's ok to edit + the record again and again. Unfortunately, the initial created SIP (before 'form load') is not uniqe anymore (multiple + tabs might contain a saved 'new record'). To guarantee correct saving of r=0 records, a unique on the fly generated SIP + is creatd during form load - individually per browser tab. + + * Formular zusammenbausen + * QuickFormQuery: doForm > loadFormSpecification - laedt alle Elemente die nicht genested sind: native, pill, fieldset, templateGroup >> $his->formNative + * Damit wird '(BuildFormBootstrap / AbstractBuildForm) > process()' aufgerufen. + * Hier wird AbstractBuildForm->elements() aufgerufen (ein Aufruf fuer alle root elemente). + * Pro native Element (inkl. pill, fieldset, templateGroup) wird $builElementFunctionName aufgerufen. + - buildText() + - .... + - buildFieldSet() << von hier werden alle zum aktuellen 'FieldSet' gehoerenden SubElemente abgearbeitet - via AbstractBuildForm->elements() (damit schliesst sich der Kreis und wird rekursiv) + - buildPill() << von hier werden alle zum aktuellen 'Pill' gehoerenden SubElemente abgearbeitet - via AbstractBuildForm->elements() (damit schliesst sich der Kreis und wird rekursiv) + - buildTemplateGroup() << von hier werden alle zum aktuellen 'Pill' gehoerenden SubElemente abgearbeitet - via AbstractBuildForm->elements() (damit schliesst sich der Kreis und wird rekursiv) + >> SAVE ---- * Via wrapper api/save.php diff --git a/extension/qfq/qfq/AbstractBuildForm.php b/extension/qfq/qfq/AbstractBuildForm.php index 18e426f8995e2728e7e6fd231d583500f47a8212..0362fbb6410a041cddedd8ad5db4f60d146b4dac 100644 --- a/extension/qfq/qfq/AbstractBuildForm.php +++ b/extension/qfq/qfq/AbstractBuildForm.php @@ -98,7 +98,8 @@ abstract class AbstractBuildForm { 'subrecord' => 'Subrecord', 'upload' => 'File', 'fieldset' => 'Fieldset', - 'pill' => 'Pill' + 'pill' => 'Pill', + 'templateGroup' => 'TemplateGroup' ]; $this->buildRowName = [ @@ -120,7 +121,8 @@ abstract class AbstractBuildForm { 'subrecord' => 'Subrecord', 'upload' => 'Native', 'fieldset' => 'Fieldset', - 'pill' => 'Pill' + 'pill' => 'Pill', + 'templateGroup' => 'TemplateGroup' ]; $this->symbol[SYMBOL_EDIT] = "<span class='glyphicon " . GLYPH_ICON_EDIT . "'></span>"; @@ -345,10 +347,10 @@ abstract class AbstractBuildForm { $modeCollectFe = FLAG_DYNAMIC_UPDATE, $htmlElementNameIdZero = false, $storeUse = STORE_USE_DEFAULT, $mode = FORM_LOAD) { $html = ''; $flagOutput = false; + // The following 'FormElement.parameter' will never be used during load (fe.type='upload'). $skip = [FE_SQL_UPDATE, FE_SQL_INSERT, FE_SQL_DELETE, FE_SQL_AFTER, FE_SQL_BEFORE, F_FE_PARAMETER]; - // get current data record if ($recordId > 0 && $this->store->getVar('id', STORE_RECORD) === false) { $row = $this->db->sql("SELECT * FROM " . $this->formSpec[F_TABLE_NAME] . " WHERE id = ?", ROW_EXPECT_1, array($recordId), "Form '" . $this->formSpec[F_NAME] . "' failed to load record '$recordId' from table '" . $this->formSpec[F_TABLE_NAME] . "'."); @@ -367,7 +369,7 @@ abstract class AbstractBuildForm { continue; // skip this FE } - $flagOutput = ($fe[FE_TYPE] !== FE_TYPE_EXTRA); + $flagOutput = ($fe[FE_TYPE] !== FE_TYPE_EXTRA); // type='extra' will not displayed not trasnmitted to the form $debugStack = array(); @@ -637,12 +639,12 @@ abstract class AbstractBuildForm { return $url; } - abstract public function buildRowNative(array $formElement, $htmlElement, $htmlFormElementId); - abstract public function buildRowPill(array $formElement, $elementHtml); abstract public function buildRowFieldset(array $formElement, $elementHtml); + abstract public function buildRowTemplateGroup(array $formElement, $elementHtml); + abstract public function buildRowSubrecord(array $formElement, $elementHtml); /** @@ -2327,6 +2329,107 @@ abstract class AbstractBuildForm { return $html; } + /** + * Build a HTML fieldset. Renders all assigned FormElements inside the fieldset. + * + * @param array $formElement + * @param $htmlFormElementId + * @param $value + * @param array $json + * @param string $mode FORM_LOAD | FORM_UPDATE | FORM_SAVE + * @return mixed + * @throws CodeException + * @throws DbException + */ + public function buildTemplateGroup(array $formElement, $htmlFormElementId, $value, array &$json, $mode = FORM_LOAD) { + $attribute = ''; + $html = ''; + + // save parent processed FE's + $tmpStore = $this->feSpecNative; + +// $attribute .= Support::doAttribute('name', $htmlFormElementId); +// $attribute .= Support::doAttribute('data-load', ($formElement['dynamicUpdate'] === 'yes') ? 'data-load' : ''); + + // <fieldset> +// $html = '<fieldset ' . $attribute . '>'; + + +// if ($formElement[FE_LABEL] !== '') { +// $html .= '<legend>' . $formElement[FE_LABEL] . '</legend>'; +// } + + $qfqFieldsName = 'qfq_fields_' . $formElement[FE_ID]; // ='qfq-fields' + $templateName = 'template_' . $formElement[FE_ID]; // ='template' + $targetName = 'target_' . $formElement[FE_ID]; // ='template' + Support::setIfNotSet($formElement, FE_VALUE, '5', ''); + $max = $formElement[FE_VALUE]; + + $codeJs = <<<EOT +<script type="text/javascript"> + $(function () { + $(".$qfqFieldsName").each( + function () { + QfqNS.initializeFields(this); + } + ); + }); +</script> +EOT; + + $htmlAdd = <<<EOT +<button type="button" onclick="QfqNS.addFields('#$templateName', '#$targetName', $max)">Add</button> +EOT; + + $htmlDelete = <<<EOT +<div class="qfq-delete-button"> + <button type="button" onclick="QfqNS.removeFields(this)">Remove</button> +</div> +EOT; + + // Element where the effective FormElements will be copied to. The BS 'col-md-* Classes are inside the template, not here. This here should be pure data without wrapping. + $html = Support::wrapTag('<div class="' . $qfqFieldsName . '" id="' . $targetName . '" data-qfq-line-template="#' . $templateName . '">', '', false); + // Add button, row below: The label & note of the FormElement 'templateGroup' will be used for the add button row. + $tmpFe = $formElement; + $tmpFe[FE_NAME] = '_add'; + $tmpFe[FE_LABEL] = ''; + $tmpFe[FE_NOTE] = ''; + $html .= $this->buildRowNative($tmpFe, $htmlAdd, $htmlFormElementId); + + $html = $this->wrap[WRAP_SETUP_IN_TEMPLATE_GROUP][WRAP_SETUP_START] . $html . $this->wrap[WRAP_SETUP_IN_TEMPLATE_GROUP][WRAP_SETUP_END]; + + // child FE's + $this->feSpecNative = $this->db->getNativeFormElements(SQL_FORM_ELEMENT_SPECIFIC_CONTAINER, + ['yes', $this->formSpec["id"], 'native,container', $formElement['id']], $this->formSpec); + + $last = count($this->feSpecNative) - 1; + if ($last < 1) { + return ''; + } + $this->feSpecNative[$last][FE_NOTE] .= $htmlDelete; + + // Get FE natives + $template = $this->elements($this->store->getVar(SIP_RECORD_ID, STORE_SIP), FORM_ELEMENTS_NATIVE, 0, $json); + + // Wrap the main html code + $template = Support::wrapTag('<div class="qfq-line">', $template); + $template = Support::wrapTag('<script id="' . $templateName . '" type="text/' . $templateName . '">', $template); + + $html .= $template . $codeJs; + + // restore parent processed FE's + $this->feSpecNative = $tmpStore; + + //TODO: nicht klar ob das hier noetig ist. +// $json = $this->getJsonElementUpdate($htmlFormElementId, $value, $formElement[FE_MODE]); + + return $html; + + } + + abstract public function buildRowNative(array $formElement, $htmlElement, $htmlFormElementId); + + /** * Create a delete link. * diff --git a/extension/qfq/qfq/BuildFormBootstrap.php b/extension/qfq/qfq/BuildFormBootstrap.php index e79efe3f2472d713bd593364cee1cdc00ba278eb..17703a29994701dfc8800d3d026014f6ed89c53f 100644 --- a/extension/qfq/qfq/BuildFormBootstrap.php +++ b/extension/qfq/qfq/BuildFormBootstrap.php @@ -62,6 +62,9 @@ class BuildFormBootstrap extends AbstractBuildForm { $this->wrap[WRAP_SETUP_IN_FIELDSET][WRAP_SETUP_START] = ""; $this->wrap[WRAP_SETUP_IN_FIELDSET][WRAP_SETUP_END] = ""; + $this->wrap[WRAP_SETUP_IN_TEMPLATE_GROUP][WRAP_SETUP_START] = ""; + $this->wrap[WRAP_SETUP_IN_TEMPLATE_GROUP][WRAP_SETUP_END] = ""; + // $this->feDivClass['radio'] = 'radio'; // $this->feDivClass['checkbox'] = 'checkbox'; } @@ -421,7 +424,9 @@ EOF; /** * @param array $formElement * @param string $htmlElement + * @param $htmlFormElementId * @return string + * @throws \qfq\UserFormException */ public function buildRowNative(array $formElement, $htmlElement, $htmlFormElementId) { $html = ''; @@ -491,6 +496,18 @@ EOF; return $html; } + /** + * Builds a templateGroup + * + * @param $formElement + * @param $elementHtml + */ + public function buildRowTemplateGroup(array $formElement, $elementHtml) { + $html = $elementHtml; + + return $html; + } + /** * @param $formElement * @param $elementHtml diff --git a/extension/qfq/qfq/BuildFormPlain.php b/extension/qfq/qfq/BuildFormPlain.php index 21c61c901446fa8dc1a87cd9d98ed1eb1068265f..e68c839276e9a51d649643ae8c07874dc624c312 100644 --- a/extension/qfq/qfq/BuildFormPlain.php +++ b/extension/qfq/qfq/BuildFormPlain.php @@ -39,6 +39,9 @@ class BuildFormPlain extends AbstractBuildForm { $this->wrap[WRAP_SETUP_IN_FIELDSET][WRAP_SETUP_START] = '<p>'; $this->wrap[WRAP_SETUP_IN_FIELDSET][WRAP_SETUP_END] = '</p>'; + $this->wrap[WRAP_SETUP_IN_TEMPLATE_GROUP][WRAP_SETUP_START] = ""; + $this->wrap[WRAP_SETUP_IN_TEMPLATE_GROUP][WRAP_SETUP_END] = ""; + } public function fillWrapLabelInputNote($label, $input, $note) { @@ -110,6 +113,10 @@ class BuildFormPlain extends AbstractBuildForm { // TODO: Implement buildRowFieldset() method. } + public function buildRowTemplateGroup(array $formElement, $elementHtml) { + // TODO: Implement buildRowTemplate() method. + } + public function buildRowSubrecord(array $formElement, $elementHtml) { // TODO: Implement buildRowSubrecord() method. } diff --git a/extension/qfq/qfq/BuildFormTable.php b/extension/qfq/qfq/BuildFormTable.php index 6531a820c31d882c76c6cd53ec0dc7ba043be173..d3ebc3888f6bed1bc4467b9155af13b24f36014e 100644 --- a/extension/qfq/qfq/BuildFormTable.php +++ b/extension/qfq/qfq/BuildFormTable.php @@ -42,6 +42,8 @@ class BuildFormTable extends AbstractBuildForm { $this->wrap[WRAP_SETUP_IN_FIELDSET][WRAP_SETUP_START] = '<p>'; $this->wrap[WRAP_SETUP_IN_FIELDSET][WRAP_SETUP_END] = '</p>'; + $this->wrap[WRAP_SETUP_IN_TEMPLATE_GROUP][WRAP_SETUP_START] = ""; + $this->wrap[WRAP_SETUP_IN_TEMPLATE_GROUP][WRAP_SETUP_END] = ""; } @@ -149,6 +151,10 @@ class BuildFormTable extends AbstractBuildForm { // TODO: Implement buildRowFieldset() method. } + public function buildRowTemplateGroup(array $formElement, $elementHtml) { + // TODO: Implement buildRowTemplate() method. + } + public function buildRowSubrecord(array $formElement, $elementHtml) { // TODO: Implement buildRowSubrecord() method. } diff --git a/extension/qfq/qfq/Constants.php b/extension/qfq/qfq/Constants.php index e31e5c4fe4e6053f66000a4cea02fa636f098502..a61b9476aea951eefd638046dbf26c3cf452822a 100644 --- a/extension/qfq/qfq/Constants.php +++ b/extension/qfq/qfq/Constants.php @@ -72,6 +72,7 @@ const WRAP_SETUP_INPUT = 'input'; const WRAP_SETUP_NOTE = 'note'; const WRAP_SETUP_SUBRECORD = 'subrecord'; const WRAP_SETUP_IN_FIELDSET = 'inFieldset'; +const WRAP_SETUP_IN_TEMPLATE_GROUP = 'inTemplateGroup'; const WRAP_SETUP_START = 'start'; const WRAP_SETUP_END = 'end'; @@ -493,6 +494,7 @@ const FE_SUBRECORD_ROW_CLASS = '_rowClass'; const FE_SUBRECORD_ROW_TITLE = '_rowTitle'; // FormElement columns: real +const FE_ID = 'id'; const FE_NAME = 'name'; const FE_TYPE = 'type'; const FE_MODE = 'mode'; diff --git a/extension/qfq/qfq/QuickFormQuery.php b/extension/qfq/qfq/QuickFormQuery.php index 9fcd90ebf10a7f4f774779c08d170a24c992f787..686ead9a50aab39282ace0b1f97570527d8ec1eb 100644 --- a/extension/qfq/qfq/QuickFormQuery.php +++ b/extension/qfq/qfq/QuickFormQuery.php @@ -281,9 +281,7 @@ class QuickFormQuery { case FORM_LOAD: case FORM_UPDATE: $formAction->elements($recordId, $this->feSpecAction, FE_TYPE_BEFORE_LOAD); - $data = $build->process($formMode); - $formAction->elements($recordId, $this->feSpecAction, FE_TYPE_AFTER_LOAD); break; @@ -407,6 +405,7 @@ class QuickFormQuery { // "SELECT *, ? AS 'nestedInFieldSet' FROM FormElement AS fe WHERE fe.formId = ? AND fe.deleted = 'no' AND FIND_IN_SET(fe.class, ? ) AND fe.feIdContainer = ? AND fe.enabled='yes' ORDER BY fe.ord, fe.id"; switch ($mode) { case FORM_LOAD: + // Select all Native elements (native, pill, fieldset, templateGroup) which are NOT nested = Root level. $this->feSpecNative = $this->db->getNativeFormElements(SQL_FORM_ELEMENT_SPECIFIC_CONTAINER, ['no', $this->formSpec["id"], 'native,container', 0], $this->formSpec); break; diff --git a/extension/qfq/sql/formEditor.sql b/extension/qfq/sql/formEditor.sql index 57da5ac7bac7feb086666446ae8dff0aa661a3e7..4fe59d5ff4089dc94697ebfc531a6548dae45534 100644 --- a/extension/qfq/sql/formEditor.sql +++ b/extension/qfq/sql/formEditor.sql @@ -69,7 +69,8 @@ CREATE TABLE IF NOT EXISTS `FormElement` ( `modeSql` TEXT NOT NULL, `class` ENUM('native', 'action', 'container') NOT NULL DEFAULT 'native', `type` ENUM('checkbox', 'date', 'datetime', 'dateJQW', 'datetimeJQW', 'extra', 'gridJQW', 'text', - 'editor', 'time', 'note', 'password', 'radio', 'select', 'subrecord', 'upload', 'fieldset', 'pill', + 'editor', 'time', 'note', 'password', 'radio', 'select', 'subrecord', 'upload', + 'fieldset', 'pill', 'templateGroup', 'beforeLoad', 'beforeSave', 'beforeInsert', 'beforeUpdate', 'beforeDelete', 'afterLoad', 'afterSave', 'afterInsert', 'afterUpdate', 'afterDelete', 'sendMail') NOT NULL DEFAULT 'text', `subrecordOption` SET('edit', 'delete', 'new') NOT NULL DEFAULT '', @@ -222,7 +223,7 @@ VALUES (2, 'modeSql', 'Mode sql', 'show', 'text', 'all', 'native', 170, '70,2', 255, '', '', '', '', '', 100, '', 'no', '', '', '', '', ''), (2, 'class', 'Class', 'show', 'select', 'all', 'native', 180, 0, 255, '', '', '{{class:FSRD0:alnumx}}', '', '', 100, '', 'yes', '', '', '', '', ''), (2, 'type', 'Type', 'show', 'select', 'all', 'native', 190, 0, 255, '', '', '', '', - 'itemList={{SELECT IF( "{{class:FRD0:alnumx}}"="native","checkbox,date,time,datetime,dateJQW,datetimeJQW,extra,gridJQW,text,editor,note,password,radio,select,subrecord,upload", IF("{{class:FRD0:alnumx}}"="action","beforeLoad,beforeSave,beforeInsert,beforeUpdate,beforeDelete,afterLoad,afterSave,afterInsert,afterUpdate,afterDelete,sendMail", "fieldset,pill") ) }}', + 'itemList={{SELECT IF( "{{class:FRD0:alnumx}}"="native","checkbox,date,time,datetime,dateJQW,datetimeJQW,extra,gridJQW,text,editor,note,password,radio,select,subrecord,upload", IF("{{class:FRD0:alnumx}}"="action","beforeLoad,beforeSave,beforeInsert,beforeUpdate,beforeDelete,afterLoad,afterSave,afterInsert,afterUpdate,afterDelete,sendMail", "fieldset,pill,templateGroup") ) }}', 100, '', 'yes', '', '', '', '', ''), (2, 'subrecordOption', 'Subrecord Option', 'show', 'checkbox', 'all', 'native', 200, 0, 0, '', '', '', '', '', 100, '', 'no', '', '', '', '', ''),