Commit 89618c91 authored by Elias Villiger's avatar Elias Villiger
Browse files

Merge remote-tracking branch 'origin/crose_work' into elvill_work

parents 9faafc01 bb48638a
/.plantuml_install
/doc/*.pdf
/.doc_plantuml /.doc_plantuml
/.support /.support
/.support_plantuml /.support_plantuml
/.plantuml /.plantuml
/doc/plantuml /doc/plantuml
/extension/Documentation/_make/build /extension/Documentation/_make/build
/qfq.ini
/doc/phpdoc /doc/phpdoc
/.idea /.idea
/node_modules /node_modules
...@@ -18,7 +19,7 @@ ...@@ -18,7 +19,7 @@
/css /css
/fonts /fonts
/qfq.flowchart.dia.autosave /qfq.flowchart.dia.autosave
/extension/config.ini /qfq*.zip
/support /support
/extension/Resources/Public/fonts /extension/Resources/Public/fonts
/extension/Resources/Public/JavaScript /extension/Resources/Public/JavaScript
......
API: Client / Server
====================
Form initial call
-----------------
Request: index.php (QuickFormQuery.php, included by Typo3 extension)
Response:
Form attributes:
data-hidden: 'yes'|'no' - yes: The element is not visible yet, maybe later.
data-disabled: 'yes'|'no' - yes: The element is visible, but the user can't interact with it.
data-required: 'yes'|'no' - yes: The element is required. The form can't be submitted if any required element is empty.
General
-------
Asynchronous request (read AJAX) initiated by the client receive a JSON Response from the server containing at least:
{
"status": "success"|"error",
"message": "<message>"
}
`status` indicates whether or not the request has been fullfiled by the server (`"success"`) or encountered an error (`"error"`).
On `"error"` the client must display `"<message>"` to the user. On `"success"`, the client may display `"<message>"` to the user.
Form load (update)
------------------
### Trigger
Form Element with attribute `data-load="data-load"`.
The client side JavaScript installs on change handlers for all HTML Form Elements having the `data-load` attribute.
### Request: api/load.php
#### Type
POST
#### Parameters
##### URL
none
##### POST
HTML Form without `<input>` elements of type `file`. The HTML Form is required to have a HTML Form Element named `s`, which must contain the SIP.
### Response
JSON Stream
{
"status": "success"|"error",
"message": "<message>",
"redirect": "client"|"url"|"no",
"field-name": "<field name>",
"field-message": "<message>",
"form-update": [
{
"form-element": "<element_name>",
"hidden": true | false,
"disabled": true | false,
"required": true | false,
"value": <value>
}
]
}
Name | Description
------- | -----------
status | see General
message | see General
redirect | not used
field-name | HTML Form Element Name which raised error on server side. Requires status to be `"error"`
field-message | reason of error. Requires status to be `"error"`.
form-update | Array of Objects. Each object describes the state and value of a HTML Form Element identfied by its `name` attribute.
Form save
---------
### Trigger
none
### Request: api/save.php
#### Type
POST
#### Parameters
##### URL
none
##### POST
HTML Form without `<input>` elements of type `file`. The HTML Form is required to have a HTML Form Element named `s`, which must contain the SIP.
### Response
JSON Stream
{
"status": "success"|"error",
"message": "<message>",
"redirect": "client"|"url"|"no",
"field-name": "<field name>",
"field-message": "<message>",
"form-update": [
{
"form-element": "<element_name>",
"hidden": true | false,
"disabled": true | false,
"required": true | false,
"value": <value>
}
]
}
Name | Description
------- | -----------
status | see General
message | see General
redirect | not used
field-name | HTML Form Element Name which raised error on server side. Requires status to be `"error"`
field-message | reason of error. Requires status to be `"error"`.
form-update | Array of Objects. Each object describes the state and value of a HTML Form Element identfied by its `name` attribute.
File (upload)
-------------
### Trigger
none
### Request: api/file.php
#### Type
POST
#### Parameters
##### URL
`action=upload`
##### POST
Multi part form with file content, parameter `s` containing SIP, and parameter `name` containing the name of the HTML Form Element.
### Response
JSON Stream
{
"status": "success"|"error",
"message": "<message>"
}
Name | Description
------- | -----------
status | see General
message | see General
Record delete
-------------
Request: api/delete.php
Return JSON encoded answer
status: success|error
message: <message>
redirect: client|url|no
redirect-url: <url>
field-name:<field name>
field-message: <message>
Description:
Delete successfull.
status = 'success'
message = <message>
redirect = 'client'
Delete successfull.
status = 'success'
message = <message>
redirect = 'url'
redirect-url = <URL>
Delete failed: Show message.
status = 'error'
message = <message>
redirect = 'no'
var path = require('path'); var path = require('path');
module.exports = function (grunt) { module.exports = function (grunt) {
'use strict';
var typo3_css = 'extension/Resources/Public/Css/'; var typo3_css = 'extension/Resources/Public/Css/';
var typo3_js = 'extension/Resources/Public/JavaScript/'; var typo3_js = 'extension/Resources/Public/JavaScript/';
var typo3_fonts = 'extension/Resources/Public/fonts/'; var typo3_fonts = 'extension/Resources/Public/fonts/';
...@@ -147,12 +149,35 @@ module.exports = function (grunt) { ...@@ -147,12 +149,35 @@ module.exports = function (grunt) {
} }
] ]
}, },
ChartJS: {
files: [
{
cwd: 'bower_components/Chart.js/dist/',
src: [
'Chart.min.js'
],
expand: true,
dest: typo3_js,
flatten: true
},
{
cwd: 'bower_components/Chart.js/dist/',
src: [
'Chart.min.js'
],
expand: true,
dest: "js/",
flatten: true
}
]
},
jqwidgets: { jqwidgets: {
files: [ files: [
{ {
cwd: 'bower_components/jqwidgets/jqwidgets/', cwd: 'bower_components/jqwidgets/jqwidgets/',
src: [ src: [
'jqx-all.js' 'jqx-all.js',
'globalization/globalize.js'
], ],
expand: true, expand: true,
dest: typo3_js, dest: typo3_js,
...@@ -162,7 +187,7 @@ module.exports = function (grunt) { ...@@ -162,7 +187,7 @@ module.exports = function (grunt) {
cwd: 'bower_components/jqwidgets/jqwidgets/styles/', cwd: 'bower_components/jqwidgets/jqwidgets/styles/',
src: [ src: [
'jqx.base.css', 'jqx.base.css',
'jqx.darkblue.css' 'jqx.bootstrap.css'
], ],
expand: true, expand: true,
dest: typo3_css, dest: typo3_css,
...@@ -183,7 +208,8 @@ module.exports = function (grunt) { ...@@ -183,7 +208,8 @@ module.exports = function (grunt) {
{ {
cwd: 'bower_components/jqwidgets/jqwidgets/', cwd: 'bower_components/jqwidgets/jqwidgets/',
src: [ src: [
'jqx-all.js' 'jqx-all.js',
'globalization/globalize.js'
], ],
expand: true, expand: true,
dest: 'js/', dest: 'js/',
...@@ -193,7 +219,7 @@ module.exports = function (grunt) { ...@@ -193,7 +219,7 @@ module.exports = function (grunt) {
cwd: 'bower_components/jqwidgets/jqwidgets/styles/', cwd: 'bower_components/jqwidgets/jqwidgets/styles/',
src: [ src: [
'jqx.base.css', 'jqx.base.css',
'jqx.darkblue.css' 'jqx.bootstrap.css'
], ],
expand: true, expand: true,
dest: 'css/', dest: 'css/',
...@@ -209,6 +235,54 @@ module.exports = function (grunt) { ...@@ -209,6 +235,54 @@ module.exports = function (grunt) {
} }
] ]
}, },
tinymce: {
files: [
{
cwd: 'bower_components/tinymce/',
src: [
'tinymce.min.js'
],
expand: true,
dest: typo3_js,
flatten: true
},
{
cwd: 'bower_components/tinymce/',
src: [
'themes/*/theme.min.js',
'plugins/*/plugin.min.js',
'skins/**'
],
dest: typo3_js,
expand: true,
flatten: false
}
]
},
tinymce_devel: {
files: [
{
cwd: 'bower_components/tinymce/',
src: [
'tinymce.min.js'
],
expand: true,
dest: 'js/',
flatten: true
},
{
cwd: 'bower_components/tinymce/',
src: [
'themes/*/theme.min.js',
'plugins/*/plugin.min.js',
'skins/**'
],
dest: 'js/',
expand: true,
flatten: false
}
]
},
eventEmitter: { eventEmitter: {
files: [ files: [
{ {
...@@ -242,9 +316,7 @@ module.exports = function (grunt) { ...@@ -242,9 +316,7 @@ module.exports = function (grunt) {
} }
}, },
jshint: { jshint: {
all: [ all: js_sources
'javascript/src/*.js'
]
}, },
concat_in_order: { concat_in_order: {
debug_standalone: { debug_standalone: {
...@@ -315,8 +387,8 @@ module.exports = function (grunt) { ...@@ -315,8 +387,8 @@ module.exports = function (grunt) {
} }
}, },
jasmine: { jasmine: {
frontend: { unit: {
src: ['tests/jasmine/spec/*Spec.js'], src: ['tests/jasmine/unit/spec/*Spec.js'],
options: { options: {
vendor: [ vendor: [
'js/jquery.min.js', 'js/jquery.min.js',
...@@ -326,18 +398,13 @@ module.exports = function (grunt) { ...@@ -326,18 +398,13 @@ module.exports = function (grunt) {
'js/qfq.debug.js' 'js/qfq.debug.js'
], ],
helpers: ['tests/jasmine/helper/mock-ajax.js'], helpers: ['tests/jasmine/helper/mock-ajax.js'],
template: 'tests/jasmine/SpecRunner.tmpl' template: 'tests/jasmine/unit/SpecRunner.tmpl'
} }
} }
}, },
watch: { watch: {
scripts: { scripts: {
files: [ files: js_sources.concat(['less/*.less']),
'javascript/src/*.js',
'javascript/src/Helper/*.js',
'javascript/src/Element/*.js',
'less/*.less'
],
tasks: ['default'], tasks: ['default'],
options: { options: {
spawn: true spawn: true
......
...@@ -2,7 +2,7 @@ PHPDOC ?= support/pear/phpdoc ...@@ -2,7 +2,7 @@ PHPDOC ?= support/pear/phpdoc
JSDOC ?= jsdoc JSDOC ?= jsdoc
PKG_VERSION = $(shell awk '/version/ { print $$3 }' extension/ext_emconf.php | sed "s/'//g") PKG_VERSION = $(shell awk '/version/ { print $$3 }' extension/ext_emconf.php | sed "s/'//g")
NIGHTLY_DATE = $(shell date '+%Y%m%d') NIGHTLY_DATE = $(shell date '+%Y%m%d')
EXTENSION_CONTENT = Classes Configuration Documentation qfq Resources ext_emconf.php ext_localconf.php ext_tables.php config.example.ini EXTENSION_CONTENT = Classes Configuration Documentation qfq Resources ext_emconf.php ext_localconf.php ext_tables.php ext_icon.png config.example.ini
all: archive t3sphinx all: archive t3sphinx
...@@ -12,10 +12,10 @@ maintainer-clean: ...@@ -12,10 +12,10 @@ maintainer-clean:
rm -f .bowerpackages .doc_plantuml .npmpackages .phpdocinstall .plantuml_install .support .support_plantuml rm -f .bowerpackages .doc_plantuml .npmpackages .phpdocinstall .plantuml_install .support .support_plantuml
rm -rf doc support rm -rf doc support
archive: clean qfq_$(PKG_VERSION).zip archive: clean qfq.zip
qfq_$(PKG_VERSION).zip: qfq.zip:
cd extension; zip -r ../$@ $(EXTENSION_CONTENT) -x config.ini cd extension; zip -r ../$@ $(EXTENSION_CONTENT)
clean: clean:
rm -f qfq_$(PKG_VERSION).zip rm -f qfq_$(PKG_VERSION).zip
......
...@@ -5,18 +5,23 @@ Version: see `extension/ext_emconf.php` ...@@ -5,18 +5,23 @@ Version: see `extension/ext_emconf.php`
Installation Installation
------------ ------------
* Take care that the `php5-mysqlnd` driver is installed: * Ubuntu < 16.04: Take care that the `php5-mysqlnd` driver is installed:
* The following functions are used and are only available with the native driver: * See also: http://dev.mysql.com/downloads/connector/php-mysqlnd/
* If there is a error message "Call to undefined method mysqli_stmt::get_result()", `php5-mysqlnd` is not installed or not active.
* The following functions are used and are only available with the native driver:
```bash
mysqli::get_result (important), mysqli::get_result (important),
mysqli::fetch_all (nice to have) mysqli::fetch_all (nice to have)
* See also: http://dev.mysql.com/downloads/connector/php-mysqlnd/ ```
* If there is a error message "Call to undefined method mysqli_stmt::get_result()", `php5-mysqlnd` is not installed or not active.
* Ubuntu: * Ubuntu:
sudo apt-get install php5-mysqlnd ```bash
sudo php5enmod mysqlnd sudo apt-get install php5-mysqlnd
sudo service apache2 restart sudo php5enmod mysqlnd
sudo service apache2 restart
```
* Install extension as regular. * Install extension as regular.
* In `typo3conf/ext/qfq` rename `config.examle.ini` to `config.ini`. * In `typo3conf/ext/qfq` rename `config.examle.ini` to `config.ini`.
...@@ -26,23 +31,28 @@ Installation ...@@ -26,23 +31,28 @@ Installation
Bootstrap: include by TypoScript Bootstrap: include by TypoScript
--------- ---------
```script
page.includeCSS { page.includeCSS {
file1 = typo3conf/ext/qfq/Resources/Public/Css/bootstrap.min.css file1 = typo3conf/ext/qfq/Resources/Public/Css/bootstrap.min.css
file2 = typo3conf/ext/qfq/Resources/Public/Css/bootstrap-theme.min.css file2 = typo3conf/ext/qfq/Resources/Public/Css/bootstrap-theme.min.css
file3 = typo3conf/ext/qfq/Resources/Public/Css/jqx.base.css file3 = typo3conf/ext/qfq/Resources/Public/Css/jqx.base.css
file4 = typo3conf/ext/qfq/Resources/Public/Css/jqx.darkblue.css file4 = typo3conf/ext/qfq/Resources/Public/Css/jqx.bootstrap.css
file5 = typo3conf/ext/qfq/Resources/Public/Css/qfq-bs.css file5 = typo3conf/ext/qfq/Resources/Public/Css/qfq-bs.css
} }
page.includeJS { page.includeJS {
file1 = typo3conf/ext/qfq/Resources/Public/JavaScript/jquery.min.js file1 = typo3conf/ext/qfq/Resources/Public/JavaScript/jquery.min.js
file2 = typo3conf/ext/qfq/Resources/Public/JavaScript/bootstrap.min.js file2 = typo3conf/ext/qfq/Resources/Public/JavaScript/bootstrap.min.js
file3 = typo3conf/ext/qfq/Resources/Public/JavaScript/jqx-all.js file3 = typo3conf/ext/qfq/Resources/Public/JavaScript/validator.min.js
file4 = typo3conf/ext/qfq/Resources/Public/JavaScript/qfq-min.js file4 = typo3conf/ext/qfq/Resources/Public/JavaScript/jqx-all.js
file4 = typo3conf/ext/qfq/Resources/Public/JavaScript/tinymce.min.js
file5 = typo3conf/ext/qfq/Resources/Public/JavaScript/EventEmitter.min.js
file6 = typo3conf/ext/qfq/Resources/Public/JavaScript/qfq.min.js
} }
```
Usage Usage
----- -----
......
...@@ -18,9 +18,11 @@ ...@@ -18,9 +18,11 @@
], ],
"dependencies": { "dependencies": {
"bootstrap": "~3.3.6", "bootstrap": "~3.3.6",
"jqwidgets": "*", "jqwidgets": "4.2.1",
"tablesorter": "jquery.tablesorter#^2.25.6", "tablesorter": "jquery.tablesorter#^2.25.6",
"eventEmitter": "^4.3.0", "eventEmitter": "^4.3.0",
"bootstrap-validator": "^0.10.2" "bootstrap-validator": "^0.11.5",
"Chart.js": "^2.1.2",
"tinymce": "tinymce-dist#^4.4.3"
} }
} }
...@@ -23,7 +23,7 @@ LOAD ...@@ -23,7 +23,7 @@ LOAD
* When qfq starts, * When qfq starts,
* (Form) Looking for a formname at: * (Form) Looking for a formname at:
1. Typo3 Bodytext Element, 1. Typo3 Bodytext Element,
2. For the 'SIP' ($_GET['s']) 2. For the 'SIP' ($_GET['s'] => $S_SESSION['qfq'][$_GET['s']]="form=person&r=123")
3. $_GET variables 'form' and 'r' (=recordId) - the parameter 'form' has to be allowed in 'Permit URL Parameter' of 3. $_GET variables 'form' and 'r' (=recordId) - the parameter 'form' has to be allowed in 'Permit URL Parameter' of
the specified form. This means: load the form to check, if it is allowed to load the form!? the specified form. This means: load the form to check, if it is allowed to load the form!?
* If a formname is found, the search stops and the specified form will be processed. * If a formname is found, the search stops and the specified form will be processed.
...@@ -36,12 +36,20 @@ LOAD ...@@ -36,12 +36,20 @@ LOAD
* All parameters from active SIP: [$this->store->getStore(STORE_SIP)] * All parameters from active SIP: [$this->store->getStore(STORE_SIP)]
* Check Contstants.php for known Store members * Check Contstants.php for known Store members
* In QuickFormQuery.php the whole Form will be copied to $this->formSpec and depending on further processing, the elements are * In QuickFormQuery.php the whole Form will be copied to `$this->formSpec` and depending on further processing, the
available in $this->feNative and $this->feAction. elements are available in `$this->feNative` and `$this->feAction`.
* The Form specificaton (table form) will be evaluated direct after loading. * The Form specificaton (table form) will be evaluated direct after loading.
* The FormElement specification will be evaluated later on in BuildForm*.php * The FormElement specification will be evaluated later on in BuildForm*.php
* If a form is called without a SIP (form.permitNew='always'), than a SIP is created on the fly (as a
parameter in the form).
* Depending on `r=0` or `r>0` a form submit will do an MySQL `insert` or `update` later during save.
* For new records (r=0), clicking on 'save' without closing the form is a tricky situation. Additionally the user might have open multiple
tabs (same form, all r=0) and after saving the record (wihtout closing the form) the user expects that it's ok to edit
the record again and again. Unfortunately, the initial created SIP (before 'form load') is not uniqe anymore (multiple
tabs might contain a saved 'new record'). To guarantee correct saving of r=0 records, a unique on the fly generated SIP
is creatd during form load - individually per browser tab.
SAVE SAVE
---- ----
* Via wrapper api/save.php * Via wrapper api/save.php
...@@ -51,6 +59,18 @@ SAVE ...@@ -51,6 +59,18 @@ SAVE
* Client will handle the response of save.php. * Client will handle the response of save.php.
* Optional redirection initiated by client. * Optional redirection initiated by client.
New records
...........
* r=0 (missing 'r' means r=0)
* After saving the SIP content will be updated with the new record.
Remember that the SIP in the URL is *not* the SIP used in the form to identify the form/record. The form use a
individual 'new record' SIP.
Existing records
................
* r>0 ('r' have to exist)
DELETE DELETE
------