From 6e049ecd5ed6febe8516bb1ae099efd7717656a7 Mon Sep 17 00:00:00 2001 From: Carsten Rose <carsten.rose@math.uzh.ch> Date: Tue, 25 Feb 2020 23:29:41 +0100 Subject: [PATCH] Refs #10145: QFQ fills data-typeahead-initial-suggestion --- Documentation/Manual.rst | 14 ++++++-- extension/Classes/Core/AbstractBuildForm.php | 20 +++++++---- extension/Classes/Core/Constants.php | 4 +-- extension/Classes/Core/Database/Database.php | 35 ++++++++++--------- .../Classes/Core/Helper/HelperFormElement.php | 1 - extension/Classes/Core/Helper/OnArray.php | 23 ++++++++++++ extension/Classes/Core/Helper/Support.php | 4 +-- .../Classes/Core/Store/FillStoreForm.php | 2 +- 8 files changed, 71 insertions(+), 32 deletions(-) diff --git a/Documentation/Manual.rst b/Documentation/Manual.rst index e9793b271..2f787953d 100644 --- a/Documentation/Manual.rst +++ b/Documentation/Manual.rst @@ -3227,6 +3227,8 @@ See also at specific *FormElement* definitions. | | | (defaults to 00:00:00 if none entered). | +------------------------+--------+----------------------------------------------------------------------------------------------------------+ | typeAheadLimit, | string | See `input-typeahead`_ | +| typeAheadInitial | | | +| Suggestion, | | | | typeAheadMinLength, | | | | typeAheadSql, | | | | typeAheadSqlPrefetch, | | | @@ -3540,7 +3542,7 @@ configured). Configuration via Form / FormElement ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -All of the `typeAhead*` (except `typeAheadLdap`) and `ldap*` parameter can be specified either in +All of the `typeAhead*` (except `typeAheadLdap`, `typeAheadInitialSuggestion`) and `ldap*` parameter can be specified either in *Form.parameter* or in *FormElement.parameter*. SQL @@ -3548,7 +3550,7 @@ SQL * *FormElement.parameter*: - * *typeAheadSql* = `SELECT ... AS 'id', ... AS 'value' FROM ... WHERE name LIKE ? OR firstName LIKE ? LIMIT 100` + * *typeAheadSql* = ``SELECT ... AS 'id', ... AS 'value' FROM ... WHERE name LIKE ? OR firstName LIKE ? LIMIT 100`` * If there is only one column in the SELECT statement, that one will be used and there is no dict (key/value pair). * If there is no column `id` or no column `value`, then the first column becomes `id` and the second column becomes `value`. @@ -3556,11 +3558,17 @@ SQL * The value, typed by the user, will be replaced on all places where a `?` appears. * All `?` will be automatically surrounded by '%'. Therefore wildcard search is implemented: `... LIKE '%<?>%' ...` - * *typeAheadSqlPrefetch* = `SELECT firstName, ' ', lastName FROM Person WHERE id = ?` + * *typeAheadSqlPrefetch* = ``SELECT firstName, ' ', lastName FROM Person WHERE id = ?`` * If the query returns several results, only the first one is returned and displayed. * If the query selects multiple columns, the columns are concatenated. + * *typeAheadInitialSuggestion* = ``{{!SELECT fr.id AS id, fr.name AS value FROM Fruit AS fr}}`` + + * Shows suggestions when the input element gets the focus, before the user starts to type anything. + * If given, *typeAheadMinLength* will be set to 0. + * Limit the number of rows via SQL ``... LIMIT ...`` clause. + LDAP ;;;; diff --git a/extension/Classes/Core/AbstractBuildForm.php b/extension/Classes/Core/AbstractBuildForm.php index 6127c2203..060a0aa84 100644 --- a/extension/Classes/Core/AbstractBuildForm.php +++ b/extension/Classes/Core/AbstractBuildForm.php @@ -737,7 +737,8 @@ abstract class AbstractBuildForm { // The following 'FormElement.parameter' will never be used during load (fe.type='upload'). FE_PARAMETER has been already expanded. $skip = [FE_SQL_UPDATE, FE_SQL_INSERT, FE_SQL_DELETE, FE_SQL_AFTER, FE_SQL_BEFORE, FE_PARAMETER, - FE_FILL_STORE_VAR, FE_FILE_DOWNLOAD_BUTTON, FE_TYPEAHEAD_GLUE_INSERT, FE_TYPEAHEAD_GLUE_DELETE, FE_TYPEAHEAD_TAG_INSERT]; + FE_FILL_STORE_VAR, FE_FILE_DOWNLOAD_BUTTON, FE_TYPEAHEAD_GLUE_INSERT, FE_TYPEAHEAD_GLUE_DELETE, + FE_TYPEAHEAD_TAG_INSERT, FE_TYPEAHEAD_INITIAL_SUGGESTION]; // get current data record $primaryKey = $this->formSpec[F_PRIMARY_KEY]; @@ -1335,6 +1336,7 @@ abstract class AbstractBuildForm { } } + // Check if typeAhead[Tag] needs to build if ('' != ($typeAheadUrlParam = $this->typeAheadBuildParam($formElement))) { if (empty($formElement[FE_INPUT_TYPE])) { @@ -1345,6 +1347,14 @@ abstract class AbstractBuildForm { $formElement[FE_INPUT_AUTOCOMPLETE] = 'off'; // typeahead behaves better with 'autocomplete'='off' } + // Collect typeAhead initial suggestion + if (!empty($formElement[FE_TYPEAHEAD_INITIAL_SUGGESTION])) { +// $formElement[FE_TYPEAHEAD_MINLENGTH] = 0; // If a suggestion is defined: minLength becomes automatically 0 + $arr = $this->evaluate->parse($formElement[FE_TYPEAHEAD_INITIAL_SUGGESTION]); + $arr = $this->dbArray[$this->dbIndexData]->makeArrayDict($arr, TYPEAHEAD_SQL_KEY_NAME, API_TYPEAHEAD_VALUE, API_TYPEAHEAD_KEY, API_TYPEAHEAD_VALUE); + $attribute .= Support::doAttribute(DATA_TYPEAHEAD_INITIAL_SUGGESTION, json_encode($arr)); + } + $class .= ' ' . CLASS_TYPEAHEAD; $dataSip = $this->sip->queryStringToSip($typeAheadUrlParam, RETURN_SIP); $attribute .= Support::doAttribute(DATA_TYPEAHEAD_SIP, $dataSip); @@ -1364,12 +1374,8 @@ abstract class AbstractBuildForm { $formElement[FE_INPUT_TYPE] = 'hidden'; // Client: TAG handling expects the '$value' as a JSON string. - $kk = KeyValueStringParser::parse($value, PARAM_KEY_VALUE_DELIMITER, PARAM_LIST_DELIMITER, KVP_IF_VALUE_EMPTY_COPY_KEY); - $jj = ''; - foreach ($kk as $arrKey => $arrValue) { - $jj .= ',' . json_encode(["key" => $arrKey, "value" => $arrValue]); - } - $value = '[' . substr($jj, 1) . ']'; + $arr = KeyValueStringParser::parse($value, PARAM_KEY_VALUE_DELIMITER, PARAM_LIST_DELIMITER, KVP_IF_VALUE_EMPTY_COPY_KEY); + $value = OnArray::arrayToQfqJson($arr); } } diff --git a/extension/Classes/Core/Constants.php b/extension/Classes/Core/Constants.php index b9f905c46..ec8f54f37 100644 --- a/extension/Classes/Core/Constants.php +++ b/extension/Classes/Core/Constants.php @@ -859,6 +859,7 @@ const DATA_ENABLE_SAVE_BUTTON = 'data-enable-save-button'; const DATA_TYPEAHEAD_LIMIT = 'data-typeahead-limit'; const DATA_TYPEAHEAD_MINLENGTH = 'data-typeahead-minlength'; const DATA_TYPEAHEAD_PEDANTIC = 'data-typeahead-pedantic'; +const DATA_TYPEAHEAD_INITIAL_SUGGESTION = 'data-typeahead-initial-suggestion'; const DATA_TYPEAHEAD_TAG = 'data-typeahead-tags'; const DATA_TYPEAHEAD_TAG_DELIMITER = 'data-typeahead-tag-delimiters'; @@ -870,8 +871,6 @@ const CLASS_FORM_ELEMENT_EDIT = 'qfq-form-element-edit'; const CLASS_FORM_ELEMENT_AUTO_GROW = 'qfq-auto-grow'; const ATTRIBUTE_DATA_MAX_HEIGHT = 'data-max-height'; - - // BuildForm const SYMBOL_NEW = 'new'; const SYMBOL_EDIT = 'edit'; @@ -1240,6 +1239,7 @@ const FE_LDAP_USE_BIND_CREDENTIALS = F_LDAP_USE_BIND_CREDENTIALS; const FE_TYPEAHEAD_LIMIT = F_TYPEAHEAD_LIMIT; const FE_TYPEAHEAD_MINLENGTH = F_TYPEAHEAD_MINLENGTH; const FE_TYPEAHEAD_PEDANTIC = F_TYPEAHEAD_PEDANTIC; +const FE_TYPEAHEAD_INITIAL_SUGGESTION = 'typeAheadInitialSuggestion'; const FE_TYPEAHEAD_TAG = 'typeAheadTag'; const FE_TYPEAHEAD_TAG_DELIMITER = 'typeAheadTagDelimiter'; const FE_TYPEAHEAD_GLUE_INSERT = 'typeAheadGlueInsert'; diff --git a/extension/Classes/Core/Database/Database.php b/extension/Classes/Core/Database/Database.php index 12e3ff723..07ef4bd8d 100644 --- a/extension/Classes/Core/Database/Database.php +++ b/extension/Classes/Core/Database/Database.php @@ -8,13 +8,13 @@ namespace IMATHUZH\Qfq\Core\Database; - -use IMATHUZH\Qfq\Core\Store\Store; -use IMATHUZH\Qfq\Core\Helper\Logger; + use IMATHUZH\Qfq\Core\Helper\BindParam; +use IMATHUZH\Qfq\Core\Helper\HelperFile; use IMATHUZH\Qfq\Core\Helper\HelperFormElement; +use IMATHUZH\Qfq\Core\Helper\Logger; use IMATHUZH\Qfq\Core\Helper\OnArray; -use IMATHUZH\Qfq\Core\Helper\HelperFile; +use IMATHUZH\Qfq\Core\Store\Store; /** * Class Database @@ -932,10 +932,12 @@ class Database { } /** - * $arr will be converted to a two column array with keys $keyName1 and $keyName2. - * $arr might contain one or more columns. - * Only when $keyName1 and $keyName2 exist, those will be used. Else the first column becomes $keyName1 and the - * second becomes $keyName2. If there is only one column, that column will be doubled. + * $arr = [ 0 => [ $srcColumn1 => $value0_1, $srcColumn2 => $value0_2 ], 1 => [ $srcColumn1 => $value1_1, $srcColumn2 => $value1_2 ], ...] + * + * $arr will be converted to a two column array with keys $destColumn1 and $destColumn2. + * If $destColumn1 or $destColumn2 is empty, take $srcColumn1, $srcColumn2 as names. + * $arr might contain one or more columns. Only the first two columns are used. + * If there is only one column, that column will be doubled. * * @param array $arr * @param string $srcColumn1 @@ -947,6 +949,11 @@ class Database { */ public function makeArrayDict(array $arr, $srcColumn1, $srcColumn2, $destColumn1 = '', $destColumn2 = '') { + if ($arr == array() || $arr === null) { + return array(); + } + + // Set defaults if ($destColumn1 == '') { $destColumn1 = $srcColumn1; } @@ -955,12 +962,7 @@ class Database { $destColumn2 = $srcColumn2; } - $new = array(); - - if ($arr == array() || $arr === null) { - return array(); - } - + // Set final column names $row = $arr[0]; $keys = array_keys($row); if (count($row) < 2) { @@ -974,9 +976,11 @@ class Database { $column2 = $keys[1]; } + $new = array(); $row = array_shift($arr); while (null !== $row) { $new[] = [$destColumn1 => $row[$column1], $destColumn2 => $row[$column2]]; +// $new[] = [$destColumn1 => htmlentities($row[$column1], ENT_QUOTES), $destColumn2 => htmlentities($row[$column2], ENT_QUOTES)]; $row = array_shift($arr); } @@ -1028,8 +1032,7 @@ class Database { try { $this->playMultiQuery($query); - } - catch (\CodeException $e) { + } catch (\CodeException $e) { throw new \CodeException("Error playing $filename", ERROR_PLAY_SQL_FILE); } diff --git a/extension/Classes/Core/Helper/HelperFormElement.php b/extension/Classes/Core/Helper/HelperFormElement.php index 030ed969b..33c8dcd15 100644 --- a/extension/Classes/Core/Helper/HelperFormElement.php +++ b/extension/Classes/Core/Helper/HelperFormElement.php @@ -718,7 +718,6 @@ EOF; $attribute .= Support::doAttribute('required', 'required'); break; case FE_MODE_READONLY: -// $attribute .= Support::doAttribute($feMode, $feMode); $attribute .= Support::doAttribute('disabled', 'disabled'); break; default: diff --git a/extension/Classes/Core/Helper/OnArray.php b/extension/Classes/Core/Helper/OnArray.php index 17a8d5358..7e9be89d1 100644 --- a/extension/Classes/Core/Helper/OnArray.php +++ b/extension/Classes/Core/Helper/OnArray.php @@ -427,4 +427,27 @@ class OnArray { public static function getMd5(array $data) { return md5(implode($data)); } + + /** + * Converts a one dimensional array to JSON array. The 'key' and 'value' will names are hardcoded: + * + * Return: [ { 'key': $key[0], 'value': $value[0] }, { 'key': $key[1], 'value': $value[2] }, ... ] + * + * @param array $arr + * @param bool $flagHtmlEntity true|false + * @return string + */ + public static function arrayToQfqJson(array $arr, $flagHtmlEntity = false) { + + $json = ''; + foreach ($arr as $arrKey => $arrValue) { + if ($flagHtmlEntity) { + $arrKey = htmlentities($arrKey, ENT_QUOTES); + $arrValue = htmlentities($arrValue, ENT_QUOTES); + } + $json .= ',' . json_encode(["key" => $arrKey, "value" => $arrValue]); + } + + return '[' . substr($json, 1) . ']'; + } } \ No newline at end of file diff --git a/extension/Classes/Core/Helper/Support.php b/extension/Classes/Core/Helper/Support.php index 33f22db34..915642a7e 100644 --- a/extension/Classes/Core/Helper/Support.php +++ b/extension/Classes/Core/Helper/Support.php @@ -292,8 +292,8 @@ class Support { * * TinyMCE: Encoding JS Attributes (keys & values) for TinyMCE needs to be encapsulated in '"' instead of '\"'. * - * @param $str - * @param string $modeEscape + * @param string $str + * @param string $modeEscape ESCAPE_WITH_BACKSLASH | ESCAPE_WITH_HTML_QUOTE * * @return string * @throws \CodeException diff --git a/extension/Classes/Core/Store/FillStoreForm.php b/extension/Classes/Core/Store/FillStoreForm.php index 75c894eb0..144726696 100644 --- a/extension/Classes/Core/Store/FillStoreForm.php +++ b/extension/Classes/Core/Store/FillStoreForm.php @@ -175,7 +175,7 @@ class FillStoreForm { // The following will never be used during load (fe.type='upload'). $skip = [FE_SLAVE_ID, FE_SQL_UPDATE, FE_SQL_INSERT, FE_SQL_DELETE, FE_SQL_AFTER, FE_SQL_BEFORE, FE_PARAMETER, - FE_VALUE, FE_FILL_STORE_VAR, FE_TYPEAHEAD_GLUE_INSERT, FE_TYPEAHEAD_GLUE_DELETE, FE_TYPEAHEAD_TAG_INSERT]; + FE_VALUE, FE_FILL_STORE_VAR, FE_TYPEAHEAD_GLUE_INSERT, FE_TYPEAHEAD_GLUE_DELETE, FE_TYPEAHEAD_TAG_INSERT, FE_TYPEAHEAD_INITIAL_SUGGESTION]; $html = ''; $newValues = array(); -- GitLab