Form
General
Important
Primary key: QFQ expect that each table, which will be loaded into a form, contains a primary key called id. That one should be autoincrement. It’s not necessary to create a FormElement id in a form.
Forms will be created by using the Form Editor on the Typo3 frontend (HTML form).
The Form editor itself consist of two predefined QFQ forms: form and formElement - these forms are regular updated during installation of newer QFQ versions.
Every form consist of a) a Form record and b) multiple FormElement records.
A form is assigned to a table. Such a table is called the primary table for this form.
Forms can roughly categorized into:
Simple form: the form acts on one record, stored in one table.
The form will create necessary SQL commands for insert, update and delete (only primary record) automatically.
Advanced form: the form acts on multiple records, stored in more than one table.
Fields of the primary table acts like a simple form, all other fields have to be specified with action/afterSave records.
Multi form: (multi-form) the form acts simultaneously on more than one record. All records use the same FormElements.
The FormElements are defined as a regular simple / or advanced form, plus an SQL Query, which selects and iterates over all records. Those records will be loaded at the same time.
Delete form: any form can be used as a form to Delete Record.
Form mode: The parameter ‘r’ (given via URL or via SIP) decide if the form is in mode:
New:
Missing parameter ‘r’ or ‘r=0’
On form load, no record is displayed.
Saving the form creates a new primary record.
E.g.: http://example.com/home&form=Person or http://example.com/home&form=Person&r=0
Edit:
Parameter ‘r>0’
On form load, the specified record (<tablename>.id= <r>) will be loaded and displayed.
Saving the form will update the existing record.
E.g.: http://example.com/home&form=Person&r=123
Providing additional parameter:
Often, it is necessary to store additional, for the user not visible, parameter in a record. See Form Magic.
Display a form:
Create a QFQ tt_content record on a Typo 3 page.
Inside the QFQ record: form = <formname>. E.g.:
Static: form = Person
Dynamic 1: form = {{form:SE}} (the left form is a keyword for QFQ, the right form is a variable name)
Dynamic 2: form = {{SELECT f.name FROM Form AS f WHERE f.id=…}} (the left form is a keyword for QFQ, the right form is a variable name)
With the Dynamic option, it’s easily possible to use one Typo3 page and display different forms on that specific page.
If a form is edited using the JSON form editor then a backup of the previous version is saved. See Form As File.
How to get started
Create a new page in your Typo3 Backend.
Create a content element of type ‘QFQ Element’.
Insert
file=_formEditor
Create a new form either by ‘+’ or the ‘+json’ button.
Button ‘+’ opens the form editor.
Button ‘+json’ open the json form editor
Define a name for the form.
Save the form.
A json structure without any FormElements are shown.
Edit form either in the FormEditor or as a json text.
Form process order
Depending on form load / save or record delete, different tasks are performed. Processing is divided into:
Native FormElements like: input, select list, checkbox, radio, ….
upload elements are categorized as native FormElement, but will be processed later.
pill, fieldset and subrecord elements are only processed during form load, they do not impact save or delete.
Action FormElement like before…, after…, sendmail …
Each FormElement has an order.
Native FormElements which ‘name’:
corresponds to a column in the form primary table: are grouped together in one insert or update query.
do not correspond to a column in the primary table: needs an explicit defined Action FormElement to be handled.
FormElement processing order:
Record locking
Support for record locking is given with mode:
exclusive: user can’t force a write.
Including a timeout (default 15 mins recordLockTimeoutSeconds in Configuration) for maximum lock time.
advisory: user is only warned, but allowed to overwrite.
none: no bookkeeping about locks.
Details:
For ‘new’ records (r=0) there is no locking at all.
The record locking protection is based on the tablename and the record id. Different Forms, with the same primary table, will be protected by record locking.
Action-FormElements updating non primary table records are not protected by ‘record locking’: the QFQ record locking is NOT 100%.
The ‘record locking’ mode will be specified per Form. If there are multiple Forms with different modes, and there is already a lock for a tablename / record id pair, the most restrictive will be applied.
A user opens a form: starting with the first change of content, a record lock will be acquired in the background. If the lock is denied (e.g. another user already owns a lock on the record) an alert is shown. This means: the lock timeout counts from the first change, not from the last modification on the form.
If a timeout expires, the lock becomes invalid. During the next change in a form, the lock is acquired again.
A lock is assigned to a record of a table. Multiple forms, with the same primary table, uses the same lock for a given record.
If a user tries to delete a record and another user already owns a lock on that record, the delete action is denied.
If there are different locking modes in multiple forms, the most restricting mode applies for the current lock.
If the same user opens the same recording in different tabs or browsers, the user has the possibility to skip a lock.
Exclusive
An existing lock on a record forbids any write action on that record. Exception: locks owned by the same user might be overwritten.
Advisory
An existing lock on a record informs the user that another user is currently working on that record. Nevertheless, writing is allowed.
None
No locking at all.
Form Settings
Name |
Description |
|---|---|
Name |
Unique and speaking name of the Form. Form will be identified by this name. Use only |
Title |
Title, shown on/above the form. Title |
Note |
Personal editor notes. |
Table |
Primary table of the form. Tablename |
Primary Key |
Primary key of the indicated table. Only needed if != ‘id’. Primary Key |
Required Parameter NEW |
Name of required SIP parameter to create a new record (r=0), separated by comma. ‘#’ as comment delimiter. See Required Parameter New|Edit |
Required Parameter EDIT |
Name of required SIP parameter to edit an existing record (r>0), separated by comma. ‘#’ as comment delimiter. See Required Parameter New|Edit |
Permit New |
‘sip, logged_in, logged_out, always, never’ (Default: sip): See permitNew & permitEdit |
Permit Edit |
‘sip, logged_in, logged_out, always, never’ (Default: sip): See permitNew & permitEdit |
Permit REST |
See REST |
Escape Type Default |
See Escape/Action class. |
Show button |
‘new, delete, close, save’ (Default: ‘new,delete,close,save’): Shown named buttons in the upper right corner of the form. See showButton |
Forward Mode |
‘auto | close | no | url | url-skip-history | url-sip | url-sip-skip-history’ (Default: auto): See Forward: Save / Close. |
Forward (Mode) Page |
|
labelAlign |
Label align (default/left/center/right)/ Default: ‘default’ (defined by Config). |
Parameter |
Misc additional parameters. See Form.parameter. |
BS Label Columns |
The bootstrap grid system is based on 12 columns. The sum of bsLabelColumns, bsInputColumns and bsNoteColumns should be 12. These values here are the base values for all FormElements. Exceptions per FormElement can be specified per FormElement. Default: label=col-md-3, input=col-md-6, note=col-md-3. See Form Layout. |
BS Input Columns |
|
BS Note Columns |
|
multiMode |
NOT IMPLEMENTED - ‘none, horizontal, vertical’ (Default ‘none’) |
multiSql |
NOT IMPLEMENTED - Optional. SQL Query which selects all records to edit. |
multiDetailForm |
NOT IMPLEMENTED - Optional. Form to open, if a record is selected to edit (double click on record line) |
multiDetailFormParameter |
NOT IMPLEMENTED - Optional. Translated Parameter submitted to detail form (like subrecord parameter) |
multiFormWrap |
By default (if empty) wraps the form in a HTML table. Alternative is to use CSS grid. See Layout: CSS grid. |
multiFormCellWrap |
By default (if empty) wraps each input in <td>. If ‘multiFormWrap’ is given but multiFormCellWrap is empty, than the default is <div>. See Layout: CSS grid. |
permitNew & permitEdit
Depending on r, the following access permission will be taken:
r=0 - permitNew
r>0 - permitEdit
Option |
Description |
|---|---|
sip |
The parameter ‘form’ and ‘r’ must be supplied via SIP or hard coded in the QFQ tt_content record. |
logged_in |
The form will only be shown if the current User is logged in as a FE User |
logged_out |
The form will only be shown if the current User is not logged in as a FE User |
always |
No access restriction, the form will always be shown |
never |
The form will never be shown |
sip
is always the preferred way. With ‘sip’ it’s not necessary to differ between logged in or not, cause the SIP only exist and is only valid, if it’s created via QFQ/report earlier. This means ‘creating’ the SIP implies ‘access granted’. The grant will be revoked when the QFQ session is destroyed - this happens when a user logs out or the web browser is closed.
logged_in / logged_out: for forms which might be displayed without a SIP, but maybe on a protected or even unprotected page. The option is probably not often used.
always: such a form is always allowed to be loaded.
permitNew=always: Public accessible forms (e.g. for registration) will allow users to fill and save the form.
permitEdit=always: Public accessible forms will allow users to update existing data. This is dangerous, cause the URL parameter (recordId) ‘r’ might be changed by the user (URL manipulating) and therefore the user might see and/or change data from other users. The option is probably not often used.
never: such a form is not allowed to be loaded.
permitNew=never: Public accessible forms. It’s not possible to create new records.
permitEdit=none: Public accessible forms. It’s not possible to update records.
Required Parameter New|Edit
Comma separated list of variable names. On form load, an error message will be shown in case of missing parameters. The parameters must be given by SIP.
The list of required parameter has to be defined for New (r=0, create a new record) and for Edit (r>0, edit existing record).
Optional a comment might be attached after the parameter definition.
E.g.:
New: grId, pId # Always specify a person, grId2
Edit: pId
Forward: Save / Close
Forward (=forwardMode)
After the user presses Save, Close, Delete or New, different actions are possible where the browser redirects to.
auto (default) - the QFQ browser Javascript logic, decides to stay on the page or to force a redirection to a previous page. The decision depends on:
Close goes back (feels like close) to the previous page. Note: if there is no history, QFQ won’t close the tab, instead a message is shown.
Save stays on the current page.
close - goes back (feels like close) to the previous page. Note: if there is no history, QFQ won’t close the tab, instead a message is shown.
no - no change, the browser remains on the current side. Close does not close the page. It just triggers a save if there are modified data.
url - the browser redirects to the URL or T3 page named in Forward URL / Page. Independent if the user presses save or close.
url-skip-history - same as url, but the current location won’t saved in the browser history.
url-sip - like url, but any given parameter will be SIP encoded. Only useful if url points to current web instance.
url-sip-skip-history - like url-sip, but skips the Browser history.
Only with Forward == url | url-skip-history, the definition of Forward URL / Page becomes active.
Forward URL / Page (=forwardPage)
Format: [<url>] or [<mode>|<url>]
<url>:
http://www.example.com/index.html?a=123#bottom
website.html?a=123#bottom
<T3 page slug>&a=123#bottom, ?id=<T3 page id>&a=123#bottom
{{SELECT …}}
<mode>|<url>
<mode> - Valid keywords are as above: auto|close|no|url|url-skip-history|url-sip|url-sip-skip-history
Specifying the mode in forwardPage overwrites formMode (but only if formMode is url…).
Also regular QFQ statements like {{var}} or {{SELECT ...}} are possible in forwardPage. This is useful to dynamically
redirect to different targets, depending on user input or any other dependencies.
If a forwardMode ‘url…’ is specified and there is no forwardPage, QFQ falls down to auto mode.
On a form, the user might click ‘save’ or ‘save,close’ or ‘close’ (with modified data this leads to ‘save,close’). The CLIENT submit_reason shows the user action:
{{submit_reason:CE:alnumx}}= save or save,close
Example forwardPage
{{SELECT IF('{{formModeGlobal:S}}'='requiredOff', 'no', 'auto') }}{{SELECT IF('{{submit_reason:CE:alnumx}}'='save', 'no', 'url'), '|http://example.com' }}
Type: combined dynamic mode & URL/page
Syntax: forwardPage=<mode>|<page>
forwardPage={{SELECT IF(a.url='', 'no', 'url'), '|', a.url FROM Address AS a }}
Title
A static title optionally enhanced by QFQ variables
{{SELECT ..}}or...AS _linkAlign elements to the right by wrapping
<span class="pull-right">
Tablename
Database table name where the form saves the data.
FormElement with a name equal to column of such table are loaded/saved automatically.
Primary Key
Column name in primary table which represents the primary key.
By default:
idChange with care - due to massive implication on several places changing is tricky.
Form.parameter
Each parameter has to be on a single line. Exception: SQL statements enclosed by ‘{{’ / ‘}}’ can use multiple lines.
Lines will be trimmed - leading and trailing spaces will be removed.
If a leading and/or trailing space is needed, escape it: \ hello world \ > ‘ hello world ‘.
Lines starting with a ‘#’ are treated as a comment and will not be parsed. Such lines are treated as ‘empty lines’.
The comment sign can be escaped with \ .
If a parameter is defined multiple time, the last one is the final one.
Name |
Type |
Description |
|---|---|---|
dbIndex |
int |
Database credential index, given via QFQ credentials to let the current Form operate on the database. |
btnTopLeft |
Set the regular to the left side form. |
|
btnTopRight |
Set the regular to the right side of form. This is default |
|
btnTopWrap |
string |
Button Wrapping Overwrite default from Configuration |
btnTopLeftWrap |
string |
Button Wrapping Overwrite default from Configuration |
btnTopRightWrap |
string |
Button Wrapping Overwrite default from Configuration |
btnTopLeftOrder |
string |
Button Order Overwrite default from Configuration |
btnTopRightOrder |
string |
Button Order Overwrite default from Configuration |
btnFooterWrap |
string |
Button Wrapping Overwrite default from Configuration |
btnFooterLeftWrap |
string |
Button Wrapping Overwrite default from Configuration |
btnFooterLeftOrder |
string |
Button Order Overwrite default from Configuration |
btnFooterRightOrder |
string |
Button Order Overwrite default from Configuration |
btnCustom[?] |
string |
Creates a custom button using the AS _link notation. Replace “?” with the desired key name reference.
This can be used in |
bsColumns |
string |
Wrap the whole form in ‘<div class=”col-md-.. col-lg-..”>. See Custom field width. |
maxVisiblePill |
int |
Show pills upto <maxVisiblePill> as button, all further in a drop-down menu. Eg.: maxVisiblePill=3. |
class |
string |
HTML div with given class, surrounding the whole form. Eg.: class=container-fluid. |
classTitle |
string |
CSS class inside of the title div. Default ‘qfq-form-title’. |
classPill |
string |
HTML div with given class, surrounding the pill title line. |
classBody |
string |
HTML div with given class, surrounding all FormElement. |
extraDeleteForm |
string |
Name of a form which specifies how to delete the primary record and optional slave records. |
data-pattern-error |
string |
Pattern violation: Text for error message used for all FormElements of current form. |
data-required-error |
string |
Required violation: Text for error message used for all FormElements of current form. |
data-match-error |
string |
Match violation: Text for error message used for all FormElements of current form. |
data-error |
string |
If none specific is defined: Text for error message used for all FormElements of current form. |
buttonOnChangeClass |
string |
Color for save button after user modified some content or current form. E.g.: ‘btn-info alert-info’. |
ldapServer |
string |
FQDN Ldap Server. E.g.: directory.example.com. |
ldapBaseDn |
string |
E.g.: ou=Addressbook,dc=example,dc=com. |
ldapAttributes |
string |
List of attributes to fill STORE_LDAP with. E.g.: cn, email. |
ldapSearch |
string |
E.g.: |
ldapTimeLimit |
int |
Maximum time to wait for an answer of the LDAP Server. |
typeAheadLdap |
Enable LDAP as ‘Typeahead’ data source. |
|
typeAheadLdapSearch |
string |
Regular LDAP search expression. E.g.: |
typeAheadLdapValuePrintf |
string |
Value formatting of LDAP result, per entry. E.g.: ‘%s / %s / %s’, mail, room number, telephone number |
typeAheadLdapIdPrintf |
string |
Key formatting of LDAP result, per entry. E.g.: ‘%s’, mail |
typeAheadLdapSearchPerToken |
Split search value in token and OR-combine every search with the individual tokens. |
|
typeAheadLimit |
int |
Maximum number of entries. The limit is applied to the server (LDAP or SQL) and the Client. |
typeAheadMinLength |
int |
Minimum number of characters which have to typed to start the search. |
mode |
string |
The value readonly will activate a global readonly mode of the form - the user can’t change any data. See Form mode global |
activateFirstRequiredTab |
digit |
0: off, 1: (default) - with formModeGlobal=requiredOffButMark bring pill to front on save. See Form mode global |
enterAsSubmit |
digit |
0: off, 1: Pressing enter in a form means save and close. Takes default from Configuration. |
submitButtonText |
string |
Show a save button at the bottom of the form, with <submitButtonText> . See submitButtonText. |
saveButtonActive |
0: off, 1: Make the ‘save’-button active on Form load (instead of waiting for the first user change). The save button is still ‘gray’ (record not dirty), but the user can click ‘save’. |
|
saveButtonText |
string |
Overwrite default from Configuration |
saveButtonTooltip |
string |
Overwrite default from Configuration |
saveButtonClass |
string |
Overwrite default from Configuration |
saveButtonGlyphIcon |
string |
Overwrite default from Configuration |
closeButtonText |
string |
Overwrite default from Configuration |
closeButtonTooltip |
string |
Overwrite default from Configuration |
closeButtonClass |
string |
Overwrite default from Configuration |
closeButtonGlyphIcon |
string |
Overwrite default from Configuration |
deleteButtonText |
string |
Overwrite default from Configuration |
deleteButtonTooltip |
string |
Overwrite default from Configuration |
deleteButtonClass |
string |
Overwrite default from Configuration |
deleteButtonGlyphIcon |
string |
Overwrite default from Configuration |
newButtonText |
string |
Overwrite default from Configuration |
newButtonTooltip |
string |
Overwrite default from Configuration |
newButtonClass |
string |
Overwrite default from Configuration |
newButtonGlyphIcon |
string |
Overwrite default from Configuration |
extraButtonInfoClass |
string |
Overwrite default from Configuration |
extraButtonInfoMinWidth |
string |
See extraButtonInfo |
fillStoreVar |
string |
Fill the STORE_VAR with custom values. See Store: VARS - V. |
showIdInFormTitle |
string |
Overwrite default from Configuration |
formSubmitLogMode |
string |
Overwrite default from Configuration |
sessionTimeoutSeconds |
int |
Overwrite default from Configuration. See FE-User: Session timeout seconds. |
maxFileSize |
int |
Overwrite default from Configuration |
maxImageDimension |
string |
Overwrite default from Configuration expected format 4000x4000. |
requiredPosition |
int |
See requiredPosition |
clearMe |
0 / 1 |
Overwrite default from Configuration. Show a small ‘x’ in every input or textarea to clear field. |
rememberLastPill |
0 / 1 |
Overwrite default from Configuration. On form load, bring last used pill to front |
doNotLogColumn |
string |
Overwrite default from Configuration. Comma separated list of Form-Element names. |
showHistory |
0 / 1 |
Overwrite default from Configuration. Show history button in form. |
showHistoryTitle |
string |
Add a new string to the history view title. |
showHistoryFeUserSet |
string |
Comma separated list of feUser names. History view will only show history logs from users list or all. |
recentLog |
0 / 1 |
Overwrite default from Configuration. For each user form open, create a record in table Recent. |
fieldsetClass |
string |
Overwrite default from Configuration. |
fieldsetCss |
string |
Add custom css to FE.fieldset. fieldsetCss = color: white;backgroundColor: black; |
btnPreviousNextSql |
string |
Query that selects records which are then accessible by the previous/next buttons. See btnPreviousNextSql |
btnPreviousNextLoop |
digit |
0: off, 1: Allow to loop through the records (from last to first and vice versa). |
btnPreviousNextWrap |
string |
Wrap the buttons in custom HTML. Default: ‘<span class=”pull-right”><div class=”btn-group” role=”group”>’ |
subrecordEmpty |
string |
Overwrite default from Configuration. Options: ‘show:custom text example’,’hide’,’mute’ |
message |
string |
Execute page or message (nchan) after save or delete. E.g.: P_<pageSlug>[|M_<channel>:<action>:<payload>|M_<channel2>:<action2>]. See message |
column[?] |
string |
Sometimes some specific values should set to a table column which does not have to be a FormElement. The given value will be saved to the named table column from the primary table during save. |
Example in field Form.parameter:
maxVisiblePill = 5 class = container-fluid classBody = qfq-form-right
message
Two types of executions are defined via prefix:
P_- Execute a page with given slug. Define it without slashes.M_- Send a message to a channel with specific action and payload. (Work in progress. Not yet implemented.)
For Installation see Form message on save/delete.
Best practice is to define a folder with shortcuts in Typo3. Those shortcuts should not use subpaths in the slug.
Message channel: Comming soon…
FormElements
Each form contains one or more FormElement.
The FormElements are divided in three categories:
Ordering and grouping: Native FormElements and Container-Elements (both with feIdContainer=0) will be ordered by ‘ord’.
Inside of a container, all nested elements will be displayed.
Do not configure a FormElement for the primary index column id.
Additional options to a FormElement will be configured via the FormElement.parameter field (analog to Form.parameter for Forms ).
See also: FormElement.parameter
Class: Container
Pills are containers for ‘fieldset’ and / or ‘native’ FormElements.
Fieldsets are containers for ‘native’ FormElements.
TemplateGroups are containers for ‘fieldset’ and / or ‘native’ FormElements.
Type: fieldset
Native FormElements can be assigned to a fieldset.
FormElement settings:
name: technical name, used as HTML identifier.
label: Shown title of the fieldset.
mode:
show: all child elements will be shown.
required: all child elements are also set to ‘required’.
readonly: all child elements are also set to ‘readonly’.
hidden:
The fieldset is invisible.
The FormElements within the fieldset still exist, but are not reachable for the user via UI.
parameter:
fieldsetClass: Overwrite default from Form.parameter.fieldsetClass
fieldsetCss: Add your own css to fieldSet Form.parameter.fieldsetCss
Type: pill (tab)
Pill is synonymous for a tab and looks like a tab.
If there is at least one pill defined:
every native FormElement needs to be assigned to a pill or to a fieldset.
every fieldset needs to be assigned to a pill.
Mode:
show: all child elements will be shown.
required: same as ‘show’. This mode has no other meaning than ‘show’.
readonly: technically it’s like HTML/CSS disabled.
The pill title is shown, but not clickable.
The FormElements on the pill still exist, but are not reachable for the user via UI.
hidden:
The pill is invisible.
The FormElements on the pill still exist, but are not reachable for the user via UI.
Note: Independent of the mode, all child elements are always rendered and processed by the client/server.
Pills are ‘dynamicUpdate’ aware. title and mode are optional recalculated during ‘dynamicUpdate’.
FormElement settings:
name: technical name, used as HTML identifier.
label: Label shown on the corresponding pill button or inside the drop-down menu.
mode:
show, required: regular mode. The pill will be shown.
readonly: the pill and it’s name is visible, but not clickable.
hidden: the pill is not shown at all.
modeSql:
type: pill
feIdContainer: 0 - Pill’s can’t be nested.
tooltip: Optional tooltip on hover. Especially helpful if the pill is set to readonly.
parameter:
maxVisiblePill: <nr> - Number of Pill-Buttons shown. Undefined means unlimited. Excess Pill buttons will be displayed as a drop-down menu.
Type: templateGroup
TemplateGroups will be used to create a series of grouped (by the given templateGroup) FormElements.
FormElements can be assigned to a templateGroup. These templateGroup will be rendered upto n-times. On ‘form load’ only a single (=first) copy of the templateGroup will be shown. Below the last copy of the templateGroup an ‘add’-button is shown. If the user click on it, an additional copy of the templateGroup is displayed. This can be repeated up to templateGroup.maxLength times. Also, the user can ‘remove’ previously created copies by clicking on a remove button near beside every templateGroup. The first copy of a templateGroup can’t be removed.
FormElement settings:
label: Shown in the FormElement-editor container field.
maxLength: Maximum number of copies of the current templateGroup. Default: 5.
bsLabelColumn, bsInputColumn, bsNoteColumn: column widths for row with the ‘Add’ button.
parameter:
tgAddClass: Class of the ‘add’ button. Default: btn btn-default.
tgAddText: Text shown on the button. Default: Add.
tgRemoveClass: Class of the ‘remove’ button. Default: btn btn-default.
tgRemoveText: Text shown on the button. Default: Remove.
tgClass: Class wrapped around every copy of the templateGroup. E.g. the class qfq-child-margin-top adds a margin between two copies of the templateGroup. Default: empty
Multiple templateGroups per form are allowed.
The name of the native FormElements, inside the templateGroup, which represents the effective table columns, uses the placeholder %d. E.g. the columns grade1, grade2, grade3 needs a FormElement.name = grade%d. The counting will always start with 1. The placeholder %d can also be used in the FormElement.label
Example of styling the Add/ Delete Button: Icons Template Group
Column: primary record
If the columns <name>%d are real columns on the primary table, saving and delete (=empty string) are done automatically. E.g. if there are up to five elements grade1, …, grade5 and the user inputs only the first three, the remaining will be set to an empty string.
Column: non primary record
Additional logic is required to load, update, insert and/or delete slave records.
Load
On each native FormElement of the templateGroup define an SQL query in the FormElement.value field. The query has to select all values of all possible existing copies of the FormElement - therefore the exclamation mark is necessary. Also define the order. E.g. FormElement.value:
{{!SELECT street FROM Address WHERE personId={{id}} ORDER BY id}}
Insert / Update / Delete
Add an action record, type=’afterSave’, and assign the record to the given templateGroup.
In the parameter field define:
slaveId = {{SELECT id FROM Address WHERE personId={{id}} ORDER BY id LIMIT %D,1}}
sqlHonorFormElements = city%d, street%d
sqlUpdate = {{UPDATE Address SET city='{{city%d:FE:alnumx:s}}', street='{{street%d:FE:alnumx:s}}' WHERE id={{slaveId}} LIMIT 1}}
sqlInsert = {{INSERT INTO Address (`personId`, `city`, `street`) VALUES ({{id}}, '{{city%d:FE:alnumx:s}}' , '{{street%d:FE:alnumx:s}}') }}
sqlDelete = {{DELETE FROM Address WHERE id={{slaveId}} LIMIT 1}}
The slaveId needs attention: the placeholder %d starts always at 1. The LIMIT directive starts at 0 - therefore use %D instead of %d, cause %D is always one below %d - but can only be used on the action element.
Class: Native
Fields:
Name |
Type |
Description |
|---|---|---|
Enabled |
enum(‘yes’|’no’) |
Process the current FormElement |
Dynamic Update |
enum(‘yes’|’no’) |
In the browser, FormElements with “dynamicUpdate=’yes’” will be updated depending on user input. Dynamic Update |
Encryption |
enum(‘yes’|’no’) |
Encryption for the user input can be activated. Encryption |
Name |
string |
|
Label |
string |
Label of FormElement. Depending on layout model, left or on top of the FormElement Additional label description can be added by wrapping in HTML tag ‘<small>’ |
Mode |
enum(‘show’, ‘readonly’, ‘required’, ‘hidden’ ) |
Show: regular user input field. This is the default.
Required: User has to specify a value. Typically, an <empty string> represents ‘no value’.
Readonly: User can’t change. Data is not saved, except for FormElement with ‘processReadOnly’
Hidden: FormElement is not visible.
|
Mode sql |
SELECT statement with a value like in mode |
A value given here overwrites the setting from mode. Most useful with Dynamic Update.
E.g.: |
Class |
enum(‘native’, ‘action’, ‘container’) |
Details below. |
Type |
enum(‘checkbox’, ‘date’, ‘time’, ‘datetime’, ‘extra’, ‘text’, ‘editor’, ‘annotate’, ‘imageCut’, ‘note’, ‘password’, ‘radio’, ‘select’, ‘subrecord’, ‘upload’, ‘fieldset’, ‘pill’, ‘beforeLoad’, ‘beforeSave’, ‘beforeInsert’, ‘beforeUpdate’, ‘beforeDelete’, ‘afterLoad’, ‘afterSave’, ‘afterInsert’, ‘afterUpdate’, ‘afterDelete’, ‘sendMail’, ‘chat’) |
|
Encode |
‘none’, ‘specialchar’, ‘single tick’ |
|
Check Type |
enum(‘auto’, ‘alnumx’, ‘digit’, ‘numerical’, ‘email’, ‘pattern’, ‘allbut’, ‘all’) |
See: Sanitize class |
Check Pattern |
‘regexp’ |
Type: Editor: If $checkType==’pattern’: pattern to match |
Order |
string |
Display order of FormElements (‘order’ is a reserved keyword) |
labelAlign |
left |
Label align (default/left/center/right)/ Default: ‘default’ (defined by Form). |
Size |
string |
Depends on the FormElement type. E.g. visible length (and height) of input element Type: text. Might be omitted, depending on the chosen form layout. Format: <width>[,<(min) height>[,<max height]] (in characters). |
BS Label Columns |
string |
Number of bootstrap grid columns. By default empty, value inherits from the form. See Custom field width |
BS Input Columns |
string |
|
BS Note Columns |
string |
|
Label / Input / Note |
enum(…) |
Switch on/off opening|closing of bootstrap form classes |
Maxlength |
string |
Maximum characters for input. Type: checkbox |
Note |
string |
Note of FormElement. Depending on layout model, right or below of the FormElement. Report syntax can also be used, see FormElement: ‘Report’ notation. |
Tooltip |
text |
Display this text as tooltip on mouse over. Type: pill (tab) |
Placeholder |
string |
Text, displayed inside the input element in light grey. |
value |
text |
Default value: See FormElement.value |
sql1 |
text |
SQL query. See individual FormElement. |
Parameter |
text |
Might contain misc parameter. See FormElement.parameter |
Deleted |
string |
‘yes’|’no’. |
FormElement.value
By default (and in most forms) this field is empty: QFQ will load/show the corresponding column (= <name>) data
of the specified record (SELECT <name> FROM <primary table> WHERE id={{r:S}}) on form load.
In case a new record should be created (r=0), it’s sometimes wished to customize a default value:
{{<name>:R:::<custom default value>}}
Explanation: STORE_RECORD (=R) is filled with the values of the current record. If there is no record (r=0), than the default is taken.
For non primary records, this is the place to load an existing value. E.g. we’re on a ‘Person’ detail form and would like to edit, on the same form, a corresponding person email address (which is in a separate table):
{{SELECT a.email FROM Address AS a WHERE a.pId={{id:R0}} ORDER BY a.id LIMIT 1}}
Report syntax can also be used, see FormElement: ‘Report’ notation.
FormElement: ‘Report’ notation
The FE fields ‘value’ and ‘note’ understand the Report syntax. Nested SQL queries as well as links with SIP encoding
are possible. To distinguish between ‘Form’ and ‘Report’ syntax, the first line has to be #!report:
#!report
{
sql = SELECT ...
}
{
sql = SELECT ...
{
sql = SELECT ...
}
}
FormElement.parameter
The following applies to the fields Form.parameter and FormElement.parameter:
Each parameter has to be on a single line. Exception: SQL statements enclosed by ‘{{’ / ‘}}’ can use multiple lines.
Lines will be trimmed - leading and trailing spaces will be removed.
If a leading and/or trailing space is needed, escape it: \ hello world \ > ‘ hello world ‘.
Lines starting with a ‘#’ are treated as a comment and will not be parsed. Such lines are treated as ‘empty lines’.
The comment sign can be escaped with \ .
If a parameter is defined multiple time, the last one is the final one.
More details might be given at specific FormElement.
Name |
Note |
|---|---|
acceptZeroAsRequired |
0|1 - Accept a ‘0’ as a valid input. Default ‘0’ (=0 is not a valid input) |
autofocus |
See autofocus |
capture, accept, maxFileSize, fileButtonText, fileDestination, fileTrash, fileTrashText, fileReplace, autoOrient, autoOrientCmd, autoOrientMimeType, chmodFile, chmodDir, slaveId, sqlBefore, sqlInsert, sqlUpdate, sqlDelete, sqlAfter, importToTable, importToColumns, importRegion, importMode, importType, importNamedSheetsOnly, importSetReadDataOnly, importListSheetNames, maxImageDimension, uploadAction, fileNoteTarget, fileNote, fileKey, |
See Type: upload See Type: upload |
checkBoxMode checked unchecked label2 itemList emptyHide emptyItemAtStart emptyItemAtEnd buttonClass checkBoxState |
|
dateFormat |
yyyy-mm-dd / dd.mm.yyyy |
data-pattern-error |
Pattern violation: Text for error message |
data-required-error |
Required violation: Text for error message |
data-match-error |
Match violation: Text for error message |
data-error |
Violation of ‘check-type’: Text for error message |
decimalFormat |
[precision,scale] Limits and formats input to a decimal number with the specified precision and scale. If no precision and scale are specified, the decimal format is pulled from the table definition. |
htmlAfter |
HTML Code wrapped after the complete FormElement |
htmlBefore |
HTML Code wrapped before the complete FormElement |
extraButtonLock |
[0|1] Show a ‘lock’ on the right side of the input element. See extraButtonLock |
extraButtonPassword |
[0|1] Show an ‘eye’ on the right side of the input element. See extraButtonPassword |
extraButtonInfo |
Text. Show an ‘i’ on the right side of the input element. See extraButtonInfo |
extraButtonInfoClass |
By default empty. Specify any class to be assigned to wrap extraButtonInfo |
extraButtonInfoMinWidth |
See extraButtonInfo |
editor-plugins, editor-toolbar, editor-statusbar, editor-forced_root_block, editor-extended_valid_elements, editor-content_css, editor-relative_urls, defaultImageClass, editorType, codemirror-mode, codemirror-lineNumbers, codemirror-lineWrapping, codemirror-tabSize, codemirror-styleActiveLine, codemirror-matchBrackets, codemirror-autoCloseBrackets, codemirror-keywords-qfq-base |
See Type: Editor |
fillStoreVar |
Fill the STORE_VAR with custom values. See Store: VARS - V. |
colorPickerConfig |
|
form, page, extraDeleteForm, detail, new, subrecordTableClass, subrecordTableAttribute, subrecordColumnTitleEdit, subrecordColumnTitleDelete, subrecordAppendSql, subrecordAppendClass, subrecordAppendForm, subrecordAppendExtraDeleteForm, subrecordAppendEmptyText, orderInterval, dndTable, orderColumn |
See Type: subrecord |
min s/d/n |
Minimum and/or maximum allowed values for input field. Can be used for numbers, dates, or strings. Always use the international format ‘yyyy-mm-dd[ hh:mm[:ss]] |
max s/d/n |
|
processReadOnly |
[0|1] By default FE’s with type=’readonly’ are not processed during ‘save’. This option forces to process them during ‘save’ as well. See processReadOnly. |
retype, retypeLabel, retypeNote |
See Type: text |
characterCountWrap, hideZero inputType, step, textareaResize, htmlAllow |
See Type: text |
emptyMeansNull |
Applies to all native FormElement types (input, checkbox, radio, select, …). See Type: text |
showSeconds |
0|1 - Shows the seconds on form load. Default: 0 |
showZero |
0|1 - Empty timestamp: ‘0’(default) - nothing shown, ‘1’ - the string ‘0000-00-00 00:00:00’ is displayed |
timeIsOptional |
0|1 - Used for datetime input. 0 (default): Time is required - 1: Entering a time is optional (defaults to 00:00:00 if none entered). |
typeAheadLimit, typeAheadInitialSuggestion, typeAheadMinLength, typeAheadSql, typeAheadSqlPrefetch, typeAheadPedantic |
See Type Ahead |
typeAheadTag, typeAheadGlueInsert, typeAheadGlueDelete, typeAheadGlueUpdate, typeAheadTagInsert, typeAheadTagDelimiter |
See Type Ahead Tag |
wrapRow |
If specified, skip default wrapping (<div class=’col-md-?’>). Instead the given string is used. |
wrapLabel |
|
wrapInput |
|
wrapNote |
|
trim |
By default, whitespace is trimmed. To disable, use ‘trim=none’. You can also specify custom trim characters: ‘trim=\ ‘ only trims spaces. |
sqlValidate, expectRecords, alert, qfqLog, requiredList |
|
dataReference |
Optional. See Application Test |
requiredPosition |
See requiredPosition. |
indicateRequired |
By default, indicate ‘required’ by an asterisk. indicateRequired=0 will hide the asterisk. Default: 1 |
minWidth |
|
clearMe |
0 (off)|1(on) - Overwrite default from Form.parameter.clearMe or Configuration. Show a small ‘x’ in input or textarea fields to clear the input. |
defaultValue |
Set custom default value. If not set, db column default value will be taken. |
toggleInput |
Checkbox near beside text inputs, which show or hide the input element. See :ref:’toggleInputElement’ |
s/d/n: string or date or number.
slaveId, sqlBefore, sqlAfter, …
Native FormElements
Like ‘input’, ‘checkbox’, …
autofocus
The first FormElement with this attribute will get the focus after form load. If there is no such attribute given to any FormElement, the attribute will be automatically assigned to the first editable FormElement.
To disable ‘autofocus’ on a form, set ‘autofocus=0’ on the first editable FormElement.
Note: If there are multiple pills defined on a form, only the first pill will be set with ‘autofocus’.
processReadOnly
By default FormElements with mode=’readonly’ are not processed. In most use cases, this is the expected behaviour: An element which can’t be modified by the user, should not be written during a save. Exceptions might be given, like: FE is enabled by dynamic update, modified by the user, deactivated again and than the record is saved.
minWidth - Checkbox / Radio
Checkbox and Radio Elements, shown in plain horizontal mode, receives a minWidth to align them. The default is 80px and might be defined per Form or per FormElement.
toggleInput
Checkbox near beside text inputs, which show or hide the input element.
FE.parameter.toggleInput = [<optional label>]On form load, if FE.value is empty (string) or 0 (number) or 0000-00-00 (date), the input element is hidden, the checkbox unchecked.
A click on the checkbox toggles the visibility of the input element.
Toggle Input is not supported for elements that contain multiple inputs, such as checkboxes, radio groups, or complex form elements like file uploads or rich text editors.
Option: When the toggleInput parameter is used and the element is hidden during form save, a default value will be submitted instead of the user input.
If no value is provided, QFQ will automatically assign a default based on the element’s type.
Note: When using a <select> element, the default value does not need to be one of the available <option> values — it will still be accepted and saved as the submitted value.
requiredPosition
By default, input elements with Mode=required will be displayed with a ‘red asterisk’ right beside the label. The position of the ‘red asterisk’ can be chosen via the parameter field:
requiredPosition = label-left|label-right|input-left|input-right|note-left|note-right
The default is ‘label-right’.
The definition can be set per Form (=affects all FormElements) or per FormElement.
Type: checkbox
Checkboxes can be rendered in mode:
single:
One column in a table corresponds to one checkbox.
The value for statuses checked and unchecked are free to choose.
This mode is selected, if a) checkBoxMode = single, or b) checkBoxMode is missing and the number of fields of the column definition is <3.
FormElement.parameter:
checkBoxMode = single (optional)
checked = <value> (optional, the value which represents ‘checked’)
If checked is empty or missing: If type = ‘enum’ or ‘set’, get first item of the definition. If type = string, get default.
unchecked = <value> (optional, the value which represents ‘unchecked’)
If unchecked is empty or missing: If type = ‘enum’ or ‘set’, get second item of checked. If type = ‘string’, get ‘’.
label2 = <value> (Text right beside checkbox) (optional)
multi:
One column in a table represents multiple checkboxes. This is typically useful for the column type set.
The value for status checked are free to choose, the value for status unchecked is always the empty string.
Each field key (or the corresponding value from the key/value pair) will be rendered right beside the checkbox.
FormElement.parameter
checkBoxMode = multi
itemList - E.g.:
itemList=red,blue,orangeitemList=1:red,2:blue,3:orangeIf ‘:’ or ‘,’ are part of key or value, it needs to escaped by \ . E.g.: itemList=1:red\: (with colon),2:blue\, (with comma),3:orange`
FormElement.sql1 =
{{!SELECT id, value FROM SomeTable}}FormElement.maxlength - vertical or horizontal alignment:
Value: ‘’, 0, 1 - The check boxes will be aligned vertical.
Value: >1 - The check boxes will be aligned horizontal, with a linebreak every ‘value’ elements.
multiState:
This mode enables a single checkbox to have multiple states rather than a simple binary (checked/unchecked).
Each column in a table corresponds to a checkbox with multiple selectable values.
The values and visual styles (color, icon, etc.) can be customized for each individual state. The icon must be a Font Awesome icon
The first value in the list is the default and will always be applied, even if the checkbox is not clicked.
Clicking the checkbox cycles through the available states in order.
Supports ENUM/SET DB columns but will use default styles for the checkbox.
FormElement.parameter
checkBoxMode = multiState
checkBoxState =
VALUE:LABEL:FONT AWESOME ICON:ICON COLOR:BACHGROUND COLOR,...
Example:
checkBoxState = maybe:Maybe:fa-question:black:white, yes:Yes:fa-check:white:#2196f3, no:No:fa-times:black:#F93827State
Maybe state (fa-question, black icon on white)
Yes state (fa-check, white icon on blue)
No state (fa-times, black icon on red)
FormElement.parameter:
emptyHide: Existence of this item hides an entry with an empty string. This is useful for e.g. Enums, which have an empty entry, but the empty value should not be selectable.
emptyItemAtStart: Existence of this item inserts an empty entry at the beginning of the select list.
emptyItemAtEnd: Existence of this item inserts an empty entry at the end of the select list.
buttonClass: Instead of the plain HTML checkbox fields, Bootstrap buttons. are rendered as checkbox elements. Use one of the following classes:
btn-default (default, grey),
btn-primary (blue),
btn-success (green),
btn-info (light blue),
btn-warning (orange),
btn-danger (red).
With a given buttonClass, all buttons (=radios) are rendered horizontal. A value in FormElement.maxlength has no effect.
No preselection:
If a form is in ‘new’ mode and if there is a default value configured on a table column, such a value is shown by default. There might be situations, where the user should be forced to select a value (e.g. specifying the gender). An unwanted default value can be suppressed by specifying an explicit definition on the FormElement field value:
{{<columnName>:RZ}}For existing records the shown value is as expected the value of the record. For new records, it’s the value 0, which is typically not one of the ENUM / SET values and therefore nothing is selected.
Type: colorPicker
The colorPicker element allows users to pick a color using the JavaScript library Pickr: https://github.com/Simonwep/pickr
QFQ uses the “classic” Pickr theme, and provides support for various configuration options.
Configuration
Use the parameter colorPickerConfig to customize the color picker. The syntax is:
colorPickerConfig = option:value|option:value|...
Special handling is applied for the swatches option: - Multiple color swatches are separated by + - Example: swatches:red+blue+#00ff00
Example full config:
colorPickerConfig = default:#ff0000|swatches:red+blue+green|hex:false|clear:true
Supported Options
Option (default) |
Description |
|---|---|
default (none) |
Default color if no color is selected (e.g., #ff0000) |
swatches (none) |
List of predefined color swatches (e.g., red+blue+#00ff00) |
preview (false) |
Show/hide preview area (true/false) |
opacity (false) |
Allow editing alpha channel (true/false) |
hex (true) |
Show HEX input (true/false) |
rgba (false) |
Show RGBA input (true/false) |
input (true) |
Show input field (true/false) |
clear (true) |
Show clear/reset button (true/false) |
Notes
The classic Pickr theme is always used. Theme changes via config are silently ignored.
default is only used when no color has been selected or saved.
The value is saved as a hex representative of the color selected (#ffffff) and the check type is all even if not configured.
Type: date
Range:
'1000-01-01'to'9999-12-31'or'0000-00-00'See: http://dev.mysql.com/doc/refman/5.5/en/datetime.htmlOptional:
FormElement.parameter.dateFormat UI input format. Examples:
YYYY-MM-DD,DD.MM.YYYY,MM.YYYY,YYYY,DDFormElement.parameter.dateFormatSave Specifies exactly what string is written to the database, regardless of frontend display.
Supported placeholders (user-input tokens and dynamic keywords):
dd→ day from user input (01–31)mm→ month from user input (01–12)yyyy→ year from user input (four digits)dd-today→ current day (e.g., “24”)dd-last→ last day of current month (e.g., “30” or “31”)mm-today→ current month (e.g., “06”)yyyy-today→ current year (e.g., “2025”)
Warning
Any user-input token (
dd,mm,yyyy) used indateFormatSavemust also appear indateFormat. Otherwise, a validation error is thrown.
Token Reference
Token |
Meaning |
|---|---|
|
Day from user input (01–31) Month from user input (01–12) Year from user input (4-digit) |
|
Current day Last day of month (28–31) Current month (01–12) Current year (e.g., 2025) |
Examples of valid configurations:
Fixed day
dateFormat = MM.YYYY dateFormatSave = 01.mm.yyyy # Input: "03.2025" → Stored: "2025-03-01"
Last day of month + selected month + static year
dateFormat = MM dateFormatSave = dd-last.MM.0001 # Input: "07" → Stored: "0001-07-31"
User provides day and month; current year is filled automatically
dateFormat = DD.MM dateFormatSave = dd.mm.yyyy-today # Input: "12.10." → Stored: "2025-10-12"
Year only; current day/month auto-filled
dateFormat = YYYY dateFormatSave = dd-today.mm-today.yyyy # Input: "2025" → Stored: "2025-06-24"
Invalid format example (will throw an exception)
dateFormat = MM.YYYY dateFormatSave = dd.MM.YYYY # Error: "dd" is not allowed because it is not part of dateFormat
Save process (simplified):
Split
dateFormatSaveinto tokens (e.g.,['dd-last','mm','yyyy'])Replace dynamic tokens using calculated values (e.g., current month, last day)
Map user input tokens (if provided), or apply defaults
Concatenate values into a single string (e.g.,
31.03.2025)Convert final result into ISO format:
YYYY-MM-DD
Special cases handled:
Supports partial input (e.g.
YYYY) with dynamic fill-inFixed/static combinations (e.g.
01.mm.yyyy)Invalid dates (e.g.
31.02.2025) trigger validation error
Default widget:
datetimepickerFor more options see Datetimepicker
Type: datetime
Range:
'1000-01-01 00:00:00'to'9999-12-31 23:59:59'or'0000-00-00 00:00:00'See: http://dev.mysql.com/doc/refman/5.5/en/datetime.htmlOptional:
FormElement.parameter.dateFormat UI input format. Supports time component as well. Examples:
YYYY-MM-DDDD.MM.YYYY HH:mmYYYY-MM-DDTHH:mm(browser format)
FormElement.parameter.dateFormatSave Defines what is stored in the DB. Time is appended if included in the input.
FormElement.parameter.showSeconds =
0 | 1If set to1, supports input with seconds (HH:mm:ss)FormElement.parameter.showZero =
0 | 1If set to1, empty field shows0000-00-00 00:00:00; otherwise blankFormElement.parameter.dateTimePickerType If set to
browser, HTML5 input format like2025-02-15T08:30is parsed automaticallyNote: Fixed values in ``dateFormatSave``
In addition to dynamic placeholders like
dd,mm, oryyyy, thedateFormatSaveparameter also supports fixed values. This allows static parts such as a predefined hour, day, or year to be hardcoded into the saved value.Examples:
dd.mm.yyyy 15:45→ always stores time as 15:45:00dd.mm.2001 08:00→ stores a fixed year and timedd.mm-today.yyyy 12:00→ combines dynamic month/day with a fixed time
Examples of valid configurations:
Full datetime
dateFormat = DD.MM.YYYY HH:mm dateFormatSave = dd.mm.yyyy # Input: "15.02.2025 14:30" → Stored: "2025-02-15 14:30:00"
Partial date + time, dynamic fallback
dateFormat = MM.YYYY HH:mm dateFormatSave = dd-today.mm.yyyy # Input: "03.2025 09:00" → Stored: "2025-03-24 09:00:00"
Fixed year and time
dateFormat = DD.MM dateFormatSave = dd.mm.2001 08:00 # Input: "05.06 08:00" → Stored: "2001-06-05 08:00:00"
Fixed time, dynamic date
dateFormat = DD.MM dateFormatSave = dd.mm.yyyy-today 15:45 # Input: "11.06" → Stored: "2025-06-11 15:45:00"
Fixed month from today, fixed time
dateFormat = DD dateFormatSave = dd.mm-today.yyyy 12:00 # Input: "02" → Stored: "2025-06-02 12:00:00" (if today is June 2025)
Save process (simplified):
Detect time part from input (
HH:mm[:ss]orTHH:mm)Apply dynamic token replacements
Fill missing date values with defaults
Convert to final format:
YYYY-MM-DD HH:MM[:SS]
$Special cases handled:
Optional time and seconds handling
Invalid input or structure throws exception
Time is added even if not stored explicitly
Supports mixing dynamic and fixed date/time values
Default widget:
datetimepickerFor more options see Datetimepicker
Type: extra
The element behaves like, and can be used as, a HTML hidden input element - with the difference & advantage, that the element never leaves the server and therefore can’t be manipulated by a user.
The following names are reserved and can’t be used to name ‘extra’ FormElements: ‘id’, ‘type’, ‘L’.
The element is not transferred to the the browser.
The element can be used to define / pre calculate values for a column, which do not already exist as a native FormElement.
The element is build / computed on form load and saved alongside with the SIP parameter of the current form.
The element is not recalculated during save - it’s stored during ‘Form Load’ inside the current form SIP handle.
Access the value without specifying any store (default store priority is sufficient).
Type: text
General input for any text.
By default, the maximum length of input data is automatically restricted by the underlying database column.
HTML decides between one line input (=Input text) and multiline input (=Textarea).
FormElement.size = [<width>[,<min height (lines)>[,<max height (pixel)>]]]
The parameter is optional and controls the behaviour of the input / textarea element.
The <width> is counted in ‘characters’.
But: the visible width of an input element is defined by the Bootstrap column width (and not the width given here). Finally: the value here is meaningless. Nevertheless it has to be given for future compatibility.
<min-height>:
Counted as ‘lines’.
If not set the height is treated as 1.
A <min-height> of 1 forces an one line input. Exception: <max-height> > 0 enables auto-grow.
<max-height>:
Controls the auto-grow-Mode.
Counted in ‘pixel’.
If not set it becomes the default of 350 pixels.
If > 0, the auto-grow mode is activated and the height of the textarea will be dynamically updated up to <max-height>.
If = 0, the auto-grow mode is disabled.
FormElement.maxLength: Maximum number of characters allowed in a text field.
FormElement.parameter:
retype = 1 (optional): Current input element will be rendered twice. The form can only submitted if both elements are equal.
retypeLabel = <text> (optional): The label of the second element.
retypeNote = <text> (optional): The note of the second element.
characterCountWrap =
<span class="qfq-cc-style">Count: | </span>(optional). Displays a character counter below the input/textarea element.Also check the FormElement.parameter data-…-error to customize error messages shown by the validator.
hideZero = 0|1 (optional): with hideZero=1 a ‘0’ in the value will be replaced by an empty string.
emptyMeansNull = [0|1] (optional): with emptyMeansNull or emptyMeansNull=1 a NULL value will be written if the value is an empty string
inputType = number (optional). Typically the HTML tag ‘type’ will be ‘text’, ‘textarea’ or ‘number’ (detected automatically). If necessary, the HTML tag ‘type’ might be forced to a specific given value.
step = Step size of the up/down buttons which increase/decrease the number of in the input field. Optional. Default 1. Only useful with inputType=number (defined explicit via inputType or detected automatically).
textareaResize = 0|1 (optional). Be default = 1 (=on). A textarea element is resizable by the user.
htmlAllow = p,br,img,table,u,ol,b,h2,h3,h5,sup (optional). By default every html tag is allowed. Allow only specific html tags. This option is only useful in case encode is not specialchar (cause otherwise there are no HTML tags). If any of the following main tags (before colon) are given, the associated tags will be added automatically:
table: td, tr, th, tbody, thead
ol,ul: li
b: strong
u,ins,del,s: span
List of most used html tags: a,b,br,div,em,h1,h2,h3,h4,h5,h6,hr,i,img,table,ol,ul,p,pre,q,section,small,span,strong,sub,sup,title,u
Type Ahead
Activating typeahead functionality offers an instant lookup of data and displaying them to the user, while the user is typing, a drop-down box offers the results. As datasource the regular SQL connection or a LDAP query can be used. With every keystroke (starting from the typeAheadMinLength characters), the already typed value will be transmitted to the server, the lookup will be performed and the result, upto typeAheadLimit entries, are displayed as a drop-down box.
FormElement.parameter:
typeAheadLimit = <number>. Max numbers of result records to be shown. Default is 20.
typeAheadMinLength = <number>. Minimum length to type before the first lookup starts. Default is 2.
Depending of the typeahead setup, the given FormElement will contain the displayed value or id (if an id/value dict is configured).
Configuration via Form / FormElement
All of the typeAhead* (except typeAheadLdap, typeAheadInitialSuggestion) and ldap* parameter can be specified either in Form.parameter or in FormElement.parameter.
SQL
For example, check Typeahead: SQL.
FormElement.parameter:
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.
The query will be fired as a ‘prepared statement’.
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 ‘%<?>%’ …
Starting with QFQ V24.12.1, the curly braces around the SQL can be specified (as on all other usages in QFQ).
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.
Starting with QFQ V24.12.1, the curly braces around the SQL can be specified (as on all other usages in QFQ).
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.Starting with QFQ V24.12.1, the curly braces around the SQL can be specified (as on all other usages in QFQ).
LDAP
Type Ahead Tag
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).
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.
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).
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).
Mode: Glue
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.
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:
typeAheadTagInsert = {{INSERT INTO Tag (tagName..) VALUES ({{tagValue:V}}..)}} - Only needed with typeAheadPedantic=0.
typeAheadGlueInsert = {{INSERT INTO glueTag (…) VALUES (…)}}
typeAheadGlueDelete = {{DELETE FROM glueTag WHERE …}}
typeAheadGlueUpdate = {{UPDATE glueTag SET … WHERE …}}
Example:
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)) 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 AS ‘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}} }}
typeAheadGlueUpdate = {{UPDATE FruitPerson SET personName = ‘{{firstName:FRE:alnumx}}’ WHERE 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}}).
Type: Editor
TinyMCE (https://www.tiny.cloud/tinymce/, free version) is used as the QFQ Rich Text Editor.
The content will be saved as HTML code in the database.
Important
FormElement.encode: To save HTML code, incl. HTML tags (bold, table, lists, …), the htmlspecialchar encoding can’t be used, cause the HTML tags loose their meaning. Therefore single tick or none is necessary.
FormElement.checktype
all: The only useful setting for Editor. HTML tags might contain
% ' " < >and so on. This is dangerous due of potential inserted malicious code! But there is no other option, cause the HTML tags are required.
All configuration and plugins will be configured via the ‘parameter’ field. Just prepend the word ‘editor-’ in front of each TinyMCE keyword. Check possible options under:
Bars:
Top: menubar - by default hidden.
Top: toolbar - by default visible.
Bottom: statusbar - by default hidden, exception: min_height and max_height are given via size parameter.
FormElement.maxLength: Maximum number of characters allowed in a text field.
The default setting in FormElement.parameter is:
editor-plugins=code link lists searchreplace table visualchars visualblocks image fullscreen editor-toolbar=code searchreplace undo redo | styles link table | bullist numlist outdent indent | link table fullscreen editor-menubar=false editor-statusbar=false
To deactivate the surrouding <p> tag, configure in FormElement.parameter:
editor-forced_root_block = falseThis might have impacts on the editor. See https://www.tinymce.com/docs/configure/content-filtering/#forced_root_block
To add a Default image style to uploaded images, configure in FormElement.parameter:
defaultImageClass = [small (50%)|medium (75%)|large (125%)]To add a default image border to uploaded images, configure it in FormElement.parameter. The default will only be applied once when the image is inserted.:
editor-imageBorderDefault = 'border-width: 2px; border-style: solid;'Set ‘extended_valid_elements’ to enable HTML tags and their attributes. Example:
editor-extended_valid_elements = span[class|style]Set ‘editor-relative_urls’ to allow relative paths. Example:
editor-relative_urls = trueSet ‘editor-content_css’ to use a custom CSS to style elements inside the editor. Example:
editor-content_css = fileadmin/custom.cssCertain settings use an object or array as value (JSON must be valid). Example:
editor-invalid_styles={"td":"width height","th":"width height"} editor-image_class_list=[{"title":"None","value":""},{"title":"No border","value":"img_no_border"},{"title":"Green border","value":"img_green_border"}]
Set own base path for image upload. Default is fileadmin/imageUploadDir Example:
fileUploadPath = fileadmin/.../... # Also Supports unique filename feature where {{filenameUnique:V}} is not part of the imageUploadDir but the placeholder of the filename. fileUploadPath = fileadmin/.../{{filenameUnique:V}}
Important
When fileUploadPath points to a protected directory (e.g. fileadmin/protected/…), QFQ will automatically rewrite image sources using its internal syntax, such as:
{{ path/to/image.jpg AS link }}– a placeholder to be later replaced with a secure SIP URLor a direct reference to the Download API (e.g.,
typo3conf/ext/qfq/Classes/Api/download.php)
This ensures that image files are only accessible via secure, signed links.
However, this also means that when using such columns in a Report, the content must be evaluated so QFQ can process and rewrite the placeholders correctly.
For example:
SELECT column AS _text FROM ..
SELECT '{{text:RE}}'
This allows QFQ to detect and replace the {{ ... AS link }} syntax with the appropriate secure link at render time.
FormElement.size = <min_height>: in pixels, including top and bottom bars. E.g.: 300
Set ‘editorType’ to use highlighted syntax inside the editor like reports. Available: codemirror. Example:
editorType = codemirrorFor editorType = codemirror additional customization is possible by prepending codemirror- to the following keywords (list not exhaustive, see codemirror 5 documentation for details):
codemirror-mode = qfq (only mode that is packaged with qfq, other modes available, see codemirror5 docs) codemirror-lineNumbers = true codemirror-lineWrapping = true codemirror-tabSize = 2 codemirror-styleActiveLine = false codemirror-matchBrackets = true codemirror-autoCloseBrackets = true codemirror-keywords-qfq-base = space delimited custom list of keywords (see javascript/src/Helper/codemirror/qfq.js for possible keyword types)
FormElement.size = <min_height>,<max_height>: in pixels, including top and bottom bars. E.g.: 300,600
Define allowed html tags. TinyMCE settings will be overwritten if this parameter is set.
Following tags are not used from TinyMCE: u,del,ins,s. In this case use textDecoration to get comparable function and correct configuration. Example:
htmlAllow = p,br,h1,h3,table,b,textDecoration,ul,imgBy default every html tag is allowed. List with tags and their automatically associated tags Type: text
Type: annotate
Annotate image or text. Typically the image or text has been uploaded during a previous step. The annotation will be saved in FormElement.name column of the current record. The uploaded file itself will not be modified. The annotations can be shown in edit (and might be modified) or in readonly mode.
Two modes are available:
- grafic
A simple graphic editor to paint on top of the image (best by a tablet with pen or graphic tablet). The uploaded image is shown in the background. All drawings are saved as a JSON fabric.js data string. Supported file types: png, svg. PDF files can be easily divided into per page SVG files during upload - see Split PDF Upload
- text
Per code line, annotation(s) can be added. Different users can add individual annotations. A user can only edit the own annotations. The annotations are saved as QFQ internal JSON string.
Note
Drawing with fabric.js might produce a lot data. Take care the column type/size is big enough (>=64kB).
Grafic
An image, specified by FormElement.parameter.imageSource={{pathFileName}}, will be displayed in the background. On
form load, both, the image and an optional already given graphical annotations, will be displayed. The image is SIP
protected and will be loaded on demand.
Form.parameter
Attribute |
Value |
Description |
|---|---|---|
annotateType |
grafic |
grafic|text. Default is grafic. Select mode. |
imageSource |
<path filename> |
Background image. E.g. fileadmin/images/scan.png |
defaultPenColor |
<rgb hex value> | Pen default color, after loading the fabric element. Default is ‘0000FF’ (blue). |
|
Note
By using the the FormElement annotate, the JS code fabric.min.js and qfq.fabric.min.js has to be included. See Setup CSS & JS.
Code
Form.parameter
Attribute |
Value |
Description |
|---|---|---|
annotateType |
text |
grafic|text. Default is grafic. Select mode. |
textSource |
<path filename> |
Text file to annotate. |
annotateUserName |
<john doe> |
Will be shown at annotation line. |
annotateUserUid |
<123> |
Will be shown at annotation line. |
annotateUserAvatar |
Will be shown at annotation line. |
|
highlight |
auto |
off,auto,javascript,qfq,python,matlab |
Type: imageCut
Uploaded images can be cut or rotate via QFQ (via fabric.js). The modified image is saved under the given pathFileName.
The ‘value’ of the FormElement has to be a valid PathFileName to an image.
Valid image file formats are SVG, PNG, JPG, GIF.
Invalid or missing filenames results to a hidden ‘imageCut’ element.
Mode: ‘keepOriginal’
If FE.parameter.keepOriginal is not empty, mode keepOriginal is activated.
If the image has been modified, on the first form save, a backup of the original image is created (same diretory as the original).
Also, the meta data of the manipulation is saved in the named column (of the imageCut element). This column should be of type varchar(255) at least.
Meta data is a JSON sring like
{"scale":0.36496350364963503,"left":-106.41605839416059,"top":157,"rotation":0,"flipX":false,"flipY":false}On form load, imageCut loads the original (umodified) image and applies the latest modification. In this way, there is no information loss.
FormElement.parameter:
resizeWidth = <empty>|[width in pixel] - the final width of the modified image. If empty (or not given), no change.
keepOriginal = <empty>|[string] - By default: ‘.save’. If empty (no string given), don’t keep the original. If an extension is given and if there is not already a <pathFileName><.extension>, than the original file is to copied to it.
Type: inputCollectionJson
A visual configuration editor for structured JSON data. The data is stored as JSON, but displayed as a user-friendly mini-form with tabs, labels and descriptions. Ideal for application settings, configuration data, or any structured key-value data.
Two usage variants are supported:
With Template: Template defines structure (labels, types, options), JSON contains only the values.
JSON-only: All metadata (labels, types, etc.) are defined directly in the JSON.
Basic Setup
Create a FormElement with native type
inputCollectionJsonThe database column must be of type
TEXT,MEDIUMTEXT,LONGTEXTorJSONSet
encode = none(JSON contains special characters like{},[],")Set
checkType = all(JSON may contain any characters)Optionally provide a template via
FE.parameter.icjTemplate
Important
A UserFormException is thrown if any of these requirements (column type, encode, checkType) is not met.
FormElement.parameter
Attribute |
Value |
Description |
|---|---|---|
icjTemplate |
<template string> |
Template in TYPO3 ext_conf_template.txt format. Can be loaded via SQL. |
Example with Template
Template syntax (same as TYPO3 ext_conf_template.txt):
# cat=<category>/<subcategory>; type=<type>; label=<Label>:<Description>
variableName = defaultValue
Template preparation example:
INSERT INTO ConfigTemplate (name, template)
VALUES("myConfig", "# cat=config/config; type=options[yes,no]; label=:Enable production mode
flagProduction = yes
# cat=config/config; type=string; label=Base URL
baseUrl = https://example.com");
FE.parameter example:
icjTemplate = {{note:RE}}
icjTemplate = {{SELECT template FROM ConfigTemplate WHERE name='myConfig'}}
JSON-only Mode (without Template)
Without a template, all metadata is defined in the JSON itself using reserved keys:
Key |
Value |
Description |
|---|---|---|
value |
<any> |
The actual value (required) |
__label |
<string> |
Display label (fallback: variable name) |
__desc |
<string> |
Description text |
__cat |
<string> |
Subcategory |
__type |
text|select|checkbox|number|textarea |
Field type (default: text) |
__options |
[“val1”, “val2”, …] |
Options for select fields |
__required |
true|false |
Mark field as required |
__readonly |
true|false |
Mark field as readonly |
Example JSON:
{
"config": {
"flagProduction": {
"value": "yes",
"__label": "Production Mode",
"__desc": "Enable production mode",
"__type": "select",
"__options": ["yes", "no"]
},
"baseUrl": {
"value": "https://example.com",
"__label": "Base URL",
"__required": true
}
},
"database": {
"dbHost": {
"value": "localhost",
"__label": "DB Host",
"__readonly": true
}
}
}
The top-level keys (config, database) become tabs in the editor.
The second-level keys (flagProduction, baseUrl, dbHost) become the input fields within each tab.
FormElement Modes
Mode |
Description |
|---|---|
mode=readonly |
All fields are disabled |
mode=required |
All fields are required |
These modes can be combined with individual __readonly and __required flags in the JSON.
No Tabs Mode
If only one category with an empty name ("") or "_default" exists, no tabs are rendered:
{
"": {
"siteName": { "value": "My Website", "__label": "Site Name" },
"debug": { "value": "0", "__label": "Debug Mode", "__type": "checkbox" }
}
}
Type: note
An FormElement without any ‘input’ functionality -just to show some text. Use the typical fields ‘label’, ‘value’ and ‘note’ to be displayed in the corresponding three standard columns.
Type: password
Like a text element, but every character is shown as an asterisk.
Often the following form.Parameter arguments are used here:
retype, retypeLabel, retypeNote -see Type: text
Type: radio
Radio Buttons will be built from one of three sources:
‘sql1’: E.g.
{{!SELECT type AS label FROM Car }}or{{!SELECT type AS label, typeNr AS id FROM Car}}or{{!SHOW tables}}.Resultset format ‘named’: column ‘label’ and optional a column ‘id’.
Resultset format ‘index’:
One column in resultset >> first column represents label
Two or more columns in resultset >> first column represents id and second column represents label.
FormElement.parameter:
itemList = <attribute> E.g.: itemList=red,blue,orange or itemList=1:red,2:blue,3:orange
If ‘:’ or ‘,’ are part of key or value, it needs to escaped by \ . E.g.: itemList=1:red\: (with colon),2:blue\, (with comma),3:orange
Definition of the enum or set field (only labels, ids are not possible).
FormElement.maxlength = <value>
Applies only to ‘plain’ radio elements (not the Bootstrap ‘buttonClass’ from below)
vertical or horizontal alignment:
<value>: ‘’, 0, 1 - The radios will be aligned vertical.
<value>: >1 - The radios will be aligned horizontal, with a linebreak every ‘value’ elements.
FormElement.parameter:
emptyHide: Existence of this item hides an entry with an empty string. This is useful for e.g. Enums, which have an empty entry, but the empty value should not be selectable.
emptyItemAtStart: Existence of this item inserts an empty entry at the beginning of the selectlist.
emptyItemAtEnd: Existence of this item inserts an empty entry at the end of the selectlist.
buttonClass = <class> - Instead of the plain radio fields, Bootstrap buttons. are rendered as radio elements. Use one of the following classes:
btn-default (default, grey),
btn-primary (blue),
btn-success (green),
btn-info (light blue),
btn-warning (orange),
btn-danger (red).
With a given buttonClass, all buttons (=radios) are rendered horizontal. A value in FormElement.maxlength has no effect.
No preselection:
If there is a default configured on a table column, such a value is selected by default. If the user should actively choose an option, the ‘preselection’ can be omitted by specifying an explicit definition on the FormElement field value:
{{<columnName>:RZ}}For existing records the shown value is as expected the value of the record. For new records, it’s the value 0, which is typically not one of the ENUM values and therefore nothing is selected.
Type: select
Select lists will be built from one of three sources:
FormElement.sql1 = {{!<SQL Query}}
Plain select: Browser-styled dropdown (without groupKey)
Available columns:
Column
Required
Description
id
No
Unique identifier for the option value. If missing, label is used as value. These aliases are also permitted:
_id,key,_keylabel
Yes
Display text shown to the user. These aliases are also permitted:
_label,value,_valueclass
No
Applies modifiers to list items. Multiple modifiers can be combined with spaces. These aliases are also permitted:
_class
Note
When using three or more columns, correct column aliases (
id,label,class) are required. A third column without the aliasclasswill be interpreted asgroupKey, which activates selectBS mode.Column ‘class’ - Item Modifiers:
The
classcolumn allows you to apply modifiers to individual dropdown items. Multiple modifiers can be combined by separating them with spaces.Available modifiers:
disabled- Prevents the item from being selected (renders as HTMLdisabledattribute).
Examples:
Single modifier:
'disabled' AS classMultiple modifiers (for future use):
'disabled readonly' AS classConditional:
IF(condition, 'disabled', '') AS class
Result set format ‘index’:
One column >> represents label (used as both id and label)
Two columns >> first is id, second is label
Three columns >> first is id, second is label, third is class
Example: Simple select with disabled items
{{!SELECT b.id AS _id , b.title AS label , IF(MAX(NOW() BETWEEN r.start AND r.end), 'disabled', '') AS class FROM Book AS b LEFT JOIN Rental AS r ON b.id = r.bookId GROUP BY b.title, b.id ORDER BY b.title; }}
selectBS: Custom dropdown (styled by bootstrap)
To enable selectBS, a
groupKeycolumn must be present in the result set.Available columns:
Column
Required
Description
id
Yes
Unique identifier for the option value. These aliases are also permitted:
_id,key,_keylabel
Yes
Display text shown to the user. These aliases are also permitted:
_label,value,_valuegroupKey
Yes
Used for sorting. Required to enable selectBS! These aliases are also permitted:
_groupKeygroupLabel
No
Groups options under a non-clickable header. Use ‘.’ for subgroups, ‘|’ for separator lines. These aliases are also permitted:
_groupLabelclass
No
Applies modifiers to list items. Multiple modifiers can be combined with spaces. These aliases are also permitted:
_classColumn ‘class’ - Item Modifiers:
The
classcolumn works the same way as in plain select mode.
Example: Books grouped by author with disabled items for currently rented books
{{!SELECT b.id AS _id , b.title AS label , b.pIdAuthor AS groupKey , REPLACE(CONCAT(p.firstName, ' ', p.lastName), ".",".") AS groupLabel , IF(MAX(NOW() BETWEEN r.start AND r.end), 'disabled', '') AS class FROM (Book AS b, Person AS p) LEFT JOIN Rental AS r ON b.id = r.bookId WHERE b.pIdAuthor = p.id GROUP BY b.title, p.lastName, p.firstName, b.id ORDER BY b.title; }}
E.g. styled options:
{{!SELECT CONCAT('<span title="This is a tooltip"><span class="glyphicon glyphicon-road"></span> ', type, '</span>') AS label, typeNr AS id, type AS groupKey FROM Car}}E.g. options grouped by first letter of type:
{{!SELECT type AS label, typeNr AS id, type AS groupKey, SUBSTRING(type, 1, 1) AS groupLabel FROM Car}}.E.g. options grouped by first letter and then first two letters of type:
{{!SELECT type AS label, typeNr AS id, type AS groupKey, CONCAT(SUBSTRING(type, 1, 1), '.', SUBSTRING(type, 1, 1)) AS groupLabel FROM Car}}.E.g. options grouped by first letter (with separator line as label):
{{!SELECT type AS label, typeNr AS id, type AS groupKey, CONCAT(SUBSTRING(type, 1, 1), '|') AS groupLabel FROM Car}}.
FormElement.parameter:
itemList = <attribute> - E.g.: itemList=red,blue,orange or itemList=1:red,2:blue,3:orange
If ‘:’ or ‘,’ are part of key or value, it needs to escaped by \ . E.g.: itemList=1:red\: (with colon),2:blue\, (with comma),3:orange
Definition of the enum or set field (only labels, ids are not possible).
FormElement.size = <value>
<value>: <empty>|0|1: drop-down list.
<value>: >1: Renders as a scrollable checkbox container with size rows height. Multiple items can be selected.
FormElement.parameter:
emptyItemAtStart: Existence of this item inserts an empty entry at the beginning of the select list.
emptyItemAtEnd: Existence of this item inserts an empty entry at the end of the select list.
emptyHide: Existence of this item hides the empty entry. This is useful for e.g. Enums, which have an empty entry and the empty value should not be an option to be selected.
placeholder: Text displayed for the empty item when emptyItemAtStart is used. E.g.: placeholder=Please select…
datalist: Similar to ‘typeAhead’. Enables the user to select a predefined option (sql1, itemList) or supply any free text. Attention: Safari (and some other) browsers do not support this fully - https://caniuse.com/#search=datalist. Not compatible with dynamicUpdate.
Type: subrecord
The FormElement type ‘subrecord’ renders a list of records (so called secondary records), typically to show, edit, delete or add new records. The list is defined as an SQL query. The number of records shown is not limited. These FormElement will be rendered inside the form as a HTML table.
mode / modeSql = <type/value>
show / required: the regular mode to show the subrecords
readonly: New / Edit / Delete Buttons are disabled
hidden: The FormElement is rendered, but hidden with display=’none’.
dynamicUpdate - not supported at the moment.
sql1 = {{!SQL Query}}
SQL query to select records. E.g.:
{{!SELECT addr.id AS id, CONCAT(addr.street, addr.streetnumber) AS a, addr.city AS b, addr.zip AS c FROM Address AS addr}}Notice the exclamation mark after
{{- this is necessary to return an array of elements, instead of a single string.Exactly one column ‘id’ has to exist; it specifies the primary record for the target form. In case the id should not be visible to the user, it has to be named ‘_id’.
Column name: [title=]<title>[|[maxLength=]<number>][|nostrip][|icon][|link][|url][|mailto][|thClass=<class>][|_rowEdit][|_rowDelete][|_rowClass][|_rowTooltip]
All column names are optional.
If the keyword is used, all parameter are position independent.
Parameter are separated by ‘|’.
[title=]<text>: Title of the column. The keyword ‘title=’ is optional. Columns with a title starting with ‘_’ won’t be rendered.
[maxLength=]<number>: Max. number of characters displayed per cell. The keyword ‘maxLength=’ is optional. Default maxLength ‘20’. A value of ‘0’ means no limit. This setting also affects the title of the column.
nostrip: by default, html tags will be stripped off the cell content before rendering. This protects the table layout. ‘nostrip’ deactivates the cleaning to make pure html possible.
icon: the cell value contains the name of an icon in typo3conf/ext/qfq/Resources/Public/icons. Empty cell values will omit an html image tag (=nothing rendered in the cell). See QFQ Icons.
link: value will be rendered as described under Column: _link
url: value will be rendered as a href url.
mailto: value will be rendered as a href mailto.
_rowEdit: per row controlled edit link. Use regular … AS _link syntax. Specify only those parameter which has to be row specific - all other will be used from generic render process.
_rowDelete: per row controlled edit link. Use regular … AS _link syntax. Specify only those parameter which has to be row specific - all other will be used from generic render process.. E.g. with renderMode (r:3) it’s possible to prevent specific rows to be deleted.
_rowClass
The value is a CSS class name(s) which will be rendered in the <tr class=”<_rowClass>”> of the subrecord table.
The column itself is not rendered.
By using Bootstrap, the following predefined classes are available:
Text color: text-muted|text-primary|text-success|text-info|text-warning|text-danger (http://getbootstrap.com/docs/3.4/css/#helper-classes)
Row background: active|success|info|warning|danger (http://getbootstrap.com/docs/3.4/css/#tables-contextual-classes)
_rowTooltip
Defines the title attribute (=tooltip) of a subrecord table row.
thClass=<class>
CSS class(es) for the column header (
<th>).Useful for controlling Tablesorter behavior (e.g.
sorter-false,filter-false) or applying Bootstrap alignment classes (e.g.text-center,text-right).
Examples:
{{!SELECT id, note1 AS 'Comment', note2 AS 'Comment|50' , note3 AS 'title=Comment|maxLength=100|nostrip', note4 AS '50|Comment',
'checked.png' AS 'Status|icon', email AS 'mailto|thClass=sorter-false', CONCAT(homepage, '|Homepage') AS 'url',
CONCAT('d|s|F:', pathFileName) AS 'Download|link',
ELT(status,'info','warning','danger') AS '_rowClass', help AS '_rowTooltip' ...}}
FormElement.parameter
form = <form name> - Target form, e.g. form=person
page = <T3 slug> - Target page with detail form. If none specified, use the current page.
extraDeleteForm: Optional. The per row delete Button will reference the form specified here (for deleting) instead of the default (form).
detail = <string> - Mapping of values from
the primary form,
the current row,
any constant or
{{...}}-
to the target form (defined via form=…).
Syntax:
<source table column name 1|&constant 1>:<target column name 1>[,<source table column name 2|&constant 2>:<target column name 2>][...]Example: detail=id:personId,rowId:secId,&12:xId,&{{a}}:personId (rowId is a column of the current selected row defined by sql1)
By default, the given value will overwrite values on the target record. In most situations, this is the wished behaviour.
Exceptions of the default behaviour have to be defined on the target form in the corresponding FormElement in the field value by changing the default Store priority definition. E.g.
{{<columnName>:RS0}}- For existing records, the store R will provide a value. For new records, store R is empty and store S will be searched for a value: the value defined in detail will be chosen. At last the store ‘0’ is defined as a fallback.source table column name: E.g. A person form is opened with person.id=5 (r=5). The definition detail=id:personId and form=address maps person.id to address.personId. On the target record, the column personId becomes ‘5’.
Constant ‘&’: Indicate a ‘constant’ value. E.g. &12:xId or
{{...}}(all possibilities, incl. further SELECT statements) might be used.
new: Optional. Use regular … AS _link syntax. Specify only those parameter which has to be specific - all other will be used from generic render process. E.g. new = r:3 will disable the new button or
new = {{SELECT IF({{r:S0}}=144, 'r:3','') }}will disable the new button only for record.id=144.subrecordTableClass: Optional. Default: ‘table table-hover qfq-subrecord-table qfq-color-grey-2’. If given, the default will be overwritten. Example:
subrecordTableClass = table table-hover qfq-subrecord-table qfq-table-50Tablesorter in Subrecord:
subrecordTableClass = table table-hover qfq-subrecord-table tablesorter tablesorter-pager tablesorter-filtersubrecordTableAttribute: Optional. Default: empty. Can be used to pass further html tags (attributes) to the subrecord table tag. Typical use case is the Tablesorter View Saver:
subrecordTableClass = table table-hover qfq-subrecord-table tablesorter tablesorter-pager tablesorter-filter tablesorter-column-selector subrecordTableAttribute = {{ 'allperson' AS _tablesorter-view-saver }}
subrecordColumnTitleEdit: Optional. Will be rendered as the column title for the new/edit column.
subrecordColumnTitleDelete: Optional. Will be rendered as the column title for the delete column.
- subrecordAppendSql: Optional. Append additional row(s) to shown subrecords. Typically used to show a ‘sum’ row at the end.
Number and order of columns should be identically for sql and subrecordAppendSql. Drag and drop is limited to general rows and not usable in append rows.:
subrecordAppendSql = {{!SELECT addr.id AS id, CONCAT(addr.street, addr.streetnumber) AS a, addr.city AS b, addr.zip AS c FROM Address AS addr WHERE addr.city = 'zurich' }}
subrecordAppendClass: Optional. Use separate class for append rows. If given without value, default class will be deactivated:
subrecordAppendClass = myClasssubrecordAppendForm: Optional. Use specified form for append rows. If not given, form from regular rows is used:
subrecordAppendForm = addresssubrecordAppendExtraDeleteForm: Optional. Use specified form for deleting append rows. If not given, form from append or regular rows is used:
subrecordAppendExtraDeleteForm = address2subrecordEmptyText: Optional. Define the text displayed when subrecord has no records:
subrecordEmptyText = my custom textsubrecordEmpty: Default is ‘show’. Sets the design used for subrecords when no records are present, Options=’hide’,’mute’,’show’:
subrecordEmpty = show:custom empty subrecord text example
Subrecord DragAndDrop
Subrecords inherently support drag-and-drop, see also Drag and drop. The following parameters can be used in the parameter field to customize/activate drag-and-drop:
orderInterval: The order interval to be used, default is 10.
dndTable: The table that contains the records to be ordered. If not given, the table name of the form specified via form=… is used.
orderColumn: The dedicated order column in the specified dndTable (needs to match a column in the table definition). Default is ord.
To switch off Drag ‘n’ Drop, specify a non existing columnname. E.g.: orderColumn=off
If dndTable is a table with a column orderColumn, QFQ automatically applies drag-and-drop logic to the rendered subrecord. It does so by using the subrecord field sql1. The sql1 query should include a column id (or _id) and a column ord (or _ord). E.g.:
FE.sql1 = {{!SELECT p.id AS _id, p.ord AS _ord, p.name FROM Person WHERE p.email!='' ORDER BY p.ord}}
In case the modified ord value should be shown without reloading the page, add html id qfq-dnd-ord-id-…:
FE.sql1 = {{!SELECT p.id AS _id, p.ord AS _ord, p.name
, CONCAT('<span id="qfq-dnd-ord-id-', p.id,'">', p.ord, '</span>') AS 'Ord|nostrip'
FROM Person WHERE p.email!='' ORDER BY p.ord}}
Tip
If you want to deactivate a drag-and-drop that QFQ automatically renders, set the orderColumn to a non-existing column. E.g., orderColumn = nonExistingColumn. This will deactivate drag-and-drop.
In order to evaluate the sql1 query dynamically during a drag-and-drop event, the STORE_RECORD (with the current subrecord) is loaded.
The stores STORE_RECORD, STORE_SIP and STORE_SYSTEM are supported during a drag-and-drop event and can be used in FE.sql1 query.
STORE_SIP: SIP values on form load
STORE_RECORD: values of the current record loaded in the form.
If the subrecord is rendered with drag-and-drop active, but the order is not affected upon reload, there is most likely a problem with evaluating the sql1 query at runtime.
DragAndDrop feature can not be used on append rows (subrecordAppendSql). It will only work for the regular rows.
Type: time
Range time: ‘00:00:00’ to ‘23:59:59’ (https://mariadb.com/kb/en/time/).
FormElement.parameter
showSeconds: Optional. 0|1. Default: 0. Shows the seconds. Independent if the user specifies seconds, they are displayed ‘1’ or not ‘0’.
showZero: Optional. 0|1. Default: 0. For an empty timestamp, with ‘0’ nothing is shown. With ‘1’ the string ‘00:00[:00]’ is shown.
Actually datetimepicker is used as default. For more options see Datetimepicker
Type: upload
An upload element is based on a ‘file browse’-button and a ‘trash’-button (=delete). Only one of them is shown at a time. The ‘file browse’-button is displayed, if there is no file uploaded already. The ‘trash’-button is displayed, if there is a file uploaded already.
After clicking on the browse button, the user select a file from the local filesystem. After choosing the file, the upload starts immediately, shown by a turning wheel. When the server received the whole file and accepts (see below) the file, the ‘file browse’-button disappears and the filename is shown, followed by a ‘trash’-button. Either the user is satisfied now or the user can delete the uploaded file (and maybe upload another one).
Until this point, the file is cached on the server but not copied to the fileDestination. The user have to save the current record, either to finalize the upload and/or to delete a previously uploaded file.
The FormElement behaves like a
native FormElement (showing controls/text on the form) as well as an
action FormElement by firing queries and doing some additional actions during form save.
Inside the Form editor it’s shown as a ‘native FormElement’. During saving the current record, it behaves like an action FormElement and will be processed after saving the primary record and before any action FormElements are processed.
FormElement.value = <string> - By default, the full path of any already uploaded file is shown. To show something different, e.g. only the filename, define:
{{filenameBase:V}}(see Store: VARS - V){{SELECT SUBSTRING_INDEX( '{{pathFileName:R}}', '/', -1) }}
See also download Button to offer a download of an uploaded file.
Unique File Name / Base64
If it is necessary to upload files with identical names, the {{filenameUnique:V}} will handle all necessary actions automatically. Technically, the filename will be base64 encoded and an index for uniqueness will be added. The filename decoding will be done by QFQ if the file is downloaded via QFQ.
When downloading the file, it will be automatically decoded back to its original upload name.
You can also add a prefix to the uploaded name that will persist into the download. The prefix will, by default, reserve an underscore before the file name even if none is specified.
Example:
# No Prefix
fileDestination = 'fileadmin/dummy/{{filenameUnique:V}}'
# With Prefix
fileDestination = 'fileadmin/dummy/Pre_{{filenameUnique:V}}'
# This will do the same as the option above
fileDestination = 'fileadmin/dummy/Pre{{filenameUnique:V}}'
Upload Note
The parameter fileNote = <LABEL> add a note input field to upload elements.
The Note is limited to 255 characters.
In simple mode, the note is stored in the same record as the uploaded file. The corresponding column must have the same name as the file column, with Note appended (see table FileUpload, e.g. pathFileNameNote).
In advanced upload mode, the parameter
fileNoteTarget = <TABLE>:<COLUMN>is required. The note will be saved in the specified table and column, identified by theslaveId.
The Note value of an upload can be accessed with Store VAR {{fileNote:V}}.
The Note will only be saved if a upload is present when saving the Form.
FormElement.parameter
uploadText: Default ‘Drag & drop or choose file’
completeMessage: Default ‘Upload complete’
capture = camera - On a smartphone, after pressing the ‘open file’ button, the camera will be opened and a chosen picture will be uploaded. Automatically set/overwrite accept=image/*.
accept = <mime type>,image/*,video/*,audio/*,.doc,.docx,.pdf
List of file extensions and/or mime types (also known as ‘media types’): http://www.iana.org/assignments/media-types/media-types.xhtml
If none mime type is specified, ‘application/pdf’ is set. This forces that always (!) one type is specified.
To allow any type, specify
*or*/*or*.*.One or more file / media type might be specified, separated by ‘,’.
Different browser respect the given definitions in different ways. Typically the ‘file choose’ dialog offer:
the specified mime type (some browsers only show ‘custom’, if more than one mime type is given),
the option ‘All files’ (the user is always free to try to upload other file types) - but the server won’t accept them,
the ‘file choose’ dialog only offers files of the selected (in the dialog) type.
maxFileSize = <size> - max filesize in bytes (no unit), kilobytes (k/K) or megabytes (m/M) for an uploaded file. If empty or not given, take value from Form, System or System default.
maxImageDimension = <width>x<height> - max image dimensions in px for an uploaded image. If empty or not given, take value from Form, System or System default.
uploadAction = [none,|denyProtect,|unProtect,|denyAcrobatOnly]
none: Disables all checks. Cannot be combined with other options.
denyProtect: Denies uploads of protected PDFs (e.g. encrypted or permission-restricted).
unProtect: Attempts to remove protection from the PDF. If it fails, the upload is still allowed.
denyAcrobatOnly: Denies PDFs that require Adobe Reader to open (e.g. Portfolios, XFA).
fileTrash = [0|1] - Default: ‘1’. This option en-/disables the trash button right beside the file chooser. By default the trash is visible. The trash is only visible if a) there is already a file uploaded or b) a new file has been chosen.
fileTrashText = <string> - Default: ‘’. Will be shown right beside the trash glyph-icon.
fileDestination = <pathFileName> - Destination where to copy the file. A good practice is to specify a relative fileDestination - such an installation (filesystem and database) are movable.
If the original filename should be part of fileDestination, the variable
{{filename}}(see Store: VARS - V) can be used. Example:fileDestination={{SELECT 'fileadmin/user/pictures/', p.name, '-{{filename}}' FROM Person AS p WHERE p.id={{id:R0}} }}Several more variants of the filename and also mimetype and filesize are available. See Store: VARS - V.
The original filename will be sanitized: only ‘<alnumx>’, ‘.’ and ‘_’ characters are allowed. German ‘Umlaut’ will be replaced by ‘ae’, ‘ue’, ‘oe’. All non valid characters will be replaced by ‘_’.
If a file already exist under fileDestination, an error message is shown and ‘save’ is aborted. The user has no possibility to overwrite the already existing file. If the whole workflow is correct, this situation should no arise. Check also fileReplace below.
All necessary subdirectories in fileDestination are automatically created.
Using the current record id in the fileDestination: Using
{{r}}is problematic for a ‘new’ primary record: that one is still ‘0’ at the time of saving. Use{{id:R0}}instead.Uploading of malicious code (e.g. PHP files) is hard to detect. The default mime type check can be easily faked by an attacker. Therefore it’s recommended to use a fileDestination-directory, which is secured against script execution (even if the file has been uploaded, the webserver won’t execute it) - see Secure direct file access.
Keywords available in:
Available
slaveId
yes
sqlBefore
yes
yes
sqlInsert
yes
sqlUpdate
yes
sqlDelete
yes
sqlAfter
yes
yes
fileSize / mimeType
In Upload simple mode the information of fileSize and mimeType will be automatically updated on the current record, if table columns fileSize and/or mimeType exist.
If there are more than one Upload FormElement in a form, the automatically update for fileSize and/or mimeType are not done automatically.
In Upload advanced mode the fileSize and / or mimeType have to be updated with an explicit SQL statement:
sqlAfter = {{UPDATE Data SET mimeType='{{mimeType:V}}', fileSize={{fileSize:V}} WHERE id={{id:R}} }}
fileReplace = always - If fileDestination exist - replace it by the new one.
chmodFile = <unix file permission mode> - e.g. 660 for owner and group read and writeable. Only the numeric mode is allowed.
chmodDir = <unix file permission mode> - e.g. 770 for owner and group read, writeable and executable. Only the numeric mode is allowed. Will be applied to all new created directories.
autoOrient: images might contain EXIF data (e.g. captured via mobile phones) incl. an orientation tag like TopLeft, BottomRight and so on. Web-Browser and other graphic programs often understand and respect those information and rotate such images automatically. If not, the image might be displayed in an unwanted orientation. With active option ‘autoOrient’, QFQ tries to normalize such images via ‘convert’ (part of ImageMagick). Especially if images are processed by the QFQ internal ‘Fabric’-JS it’s recommended to normalize images first. The normalization process does not solve all orientation problems.
autoOrient = [0|1]
autoOrientCmd =
convert -auto-orient {{fileDestination:V}} {{fileDestination:V}}.new; mv {{fileDestination:V}}.new {{fileDestination:V}}autoOrientMimeType = image/jpeg,image/png,image/tiff
If the defaults for autoOrientCmd and autoOrientMimeType are sufficient, it’s not necessary to specify them.
fileDestination = fileadmin/file_{{id:R}}.zip
fileUnzip
sqlValidate ={{! SELECT '' FROM (SELECT '') AS fake WHERE '{{mimeType:V}}' LIKE 'application/pdf%' }}
expectRecords=1
alert=Unexpected filetype
# Set new
sqlAfter={{INSERT INTO Upload (pathFileName) VALUES '{{filename:V}}' }}
fileSplit, fileDestinationSplit, tableNameSplit: see Split PDF Upload
Excel Import: QFQ offers functionality to directly import excel data into the database. This functionality can optionally be combined with saving the file by using the above parameters like fileDestination. The data is imported without formatting. Please note that this means Excel dates will be imported as a number (e.g. 43214), which is the serial value date in Excel. To convert such a number to a MariaDb date, use: DATE_ADD(‘1899-12-30’, INTERVAL serialValue DAY).
importToTable = <[db.]tablename> - Required. Providing this parameter activates the import. If the table doesn’t exist, it will be created.
importToColumns = <col1>,<col2>,… - If none provided, the Excel column names A, B, … are used. Note: These have to match the table’s column names if the table already exists.
importRegion = [tab],[startColumn],[startRow],[endColumn],[endRow],[mode](optional - possible: ‘append’ or ‘join’. ‘join’ is default.)|… - All parts are optional (default: entire 1st sheet). Tab can either be given as an index (1-based) or a name. start/endColumn can be given either numerically (1, 2, …) or by column name (A, B, …). Note that you can specify several regions to import.
importMode = append (default) | replace - The data is either appended or replaced in the specified table.
importType = auto (default) | xls | xlsx | ods | csv - Define what kind of data should be expected by the Spreadsheet Reader.
importNamedSheetsOnly = <comma separated list of sheet names>. Use this option if specific sheets cause problems during import and should be skipped, by naming only those sheets, who will be read. This will also reduce the memory usage.
importSetReadDataOnly = 0|1. Read only cell data, not the cell formatting. Warning: cell types other than numerical will be misinterpreted.
importListSheetNames = 0|1. For debug use only. Will open a dialog and report all found worksheet names.
uploadType = dnd|button
‘dnd’ (default) is based on Filepond and is drag’n’drop capable.
‘button’ is the standard HTML upload button.
This option can be set in QFQ system config (system wide), per Form.parameter or per FormElement.parameter.
importFromColumns = [tab,]<excel-col1>,<excel-col2>,… - Header names in the excel file to import from. Regions will be selected automatically based on the header names. Auto search looks only for the first 20 rows of a worksheet.
The header names should exist in the first 20 rows of the excel file. Otherwise it will throw an exception.
The first value in list can be the tab name or index (1-based). If not set, it will check the sheets for the existing header names.
Header names matching with the table column will insert the data to the associated column. If the column is not found, it will insert the data into the next not mapped column. If any of the associated table columns are not found, they will be inserted in the order specified.
Simple mode: If importRegion and importFromColumns are both not set as a parameter, excel sheets will be searched with the existing table columns from importToTable and the regions selected based on that. If all columns are found, the import from first matched sheet will execute. If there is a table column which is not found in the excel sheet, it will throw an exception.
Combined with importToColumns it will map the excel columns to the table columns. Same amount of columns is required.
Using importRegion and importFromColumns together is not supported. This will throw an exception.
Immediately after the upload finished (before the user press save), the file will be checked on the server for it’s content or file extension (see ‘accept’).
The maximum size is defined by the minimum of upload_max_filesize, post_max_size and memory_limit (PHP script) in the php.ini.
In case of broken uploads, please also check max_input_time in php.ini.
Deleting a record and the referenced file
If the user deletes a record (e.g. pressing the delete button on a form) which contains reference(s) to files, such files are deleted too.
For Advanced Uploads the SlaveId and sqlDelete are leveraged to delete files from the System.
Only column(name)s which contains pathFileName as part of their name, are checked for file references.
If there are other records, which references the same file, such files are not deleted. It’s a very basic check: just the current column of the current table is compared. In general it’s not a good idea to have multiple references to a single file. Therefore this check is just a fallback.
Upload simple mode
Requires: ‘upload’-FormElement.name = ‘column name’ of an column in the primary table.
After moving the file to fileDestination, the current record/column will be updated to fileDestination. The database definition of the named column has to be a string variant (varchar, text but not numeric or else). On form load, the column value will be displayed as the whole value (pathFileName)
Deleting an uploaded file in the form (by clicking on the trash near beside) will delete the file on the filesystem as well. The column will be updated to an empty string.
This happens automatically without any further definition in the ‘upload’-FormElement.
Multiple ‘upload’-FormElements per form are possible. Each of it needs an own table column.
fileKey (Simple Upload)
The parameter fileKey generates a slugified, unique key from the uploaded filename and stores it in a
column of the primary table. The column must exist in the table.
fileKey = 1- stores the key in the default columnpathFileNameKey.fileKey = <columnName>- stores the key in the specified column. This allows multiple upload FormElements per form, each with its own key column (e.g.fileKey = photoKey,fileKey = cvKey).
Example: Uploading Mein Dokument (Final).pdf generates the key mein-dokument-final.pdf.
If that key already exists, a counter is appended: mein-dokument-final2.pdf, mein-dokument-final3.pdf, etc.
This key can be used in sqlDirect queries to provide human-readable download URLs instead of numeric IDs.
Upload advanced mode
Requires: ‘upload’-FormElement.name is unknown as a column in the primary table.
This mode will serve further database structure scenarios.
A typical name for such an ‘upload’-FormElement, to show that the name does not exist in the primary table, might start with ‘my’, e.g. ‘myUpload1’.
FormElement.value = <string> - The path/filename, shown during ‘form load’ to indicate a previous uploaded file, has to be queried with this field. E.g.:
{{SELECT pathFileNamePicture FROM Note WHERE id={{slaveId}} }}
FormElement.parameter:
fileDestination = <pathFileName> - define the path/filename (see Store: VARS - V). E.g.:
fileDestination=fileadmin/person/{{name:R0}}_{{id:R}}/uploads/picture_{{filename}}
* *slaveId* = `<id>` - Defines the target record where to retrieve and store
the path/filename of the uploaded file. Check also :ref:`slave-id`. E.g.:
slaveId={{SELECT id FROM Note WHERE pId={{id:R0}} AND type='picture' LIMIT 1}}
* *sqlBefore* = `{{<query>}}` - fired during a form save, before the following queries are fired.
* *sqlInsert* = `{{<query>}}` - fired if `slaveId=0` and an upload exist (user has chosen a file):
sqlInsert={{INSERT INTO Note (pId, type, pathFileName) VALUE ({{id:R0}}, 'image', '{{fileDestination}}') }}
* *sqlUpdate* = `{{<query>}}` - fired if `slaveId>0` and an upload exist (user has chosen a file). E.g.:
sqlUpdate={{UPDATE Note SET pathFileName = '{{fileDestination}}' WHERE id={{slaveId}} LIMIT 1}}
* *sqlDelete* = `{{<query>}}` - fired if `slaveId>0` and no upload exist (user has not chosen a file). E.g.:
sqlDelete={{DELETE FROM Note WHERE id={{slaveId:V}} LIMIT 1}}
* *sqlAfter* = `{{<query>}}` - fired after all previous queries have been fired.
Might update the new created id to a primary record. E.g.:
sqlAfter={{UPDATE Person SET noteIdPicture = {{slaveId}} WHERE id={{id:R0}} LIMIT 1 }}
fileKey (Advanced Upload)
The parameter fileKey generates a slugified, unique key from the uploaded filename. The generated key is
available as {{pathFileNameKey:V}} in sqlInsert and sqlUpdate queries.
fileKey = <tableName>- checks uniqueness against thepathFileNameKeycolumn of the specified table.fileKey = <tableName>,<columnName>- checks uniqueness against a custom column. Use this when the key column in the target table has a different name.
Important
Always use the explicit :V store suffix: {{pathFileNameKey:V}}. Without it, the default store search
order (FSRVD) may find a pathFileNameKey column from the primary record (STORE_RECORD) before reaching
STORE_VAR, returning a stale or wrong value.
Example with default column:
fileKey = Note
sqlInsert={{INSERT INTO Note (pId, type, pathFileName, pathFileNameKey) VALUE ({{id:R0}}, 'image', '{{fileDestination}}', '{{pathFileNameKey:V}}') }}
sqlUpdate={{UPDATE Note SET pathFileName = '{{fileDestination}}', pathFileNameKey = '{{pathFileNameKey:V}}' WHERE id={{slaveId}} LIMIT 1}}
Example with custom column:
fileKey = Note,fileSlug
sqlInsert={{INSERT INTO Note (pId, type, pathFileName, fileSlug) VALUE ({{id:R0}}, 'image', '{{fileDestination}}', '{{pathFileNameKey:V}}') }}
Upload Multi Mode
Requirements
FE.parameter:
To trigger the multiUpload element:
uploadType = multiUploadThe upload ID that will be used to save all elements:
uploadId = {{uploadId:S0}}File destination:
fileDestination = 'fileadmin/multiUpload/{{id:R}}/{{filename}}'
DB Table:
When no
targetTableis provided, qfq usesFileUploadas the default.This table must have at least the following columns:
id(autoincrement)pathFileNameuploadIdfileSizemimeType
Optional for Multi Upload
FE.parameter:
Specify an alternative table for upload data:
targetTable = myTableThis table must have the five columns described above.
Additional details to be added to the upload table:
detail = xId:1,grId:{{var:SF0}}In this example, the columns
xIdandgrIdmust exist in the target table.
fileKey = 1- Generates a slugified, unique key from each uploaded filename and stores it in thepathFileNameKeycolumn of the upload table. This column must exist in the target table.
What Multi Upload Doesn’t Support
fileUnzipDirectly importing Excel to DB
fileReplace(this option is not applicable for Multi Upload)
Multi Upload good to know Information
Uploads are saved only after the form has been saved.
Uploads are deleted only after the form has been saved.
When deleting a record via a form containing a Multi Upload element and an
uploadId != 0, all uploads matching thatuploadIdwill be removed from both the filesystem and the table.You can use
{{id:R}}as part of the fileDestination even if (r = 0). The ID will be replaced during runtime.
Split PDF Upload
Additional to the upload, it’s possible to split the uploaded file (only PDF files) into several SVG or JPEG files, one file per PDF page. The split is done via a) http://www.cityinthesky.co.uk/opensource/pdf2svg/ or b) Image Magick convert.
Currently, QFQ can only split PDF files.
If the source file is not of type PDF, activating fileSplit has no impact: no split and NO complain about invalid
file type.
Prerequisites:
Table Split must exist in the database for this feature to function.
The ImageMagick policy file /etc/ImageMagick/policy.xml must allow PDF processing. Ensure the following line is present (or uncommented): <policy domain=”coder” rights=”read|write” pattern=”PDF” />
Note: If you’re using ImageMagick 6 on Debian/Ubuntu, the path may also be: /etc/ImageMagick-6/policy.xml
FormElement.parameter:
fileSplit = <type> - Activate the splitting process. Possible values: svg or jpeg. No default.
fileSplitOptions = <command line options>.
[svg] - no default
[jpeg] - default: -density 150 -quality 90
fileDestinationSplit = <pathFileName (pattern)> - Target directory and filename pattern for the created & split files. Default <fileDestination>.split/split.<nr>.<fileSplit>. If explicit given, respect that SVG needs a printf style for <nr>, whereas JPEG is numbered automatically. E.g.
[svg] fileDestinationSplit = fileadmin/protected/{{id:R}}.{{filenameBase:V}}.%02d.svg [jpeg] fileDestinationSplit = fileadmin/protected/{{id:R}}.{{filenameBase:V}}.jpg
tableNameSplit = <tablename> - Default: name of table of current form. This name will be saved in table Split
The splitting happens immediately after the user pressed save.
To easily access the split files via QFQ, per file one record is created in table ‘Split’.
Table ‘Split’:
Column |
Description |
|---|---|
id |
Uniq auto increment index |
tableName |
Name of the table, where the reference to the original file (multi page PDF file) is saved. |
xId |
Primary id of the reference record. |
pathFileName |
Path/filename reference to one of the created files |
One use case why to split an upload: annotate individual pages by using the FormElement.type=`annotate`.
BibTex Import
Our system allows users to upload a BibTeX file containing one or more publication entries. The uploaded file is parsed using a regular expression–based parser, and for each publication found, a new database entry is created in the Publication table. This process automatically assigns each publication to the user who uploaded the file.
How It Works
File Upload:
The user selects a BibTeX file using our upload form.
Required Parameters:
User ID (pId):
The form must provide their unique identifier parameter
pId, which links the uploaded publications to that user.File Type:
The form must include the parameter
fileTypeset to"bib"ensuring that only BibTeX files are processed.Target Database Table:
The form must include the parameter
targetTableset to"yourDbTable", to select where the data should be uploaded.
The MIME type of the file is checked on the server (either accepting all types or validating against a BibTeX-specific pattern). Once the file is selected, the upload begins immediately.
Parsing:
A regular expression parser scans the entire BibTeX text to extract individual publication entries.
Each entry is expected to follow the pattern:
@<entryType>{<citeKey>, ... }For each entry, the parser extracts:
The entry type (e.g.
article,book,incollection)The citation key
The fields (e.g. author, title, publisher) which are captured using a regex that recognizes values in braces
{...}, double quotes"...", or as bare numbers.
Database Insertion:
For each parsed publication, a new row is inserted into the Publication table.
The owner’s ID (
pIdOwner) is taken from the form’spIdparameter.Important: The SQL
INSERTstatement is currently hardcoded with a fixed set of columns. If you need to add, remove, or modify columns, you must update the PHP backend code accordingly.
Assignment:
During the upload process, each publication entry is linked to the user by including the
pIdin the database insert.The backend relies on three key parameters:
pId: The user’s unique identifier.fileType: Must be set to"bib"to process the file.An internal MIME type check ensures that the file is valid.
Publication Table Columns
The following table provides an overview of the columns used in the Publication table, along with a brief description for each:
Column Name |
Description |
|---|---|
pIdOwner |
The unique identifier of the user who uploaded the file. This links each publication to its owner. |
status |
The publication type (e.g. |
citationKey |
A unique key for the publication (if provided in the BibTeX file). |
author |
The primary author(s) of the publication. |
title |
The title of the publication. |
detail |
Additional details such as journal name or extended description (if available). |
bookTitle |
The title of the book or compilation where the publication appears (if applicable). |
publisher |
The name of the publisher. |
address |
The location (e.g. city) associated with the publisher. |
chapter |
Chapter information, used when the publication is a book chapter. |
edition |
The edition of the publication (e.g. |
editor |
The editor(s) of the publication (if applicable). |
isbn |
The ISBN number for the publication. |
month |
The month when the publication was released. |
pubYear |
The year of publication. |
volume |
The volume number (if applicable). |
number |
The issue number (if applicable). |
pages |
The page range where the publication can be found. |
series |
The series name if the publication is part of a series. |
abstractchapter |
A short abstract or summary of the publication. |
dxDoi |
The Digital Object Identifier (DOI) of the publication. |
eprint |
An identifier for preprints (e.g. arXiv IDs). |
keywords |
A comma-separated list of keywords related to the publication. |
url |
The URL for additional information or to access the publication online. |
note |
Additional notes regarding the publication. |
organization |
The organization associated with the publication (if applicable). |
school |
The school or university affiliation (if applicable). |
institution |
The institution related to the publication (if applicable). |
Summary
Upload Process: Users upload a BibTeX file with one or more publication entries. The file must have
fileTypeset to"bib"and include the user’s ID (pId).Parsing & Insertion: The system uses a regex parser to split the file into individual publications. Each publication is then inserted into the Publication table using a hardcoded SQL
INSERTstatement. The insertion includes all fields (as listed above) and automatically associates the publication with the user throughpIdOwner.Customizability: Since the SQL insert is hardcoded, any changes to the publication structure (such as adding new fields or changing the target table) require modifications in the backend PHP code.
Type: chat
The FormElement Chat together with the Websocket implementation offers typical chat environment. Chat rooms (cIdTopic + xId) have to be defined by application logic. Offering a dedicated chat room for Admin staff on one side and a customer on the other side is a typical usecase for such implicit chat rooms. Even if chat rooms with many users are possible, this is not the typical use case.
Features:
Realtime updates of messages and tags.
Threads per chat room (has to be enabled in application logic).
Particpant(s) can mark/assign per chat or thread:
a flag ‘done’
any custom tag(s)
The tags can be shown on a dashboard.
Details
The FormElement Chat hasn’t a direct relationship to the form itself! It is just shown inside a form.
Even it is a part of a form, it does not save anything via form save logic.
cIdTopic - chat room identification
xId - extend chat room identification
pIdCreator - Person.id from current user. if not set, username ‘Anonym’ will be taken.
username - displayed username in chat.
Optional parameter and per default off/disabled:
thread = 0|1 - Enable thread feature. Create threads inside chat room.
optionTag = off|all|my - Enable feature to set tags for chat room and each open thread. all: Shows tags from all users. my: Shows only own tags.
optionTagDone = off|all|my - Enable feature to set done flags for chat room and each open thread. all: Shows done flags from all users. my: Shows only own done flag.
optionTagDoneResetOnNewMessage = 0|1 - Enable feature to reset the done flag automatically if an user writes a new message.
Parameter configuration could look like this. Example:
fillStoreVar={{!SELECT id AS pIdCreator, CONCAT(firstName, ' ', lastName) AS fullname FROM Person WHERE account = '{{feUser:UTE}}'}}
pIdCreator={{pIdCreator:V0}}
username={{fullname:VE}}
# General Topic chat room identification
cIdTopic={{SELECT id FROM Chat WHERE type = 'topic' AND reference='user_request_agreement'}}
# Separate chat room to make it uniqe per user or per request
xId={{id:R0}}
optionTagDone=all
optionTag=all
optionTagDoneResetOnNewMessage=1
thread=1
Optional following parameter can be used to save a custom id in chat record:
xGrIdStatus
QFQ delivers a table named Chat. This is a multi purpose table and saves the messages itself, the tags, the pointer to last message read by user, flag to send reminder.
id <int>
xId <int>
cIdTopic <int>
pIdCreator <int>
cIdThread <int>
cIdTag <int>
xGrIdStatus <int>
type <enum> ‘message’, ‘topic’, ‘tag’, ‘read’, ‘reminder’
message <text>
username <varchar>
emoticon <varchar>
reference <varchar>
Instead of using table Chat, it’s possible to use a different table with different columnames for the chat functionality:
tableName - Name of the table where the chat data should be stored instead of the default one.
columnMap - Map the columns from the custom table with the given ones from default. [customColumn:defaultColumn,…].
Example:
tableName = Note
columnMap = [apId:xId,pId:pIdCreator,grIdStatus:xGrIdStatus,xId2:cIdTopic,note:message,note2:username,inserted:created]
The column reference is used to create topic ids which can be taken as reference for the column cIdTopic. Otherwise you can use your own preferred group ids like Ggroup as reference.
Example INSERT statement to create a topic in chat table:
INSERT INTO Chat (type, message, reference) VALUES ('topic', 'Request agreement', 'user_request_agreement');
Caution: Currently multi-db setup is not implemented for this feature.
Class: Action
FormElement.type: before… | after…
These type of ‘action’ FormElements will be used to implement data validation or creating/updating additional records.
Types:
beforeLoad (e.g. good to check access permission)
afterLoad
beforeSave (e.g. to prohibit creating of duplicate records)
afterSave (e.g. to to create & update additional records)
beforeInsert
afterInsert
beforeUpdate
afterUpdate
beforeDelete (e.g. to delete slave records)
afterDelete
paste (configure copy/paste forms)
FormElement.parameter: sqlValidate
Perform checks by firing an SQL query and expecting a predefined number of selected records.
OK: the expectRecords number of records has been selected. Continue processing the next FormElement.
Fail: the expectRecords number of records has not been selected (less or more): Display the error message alert and abort the whole (!) current form load or save.
FormElement.parameter:
requiredList = <fe.name[s]> - List of native-FormElement names: only if all of those elements are filled (!=0 and !=’’), the current action-FormElement will be processed. This will enable or disable the check, based on the user input! If no native-FormElement names are given, the specified check will always be performed.
sqlValidate = {{<query>}} - validation query. E.g.: sqlValidate={{!SELECT id FROM Person AS p WHERE p.name LIKE {{name:F:all}} AND p.firstname LIKE {{firstname:F:all}} }}
expectRecords = <value>- number of expected records.
expectRecords = 0 or expectRecords = 0,1 or expectRecords = {{SELECT COUNT(id) FROM Person}}
Separate multiple valid record numbers by ‘,’. If at least one of those matches, the check will pass successfully.
qfqLog = <value> - determines if the error should be logged.
qfqLog and qfqLog = 1 (default) - error will be logged in both cases.
qfqLog = 0 - no error will be logged.
alert = <alert text>[:<level>[:<ok button text>[:<force button text>[:<timeout>[:<flag modal>]]]]]
Parameter
Description
Text
The text shown by the alert. HTML is allowed to format the text. Any ‘:’ needs to be escaped.
Level
info (default), success, warning, danger/error
Ok button text
Default: ‘Ok’. Closes the alert.
Force button text
Forces a save of the form in case expectRecords fails.
Timeout in seconds
Default: 0, no timeout. > 0, after the specified time in seconds, the alert will disappear (no forced save).
Flag modal
Default: 1, alert behaves modal. 0, alert does not behave modal and appears on the side.
messageFail = <string> - (Deprecated) Message to show. E.g.: messageFail =
There is already a person called {{firstname:F:all}} {{name:F:all}}
FormElement.parameter: slaveId
Most of the slaveId concept is part of sqlInsert / sqlUpdate - see below.
Note
slaveId: 0 (default) or any integer which references a record.
Set slaveId explicit or by query:
slaveId = 123orslaveId = {{SELECT id ...}}.fillStoreVar is fired first, than slaveId. Don’t use
{{slaveId:V}}in fillStoreVar.To set slaveId, a value from STORE_VARS can be used:
slaveId={{someId:V}}.{{slaveId:V}}can be used in any query of the current FormElement (but not fillStoreVar).If the FormElement name is equal to a column of the primary table: QFQ updates the current loaded primary table record with the latest slaveId.
Important
After an INSERT (= sqlInsert) the last_insert_id() is copied to {{slaveId:V}} automatically.
FormElement.parameter: sqlBefore / sqlInsert / sqlUpdate / sqlDelete / sqlAfter
Tip
Flexible way to update record(s), even on different table(s).
Often used by FormElement.type=afterSave or similar.
Note
Side note: a) Form.type beforeLoad|Save|Insert|Update is independent of b) Form.parameter sqlBefore / sqlAfter. Think of that a) represents a class and b) is a property of a class.
All of the following attributes are optional:
requiredList = [<fe.name>,] - List of native-FormElement names.
Simplifies to completely enable or disable the current FormElement.
If empty: process the current FormElement. This the typical situation.
If not empty, all named FormElements will be checked: if all of them are filled, the current FormElement will be processed else not.
Note: The requiredList is independent of FormElement.mode=required.
sqlBefore = {{<query>}} - always fired (before any sqlInsert, sqlUpdate, ..)
sqlInsert = {{<query>}} - fired if slaveId == 0 or slaveId == ‘’.
sqlUpdate = {{<query>}} - fired if slaveId > 0.
sqlDelete = {{<query>}} - fired if slaveId > 0, after sqlInsert or sqlUpdate. Be careful not to delete filled records! Look for sqlHonorFormElements to simplify checks.
sqlAfter = {{<query>}} - always fired (after sqlInsert, sqlUpdate or sqlDelete).
sqlHonorFormElements = [<fe.name>,] list of FormElement names.
If one of the named FormElements is given:
fire sqlInsert if slaveId == 0
fire sqlUpdate if slaveId > 0*
If all of the named FormElements are empty:
fire sqlDelete if slaveId > 0
Example
Situation 1: master.xId=slave.id (1:1)
Name the action element ‘xId’: than
{{slaveId}}will be automatically set to the value of ‘master.xId’{{slaveId}} == 0- ‘sqlInsert’ will be fired.{{slaveId}} != 0- ‘sqlUpdate’ will be fired.
In case of firing ‘sqlInsert’, the ‘slave.id’ of the new created record are copied to master.xId (the database will be updated automatically).
If the automatic update of the master record is not suitable, the action element should have no name or a name which does not exist as a column of the master record. Define
slaveId={{SELECT id ...}}.Two FormElements myStreet and myCity:
Without sqlHonorFormElements. Parameter:
sqlInsert = {{INSERT INTO address (`street`, `city`) VALUES ('{{myStreet:FE:alnumx:s}}', '{{myCity:FE:alnumx:s}}') }}
sqlUpdate = {{UPDATE address SET `street` = '{{myStreet:FE:alnumx:s}}', `city` = '{{myCity:FE:alnumx:s}}' WHERE id={{slaveId}} LIMIT 1 }}
sqlDelete = {{DELETE FROM Address WHERE id={{slaveId}} AND '{{myStreet:FE:alnumx:s}}'='' AND '{{myCity:FE:alnumx:s}}'='' LIMIT 1 }}
* With *sqlHonorFormElements*. Parameter:
sqlHonorFormElements = myStreet, myCity # Non Templategroup
sqlInsert = {{INSERT INTO address (`street`, `city`) VALUES ('{{myStreet:FE:alnumx:s}}', '{{myCity:FE:alnumx:s}}') }}
sqlUpdate = {{UPDATE address SET `street` = '{{myStreet:FE:alnumx:s}}', `city` = '{{myCity:FE:alnumx:s}}' WHERE id={{slaveId}} LIMIT 1 }}
sqlDelete = {{DELETE FROM Address WHERE id={{slaveId}} LIMIT 1 }}
# For Templategroups: sqlHonorFormElements = myStreet%d, myCity%d
Situation 2: master.id=slave.xId (1:n)
Name the action element different to any column name of the master record (or no name).
Determine the slaveId:
slaveId={{SELECT id FROM Slave WHERE slave.xxx={{...}} LIMIT 1}}{{slaveId}} == 0- ‘sqlInsert’ will be fired.{{slaveId}} != 0- ‘sqlUpdate’ will be fired.
Two FormElements myStreet and myCity. The person is the master record, address is the slave:
Without sqlHonorFormElements. Parameter:
slaveId = {{SELECT id FROM Address WHERE personId={{id}} ORDER BY id LIMIT 1 }}
sqlInsert = {{INSERT INTO address (`personId`, `street`, `city`) VALUES ({{id}}, '{{myStreet:FE:alnumx:s}}', '{{myCity:FE:alnumx:s}}') }}
sqlUpdate = {{UPDATE address SET `street` = '{{myStreet:FE:alnumx:s}}', `city` = '{{myCity:FE:alnumx:s}}' WHERE id={{slaveId}} LIMIT 1 }}
sqlDelete = {{DELETE FROM Address WHERE id={{slaveId}} AND '{{myStreet:FE:alnumx:s}}'='' AND '{{myCity:FE:alnumx:s}}'='' LIMIT 1 }}
* With *sqlHonorFormElements*. Parameter:
slaveId = {{SELECT id FROM Address WHERE personId={{id}} ORDER BY id LIMIT 1 }}
sqlHonorFormElements = myStreet, myCity # Non Templategroup
sqlInsert = {{INSERT INTO address (`personId`, `street`, `city`) VALUES ({{id}}, '{{myStreet:FE:alnumx:s}}', '{{myCity:FE:alnumx:s}}') }}
sqlUpdate = {{UPDATE address SET `street` = '{{myStreet:FE:alnumx:s}}', `city` = '{{myCity:FE:alnumx:s}}' WHERE id={{slaveId}} LIMIT 1 }}
sqlDelete = {{DELETE FROM Address WHERE id={{slaveId}} LIMIT 1 }}
# For Templategroups: sqlHonorFormElements = myStreet%d, myCity%d
FormElement.parameter: saveFormJson, saveFormJsonName (System)
FormElement.parameter.saveFormJson and FormElement.parameter.saveFormJsonName
These parameters are used in the JSON form editor. See Form As File.
If both parameters are present in an action FormElement then the form with name given by saveFormJsonName is overwritten by the JSON string given by saveFormJson when the action element is processed.
A backup of the previous version of the form is saved before overwriting. See Form As File.
FormElement.parameter.saveFormElementJson
This parameter is used in the JSON form-element editor. See Form As File.
Type: sendmail
Send mail(s) will be processed after:
saving the record ,
processing all uploads,
together with after… action FormElements in the given order.
FormElement.value = <string> - Body of the email. See also: html-formatting
FormElement.parameter:
sendMailTo = <string> - Comma-separated list of receiver email addresses. Optional: ‘realname <john@doe.com>. If there is no recipient email address, no mail will be sent.
sendMailCc = <string> - Comma-separated list of receiver email addresses. Optional: ‘realname <john@doe.com>.
sendMailBcc = <string> - Comma-separated list of receiver email addresses. Optional: ‘realname <john@doe.com>.
sendMailFrom = <string> - Sender of the email. Optional: ‘realname <john@doe.com>’. Mandatory.
sendMailSubject = <string> - Subject of the email.
sendMailReplyTo = <string> - Reply this email address. Optional: ‘realname <john@doe.com>’.
sendMailAttachment = <string> - List of ‘sources’ to attach to the mail as files. Check Attachment for options.
sendMailHeader = <string> - Specify custom header.
sendMailFlagAutoSubmit = <string> - on|off - If ‘on’ (default), the mail contains the header ‘Auto-Submitted: auto-send’ - this suppress a) OoO replies, b) forwarding of emails.
sendMailGrId = <string> - Will be copied to the mailLog record. Helps to setup specific logfile queries.
sendMailXId = <string> - Will be copied to the mailLog record. Helps to setup specific logfile queries.
sendMailXId2 = <string> - Will be copied to the mailLog record. Helps to setup specific logfile queries.
sendMailXId3 = <string> - Will be copied to the mailLog record. Helps to setup specific logfile queries.
sendMailMode = <string> - html - if set, the e-mail body will be rendered as html.
sendMailSubjectHtmlEntity = <string> - encode|decode|none - the mail subject will be htmlspecialchar() encoded / decoded (default) or none (untouched).
sendMailBodyHtmlEntity*= `<string>` - **encode|decode|none* - the mail body will be htmlspecialchar() encoded, decoded (default) or none (untouched).
sqlBefore / sqlAfter = <string> - can be used like with other action elements (will be fired before/after sending the e-mail).
An empty sendMailTo will cancel any sendmail action, even if sendMailCc|Bcc is set. This can be used to determine during runtime if sending is wished.
To use values of the submitted form, use the STORE_FORM. E.g.
{{name:F:allbut}}To use the id of a new created or already existing primary record, use the STORE_RECORD. E.g.
{{id:R}}.By default, QFQ stores values ‘htmlspecialchars()’ encoded. If such values have to send by email, the html entities are unwanted. Therefore the default setting for ‘subject’ und ‘body’ is to decode the values via ‘htmlspecialchars_decode()’. If this is not wished, it can be turned off by sendMailSubjectHtmlEntity=none and/or sendMailBodyHtmlEntity=none.
For debugging, please check Redirect all mail to (catch all).
Tip
If you encounter \r at EOL character in your emails, you probably use a QFQ variable for the body.
Switch of the escaping by using - as escape/action class. E.g.: {{body:F:all:-}}
Example to attach one file1.pdf (with the attachment filename readme.pdf) and concatenate two PDF, created on the fly
from the www.example.com and ?export (with the attachment filename ‘personal.pdf’):
sendMailAttachment = F:fileadmin/file1.pdf|d:readme.pdf|C|u:http://www.example.com|p:/export&r=123&_sip=1|d:personal.pdf
Type: paste
See also Copy Form.
sql1 = {{<query>}} - e.g. {{!SELECT {{id:P}} AS id, ‘{{myNewName:FE:allbut}}’ AS name}} (only one record) or {{!SELECT i.id AS id, {{basketId:P}} AS basketId FROM Item AS i WHERE i.basketId={{id:P}} }} (multiple records)
Pay attention to ‘!’.
For every row, a new record is created in recordDestinationTable.
Column ‘id’ is not copied.
The recordSourceTable together with column id will identify the source record.
Columns not specified, will be copied 1:1 from source to destination.
Columns specified, will overwrite the source value.
FormElement.parameter:
recordSourceTable = <tableName> - Optional: table from where the records will be copied. Default: <recordDestinationTable>
recordDestinationTable = <tableName> - table where the new records will be copied to.
translateIdColumn = <column name> - column name to update references of newly created id’s.
Form Magic
Read the following carefully to understand and use QFQ form functionality.
Check also the Form process order.
Parameter
Important
SIP parameter name = primary table column name
Parameter (one or more) in the SIP url, which exist as a column in the form table (SIP parameter name is equal to a table column name), will be automatically saved in the record!
Example: A slave record (e.g. an address of a person) has to be assigned to a master record (a person):
person.id=address.pId. Just give the pId in the link who calls the address form. The following creates a ‘new’
button for an address for all persons, and the pId will be automatically saved in the address table:
SELECT CONCAT('p:{{pageSlug:T}}?form=address&r=0&pId=', p.id) AS _pagen FROM Person AS p
Remember: it’s a good practice to specify such parameter in Form.permitNew and/or Form.permitEdit. It’s only a check for the webmaster, not to forgot a parameter in a SIP url.
Note
FormElement.type = subrecord
Subrecord’s typically use new, edit and delete links. To inject parameter in those QFQ created links, use FormElement.parameter.detail . See Type: subrecord.
Note
FormElement.type = extra
If a table column should be saved with a specific value, and the value should not be shown to the user, the FE.type=’extra’ will do the job. The value could be static or calculated on the fly. Often it’s easier to specify such a parameter/value in the SIP url, but if the form is called from multiple places, an extra element is more suitable. In traditional web applications HTML input fields of type hidden are often used for this. Such content can be tempered by an attacker. It’s much safer to use SIP parameter or FormElement.type=extra fields.
Note
slaveId concept
For each native and action FormElement a few custom SQL command can be fired (sqlBefore, sqlAfter, sqlInsert, sqlUpdate, sqlDelete). To assist the application developer the slaveId concept automatically checks if a
sqlInsert or sqlUpdate has to be fired
or even a sqlDelete.
automatically update the named column.
For details see FormElement.parameter: slaveId
Variables
Form.parameter.fillStoreVar / FormElement.parameter.fillStoreVar
An SQL statement will fill STORE_VARS. Such values can be used during form load and/or save.
Action
Action FE
Via FormElement.parameter.requiredList an element can be enabled / disabled, depending of a user provided input in one of the specified required FEs.
Multi Form
Multi Forms are like a regular form with the difference that the shown FormElements are repeated for each selected record (defined by multiSql).
Name |
||
|---|---|---|
multiSql |
|
Query to select MultiForm records |
multiMsgNoRecord |
Default: No data |
Message shown if multiSql selects no records |
Important
Multi Form do not use ‘record-locking’ at all.
form.multiSql = <string> - Selects the records where the defined FormElements will work on each.
A uniq column id or _id (not shown) is mandatory and has to reference an existing record in table primary table.
Per row, the referenced record <primary table>.<id> is loaded in STORE_RECORD.
Columns starting with a ‘_’ are not rendered.
The optional special column _processRow will uncheck/check the Process Row checkbox during form load.
Per row, the STORE_PARENT is filled with the current record of multiSql. All columns can be retrieved via
{{<colname>:P}}form.parameter.fillStoreVar is fired only once, before multiSql is fired. If per row data is needed, multiSql can be used (access via STORE_PARENT).
form.parameter
Optionally, use multiFormAddRow to add a new row for saving a new record.
Optionally, use multiFormDeleteRow to delete a row if it is not saved in the database.
With these parameters, MultiForm will display the plus button and delete button.
(Multi) Form.parameter:
multiFormAddRow = 1
multiFormDeleteRow = 1
Layout: table
By default the form is shown as a HTML table:
Each multiSql record is table row.
Columns, defined in multiSql, will be shown on the form on the same line, before the FormElements.
Each defined FormElement is a separate column.
Process Row is supported.
Typically empty: F.parameter.multiFormWrap, F.parameter.multiFormCellWrap
F.parameter.multiFormTableClass: CSS class(es) for the
<table>element. Default:table table-multi-form qfq-table-100
Column Header Classes
Custom CSS classes can be assigned to individual column headers (<th>):
Via multiSql column alias: Append
|thClass=<class>to the column alias.Via FormElement.parameter: Add
thClass=<class>to the parameter field.
Column name syntax: [title=]<title>[|thClass=<class>]
This is useful for controlling tablesorter behavior (e.g. sorter-false, filter-false) or applying
Bootstrap alignment classes (e.g. text-center, text-right).
Example:
# In multiSql:
{{!SELECT id, name AS 'Name|thClass=text-center',
reference AS 'Reference|thClass=sorter-false filter-false'
FROM Grp WHERE grId='{{id:R0}}'}}
In FormElement.parameter:
thClass = sorter-false filter-false
Layout: CSS grid
multiSql records are rendered one after each other in HTML and the CSS will wrap them in as many columns as 1fr is specified.
F.parameter.multiFormWrap=<div style=”display: grid; grid-template-columns: 1fr 1fr 1fr 1fr”>
Specify as many 1fr as columns are wished.
F.parameter.multiFormCellWrap=<div> (or empty - the default is <div>)
FormElement.parameter.label2=`<custom text>` (e.g. for checkboxes>
Process Row
Activating processRow adds a checkbox to every row of the Multi Form, including the header. During save, only selected rows get processed.
The checkbox in the header selects all/none rows at once.
Form.parameter:
processRow = <string> - the value displayed in table header next to the checkbox.
Form.multiSql: If there is a column _processRow, value of 0/1 per row will control unchecked/checked during form load.
Implicit Multi Form mode
The following definition of Simple and Advanced is just for explanation, there is no flag or mode which has to be set. Also the Simple and Advanced variant can be mixed in the same Multi Form.
Simple
General:
All FormElements uses columns of the primary table.
QFQ handles all updates - that’s why it’s called Simple.
FormElement:
The FormElement.name represents a column of the defined primary table.
The existing values of such FormElements are automatically loaded.
No further definition (sqlInsert, sqlUpdate, …) is required.
Advanced
To handle foreign records (insert/update/delete), use the FormElement.parameter: slaveId concept.
Typically the FormElement.name is not a column of the primary table.
Example of how to edit the address of each person, saved in a separate address record:
# Iterate over all person in the database. If there are 10 persons in the table person, than 10 rows are shown.
Form.multiSql = {{!SELECT p.name, p.id AS _id FROM Person AS p}}
# Only one input field in this example: street. It's saved in table *Address*.
FE.name = myStreet
FE.type = text
# ``{{street:VE}}`` will be set via ``fillStoreVar``.
FE.value = {{street:VE}}
# Select the first address record owned by the current person.id = ``{{id:R}}`` ( or ``{{id:P}}``)
# Task 1: Get the ``slaveId`` (Address.id).
# Task 2: Get the value of column *street*.
FE.parameter.fillStoreVar={{!SELECT a.id AS aId, a.street FROM Address AS a WHERE a.personId={{id:R}} ORDER BY a.id LIMIT 1}}
# Set the slaveId. If there is no address, than {{aId:V}} doesn't exist and will be replaced by 0.
FE.parameter.slaveId={{aId:V0}}
# Update existing Address record.
FE.parameter.sqlUpdate={{UPDATE Address SET street='{{myStreet:FE:allbut}}' WHERE id={{slaveId:V}} }}
# Create new Address record.
FE.parameter.sqlInsert={{INSERT INTO Address (personId, street) VALUES ({{id:R0}}, '{{myStreet:FE:allbut}}') }}
# In case FE *myStreet* is empty, remove the whole Address record.
FE.parameter.sqlHonorFormElements = myStreet
FE.parameter.sqlDelete = {{DELETE FROM Address WHERE id={{slaveId:V}} }}
FormWizard
FormWizard is a tool in multiForm mode for quickly creating FormElements. The FormElements can be created on the fly with the plus Button, using the primary table and its columns to generate the FormElements. It can also delete FormElements that do not exist in the database. It uses the Process Row concept from MultiForm to save records.
Access
Access to formWizard requires that the form already exists. It can be found by clicking the “Wizard” button located in the Form “FormEditor”.
Default values
In FormWizard, the following values can be modified:
name: Uses the column name if it exists.
label: Sets the first letter from column name to uppercase.
type: checks the datatype to determine if it’s a default value.
mode: Default set to “show”.
sql1: No default value.
parameter: No default value.
pill: Displays the pill name as a reference.
Creating Pill
It is also possible to create a pill on the fly even if it does not exist. Simply write a pill name in the pill textbox, and it will create the pill and reference it to the FormElement.
Multiple languages
QFQ Forms might be configured for up to 5 different languages. Per language there is one extra field in the Form editor. Which field represents which language is configured in Configuration.
The Typo3 installation needs to be configured to handle different languages - this is independent of QFQ and not covered here. QFQ will use the Typo3 internal variable ‘pageLanguage’, which typically correlates to the URL parameter ‘L’ in the URL.
In Configuration the Typo3 language index (value of ‘L’) and a language label have to be configured for each language. Only than, the additional language fields in the Form editor will be shown.
Example
Assuming the Typo3 page has the
default language, L=0
English, L=1
Spanish, L=2
Configuration in Configuration:
formLanguageAId = 1
formLanguageALabel = English
formLanguageBId = 2
formLanguageBLabel = Spanish
The default language is not covered in Configuration.
The Form editor now shows on the pill ‘Basic’ (Form and FormEditor) for both languages each an additional parameter input field. Any input field in the Form editor can be redeclared in the corresponding language parameter field. Any missing definition means ‘take the default’. E.g.:
Form: ‘person’
Column
Value
title
Eingabe Person
languageParameterA
title=Input Person
languageParameterB
title=Persona de entrada
FormElement ‘firstname’ in Form ‘person’:
Column
Value
title
Vorname
note
Bitte alle Vornamen erfassen
languageParameterA
title=Firstnamenote=Please give all firstnameslanguageParameterB
title=Persona de entradanote=Por favor, introduzca todos los nombres
The following fields are possible:
Form: title, showButton, forwardMode, forwardPage, bsLabelColumns, bsInputColumns, bsNoteColumns, recordLockTimeoutSeconds
FormElement: label, mode, modeSql, class, type, subrecordOption, encode, checkType, ord, size, maxLength, bsLabelColumns, bsInputColumns, bsNoteColumns,rowLabelInputNote, note, tooltip, placeholder, value, sql1
Dynamic Update
The ‘Dynamic Update’ feature makes a form more interactive. If a user changes a FormElement who is tagged with ‘dynamicUpdate’, all elements who are tagged with ‘dynamicUpdate’, will be recalculated and re-rendered.
The following fields will be recalculated during ‘Dynamic Update’
‘modeSql’ - Possible values: ‘show’, ‘required’, ‘readonly’, ‘hidden’
‘label’
‘value’
‘note’
‘parameter.*’ - especially ‘itemList’
To make a form dynamic:
Mark all FormElements with dynamic update`=`enabled, which should initiate or receive updates.
See #3426 / Dynamic Update: Inputs loose the new content and shows the old value:
On all dynamic update FormElements an explicit definition of value, including a sanitize class, is necessary (except the field is numeric). A missing definition let’s the content overwrite all the time with the old value. A typical definition for value looks like (default store priority is: FSRVD):
{{<FormElement name>::alnumx}}Define the receiving FormElements in a way, that they will interpret the recent user change! The form variable of the specific sender FormElement {{<sender element>:F:<sanitize>}} should be part of one of the above fields to get an impact. E.g.:
[receiving *FormElement*].parameter: itemList={{ SELECT IF({{carPriceRange:FE:alnumx}}='expensive','Ferrari,Bugatti,Masarati','General Motors,Honda,Seat,Fiat') }}Remember to specify a ‘sanitize’ class - a missing sanitize class means ‘digit’, every content, which is not numeric, violates the sanitize class and becomes therefore an empty string!
If the dynamic update should work on existing and new records, it’s important to guarantee that the query result is not empty! even if the primary record does not exist! E.g. use a LEFT JOIN. The following query is ok for new and edit.
{{SELECT IF( IFNULL(adr.type,'') LIKE '%token%','show','hidden') FROM (SELECT 1) AS fake LEFT JOIN Address AS adr ON adr.type='{{type:FR0}}' LIMIT 1}}
Examples
Master FormElement ‘music’ is a radio/enum of ‘classic’, ‘jazz’, ‘pop’.
Content of a select list
Slave FormElement ‘interpret’ is ‘select’-list, depending of ‘music’
sql={{!SELECT name FROM Interpret WHERE music={{music:FE:alnumx}} ORDER BY name}}
Show / Hide a FormElement
Slave ‘interpret’ is displayed only for ‘pop’. Field ‘modeSql’:
{{SELECT IF( '{{music:FR:alnumx}}'='pop' ,'show', 'hidden' ) }}
Encryption
By activating encryption checkbox input of the form element will be first encrypted with AES (Advanced Encryption Standard) and then stored in database. You are able to select one of the available encryption methods. Encrypted data will be decrypted automatically and is shown readable in forms. From the beginning AES-128 is set as system wide default. Default can be changed via Extension Manager: QFQ Configuration.
If feature is used first time then you need so set up the encryption key: Encryption key
Important:
Database field type from form element must be VARCHAR or TEXT.
If VARCHAR is used it is necessary to have a length of minimum 70 characters.
Following encryption methods are currently available:
AES-128 - is faster than AES-256 and more efficient.
AES-256 - is more resistant than AES-128 to brute force attacks.
Please note that using a longer key can bring more security, too.
Form Layout
The forms will be rendered with Bootstrap CSS classes, based on the 12 column grid model (Bootstrap 3.x). Generally a 3 column layout for label columns on the left side, an input field column in the middle and a note column on the right side will be rendered.
The used default column (=bootstrap grid) width is 3,6,3 (col-md , col-lg) for label, input, note.
The system wide defaults can be changed via Configuration.
Per Form settings can be done in the Form parameter field. They overwrite the system wide default.
Per FormElement settings can be done in the FormElement parameter field. They overwrite the Form setting.
A column will be switched off (no wrapping via <div class=’col-md-?>) by setting a 0 on the respective column.
Custom field width
Per FormElement set BS Label Columns, BS Input Columns or BS Note Columns to customize an individual width. If only a number is specified, it’s used as col-md-<number>. Else the whole text string is used as CSS class, e.g. col-md-3 col-lg-2.
Multiple Elements per row
Every row is by default wrapped in a <div class=’form-group’> and every column is wrapped in a <div class=’col-md-?’>. To display multiple input elements in one row, the wrapping of the FormElement row and of the three columns can be customized via the checkboxes of Label / Input / Note. Every open and every close tag can be individually switched on or off.
E.g. to display 2 FormElements in a row with one label (first FormElement) and one note (last FormElement) we need the following (switch off all non named):
First FormElement
open row tag: row ,
open and close label tag: label, /label,
open and close field tag: input, /input,
Second FormElement
open and close field tag: input, /input,
open and close note tag: note, /note,
close row tag: /row ,
Copy Form
Records (=master) and child records can be duplicated (=copied) by a regular Form, extended by FormElements of type ‘paste’. A ‘copy form’ works either in:
‘copy and paste now’ mode: the ‘select’ and ‘paste’ Form is merged in one form, only one master record is possible,
‘copy now, paste later’ mode: the ‘select’ Form selects master record(s), the ‘paste’ Form paste’s them later.
Concept
A ‘select action’ (e.g. a Form or a button click) creates record(s) in the table Clipboard. Each clipboard record contains:
the ‘id(s)’ of the record(s) to duplicate,
the ‘paste’ form id (that Form defines, to which table the master records belongs to, as well as rules of how to duplicate any slave records) and where to copy the new records
user identifier (QFQ cookie) to separate clipboard records of different users inside the Clipboard table.
The ‘select action’ is also responsible to delete old clipboard records of the current user, before new clipboard records are created.
The ‘paste form’ iterates over all master record id(s) in the Clipboard table. For each master record id, all FormElements of type paste are fired (incl. the creating of slave records).
E.g. if there is a basket with different items and you want to duplicate the whole basket including new items, create a form with the following parameter
Form
Name: copyBasket
Table: Clipboard
Show Button: only close and save
FormElement 1: Record id of the source record.
Name: idSrc
Label: Source Form
Class: native
Type: select
sql1:
{{! SELECT id, title FROM Basket }}
FormElement 2: New name of the copied record.
Name: myNewName
Class: native
Type: text
FormElement 3: a) Check that there is no name conflict. b)Purge any old clipboard content of the current user.
Name: clearClipboard
Class: action
Type: beforeSave
Parameter:
sqlValidate={{!SELECT f.id FROM Form AS f WHERE f.name LIKE '{{myName:FE:alnumx}}' LIMIT 1}}expectRecords = 0alert = There is already a form with this namesqlAfter={{DELETE FROM Clipboard WHERE cookie='{{cookieQfq:C0:alnumx}}' }}
FormElement 4: Update the clipboard source reference, with current
{{cookieQfq:C}}identifier.Name: updateClipboardRecord
Class: action
Type: afterSave
Parameter:
sqlAfter={{UPDATE Clipboard SET cookie='{{cookieQfq:C0:alnumx}}', formIdPaste={{formId:S0}} /* PasteForm */ WHERE id={{id:R}} LIMIT 1 }}
FormElement 5: Copy basket identifier.
Name: basketId
Class: action
Type: paste
sql1:
{{!SELECT {{id:P}} AS id, '{{myNewName:FE:allbut}}' AS name}}Parameter: recordDestinationTable=Basket
FormElement 6: Copy items of basket.
Name: itemId
Class: action
Type: paste
sql1:
{{!SELECT i.id AS id, {{basketId:P}} AS basketId FROM Item AS i WHERE i.basketId={{id:P}} }}Parameter: recordDestinationTable=Item
Table self referencing records
Records might contain references to other records in the same table. E.g. native FormElements might assigned to a fieldSet, templateGroup or pill, a fieldSet might assigned to other fieldsets or pills and so on. When duplicating a Form and the corresponding FormElements all internal references needs to be updated as well.
On each FormElement.type=`paste` record, the column to be updated is defined via:
parameter: translateIdColumn = <column name>
For the ‘copyForm’ this would be ‘feIdContainer’.
The update of the records is started after all records have been copied (of the specific FormElement.type=`paste` record).
Delete Record
Deleting record(s) via QFQ might be solved by either:
using the delete button on a form on the top right corner.
by letting Report creating a special link (see below). The link contains the record id and:
a form name, or
a table name.
Deleting a record just by specifying a table name, will only delete the defined record (no slave records).
By using a delete button via report or in a subrecord row, a ajax request is send.
By using a delete button on the top right corner of the form, the form will be closed after deleting the record.
Example for report:
SELECT p.name, CONCAT('U:form=person&r=', p.id) AS _paged FROM Person AS p
SELECT p.name, CONCAT('U:table=Person&r=', p.id) AS _paged FROM Person AS p
To automatically delete slave records, use a form and create beforeDelete FormElement(s) on the form:
class: action
type: beforeDelete
parameter:
sqlAfter={{DELETE FROM <slaveTable> WHERE <slaveTable>.<masterId>={{id:R}} }}
You might also check the form ‘form’ how the slave records ‘FormElement’ will be deleted.
Best practice
View: List vs. Detail
As ‘list’ a number of data/rows shown on the page is meant.
As ‘detail’ a form is meant, which shows one single data record and let the user edit it.
To provide an easy understandable navigation structure, it’s nice for the user to stay on the same page, even the user is in ‘detail’ or ‘list’ mode. Create a single QFQ tt-content record on a fresh page:
form = {{form:SE}}
{
sql = SELECT p.name, CONCAT('p:{{pageSlug:T}}?form=Person&r=', p.id) AS _pagee FROM Person AS p
rend = <br>
}
If the page is called without any parameter, a list of persons is shown.
Behind each name, a button is shown. A click on it opens the form ‘Person’ (with the selected person record) on the same page.
Mode ‘list’ or ‘detail’ is detected automatically: if a form is given via STORE_SIP or STORE_TYPO3, the form (=detail) is shown else the report (=list).
Custom default value only for ‘new records’
Method 1
On Form.parameter define a fillStoreVar query with a column name equal to a form field. That’s all.
Example:
FormElement.name = technicalContact
Form.parameter.fillStoreVar = {{! SELECT CONCAT(p.firstName, ' ', p.name) AS technicalContact FROM Person AS p WHERE p.account='{{feUser:T}}' }}
What we use here is the default STORE prioritized FSRVD. If the form loads with r=0, ‘F’, ‘S’ and ‘R’ are empty. ‘V’ is filled. If r>0, than ‘F’ and ‘S’ are empty and ‘R’ is filled.
Method 2
In the specific FormElement set value={{columnName:RSE}}. The link to the form should be rendered with
‘”…&columnName=<data>&…” AS _page’. The trick is that the STORE_RECORD is empty for new records, and therefore the
corresponding value from STORE_SIP will be returned. Existing records will use the already saved value.
Central configured values
Any variable in Configuration can be used by {{<varname>:Y}} in form or report statements.
E.g.
TECHNICAL_CONTACT = jane.doe@example.net
Could be used in an FormElement.type = sendmail with parameter setting sendMailFrom={{TECHNICAL_CONTACT:Y}}.
Debug Report
Writing “report’s” in the nested notation or long queries broken over several lines, might not interpreted as wished. Best for debugging is to specify in the tt-content record:
debugShowBodyText = 1
Note: Debug information is only display if it’s enabled in Configuration by
showDebugInfo: yes or
showDebugInfo: auto and logged in in the same Browser as a Typo3 backend user.
More detailed error messages
If showDebugInfo is enabled, a full stacktrace and variable contents are displayed in case of an error.
Form search
QFQ content record:
# Creates a small form that redirects back to this page
{
sql = SELECT '_'
head = <form action='#' method='get'><input type='hidden' name='id' value='{{pageSlug:T}}'>Search: <input type='text' name='search' value='{{search:CE:all}}'><input type='submit' value='Submit'></form>
}
# SQL statement will find and list all the relevant forms - be careful not to open a cross site scripting door: the parameter 'search' needs to be sanitized.
{
sql = SELECT CONCAT('p:{{pageSlug:T}}?form=form&r=', f.id) AS _pagee, f.id, f.name, f.title
FROM Form AS f
WHERE f.name LIKE '%{{search:CE:alnumx}}%'
head = <table class='table'>
tail = </table>
rbeg = <tr>
rend = </tr>
fbeg = <td>
fend = </td>
}
Flexible (person) search
Action class f offers a powerful and easy to use MySQL search functionality (with a short query implementation).
In a given string any repetition of characters, that aren’t Unicode letters or numbers, are replaced by a single ‘%’.
# 'search' is a GET parameter.
SELECT ... FROM Person AS p
LEFT JOIN Address AS adr ON p.id=adr.pId
WHERE CONCAT(p.title, p.name, p.firstName, p.name, IFNULL(adr.email, '') ) LIKE '{{search:C:alnumx:f}}'
For person ‘John Doe’ (email: john@doe.com) the CONCAT() produces (note the doubling of ‘p.name’):
DrDoeJohnDoejohn@doe.com
Therefore the following search phrases will match. {{search:C:alnumx:f}} will be replaced as:
Input |
Final Pattern |
What matches |
Notes |
|---|---|---|---|
John Doe John Doe Doe John Doe, John J. Doe Doe J Dr. Doe Dr. Doe John Dr. John Doe john@doe.com |
%John% %Doe% %John%Doe% %Doe%John% %Doe%John% %J%Doe% %Doe%J% %Dr%Doe% %Dr%Doe%John% %Dr%John%Doe% %john%doe%com% |
DrDoe[John]Doejohn@doe.com DrDoeJohn[Doe]john@doe.com DrDoe[JohnDoe]john@doe.com Dr[DoeJohn]Doejohn@doe.com Dr[DoeJohn]Doejohn@doe.com DrDoe[J]ohn[Doe]john@doe.com Dr[DoeJ]ohnDoejohn@doe.com [DrDoe]JohnDoejohn@doe.com [DrDoeJohn]Doejohn@doe.com [Dr]Doe[JohnDoe]john@doe.com DrDoeJohnDoe[john@doe.com] |
— — — — , ` is replaced by a single `% - — — — — Email-based match |
That’s awesome!
That’s awesome!
Form: compute next free ‘ord’ automatically
Requirement: new records should automatically get the highest number plus 10 for their ‘ord’ value. Existing records should not be altered.
Version 1
Compute the next ‘ord’ in advance in the subrecord field of the primary form. Submit that value to the new record via SIP parameter to the secondary form.
On the secondary form: for ‘new’ records choose the computed value, for existing records leave the value unchanged.
Master form, subrecord FormElement, field parameter:
detail=id:formId,{{SELECT '&', IFNULL(fe.ord,0)+10 FROM Form AS f LEFT JOIN *FormElement* AS fe ON fe.formId=f.id WHERE
f.id={{r:S0}} ORDER BY fe.ord DESC LIMIT 1}}:ord
Slave form, ord FormElement, field value: set
`{{ord:RS0}}`.
Version 2
Compute the next ‘ord’ as default value direct inside the secondary form. No change is needed for the primary form.
Secondary form, ord FormElement, field value: set
{{SELECT IF({{ord:R0}}=0, MAX(IFNULL(fe.ord,0))+10,{{ord:R0}}) FROM (SELECT '') AS fake LEFT JOIN FormElement AS fe ON fe.formId={{formId:S0}} GROUP BY fe.formId}}.
Form: Person Wizard - firstname, city
Requirement: A form that displays the column ‘firstname’ from table ‘Person’ and ‘city’ from table ‘Address’. If the records not exist, the form should create it.
Form primary table: Person
Form slave table: Address
Relation: Person.id = Address.personId
Form: wizard
Name: wizard
Title: Person Wizard
Table: Person
Render: bootstrap
FormElement: firstname
Class: native
Type: text
Name: firstname
Label: Firstname
FormElement: email, text, 20
Class: native
Type: text
Name: city
Label: City
Value:
{{SELECT city FROM Address WHERE personId={{r}} ORDER BY id LIMIT 1}}
FormElement: insert/update address record
Class: action
Type: afterSave
Label: Manage Address
Parameter:
slaveId={{SELECT id FROM Address WHERE personId={{r}} ORDER BY id LIMIT 1}}sqlInsert={{INSERT INTO Address (personId, city) VALUES ({{r}}, '{{city:F:allbut:s}}') }}sqlUpdate={{UPDATE Address SET city='{{city:F:allbut:s}}' WHERE id={{slaveId:V}} }}sqlDelete={{DELETE FROM Address WHERE id={{slaveId:V}} AND ''='{{city:F:allbut:s}}' LIMIT 1}}
Form: Person Wizard - firstname, single note
Requirement: A form that displays the column ‘firstname’ from table ‘Person’ and ‘note’ from table ‘Note’. If the records don’t exist, the form should create it. Column Person.noteId points to Note.id
Form primary table: Person
Form slave table: Address
Relation: Person.id = Address.personId
Form: wizard
Name: wizard
Title: Person Wizard
Table: Person
Render: bootstrap
FormElement: firstname
Class: native
Type: text
Name: firstname
Label: Firstname
FormElement: email, text, 20
Class: native
Type: text
Name: note
Label: Note
Value:
{{SELECT Note FROM Note AS n, Person AS p WHERE p.id={{r}} AND p.noteId=n.id ORDER BY id }}
FormElement: insert/update address record
Class: action
Type: afterSave
Name: noteId
Label: Manage Note
Parameter:
sqlInsert={{INSERT INTO Note (note) VALUES ('{{note:F:allbut:s}}') }}sqlUpdate={{UPDATE Note SET note='{{note:F:allbut:s}}' WHERE id={{slaveId:V}} }}
Icons Template Group
This example will display graphics instead of text ‘add’ and ‘remove’. Also there is a distance between the templateGroups.
FormElement.parameter:
tgClass = qfq-child-margin-top tgAddClass = btn alert-success tgAddText = <span class="glyphicon glyphicon-plus" aria-hidden="true"></span> tgRemoveClass = btn alert-danger tgRemoveText = <span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
Chart
QFQ delivers a chart JavaScript lib: https://github.com/nnnick/Chart.js.git. Docs: http://www.chartjs.org/docs/
The library is not sourced in the HTML page automatically. To do it, either include the lib typo3conf/ext/qfq/Resources/Public/JavaScript/Chart.min.js:
in the specific tt_content record (shown below in the example) or
system wide via Typo3 Template record.
By splitting HTML and JavaScript code over several lines, take care not accidentally to create a ‘nesting’-end token. Check the line after 10.tail =. It’s ‘}’ alone on one line. This is a valid ‘nesting’-end token!. There are two options to circumvent this:
Don’t nest the HTML & JavaScript code - bad workaround, this is not human readable.
Select different nesting token, e.g. ‘<’ (check the first line on the following example).
# < < sql = SELECT '_' head = <div style="height: 1024px; width: 640px;"> <h3>Distribution of FormElement types over all forms</h3> <canvas id="barchart" width="1240" height="640"></canvas> </div> <script src="typo3conf/ext/qfq/Resources/Public/JavaScript/Chart.min.js"></script> <script> $(function () { var ctx = document.getElementById("barchart"); var barChart = new Chart(ctx, { type: 'bar', data: { tail = } }); }); </script> # Labels < sql = SELECT "'", fe.type, "'" FROM FormElement AS fe GROUP BY fe.type ORDER BY fe.type head = labels: [ tail = ], rsep = , > # Data 10.20 < sql = SELECT COUNT(fe.id) FROM FormElement AS fe GROUP BY fe.type ORDER BY fe.type head = datasets: [ { data: [ tail = ], backgroundColor: "steelblue", label: "FormElements" } ] rsep = , > >
Upload Form Simple
Table Person
Name |
Type |
|---|---|
id |
int |
name |
varchar(255) |
pathFileNamePicture |
varchar(255) |
pathFileNameAvatar |
varchar(255) |
Form:
Name: UploadSimple
Table: Person
FormElements:
Name: name
Type: text
Label: Name
Name: pathFileNamePicture
Type: upload
Label: Picture
Parameter:
fileDestination=fileadmin/user/{{id:R0}}-picture-{{filename}}
Name: pathFileNameAvatar
Type: upload
Label: Avatar
Parameter:
fileDestination=fileadmin/user/{{id:R0}}-avatar-{{filename}}
Upload Form Advanced 1
Table: Person
Name |
Type |
|---|---|
id |
int |
name |
varchar(255) |
Table: Note
Name |
Type |
|---|---|
id |
int |
pId |
int |
type |
varchar(255) |
pathFileName |
varchar(255) |
Form:
Name: UploadAdvanced1
Table: Person
FormElements
Name: name
Type: text
Label: Name
Name: mypathFileNamePicture
Type: upload
Label: Picture
Value:
{{SELECT pathFileName FROM Note WHERE id={{slaveId}} }}Parameter:
fileDestination=fileadmin/user/{{id:R0}}-picture-{{filename}}
slaveId={{SELECT id FROM Note WHERE pId={{id:R0}} AND type='picture' LIMIT 1}}
sqlInsert={{INSERT INTO Note (pathFileName, type, pId) VALUE ('{{fileDestination}}', 'picture', {{id:R0}}) }}
sqlUpdate={{UPDATE Note SET pathFileName = '{{fileDestination}}' WHERE id={{slaveId}} LIMIT 1}}
sqlDelete={{DELETE FROM Note WHERE id={{slaveId}} LIMIT 1}}
* Name: mypathFileNameAvatar
* Type: upload
* Label: Avatar
* Value: ``{{SELECT pathFileName FROM Note WHERE id={{slaveId}} }}``
* Parameter:
fileDestination=fileadmin/user/{{id:R0}}-avatar-{{filename}}
slaveId={{SELECT id FROM Note WHERE pId={{id:R0}} AND type='avatar' LIMIT 1}}
sqlInsert={{INSERT INTO Note (pathFileName, type, pId) VALUE ('{{fileDestination}}', 'avatar', {{id:R0}}) }}
sqlUpdate={{UPDATE Note SET pathFileName = '{{fileDestination}}' WHERE id={{slaveId}} LIMIT 1}}
sqlDelete={{DELETE FROM Note WHERE id={{slaveId}} LIMIT 1}}
Upload Form Advanced 2
Table: Person
Name |
Type |
|---|---|
id |
int |
name |
varchar(255) |
noteIdPicture |
int |
noteIdAvatar |
int |
Table: Note
Name |
Type |
|---|---|
id |
int |
pathFileName |
varchar(255) |
Form:
Name: UploadAdvanced2
Table: Person
FormElements
Name: name
Type: text
Label: Name
Name: mypathFileNamePicture
Type: upload
Label: Picture
Value:
{{SELECT pathFileName FROM Note WHERE id={{slaveId}} }}Parameter:
fileDestination=fileadmin/user/{{id:R0}}-picture-{{filename}}
slaveId={{SELECT id FROM Note WHERE id={{noteIdPicture}} LIMIT 1}}
sqlInsert={{INSERT INTO Note (pathFileName) VALUE ('{{fileDestination}}') }}
sqlUpdate={{UPDATE Note SET pathFileName = '{{fileDestination}}' WHERE id={{slaveId}} LIMIT 1}}
sqlDelete={{DELETE FROM Note WHERE id={{slaveId}} LIMIT 1}}
sqlAfter={{UPDATE Person SET noteIdPicture={{slaveId}} WHERE id={{id:R0}} LIMIT 1
* Name: mypathFileNameAvatar
* Type: upload
* Label: Avatar
* Value: ``{{SELECT pathFileName FROM Note WHERE id={{slaveId}} }}``
* Parameter:
fileDestination=fileadmin/user/{{id:R0}}-avatar-{{filename}}
slaveId={{SELECT id FROM Note WHERE id={{noteIdAvatar}} LIMIT 1}}
sqlInsert={{INSERT INTO Note (pathFileName) VALUE ('{{fileDestination}}') }}
sqlUpdate={{UPDATE Note SET pathFileName = '{{fileDestination}}' WHERE id={{slaveId}} LIMIT 1}}
sqlDelete={{DELETE FROM Note WHERE id={{slaveId}} LIMIT 1}}
sqlAfter={{UPDATE Person SET noteIdAvatar={{slaveId}} WHERE id={{id:R0}} LIMIT 1
Typeahead: SQL
Reference: Type Ahead
Table: Person
Name |
Type |
|---|---|
id |
int |
name |
varchar(255) |
Form:
Name: PersonNameTypeahead
Table: Person
FormElements
Name: name
Type: text
Label: Name
Parameter:
typeAheadSql = SELECT name FROM Person WHERE name LIKE ? OR firstName LIKE ? LIMIT 100
Typeahead: LDAP with additional values
Table: Person
Name |
Type |
|---|---|
id |
int |
name |
varchar(255) |
firstname |
varchar(255) |
varchar(255) |
Form:
Name: PersonNameTypeaheadSetNames
Table: Person
Parameter:
ldapServer = directory.example.com ldapBaseDn = ou=Addressbook,dc=example,dc=com
FormElements
Name: email
Class: native
Type: text
Label: Email
Note: Name:
{{cn:LE}}<br>Email: {{mail:LE}}dynamicUpdate: checked
Parameter:
# Typeahead typeAheadLdapSearch = (|(cn=*?*)(mail=*?*)) typeAheadLdapValuePrintf ‘%s / %s’, cn, email typeAheadLdapIdPrintf ‘%s’, email # dynamicUpdate: show note fillStoreLdap ldapSearch = (mail={{email::alnumx}}) ldapAttributes = cn, email
Name: fillLdapValues
Class: action
Type: afterSave
Parameter:
fillStoreLdap ldapSearch = (mail={{email::alnumx}}) ldapAttributes = cn, email slaveId={{id:R0}} sqlUpdate={{ UPDATE Person AS p SET p.name='{{cn:L:alnumx:s}}' WHERE p.id={{slaveId}} LIMIT 1 }}
Import/merge form
The form copyFormFromExt copies a form from table ExtForm / ExtFormElement to Form / FormElement. The import/merge form:
offers a drop down list with all forms of ExtForm,
an input element for the new form name,
create new Form.id
copied FormElements get the new Form.id.
the copied form will be opened in the FormEditor.
Installation:
Play (do all sql statements on your QFQ database, e.g. via mysql <dbname> < copyFormFromExt.sql or phpMyAdmin) the file <ext_dir>/Classes/Sql/copyFormFromExt.sql.
Insert a link/button ‘Copy form from ExtForm’ to open the import/merge form. A good place is the list of all forms (see FormEditor). E.g.: :
head = {{'b|p:{{pageSlug:T}}?form=copyFormFromExt|t:Copy form from ExtForm' AS _link }} ...
If there are several T3/QFQ instances and if forms should be imported frequently/easily, set up a one shot ‘import Forms from db xyz’ like:
{
sql = CREATE OR REPLACE table ExtForm SELECT * FROM <db xyz>.Form
}
{
sql = CREATE OR REPLACE table ExtFormElement SELECT * FROM <db xyz>.FormElement
}
Form As File
Json Form Editor
The JSON form editor allows developers to view/edit/copy/paste forms in the json format.
The JSON form editor can be accessed via the standard form-editor report. See: FormEditor
All fields of the Form and the FormElements Table are encoded into one big JSON object. Each formElement is represented as an object contained in the top-level array called FormElement_ff.
Form and FormElement ids are not encoded into the json string. Therefore the json may be freely copied and pasted i.e. reused without fear of overwriting the original form.
Container : Container FormElements are referenced via their name instead of their id by other FormElements. The additional key containerName_ff is added to the JSON of a FormElement to reference a container.
Form Backups : If a form is edited using the JSON form editor then a backup of the previous version is saved in the directory form/.backup inside the qfq project directory (qfq.project.path.php).
Cron job to remove old backups: Cron Job: Remove Form Backup Files
Json FormElement Editor
The JSON FormElement editor allows developers to view/edit/copy/paste FormElements in the json format.
The JSON FormElement editor can be accessed in the standard form-editor via the FormElements subrecord.
The FormElement is encoded into one JSON object.
FormElement ids are not encoded into the json string(formId, feIdContainer, feId). Therefore the json may be freely copied and pasted i.e. reused without fear of overwriting the original FormElement.
Auto Sync
ATTENTION : The synchronisation between form files and Form/FormElement tables has been disabled in favor of the JSON form editor. It might be enabled again in a future version.
Sync : Forms are synced between the database and form files located in the form directory contained in the qfq project directory. See: qfq.project.path.php
The form files have precedence over database forms. i.e. If the form has changed both in the file and database since the last sync, then the database changes are lost. (But there is a backup, see bellow).
If a form exists in the database but was never exported to a file (i.e. column fileStats in the Form table is empty) then the form is not deleted but kept and exported to a new form file.
ATTENTION : Form and FormElement changes in the database are only registered if they are performed by the form editor. Otherwise they might get overwritten during the next file sync!
First Export : If there is no form directory then it is created and all forms are exported from the database once.
Form Backups : On every change of a form (both in file and in database) a new backup file is saved in
qfqProject/form/.backupTo restore a backup file just copy it into the parent folder (form) and rename it to <formName>.json. (Don’t forget to adjust the file read/write rights accordingly)
Cron job to remove old backups: Cron Job: Remove Form Backup Files
Form Copy : To copy a form simply duplicate the corresponding form file <formName>.json and rename the duplicate accordingly <newFormName>.json>. (Don’t forget to adjust the file read/write rights accordingly)