Skip to content
Snippets Groups Projects
Manual.rst 505 KiB
Newer Older
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

The SQL function QMORE(text, n) truncates `text` if it is longer than `n` characters and adds a "more.." button. If the "more..."
button is clicked, the whole text is displayed. The stored procedure QMORE() will inject some HTML/CSS code.

Example::

    10.sql = SELECT QMORE("This is a text which is longer than 10 characters", 10)

Carsten  Rose's avatar
Carsten Rose committed
Output::
Carsten  Rose's avatar
Carsten Rose committed
  This is a `more..`

.. _qifempty:

QIFEMPTY: if empty show token
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

The SQL function QIFEMPTY(input, token) returns 'token' if 'input' is 'empty string' / '0' / '0000-00-00' / '0000-00-00 00:00:00'.
Carsten  Rose's avatar
Carsten Rose committed

Example::

Carsten  Rose's avatar
Carsten Rose committed
    10.sql = SELECT QIFEMPTY('hello world','+'), QIFEMPTY('','-')
Carsten  Rose's avatar
Carsten Rose committed

Output::

  hello world-
.. _qdate_format:

QDATE_FORMAT: format a timestamp, show '-' if empty
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

The SQL function QDATE_FORMAT(timestamp) returns 'dd.mm.YYYY hh:mm', if 'timestamp' is 0 returns '-'

Example::

    10.sql = SELECT QDATE_FORMAT( '2019-12-31 23:55:41' ), ' / ', QDATE_FORMAT( 0 ), ' / ', QDATE_FORMAT( '' )

Output::

  31.12.2019 23:55 / - / -

.. _strip_tags:

strip_tags: strip html tags
^^^^^^^^^^^^^^^^^^^^^^^^^^^

The SQL function strip_tags(input) returns 'input' without any HTML tags.

Example::

   10.sql = SELECT strip_tags('<a href="https://example.com"><b>my name</b> <i>is john</i></a> - end of sentence')

Output::

  my name is john - end of sentence

.. _download:
Download
--------

Download offers:

* Single file - download a single file (any type),
* PDF create - one or concatenate several files (uploaded) and/or web pages (=HTML to PDF) into one PDF output file,
* ZIP archive - filled with several files ('uploaded' or 'HTML to PDF'-converted).
* Excel - created from scratch or fill a template xlsx with database values.

The downloads are SIP protected. Only the current user can use the link to download files.


* the option `d:...` initiate creating the download link and optional specifies an export filename,
* the optional `M:...` (Mode) specifies the export type (file, pdf, zip, export),
* setting `s:1` is recommended for the download function (file / path name is hidden to the user),
* the alttext `a:...` specifies a message in the download popup.

By using `_pdf`,  `_Pdf`, `_file`, `_File`, `_zip`, `_Zip`, `_excel` as column name, the options `d`, `M` and `s`
All files will be read by PHP - therefore the directory might be protected against direct web access. This is the
preferred option to offer secure downloads via QFQ.

In case the download needs a persistant URL (no SIP, no user session), a regular
link, pointing directly to a file, have to be used - the download functionality described here is not appropriate for
such a scenario. If necessary, column-save-pdf_ can be used to generate such a file.

.. _download-parameter-files:

Parameter and (element) sources
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

* *download*: `d[:<exportFilename>]`

  * *exportFilename* = <filename for save as> - Name, offered in the 'File save as' browser dialog. Default: 'output.<ext>'.

    If there is no `exportFilename` defined, then the original filename is taken (if there is one, else: output...).
    The user typically expects meaningful and distinct file names for different download links.

* *popupMessage*: `a:<text>` - will be displayed in the popup window during download. If the creating/download is fast, the window might disappear quickly.

* *mode*: `M:<mode>`


      * If `M:file`, the mime type is derived dynamically from the specified file. In this mode, only one element source
        is allowed per download link (no concatenation).

      * In case of multiple element sources, only `pdf`, `zip` and `excel` (template mode) is supported.
      * If `M:zip` is used together with `p:...`, `U:...` or `u:..`, those HTML pages will be converted to PDF. Those files
        get generic filenames inside the archive.
      * If not specified, the **default** 'Mode' depends on the number of specified element sources (=file or web page):

        * If only one `file` is specified, the default is `file`.
        * If there is a) a page defined or b) multiple elements, the default is `pdf`.

* *element sources* - for `M:pdf` or `M:zip`, all of the following element sources may be specified multiple times.
Carsten  Rose's avatar
Carsten Rose committed
  Any combination and order of these options are allowed.

  * *file*: `F:<pathFileName>` - relative or absolute pathFileName offered for a) download (single), or to be concatenated
    in a PDF or ZIP.
  * *page*: `p:id=<t3 page>&<key 1>=<value 1>&<key 2>=<value 2>&...&<key n>=<value n>`.

    * By default, the options given to wkhtml will *not* be encoded by a SIP!
    * To encode the parameter via SIP: Add '_sip=1' to the URL GET parameter.

      E.g. `p:id=form&_sip=1&form=Person&r=1`.

      In that way, specific sources for the `download` might be SIP encrypted.

    * Any current HTML cookies will be forwarded to/via `wkhtml`. This includes the current FE Login as well as any
      QFQ session. Also the current User-Agent are faked via the `wkhtml` page request.

    * If there are trouble with accessing FE_GROUP protected content, please check `wkhtmltopdf`_.

  * *url*: `u:<url>` - any URL, pointing to an internal or external destination.

  * *uid*: `uid:<tt-content record id>` - the tt_content.uid of a QFQ PageContent record (shown on hover in the backend). This will render
    only the specified  QFQ content record, without any Typo3 layout elements (Menu, Body,...)
    QFQ will retrieve the tt-content's bodytext from the Typo3 database, parse it, and render it as a PDF. Parameters can be
    passed: `uid:<tt-content record id>[&arg1=value1][&arg2=value2][...]` and will be available in the SIP store for the QFQ PageContent,
    or passed as wkhtmltopdf arguments, if applicable.

  * *WKHTML Options* for `page`, `urlParam` or `url`:

    * The 'HTML to PDF' will be done via `wkhtmltopdf`.
    * All possible options, suitable for `wkhtmltopdf`, can be submitted in the `p:...`, `u:...` or `U:...` element source.
      Check `wkhtmltopdf.txt <https://wkhtmltopdf.org/usage/wkhtmltopdf.txt>`_ for possible options. Be aware that
      key/value tuple in the  documentation is separated by a space, but to respect the QFQ key/value notation of URLs,
      the key/value tuple in `p:...`, `u:...` or `U:...` has to be separated by '='. Please see last example below.
Carsten  Rose's avatar
Carsten Rose committed
    * If an option contains an '&' it must be escaped with double \\ . See example.
  Most of the other Link-Class attributes can be used to customize the link as well.

Example `_link`: ::

  # single `file`. Specifying a popup message window text is not necessary, cause a file directly accessed is fast.
  SELECT "d:file.pdf|s|t:Download|F:fileadmin/pdf/test.pdf" AS _link
  # single `file`, with mode
  SELECT "d:file.pdf|M:pdf|s|t:Download|F:fileadmin/pdf/test.pdf" AS _link
  # three sources: two pages and one file
  SELECT "d:complete.pdf|s|t:Complete PDF|p:id=detail&r=1|p:id=detail2&r=1|F:fileadmin/pdf/test.pdf" AS _link
  # three sources: two pages and one file
  SELECT "d:complete.pdf|s|t:Complete PDF|p:id=detail&r=1|p:id=detail2&r=1|F:fileadmin/pdf/test.pdf" AS _link
  # three sources: two pages and one file, parameter to wkhtml will be SIP encoded
  SELECT "d:complete.pdf|s|t:Complete PDF|p:id=detail&r=1&_sip=1|p:id=detail2&r=1&_sip=1|F:fileadmin/pdf/test.pdf" AS _link
  # three sources: two pages and one file, the second page will be in landscape and pagesize A3
  SELECT "d:complete.pdf|s|t:Complete PDF|p:id=detail&r=1|p:id=detail2&r=1&--orientation=Landscape&--page-size=A3|F:fileadmin/pdf/test.pdf" AS _link
  # One source and a header file. Note: the parameter to the header URL is escaped with double backslash.
  SELECT "d:complete.pdf|s|t:Complete PDF|p:id=detail2&r=1&--orientation=Landscape&--header={{URL:R}}?indexp.php?id=head\\&L=1|F:fileadmin/pdf/test.pdf" AS _link
..

Example `_pdf`, `_zip`: ::

  # File 1: p:id=1&--orientation=Landscape&--page-size=A3
  # File 2: p:id=form
  # File 3: F:fileadmin/file.pdf
  SELECT 't:PDF|a:Creating a new PDF|p:id=1&--orientation=Landscape&--page-size=A3|p:id=form|F:fileadmin/file.pdf' AS _pdf
  # File 1: p:id=1
  # File 2: u:http://www.example.com
  # File 3: F:fileadmin/file.pdf
  SELECT 't:PDF - 3 Files|a:Please be patient|p:id=1|u:http://www.example.com|F:fileadmin/file.pdf' AS _pdf
  # File 1: p:id=1
  # File 2: p:id=form
  # File 3: F:fileadmin/file.pdf
  SELECT CONCAT('t:ZIP - 3 Pages|a:Please be patient|p:id=1|p:id=form|F:', p.pathFileName) AS _zip

..

Use the `--print-media-type` as wkhtml option to access the page with media type 'printer'. Depending on the website
configuration this switches off navigation and background images.

Rendering PDF letters
^^^^^^^^^^^^^^^^^^^^^

`wkhtmltopdf`, with the header and footer options, can be used to render multi page PDF letters (repeating header,
pagination) in combination with dynamic content. Such PDFs might look-alike official letters, together with logo and signature.

Best practice:

#. Create a clean (=no menu, no website layout) letter layout in a separated T3 branch: ::

      page = PAGE
      page.typeNum = 0
      page.includeCSS {
        10 = typo3conf/ext/qfq/Resources/Public/Css/qfq-letter.css
      }
      // Grant access to any logged in user or specific development IPs
      [usergroup = *] || [IP = 127.0.0.1,192.168.1.* ]
        page.10 < styles.content.get
      [else]
        page.10 = TEXT
        page.10.value = access forbidden
      [global]

#. Create a T3 `body` page (e.g. page alias: 'letterbody') with some content. Example static HTML content: ::

      <div class="letter-receiver">
        <p>Address</p>
      </div>
      <div class="letter-sender">
       <p><b>firstName name</b><br>
        Phone +00 00 000 00 00<br>
        Fax +00 00 000 00 00<br>
       </p>
      </div>

      <div class="letter-date">
        Zurich, 01.12.2017
      </div>

      <div class="letter-body">
       <h1>Subject</h1>

       <p>Dear Mrs...</p>
       <p>Lucas ipsum dolor sit amet organa solo skywalker darth c-3p0 anakin jabba mara greedo skywalker.</p>

       <div class="letter-no-break">
       <p>Regards</p>
       <p>Company</p>
       <img class="letter-signature" src="">
       <p>Firstname Name<br>Function</p>
       </div>
      </div>

#. Create a T3 letter-`header` page (e.g. page alias: 'letterheader') , with only the header information: ::

        <header>
        <img src="fileadmin/logo.png" class="letter-logo">

        <div class="letter-unit">
          <p class="letter-title">Department</p>
          <p>
           Company name<br>
           Company department<br>
           Street<br>
           City
          </p>
        </div>
        </header>

#. Create a) a link (Report) to the PDF letter or b) attach the PDF (on the fly rendered) to a mail. Both will call the
   `wkhtml` via the `download` mode and forwards the necessary parameter.

Carsten  Rose's avatar
Carsten Rose committed
  sql = SELECT CONCAT('d:Letter.pdf|t:',p.firstName, ' ', p.name
                       , '|p:id=letterbody&pId=', p.id, '&_sip=1'
Carsten  Rose's avatar
Carsten Rose committed
                       , '&--margin-top=50mm'
Carsten  Rose's avatar
Carsten Rose committed
                       , '&--header-html={{BASE_URL_PRINT:Y}}?id=letterheader'
Carsten  Rose's avatar
Carsten Rose committed

                       # IMPORTANT: set margin-bottom to make the footer visible!
                       , '&--margin-bottom=20mm'
Carsten  Rose's avatar
Carsten Rose committed
                       , '&--footer-right="Seite: [page]/[toPage]"'
                       , '&--footer-font-size=8&--footer-spacing=10') AS _pdf
Carsten  Rose's avatar
Carsten Rose committed

                FROM Person AS p ORDER BY p.id


Sendmail. Parameter: ::

  sendMailAttachment={{SELECT 'd:Letter.pdf|t:', p.firstName, ' ', p.name, '|p:id=letterbody&pId=', p.id, '&_sip=1&--margin-top=50mm&--margin-bottom=20mm&--header-html={{BASE_URL_PRINT:Y}}?id=letterheader&--footer-right="Seite: [page]/[toPage]"&--footer-font-size=8&--footer-spacing=10' FROM Person AS p WHERE p.id={{id:S}} }}
Replace the static content elements from 2. and 3. by QFQ Content elements as needed::

  10.sql = SELECT '<div class="letter-receiver"><p>', p.name AS '_+br', p.street AS '_+br', p.city AS '_+br', '</p>'
            FROM Person AS p WHERE p.id={{pId:S}}


Export area
^^^^^^^^^^^

This description might be interesting if a page can't be protected by SIP.

To offer protected pages, e.g. directly referenced files (remember: this is not recommended) in download links, the
regular FE_GROUPs can't be used, cause the download does not have the current user privileges (it's a separate process,
started as the webserver user).

Create a separated export tree in Typo3 Backend, which is IP access restricted. Only localhost or the FE_GROUP 'admin'
is allowed to access: ::

   tmp.restrictedIPRange = 127.0.0.1,::1
   [IP = {$tmp.restrictedIPRange} ][usergroup = admin]
      page.10 < styles.content.get
   [else]
      page.10 = TEXT
      page.10.value = Please access from localhost or log in as 'admin' user.
   [global]
This chapter explains how to create Excel files on the fly.
Hint: For up/downloading of excel files (without modification), check the generic Form
`input-upload`_ element and the report 'download' (`column_pdf`_) function.
The Excel file is build in the moment when the user request it by clicking on a
download link.
Mode building:
* `New`: The export file will be completely build from scratch.
* `Template`: The export file is based on an earlier uploaded xlsx file (template). The template itself is unchanged.
Injecting data into the Excel file is done in the same way in both modes: a Typo3 page (rendered without any HTML header
or tags) contains one or more Typo3 QFQ records. Those QFQ records will create plain ASCII output.
If the export file has to be customized (colors, pictures, headlines, ...), the `Template` mode is the preferred option.
It's much easier to do all customizations via Excel and creating a template than by coding in QFQ / Excel export notation.
* Create a special column name `_excel` (or `_link`) in QFQ/Report. As a source, define a T3 PageContent, which has to
  deliver the dynamic content (also `excel-export-sample`_). ::
    SELECT CONCAT('d:final.xlsx|M:excel|s:1|t:Excel (new)|uid:<tt-content record id>') AS _link
* Create a T3 PageContent which delivers the content.
  * It is recommended to use the `uid:<tt-content record id>` syntax for excel imports, because there should be no html code on the
    resulting content. QFQ will retrieve the PageContent's bodytext from the Typo3 database, parse it, and pass the
    result as the instructions for filling the excel file.
  * Parameters can be passed: `uid:<tt-content record id>?param=<value1>&param2=<value2>` and will be accessible in the SIP Store (S) in the
  * Use the regular QFQ Report syntax to create some output.
  * The newline at the end of every line needs to be CHAR(10). To make it simpler, the special column name `... AS _XLS`
    (see _XLS, _XLSs, _XLSb, _XLSn) can be used.
  * Empty lines will be skipped.
  * Lines starting with '#' will be skipped (comments). Inline comment signs are NOT recognized as comment sign.
+-------------+----------------------+---------------------------------------------------------------------------------------------------+
| Keyword     | Example              | Description                                                                                       |
+=============+======================+===================================================================================================+
| 'worksheet' | worksheet=main       | Select a worksheet in case the excel file has multiple of them.                                   |
+-------------+----------------------+---------------------------------------------------------------------------------------------------+
| 'mode'      | mode=insert          | Values: insert,overwrite.                                                                         |
+-------------+----------------------+---------------------------------------------------------------------------------------------------+
| 'position'  | position=A1          | Default is 'A1'. Use the excel notation.                                                          |
+-------------+----------------------+---------------------------------------------------------------------------------------------------+
| 'newline'   | newline              | Start a new row. The column will be the one of the last 'position' statement.                     |
+-------------+----------------------+---------------------------------------------------------------------------------------------------+
| 'str', 's'  | s=hello world        | Set the given string on the given position. The current position will be shift one to the right.  |
|             |                      | If the string contains newlines, option'b' (base64) should be used.                               |
+-------------+----------------------+---------------------------------------------------------------------------------------------------+
| 'b'         | b=aGVsbG8gd29ybGQK   | Same as 's', but the given string has to Base64 encoded and will be decoded before export.        |
+-------------+----------------------+---------------------------------------------------------------------------------------------------+
| 'n'         | n=123                | Set number on the given position. The current position will be shift one to the right.            |
+-------------+----------------------+---------------------------------------------------------------------------------------------------+
Carsten  Rose's avatar
Carsten Rose committed
| 'f'         | f==SUM(A5:C6)        | Set a formula on the given position. The current position will be shift one to the right.         |
+-------------+----------------------+---------------------------------------------------------------------------------------------------+

Create a output like this: ::

    position=D11
    s=Hello
    s=World
    s=First Line
    # With ... AS _XLS (token explicit given)
    10.sql = SELECT 'position=D10' AS _XLS,
                    's=Hello' AS _XLS,
                    's=World' AS _XLS,
                    's=First Line' AS _XLS,
                    'newline' AS _XLS,
                    's=Second line' AS _XLS,
                    'n=123' AS _XLS,
    # With ... AS _XLSs (token generated internally)
    20.sql = SELECT 'position=D20' AS _XLS,
                    'Hello' AS _XLSs,
                    'World' AS _XLSs,
                    'First Line' AS _XLSs,
                    'newline' AS _XLS,
                    'Second line' AS _XLSs,
                    'n=123' AS _XLS,

    # With ... AS _XLSb (token generated internally and content is base64 encoded)
    30.sql = SELECT 'position=D30' AS _XLS,
                     '<some content with special characters like newline/carriage return>' AS _XLSb

Excel export samples (54 is a example <tt-content record id>)::

    # From scratch (both are the same, one with '_excel' the other with '_link')
    SELECT CONCAT('d:new.xlsx|t:Excel (new)|uid:54') AS _excel
    SELECT CONCAT('d:new.xlsx|t:Excel (new)|uid:54|M:excel|s:1') AS _link
    SELECT CONCAT('d:final.xlsx|t:Excel (template)|F:fileadmin/template.xlsx|uid:54') AS _excel

    # With parameter (via SIP) - get the Parameter on page 'exceldata' with '{{arg1:S}}' and '{{arg2:S}}'
    SELECT CONCAT('d:final.xlsx|t:Excel (parameter)|uid:54&arg1=hello&arg2=world') AS _excel
.. _dropdownMenu:

Dropdown Menu
-------------

Creates a menu with custom links. The same notation and options are used as with regular QFQ links.

Format String::

  <dropdown menu symbol options>||<menu entry 1>||<menu entry 2>||...

Each menu entry is separated by two bars! A menu entry itself might contain multiple single bars.

Example 1::

  SELECT 'z||p:home|t:Home|o:Jump to home||p:person&form=person&r=123|t:Edit: John Doe|s' AS _link

This defines a menu (three vertical buttons) - a click on it shows two menu entries: 'Home' and 'Edit: John Doe'

Format the dropdown menu symbol:

  * *Glyph*: Via `G:<glyphicon name>` any glyphicon can be defined. To hide the default glyph, specify: `G:0`.
  * *Text*: Via `t:Menu` an additional text will be displayed for the menu symbol.
  * *Tooltip*: Via `o:Detail menu` a tooltip is defined.
  * *Render mode*: Via `r:3` the menu is disabled. No menu entries / links / sip are rendered.
  * *Button*: Via `b` the dropdown meny symbol will be rendered with a button. Also `b:<style>` might set the BS color.

Format a menu entry:

 * *qfq link*: All options as with a regular QFQ link.
 * *header*: If a text starts with '===', it becomes a header in the dropdown menu. Multiple headers are possible. Headers can't be a link. An additional `r:1` is necessary.
 * *separator*: If a text is exactly '---', it becomes a separator line between two menu entries. An additional `r:1` is necessary.
 * *disabled menu entry*: If a text starts with '---' (like separator), the following text becomes a disable menu entry. An additional `r:1` is necessary.
    SELECT CONCAT('z|t:Menu|G:0|o:Please select an option',
                  '||p:home|t:Home|o:Jump to home|G:glyphicon-home|b:0',
                  '||r:1|t:---',
                  '||p:person&form=person&r=123|t:Edit: John Doe|s|q:Really edit?|G:glyphicon-user|b:0',
                  '||t:===Header|r:1',
                  '||d|p:form&form=person&r=',p.id,'|s|t:Download Person|b:0',
                  '||r:1|t:---Disabled entry') AS _link

Line 1: The dropdown menu symbol definition, with a text 'Menu' `t:Menu`, but without the three vertical bullets `G:0`
Carsten  Rose's avatar
Carsten Rose committed
and a tooltip for the menu `o:Please select an option`.

Line 2: First menu entry. Directs to T3 page 'home' `p:home`. `t:Home` sets the menu entry text. `G:glyphicon-home` set's
Carsten  Rose's avatar
Carsten Rose committed
glyphicon symbol in the menu entry. `b:0` switches off the button, which has been implicit activated by the use of `G:...`.

Line 3: A separator line.

Line 4: A SIP encoded edit record link, with a question dialog.

Line 5: A header line.

Line 6: A PDF download.

Line 7: A disabled menu entry.

Drag and drop
-------------

Ordering of elements via `HTML5 drag and drop` is supported via QFQ. Any element to order
should be represented by a database record with an order column. If the elements are unordered, they will be ordered after
Carsten  Rose's avatar
Carsten Rose committed
the first 'drag and drop' move of an element.
* Display: the records will be displayed via QFQ/report.
* Order records: updates of the order column are managed by a specific definition form. The form is not a regular form
  (e.g. there are no FormElements), instead it's only a container to held the SQL update query as well as providing
  access control via SIP. The form is automatically called via AJAX.

Part 1: Display list
Display the list of elements via a regular QFQ content record. All 'drag and drop' elements together have to be nested by a HTML
element. Such HTML element:

* With `class="qfq-dnd-sort"`.
* With a form name: `{{'form=<form name>' AS _data-dnd-api}}` (will be replaced by QFQ)
* Only *direct* children of such element can be dragged.
* Every children needs a unique identifier `data-dnd-id="<unique>"`. Typically this is the corresponding record id.
* The record needs a dedicated order column, which will be updated through API calls in time.

A `<div>` example HTML output (HTML send to the browser): ::
    <div class="qfq-dnd-sort" data-dnd-api="typo3conf/ext/qfq/Classes/Api/dragAndDrop.php?s=badcaffee1234">
        <div class="anyClass" id="<uniq1>" data-dnd-id="55">
            Numbero Uno
        </div>
        <div class="anyClass" id="<uniq2>" data-dnd-id="18">
            Numbero Deux
        </div>
        <div class="anyClass" id="<uniq3>" data-dnd-id="27">
            Numbero Tre
        </div>
    </div>


A typical QFQ report which generates those `<div>` HTML::
      sql = SELECT '<div id="anytag-', n.id,'" data-dnd-id="', n.id,'">' , n.note, '</div>'
                   FROM Note AS n
Carsten  Rose's avatar
Carsten Rose committed
                   WHERE grId=28
      head = <div class="qfq-dnd-sort" {{'form=dndSortNote&grId=28' AS _data-dnd-api}}">
A `<table>` based setup is also possible. Note the attribute  `data-columns="3"` - this generates a dropzone
which is the same column width as the outer table. ::
        <tbody class="qfq-dnd-sort" data-dnd-api="typo3conf/ext/qfq/Classes/Api/dragAndDrop.php?s=badcaffee1234" data-columns="3">
Carsten  Rose's avatar
Carsten Rose committed
            <tr> class="anyClass" id="<uniq1>" data-dnd-id="55">
                <td>Numbero Uno</td><td>Numbero Uno.2</td><td>Numbero Uno.3</td>
            </tr>
            <tr class="anyClass" id="<uniq2>" data-dnd-id="18">
                <td>Numbero Deux</td><td>Numbero Deux.2</td><td>Numbero Deux.3</td>
            </tr>
            <tr class="anyClass" id="<uniq3>" data-dnd-id="27">
                <td>Numbero Tre</td><td>Numbero Tre.2</td><td>Numbero Tre.3</td>
            </tr>
        </tbody>
    </table>

Carsten  Rose's avatar
Carsten Rose committed

    10 {
      sql = SELECT '<tr id="anytag-', n.id,'" data-dnd-id="', n.id,'" data-columns="3">' , n.id AS '_+td', n.note AS '_+td', n.ord AS '_+td', '</tr>'
                   FROM Note AS n
                   WHERE grId=28
                   ORDER BY n.ord

      head = <table><tbody class="qfq-dnd-sort" {{'form=dndSortNote&grId=28' AS _data-dnd-api}} data-columns="3">
      tail = </tbody><table>
    }

Show / update order value in the browser
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

The 'drag and drop' action does not trigger a reload of the page. In case the order number is shown and the user does
a 'drag and drop', the order number shows the old. To update the dragable elements with the latest order number, a
predefined html id has to be assigned them. After an update, all changed order number (referenced by the html id) will
be updated via AJAX.
The html id per element is defined by `qfq-dnd-ord-id-<id>` where `<id>` is the record id. Same example as above, but

    10 {
      sql = SELECT '<tr id="anytag-', n.id,'" data-dnd-id="', n.id,'" data-columns="3">' , n.id AS '_+td', n.note AS '_+td',
                   '<td id="qfq-dnd-ord-id-', n.id, '">', n.ord, '</td></tr>'
                   FROM Note AS n
                   WHERE grId=28
                   ORDER BY n.ord
      head = <table><tbody class="qfq-dnd-sort" {{'form=dndSortNote&grId=28' AS _data-dnd-api}} data-columns="3">
      tail = </tbody><table>
    }

A dedicated `Form`, without any `FormElements`, is used to define the reorder logic (database update definition).
* Name: <custom form name> - used in Part 1 in the  `_data-dnd-api` variable.
Carsten  Rose's avatar
Carsten Rose committed
* Table: <table with the element records> - used to update the records specified by `dragAndDropOrderSql`.
Carsten  Rose's avatar
Carsten Rose committed
+-------------------------------------------------------+--------------------------------------------------------------+
| Attribute                                             | Description                                                  |
+=======================================================+==============================================================+
| orderInterval = <number>                              | Optional. By default '10'. Might be any number > 0.          |
+-------------------------------------------------------+--------------------------------------------------------------+
| orderColumn = <column name>                           | Optional. By default 'ord'.                                  |
+-------------------------------------------------------+--------------------------------------------------------------+
| dragAndDropOrderSql =                                 | Query to selects the *same* records as the report in the     |
| {{!SELECT n.id AS id, n.ord AS ord FROM Note AS n     | same *order!* Inconsistencies results in order differences.  |
| ORDER BY n.ord}}                                      | The columns `id` and `ord` are *mandatory.*                  |
+-------------------------------------------------------+--------------------------------------------------------------+

The form related to the example of part 1 ('div' or 'table'): ::

  Form.name: dndSortNote
  Form.table: Note
  Form.parameter: orderInterval = 1
  Form.parameter: orderColumn = ord
  Form.parameter: dragAndDropOrderSql = {{!SELECT n.id AS id, n.ord AS ord FROM Note AS n WHERE n.grId={{grId:S0}} ORDER BY n.ord}}
QFQ iterates over the result set of `dragAndDropOrderSql`. The value of column `id` have to correspond to the dragged HTML
 element (given by `data-dnd-id`). Reordering always start with `orderInterval` and is incremented by `orderInterval` with each
 record of the result set. The client reports a) the id of the dragged HTML element, b) the id of the hovered element and
 c) the dropped position of above or below the hovered element. This information is compared to the result set and
 changes are applied where appropriate.

 Take care that the query of part 1 (display list) does a) select the same records and b) in the same order as the query
 defined in part 2 (order records) via `dragAndDropOrderSql`.
 If you find that the reorder does not work at expected, those two sql queries are not identical.
QFQ CSS Classes
---------------

* `qfq-table-50`, `qfq-table-80`, `qfq-table-100` - assigned to `<table>`, set min-width and column width to 'auto'.
* Background Color: `qfq-color-grey-1`, `qfq-color-grey-2` - assigned to different tags (table, row, cell).
* `qfq-100` - assigned to different tags, makes an element 'width: 100%'.
* `qfq-left`- assigned to different tags, Text align left.
* `qfq-sticky` - assigned to `<thead>`, makes the header sticky.
* `qfq-badge`, `qfq-badge-error`, `qfq-badge-warning`, `qfq-badge-success`, `qfq-badge-info`, `qfq-badge-invers` - colorized BS3 badges.
* `letter-no-break` - assigned to a `div` will protect a paragraph (CSS: page-break-before: avoid;) not to break around
  a page border (converted to PDF via wkhtml). Take care that `qfq-letter.css` is included in TypoScript setup.

Bootstrap
---------

* Table: `table`
* Table > hover: `table-hover`
* Table > condensed: `table-condensed`


  10.sql = SELECT id, name, firstName, ...
  10.head = <table class='table table-condensed qfq-table-50'>

* `qfq-100`, `qfq-left` - makes e.g. a button full width and aligns the text left.

    10.sql = SELECT "p:home&r=0|t:Home|c:qfq-100 qfq-left" AS _pagev

QFQ includes a third-party client-side table sorter: https://mottie.github.io/tablesorter/docs/index.html
To turn any table into a sortable table:
* Ensure that your QFQ installation imports the appropriate js/css files, see setup-css-js_.
* Add the `class="tablesorter"` to your `<table>` element.
* Take care the `<table>` has a `<thead>` and `<tbody>` tag.
* Evey table with active tablesorter should have a uniq HTML id.
.. important::
   Custom settings will be saved per table automatically in the browser local storage. To distinguish different table
   settings, define an uniq HTML id per table.
   Example: `<table class="tablesorter" id="{{pageAlias:T}}-person">` - the `{{pageAlias:T}}` makes it easy to keep the
   overview over given name on the site.
The *tablesorter* options:
* Class `tablesorter-filter` enables row filtering.
* Class `tablesorter-pager` adds table paging functionality. A page navigation
  is shown.
* Class `tablesorter-column-selector` adds a column selector widget.
* Activate/Save/Delete `views`: Insert inside of a table html-tag the command::
   {{ '<uniqueName>' AS _tablesorter-view-saver }}
  This adds a menu to save the current view (column filters, selected columns, sort order).
  * `<uniqueName>` should be a name which is at least unique inside the typo3 content element. Example::
    <table {{ 'allperson' AS _tablesorter-view-saver }} class="tablesorter tablesorter-filter tablesorter-column-selector" id="{{pageAlias:T}}-demo"> ... </table>
  * 'Views' can be saved as:
     * public: every user will see the `view` and can modify it.
     * private: only the user who created the `view` will see/modify it.
     * readonly: manually mark a `view` as readonly (no FE User can change it) by setting column `readonly='true'` in table
       `Setting` of the corresponding view (identified by `name`).
  * Views will be saved in the table 'Setting'.
  * Include 'font-awesome' CSS in your T3 page setup: `typo3conf/ext/qfq/Resources/Public/Css/font-awesome.min.css` to get the icons.
  * The view 'Clear' is always available and can't be modified.
  * To preselect a view, append a HTML anker to the current URL. Get the anker by selecting the view and copy it from the
    browser address bar. Example::
    https://localhost/index.php?id=person#allperson=public:email
    * 'allperson' is the '<uniqueName>' of the `tablesorter-view-saver` command.
    * 'public' means the view is tagged as 'public' visible.
    * 'email' is the name of the view, as it is shown in the dropdown list.
  * If there is a public view with the name 'Default' and a user has no choosen a view earlier, that one will be selected.
Customization of tablesorter:
* Add the desired classes or data attributes to your table html, e.g.:
  * Disable sorting `class="sorter-false"` on a '<th>' to disable sorting on that column (or: `data-sorter="false"`).
  * Disable filter `class="filter-false"` on a '<th>' to hide the filter field for that column
  * see docs for more options: https://mottie.github.io/tablesorter/docs/index.html
* You can pass in a default configuration object for the main `tablesorter()` function by using the attribute
  `data-tablesorter-config` on the table.
  Use JSON syntax when passing in your own configuration, such as: ::
    data-tablesorter-config='{"theme":"bootstrap","widthFixed":true,"headerTemplate":"{content} {icon}","dateFormat":"ddmmyyyy","widgets":["uitheme","filter","saveSort","columnSelector"],"widgetOptions":{"filter_columnFilters":true,"filter_reset":".reset","filter_cssFilter":"form-control","columnSelector_mediaquery":false} }'
* If the above customization options are not enough, you can output your own HTML for the pager and/or column selector,
  as well as your own `$(document).ready()` function with the desired config. In this case, it is recommended not to
  use the above *tablesorter* classes since the QFQ javascript code could interfere with your javascript code.
      sql = SELECT id, CONCAT('form&form=person&r=', id) AS _Pagee, lastName, title FROM person
      head = <table class="table tablesorter tablesorter-filter tablesorter-pager tablesorter-column-selector" id="{{pageAlias:T}}-ts1">
          <thead><tr><th>Id</th><th class="filter-false sorter-false">Edit</th>
          <th>Name</th><th class="filter-select" data-placeholder="Select a title">Title</th>
          </tr></thead><tbody>
      tail = </tbody></table>
      rbeg = <tr>
      rend = </tr>
      fbeg = <td>
      fend = </td>
    }
.. _monitor:
Monitor
-------
Display a (log)file from the server, inside the browser, which updates automatically by a user defined interval. Access
to the file is SIP protected. Any file on the server is possible.
* On a Typo3 page, define a HTML element with a unique html-id. E.g.::
    10.head = <pre id="monitor-1">Please wait</pre>
* On the same Typo3 page, define an SQL column '_monitor' with the necessary parameter::
    10.sql = SELECT 'file:fileadmin/protected/log/sql.log|tail:50|append:1|refresh:1000|htmlId:monitor-1' AS _monitor
* Short version with all defaults used to display system configured sql.log::
    10.sql = SELECT 'file:{{sqlLog:Y}}' AS _monitor, '<pre id="monitor-1" style="white-space: pre-wrap;">Please wait</pre>'
Report Examples
---------------
The following section gives some examples of typical reports.
    10.sql = SELECT "Hello World<br>"

    10.sql = SELECT p.firstName, p.lastName FROM exp_person AS p

The result of the SQL query is an output, row by row and column by column, without adding any formatting information.
See `Formatting Examples`_ for examples of how the output can be formatted.
Formatting (i.e. wrapping of data with HTML tags etc.) can be achieved in two different ways:
One can add formatting output directly into the SQL by either putting it in a separate column of the output or by using
concat to concatenate data and formatting output in a single column.
One can use 'level' keys to define formatting information that will be put before/after/between all rows/columns of the
actual levels result.
    # Add the formatting information as a column
    10.sql = SELECT p.firstName, " " , p.lastName, "<br>" FROM exp_person AS p
Carsten  Rose's avatar
Carsten Rose committed
One column 'rend' as linebreak - no extra column '<br>' needed::
Carsten  Rose's avatar
Carsten Rose committed
    10.sql = SELECT p.firstName, " " , p.lastName, " ", p.country FROM exp_person AS p
    10.rend = <br>
Carsten  Rose's avatar
Carsten Rose committed
    Billie Holiday USA
    Elvis Presley USA
    Louis Armstrong USA
    Diana Ross USA
Carsten  Rose's avatar
Carsten Rose committed
Same with 'fsep' (column " " removed):
Carsten  Rose's avatar
Carsten Rose committed
    10.sql = SELECT p.firstName, p.lastName, p.country FROM exp_person AS p
    10.rend = <br>
    10.fsep = " "
Carsten  Rose's avatar
Carsten Rose committed
Result::
Carsten  Rose's avatar
Carsten Rose committed
    Billie Holiday USA
    Elvis Presley USA
    Louis Armstrong USA
    Diana Ross USA

    10.sql = SELECT p.name FROM exp_person AS p
    10.head = <ul>
    10.tail = </ul>
    10.rbeg = <li>
    10.rend = </li>


    o Billie Holiday
    o Elvis Presley
    o Louis Armstrong
    o Diana Ross

The same as above, but with braces::

  10 {
    sql = SELECT p.name FROM exp_person AS p
    head = <ul>
    tail = </ul>
    rbeg = <li>
    rend = </li>
  }

    10.rend = <br>
    20.sql = SELECT a.street FROM exp_address AS a
    20.rend = <br>

    # outer query
    10.sql = SELECT p.name FROM exp_person AS p
    10.rend = <br>

    # inner query
    10.10.sql = SELECT a.street FROM exp_address AS a
    10.10.rend = <br>

* For every record of '10', all records of 10.10 will be printed.


    # outer query
    10.sql = SELECT p.id, p.name FROM exp_person AS p
    10.rend = <br>

    # inner query
    10.10.sql = SELECT a.street FROM exp_address AS a WHERE a.pId='{{10.id}}'
    10.10.rend = <br>

* For every record of '10', all assigned records of 10.10 will be printed.

Two queries: nested with hidden variables in a table::

    10.sql = SELECT p.id AS _pId, p.name FROM exp_person AS p
    10.rend = <br>

    # inner query
    10.10.sql = SELECT a.street FROM exp_address AS a WHERE a.pId='{{10.pId}}'
    10.10.rend = <br>

  10 {
    sql = SELECT p.id AS _pId, p.name FROM exp_person AS p

    10 {
    # inner query
      sql = SELECT a.street FROM exp_address AS a WHERE a.pId='{{10.pId}}'
Best practice *recommendation* for using parameter - see `access-column-values`_::