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
/.support
/.support_plantuml
/.plantuml
/doc/plantuml
/extension/Documentation/_make/build
/qfq.ini
/doc/phpdoc
/.idea
/node_modules
......@@ -18,7 +19,7 @@
/css
/fonts
/qfq.flowchart.dia.autosave
/extension/config.ini
/qfq*.zip
/support
/extension/Resources/Public/fonts
/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');
module.exports = function (grunt) {
'use strict';
var typo3_css = 'extension/Resources/Public/Css/';
var typo3_js = 'extension/Resources/Public/JavaScript/';
var typo3_fonts = 'extension/Resources/Public/fonts/';
......@@ -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: {
files: [
{
cwd: 'bower_components/jqwidgets/jqwidgets/',
src: [
'jqx-all.js'
'jqx-all.js',
'globalization/globalize.js'
],
expand: true,
dest: typo3_js,
......@@ -162,7 +187,7 @@ module.exports = function (grunt) {
cwd: 'bower_components/jqwidgets/jqwidgets/styles/',
src: [
'jqx.base.css',
'jqx.darkblue.css'
'jqx.bootstrap.css'
],
expand: true,
dest: typo3_css,
......@@ -183,7 +208,8 @@ module.exports = function (grunt) {
{
cwd: 'bower_components/jqwidgets/jqwidgets/',
src: [
'jqx-all.js'
'jqx-all.js',
'globalization/globalize.js'
],
expand: true,
dest: 'js/',
......@@ -193,7 +219,7 @@ module.exports = function (grunt) {
cwd: 'bower_components/jqwidgets/jqwidgets/styles/',
src: [
'jqx.base.css',
'jqx.darkblue.css'
'jqx.bootstrap.css'
],
expand: true,
dest: 'css/',
......@@ -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: {
files: [
{
......@@ -242,9 +316,7 @@ module.exports = function (grunt) {
}
},
jshint: {
all: [
'javascript/src/*.js'
]
all: js_sources
},
concat_in_order: {
debug_standalone: {
......@@ -315,8 +387,8 @@ module.exports = function (grunt) {
}
},
jasmine: {
frontend: {
src: ['tests/jasmine/spec/*Spec.js'],
unit: {
src: ['tests/jasmine/unit/spec/*Spec.js'],
options: {
vendor: [
'js/jquery.min.js',
......@@ -326,18 +398,13 @@ module.exports = function (grunt) {
'js/qfq.debug.js'
],
helpers: ['tests/jasmine/helper/mock-ajax.js'],
template: 'tests/jasmine/SpecRunner.tmpl'
template: 'tests/jasmine/unit/SpecRunner.tmpl'
}
}
},
watch: {
scripts: {
files: [
'javascript/src/*.js',
'javascript/src/Helper/*.js',
'javascript/src/Element/*.js',
'less/*.less'
],
files: js_sources.concat(['less/*.less']),
tasks: ['default'],
options: {
spawn: true
......
......@@ -2,7 +2,7 @@ PHPDOC ?= support/pear/phpdoc
JSDOC ?= jsdoc
PKG_VERSION = $(shell awk '/version/ { print $$3 }' extension/ext_emconf.php | sed "s/'//g")
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
......@@ -12,10 +12,10 @@ maintainer-clean:
rm -f .bowerpackages .doc_plantuml .npmpackages .phpdocinstall .plantuml_install .support .support_plantuml
rm -rf doc support
archive: clean qfq_$(PKG_VERSION).zip
archive: clean qfq.zip
qfq_$(PKG_VERSION).zip:
cd extension; zip -r ../$@ $(EXTENSION_CONTENT) -x config.ini
qfq.zip:
cd extension; zip -r ../$@ $(EXTENSION_CONTENT)
clean:
rm -f qfq_$(PKG_VERSION).zip
......
......@@ -5,18 +5,23 @@ Version: see `extension/ext_emconf.php`
Installation
------------
* Take care that the `php5-mysqlnd` driver is installed:
* The following functions are used and are only available with the native driver:
* Ubuntu < 16.04: Take care that the `php5-mysqlnd` driver is installed:
* 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::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
sudo php5enmod mysqlnd
sudo service apache2 restart
```bash
sudo apt-get install php5-mysqlnd
sudo php5enmod mysqlnd
sudo service apache2 restart
```
* Install extension as regular.
* In `typo3conf/ext/qfq` rename `config.examle.ini` to `config.ini`.
......@@ -26,23 +31,28 @@ Installation
Bootstrap: include by TypoScript
---------
```script
page.includeCSS {
file1 = typo3conf/ext/qfq/Resources/Public/Css/bootstrap.min.css
file2 = typo3conf/ext/qfq/Resources/Public/Css/bootstrap-theme.min.css
file3 = typo3conf/ext/qfq/Resources/Public/Css/jqx.base.css
file4 = typo3conf/ext/qfq/Resources/Public/Css/jqx.darkblue.css
file5 = typo3conf/ext/qfq/Resources/Public/Css/qfq-bs.css
file1 = typo3conf/ext/qfq/Resources/Public/Css/bootstrap.min.css
file2 = typo3conf/ext/qfq/Resources/Public/Css/bootstrap-theme.min.css
file3 = typo3conf/ext/qfq/Resources/Public/Css/jqx.base.css
file4 = typo3conf/ext/qfq/Resources/Public/Css/jqx.bootstrap.css
file5 = typo3conf/ext/qfq/Resources/Public/Css/qfq-bs.css
}
page.includeJS {
file1 = typo3conf/ext/qfq/Resources/Public/JavaScript/jquery.min.js
file2 = typo3conf/ext/qfq/Resources/Public/JavaScript/bootstrap.min.js
file3 = typo3conf/ext/qfq/Resources/Public/JavaScript/jqx-all.js
file4 = typo3conf/ext/qfq/Resources/Public/JavaScript/qfq-min.js
file1 = typo3conf/ext/qfq/Resources/Public/JavaScript/jquery.min.js
file2 = typo3conf/ext/qfq/Resources/Public/JavaScript/bootstrap.min.js
file3 = typo3conf/ext/qfq/Resources/Public/JavaScript/validator.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
-----
......
......@@ -18,9 +18,11 @@
],
"dependencies": {
"bootstrap": "~3.3.6",
"jqwidgets": "*",
"jqwidgets": "4.2.1",
"tablesorter": "jquery.tablesorter#^2.25.6",
"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
* When qfq starts,
* (Form) Looking for a formname at:
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
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.
......@@ -36,12 +36,20 @@ LOAD
* All parameters from active SIP: [$this->store->getStore(STORE_SIP)]
* 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
available in $this->feNative and $this->feAction.
* In QuickFormQuery.php the whole Form will be copied to `$this->formSpec` and depending on further processing, the
elements are available in `$this->feNative` and `$this->feAction`.
* The Form specificaton (table form) will be evaluated direct after loading.
* 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
----
* Via wrapper api/save.php
......@@ -51,6 +59,18 @@ SAVE
* Client will handle the response of save.php.
* 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
------
* Via wrapper api/delete.php
......@@ -60,153 +80,67 @@ DELETE
* class=record-delete
* Button: data-sip={{SIP}}
* SIP values:
* SIP_RECORD_ID: Mandatory.
* SIP_TABLE: Either SIP_TABLE or SIP_FORM has to be given.
* SIP_FORM: Either SIP_TABLE or SIP_FORM has to be given. Not implemented now.
* SIP_TARGET_URL: Only with SIP_MODE_ANSWER=MODE_HTML - Url to redirect browser to.
* SIP_MODE_ANSWER: MODE_JSON / MODE_HTML. If not given, this means MODE_JSON.
* Three possible variants with delete links:
* Form: main record
* (1) Form: main record
* HTML Code:
<button id="delete-button" type="button" class="btn btn-default navbar-btn" ><span class="glyphicon glyphicon-trash"></span></button>
* Form: subrecord, one delete button per record
* Report: typially inside a table, but maybe different.
* (2) Form: subrecord, one delete button per record
* HTML Code:
<button type="button" class="record-delete" data-sip={{SIP}} ><span class="glyphicon glyphicon-trash"></span></button>
* (3) Report: typially inside a table, but maybe different.
USER INTERFACE
==============
<button type="button" class="record-delete" data-sip={{SIP}} ><span class="glyphicon glyphicon-trash"></span></button>
Button status
-------------
* Form modified:
* Buttons enabled: Save, Close, New, Delete
* Button disable: -
* Form not modified:
* Buttons enabled: Close, New, Delete
* Button disabled: Save
Save Button
-----------
* User presses the button
* Reset all validation states
* Client validates HTML Form
* Form is submitted to server
* Success:
* Show message provided by server
* Current formelements and data will be reloaded.
* Process server reponse 'redirect':
* 'client': No redirect.
* 'no': No redirect.
* 'url': Redirect to URL
* Failure: Happens on communication errors, if data validation fails, form actions fails or saving data fails.
* Show error message.
* Client: Ignore server reponse 'redirect'. Client stays on current page.
Close Button
------------
* User presses the button
* Form not modified by user: Client redirects to previous page.
* Form modified by user: Ask User 'You have unsaved changes. Do you want to close?'
* Yes: Client redirects to previous page.
* No: Client stays on current page.
* Save & Close:
* Client reset all validation states
* Client validates HTML Form
* Client submits form to server.
* Success: Process server response 'redirect':
* 'client': Client shows previous page.
* 'no': No redirect.
* 'url': Redirect to URL
* Failure: Happens on communication errors, if data validation fails, form actions fails or saving data fails.
* Show error message.
* Client: No redirect. Ignore server reponse 'redirect'.
Delete Button: Main record
--------------------------
* User presses the button. Ask User 'Do you really want to delete the record?
* Yes:
* Delete record on server.
* Process server reponse 'redirect':
* 'client': Client redirects to previous page.
* 'no': Error message.
* 'url': Redirect to URL
* No:
* Client does not delete record on server.
* No redirect. Client stays on current page.
New Button
----------
* User presses the button
* Form not modified by user: Client redirects to href url.
* Form modified by user: Ask User 'You have unsaved changes. Do you want to save first?'
* Yes:
* Client reset all validation states
* Client validates HTML Form
* Form is submitted to server
* Success: