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)
This is a `more..`
.. _qifempty:
QIFEMPTY: if empty show token
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Carsten Rose
committed
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
committed
.. _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 offers:

Carsten Rose
committed
* 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.

Carsten Rose
committed
By using the `_link` column name:
* the option `d:...` initiate creating the download link and optional specifies an export filename,

Carsten Rose
committed
* the optional `M:...` (Mode) specifies the export type (file, pdf, zip, export),

Carsten Rose
committed
* 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`

Carsten Rose
committed
will be set.

Carsten Rose
committed
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>'.

Carsten Rose
committed
If there is no `exportFilename` defined, then the original filename is taken (if there is one, else: output...).

Carsten Rose
committed
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>`

Carsten Rose
committed
* *mode* = <file | pdf | zip | excel>
* 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).

Carsten Rose
committed
* 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`.
Elias Villiger
committed
* *element sources* - for `M:pdf` or `M:zip`, all of the following element sources may be specified multiple times.
* *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.

Carsten Rose
committed
* *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,
Elias Villiger
committed
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.
* 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.
# 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

Carsten Rose
committed
..
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.

Carsten Rose
committed
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: ::
7233
7234
7235
7236
7237
7238
7239
7240
7241
7242
7243
7244
7245
7246
7247
7248
7249
7250
7251
7252
7253
7254
7255
7256
7257
7258
7259
<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
committed
Use in `report`::
sql = SELECT CONCAT('d:Letter.pdf|t:',p.firstName, ' ', p.name
, '|p:id=letterbody&pId=', p.id, '&_sip=1'
# IMPORTANT: set margin-bottom to make the footer visible!
, '&--margin-bottom=20mm'
, '&--footer-right="Seite: [page]/[toPage]"'
, '&--footer-font-size=8&--footer-spacing=10') AS _pdf
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}} }}

Carsten Rose
committed
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]

Carsten Rose
committed
.. _excel-export:
Excel export
^^^^^^^^^^^^
This chapter explains how to create Excel files on the fly.

Carsten Rose
committed
Hint: For up/downloading of excel files (without modification), check the generic Form
`input-upload`_ element and the report 'download' (`column_pdf`_) function.

Carsten Rose
committed
The Excel file is build in the moment when the user request it by clicking on a
download link.

Carsten Rose
committed

Carsten Rose
committed

Carsten Rose
committed
* `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.

Carsten Rose
committed
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.

Carsten Rose
committed

Carsten Rose
committed
If the export file has to be customized (colors, pictures, headlines, ...), the `Template` mode is the preferred option.
Elias Villiger
committed
It's much easier to do all customizations via Excel and creating a template than by coding in QFQ / Excel export notation.

Carsten Rose
committed
Setup

Carsten Rose
committed
"""""

Carsten Rose
committed
Elias Villiger
committed
* 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`_). ::

Carsten Rose
committed

Carsten Rose
committed
SELECT CONCAT('d:final.xlsx|M:excel|s:1|t:Excel (new)|uid:<tt-content record id>') AS _link
Elias Villiger
committed
* Create a T3 PageContent which delivers the content.

Carsten Rose
committed

Carsten Rose
committed
* It is recommended to use the `uid:<tt-content record id>` syntax for excel imports, because there should be no html code on the
Elias Villiger
committed
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.

Carsten Rose
committed
* Parameters can be passed: `uid:<tt-content record id>?param=<value1>¶m2=<value2>` and will be accessible in the SIP Store (S) in the
Elias Villiger
committed
QFQ PageContent.

Carsten Rose
committed
* 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.

Carsten Rose
committed
* One option per line.
* Empty lines will be skipped.
* Lines starting with '#' will be skipped (comments). Inline comment signs are NOT recognized as comment sign.

Carsten Rose
committed
* Separate <keyword> and <value> by '='.
+-------------+----------------------+---------------------------------------------------------------------------------------------------+
| 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. |
+-------------+----------------------+---------------------------------------------------------------------------------------------------+
| 'f' | f==SUM(A5:C6) | Set a formula on the given position. The current position will be shift one to the right. |
+-------------+----------------------+---------------------------------------------------------------------------------------------------+

Carsten Rose
committed
Create a output like this: ::
position=D11
s=Hello
s=World
s=First Line

Carsten Rose
committed
s=Second line
n=123
This fills D11, E11, F11, D12

Carsten Rose
committed
In Report Syntax::

Carsten Rose
committed
# 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,

Carsten Rose
committed
# 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-sample`:

Carsten Rose
committed
Excel export samples (54 is a example <tt-content record id>)::
# From scratch (both are the same, one with '_excel' the other with '_link')
Elias Villiger
committed
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
# Template

Carsten Rose
committed
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}}'

Carsten Rose
committed
SELECT CONCAT('d:final.xlsx|t:Excel (parameter)|uid:54&arg1=hello&arg2=world') AS _excel

Carsten Rose
committed

Carsten Rose
committed
7443
7444
7445
7446
7447
7448
7449
7450
7451
7452
7453
7454
7455
7456
7457
7458
7459
7460
7461
7462
7463
7464
7465
7466
7467
7468
7469
7470
7471
7472
7473
7474
7475
.. _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.

Carsten Rose
committed

Carsten Rose
committed
Example 2::

Carsten Rose
committed

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

Carsten Rose
committed
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.

Carsten Rose
committed
Drag and drop
-------------

Carsten Rose
committed
Order elements
^^^^^^^^^^^^^^

Carsten Rose
committed
Ordering of elements via `HTML5 drag and drop` is supported via QFQ. Any element to order

Carsten Rose
committed
should be represented by a database record with an order column. If the elements are unordered, they will be ordered after

Carsten Rose
committed
Functionality divides into:

Carsten Rose
committed
* 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.

Carsten Rose
committed
""""""""""""""""""""

Carsten Rose
committed
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"`.

Carsten Rose
committed
* 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.

Carsten Rose
committed
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>

Carsten Rose
committed
A typical QFQ report which generates those `<div>` HTML::
sql = SELECT '<div id="anytag-', n.id,'" data-dnd-id="', n.id,'">' , n.note, '</div>'
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">
<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
committed
A typical QFQ report which generates this HTML::
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>
}

Carsten Rose
committed
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.

Carsten Rose
committed
The html id per element is defined by `qfq-dnd-ord-id-<id>` where `<id>` is the record id. Same example as above, but

Carsten Rose
committed
with an updated `n.ord` column::

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',
'<td id="qfq-dnd-ord-id-', n.id, '">', n.ord, '</td></tr>'
FROM Note AS n
WHERE grId=28
ORDER BY n.ord

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

Carsten Rose
committed
Part 2: Order records

Carsten Rose
committed
"""""""""""""""""""""

Carsten Rose
committed
A dedicated `Form`, without any `FormElements`, is used to define the reorder logic (database update definition).

Carsten Rose
committed
* Name: <custom form name> - used in Part 1 in the `_data-dnd-api` variable.
* Table: <table with the element records> - used to update the records specified by `dragAndDropOrderSql`.

Carsten Rose
committed
+-------------------------------------------------------+--------------------------------------------------------------+
| Attribute | Description |
+=======================================================+==============================================================+
| orderInterval = <number> | Optional. By default '10'. Might be any number > 0. |
+-------------------------------------------------------+--------------------------------------------------------------+
| orderColumn = <column name> | Optional. By default 'ord'. |
+-------------------------------------------------------+--------------------------------------------------------------+

Carsten Rose
committed
| dragAndDropOrderSql = | Query to selects the *same* records as the report in the |

Carsten Rose
committed
| {{!SELECT n.id AS id, n.ord AS ord FROM Note AS n | same *order!* Inconsistencies results in order differences. |

Carsten Rose
committed
| ORDER BY n.ord}} | The columns `id` and `ord` are *mandatory.* |
+-------------------------------------------------------+--------------------------------------------------------------+
The form related to the example of part 1 ('div' or 'table'): ::

Carsten Rose
committed
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}}

Carsten Rose
committed
Re-Order:
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

Carsten Rose
committed
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
---------------

Carsten Rose
committed
* `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`

Carsten Rose
committed
Example::
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.

Carsten Rose
committed
Example::
10.sql = SELECT "p:home&r=0|t:Home|c:qfq-100 qfq-left" AS _pagev
Elias Villiger
committed
Tablesorter
-----------
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_.
Elias Villiger
committed
* 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.
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.
* Class `tablesorter-filter` enables row filtering.
* Class `tablesorter-pager` adds table paging functionality. A page navigation
* Class `tablesorter-column-selector` adds a column selector widget.

Carsten Rose
committed
* Activate/Save/Delete `views`: Insert inside of a table html-tag the command::

Carsten Rose
committed
{{ '<uniqueName>' AS _tablesorter-view-saver }}

Carsten Rose
committed
This adds a menu to save the current view (column filters, selected columns, sort order).

Carsten Rose
committed
* `<uniqueName>` should be a name which is at least unique inside the typo3 content element. Example::

Carsten Rose
committed

Carsten Rose
committed
<table {{ 'allperson' AS _tablesorter-view-saver }} class="tablesorter tablesorter-filter tablesorter-column-selector" id="{{pageAlias:T}}-demo"> ... </table>

Carsten Rose
committed

Carsten Rose
committed
* 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`).

Carsten Rose
committed
* 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::

Carsten Rose
committed
https://localhost/index.php?id=person#allperson=public:email

Carsten Rose
committed
* '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.

Carsten Rose
committed
* If there is a public view with the name 'Default' and a user has no choosen a view earlier, that one will be selected.

Carsten Rose
committed

Carsten Rose
committed
* Add the desired classes or data attributes to your table html, e.g.:

Carsten Rose
committed
* 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

Carsten Rose
committed
* 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: ::

Carsten Rose
committed
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} }'

Carsten Rose
committed
* 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.

Carsten Rose
committed

Carsten Rose
committed
Example::

Carsten Rose
committed
Elias Villiger
committed
10 {
sql = SELECT id, CONCAT('form&form=person&r=', id) AS _Pagee, lastName, title FROM person

Carsten Rose
committed
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>
Elias Villiger
committed
tail = </tbody></table>
rbeg = <tr>
rend = </tr>
fbeg = <td>
fend = </td>
}

Carsten Rose
committed

Carsten Rose
committed

Carsten Rose
committed
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.

Carsten Rose
committed

Carsten Rose
committed
* On a Typo3 page, define a HTML element with a unique html-id. E.g.::

Carsten Rose
committed
10.head = <pre id="monitor-1">Please wait</pre>

Carsten Rose
committed
* On the same Typo3 page, define an SQL column '_monitor' with the necessary parameter::

Carsten Rose
committed
10.sql = SELECT 'file:fileadmin/protected/log/sql.log|tail:50|append:1|refresh:1000|htmlId:monitor-1' AS _monitor

Carsten Rose
committed

Carsten Rose
committed
* Short version with all defaults used to display system configured sql.log::

Carsten Rose
committed
10.sql = SELECT 'file:{{sqlLog:Y}}' AS _monitor, '<pre id="monitor-1" style="white-space: pre-wrap;">Please wait</pre>'

Carsten Rose
committed
Report Examples
---------------

Carsten Rose
committed
The following section gives some examples of typical reports.

Carsten Rose
committed
Basic Queries
^^^^^^^^^^^^^

Carsten Rose
committed
One simple query::

Carsten Rose
committed
10.sql = SELECT "Hello World"

Carsten Rose
committed
Hello World

Carsten Rose
committed
Two simple queries::

Carsten Rose
committed
10.sql = SELECT "Hello World"
20.sql = SELECT "Say hello"

Carsten Rose
committed
Hello WorldSay hello
..

Carsten Rose
committed
Two simple queries, with break::

Carsten Rose
committed
10.sql = SELECT "Hello World<br>"

Carsten Rose
committed
20.sql = SELECT "Say hello"

Carsten Rose
committed
Hello World
Say hello
Accessing the database
^^^^^^^^^^^^^^^^^^^^^^

Carsten Rose
committed
Real data, one single column::

Carsten Rose
committed
10.sql = SELECT p.firstName FROM exp_person AS p

Carsten Rose
committed
BillieElvisLouisDiana

Carsten Rose
committed
Real data, two columns::

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

Carsten Rose
committed
BillieHolidayElvisPresleyLouisArmstrongDianaRoss
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.

Carsten Rose
committed
Formatting Examples
^^^^^^^^^^^^^^^^^^^
Formatting (i.e. wrapping of data with HTML tags etc.) can be achieved in two different ways:

Carsten Rose
committed
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.

Carsten Rose
committed
One can use 'level' keys to define formatting information that will be put before/after/between all rows/columns of the

Carsten Rose
committed

Carsten Rose
committed
Two columns::

Carsten Rose
committed
# Add the formatting information as a column
10.sql = SELECT p.firstName, " " , p.lastName, "<br>" FROM exp_person AS p

Carsten Rose
committed

Carsten Rose
committed
Billie Holiday
Elvis Presley
Louis Armstrong
Diana Ross
One column 'rend' as linebreak - no extra column '<br>' needed::

Carsten Rose
committed
10.sql = SELECT p.firstName, " " , p.lastName, " ", p.country FROM exp_person AS p

Carsten Rose
committed

Carsten Rose
committed
Billie Holiday USA
Elvis Presley USA
Louis Armstrong USA
Diana Ross USA

Carsten Rose
committed

Carsten Rose
committed
10.sql = SELECT p.firstName, p.lastName, p.country FROM exp_person AS p
10.rend = <br>
10.fsep = " "

Carsten Rose
committed

Carsten Rose
committed
Billie Holiday USA
Elvis Presley USA
Louis Armstrong USA
Diana Ross USA

Carsten Rose
committed

Carsten Rose
committed
More HTML::

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

Carsten Rose
committed
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>
}

Carsten Rose
committed
Two queries::

Carsten Rose
committed
10.sql = SELECT p.name FROM exp_person AS p

Carsten Rose
committed
20.sql = SELECT a.street FROM exp_address AS a

Carsten Rose
committed

Carsten Rose
committed
Two queries: nested::

Carsten Rose
committed
# outer query
10.sql = SELECT p.name FROM exp_person AS p

Carsten Rose
committed
# inner query
10.10.sql = SELECT a.street FROM exp_address AS a

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

Carsten Rose
committed
Two queries: nested with variables::

Carsten Rose
committed
# outer query
10.sql = SELECT p.id, p.name FROM exp_person AS p

Carsten Rose
committed
# inner query
10.10.sql = SELECT a.street FROM exp_address AS a WHERE a.pId='{{10.id}}'

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

Carsten Rose
committed
Two queries: nested with hidden variables in a table::

Carsten Rose
committed
10.sql = SELECT p.id AS _pId, p.name FROM exp_person AS p

Carsten Rose
committed
# inner query
10.10.sql = SELECT a.street FROM exp_address AS a WHERE a.pId='{{10.pId}}'

Carsten Rose
committed

Carsten Rose
committed
Same as above, but written in the nested notation::

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

Carsten Rose
committed
10 {
# inner query
sql = SELECT a.street FROM exp_address AS a WHERE a.pId='{{10.pId}}'

Carsten Rose
committed
}
}

Carsten Rose
committed
Best practice *recommendation* for using parameter - see `access-column-values`_::