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/index.php?id=home&form=Person or http://example.com/index.php?id=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/index.php?id=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.

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:

_images/FormProcess.png

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.

Comment- and space-character

The following applies to the fields Form.parameter and FormElement.parameter:

  • 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 \ .

Form Settings

Name

Description

Name

Unique and speaking name of the Form. Form will be identified by this name. Use only ‘[a-zA-Z0-9._+-]’. form-name

Title

Title, shown on/above the form. form-title

Note

Personal editor notes. form-note

Table

Primary table of the form. form-tablename

Primary Key

Primary key of the indicated table. Only needed if != ‘id’. form-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

  1. URL / Typo3 page slug or b) Forward Mode (via ‘{{…}}’) or combination of a) & b). See Forward: Save / Close.

labelAlign

Label align (default/left/center/right)/ Default: ‘default’ (defined by Config). form-label-align

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

showButton

Display or hide the button new, delete, close, save.

  • new: Creates a new record. If the form needs any special parameter via SIP or Client (=browser), hide this ‘new’ button - the necessary parameter are not provided.

  • delete: This either deletes the current record only, or (if defined via action FormElement ‘beforeDelete’ ) any specified subrecords.

  • close: Close the current form. If there are changes, a popup opens and ask to save / close / cancel. The last page from the history will be shown.

  • save: Save the form.

  • Default: show all buttons.

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 }}

Form.parameter

  • The following parameter are optional.

  • Each parameter has to be on a single line.

  • If a parameter is defined multiple time, the last one is the final one.

  • Comment lines have to start with #.

Name

Type

Description

dbIndex

int

Database credential index, given via config.qfq.php to let the current Form operate on the database.

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.: (mail={{email::alnumx:l}})

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.: (|(cn=*?*)(mail=*?*))

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

requiredPosition

int

See Required Position

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.

fieldsetClass

string

Overwrite default from Configuration.

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”>’

  • Example in field Form.parameter:

    maxVisiblePill = 5
    class = container-fluid
    classBody = qfq-form-right
    

submitButtonText

If specified and non empty, display a regular submit button at the bottom of the page with the given text. This gives the form a ordinary HTML-form look ‘n’ feel. With this option, the standard buttons on the top right border should be hidden to not confuse the user.

  • Optional.

  • Default: Empty

class

  • Optional.

  • Default: container

  • Any CSS class name(s) can be specified.

  • Check typo3conf/ext/qfq/Resources/Public/Css/qfq-bs.css for predefined classes.

  • Typical use: adjust the floating rules of the form.

classPill

  • Optional.

  • Default: qfq-color-grey-1

  • Any CSS class name(s) can be specified.

  • Check typo3conf/ext/qfq/Resources/Public/Css/qfq-bs.css for predefined classes.

  • Typical use: adjust the background color of the pill title area.

  • Predefined background colors: qfq-color-white, qfq-color-grey-1 (dark), qfq-color-grey-2 (light), qfq-color-blue-1 (dark), qfq-color-blue-2. (light)

  • classPill is only visible on forms with container elements of type ‘Pill’.

classBody

  • Optional.

  • Default: qfq-color-grey-2

  • Any CSS class name(s) can be specified.

  • Check typo3conf/ext/qfq/Resources/Public/Css/qfq-bs.css for predefined classes.

  • Typical use:

    • adjust the background color of the FormElement area.

    • make all form labels right align: qfq-form-right.

  • Predefined background colors: qfq-color-white, qfq-color-grey-1 (dark), qfq-color-grey-2 (light), qfq-color-blue-1 (dark), qfq-color-blue-2. (light)

extraDeleteForm

Depending on the database definition, it might be necessary to delete the primary record and corresponding slave records.

To not repeat such ‘slave record delete definition’, an ‘extraDeleteForm’ can be specified. If the user opens a record in a form and clicks on the ‘delete’ button, a defined ‘extraDeleteForm’-form will be used to delete primary and slave records instead of using the current form.

E.g. if there are multiple different forms to work on the same table, all of theses forms might reference to the same ‘extraDeleteForm’-form. This simplifies the maintenance.

The ‘extraDeleteForm’ parameter might be specified for a ‘form’ and/or for ‘subrecords’

See also: Delete Record.

btnPreviousNextSql

Generates two navigation buttons in the form title that link to the previous/next record.

Records are selected and ordered by a query which uses the columns ‘id’, ‘btnPrevious’ (optional), ‘btnNext’ (optional) and ‘btnCurrent’ (optional).

Optional columns can be used to style the buttons using the AS _link notation. ‘bntCurrent’ generates a third button in between the other two. Example:

# Only arrows, no text.
btnPreviousNextSql = {{!SELECT adr.id AS id FROM Address AS adr WHERE adr.personId={{personId:R}} }}

# Text
btnPreviousNextSql = {{!SELECT adr.id AS id
                               , CONCAT('p:{{pageSlug:T}}?form={{form:S}}&r=', adr.id, '|s|b|t:Previous: ', adr.city) AS btnPrevious
                               , CONCAT('p:{{pageSlug:T}}?form={{form:S}}&r=', adr.id, '|s|b|t:Next: ', adr.city) AS btnNext
                         FROM Address AS adr
                         WHERE adr.personId={{personId:R}} }}
  • btnPreviousNextLoop:

    • 0: no loop.

    • 1: Allow to loop through the records (from last to first and vice versa).

  • btnPreviousNextWrap: Wrap the buttons in custom HTML. Default: ‘<span class=”pull-right”><div class=”btn-group” role=”group”>’|

Form mode global

A form can be operated in modes: standard | readonly | requiredOff | requiredOffButMark.

Mode standard is given if none of the others are defined.

The mode is given via (in this priority):

  • Via STORE_USER: {{formModeGlobal:U}}

  • Via STORE_SIP: {{formModeGlobal:S}}

  • Via Form: form.parameter.formModeGlobal=…

Mode
  • standard:

    • The form will behave like defined in the form editor.

    • Missing required values will a) be indicated and b) block saving the record.

  • readonly: all FormElement of the whole form are temporarily in readonly mode.

    • Fast way to display the form data, without a possibility for the user to change any data of the form.

    • The mode will be be inherited to all subforms.

  • requiredOff:

    • All FormElement with mode=required, will be handled as mode=show.

    • The user can save the form without filling all required inputs!

    • Required inputs are indicated by a red star - missing values won’t be complained.

  • requiredOffButMark:

    • All FormElement with mode=required, will be handled as mode=show.

    • The user can save the form without filling all required inputs!

    • Missing required inputs will be marked:

      • On lost focus.

      • When the user saves the record.

        • After saving the record, by default the first pill with a missing input comes to front.

        • This behaviour can be switch on/off with form.parameter.activateFirstRequiredTab=0

Extra

Via Store: VARS - V the variable {{allRequiredGiven:V}} shows, if the form has been filled completely - independent of the mode. The variable is only accessible during form save.

Usage example

Readonly

Code: SELECT 'p:{{pageSlug}}?form=person&r=1&formModeGlobal=readonly|s|t:View|s|b' AS _link

  • The form is called with SIP parameter formModeGlobal=readonly or form.parameter.mode=readonly.

  • The user can’t change any data.

Readonly system wide

Code (somewhere): SELECT 'requiredOff' AS '_=formModeGlobal'

Code: SELECT 'p:{{pageSlug}}?form=person&r=1|s|t:View|s|b' AS _link

  • The STORE_USER variable is set formModeGlobal=readonly.

  • All forms will be shown in readonly mode - fast option to give a user access to the website, without the possibility to change any data.

Draft Mode 1

Code: SELECT 'p:{{pageSlug}}?form=person&r=1&formModeGlobal=requiredOff|s|t:View|s|b' AS _link

  • A form has one or more FormElement with ‘fe.type=required’.

  • Opening the form with formModeGlobal=requiredOff will allow the user to save the form, even if not all FE.type=required elements are given. This can be called ‘draft’ mode.

  • Opening the form without formModeGlobal (that’s the default), forces the user to fill out all FE.type=required fields. This can be called ‘final submit’ mode.

Draft Mode 2

Code: SELECT 'p:{{pageSlug}}?form=person&r=1&formModeGlobal=requiredOff|s|t:View|s|b' AS _link

  • A form has one or more FormElement with ‘fe.type=required’.

  • Calling the form with formModeGlobal=requiredOff will allow the user to save the form, even if not all FE.type=required elements are given.

  • Define an FE-Action ‘afterSave’, and do some action on {{allRequiredGiven:V0}} like:

    {{UPDATE <table> SET dataValid={{allRequiredGiven:V0}} WHERE id={{id:R}} }}
    
  • In the application logic, open the next process step if all data is given by evaluating dataValid.

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.

  • Technical, it’s not necessary to 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 ).

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: technically it’s like HTML/CSS disabled.

      • 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

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:

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

10.sql = SELECT ...

20 {
  sql = SELECT ...
  5.sql = SELECT ...
}

FormElement.parameter

  • The following parameter are optional.

  • Each parameter has to be on a single line.

  • If a parameter is defined multiple time, the last one is the final one.

  • Comment lines have to start with #.

  • See also documentation 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, fileDestination, fileTrash, fileTrashText, fileReplace, autoOrient, autoOrientCmd, autoOrientMimeType, chmodFile, chmodDir, slaveId, sqlBefore, sqlInsert, sqlUpdate, sqlDelete, sqlAfter, importToTable, importToColumns, importRegion, importMode, importType, importNamedSheetsOnly, importSetReadDataOnly, importListSheetNames,

See Type: upload

checkBoxMode checked unchecked label2 itemList emptyHide emptyItemAtStart emptyItemAtEnd buttonClass

See Type: checkbox, Type: radio, Type: select

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, editorType, codemirror-mode, codemirror-lineNumbers, codemirror-lineWrapping, codemirror-tabSize, codemirror-styleActiveLine, codemirror-matchBrackets, codemirror-autoCloseBrackets, codemirror-keywords-qfq-base

See Type: editor

fileButtonText

Overwrite default ‘Choose File’

fillStoreVar

Fill the STORE_VAR with custom values. See Store: VARS - V.

form, page, title, 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 retype

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, 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

See FormElement.parameter: sqlValidate

dataReference

Optional. See Application Test

requiredPosition

See Required Position.

indicateRequired

By default, indicate ‘required’ by an asterisk. indicateRequired=0 will hide the asterisk. Default: 1

minWidth

See Checkbox / Radio: 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.

  • s/d/n: string or date or number.

slaveId, sqlBefore, sqlAfter, …

See FormElement.parameter: slaveId

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’.

extraButtonLock
  • The user has to click on the lock, before it’s possible to change the value. This will protect data against unwanted modification.

  • After Form load, the value is shown, but not editable.

  • Shows a ‘lock’ on the right side of an input element of type text, date, time or datetime.

  • This option is not available for FormElements with mode=readonly.

  • There is no value needed for this parameter.

extraButtonPassword
  • The user has to click on the eye (un-hide) to see the value.

  • After Form load, the data is hidden by asterisk.

  • Shows an ‘eye’ on the right side of an input element of type text, date, time or datetime.

  • There is no value needed for this parameter.

extraButtonInfo
  • After Form load, the info button/icon is shown but the information message is hidden.

  • The user has to click on the info button/icon to see an additional message.

  • The value of this parameter is the text shown to the user.

  • Shows an info button/icon, depending of extraButtonInfoPosition in Configuration

    • auto, depending on FormElement type:

      • on the right side of an input element for type text, date, time or datetime,

      • below the FormElement for all other types.

    • below: below the FormElement for all types.

  • extraButtonInfoMinWidth: default is 250 and defines a minimal width.

  • For FormElement with mode below, a span element with the given class in extraButtonInfoClass (FE, F, Configuration) will be applied. E.g. this might be pull-right to align the info button/icon on the right side below the input element.

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.

Checkbox / Radio: minWidth

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.

Required Position

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,orange

        • 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`

    • 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.

  • 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: date

Actually datetimepicker is used as default. For more options see Installation_datetimepicker

Type: datetime

  • Range datetime: ‘1000-01-01 00:00:00’ to ‘9999-12-31 23:59:59’ or ‘0000-00-00 00:00:00’. (http://dev.mysql.com/doc/refman/5.5/en/datetime.html)

  • Optional:

    • FormElement.parameter:

      • dateFormat = YYYY-MM-DD | DD.MM.YYYY

      • showSeconds = 0|1 - shows the seconds. Independent if the user specifies seconds, they are displayed ‘1’ or not ‘0’.

      • showZero = 0|1 - For an empty timestamp, With ‘0’ nothing is displayed. With ‘1’ the string ‘0000-00-00 00:00:00’ is displayed.

Actually datetimepicker is used as default. For more options see Installation_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.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
  • 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 ‘%<?>%’ …

    • typeAheadSqlPrefetch = SELECT firstName, ' ', lastName FROM Person WHERE id = ?

      • If the query returns several results, only the first one is returned and displayed.

      • If the query selects multiple columns, the columns are concatenated.

    • typeAheadInitialSuggestion = {{!SELECT fr.id AS id, fr.name AS value FROM Fruit AS fr}}

      • Shows suggestions when the input element gets the focus, before the user starts to type anything.

      • If given, typeAheadMinLength will be set to 0.

      • Limit the number of rows via SQL ... LIMIT ... clause.

LDAP

See Typeahead (TA) - 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 …}}

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}} }}

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.tinymce.com, community edition) 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.

  • The default setting in FormElement.parameter is:

    editor-plugins=code link lists searchreplace table textcolor textpattern visualchars
    editor-toolbar=code searchreplace undo redo | styleselect link table | bullist numlist outdent indent | forecolor backcolor bold italic editor-menubar=false
    editor-statusbar=false
    
  • To activate drag and drop option for images in TinyMCE add ‘image,paste’ to editor-plugins. Example:

    editor-plugins=code link lists searchreplace table textcolor textpattern visualchars image,paste
    
  • To deactivate the surrouding <p> tag, configure in FormElement.parameter:

    editor-forced_root_block = false
    

    This might have impacts on the editor. See https://www.tinymce.com/docs/configure/content-filtering/#forced_root_block

  • 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 = true
    
  • Set ‘editor-content_css’ to use a custom CSS to style elements inside the editor. Example:

    editor-content_css = fileadmin/custom.css
    
  • Set own base path for image upload. Default is fileadmin/imageUploadDir Example:

    fileUploadPath = fileadmin/.../...
    
  • 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 = codemirror
    
  • For 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,img
    
  • By default every html tag is allowed. List with tags and their automatically associated tags htmlAllow

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

<https://gravatar…>

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 an empty ‘imageCut’ element.

  • 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: 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 retype.

  • extraButtonPassword.

Type: radio

  • Radio Buttons will be built from one of three sources:

    1. ‘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.

    2. 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

    3. 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}}

      • 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.size = <value>

    • <value>: <empty>|0|1: drop-down list.

    • <value>: >1: Select field with size rows height. Multiple selection of items is possible.

  • 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.

    • 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.

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][|_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

      • _rowTooltip

        • Defines the title attribute (=tooltip) of a subrecord table row.

      • 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', 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

        1. the primary form,

        1. the current row,

        1. 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-50
      
    • Tablesorter in Subrecord:

      subrecordTableClass = table table-hover qfq-subrecord-table tablesorter tablesorter-pager tablesorter-filter
      
    • subrecordTableAttribute: 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 = myClass
      
    • subrecordAppendForm: Optional. Use specified form for append rows. If not given, form from regular rows is used:

      subrecordAppendForm = address
      
    • subrecordAppendExtraDeleteForm: Optional. Use specified form for deleting append rows. If not given, form from append or regular rows is used:

      subrecordAppendExtraDeleteForm = address2
      
    • subrecordEmptyText: Optional. Define the text displayed when subrecord has no records:

      subrecordEmptyText = my custom text
      

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’ or ‘00:00:00’. (http://dev.mysql.com/doc/refman/5.5/en/datetime.html)

  • Optional:

  • FormElement.parameter

    • showSeconds = 0|1 - shows the seconds. Independent if the user specifies seconds, they are displayed ‘1’ or not ‘0’.

    • showZero = 0|1 - For an empty timestamp, With ‘0’ nothing is displayed. With ‘1’ the string ‘00:00[:00]’ is displayed.

Actually datetimepicker is used as default. For more options see Installation_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.

FormElement.parameter

  • fileButtonText: Overwrite default ‘Choose File’

  • 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 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 media types 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.

    • If for a specific file type is no mime type available, the definition of file extension(s) is possible. This is less secure, cause there is no content check on the server after the upload.

    • List of extensions automatically converted to their mimetypes

    .pdf,.jpg,.jpeg,.png,.gif,.svg,.txt,.doc,.docx,.xls,.xlsx,.mp3,.wav,.ogg,.mp4,.avi,.mov,.wmv,.xml,.py,.r,.rmd

    • All other file types have to be specified via their mimetypes.

  • 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.

  • 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

      Upload simple mode

      Upload advanced mode

      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.

  • downloadButton = t:<string> - If given, shows a button to download the previous uploaded file - instead of the string given in fe.value. The button is only shown if fe.value points to a readable file on the server.

    • If downloadButton is empty, just shows the regular download glyph.

    • To just show the filename: downloadButton = t:{{filenameOnly:V}} (see Store: VARS - V)

    • Additional attributes might be given like downloadButton = t:Download|o:check file|G:0. Please check Download.

  • fileUnzip - If the file is a ZIP file (only then) it will be unzipped. If no directory is given via fileUnzip, the basedir of fileDestination is taken, appended by unpack.

    If an unzip will be done, for each file of the archive STORE_VAR will be filled (name, path of the extracted file, mime type, size) and the following will be triggered: sqlValidate, slaveId, sqlBefore, sqlAfter, sqlInsert, sqlUpdate.

    Example:

    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 = v1|v2 - v2 is default

    • Upload Dialoag v1 is based on jQWidget and is not drag’n’drop capable.

    • Upload Dialoag v2 (default) is based on Filepond and is drag’n’drop capable.

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. Slave records, which might be also deleted through a ‘delete’-form, are not checked for file references and therefore such files are not deleted on the filesystem.

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.

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 FormElement.parameter: slaveId. 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 }}
      

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.

  • 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’:

One use case why to split an upload: annotate individual pages by using the FormElement.type=`annotate`.

Type: chat

The FormElement Chat together with the WebSocket 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 = 123 or slaveId = {{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.

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

{{!SELECT id, name FROM Person}}

Query to select MultiForm records

multiMgsNoRecord

Default: No data

Message shown if multiSql selects no records

Important

Multi Form do not use ‘record-locking’ at all.

  • 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 STORE_PARENT is filled with the current record of the primary table.

    • The optional special column _processRow will uncheck/check the processRow checkbox during form load.

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.

  • processRow is supported.

  • Typically empty: F.parameter.multiFormWrap, F.parameter.multiFormCellWrap

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.

  • processRow is not supported.

  • 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, than 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.

  • It’s not possible to create new records in simple mode, only existing records can be modified.

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}} }}

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=Firstname
    note=Please give all firstnames

    languageParameterB

    title=Persona de entrada
    note=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,Tesla,Jaguar','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 and default method

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

AES-128 is faster and more efficient.

  • AES-256

AES-256 is more resistant 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 = 0

      • alert = There is already a form with this name

      • sqlAfter={{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}}

10.sql = SELECT p.name, CONCAT('p:{{pageSlug:T}}?form=Person&r=', p.id) AS _pagee FROM Person AS p
10.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: 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: set

    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 1) AS a 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).

      # <
      
      10.sql = SELECT '_'
      10.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: {
      
      10.tail =
                }
            });
          });
        </script>
      
      # Labels
      10.10 <
        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

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)

email

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.:

    10.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:

10.sql = CREATE OR REPLACE table ExtForm SELECT * FROM <db xyz>.Form
20.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).

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/.backup

    • To 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)