From 1d995d9640d4911ee41ed40833420bca8faad70f Mon Sep 17 00:00:00 2001 From: Carsten Rose <carsten.rose@math.uzh.ch> Date: Wed, 1 Mar 2017 22:13:31 +0100 Subject: [PATCH] Implemented generating of 'id' per FormElement. Support.php: new function insertAttribute(). AbstractBuildForm.php: added 'id' to all FormElements. BuildFormBootstrap: extended customWrap to insert 'id' in every wrap element. QuickFormQuery.php: Add 'id' to Form ToolTip. --- extension/qfq/qfq/AbstractBuildForm.php | 66 ++++++++++++--------- extension/qfq/qfq/BuildFormBootstrap.php | 16 +++-- extension/qfq/qfq/Constants.php | 8 +++ extension/qfq/qfq/QuickFormQuery.php | 5 +- extension/qfq/qfq/helper/Support.php | 39 +++++++++++- extension/qfq/tests/phpunit/SupportTest.php | 25 ++++++++ 6 files changed, 121 insertions(+), 38 deletions(-) diff --git a/extension/qfq/qfq/AbstractBuildForm.php b/extension/qfq/qfq/AbstractBuildForm.php index 303972969..7876f7981 100644 --- a/extension/qfq/qfq/AbstractBuildForm.php +++ b/extension/qfq/qfq/AbstractBuildForm.php @@ -398,15 +398,10 @@ abstract class AbstractBuildForm { //In case the current element is a 'RETYPE' element: take the element name of the source FormElement. Needed in the next row to retrieve the default value. $name = (isset($formElement[FE_RETYPE_SOURCE_NAME])) ? $formElement[FE_RETYPE_SOURCE_NAME] : $formElement[FE_NAME]; - // Get default value -// $value = ($formElement[FE_VALUE] === '') ? $this->store->getVar($name, $storeUse, -// $formElement['checkType']) : $formElement[FE_VALUE]; - // If there is a value explicit defined: take it $value = $formElement[FE_VALUE]; - // if ($value === '') { - // Only take the default, if the FE is a real tablecolumn. + // Only take the default, if the FE is a real tablecolumn. See #2064 if ($this->store->getVar($formElement[FE_NAME], STORE_TABLE_COLUMN_TYPES) !== false) { $value = $this->store->getVar($name, $storeUse, $formElement['checkType']); } @@ -445,7 +440,7 @@ abstract class AbstractBuildForm { if ($flagOutput) { // debugStack as Tooltip if ($this->showDebugInfo && count($debugStack) > 0) { - $elementHtml .= Support::doTooltip($formElement[FE_HTML_ID], implode("\n", $debugStack)); + $elementHtml .= Support::doTooltip($formElement[FE_HTML_ID] . HTML_ID_EXTENSION_TOOLTIP, implode("\n", $debugStack)); } // Construct Marshaller Name: buildRow @@ -690,17 +685,19 @@ abstract class AbstractBuildForm { * * * @param array $formElement - * @param $htmlFormElementName - * @param $value + * @param string $htmlFormElementName + * @param string $value * @param array $json * @param string $mode FORM_LOAD | FORM_UPDATE | FORM_SAVE - * @return string + * @return string complete rendered HTML input element. * @throws \qfq\UserFormException */ public function buildInput(array $formElement, $htmlFormElementName, $value, array &$json, $mode = FORM_LOAD) { $textarea = ''; + $attribute = ''; - $attribute = Support::doAttribute('name', $htmlFormElementName); + $attribute .= Support::doAttribute('id', $formElement[FE_HTML_ID]); + $attribute .= Support::doAttribute('name', $htmlFormElementName); $attribute .= Support::doAttribute('class', 'form-control'); if (isset($formElement[FE_RETYPE_SOURCE_NAME])) { @@ -708,7 +705,7 @@ abstract class AbstractBuildForm { $attribute .= Support::doAttribute('data-match', '[name=' . str_replace(':', '\\:', $htmlFormElementNamePrimary) . ']'); } - // Check for input type 'textarea' + // Check for input type 'textarea'. $colsRows = explode(',', $formElement['size'], 2); if (count($colsRows) === 2) { // <textarea> @@ -723,9 +720,9 @@ abstract class AbstractBuildForm { $this->adjustMaxLength($formElement); - if ($formElement['maxLength'] > 0 && $value !== '') { + if ($formElement[FE_MAX_LENGTH] > 0 && $value !== '') { // crop string only if it's not empty (substr returns false on empty strings) - $value = substr($value, 0, $formElement['maxLength']); + $value = substr($value, 0, $formElement[FE_MAX_LENGTH]); } // 'maxLength' needs an upper 'L': naming convention for DB tables! @@ -1217,6 +1214,7 @@ abstract class AbstractBuildForm { $html = ''; $valueJson = false; + $attribute .= Support::doAttribute('id', $formElement[FE_HTML_ID]); $attribute .= Support::doAttribute('name', $htmlFormElementName); $attribute .= Support::doAttribute('value', $formElement['checked'], false); $attribute .= Support::doAttribute('title', $formElement['tooltip']); @@ -1271,6 +1269,7 @@ abstract class AbstractBuildForm { $html = ''; $valueJson = false; + $attribute .= Support::doAttribute('id', $formElement[FE_HTML_ID]); $attribute .= Support::doAttribute('name', $htmlFormElementName); $attribute .= Support::doAttribute('value', $formElement['checked'], false); $attribute .= Support::doAttribute('title', $formElement['tooltip']); @@ -1360,6 +1359,7 @@ abstract class AbstractBuildForm { $jsonValue = false; $classActive = ''; $htmlFormElementNameUniq = HelperFormElement::prependFormElementNameCheckBoxMulti($htmlFormElementName, $ii); + $attribute .= Support::doAttribute('id', $formElement[FE_HTML_ID] . '-' . $ii); $attribute .= Support::doAttribute('name', $htmlFormElementNameUniq); $attribute .= Support::doAttribute('value', $itemKey[$ii]); @@ -1416,7 +1416,7 @@ abstract class AbstractBuildForm { $html = $this->buildNativeHidden(HelperFormElement::prependFormElementNameCheckBoxMulti($htmlFormElementName, 'h'), ''); - $orientation = ($formElement['maxLength'] > 1) ? ALIGN_HORIZONTAL : ALIGN_VERTICAL; + $orientation = ($formElement[FE_MAX_LENGTH] > 1) ? ALIGN_HORIZONTAL : ALIGN_VERTICAL; $checkboxClass = ($orientation === ALIGN_HORIZONTAL) ? 'checkbox-inline' : 'checkbox'; $br = ''; @@ -1425,6 +1425,7 @@ abstract class AbstractBuildForm { $jsonValue = false; $attribute = $attributeBase; $htmlFormElementNameUniq = HelperFormElement::prependFormElementNameCheckBoxMulti($htmlFormElementName, $ii); + $attribute .= Support::doAttribute('id', $formElement[FE_HTML_ID] . '-' . $ii); $attribute .= Support::doAttribute('name', $htmlFormElementNameUniq); // Do this only the first round. @@ -1455,9 +1456,9 @@ abstract class AbstractBuildForm { $htmlElement = Support::wrapTag("<div class='$checkboxClass'>", $htmlElement, true); // control orientation - if ($formElement['maxLength'] > 1) { + if ($formElement[FE_MAX_LENGTH] > 1) { - if ($jj == $formElement['maxLength']) { + if ($jj == $formElement[FE_MAX_LENGTH]) { $jj = 0; $br = '<br>'; } else { @@ -1567,6 +1568,7 @@ abstract class AbstractBuildForm { $attributeBase = $this->getAttributeFeMode($formElement[FE_MODE]); $attributeBase .= $this->getAttributeList($formElement, [F_FE_DATA_PATTERN_ERROR, F_FE_DATA_REQUIRED_ERROR, F_FE_DATA_MATCH_ERROR, F_FE_DATA_ERROR]); + $attributeBase .= Support::doAttribute('id', $formElement[FE_HTML_ID]); $attributeBase .= Support::doAttribute('name', $htmlFormElementName); $attributeBase .= Support::doAttribute('type', $formElement[FE_TYPE]); $attributeBase .= Support::doAttribute('data-load', ($formElement[FE_DYNAMIC_UPDATE] === 'yes') ? 'data-load' : ''); @@ -1624,6 +1626,7 @@ abstract class AbstractBuildForm { * @throws \qfq\UserFormException */ private function constructRadioPlain(array $formElement, $htmlFormElementName, $value, array &$json, $mode = FORM_LOAD) { + $attributeBase = ''; if (isset($formElement[FE_BUTTON_CLASS])) { return $this->constructRadioButton($formElement, $htmlFormElementName, $value, $json, $mode); @@ -1636,15 +1639,16 @@ abstract class AbstractBuildForm { // Fill $itemKey & $itemValue $this->getKeyValueListFromSqlEnumSpec($formElement, $itemKey, $itemValue); - $attributeBase = $this->getAttributeFeMode($formElement[FE_MODE]); + $attributeBase .= $this->getAttributeFeMode($formElement[FE_MODE]); $attributeBase .= $this->getAttributeList($formElement, [F_FE_DATA_PATTERN_ERROR, F_FE_DATA_REQUIRED_ERROR, F_FE_DATA_MATCH_ERROR, F_FE_DATA_ERROR]); + $attributeBase .= Support::doAttribute('id', $formElement[FE_HTML_ID]); $attributeBase .= Support::doAttribute('name', $htmlFormElementName); $attributeBase .= Support::doAttribute('type', $formElement[FE_TYPE]); $attributeBase .= Support::doAttribute('data-load', ($formElement[FE_DYNAMIC_UPDATE] === 'yes') ? 'data-load' : ''); $jj = 0; - $orientation = ($formElement['maxLength'] > 1) ? ALIGN_HORIZONTAL : ALIGN_VERTICAL; + $orientation = ($formElement[FE_MAX_LENGTH] > 1) ? ALIGN_HORIZONTAL : ALIGN_VERTICAL; $radioClass = ($orientation === ALIGN_HORIZONTAL) ? 'radio-inline' : 'radio'; $br = ''; @@ -1675,9 +1679,9 @@ abstract class AbstractBuildForm { $htmlElement = Support::wrapTag('<label>', $htmlElement); } - if ($formElement['maxLength'] > 1) { + if ($formElement[FE_MAX_LENGTH] > 1) { - if ($jj == $formElement['maxLength']) { + if ($jj == $formElement[FE_MAX_LENGTH]) { $jj = 0; $br = '<br>'; } else { @@ -1712,11 +1716,13 @@ abstract class AbstractBuildForm { public function buildSelect(array $formElement, $htmlFormElementName, $value, array &$json, $mode = FORM_LOAD) { $itemKey = array(); $itemValue = array(); + $attribute = ''; // Fill $itemKey & $itemValue $this->getKeyValueListFromSqlEnumSpec($formElement, $itemKey, $itemValue); - $attribute = $this->getAttributeFeMode($formElement[FE_MODE]); + $attribute .= $this->getAttributeFeMode($formElement[FE_MODE]); + $attribute .= Support::doAttribute('id', $formElement[FE_HTML_ID]); $attribute .= Support::doAttribute('name', $htmlFormElementName); $attribute .= Support::doAttribute('class', 'form-control'); $attribute .= Support::doAttribute('title', $formElement['tooltip']); @@ -2188,6 +2194,7 @@ abstract class AbstractBuildForm { $hiddenSipUpload = $this->buildNativeHidden($htmlFormElementName, $sipUpload); + $attribute .= Support::doAttribute('id', $formElement[FE_HTML_ID]); $attribute .= Support::doAttribute('name', $htmlFormElementName); // $attribute .= Support::doAttribute('class', 'form-control'); $attribute .= Support::doAttribute('type', 'file'); @@ -2237,8 +2244,10 @@ abstract class AbstractBuildForm { * @throws UserFormException */ public function buildDateTime(array $formElement, $htmlFormElementName, $value, array &$json, $mode = FORM_LOAD) { + $attribute = ''; - $attribute = Support::doAttribute('name', $htmlFormElementName); + $attribute .= Support::doAttribute('id', $formElement[FE_HTML_ID]); + $attribute .= Support::doAttribute('name', $htmlFormElementName); $attribute .= Support::doAttribute('class', 'form-control'); $arrMinMax = null; @@ -2271,8 +2280,8 @@ abstract class AbstractBuildForm { } // truncate if necessary - if ($value != '' && $formElement['maxLength'] > 0) { - $value = substr($value, 0, $formElement['maxLength']); + if ($value != '' && $formElement[FE_MAX_LENGTH] > 0) { + $value = substr($value, 0, $formElement[FE_MAX_LENGTH]); } $type = $formElement[FE_TYPE]; @@ -2422,13 +2431,15 @@ abstract class AbstractBuildForm { * @throws \qfq\UserFormException */ public function buildEditor(array $formElement, $htmlFormElementName, $value, array &$json, $mode = FORM_LOAD) { + $attribute = ''; //TODO plugin autoresize nutzen um Editorgroesse anzugeben $this->adjustMaxLength($formElement); - $attribute = Support::doAttribute('name', $htmlFormElementName); - $attribute .= Support::doAttribute('id', $htmlFormElementName); + $attribute .= Support::doAttribute('id', $formElement[FE_HTML_ID]); + $attribute .= Support::doAttribute('name', $htmlFormElementName); +// $attribute .= Support::doAttribute('id', $htmlFormElementName); $attribute .= Support::doAttribute('class', 'qfq-tinymce'); $attribute .= Support::doAttribute('data-control-name', "$htmlFormElementName"); @@ -2601,6 +2612,7 @@ abstract class AbstractBuildForm { // save parent processed FE's $tmpStore = $this->feSpecNative; + $attribute .= Support::doAttribute('id', $formElement[FE_HTML_ID]); $attribute .= Support::doAttribute('name', $htmlFormElementName); $attribute .= Support::doAttribute('data-load', ($formElement[FE_DYNAMIC_UPDATE] === 'yes') ? 'data-load' : ''); diff --git a/extension/qfq/qfq/BuildFormBootstrap.php b/extension/qfq/qfq/BuildFormBootstrap.php index b618dc094..fcd0467d3 100644 --- a/extension/qfq/qfq/BuildFormBootstrap.php +++ b/extension/qfq/qfq/BuildFormBootstrap.php @@ -421,19 +421,19 @@ EOF; } $html .= $this->customWrap($formElement, $htmlLabel, FE_WRAP_LABEL, $formElement[FE_BS_LABEL_COLUMNS], - [$this->wrap[WRAP_SETUP_LABEL][WRAP_SETUP_START], $this->wrap[WRAP_SETUP_LABEL][WRAP_SETUP_END]]); + [$this->wrap[WRAP_SETUP_LABEL][WRAP_SETUP_START], $this->wrap[WRAP_SETUP_LABEL][WRAP_SETUP_END]], $formElement[FE_HTML_ID] . HTML_ID_EXTENSION_LABEL); $html .= $this->customWrap($formElement, $htmlElement, FE_WRAP_INPUT, $formElement[FE_BS_INPUT_COLUMNS], - [$this->wrap[WRAP_SETUP_INPUT][WRAP_SETUP_START], $this->wrap[WRAP_SETUP_INPUT][WRAP_SETUP_END]]); + [$this->wrap[WRAP_SETUP_INPUT][WRAP_SETUP_START], $this->wrap[WRAP_SETUP_INPUT][WRAP_SETUP_END]], $formElement[FE_HTML_ID] . HTML_ID_EXTENSION_INPUT); $note = Support::wrapTag("<div class='qfq-note'>", $formElement[FE_NOTE], true); $html .= $this->customWrap($formElement, $note, FE_WRAP_NOTE, $formElement[FE_BS_NOTE_COLUMNS], - [$this->wrap[WRAP_SETUP_NOTE][WRAP_SETUP_START], $this->wrap[WRAP_SETUP_NOTE][WRAP_SETUP_END]]); + [$this->wrap[WRAP_SETUP_NOTE][WRAP_SETUP_START], $this->wrap[WRAP_SETUP_NOTE][WRAP_SETUP_END]], $formElement[FE_HTML_ID] . HTML_ID_EXTENSION_NOTE); // ROW $openTag = (Support::findInSet('row', $formElement[FE_WRAP_ROW_LABEL_INPUT_NOW])) ? $this->wrap[WRAP_SETUP_ELEMENT][WRAP_SETUP_START] : ''; $closeTag = (Support::findInSet('/row', $formElement[FE_WRAP_ROW_LABEL_INPUT_NOW])) ? $this->wrap[WRAP_SETUP_ELEMENT][WRAP_SETUP_END] : ''; - $html = $this->customWrap($formElement, $html, FE_WRAP_ROW, 99, [$openTag, $closeTag]); + $html = $this->customWrap($formElement, $html, FE_WRAP_ROW, 99, [$openTag, $closeTag], $formElement[FE_HTML_ID] . HTML_ID_EXTENSION_ROW); return $html; } @@ -448,13 +448,15 @@ EOF; * @return string Wrapped $htmlElement * @throws \qfq\UserFormException */ - private function customWrap(array $formElement, $htmlElement, $wrapName, $bsColumns, array $wrapArray) { + private function customWrap(array $formElement, $htmlElement, $wrapName, $bsColumns, array $wrapArray, $htmlId = '') { + // If $bsColumns==0: do not wrap with default. if ($bsColumns == 0) { $wrapArray[0] = ''; $wrapArray[1] = ''; } + // If there is a 'per FormElement'-wrap, take it. if (isset($formElement[$wrapName])) { $wrapArray = explode('|', $formElement[$wrapName], 2); } @@ -463,6 +465,10 @@ EOF; throw new UserFormException("Need open & close wrap token for FormElement.parameter" . $wrapName . " - E.g.: <div ...>|</div>", ERROR_MISSING_VALUE); } + if ($wrapArray[0] != '') { + $wrapArray[0] = Support::insertAttribute($wrapArray[0], 'id', $htmlId); + } + return $wrapArray[0] . $htmlElement . $wrapArray[1]; } diff --git a/extension/qfq/qfq/Constants.php b/extension/qfq/qfq/Constants.php index 75ca169ac..302817e24 100644 --- a/extension/qfq/qfq/Constants.php +++ b/extension/qfq/qfq/Constants.php @@ -161,6 +161,7 @@ const ERROR_SENDMAIL_MISSING_VALUE = 1072; const ERROR_OVERWRITE_RECORD_ID = 1073; const ERROR_MISSING_SLAVE_ID_DEFINITION = 1074; const ERROR_MISSING_INTL = 1075; +const ERROR_HTML_TOKEN_TOO_SHORT = 1076; // Subrecord const ERROR_SUBRECORD_MISSING_COLUMN_ID = 1100; @@ -574,6 +575,7 @@ const RETYPE_FE_NAME_EXTENSION = 'RETYPE'; const FE_HTML_ID = 'htmlId'; // Will be dynamically computed during runtime. + // FormElement Types const FE_TYPE_UPLOAD = 'upload'; const FE_TYPE_EXTRA = 'extra'; @@ -599,6 +601,12 @@ const ESCAPE_WITH_HTML_QUOTE = 'htmlquote'; const FLAG_ALL = 'flagAll'; const FLAG_DYNAMIC_UPDATE = 'flagDynamicUpdate'; +const HTML_ID_EXTENSION_LABEL = '-l'; +const HTML_ID_EXTENSION_INPUT = '-i'; +const HTML_ID_EXTENSION_NOTE = '-n'; +const HTML_ID_EXTENSION_TOOLTIP = '-t'; +const HTML_ID_EXTENSION_ROW = '-r'; + const QUERY_TYPE_SELECT = 'type: select,show,describe,explain'; const QUERY_TYPE_INSERT = 'type: insert'; const QUERY_TYPE_UPDATE = 'type: update,replace,delete'; diff --git a/extension/qfq/qfq/QuickFormQuery.php b/extension/qfq/qfq/QuickFormQuery.php index d1970ad96..f2860fbc9 100644 --- a/extension/qfq/qfq/QuickFormQuery.php +++ b/extension/qfq/qfq/QuickFormQuery.php @@ -186,9 +186,8 @@ class QuickFormQuery { $html = ''; if ($this->store->getVar(TYPO3_DEBUG_SHOW_BODY_TEXT, STORE_TYPO3) === 'yes') { -// TODO: hier den Tootltip mit eienr ID versehen -// $htmlId = HelperFormElement::buildFormElementId($this->) - $html .= Support::doTooltip('', $this->t3data['bodytext']); + $htmlId = HelperFormElement::buildFormElementId($this->formSpec[F_ID], 0, 0, 0); + $html .= Support::doTooltip($htmlId . HTML_ID_EXTENSION_TOOLTIP, $this->t3data['bodytext']); } $html .= $this->doForm(FORM_LOAD); diff --git a/extension/qfq/qfq/helper/Support.php b/extension/qfq/qfq/helper/Support.php index 1b3f38a32..7526a468b 100644 --- a/extension/qfq/qfq/helper/Support.php +++ b/extension/qfq/qfq/helper/Support.php @@ -100,11 +100,14 @@ class Support { /** * Format's an attribute: $type=$value. If $flagOmitEmpty==true && $value=='': return ''. + * Add's a space at the end. * * @param string $type * @param string|array $value * @param bool $flagOmitEmpty - * @return string + * @param string $modeEscape + * @return string correctly fomratted attribute. Space at the end. + * @throws CodeException */ public static function doAttribute($type, $value, $flagOmitEmpty = true, $modeEscape = ESCAPE_WITH_BACKSLASH) { @@ -120,7 +123,7 @@ class Support { switch (strtolower($type)) { case 'size': case 'maxlength': - // empty or '0' for attributes of type 'size' or 'maxlenght' result in unsuable input elements: skip this. + // empty or '0' for attributes of type 'size' or 'maxlength' result in unsuable input elements: skip this. if ($value === '' || $value == 0) { return ''; } @@ -137,7 +140,6 @@ class Support { $value = self::escapeDoubleTick(trim($value), $modeEscape); return $type . '="' . $value . '" '; - } /** @@ -175,6 +177,37 @@ class Support { return $newStr; } + /** + * Format's an attribute and inserts them at the beginning of the $htmlTag: + * If $flagOmitEmpty==true && $value=='': insert nothing + * + * @param string $htmlTag with open and closing angle. + * @param string $type + * @param string|array $value + * @param bool $flagOmitEmpty + * @param string $modeEscape + * @return string correctly fomratted attribute. Space at the end. + * @throws CodeException + */ + public static function insertAttribute($htmlTag, $type, $value, $flagOmitEmpty = true, $modeEscape = ESCAPE_WITH_BACKSLASH) { + $htmlTag = trim($htmlTag); + + // e.g. '<div class=...' will be exploded to '<div' and 'class=...' + $parts = explode(' ', $htmlTag, 2); + if (count($parts) < 2) { + if (strlen($htmlTag) < 3) { + throw new CodeException('HTML Token too short (<3 chars):' . $htmlTag, ERROR_HTML_TOKEN_TOO_SHORT); + } else { + $parts[0] = substr($htmlTag, 0, -1); + $parts[1] = '>'; + } + } + + $attr = self::doAttribute($type, $value, $flagOmitEmpty, $modeEscape); + + return $parts[0] . ' ' . $attr . $parts[1]; + } + /** * Search for the parameter $needle in $haystack. The arguments has to be seperated by ','. * diff --git a/extension/qfq/tests/phpunit/SupportTest.php b/extension/qfq/tests/phpunit/SupportTest.php index 33a5adef3..7c9a6b164 100644 --- a/extension/qfq/tests/phpunit/SupportTest.php +++ b/extension/qfq/tests/phpunit/SupportTest.php @@ -404,6 +404,31 @@ class SupportTest extends \PHPUnit_Framework_TestCase { $this->assertEquals('\\"\\"\\"', $new); } + public function testInsertAttribute() { + + $new = Support::insertAttribute('<i>', 'class', 'qfq'); + $this->assertEquals('<i class="qfq" >', $new); + + $new = Support::insertAttribute('<div>', 'class', 'qfq'); + $this->assertEquals('<div class="qfq" >', $new); + + $new = Support::insertAttribute(' <div> ', 'class', 'qfq'); + $this->assertEquals('<div class="qfq" >', $new); + + $new = Support::insertAttribute('<div >', 'class', 'qfq'); + $this->assertEquals('<div class="qfq" >', $new); + + $new = Support::insertAttribute('<div class="123">', 'class', 'qfq'); + $this->assertEquals('<div class="qfq" class="123">', $new); + } + + /** + * @expectedException \qfq\CodeException + */ + public function testInsertAttributeException1() { + Support::insertAttribute('<>', 'class', 'qfq'); + } + protected function setUp() { parent::setUp(); -- GitLab