.. ================================================== .. ================================================== .. ================================================== .. Header hierarchy .. == .. -- .. ^^ .. "" .. ;; .. ,, .. .. --------------------------------------------used to the update the records specified ------ .. Best Practice T3 reST: https://docs.typo3.org/m/typo3/docs-how-to-document/master/en-us/WritingReST/CheatSheet.html .. Reference: https://docs.typo3.org/m/typo3/docs-how-to-document/master/en-us/WritingReST/Index.html .. Italic *italic* .. Bold **bold** .. Code ``text`` .. External Links: `Bootstrap `_ .. Internal Link: :ref:`downloadButton` (default url text) or :ref:`download Button` (explicit url text) .. Add Images: .. image:: ./Images/a4.jpg .. .. .. Admonitions .. .. note:: .. important:: .. tip:: .. warning:: .. Color: (blue) (orange) (green) (red) .. .. Definition: .. some text becomes strong (only one line) .. description has to indented .. -*- coding: utf-8 -*- with BOM. .. include:: Includes.txt 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 :ref:`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 (.id= ) 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 :ref:`form-magic`. * Display a form: * Create a QFQ tt_content record on a Typo 3 page. * Inside the QFQ record: `form = `. 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 :ref:`formAsFile`. 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: .. image:: Images/FormProcess.png .. _record_locking: 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 :ref:`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-space-character: 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-main: 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 :ref:`form-requiredParameter` | +-------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------+ |Required Parameter EDIT | Name of required SIP parameter to edit an existing record (r>0), separated by comma. '#' as comment delimiter. See :ref:`form-requiredParameter` | +-------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------+ |Permit New | 'sip, logged_in, logged_out, always, never' (Default: sip): See :ref:`form-permitNewEdit` | +-------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------+ |Permit Edit | 'sip, logged_in, logged_out, always, never' (Default: sip): See :ref:`form-permitNewEdit` | +-------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------+ |Permit REST | See :ref:`restApi` | +-------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------+ |Escape Type Default | See :ref:`variable-escape`. | +-------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------+ |Show button | 'new, delete, close, save' (Default: 'new,delete,close,save'): Shown named buttons in the upper right corner of the form. | | | See :ref:`form-showButton` | +-------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------+ |Forward Mode | 'auto | close | no | url | url-skip-history | url-sip | url-sip-skip-history' (Default: auto): See :ref:`form-forward`. | +-------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------+ |Forward (Mode) Page | a) URL / Typo3 page slug or b) Forward Mode (via '{{...}}') or combination of a) & b). See :ref:`form-forward`. | +-------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------+ |labelAlign | Label align (default/left/center/right)/ Default: 'default' (defined by Config). _`form-label-align` | +-------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------+ |Parameter | Misc additional parameters. See :ref:`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 | |BS Input Columns | 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 :ref:`form-layout`. | |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 :ref:`multi-form-grid`. | +-------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------+ |multiFormCellWrap | By default (if empty) wraps each input in ``. If 'multiFormWrap' is given but `multiFormCellWrap` is empty, than the default is `
`. | | | See :ref:`multi-form-grid`. | +-------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------+ .. _`form-permitNewEdit`: 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. .. _`form-requiredParameter`: 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 .. _`form-showButton`: 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. .. _`form-forward`: 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: [] or [|] * ``: * `http://www.example.com/index.html?a=123#bottom` * `website.html?a=123#bottom` * `&a=123#bottom, ?id=&a=123#bottom` * `{{SELECT ...}}` * `|` * `` - 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=|` * `forwardPage={{SELECT IF(a.url='','no','url'), '|', a.url FROM Address AS a }}` .. _form-parameter: 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 :ref:`config-qfq-php` to let the current `Form` | | | | operate on the database. | +-----------------------------+--------+----------------------------------------------------------------------------------------------------------+ | bsColumns | string | Wrap the whole form in '
. See :ref:`bs-custom-field-width`. | +-----------------------------+--------+----------------------------------------------------------------------------------------------------------+ | maxVisiblePill | int | Show pills upto 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 :ref:`form-mode-global` | +-----------------------------+--------+----------------------------------------------------------------------------------------------------------+ | activateFirstRequiredTab | digit | 0: off, 1: (default) - with formModeGlobal=requiredOffButMark bring pill to front on save. | | | | See :ref:`form-mode-global` | +-----------------------------+--------+----------------------------------------------------------------------------------------------------------+ | enterAsSubmit | digit | 0: off, 1: Pressing *enter* in a form means *save* and *close*. Takes default from :ref:`configuration`. | +-----------------------------+--------+----------------------------------------------------------------------------------------------------------+ | submitButtonText | string | Show a save button at the bottom of the form, with . See :ref:`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 :ref:`configuration` | +-----------------------------+--------+----------------------------------------------------------------------------------------------------------+ | saveButtonTooltip | string | Overwrite default from :ref:`configuration` | +-----------------------------+--------+----------------------------------------------------------------------------------------------------------+ | saveButtonClass | string | Overwrite default from :ref:`configuration` | +-----------------------------+--------+----------------------------------------------------------------------------------------------------------+ | saveButtonGlyphIcon | string | Overwrite default from :ref:`configuration` | +-----------------------------+--------+----------------------------------------------------------------------------------------------------------+ | closeButtonText | string | Overwrite default from :ref:`configuration` | +-----------------------------+--------+----------------------------------------------------------------------------------------------------------+ | closeButtonTooltip | string | Overwrite default from :ref:`configuration` | +-----------------------------+--------+----------------------------------------------------------------------------------------------------------+ | closeButtonClass | string | Overwrite default from :ref:`configuration` | +-----------------------------+--------+----------------------------------------------------------------------------------------------------------+ | closeButtonGlyphIcon | string | Overwrite default from :ref:`configuration` | +-----------------------------+--------+----------------------------------------------------------------------------------------------------------+ | deleteButtonText | string | Overwrite default from :ref:`configuration` | +-----------------------------+--------+----------------------------------------------------------------------------------------------------------+ | deleteButtonTooltip | string | Overwrite default from :ref:`configuration` | +-----------------------------+--------+----------------------------------------------------------------------------------------------------------+ | deleteButtonClass | string | Overwrite default from :ref:`configuration` | +-----------------------------+--------+----------------------------------------------------------------------------------------------------------+ | deleteButtonGlyphIcon | string | Overwrite default from :ref:`configuration` | +-----------------------------+--------+----------------------------------------------------------------------------------------------------------+ | newButtonText | string | Overwrite default from :ref:`configuration` | +-----------------------------+--------+----------------------------------------------------------------------------------------------------------+ | newButtonTooltip | string | Overwrite default from :ref:`configuration` | +-----------------------------+--------+----------------------------------------------------------------------------------------------------------+ | newButtonClass | string | Overwrite default from :ref:`configuration` | +-----------------------------+--------+----------------------------------------------------------------------------------------------------------+ | newButtonGlyphIcon | string | Overwrite default from :ref:`configuration` | +-----------------------------+--------+----------------------------------------------------------------------------------------------------------+ | extraButtonInfoClass | string | Overwrite default from :ref:`configuration` | +-----------------------------+--------+----------------------------------------------------------------------------------------------------------+ | extraButtonInfoMinWidth | string | See :ref:`extraButtonInfo` | +-----------------------------+--------+----------------------------------------------------------------------------------------------------------+ | fillStoreVar | string | Fill the STORE_VAR with custom values. See :ref:`STORE_VARS`. | +-----------------------------+--------+----------------------------------------------------------------------------------------------------------+ | showIdInFormTitle | string | Overwrite default from :ref:`configuration` | +-----------------------------+--------+----------------------------------------------------------------------------------------------------------+ | formSubmitLogMode | string | Overwrite default from :ref:`configuration` | +-----------------------------+--------+----------------------------------------------------------------------------------------------------------+ | sessionTimeoutSeconds | int | Overwrite default from :ref:`configuration`. See :ref:`sessionTimeoutSeconds`. | +-----------------------------+--------+----------------------------------------------------------------------------------------------------------+ | maxFileSize | int | Overwrite default from :ref:`configuration` | +-----------------------------+--------+----------------------------------------------------------------------------------------------------------+ | requiredPosition | int | See :ref:`requiredPosition` | +-----------------------------+--------+----------------------------------------------------------------------------------------------------------+ | clearMe | 0 / 1 | Overwrite default from :ref:`configuration`. Show a small 'x' in every input or textarea to clear field. | +-----------------------------+--------+----------------------------------------------------------------------------------------------------------+ | rememberLastPill | 0 / 1 | Overwrite default from :ref:`configuration`. On form load, bring last used pill to front | +-----------------------------+--------+----------------------------------------------------------------------------------------------------------+ | doNotLogColumn | string | Overwrite default from :ref:`configuration`. Comma separated list of Form-Element names. | +-----------------------------+--------+----------------------------------------------------------------------------------------------------------+ | fieldsetClass | string | Overwrite default from :ref:`configuration`. | +-----------------------------+--------+----------------------------------------------------------------------------------------------------------+ | btnPreviousNextSql | string | Query that selects records which are then accessible by the previous/next buttons. | | | | See :ref:`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: '
'| +-----------------------------+--------+----------------------------------------------------------------------------------------------------------+ * Example in field Form.parameter:: maxVisiblePill = 5 class = container-fluid classBody = qfq-form-right .. _submitButtonText: 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. * See: http://getbootstrap.com/docs/3.4/css/#overview-container * Expand the form over the whole area: `container-fluid` 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: :ref:`delete-record`. .. _btnPreviousNext: 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: '
'| .. _form-mode-global: 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 :ref:`STORE_VARS` 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 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: * :ref:`class-container` * :ref:`class-native` * :ref:`class-action` * 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* ). * See also: :ref:`comment-space-character` .. _class-container: 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*: `` - 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: :ref:`example_class_template_group` Column: primary record """""""""""""""""""""" If the columns `%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: Class: Native ------------- Fields: +---------------------+-----------------------------+-----------------------------------------------------------------------------------------------------+ | Name | Type | Description | +=====================+=============================+=====================================================================================================+ |Container | int | 0 or *FormElement*.id of container element (pill, fieldSet, templateGroup) part the current *Form* | +---------------------+-----------------------------+-----------------------------------------------------------------------------------------------------+ |Enabled | enum('yes'|'no') | Process the current FormElement | +---------------------+-----------------------------+-----------------------------------------------------------------------------------------------------+ |Dynamic Update | enum('yes'|'no') | In the browser, *FormElements* with "dynamicUpdate='yes'" will be updated depending on user | | | | input. :ref:`dynamic-update` | +---------------------+-----------------------------+-----------------------------------------------------------------------------------------------------+ |Encryption | enum('yes'|'no') | Encryption for the user input can be activated. :ref:`encryption` | +---------------------+-----------------------------+-----------------------------------------------------------------------------------------------------+ |Name | string | | +---------------------+-----------------------------+-----------------------------------------------------------------------------------------------------+ |Label | string | Label of *FormElement*. Depending on layout model, left or on top of the *FormElement* | | | | Additional label description can be added by wrapping in HTML tag '' | +---------------------+-----------------------------+-----------------------------------------------------------------------------------------------------+ |Mode | enum('show', 'readonly', | | *Show*: regular user input field. This is the default. | | | 'required', | | *Required*: User has to specify a value. Typically, an represents 'no value'. | | | 'hidden' ) | | *Readonly*: User can't change. Data is not saved, except for FormElement with 'processReadOnly' | | | | | *Hidden*: *FormElement* is not visible. | +---------------------+-----------------------------+-----------------------------------------------------------------------------------------------------+ |Mode sql | `SELECT` statement with | A value given here overwrites the setting from `mode`. Most useful with :ref:`dynamic-update`. | | | a value like in `mode` | E.g.: {{SELECT IF( '{{otherFunding:FR:alnumx}}'='yes' ,'show', 'hidden' }} | +---------------------+-----------------------------+-----------------------------------------------------------------------------------------------------+ |Class | enum('native', 'action', | Details below. | | | 'container') | | +---------------------+-----------------------------+-----------------------------------------------------------------------------------------------------+ |Type | enum('checkbox', 'date', 'time', 'datetime', 'dateJQW', 'datetimeJQW', 'extra', 'gridJQW', 'text', 'editor', 'annotate', | | | 'imageCut', 'note', 'password', 'radio', 'select', 'subrecord', 'upload', 'fieldset', 'pill', 'beforeLoad', 'beforeSave', | | | 'beforeInsert', 'beforeUpdate', 'beforeDelete', 'afterLoad', 'afterSave', 'afterInsert', 'afterUpdate', 'afterDelete', | | | 'sendMail') | +---------------------+-----------------------------+-----------------------------------------------------------------------------------------------------+ |Encode | 'none', 'specialchar' | With 'specialchar' (default) the chars <>"'& will be encoded to their html entity. _`field-encode` | +---------------------+-----------------------------+-----------------------------------------------------------------------------------------------------+ |Check Type | enum('auto', 'alnumx', | See: :ref:`sanitize-class` | | | 'digit', 'numerical', | | | | 'email', 'pattern', | | | | 'allbut', 'all') | | +---------------------+-----------------------------+-----------------------------------------------------------------------------------------------------+ |Check Pattern | 'regexp' | _`field-checktype`: If $checkType=='pattern': pattern to match | +---------------------+-----------------------------+-----------------------------------------------------------------------------------------------------+ |Order | string | Display order of *FormElements* ('order' is a reserved keyword) _`field-ord` | +---------------------+-----------------------------+-----------------------------------------------------------------------------------------------------+ |labelAlign | left | Label align (default/left/center/right)/ Default: 'default' (defined by Form). | +---------------------+-----------------------------+-----------------------------------------------------------------------------------------------------+ |Size | string | Depends on the FormElement type. E.g. visible length (and height) of input | | | | element :ref:`input-text`. Might be omitted, depending on the chosen form layout. | | | | Format: [,<(min) height>[,) data of the specified record (`SELECT FROM 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:: {{:R:::}} 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 :ref:`report-notation`. .. _`report-notation`: FormElement: 'Report' notation ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The FE fields 'value' and 'note' understand the :ref:`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 ... } .. _fe-parameter-attributes: 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 :ref:`input-option-autofocus` | +---------------------------------+----------------------------------------------------------------------------------------------------------+ | capture, | See :ref:`input-upload` | | 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, | | +---------------------------------+----------------------------------------------------------------------------------------------------------+ | checkBoxMode | See :ref:`input-checkbox`, :ref:`input-radio`, :ref:`input-select` | | checked | | | unchecked | | | label2 | | | itemList | | | emptyHide | | | emptyItemAtStart | | | emptyItemAtEnd | | | buttonClass | | +---------------------------------+----------------------------------------------------------------------------------------------------------+ | 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 :ref:`extraButtonLock` | +---------------------------------+----------------------------------------------------------------------------------------------------------+ | extraButtonPassword | [0|1] Show an 'eye' on the right side of the input element. See :ref:`extraButtonPassword` | +---------------------------------+----------------------------------------------------------------------------------------------------------+ | extraButtonInfo | Text. Show an 'i' on the right side of the input element. See :ref:`extraButtonInfo` | +---------------------------------+----------------------------------------------------------------------------------------------------------+ | extraButtonInfoClass | By default empty. Specify any class to be assigned to wrap extraButtonInfo | +---------------------------------+----------------------------------------------------------------------------------------------------------+ | extraButtonInfoMinWidth | See :ref:`extraButtonInfo` | +---------------------------------+----------------------------------------------------------------------------------------------------------+ | editor-plugins, | See :ref:`input-editor` | | 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 | | +---------------------------------+----------------------------------------------------------------------------------------------------------+ | fileButtonText | Overwrite default 'Choose File' | +---------------------------------+----------------------------------------------------------------------------------------------------------+ | fillStoreVar | Fill the STORE_VAR with custom values. See :ref:`STORE_VARS`. | +---------------------------------+----------------------------------------------------------------------------------------------------------+ | form, | See :ref:`subrecord-option` | | page, | | | extraDeleteForm, | | | detail, new, | | | subrecordTableClass, | | | subrecordTableAttribute, | | | subrecordColumnTitleEdit, | | | subrecordColumnTitleDelete, | | | subrecordAppendSql, | | | subrecordAppendClass, | | | subrecordAppendForm, | | | subrecordAppendExtraDeleteForm, | | | subrecordAppendEmptyText, | | | orderInterval, | | | dndTable, | | | orderColumn | | +---------------------------------+----------------------------------------------------------------------------------------------------------+ | min s/d/n | Minimum and/or maximum allowed values for input field. Can be used for numbers, dates, or strings. | +---------------------------------+ | | max s/d/n | *Always use the international format 'yyyy-mm-dd[ hh:mm[:ss]]* | +---------------------------------+----------------------------------------------------------------------------------------------------------+ | 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 :ref:`processReadOnly`. | +---------------------------------+----------------------------------------------------------------------------------------------------------+ | retype, | See :ref:`retype` | | retypeLabel, | | | retypeNote | | +---------------------------------+----------------------------------------------------------------------------------------------------------+ | characterCountWrap, | See :ref:`input-text` | | hideZero | | | inputType, step, | | | textareaResize, htmlAllow | | +---------------------------------+----------------------------------------------------------------------------------------------------------+ | emptyMeansNull | Applies to all native FormElement types (input, checkbox, radio, select, ...). See :ref:`input-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, | See :ref:`input-typeahead` | | typeAheadInitialSuggestion, | | | typeAheadMinLength, | | | typeAheadSql, | | | typeAheadSqlPrefetch, | | | typeAheadPedantic | | +---------------------------------+----------------------------------------------------------------------------------------------------------+ | typeAheadTag, | See :ref:`type_ahead_tag` | | typeAheadGlueInsert, | | | typeAheadGlueDelete, | | | typeAheadTagInsert, | | | typeAheadTagDelimiter | | +---------------------------------+----------------------------------------------------------------------------------------------------------+ | wrapRow | If specified, skip default wrapping (`
`). 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, | See :ref:`sqlValidate` | | expectRecords, | | | alert, | | | qfqLog, | | | requiredList | | +---------------------------------+----------------------------------------------------------------------------------------------------------+ | dataReference | Optional. See :ref:`applicationTest` | +---------------------------------+----------------------------------------------------------------------------------------------------------+ | requiredPosition | See :ref:`requiredPosition`. | +---------------------------------+----------------------------------------------------------------------------------------------------------+ | indicateRequired | By default, indicate 'required' by an asterisk. indicateRequired=0 will hide the asterisk. Default: 1 | +---------------------------------+----------------------------------------------------------------------------------------------------------+ | minWidth | See :ref:`checkboxRadioMinWidth`. | +---------------------------------+----------------------------------------------------------------------------------------------------------+ | clearMe | 0 (off)|1(on) - Overwrite default from Form.parameter.clearMe or :ref:`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 :ref:`slave-id` Native *FormElements* """"""""""""""""""""" * Like 'input', 'checkbox', ... .. _`input-option-autofocus`: 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`: 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`: 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`: 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 :ref:`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, :ref:`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`: 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. .. _`checkboxRadioMinWidth`: 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. .. _`requiredPosition`: 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. .. _`input-checkbox`: 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* = (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* = (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* = (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`:: {{: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 ^^^^^^^^^^ * Range datetime: '1000-01-01' to '9999-12-31' or '0000-00-00'. (http://dev.mysql.com/doc/refman/5.5/en/datetime.html) * Optional: * *FormElement.parameter.dateFormat*: YYYY-MM-DD | DD.MM.YYYY Actually datetimepicker is used as default. For more options see :ref:`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 :ref:`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). .. _`input-text`: Type: text ^^^^^^^^^^ General input for any text. .. _`field-size`: * 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* = [[,[,]]] * The parameter is optional and controls the behaviour of the input / textarea element. * The `` 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. * ``: * Counted as 'lines'. * If not set the height is treated as 1. * A `` of 1 forces an one line input. Exception: `` > 0 enables `auto-grow`. * ``: * 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 ``. * If = 0, the `auto-grow` mode is disabled. * *FormElement.parameter*: .. _`retype`: * *retype* = 1 (optional): Current input element will be rendered twice. The form can only submitted if both elements are equal. * *retypeLabel* = (optional): The label of the second element. * *retypeNote* = (optional): The note of the second element. * *characterCountWrap* = ``Count: | `` (optional). Displays a character counter below the input/textarea element. * Also check the :ref:`fe-parameter-attributes` *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`: * *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 .. _`input-typeahead`: 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* = . Max numbers of result records to be shown. Default is 20. * *typeAheadMinLength* = . 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 :ref:`LDAP_Typeahead` .. _`type_ahead_tag`: 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*. .. _`ta_mode_tag`: Mode: Tag ;;;;;;;;; Tags will be loaded and saved as a comma separated list. Maximum length of saved tags is limit by the size of the column (incl. separator). 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). .. _`ta_mode_glue`: 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}}*). .. _`input-editor`: 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: * https://www.tinymce.com/docs/configure/, * https://www.tinymce.com/docs/plugins/, * https://www.tinymce.com/docs/advanced/editor-control-identifiers/#toolbarcontrols * 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 `

` 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* = : 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* = ,: 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 :ref:`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 :ref:`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 | | Background image. E.g. `fileadmin/images/scan.png` | +-------------------+-----------------------+----------------------------------------------------------------------------------+ | defaultPenColor | | 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 :ref:`setup-css-js`. Code """" **Form.parameter** +--------------------+-----------------------+----------------------------------------------------------------------------------+ | Attribute | Value | Description | +====================+=======================+==================================================================================+ | annotateType | text | *grafic|text*. Default is *grafic*. Select mode. | +--------------------+-----------------------+----------------------------------------------------------------------------------+ | textSource | | Text file to annotate. | +--------------------+-----------------------+----------------------------------------------------------------------------------+ | annotateUserName | | Will be shown at annotation line. | +--------------------+-----------------------+----------------------------------------------------------------------------------+ | annotateUserUid | <123> | Will be shown at annotation line. | +--------------------+-----------------------+----------------------------------------------------------------------------------+ | annotateUserAvatar | | Will be shown at annotation line. | +--------------------+-----------------------+----------------------------------------------------------------------------------+ | highlight | auto | off,auto,javascript,qfq,python,matlab | +--------------------+-----------------------+----------------------------------------------------------------------------------+ Type: imageCut ^^^^^^^^^^^^^^ Uploaded images can be cut or rotate via QFQ (via fabric.js). The modified image is saved under the given pathFileName. * The 'value' of the `FormElement` has to be a valid PathFileName to an image. * Valid image file formats are SVG, PNG, JPG, GIF. * Invalid or missing filenames results to an empty 'imageCut' element. * *FormElement.parameter*: * *resizeWidth* = |[width in pixel] - the final width of the modified image. If empty (or not given), no change. * *keepOriginal* = |[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 <.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`_. * :ref:`extraButtonPassword`. .. _`input-radio`: 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* = `` 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* = `` * Applies only to 'plain' radio elements (not the Bootstrap 'buttonClass' from below) * *vertical* or *horizontal* alignment: * ``: '', 0, 1 - The radios will be aligned *vertical*. * ``: >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* = - 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`:: {{: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. .. _`input-select`: Type: select ^^^^^^^^^^^^ * Select lists will be built from one of three sources: * *FormElement.sql1* = `{{!> first column represents *label* * Two or more columns in resultset >> first column represents *id* and second column represents *label*. * *FormElement.parameter*: * *itemList* = `` - 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* = `` * ``: |0|1: drop-down list. * ``: >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. .. _`subrecord-option`: 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* = `` * *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=][|[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 :ref:`qfq-icons`. * *link*: value will be rendered as described under :ref:`column-link` * *url*: value will be rendered as a href url. * *mailto*: value will be rendered as a href mailto. * *_rowEdit*: per row controlled edit link. Use regular *... AS _link* syntax. Specify only those parameter which has to be row specific - all other will be used from generic render process. * *_rowDelete*: per row controlled edit link. Use regular *... AS _link* syntax. Specify only those parameter which has to be row specific - all other will be used from generic render process.. E.g. with *renderMode* (r:3) it's possible to prevent specific rows to be deleted. * *_rowClass* * The value is a CSS class name(s) which will be rendered in the *<tr class="<_rowClass>">* of the subrecord table. * The column itself is not rendered. * By using Bootstrap, the following predefined classes are available: * Text color: *text-muted|text-primary|text-success|text-info|text-warning|text-danger* (http://getbootstrap.com/docs/3.4/css/#helper-classes) * Row background: *active|success|info|warning|danger* (http://getbootstrap.com/docs/3.4/css/#tables-contextual-classes) * *_rowTooltip* * Defines the title attribute (=tooltip) of a subrecord table row. * 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 * a) the primary form, * b) the current row, * c) 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 :ref:`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 :ref:`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 :ref:`Installation_datetimepicker` .. _`input-upload`: 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 :ref:`STORE_VARS`) * ``{{SELECT SUBSTRING_INDEX( '{{pathFileName:R}}', '/', -1) }}`` See also :ref:`download Button<downloadButton>` 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 :ref:`STORE_VARS`) 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 :ref:`STORE_VARS`. * 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 :ref:`SecureDirectFileAccess`. * Keywords available in: +-----------+----------------------------+-----------------------------+ | Available | :ref:`Upload simple mode` | :ref:`Upload advanced mode` | +===========+============================+=============================+ | slaveId | - | yes | +-----------+----------------------------+-----------------------------+ | sqlBefore | yes | yes | +-----------+----------------------------+-----------------------------+ | sqlInsert | - | yes | +-----------+----------------------------+-----------------------------+ | sqlUpdate | - | yes | +-----------+----------------------------+-----------------------------+ | sqlDelete | - | yes | +-----------+----------------------------+-----------------------------+ | sqlAfter | yes | yes | +-----------+----------------------------+-----------------------------+ * `fileSize` / `mimeType` * In :ref:`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 :ref:`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`: * *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 :ref:`STORE_VARS`) * Additional attributes might be given like `downloadButton = t:Download|o:check file|G:0`. Please check :ref:`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 :ref:`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 <https://www.jqwidgets.com>`_ and is not drag'n'drop capable. * Upload Dialoag v2 (default) is based on `Filepond <https://github.com/pqina/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: 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: 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 :ref:`STORE_VARS`). E.g.:: fileDestination=fileadmin/person/{{name:R0}}_{{id:R}}/uploads/picture_{{filename}} * *slaveId* = `<id>` - Defines the target record where to retrieve and store the path/filename of the uploaded file. Check also :ref:`slave-id`. E.g.:: slaveId={{SELECT id FROM Note WHERE pId={{id:R0}} AND type='picture' LIMIT 1}} * *sqlBefore* = `{{<query>}}` - fired during a form save, before the following queries are fired. * *sqlInsert* = `{{<query>}}` - fired if `slaveId=0` and an upload exist (user has chosen a file):: sqlInsert={{INSERT INTO Note (pId, type, pathFileName) VALUE ({{id:R0}}, 'image', '{{fileDestination}}') }} * *sqlUpdate* = `{{<query>}}` - fired if `slaveId>0` and an upload exist (user has chosen a file). E.g.:: sqlUpdate={{UPDATE Note SET pathFileName = '{{fileDestination}}' WHERE id={{slaveId}} LIMIT 1}} * *sqlDelete* = `{{<query>}}` - fired if `slaveId>0` and no upload exist (user has not chosen a file). E.g.:: sqlDelete={{DELETE FROM Note WHERE id={{slaveId:V}} LIMIT 1}} * *sqlAfter* = `{{<query>}}` - fired after all previous queries have been fired. Might update the new created id to a primary record. E.g.:: sqlAfter={{UPDATE Person SET noteIdPicture = {{slaveId}} WHERE id={{id:R0}} LIMIT 1 }} .. _split-pdf-upload: 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': +--------------+--------------------------------------------------------------------------------------------+ | Column | Description | +==============+============================================================================================+ | id | Uniq auto increment index | +--------------+--------------------------------------------------------------------------------------------+ | tableName | Name of the table, where the reference to the original file (multi page PDF file) is saved. | +--------------+--------------------------------------------------------------------------------------------+ | xId | Primary id of the reference record. | +--------------+--------------------------------------------------------------------------------------------+ | pathFileName | Path/filename reference to one of the created files | +--------------+--------------------------------------------------------------------------------------------+ One use case why to split an upload: annotate individual pages by using the `FormElement`.type=`annotate`. .. _input-chat: Type: chat ^^^^^^^^^^ The FormElement Chat together with the :ref:`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: 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) .. _sqlValidate: 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}}` .. _slave-id: 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 :ref:`formAsFile`. * 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 :ref:`formAsFile`. 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: :ref:`html-formatting<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 :ref:`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 :ref:`REDIRECT_ALL_MAIL_TO`. .. 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 :ref:`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: Form Magic ---------- * Read the following carefully to understand and use QFQ form functionality. * Check also the :ref:`Form process order<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! .. Comment to separate block 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 :ref:`subrecord-option`. .. 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 :ref:`slave-id` 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 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. .. _multi-form-table: 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 .. _multi-form-grid: 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 :ref:`slave-id` 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: 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 :ref:`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 :ref:`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 :ref:`configuration`: :: formLanguageAId = 1 formLanguageALabel = English formLanguageBId = 2 formLanguageBLabel = Spanish The default language is not covered in :ref:`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: 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: 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 :ref:`extension-manager-qfq-configuration`. If feature is used first time then you need so set up the encryption key: :ref:`encryption-key` Important: * Database field type from form element must be VARCHAR or TEXT. * If VARCHAR is used it is necessary to have a length of minimum 70 characters. .. _encryptionMethod: 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: 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 :ref:`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. .. _bs-custom-field-width: 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`: 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: 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 :ref:`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 :ref:`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 :ref:`configuration` by * *showDebugInfo: yes* or * *showDebugInfo: auto* and logged in in the same Browser as a Typo3 backend user. More detailed error messages ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ If *showDebugInfo* is enabled, a full stacktrace and variable contents are displayed in case of an error. Form search ^^^^^^^^^^^ QFQ content record:: # Creates a small form that redirects back to this page 10 { sql = SELECT '_' head = <form action='#' method='get'><input type='hidden' name='id' value='{{pageSlug:T}}'>Search: <input type='text' name='search' value='{{search:CE:all}}'><input type='submit' value='Submit'></form> } # SQL statement will find and list all the relevant forms - be careful not to open a cross site scripting door: the parameter 'search' needs to be sanitized. 20 { sql = SELECT CONCAT('p:{{pageSlug:T}}?form=form&r=', f.id) AS _pagee, f.id, f.name, f.title FROM Form AS f WHERE f.name LIKE '%{{search:CE:alnumx}}%' head = <table class='table'> tail = </table> rbeg = <tr> rend = </tr> fbeg = <td> fend = </td> } 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}} }}` .. _example_class_template_group: 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`: 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 :ref:`form-editor`). 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 .. _`formAsFile`: 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: :ref:`form-editor` * 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 (:ref:`qfq-project-path-php`). * Cron job to remove old backups: :ref:`removeFormBackupCron` 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: :ref:`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: :ref:`removeFormBackupCron` * **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)