Commit 6e049ecd authored by Carsten  Rose's avatar Carsten Rose
Browse files

Refs #10145: QFQ fills data-typeahead-initial-suggestion

parent 5741fc8d
Pipeline #3331 passed with stages
in 4 minutes and 33 seconds
...@@ -3227,6 +3227,8 @@ See also at specific *FormElement* definitions. ...@@ -3227,6 +3227,8 @@ See also at specific *FormElement* definitions.
| | | (defaults to 00:00:00 if none entered). | | | | (defaults to 00:00:00 if none entered). |
+------------------------+--------+----------------------------------------------------------------------------------------------------------+ +------------------------+--------+----------------------------------------------------------------------------------------------------------+
| typeAheadLimit, | string | See `input-typeahead`_ | | typeAheadLimit, | string | See `input-typeahead`_ |
| typeAheadInitial | | |
| Suggestion, | | |
| typeAheadMinLength, | | | | typeAheadMinLength, | | |
| typeAheadSql, | | | | typeAheadSql, | | |
| typeAheadSqlPrefetch, | | | | typeAheadSqlPrefetch, | | |
...@@ -3540,7 +3542,7 @@ configured). ...@@ -3540,7 +3542,7 @@ configured).
Configuration via Form / FormElement 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*. *Form.parameter* or in *FormElement.parameter*.
   
SQL SQL
...@@ -3548,7 +3550,7 @@ SQL ...@@ -3548,7 +3550,7 @@ SQL
   
* *FormElement.parameter*: * *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 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`. * 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 ...@@ -3556,11 +3558,17 @@ SQL
* The value, typed by the user, will be replaced on all places where a `?` appears. * 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 '%<?>%' ...` * 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 returns several results, only the first one is returned and displayed.
* If the query selects multiple columns, the columns are concatenated. * 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 LDAP
;;;; ;;;;
   
......
...@@ -737,7 +737,8 @@ abstract class AbstractBuildForm { ...@@ -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. // 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, $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 // get current data record
$primaryKey = $this->formSpec[F_PRIMARY_KEY]; $primaryKey = $this->formSpec[F_PRIMARY_KEY];
...@@ -1335,6 +1336,7 @@ abstract class AbstractBuildForm { ...@@ -1335,6 +1336,7 @@ abstract class AbstractBuildForm {
} }
} }
// Check if typeAhead[Tag] needs to build
if ('' != ($typeAheadUrlParam = $this->typeAheadBuildParam($formElement))) { if ('' != ($typeAheadUrlParam = $this->typeAheadBuildParam($formElement))) {
if (empty($formElement[FE_INPUT_TYPE])) { if (empty($formElement[FE_INPUT_TYPE])) {
...@@ -1345,6 +1347,14 @@ abstract class AbstractBuildForm { ...@@ -1345,6 +1347,14 @@ abstract class AbstractBuildForm {
$formElement[FE_INPUT_AUTOCOMPLETE] = 'off'; // typeahead behaves better with 'autocomplete'='off' $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; $class .= ' ' . CLASS_TYPEAHEAD;
$dataSip = $this->sip->queryStringToSip($typeAheadUrlParam, RETURN_SIP); $dataSip = $this->sip->queryStringToSip($typeAheadUrlParam, RETURN_SIP);
$attribute .= Support::doAttribute(DATA_TYPEAHEAD_SIP, $dataSip); $attribute .= Support::doAttribute(DATA_TYPEAHEAD_SIP, $dataSip);
...@@ -1364,12 +1374,8 @@ abstract class AbstractBuildForm { ...@@ -1364,12 +1374,8 @@ abstract class AbstractBuildForm {
$formElement[FE_INPUT_TYPE] = 'hidden'; $formElement[FE_INPUT_TYPE] = 'hidden';
// Client: TAG handling expects the '$value' as a JSON string. // 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); $arr = KeyValueStringParser::parse($value, PARAM_KEY_VALUE_DELIMITER, PARAM_LIST_DELIMITER, KVP_IF_VALUE_EMPTY_COPY_KEY);
$jj = ''; $value = OnArray::arrayToQfqJson($arr);
foreach ($kk as $arrKey => $arrValue) {
$jj .= ',' . json_encode(["key" => $arrKey, "value" => $arrValue]);
}
$value = '[' . substr($jj, 1) . ']';
} }
} }
......
...@@ -859,6 +859,7 @@ const DATA_ENABLE_SAVE_BUTTON = 'data-enable-save-button'; ...@@ -859,6 +859,7 @@ const DATA_ENABLE_SAVE_BUTTON = 'data-enable-save-button';
const DATA_TYPEAHEAD_LIMIT = 'data-typeahead-limit'; const DATA_TYPEAHEAD_LIMIT = 'data-typeahead-limit';
const DATA_TYPEAHEAD_MINLENGTH = 'data-typeahead-minlength'; const DATA_TYPEAHEAD_MINLENGTH = 'data-typeahead-minlength';
const DATA_TYPEAHEAD_PEDANTIC = 'data-typeahead-pedantic'; 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 = 'data-typeahead-tags';
const DATA_TYPEAHEAD_TAG_DELIMITER = 'data-typeahead-tag-delimiters'; const DATA_TYPEAHEAD_TAG_DELIMITER = 'data-typeahead-tag-delimiters';
...@@ -870,8 +871,6 @@ const CLASS_FORM_ELEMENT_EDIT = 'qfq-form-element-edit'; ...@@ -870,8 +871,6 @@ const CLASS_FORM_ELEMENT_EDIT = 'qfq-form-element-edit';
const CLASS_FORM_ELEMENT_AUTO_GROW = 'qfq-auto-grow'; const CLASS_FORM_ELEMENT_AUTO_GROW = 'qfq-auto-grow';
const ATTRIBUTE_DATA_MAX_HEIGHT = 'data-max-height'; const ATTRIBUTE_DATA_MAX_HEIGHT = 'data-max-height';
// BuildForm // BuildForm
const SYMBOL_NEW = 'new'; const SYMBOL_NEW = 'new';
const SYMBOL_EDIT = 'edit'; const SYMBOL_EDIT = 'edit';
...@@ -1240,6 +1239,7 @@ const FE_LDAP_USE_BIND_CREDENTIALS = F_LDAP_USE_BIND_CREDENTIALS; ...@@ -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_LIMIT = F_TYPEAHEAD_LIMIT;
const FE_TYPEAHEAD_MINLENGTH = F_TYPEAHEAD_MINLENGTH; const FE_TYPEAHEAD_MINLENGTH = F_TYPEAHEAD_MINLENGTH;
const FE_TYPEAHEAD_PEDANTIC = F_TYPEAHEAD_PEDANTIC; const FE_TYPEAHEAD_PEDANTIC = F_TYPEAHEAD_PEDANTIC;
const FE_TYPEAHEAD_INITIAL_SUGGESTION = 'typeAheadInitialSuggestion';
const FE_TYPEAHEAD_TAG = 'typeAheadTag'; const FE_TYPEAHEAD_TAG = 'typeAheadTag';
const FE_TYPEAHEAD_TAG_DELIMITER = 'typeAheadTagDelimiter'; const FE_TYPEAHEAD_TAG_DELIMITER = 'typeAheadTagDelimiter';
const FE_TYPEAHEAD_GLUE_INSERT = 'typeAheadGlueInsert'; const FE_TYPEAHEAD_GLUE_INSERT = 'typeAheadGlueInsert';
......
...@@ -8,13 +8,13 @@ ...@@ -8,13 +8,13 @@
namespace IMATHUZH\Qfq\Core\Database; 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\BindParam;
use IMATHUZH\Qfq\Core\Helper\HelperFile;
use IMATHUZH\Qfq\Core\Helper\HelperFormElement; use IMATHUZH\Qfq\Core\Helper\HelperFormElement;
use IMATHUZH\Qfq\Core\Helper\Logger;
use IMATHUZH\Qfq\Core\Helper\OnArray; use IMATHUZH\Qfq\Core\Helper\OnArray;
use IMATHUZH\Qfq\Core\Helper\HelperFile; use IMATHUZH\Qfq\Core\Store\Store;
/** /**
* Class Database * Class Database
...@@ -932,10 +932,12 @@ class Database { ...@@ -932,10 +932,12 @@ class Database {
} }
/** /**
* $arr will be converted to a two column array with keys $keyName1 and $keyName2. * $arr = [ 0 => [ $srcColumn1 => $value0_1, $srcColumn2 => $value0_2 ], 1 => [ $srcColumn1 => $value1_1, $srcColumn2 => $value1_2 ], ...]
* $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 * $arr will be converted to a two column array with keys $destColumn1 and $destColumn2.
* second becomes $keyName2. If there is only one column, that column will be doubled. * 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 array $arr
* @param string $srcColumn1 * @param string $srcColumn1
...@@ -947,6 +949,11 @@ class Database { ...@@ -947,6 +949,11 @@ class Database {
*/ */
public function makeArrayDict(array $arr, $srcColumn1, $srcColumn2, $destColumn1 = '', $destColumn2 = '') { public function makeArrayDict(array $arr, $srcColumn1, $srcColumn2, $destColumn1 = '', $destColumn2 = '') {
if ($arr == array() || $arr === null) {
return array();
}
// Set defaults
if ($destColumn1 == '') { if ($destColumn1 == '') {
$destColumn1 = $srcColumn1; $destColumn1 = $srcColumn1;
} }
...@@ -955,12 +962,7 @@ class Database { ...@@ -955,12 +962,7 @@ class Database {
$destColumn2 = $srcColumn2; $destColumn2 = $srcColumn2;
} }
$new = array(); // Set final column names
if ($arr == array() || $arr === null) {
return array();
}
$row = $arr[0]; $row = $arr[0];
$keys = array_keys($row); $keys = array_keys($row);
if (count($row) < 2) { if (count($row) < 2) {
...@@ -974,9 +976,11 @@ class Database { ...@@ -974,9 +976,11 @@ class Database {
$column2 = $keys[1]; $column2 = $keys[1];
} }
$new = array();
$row = array_shift($arr); $row = array_shift($arr);
while (null !== $row) { while (null !== $row) {
$new[] = [$destColumn1 => $row[$column1], $destColumn2 => $row[$column2]]; $new[] = [$destColumn1 => $row[$column1], $destColumn2 => $row[$column2]];
// $new[] = [$destColumn1 => htmlentities($row[$column1], ENT_QUOTES), $destColumn2 => htmlentities($row[$column2], ENT_QUOTES)];
$row = array_shift($arr); $row = array_shift($arr);
} }
...@@ -1028,8 +1032,7 @@ class Database { ...@@ -1028,8 +1032,7 @@ class Database {
try { try {
$this->playMultiQuery($query); $this->playMultiQuery($query);
} } catch (\CodeException $e) {
catch (\CodeException $e) {
throw new \CodeException("Error playing $filename", ERROR_PLAY_SQL_FILE); throw new \CodeException("Error playing $filename", ERROR_PLAY_SQL_FILE);
} }
......
...@@ -718,7 +718,6 @@ EOF; ...@@ -718,7 +718,6 @@ EOF;
$attribute .= Support::doAttribute('required', 'required'); $attribute .= Support::doAttribute('required', 'required');
break; break;
case FE_MODE_READONLY: case FE_MODE_READONLY:
// $attribute .= Support::doAttribute($feMode, $feMode);
$attribute .= Support::doAttribute('disabled', 'disabled'); $attribute .= Support::doAttribute('disabled', 'disabled');
break; break;
default: default:
......
...@@ -427,4 +427,27 @@ class OnArray { ...@@ -427,4 +427,27 @@ class OnArray {
public static function getMd5(array $data) { public static function getMd5(array $data) {
return md5(implode($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
...@@ -292,8 +292,8 @@ class Support { ...@@ -292,8 +292,8 @@ class Support {
* *
* TinyMCE: Encoding JS Attributes (keys & values) for TinyMCE needs to be encapsulated in '&quot;' instead of '\"'. * TinyMCE: Encoding JS Attributes (keys & values) for TinyMCE needs to be encapsulated in '&quot;' instead of '\"'.
* *
* @param $str * @param string $str
* @param string $modeEscape * @param string $modeEscape ESCAPE_WITH_BACKSLASH | ESCAPE_WITH_HTML_QUOTE
* *
* @return string * @return string
* @throws \CodeException * @throws \CodeException
......
...@@ -175,7 +175,7 @@ class FillStoreForm { ...@@ -175,7 +175,7 @@ class FillStoreForm {
// The following will never be used during load (fe.type='upload'). // 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, $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 = ''; $html = '';
$newValues = array(); $newValues = array();
......
Markdown is supported
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