Commit d4614f8c authored by Carsten  Rose's avatar Carsten Rose

Refs #9517: TypeAhead - Mainly update Manual.rst

parent 7bf7d517
Pipeline #3304 passed with stages
in 3 minutes and 42 seconds
......@@ -3230,6 +3230,12 @@ See also at specific *FormElement* definitions.
| typeAheadMinLength, | | |
| typeAheadSql, | | |
| typeAheadSqlPrefetch, | | |
| typeAheadPedantic | | |
+------------------------+--------+----------------------------------------------------------------------------------------------------------+
| typeAheadTag, | | See `type_ahead_tag`_ |
| typeAheadGlueInsert, | | |
| typeAheadGlueDelete, | | |
| typeAheadTagInsert | | |
+------------------------+--------+----------------------------------------------------------------------------------------------------------+
| wrapRow | string | If specified, skip default wrapping (`<div class='col-md-?'>`). Instead the given string is used. |
+------------------------+--------+ |
......@@ -3560,49 +3566,124 @@ LDAP
See :ref:`LDAP_Typeahead`
.. _`input-editor`:
.. _`type_ahead_tag`:
Type Ahead Tag
""""""""""""""
This extends a TypeAhead input element to take more than one token (=tag) in the same input element. The user starts
typing and this searches a list defined by *typeAheadSql*. The user selects an element by clicking on it or by using one
of the *typeAheadTagDelimiter* characters (by default tab or comma). If a tag is selected, it will be visual separated
from the input cursor. Already selected tags can not be edited but removed (clicking on the x). Further tags can be selected.
Extend a TypeAhead input element to take more than one token (=tag) in the same input element.
This mode supports only *typeAheadSql* (no LDAP).
Usage: A user might choose one or more tags from a typeahead list (to minimize typos and to reuse already given tags).
*typeAhaedTag* support two modes:
The user starts typing and for each keypress *typeAheadSql* is searched for all matches. The user selects an element
by clicking on it or by using one of the *typeAheadTagDelimiter* key presses (by default tab or comma). If a tag is
selected, it will be visual separated from the input cursor. Already selected tags can not be edited but removed
(clicking on the x). Further tags can be added.
*typeAheadTag* support two different modes: a) *Tag* , b) *Glue*.
.. _`ta_mode_tag`:
Mode: Tag
;;;;;;;;;
Tags will be loaded and saved as a comma separated list. Maximum length of saved tags is limit by
the size of the column (incl. separator).
* Mode *tag*: Default. User selected tags will be given as a comma separated list. Maximum of saved records is limit by
the size of the column.
* Mode *glue*: Activated by set *sqlInsertGlue*. Details see `ta_mode_glue`_
Additional arguments needed for *typeAheadTag*:
* *FormElement.parameter*:
* *typeAheadTag* = [0|1] - Default 0 (=off), existence or =1 switches the mode *typeAheadTag* on.
* *typeAheadTagDelimiter* = List of ASCII codes to separate tags during input. Default '9,44' (tab and comma).
* *sqlInsertTag* = {{INSERT INTO tags (name) VALUES (?)}}
* *sqlInsertGlue* = {{INSERT INTO glue (name) VALUES (?)}}
* *FromElement.value* = Used in glue mode {{!SELECT
.. _'ta_mode_glue':
.. _`ta_mode_glue`:
Mode: Glue
;;;;;;;;;;
Instead of saving the tag value (mode *tag*), save references (=glue) to the tags.
For each selected tag a glue record, pointing to the tag, is created.
The *Glue* mode will be activated by setting *FormElement.parameter.typeAheadGlueInsert* with a corresponding SQL statement.
Glue records will be created or deleted, as the user select or deselect tags. Processing of those Glue records will be done
after the primary form record has been written and before any after*-action FormElements will be processed.
*FormElement.name* should **not** point to a column in the form primary table. Instead a free name should be used for the *typeAhead*
FormElement.
In *FormElement.value* select all references and the shown values:
The maximum number of tags is not limited - but take care to size the FormElement big enough (*FormElement.maxLength*) to
show all tags.
On *Form load* (to show already assigned tags) a comma separated list has to be given in *FormElement.value*, based on
the previously saved Glue records. The string format is identically to the one used in mode *Tag*.
Extra parameter for mode = *Tag* :
* *FormElement.parameter*:
{{!SELECT gl.id AS id, t.value AS value FROM Glue AS gl, Tag as t WHERE gl.recordId={{id:R}} AND gl.idTag=t.id}}
* *typeAheadTagInsert* = {{INSERT INTO Tag (....) VALUES (...)}} - Only needed with *typeAheadPedantic=0*.
* *typeAheadGlueInsert* = {{INSERT INTO glueTag (...) VALUES (...)}}
* *typeAheadGlueDelete* = {{DELETE FROM glueTag WHERE ...}}
The maximum number of tags is not limited.
**Example**:
If the user removes a tag, the corresponding glue record will be deleted. QFQ detects such records by comparing the initial
dataset of FormElement.value (looking for missing tags on form save).
Table *Person* with some records.
Table *Fruit* with a list of fruits.
Table *FruitPerson* with glue records.
Usage: assign favourite fruits to a person. The fruits are the tags, the glue records will assign the fruits to a person.
The form will be open with a person record and has only one FormElement.
* Form.name=personFavouriteFruits
* Form.title=Person Favourite Fruits
* Form.primaryTable = Person
* FormElement[1].name = myFavoriteFruits
* FormElement[1].type = Text
* FormElement[1].value = {{SELECT GROUP_CONCAT( CONCAT(f.id, ':', f.name) ORDER BY f.name) FROM FruitPerson AS fp, Fruit AS f WHERE fp.pId={{id:R}} AND fp.fruitId=f.id ORDER BY f.name}}
* FormElement[1].parameter:
* typeAheadTag = 1
* typeAheadSql = SELECT f.id, f.name AS value FROM Fruit AS f WHERE f.name LIKE ?
* typeAheadMinLength = 1
* typeAheadGlueInsert = {{INSERT INTO FruitPerson (pId, fruitId) VALUES ({{id:R}}, {{tagId:V}} ) }}
* typeAheadGlueDelete = {{DELETE FROM FruitPerson WHERE pId={{id:R}} AND fruitId={{tagId:V}} }}
Explanation:
* On form load, without any assigned tags (=fruits), *FormElement.value* will be empty.
* The User will assign three fruits: Apple, Banana, Lemon.
* On form save, QFQ does:
* compares the old tag assigment (empty) with the new tag assigment (3 elements).
* for each new assigned tag:
* the *tagId* and *tagValue* will be stored in STORE_VAR (that's the one selected by the user and defined
via *typeAheadSql*)
* *typeAheadGlueInsert* will be fired (with the replaced variable *{{tagId:V}}*).
* The user loads the person favourite fruit form again (same user).
* *FormElement.value* will now be: ``1:Apple,3:Banana,10:Lemon``.
* The user removes 'Banana' and adds 'Orange'.
* On form save, QFQ does:
* compares the old tag assigment (3 elements) with the new tag assigment (also 3 elements, but different).
* for each new assigned tag:
* the *tagId* and *tagValue* will be stored in STORE_VAR.
* *typeAheadGlueInsert* will be fired (with the replaced variable *{{tagId:V}}*).
* for each removed assigned tag:
* the *tagId* and *tagValue* will be stored in STORE_VAR.
* *typeAheadGlueDelete* will be fired (with the replaced variable *{{tagId:V}}*).
.. _`input-editor`:
Type: editor
^^^^^^^^^^^^
......@@ -3622,7 +3703,6 @@ Type: editor
* Top: *toolbar* - by default visible.
* Bottom: *statusbar* - by default hidden, exception: *min_height* and *max_height* are given via size parameter.
* The default setting in *FormElement.parameter* is::
editor-plugins=code link lists searchreplace table textcolor textpattern visualchars
......
......@@ -1245,8 +1245,6 @@ const FE_TYPEAHEAD_TAG_DELIMITER = 'typeAheadTagDelimiter';
const FE_TYPEAHEAD_GLUE_INSERT = 'typeAheadGlueInsert';
const FE_TYPEAHEAD_GLUE_DELETE = 'typeAheadGlueDelete';
const FE_TYPEAHEAD_TAG_INSERT = 'typeAheadTagInsert';
const FE_TYPEAHEAD_TAG_TABLE = 'typeAheadTagTable';
const FE_TYPEAHEAD_SQL = 'typeAheadSql';
const FE_TYPEAHEAD_SQL_PREFETCH = 'typeAheadSqlPrefetch';
......
......@@ -135,10 +135,11 @@ class Evaluate {
* @param string $line
* @param string $sqlMode ROW_IMPLODE | ROW_REGULAR | ... - might be overwritten in $line by '{{!...'
* @param int $recursion
*
* @param array $debugStack
* @param string $foundInStore
* @return array|mixed|null|string
*
* @return array|mixed|null|string - in case of INSERT: last_insert_id()
*
* @throws \CodeException
* @throws \DbException
* @throws \UserFormException
......
......@@ -347,6 +347,7 @@ class Save {
*/
private function typeAheadDoTagGlue(array $fe) {
// Update 'glue' records?
if (($fe[FE_TYPEAHEAD_TAG] ?? '0') == '0' || (!isset($fe[FE_TYPEAHEAD_GLUE_INSERT]) && !isset($fe[FE_TYPEAHEAD_TAG_INSERT]))) {
return;
}
......@@ -369,6 +370,16 @@ class Save {
foreach ($result as $id => $value) {
$this->store->setVar(VAR_TAG_ID, $id, STORE_VAR);
$this->store->setVar(VAR_TAG_VALUE, $value, STORE_VAR);
if ($id == 0) {
if (empty($fe[FE_TYPEAHEAD_TAG_INSERT])) {
throw new \UserFormException("Missing 'typeAheadTagInsert'", ERROR_MISSING_REQUIRED_PARAMETER);
}
// Create tag
$id = $this->evaluate->parse($fe[FE_TYPEAHEAD_TAG_INSERT]);
$this->store->setVar(VAR_TAG_ID, $id, STORE_VAR);
}
// Create glue
$this->evaluate->parse($fe[FE_TYPEAHEAD_GLUE_INSERT]);
}
......
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