diff --git a/CHANGELOG.md b/CHANGELOG.md index 69bee416733fb2a5d668b1a0b749d91c5ebfe3ea..7f6c59c50137d82263e90c166e7cadf237b2e530 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1203,7 +1203,7 @@ Bug Fixes * Dynamic Update has been broken since implementing of 'element-update' (#3180). Now both methods, 'element-update' and 'form-update' should be fine. * qfq-bs.css.less: Fixed problem with 'typeahead input elements' not expanded to Bootstrap column width. Changed - Layout/Design Typeahead drop-down box. Add hoover for the drop-down box with a blue background + Layout/Design Typeahead drop-down box. Add hover for the drop-down box with a blue background * AbstractBuildForm.php: #3374 - textarea elements now contains 'maxlength' attribute. * BuildFormBootstrap.php: wrapping of optional 'submitButtonText' now done with the 'per form' values. * typeahead.php: if there is an exception, the message body is sent as regular 'content' for the drop-down box. At the diff --git a/doc/DRAGANDDROP.md b/doc/DRAGANDDROP.md new file mode 100644 index 0000000000000000000000000000000000000000..14d9b9cd5216ac4d23fdc6d8b618fc8810067b7b --- /dev/null +++ b/doc/DRAGANDDROP.md @@ -0,0 +1,22 @@ +# Drag And Drop + +## Sort +Initialize a dnd container by adding the class "qfq-dnd" + +Set container object class to `class="qfq-dnd qfq-dnd-sort"`. + +Add the data elements: `data-dnd-api="url"` and `data-dnd-key="key"`. + +For the children inside of the container (just the first children): +add `data-dnd-id` to a reference you can handle (probably record id). + +Request will be sent containing following GET variables: + +* dragId = `data-dnd-id` of the dragged object, +* dragPosition = client internal old position of the dragged object. +* setTo = "after" or "before", +* hoverId = `data-dnd-id` id of the element the dragged element is now hovering, meaning before or after. +* hoverPosition = client internal position of currently hovered element. + + +Example: http://something/bla?dragId=uno&dragPosition=1&setTo=before&hoverId=tre&hoverPosition=3 \ No newline at end of file diff --git a/doc/PROTOCOL.md b/doc/PROTOCOL.md index a7fa4211f1225e2f9a6fd9087b573729d519b2ac..a7ef5fc514f64e5d708c480436a7ba807fefaf62 100644 --- a/doc/PROTOCOL.md +++ b/doc/PROTOCOL.md @@ -73,11 +73,11 @@ the Client by adding following name/value pairs to the response JSON Stream { - "status": "error", - ... - "field-name": "<field name>", - "field-message": "<message>", - ... + "status": "error", + ... + "field-name": "<field name>", + "field-message": "<message>", + ... } Only one validation failure per request can be reported to Client. @@ -465,7 +465,7 @@ Request Method : GET URL Parameters -: `s=<SIP>` (form, r) +: `s=<SIP>` (form, r) : `action=lock`, `action=extend`, `action=release>` : `recordHashMd5=<value of hidden form element 'recordHashMd5'>` @@ -479,7 +479,7 @@ JSON Response from the server (extended [Minimal Response]) containing: { "status": "success"|"error"|"conflict"|"conflict_allow_force", - "message": "<message>" + "message": "<message>"e5 } `status` indicates how the request has been fulfilled by the server. @@ -488,6 +488,29 @@ On one of`"error"|"conflict"|"conflict_allow_force"` the Client must display `"< On `"conflict"` the Client opens the alert as modal dialog (user can't change anything on the form) with a 'reload current form' button. On `"conflict_allow_force"` the Client opens the alert non-modal (default). + + +### Drag And Drop (sort) + +Request +: api/dragAndDrop.php + +Request Method +: GET + +URL Parameters: + +: `s=<SIP>` (`form=<formname>`) +: +: `dragId=<data-dnd-id of dragged element>` +: `dragPosition=<client internal position (numbering) of element before dragging>` +: `setTo=before`, `setTo=after` +: `hoverId=<data-dnd-id of dragged element>` +: `hoverPosition=<client internal position (numbering) of element after dragging>` + +Server Response +: The response contains at least a [Minimal Response]. In addition, a + [HTML Element Update] may be included. ## Glossary diff --git a/extension/Documentation/Manual.rst b/extension/Documentation/Manual.rst index 353e8e9ea3f7bccee6bcf629c9283d38a50bfd5d..e721d7b186b7c7dad23933e09a28406c1cc3d442 100644 --- a/extension/Documentation/Manual.rst +++ b/extension/Documentation/Manual.rst @@ -2556,7 +2556,7 @@ See also at specific *FormElement* definitions. +------------------------+--------+----------------------------------------------------------------------------------------------------------+ | htmlAfter | string | HTML Code wrapped after the complete *FormElement* | +------------------------+--------+----------------------------------------------------------------------------------------------------------+ -| wrapRow | string | If specified, skip default wrapping (`<div class='col-md-?>`). Instead the given string is used. | +| wrapRow | string | If specified, skip default wrapping (`<div class='col-md-?'>`). Instead the given string is used. | +------------------------+--------+ | | wrapLabel | string | | +------------------------+--------+ | @@ -2937,6 +2937,8 @@ Type: text the value is an empty string * *inputType* = number (optional). Typically the HTML tag 'type' will be 'text', 'textarea' or 'number' (detected automatically). If necessary, the HTML tag 'type' might be forced to a specific given value. + * *step* = Step size of the up/down buttons which increase/decrease the number of in the input field. Optional. + Default 1. Only useful with `inputType=number` (defined explicit via `inputType` or detected automatically). .. _`input-typeahead`: @@ -4119,6 +4121,21 @@ Best practice Custom default value only for 'new records' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Method 1 +'''''''' + +On `Form.parameter` define a `fillStoreVar` query with a column name equal to a form field. That's all. + +Example: :: + + FormElement.name = technicalContact + Form.parameter.fillStoreVar = {{!SELECT CONCAT(p.firstName, ' ', p.name) AS technicalContact FROM Person AS p WHERE p.account='{{feUser:T}}' }} + +What we use here is the default STORE prio FSRVD. If the form loads with r=0, 'F', 'S' and 'R' are empty. 'V' is filled. +If r>0, than 'F' and 'S' are empty and 'R' is filled. + +Method 2 +'''''''' In the specific `FormElement` set `value={{columnName:RSE}}`. The link to the form should be rendered with '"...&columnName=<data>&..." AS _page'. The trick is that the STORE_RECORD is empty for new records, and therefore the corresponding value from STORE_SIP will be returned. Existing records will use the already saved value. @@ -6218,8 +6235,125 @@ E.g.:: 10.sql = SELECT "p:home&r=0|t:Home|c:qfq-100 qfq-left" AS _pagev -Examples --------- + +Drag and drop +------------- + +Sort/order elements +^^^^^^^^^^^^^^^^^^^ + +Manually sorting and ordering of elements via `HTML5 drag and drop` is supported via QFQ. Any sortable element +should be represented by a database record with an order column. If the elements are unordered, they will be ordered after +the first 'drag and drop' move of an element. + +Functionality is divided into: + +* Display list: the records will be displayed via QFQ/report. +* Update database: updates of the order column are managed by a specific 'drag and drop' definition form. + +Part 1: Display list +'''''''''''''''''''' + +Display the list of elements via a regular QFQ content record. All 'drag and drop' elements have to be nested by an HTML +element: + +* With `class="qfq-dnd-sort"`. +* With an automatically SIP protected form name: `{{'form=<form name>' AS _data-dnd-api}}` +* 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: :: + + <div class="qfq-dnd-sort" data-dnd-api="typo3conf/ext/qfq/qfq/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: :: + + 10 { + sql = SELECT '<div id="anytag-', n.id,'" data-dnd-id="', n.id,'">' , n.note, '</div>' + FROM Note AS n + WHERE grId=28 + ORDER BY n.ord + + head = <div class="qfq-dnd-sort" data-dnd-api="{{'form=dndSortNote&grId=28|A:dnd-sort' AS _api}}"> + tail = </div> + } + + +A `<table>` based setup is also possible. Note the attribute `data-columns="3"` - those generates a dropzone +which is the same width as the outer table. :: + + <table> + <tbody class="qfq-dnd-sort" data-dnd-api="typo3conf/ext/qfq/qfq/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> + +A typical QFQ report which generates those 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> + } + +Part 2: Update database +''''''''''''''''''''''' + +A dedicated `Form`, without any `FormElements`, is needed to define the database update definition. + +Fields: + +* Name: <custom form name> - used in Part 1 in the `_data-dnd-api` variable. +* Table: <table with the element records> - used to the update the records specified by `dragAndDropOrderSql`. + +* Parameter: + ++-------------------------------------+--------------------------------------------------------------------------------+ +| 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 sort differences. | +| ORDER BY n.ord}} | The columns `id` and `ord` are *mandatory.* | ++-------------------------------------------------------+--------------------------------------------------------------+ + +The form related to the example of part 1: :: + + 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}} + +Report Examples +--------------- The following section gives some examples of typical reports @@ -6571,42 +6705,42 @@ Create / edit `AutoCron` jobs Create a T3 page with a QFQ record (similar to the formeditor). Such page should be access restricted and is only needed to edit `AutoCron` jobs: :: - dbIndex={{indexQfq:Y}} - form={{form:S}} + dbIndex={{indexQfq:Y}} + form={{form:S}} - 10 { - # Table header. - sql = SELECT CONCAT('p:{{pageId:T}}&form=cron') AS _pagen, 'id', 'Next run','Frequency','Comment','Last run','In progress', 'Status' FROM (SELECT 1) AS fake WHERE '{{form:SE}}'='' - head = <table class='table table-hover qfq-table-50'> - tail = </table> - rbeg = <thead><tr> - rend = </tr></thead> - fbeg = <th> - fend = </th> - - 10 { - # All Cron Jobs - sql = SELECT CONCAT('<tr class="', - IF(c.lastStatus LIKE 'Error%','danger',''), - IF(c.inProgress!=0 AND DATE_ADD(c.inProgress, INTERVAL 10 MINUTE)<NOW(),' warning',''), - IF(c.status='enable','',' text-muted'),'" ', - - IF(c.inProgress!=0 AND DATE_ADD(c.inProgress, INTERVAL 10 MINUTE)<NOW(),'title="inProgress > 10mins"', - IF(c.lastStatus LIKE 'Error%','title="Status: Error"','')), - '>'), - '<td>', CONCAT('p:{{pageId:T}}&form=cron&r=', c.id) AS _pagee, '</td><td>', - c.id, '</td><td>', - IF(c.nextrun=0,"", DATE_FORMAT(c.nextrun, "%d.%m.%y %H:%i:%s")), '</td><td>', - c.frequency, '</td><td>', - c.comment, '</td><td>', - IF(c.lastrun=0,"", DATE_FORMAT(c.lastrun,"%d.%m.%y %H:%i:%s")), '</td><td>', - IF(c.inProgress=0,"", DATE_FORMAT(c.inProgress,"%d.%m.%y %H:%i:%s")), '</td><td>', - LEFT(c.laststatus,40) AS '_+pre', '</td><td>', - CONCAT('U:form=cron&r=', c.id) AS _paged, '</td></tr>' - FROM Cron AS c - ORDER BY c.id - } - } + 10 { + # Table header. + sql = SELECT CONCAT('p:{{pageId:T}}&form=cron') AS _pagen, 'id', 'Next run','Frequency','Comment','Last run','In progress', 'Status' FROM (SELECT 1) AS fake WHERE '{{form:SE}}'='' + head = <table class='table table-hover qfq-table-50'> + tail = </table> + rbeg = <thead><tr> + rend = </tr></thead> + fbeg = <th> + fend = </th> + + 10 { + # All Cron Jobs + sql = SELECT CONCAT('<tr class="', + IF(c.lastStatus LIKE 'Error%','danger',''), + IF(c.inProgress!=0 AND DATE_ADD(c.inProgress, INTERVAL 10 MINUTE)<NOW(),' warning',''), + IF(c.status='enable','',' text-muted'),'" ', + + IF(c.inProgress!=0 AND DATE_ADD(c.inProgress, INTERVAL 10 MINUTE)<NOW(),'title="inProgress > 10mins"', + IF(c.lastStatus LIKE 'Error%','title="Status: Error"','')), + '>'), + '<td>', CONCAT('p:{{pageId:T}}&form=cron&r=', c.id) AS _pagee, '</td><td>', + c.id, '</td><td>', + IF(c.nextrun=0,"", DATE_FORMAT(c.nextrun, "%d.%m.%y %H:%i:%s")), '</td><td>', + c.frequency, '</td><td>', + c.comment, '</td><td>', + IF(c.lastrun=0,"", DATE_FORMAT(c.lastrun,"%d.%m.%y %H:%i:%s")), '</td><td>', + IF(c.inProgress=0,"", DATE_FORMAT(c.inProgress,"%d.%m.%y %H:%i:%s")), '</td><td>', + LEFT(c.laststatus,40) AS '_+pre', '</td><td>', + CONCAT('U:form=cron&r=', c.id) AS _paged, '</td></tr>' + FROM Cron AS c + ORDER BY c.id + } + } Usage diff --git a/extension/qfq/api/dragAndDrop.php b/extension/qfq/api/dragAndDrop.php new file mode 100644 index 0000000000000000000000000000000000000000..f4b8d21895ebd490bda0bf5ef90e26e2d44d7ddb --- /dev/null +++ b/extension/qfq/api/dragAndDrop.php @@ -0,0 +1,80 @@ +<?php +/** + * Created by PhpStorm. + * User: ep + * Date: 12/23/15 + * Time: 6:17 PM + */ + +namespace qfq; + +use qfq; + +require_once(__DIR__ . '/../qfq/store/Store.php'); +require_once(__DIR__ . '/../qfq/Constants.php'); +require_once(__DIR__ . '/../qfq/QuickFormQuery.php'); +//require_once(__DIR__ . '/../qfq/exceptions/UserFormException.php'); +//require_once(__DIR__ . '/../qfq/exceptions/CodeException.php'); +//require_once(__DIR__ . '/../qfq/exceptions/DbException.php'); +//require_once(__DIR__ . '/../qfq/exceptions/ErrorHandler.php'); + + +/** + * Return JSON encoded answer + * + * status: success|error + * message: <message> + * redirect: client|url|no + * redirect-url: <url> + * field-name: <field name> + * field-message: <message> + * form-data: [ fieldname1 => value1, fieldname2 => value2, ... ] + * form-control: [ fieldname1 => status1, fieldname2 => status2, ... ] status: show|hide, enabled|disabled, + * readonly|readwrite + * + * Description: + * + * Save successful. Button 'close', 'new'. Form.forward: 'auto'. Client logic decide to redirect or not. Show message + * if no redirect. status = 'success' message = <message> redirect = 'client' + * + * Save successful. Button 'close': Form.forward: 'page'. Client redirect to url. + * status = 'success' + * message = <message> + * redirect = 'url' + * redirect-url = <URL> + * + * Save failed: Button: any. Show message and set 'alert' on _optional_ specified form element. Bring 'pill' of + * specified form element to front. status = 'error' message = <message> redirect = 'no' Optional: field-name = <field + * name> field-message = <message appearing as tooltip (or similar) near the form element> + */ + +$answer = array(); + +$answer[API_REDIRECT] = API_ANSWER_REDIRECT_NO; +$answer[API_STATUS] = API_ANSWER_STATUS_ERROR; +$answer[API_MESSAGE] = ''; + +try { + $qfq = new QuickFormQuery(['bodytext' => '']); + + $data = $qfq->dragAndDrop(); + + $answer[API_STATUS] = API_ANSWER_STATUS_SUCCESS; + $answer[API_MESSAGE] = 'reorder: success'; +// $answer[API_FORM_UPDATE] = $data[API_FORM_UPDATE]; +// $answer[API_ELEMENT_UPDATE] = $data[API_ELEMENT_UPDATE]; +// unset($answer[API_FORM_UPDATE][API_ELEMENT_UPDATE]); + +} catch (qfq\UserFormException $e) { + $answer[API_MESSAGE] = $e->formatMessage(); +} catch (qfq\CodeException $e) { + $answer[API_MESSAGE] = $e->formatMessage(); +} catch (qfq\DbException $e) { + $answer[API_MESSAGE] = $e->formatMessage(); +} catch (\Exception $e) { + $answer[API_MESSAGE] = "Generic Exception: " . $e->getMessage(); +} + +header("Content-Type: application/json"); +echo json_encode($answer); + diff --git a/extension/qfq/qfq/AbstractBuildForm.php b/extension/qfq/qfq/AbstractBuildForm.php index 6dc85c838d5026dbce982f9eef30a0b89d0ff66f..54d53da30f57309a960e401448f2869719c24521 100644 --- a/extension/qfq/qfq/AbstractBuildForm.php +++ b/extension/qfq/qfq/AbstractBuildForm.php @@ -86,6 +86,8 @@ abstract class AbstractBuildForm { * @param array $feSpecAction * @param array $feSpecNative * @param array $db + * @throws CodeException + * @throws UserFormException */ public function __construct(array $formSpec, array $feSpecAction, array $feSpecNative, array $db = null) { $this->formSpec = $formSpec; @@ -172,6 +174,8 @@ abstract class AbstractBuildForm { * @param array $latestFeSpecNative * @return array|string $mode=LOAD_FORM: The whole form as HTML, $mode=FORM_UPDATE: array of all * formElement.dynamicUpdate-yes values/states + * @throws CodeException + * @throws DbException * @throws UserFormException */ public function process($mode, $htmlElementNameIdZero = false, $latestFeSpecNative = array()) { @@ -247,6 +251,8 @@ abstract class AbstractBuildForm { * * @param string $mode * @return string + * @throws CodeException + * @throws UserFormException */ public function head($mode = FORM_LOAD) { $html = ''; @@ -276,6 +282,8 @@ abstract class AbstractBuildForm { * * @return string String: <a href="?pageId&sip=....">Edit</a> <small>[sip:..., r:..., urlparam:..., * ...]</small> + * @throws CodeException + * @throws UserFormException */ public function createFormEditorUrl($form, $recordId, array $param = array()) { @@ -321,6 +329,9 @@ abstract class AbstractBuildForm { * Returns '<form ...>'-tag with various attributes. * * @return string + * @throws CodeException + * @throws DbException + * @throws UserFormException */ public function getFormTag() { $md5 = ''; @@ -338,6 +349,9 @@ abstract class AbstractBuildForm { * Build MD5 from the current record. Return HTML Input element. * * @return string + * @throws CodeException + * @throws DbException + * @throws UserFormException */ public function buildInputRecordHashMd5() { @@ -357,6 +371,9 @@ abstract class AbstractBuildForm { * @param $recordId * * @return string + * @throws CodeException + * @throws DbException + * @throws UserFormException */ public function buildRecordHashMd5($tableName, $recordId) { $record = array(); @@ -372,6 +389,8 @@ abstract class AbstractBuildForm { * Create HTML Input vars to detect bot automatic filling of forms. * * @return string + * @throws CodeException + * @throws UserFormException */ public function getHoneypotVars() { $html = ''; @@ -442,6 +461,9 @@ abstract class AbstractBuildForm { * See: https://www.w3.org/wiki/HTML/Elements/form#HTML_Attributes * * @return string + * @throws CodeException + * @throws DbException + * @throws UserFormException */ public function getEncType() { @@ -457,6 +479,10 @@ abstract class AbstractBuildForm { * @param array|string $value * * @return array|string + * @throws CodeException + * @throws DbException + * @throws UserFormException + * @throws UserReportException */ private function processReportSyntax($value) { @@ -502,6 +528,10 @@ abstract class AbstractBuildForm { * @param string $mode FORM_LOAD | FORM_UPDATE | FORM_SAVE * * @return string + * @throws CodeException + * @throws DbException + * @throws UserFormException + * @throws UserReportException */ public function elements($recordId, $filter = FORM_ELEMENTS_NATIVE, $feIdContainer = 0, array &$json, $modeCollectFe = FLAG_DYNAMIC_UPDATE, $htmlElementNameIdZero = false, @@ -672,6 +702,10 @@ abstract class AbstractBuildForm { * @param array $formElement * * @return array + * @throws CodeException + * @throws DbException + * @throws UserFormException + * @throws UserReportException */ private function prepareFillStoreFireLdap(array $formElement) { @@ -767,6 +801,8 @@ abstract class AbstractBuildForm { * save/update. * * @return string + * @throws CodeException + * @throws UserFormException */ private function prepareT3VarsForSave() { @@ -802,6 +838,8 @@ abstract class AbstractBuildForm { * Get all elements from STORE_ADDITIONAL_FORM_ELEMENTS and return them as a string. * * @return string + * @throws CodeException + * @throws UserFormException */ private function buildAdditionalFormElements() { @@ -816,6 +854,8 @@ abstract class AbstractBuildForm { * @param array $json * * @return string <input type='hidden' name='s' value='<sip>'> + * @throws CodeException + * @throws UserFormException */ public function buildHiddenSip(array &$json) { @@ -921,6 +961,7 @@ abstract class AbstractBuildForm { * @param array $feMode * * @return array + * @throws UserFormException */ private function getJsonFeMode($feMode) { @@ -970,6 +1011,7 @@ abstract class AbstractBuildForm { * @param string $addClass * * @return string + * @throws CodeException */ public function buildLabel($htmlFormElementName, $label, $addClass = '') { $attributes = Support::doAttribute('for', $htmlFormElementName); @@ -985,6 +1027,8 @@ abstract class AbstractBuildForm { * * @param $toolTipNew * @return string + * @throws CodeException + * @throws UserFormException */ public function deriveNewRecordUrlFromExistingSip(&$toolTipNew) { @@ -1048,6 +1092,8 @@ abstract class AbstractBuildForm { * @param string $mode FORM_LOAD | FORM_UPDATE | FORM_SAVE * * @return string complete rendered HTML input element. + * @throws CodeException + * @throws UserFormException */ public function buildInput(array $formElement, $htmlFormElementName, $value, array &$json, $mode = FORM_LOAD) { $textarea = ''; @@ -1154,7 +1200,7 @@ abstract class AbstractBuildForm { if (empty($formElement[F_FE_DATA_PATTERN_ERROR])) { $formElement[F_FE_DATA_PATTERN_ERROR] = $sanitizeMessage; }; - $attribute .= $this->getAttributeList($formElement, [F_FE_DATA_PATTERN_ERROR, F_FE_DATA_REQUIRED_ERROR, F_FE_DATA_MATCH_ERROR, F_FE_DATA_ERROR, FE_MIN, FE_MAX]); + $attribute .= $this->getAttributeList($formElement, [F_FE_DATA_PATTERN_ERROR, F_FE_DATA_REQUIRED_ERROR, F_FE_DATA_MATCH_ERROR, F_FE_DATA_ERROR, FE_MIN, FE_MAX, FE_STEP]); $attribute .= Support::doAttribute('data-load', ($formElement[FE_DYNAMIC_UPDATE] === 'yes') ? 'data-load' : ''); $attribute .= Support::doAttribute('title', $formElement[FE_TOOLTIP]); @@ -1192,6 +1238,7 @@ abstract class AbstractBuildForm { * @param array $formElement * * @return string + * @throws CodeException * @throws UserFormException */ private function typeAheadBuildParam(array &$formElement) { @@ -1297,6 +1344,7 @@ abstract class AbstractBuildForm { * @param bool $flagOmitEmpty * * @return string + * @throws CodeException */ private function getAttributeList(array $formElement, array $attributeList, $flagOmitEmpty = true) { $attribute = ''; @@ -1315,6 +1363,7 @@ abstract class AbstractBuildForm { * * @param bool $cssDisable * @return string + * @throws CodeException * @throws UserFormException */ private function getAttributeFeMode($feMode, $cssDisable = true) { @@ -1371,6 +1420,7 @@ abstract class AbstractBuildForm { * @param string $mode FORM_LOAD | FORM_UPDATE | FORM_SAVE* * * @return string + * @throws CodeException * @throws UserFormException */ public function buildCheckbox(array $formElement, $htmlFormElementName, $value, array &$json, $mode = FORM_LOAD) { @@ -1426,6 +1476,7 @@ abstract class AbstractBuildForm { * @param array $itemKey * @param array $itemValue * + * @throws CodeException * @throws UserFormException */ public function getKeyValueListFromSqlEnumSpec(array $formElement, array &$itemKey, array &$itemValue) { @@ -1498,6 +1549,7 @@ abstract class AbstractBuildForm { * @param string $fieldType * * @return array + * @throws CodeException * @throws UserFormException */ private function getItemsForEnumOrSet($column, &$fieldType) { @@ -1543,6 +1595,7 @@ abstract class AbstractBuildForm { * @param array $itemKey * @param array $formElement * + * @throws CodeException * @throws UserFormException */ private function prepareCheckboxCheckedUncheckedValue(array $itemKey, array &$formElement) { @@ -1583,6 +1636,8 @@ abstract class AbstractBuildForm { * @param string $mode FORM_LOAD | FORM_UPDATE | FORM_SAVE * * @return string + * @throws CodeException + * @throws UserFormException */ public function buildCheckboxSingle(array $formElement, $htmlFormElementName, $attribute, $value, array &$json, $mode = FORM_LOAD) { @@ -1616,6 +1671,8 @@ abstract class AbstractBuildForm { * @param array $json * * @return string + * @throws CodeException + * @throws UserFormException */ public function constructCheckboxSingleButton(array $formElement, $htmlFormElementName, $attribute, $value, array &$json) { $html = ''; @@ -1674,6 +1731,8 @@ abstract class AbstractBuildForm { * @param array $json * * @return string + * @throws CodeException + * @throws UserFormException */ public function constructCheckboxSinglePlain(array $formElement, $htmlFormElementName, $attribute, $value, array &$json) { $html = ''; @@ -1722,6 +1781,7 @@ abstract class AbstractBuildForm { * @param array $itemValue * @param array $json * @return string + * @throws CodeException */ public function buildCheckboxMulti(array $formElement, $htmlFormElementName, $attributeBase, $value, array $itemKey, array $itemValue, array &$json) { @@ -1741,7 +1801,8 @@ abstract class AbstractBuildForm { * @param array $formElement * @param string $htmlFormElementName * @param string $htmlHidden - * + * @throws CodeException + * @throws UserFormException */ private function fillStoreAdditionalFormElementsCheckboxHidden(array $formElement, $htmlFormElementName, $htmlHidden) { @@ -1771,6 +1832,7 @@ abstract class AbstractBuildForm { * @param array $json * * @return string + * @throws CodeException */ public function constructCheckboxMultiButton(array $formElement, $htmlFormElementName, $attributeBase, $value, array $itemKey, array $itemValue, array &$json) { $json = array(); @@ -1842,6 +1904,7 @@ abstract class AbstractBuildForm { * @param array $json * * @return string + * @throws CodeException */ public function constructCheckboxMultiPlain(array $formElement, $htmlFormElementName, $attributeBase, $value, array $itemKey, array $itemValue, array &$json) { $json = array(); @@ -1930,6 +1993,8 @@ abstract class AbstractBuildForm { * @param string $mode FORM_LOAD | FORM_UPDATE | FORM_SAVE * * @return string + * @throws CodeException + * @throws UserFormException */ public function buildExtra(array $formElement, $htmlFormElementName, $value, array &$json, $mode = FORM_LOAD) { @@ -1954,6 +2019,8 @@ abstract class AbstractBuildForm { * @param string $mode FORM_LOAD | FORM_UPDATE | FORM_SAVE * * @return string + * @throws CodeException + * @throws UserFormException */ public function buildRadio(array $formElement, $htmlFormElementName, $value, array &$json, $mode = FORM_LOAD) { @@ -2000,6 +2067,8 @@ abstract class AbstractBuildForm { * @param string $mode FORM_LOAD | FORM_UPDATE | FORM_SAVE * * @return string + * @throws CodeException + * @throws UserFormException */ private function constructRadioButton(array $formElement, $htmlFormElementName, $value, array &$json, $mode = FORM_LOAD) { $itemKey = array(); @@ -2081,6 +2150,8 @@ abstract class AbstractBuildForm { * @param string $mode FORM_LOAD | FORM_UPDATE | FORM_SAVE * * @return string + * @throws CodeException + * @throws UserFormException */ private function constructRadioPlain(array $formElement, $htmlFormElementName, $value, array &$json, $mode = FORM_LOAD) { $attributeBase = ''; @@ -2169,6 +2240,8 @@ abstract class AbstractBuildForm { * @param string $mode FORM_LOAD | FORM_UPDATE | FORM_SAVE * * @return mixed + * @throws CodeException + * @throws UserFormException */ public function buildSelect(array $formElement, $htmlFormElementName, $value, array &$json, $mode = FORM_LOAD) { $itemKey = array(); @@ -2242,6 +2315,7 @@ abstract class AbstractBuildForm { * @param array $control Array with <th> column names / format. * * @return string + * @throws UserFormException */ private function subrecordHead($linkNew, $flagDelete, array $firstRow, array &$control) { @@ -2274,6 +2348,9 @@ abstract class AbstractBuildForm { * @param string $mode FORM_LOAD | FORM_UPDATE | FORM_SAVE * * @return string + * @throws CodeException + * @throws UserFormException + * @throws UserReportException */ public function buildSubrecord(array $formElement, $htmlFormElementName, $value, array &$json, $mode = FORM_LOAD) { $rcText = false; @@ -2431,6 +2508,8 @@ abstract class AbstractBuildForm { * @param $toolTip * @param array $currentRow * @return string + * @throws CodeException + * @throws UserFormException */ private function createFormLink(array $formElement, $targetRecordId, array $record, $symbol, $toolTip, $currentRow = array()) { @@ -2498,6 +2577,9 @@ abstract class AbstractBuildForm { * @param string $formName * * @return string tableName for $formName + * @throws CodeException + * @throws DbException + * @throws UserFormException */ private function getFormTable($formName) { $row = $this->dbArray[$this->dbIndexQfq]->sql("SELECT " . F_TABLE_NAME . " FROM Form AS f WHERE f.name = ?", ROW_EXPECT_0_1, [$formName]); @@ -2528,6 +2610,7 @@ abstract class AbstractBuildForm { * @param array $titleRaw * * @return array + * @throws UserFormException */ private function getSubrecordColumnControl(array $titleRaw) { $control = array(); @@ -2605,6 +2688,9 @@ abstract class AbstractBuildForm { * @param string $columnValue * * @return string + * @throws CodeException + * @throws UserFormException + * @throws UserReportException */ private function renderCell(array $control, $columnName, $columnValue) { $link = null; @@ -2667,6 +2753,8 @@ abstract class AbstractBuildForm { * parameters. * * @return string String: "API_DIR/delete.php?sip=...." + * @throws CodeException + * @throws UserFormException */ public function createDeleteUrl($formName, $recordId, $mode = RETURN_URL) { @@ -2694,7 +2782,10 @@ abstract class AbstractBuildForm { * @param string $mode FORM_LOAD | FORM_UPDATE | FORM_SAVE * * @return string + * @throws CodeException + * @throws DbException * @throws UserFormException + * @throws UserReportException */ public function buildFile(array $formElement, $htmlFormElementName, $value, array &$json, $mode = FORM_LOAD) { $attribute = ''; @@ -2811,6 +2902,7 @@ abstract class AbstractBuildForm { * @param string $mode FORM_LOAD | FORM_UPDATE | FORM_SAVE * * @return string + * @throws CodeException * @throws UserFormException */ public function buildAnnotate(array $formElement, $htmlFormElementName, $value, array &$json, $mode = FORM_LOAD) { @@ -2863,6 +2955,7 @@ abstract class AbstractBuildForm { * @param string $mode FORM_LOAD | FORM_UPDATE | FORM_SAVE * * @return string + * @throws CodeException * @throws UserFormException */ public function buildImageCut(array $formElement, $htmlFormElementName, $value, array &$json, $mode = FORM_LOAD) { @@ -2930,6 +3023,8 @@ abstract class AbstractBuildForm { /** * @param string $pathFileName * @return string SIP encoded URL + * @throws CodeException + * @throws UserFormException */ private function fileToSipUrl($pathFileName) { $param[DOWNLOAD_MODE] = DOWNLOAD_MODE_FILE; @@ -2953,6 +3048,7 @@ abstract class AbstractBuildForm { * @param string $mode FORM_LOAD | FORM_UPDATE | FORM_SAVE * * @return string + * @throws CodeException * @throws UserFormException */ public function buildDateTime(array $formElement, $htmlFormElementName, $value, array &$json, $mode = FORM_LOAD) { @@ -3065,6 +3161,8 @@ abstract class AbstractBuildForm { * @param string $mode FORM_LOAD | FORM_UPDATE | FORM_SAVE * * @return string + * @throws CodeException + * @throws UserFormException */ public function buildDateJQW(array $formElement, $htmlFormElementName, $value, array &$json, $mode = FORM_LOAD) { $arrMinMax = null; @@ -3147,6 +3245,8 @@ abstract class AbstractBuildForm { * @param string $mode * * @return string + * @throws CodeException + * @throws UserFormException */ public function buildEditor(array $formElement, $htmlFormElementName, $value, array &$json, $mode = FORM_LOAD) { $attribute = ''; @@ -3297,6 +3397,7 @@ abstract class AbstractBuildForm { * * @param string $mode FORM_LOAD | FORM_UPDATE | FORM_SAVE * @return mixed + * @throws CodeException */ public function buildNote(array $formElement, $htmlFormElementName, $value, array &$json, $mode = FORM_LOAD) { @@ -3331,6 +3432,10 @@ abstract class AbstractBuildForm { * @param string $mode FORM_LOAD | FORM_UPDATE | FORM_SAVE * * @return mixed + * @throws CodeException + * @throws DbException + * @throws UserFormException + * @throws UserReportException */ public function buildFieldset(array $formElement, $htmlFormElementName, $value, array &$json, $mode = FORM_LOAD) { $attribute = ''; @@ -3397,6 +3502,10 @@ abstract class AbstractBuildForm { * @param string $mode FORM_LOAD | FORM_UPDATE | FORM_SAVE * * @return mixed + * @throws CodeException + * @throws DbException + * @throws UserFormException + * @throws UserReportException */ public function buildTemplateGroup(array $formElement, $htmlFormElementName, $value, array &$json, $mode = FORM_LOAD) { $attribute = ''; @@ -3509,6 +3618,10 @@ EOT; * @param array $json * * @return string + * @throws CodeException + * @throws DbException + * @throws UserFormException + * @throws UserReportException */ private function templateGroupCollectFilledElements($max, $htmlDelete, array &$json) { @@ -3591,6 +3704,10 @@ EOT; * Additional the maximum count of all select rows will be determined and returned. * * @return int max number of records in FormElement[FE_VALUE] over all FormElements. + * @throws CodeException + * @throws DbException + * @throws UserFormException + * @throws UserReportException */ private function templateGroupDoValue() { diff --git a/extension/qfq/qfq/BuildFormBootstrap.php b/extension/qfq/qfq/BuildFormBootstrap.php index 0a686799147e0d6012c59bf732b373e44f83aa12..280f8a7814baf420ad39cb7363a2aebfd23f765c 100644 --- a/extension/qfq/qfq/BuildFormBootstrap.php +++ b/extension/qfq/qfq/BuildFormBootstrap.php @@ -113,6 +113,7 @@ class BuildFormBootstrap extends AbstractBuildForm { * @throws CodeException * @throws DbException * @throws UserFormException + * @throws UserReportException */ public function head($mode = FORM_LOAD) { $html = ''; @@ -383,7 +384,9 @@ class BuildFormBootstrap extends AbstractBuildForm { * @param array $json * @return string * @throws CodeException + * @throws DbException * @throws UserFormException + * @throws UserReportException */ private function buildPillNavigation($mode = FORM_LOAD, array $pillArray, array &$json) { $pillButton = ''; @@ -513,6 +516,7 @@ class BuildFormBootstrap extends AbstractBuildForm { * @return string * @throws CodeException * @throws DbException + * @throws UserFormException */ public function getFormTag() { @@ -595,6 +599,7 @@ class BuildFormBootstrap extends AbstractBuildForm { fileDeleteUrl: '$apiDir/file.php?$actionDelete' }); + var qfqRecordList = new QfqNS.QfqRecordList('$apiDeletePhp'); }) </script> @@ -614,6 +619,7 @@ EOF; * @throws CodeException * @throws DbException * @throws UserFormException + * @throws UserReportException */ public function buildPill(array $formElement, $htmlFormElementName, $value, array &$json) { $html = ''; @@ -809,6 +815,7 @@ EOF; * @throws CodeException * @throws DbException * @throws UserFormException + * @throws UserReportException */ public function process($mode, $htmlElementNameIdZero = false, $latestFeSpecNative = array()) { diff --git a/extension/qfq/qfq/Constants.php b/extension/qfq/qfq/Constants.php index b7d84ed492a81619d031b1755a838812125c4a3a..da2e77b05b9cba473246bab267bfb86f87389338 100644 --- a/extension/qfq/qfq/Constants.php +++ b/extension/qfq/qfq/Constants.php @@ -33,6 +33,7 @@ const FORM_LOAD = 'form_load'; const FORM_SAVE = 'form_save'; const FORM_UPDATE = 'form_update'; const FORM_DELETE = 'form_delete'; +const FORM_DRAG_AND_DROP = 'form_drag_and_drop'; const FORM_PERMISSION_SIP = 'sip'; const FORM_PERMISSION_LOGGED_IN = 'logged_id'; const FORM_PERMISSION_LOGGED_OUT = 'logged_out'; @@ -143,10 +144,11 @@ const ERROR_DEBUG = 1031; const ERROR_UNKNOWN_MODE = 1032; const ERROR_NOT_IMPLEMENTED = 1033; const ERROR_RESERVED_KEY_NAME = 1034; - +const ERROR_MISSING_FORM = 1035; const ERROR_UNKNOWN_FORWARD_MODE = 1036; - -const ERROR_MISSING_HIDDEN_FIELD_IN_SIP = 1038; +const ERROR_MISSING_MESSAGE_FAIL = 1037; +const ERROR_MISSING_EXPECT_RECORDS = 1038; +const ERROR_MISSING_HIDDEN_FIELD_IN_SIP = 1039; const ERROR_UNKNOWN_CHECKTYPE = 1042; const ERROR_PATTERN_VIOLATION = 1043; @@ -169,7 +171,7 @@ const ERROR_MISSING_OPEN_DELIMITER = 1060; const ERROR_MISSING_CLOSE_DELIMITER = 1061; const ERROR_EXPECTED_ARRAY = 1062; const ERROR_REPORT_FAILED_ACTION = 1063; -const ERROR_MISSING_MESSAGE_FAIL = 1064; + const ERROR_MISSING_TABLE_NAME = 1065; const ERROR_MISSING_TABLE = 1066; const ERROR_RECORD_NOT_FOUND = 1067; @@ -247,6 +249,7 @@ const ERROR_DOWNLOAD_NO_FILES = 1701; const ERROR_DOWNLOAD_NOTHING_TO_DO = 1702; const ERROR_DOWNLOAD_UNEXPECTED_MIME_TYPE = 1703; const ERROR_DOWNLOAD_UNEXPECTED_NUMBER_OF_SOURCES = 1704; +const ERROR_DOWNLOAD_FILE_NOT_READABLE = 1705; // KeyValueParser const ERROR_KVP_VALUE_HAS_NO_KEY = 1900; @@ -546,6 +549,7 @@ const SYSTEM_DOWNLOAD_POPUP = 'hasDownloadPopup'; // Marker which is set to 'tru const DOWNLOAD_POPUP_REQUEST = 'true'; const DOWNLOAD_POPUP_REPLACE_TEXT = '#downloadPopupReplaceText#'; const DOWNLOAD_POPUP_REPLACE_TITLE = '#downloadPopupReplaceTitle#'; +const SYSTEM_DRAG_AND_DROP_JS = 'hasDragAndDropJS'; const SYSTEM_PARAMETER_LANGUAGE_FIELD_NAME = 'parameterLanguageFieldName'; const CSS_REQUIRED = 'required-field'; @@ -647,6 +651,7 @@ const MODE_LDAP_MULTI = 'ldapMulti'; // api/save.php, api/delete.php, api/load.php const API_DELETE_PHP = 'delete.php'; const API_DOWNLOAD_PHP = 'download.php'; +const API_DRAG_AND_DROP_PHP = 'dragAndDrop.php'; const API_STATUS = 'status'; const API_MESSAGE = 'message'; @@ -851,6 +856,11 @@ const F_NEW_BUTTON_GLYPH_ICON = SYSTEM_NEW_BUTTON_GLYPH_ICON; const F_ENTER_AS_SUBMIT = SYSTEM_ENTER_AS_SUBMIT; +const F_DRAG_AND_DROP_ORDER_SQL = 'dragAndDropOrderSql'; +const F_ORDER_INTERVAL = 'orderInterval'; +const F_ORDER_COLUMN = 'orderColumn'; +const F_ORDER_COLUMN_NAME = 'ord'; + // FORM_ELEMENT_STATI const FE_MODE_SHOW = 'show'; const FE_MODE_READONLY = 'readonly'; @@ -994,6 +1004,7 @@ const FE_SUBRECORD_TABLE_CLASS = 'subrecordTableClass'; const FE_FILE_BUTTON_TEXT = 'fileButtonText'; const FE_FILE_BUTTON_TEXT_DEFAULT = 'Choose File'; const FE_INPUT_TYPE = 'inputType'; +const FE_STEP = 'step'; const FE_IMAGE_CUT_RESIZE_WIDTH = 'resizeWidth'; const FE_IMAGE_CUT_KEEP_ORIGINAL = 'keepOriginal'; @@ -1070,6 +1081,8 @@ const HTML_ATTR_ID = 'id'; const HTML_ATTR_NAME = 'name'; const HTML_ATTR_CLASS = 'class'; +const HTML_INPUT_TYPE_NUMBER = 'number'; + const SHEBANG_REPORT = '#!report'; // SUPPORT @@ -1420,3 +1433,15 @@ const EXCEPTION_STACKTRACE = 'Stacktrace'; const EXCEPTION_TABLE_CLASS = 'table table-hover qfq-table-80'; +// Drag And Drop +const DND_DRAG_ID = 'dragId'; +const DND_DRAG_POSITION = 'dragPosition'; +const DND_SET_TO = 'setTo'; +const DND_SET_TO_BEFORE = 'before'; +const DND_SET_TO_AFTER = 'after'; +const DND_HOVER_ID = 'hoverId'; +const DND_HOVER_POSITION = 'hoverPosition'; +const DND_COLUMN_ID = 'id'; +const DND_COLUMN_ORD = 'ord'; +const DND_COLUMN_ORD_NEW = 'ordNew'; +const DND_DATA_DND_API = 'data-dnd-api'; \ No newline at end of file diff --git a/extension/qfq/qfq/Delete.php b/extension/qfq/qfq/Delete.php index afd4ec52101ba9a08f6d96123fbc53dc66a1020a..70abc44f504aacfb002c4c220072db1914173807 100644 --- a/extension/qfq/qfq/Delete.php +++ b/extension/qfq/qfq/Delete.php @@ -32,6 +32,7 @@ class Delete { * @param bool $dbIndexData * @param bool $phpUnit * @throws CodeException + * @throws DbException * @throws UserFormException */ public function __construct($dbIndexData = false, $phpUnit = false) { diff --git a/extension/qfq/qfq/Evaluate.php b/extension/qfq/qfq/Evaluate.php index b29a27101aadbd1475977026aea2108a6b3cd00c..c84538ac9283f2ad88ad4ef3c9ac6888c4a7d0f5 100644 --- a/extension/qfq/qfq/Evaluate.php +++ b/extension/qfq/qfq/Evaluate.php @@ -197,6 +197,59 @@ class Evaluate { return $result; } + /** + * @param $arrToken + * @param $dbIndex + * @param $foundInStore + * @return string + * @throws CodeException + * @throws UserFormException + * @throws UserReportException + */ + private function inlineLink($arrToken, $dbIndex, &$foundInStore){ + + $token = OnString::trimQuote(trim(implode(' ', $arrToken))); + + if ($this->link === null) { + $this->link = new Link($this->store->getSipInstance(), $dbIndex); + } + + $foundInStore = TOKEN_FOUND_AS_COLUMN; + + return $this->link->renderLink($token); + } + + /** + * @param $arrToken + * @param $dbIndex + * @param $foundInStore + * @return string + * @throws CodeException + * @throws UserFormException + * @throws UserReportException + */ + private function inlineDataDndApi($arrToken, $dbIndex, &$foundInStore){ + + $token = OnString::trimQuote(trim(implode(' ', $arrToken))); + if(empty($token)){ + throw new UserReportException('Missing form name for "data-dnd-api"', ERROR_MISSING_FORM); + } + + if ($this->link === null) { + $this->link = new Link($this->store->getSipInstance(), $dbIndex); + } + + $foundInStore = TOKEN_FOUND_AS_COLUMN; + + $s = $this->link->renderLink('U:' . $token . '|s|r:8'); + + // Flag to add DND JS code later on. + $this->store->setVar(SYSTEM_DRAG_AND_DROP_JS, 'true', STORE_SYSTEM); + + // data-dnd-api="typo3conf/ext/qfq/qfq/api/dragAndDrop.php?s={{'U:form=<form name>[¶mX=<any value>]|s|r:8' AS _link}}" + return DND_DATA_DND_API . '="' . API_DIR . '/' . API_DRAG_AND_DROP_PHP . '?s=' . $s . '"'; + } + /** * Tries to substitute $token. * Token might be: @@ -244,7 +297,7 @@ class Evaluate { $sqlMode = ROW_REGULAR; } - // Extract token: check if this is a SQL Statement + // Extract token: check if this is a 'variable', 'SQL Statement', 'link', 'data-dnd-api' $arrToken = explode(' ', $token); // Variable Type 'SQL Statement' @@ -254,23 +307,22 @@ class Evaluate { return $this->dbArray[$dbIndex]->sql($token, $sqlMode); } - // Variable Type '... AS LINK' + // Variable Type '... AS _link', '... as data-dnd-api' $countToken = count($arrToken); if ($countToken > 2 && strcasecmp($arrToken[$countToken - 2], 'as') == 0) { $type = OnString::stripFirstCharIf('_', $arrToken[$countToken - 1]); - if (strcasecmp($type, 'link') == 0) { - - $str = OnString::trimQuote(substr($token, 0, strlen($token) - 8)); // strlen('_as_link')=8 + array_pop($arrToken); // remove 'link' | 'data-dnd-api' + array_pop($arrToken); // remove 'as' - if ($this->link === null) { - $this->link = new Link($this->store->getSipInstance(), $dbIndex); - } + if (strcasecmp($type, COLUMN_LINK ) == 0) { + return($this->inlineLink($arrToken, $dbIndex,$foundInStore)); + } - $foundInStore = TOKEN_FOUND_AS_COLUMN; + if($type == DND_DATA_DND_API){ + return($this->inlineDataDndApi($arrToken, $dbIndex,$foundInStore)); - return $this->link->renderLink($str); } } diff --git a/extension/qfq/qfq/QuickFormQuery.php b/extension/qfq/qfq/QuickFormQuery.php index 0f2cb03d5eb102cf908a9e581865612c2337c1bb..161c53b0edd91b5641e40a0c9fc87d9340443650 100644 --- a/extension/qfq/qfq/QuickFormQuery.php +++ b/extension/qfq/qfq/QuickFormQuery.php @@ -44,6 +44,7 @@ require_once(__DIR__ . '/BodytextParser.php'); require_once(__DIR__ . '/Delete.php'); require_once(__DIR__ . '/form/FormAction.php'); require_once(__DIR__ . '/form/Dirty.php'); +require_once(__DIR__ . '/form/DragAndDrop.php'); /* * Form will be called @@ -219,6 +220,7 @@ class QuickFormQuery { * @return string * @throws CodeException * @throws DbException + * @throws DownloadException * @throws UserFormException * @throws UserReportException */ @@ -238,6 +240,11 @@ class QuickFormQuery { $html .= $this->getModalCode(); } + // Only needed if there are 'drag and drop' elements. + if ($this->store->getVar(SYSTEM_DRAG_AND_DROP_JS, STORE_SYSTEM) == 'true') { + $html .= $this->getDragAndDropCode(); + } + $class = $this->store->getVar(SYSTEM_CSS_CLASS_QFQ_CONTAINER, STORE_SYSTEM); if ($class) { $html = Support::wrapTag("<div class='$class'>", $html); @@ -265,6 +272,7 @@ class QuickFormQuery { } } + /** * Process form. * $mode= @@ -278,6 +286,7 @@ class QuickFormQuery { * @return array|string * @throws CodeException * @throws DbException + * @throws DownloadException * @throws UserFormException * @throws UserReportException */ @@ -286,7 +295,7 @@ class QuickFormQuery { $foundInStore = ''; // Fill STORE_FORM - if ($formMode === FORM_UPDATE || $formMode === FORM_SAVE) { + if ($formMode === FORM_UPDATE || $formMode === FORM_SAVE || $formMode === FORM_DRAG_AND_DROP) { $fillStoreForm = new FillStoreForm(); $fillStoreForm->process($formMode); } @@ -295,9 +304,15 @@ class QuickFormQuery { $this->setParameterLanguageFieldName(); $formName = $this->loadFormSpecification($formMode, $recordId, $foundInStore); - if ($formName === false && $formMode !== FORM_DELETE) { - // No form found: do nothing - return ''; + if ($formName === false) { + switch ($formName) { + case FORM_DELETE: + break; + case FORM_DRAG_AND_DROP: + throw new CodeException('Missing form in SIP', ERROR_MISSING_FORM); + default: + return '';// No form found: do nothing + } } if ($formName !== false) { @@ -310,6 +325,7 @@ class QuickFormQuery { if ($table === false) { throw new UserFormException("No 'form' and no 'table' definition found.", ERROR_MISSING_VALUE); } + $sipFound = true; $this->formSpec[F_NAME] = ''; $this->formSpec[F_TABLE_NAME] = $table; @@ -476,6 +492,14 @@ class QuickFormQuery { } break; + case FORM_DRAG_AND_DROP: + $formAction->elements($recordId, $this->feSpecAction, FE_TYPE_BEFORE_LOAD); + + $dragAndDrop = new DragAndDrop($this->formSpec); + $dragAndDrop->process($this->formSpec); + + $formAction->elements($recordId, $this->feSpecAction, FE_TYPE_AFTER_LOAD); + break; default: throw new CodeException("This statement should never be reached", ERROR_CODE_SHOULD_NOT_HAPPEN); @@ -764,7 +788,7 @@ class QuickFormQuery { case FORM_SAVE: case FORM_UPDATE: - $feSpecNative = $this->getNativeFormElements(SQL_FORM_ELEMENT_NATIVE_TG_COUNT, [$this->formSpec[F_ID]], $this->formSpec); + $feSpecNative = $this->getNativeFormElements(SQL_FORM_ELEMENT_NATIVE_TG_COUNT, [$this->formSpec[F_ID]], $this->formSpec); break; case FORM_DELETE: @@ -902,6 +926,7 @@ class QuickFormQuery { case FORM_SAVE: case FORM_UPDATE: case FORM_DELETE: + case FORM_DRAG_AND_DROP: $store = STORE_SIP; break; default: @@ -1218,7 +1243,6 @@ class QuickFormQuery { $html = $report->process($this->t3data['bodytext']); return $html; - } /** @@ -1227,6 +1251,7 @@ class QuickFormQuery { * @return string * @throws CodeException * @throws DbException + * @throws DownloadException * @throws UserFormException * @throws UserReportException */ @@ -1243,6 +1268,7 @@ class QuickFormQuery { * @return array * @throws CodeException * @throws DbException + * @throws DownloadException * @throws UserFormException * @throws UserReportException */ @@ -1253,12 +1279,30 @@ class QuickFormQuery { return $json; } + /** + * Update FormElements and form values. Receives the current form values via POST. + * + * @return array + * @throws CodeException + * @throws DbException + * @throws DownloadException + * @throws UserFormException + * @throws UserReportException + */ + public function dragAndDrop() { + + $json = $this->doForm(FORM_DRAG_AND_DROP); + + return $json; + } + /** * Delete a record (tablename and recordid are given) or process a 'delete form' * * @return array * @throws CodeException * @throws DbException + * @throws DownloadException * @throws UserFormException * @throws UserReportException */ @@ -1343,5 +1387,32 @@ EOF; return $code; } + /** + * @return string + */ + private function getDragAndDropCode() { + + $code = <<<EOF + <script type="text/javascript"> + $(function () { + + + $('.qfq-dnd-sort').each(function() { + var dndObject = new QfqNS.DragAndDrop($(this)); + dndObject.makeSortable(); + }); + + var zoni = new QfqNS.DragAndDrop($('.qfq-dnd')); + zoni.makeBasketCase(); + + + }); + </script> +EOF; + + return $code; + + } + } \ No newline at end of file diff --git a/extension/qfq/qfq/form/DragAndDrop.php b/extension/qfq/qfq/form/DragAndDrop.php new file mode 100644 index 0000000000000000000000000000000000000000..9be716f0fe64059ca7972cbe5a07c17bcf0c2f9a --- /dev/null +++ b/extension/qfq/qfq/form/DragAndDrop.php @@ -0,0 +1,162 @@ +<?php +/** + * Created by PhpStorm. + * User: crose + * Date: 3/13/17 + * Time: 9:29 PM + */ + +namespace qfq; + +//use TYPO3\CMS\Core\FormProtection\Exception; + +//require_once(__DIR__ . '/../store/Sip.php'); +//require_once(__DIR__ . '/../store/Session.php'); +require_once(__DIR__ . '/../store/Store.php'); +require_once(__DIR__ . '/../Constants.php'); +//require_once(__DIR__ . '/../helper/Ldap.php'); +require_once(__DIR__ . '/../database/Database.php'); +require_once(__DIR__ . '/../Evaluate.php'); + + +/** + * Class DragAndDrop + * @package qfq + */ +class DragAndDrop { + + /** + * @var Database instantiated class + */ + private $db = null; + + /** + * @var Store + */ + private $store = null; + + /** + * @var Evaluate instantiated class + */ + protected $evaluate = null; // copy of the loaded form + + /** + * @var array + */ + private $formSpec = null; + + /** + * @param array $formSpec F_TABLE_NAME, F_DRAG_AND_DROP_ORDER_SQL, F_DRAG_AND_DROP_INTERVAL + * @param bool|false $phpUnit + * + * @throws CodeException + * @throws DbException + * @throws UserFormException + */ + public function __construct(array $formSpec = array(), $phpUnit = false) { + + $this->formSpec = $formSpec; + + $dbIndex = DB_INDEX_DEFAULT; //TODO hier muss noch die aktuelle DB ermittelt werden (kann im Form angegeben sein) - Gerade im Formular FORM Editor genau testen! + $this->db = new Database($dbIndex); + + $this->store = Store::getInstance('', $phpUnit); +// $this->evaluate = new Evaluate($this->store, $this->db); + } + + /** + * + * @return array|int + * @throws CodeException + * @throws DbException + * @throws UserFormException + * @throws UserReportException + */ + public function process() { + + $dragId = $this->store->getVar(DND_DRAG_ID, STORE_CLIENT, SANITIZE_ALLOW_ALNUMX); + $setTo = $this->store->getVar(DND_SET_TO, STORE_CLIENT, SANITIZE_ALLOW_ALNUMX); + $hoverId = $this->store->getVar(DND_HOVER_ID, STORE_CLIENT, SANITIZE_ALLOW_ALNUMX); + + $orderInterval = empty($this->formSpec[F_ORDER_INTERVAL]) ? 1 : $this->formSpec[F_ORDER_INTERVAL]; + $orderColumn = empty($this->formSpec[F_ORDER_COLUMN]) ? F_ORDER_COLUMN_NAME : $this->formSpec[F_ORDER_COLUMN]; + + if (!is_array($this->formSpec[F_DRAG_AND_DROP_ORDER_SQL])) { + return []; + } + + $this->reorder($this->formSpec[F_DRAG_AND_DROP_ORDER_SQL], $dragId, $setTo, $hoverId, $orderColumn, $orderInterval, $this->formSpec[F_TABLE_NAME]); + + return []; + } + + /** + * @param array $rows + * @param $dragId + * @param $setTo + * @param $hoverId + * @param $orderColumn + * @param $orderInterval + * @param $tableName + * @throws CodeException + * @throws DbException + * @throws UserFormException + */ + private function reorder(array $rows, $dragId, $setTo, $hoverId, $orderColumn, $orderInterval, $tableName) { + $ord = $orderInterval; + $ordDragOld = -1; + + // Reorder. Get index for 'drag' and 'hover' + foreach ($rows as $key => $row) { + + // the dragged element: skip old position. + if ($row[DND_COLUMN_ID] == $dragId) { + $ordDragOld = $row[DND_COLUMN_ORD]; + continue; + } + + // the dragged element: new position. + if ($row[DND_COLUMN_ID] == $hoverId) { + + switch ($setTo) { + case DND_SET_TO_BEFORE: + $this->setNewOrder($tableName, $orderColumn, $dragId, $ordDragOld, $ord); + $ord += $orderInterval; + $this->setNewOrder($tableName, $orderColumn, $row[DND_COLUMN_ID], $row[DND_COLUMN_ORD], $ord); + break; + + case DND_SET_TO_AFTER: + $this->setNewOrder($tableName, $orderColumn, $row[DND_COLUMN_ID], $row[DND_COLUMN_ORD], $ord); + $ord += $orderInterval; + $this->setNewOrder($tableName, $orderColumn, $dragId, $ordDragOld, $ord); + break; + + default: + throw new CodeException('Unkown setTo string', $setTo, ERROR_UNKNOWN_TOKEN); + } + } else { + $this->setNewOrder($tableName, $orderColumn, $row[DND_COLUMN_ID], $row[DND_COLUMN_ORD], $ord); + } + $ord += $orderInterval; + } + } + + /** + * @param $tableName + * @param $orderColumn + * @param $id + * @param $ordOld + * @param $ordNew + * @throws CodeException + * @throws DbException + * @throws UserFormException + */ + private function setNewOrder($tableName, $orderColumn, $id, $ordOld, $ordNew) { + + if ($ordNew == $ordOld) { + return; + } + + $this->db->sql("UPDATE $tableName SET $orderColumn=? WHERE id=?", ROW_REGULAR, [$ordNew, $id]); + } +} \ No newline at end of file diff --git a/extension/qfq/qfq/form/FormAction.php b/extension/qfq/qfq/form/FormAction.php index 43edcf6bfb3b36bf92e7cddd736b6c0784d91c0b..c816f8dc3b8eaaa446708013c4f74dfd54dcfd10 100644 --- a/extension/qfq/qfq/form/FormAction.php +++ b/extension/qfq/qfq/form/FormAction.php @@ -28,12 +28,15 @@ class FormAction { * @var Evaluate instantiated class */ protected $evaluate = null; // copy of the loaded form + private $formSpec = array(); private $primaryTableName = ''; + /** * @var Database */ private $db = null; + /** * @var Store */ @@ -71,6 +74,7 @@ class FormAction { * ACTION_ELEMENT_DELETED: if a record has been deleted (only in recursive calls, not the initial one) * @throws CodeException * @throws DbException + * @throws DownloadException * @throws UserFormException * @throws UserReportException */ @@ -245,6 +249,7 @@ class FormAction { * @param array $feSpecAction * @throws CodeException * @throws DbException + * @throws DownloadException * @throws UserFormException * @throws UserReportException */ @@ -294,10 +299,13 @@ class FormAction { return; } + if($fe[FE_EXPECT_RECORDS]===''){ + throw new UserFormException("Missing parameter '" . FE_EXPECT_RECORDS . "'", ERROR_MISSING_EXPECT_RECORDS); + } $expect = $this->evaluate->parse($fe[FE_EXPECT_RECORDS]); if ($fe[FE_MESSAGE_FAIL] === '') { - throw new UserFormException("Missing error message. Column: " . FE_MESSAGE_FAIL, ERROR_MISSING_MESSAGE_FAIL); + throw new UserFormException("Missing parameter '" . FE_MESSAGE_FAIL . "'", ERROR_MISSING_MESSAGE_FAIL); } // Do the check diff --git a/extension/qfq/qfq/helper/HelperFormElement.php b/extension/qfq/qfq/helper/HelperFormElement.php index d10b71ba01a435bde14e0fa771439548879f0ade..97547f9fb97c20cdbde040e5c4ae916681b6f7e6 100644 --- a/extension/qfq/qfq/helper/HelperFormElement.php +++ b/extension/qfq/qfq/helper/HelperFormElement.php @@ -335,11 +335,9 @@ class HelperFormElement { // INFO: $showinline =- TRUE ('input' elemente) if (isset($formElement[FE_INPUT_EXTRA_BUTTON_INFO]) && $showInline) { $extraButton .= <<<EOF - <div class="input-group-btn"> - <button class="btn btn-info" onclick="$('#$id-extra-info').slideToggle('swing')"> - $infoSymbolInside - </button> - </div> + <button class="btn btn-info" onclick="$('#$id-extra-info').slideToggle('swing')"> + $infoSymbolInside + </button> EOF; $value = $formElement[FE_INPUT_EXTRA_BUTTON_INFO]; @@ -379,12 +377,10 @@ EOF; $formElement[FE_MODE] = FE_MODE_READONLY; $extraButton .= <<<EOF - <div class="input-group-btn"> - <button class="btn btn-info" - onclick="$('#$id').prop('readonly',!$('#$id').prop('readonly'))"> - <span class="glyphicon glyphicon-lock" aria-hidden="true"></span> - </button> - </div> + <button class="btn btn-info" + onclick="$('#$id').prop('readonly',!$('#$id').prop('readonly'))"> + <span class="glyphicon glyphicon-lock" aria-hidden="true"></span> + </button> EOF; } @@ -394,16 +390,14 @@ EOF; $formElement[FE_TYPE] = 'password'; $extraButton .= <<<EOF - <div class="input-group-btn"> - <button class="btn btn-info" - onclick="$('#$id').attr('type',$('#$id').attr('type')==='password' ? 'text': 'password')"> - <span class="glyphicon glyphicon-eye-open" aria-hidden="true"></span> - </button> - </div> + <button class="btn btn-info" + onclick="$('#$id').attr('type',$('#$id').attr('type')==='password' ? 'text': 'password')"> + <span class="glyphicon glyphicon-eye-open" aria-hidden="true"></span> + </button> EOF; } - $formElement[FE_TMP_EXTRA_BUTTON_HTML] = $extraButton; + $formElement[FE_TMP_EXTRA_BUTTON_HTML] = Support::wrapTag('<div class="input-group-btn">', $extraButton, true); Support::setIfNotSet($formElement, FE_INPUT_EXTRA_BUTTON_INFO); return $formElement; diff --git a/extension/qfq/qfq/helper/Support.php b/extension/qfq/qfq/helper/Support.php index ca924704faf44e07e5f6b7e03ef16e3f11b66367..bfe6c7665f82aa228e251c59d8a8bf600877ad0d 100644 --- a/extension/qfq/qfq/helper/Support.php +++ b/extension/qfq/qfq/helper/Support.php @@ -867,7 +867,7 @@ class Support { case 'mediumint': case 'int': case 'bigint': - $inputType = 'number'; + $inputType = HTML_INPUT_TYPE_NUMBER; $arr = $control[$token]; if ($sign == 'signed') { $min = $arr[0]; @@ -887,7 +887,7 @@ class Support { break; case 'bit': - $inputType = 'number'; + $inputType = HTML_INPUT_TYPE_NUMBER; $checkType = SANITIZE_ALLOW_DIGIT; break; diff --git a/extension/qfq/qfq/report/Download.php b/extension/qfq/qfq/report/Download.php index 6d63a592887bb689e75ce6e15c84572c97970d67..8a2b0ac9f6ed52ec36be5b0135c7a12b347c8594 100644 --- a/extension/qfq/qfq/report/Download.php +++ b/extension/qfq/qfq/report/Download.php @@ -94,6 +94,16 @@ class Download { */ private function concatPdfFiles(array $files) { + // Remove empty entries. Might happen if there was no upload + $files = OnArray::removeEmptyElementsFromArray($files); + + // Check that all files exist and readable + foreach ($files AS $filename) { + if(!is_readable($filename)){ + throw new downloadException("Error reading file $filename. Not found or no permission", ERROR_DOWNLOAD_FILE_NOT_READABLE); + } + } + switch (count($files)) { case 0: return ''; diff --git a/extension/qfq/qfq/report/Link.php b/extension/qfq/qfq/report/Link.php index 6e3a6990ca9d7585d42d489c230470db91ed4008..578df997a038386fe3652a5899bf6f9685ad17bd 100644 --- a/extension/qfq/qfq/report/Link.php +++ b/extension/qfq/qfq/report/Link.php @@ -262,6 +262,7 @@ class Link { * 4: <a href=url>Text</a> * 5: text * 6: url + * 8: SIP only - 's=badcaffee1234' * * r=render mode, u=url, t:text and/or image. * @@ -308,10 +309,15 @@ class Link { $this->renderControl[7][1][0] = 6; $this->renderControl[7][1][1] = 6; + $this->renderControl[8][0][0] = 0; + $this->renderControl[8][0][1] = 0; + $this->renderControl[8][1][0] = 8; + $this->renderControl[8][1][1] = 8; + } /** - * In render mode 3,4,5 there is no '<a href ...>'. Nevertheless, tooltip and BS Button should be displaye. + * In render mode 3,4,5 there is no '<a href ...>'. Nevertheless, tooltip and BS Button should be displayed. * Do this by applying a '<span>' attribute around the text. * * @param array $vars @@ -421,10 +427,10 @@ class Link { case '22': case '23': case '24': - //TODO: Alter Code, umstellen auf JS Client von RO. Vorlage koennte 'Delete' in Subrecord sein. - $link = "<a href=\"javascript: void(0);\" onClick=\"var del = new FR.Delete({recordId:'',sip:'',forward:'" . - $vars[NAME_PAGE] . "'});\" " . $vars[NAME_LINK_CLASS] . ">" . $vars[NAME_TEXT] . "</a>"; - break; + //TODO: Alter Code, umstellen auf JS Client von RO. Vorlage koennte 'Delete' in Subrecord sein. + $link = "<a href=\"javascript: void(0);\" onClick=\"var del = new FR.Delete({recordId:'',sip:'',forward:'" . + $vars[NAME_PAGE] . "'});\" " . $vars[NAME_LINK_CLASS] . ">" . $vars[NAME_TEXT] . "</a>"; + break; // 5: plain text, no <span> around case '5': @@ -442,7 +448,9 @@ class Link { case '26': throw new UserReportException ("Mode not implemented. internal render mode=$mode", ERROR_UNKNOWN_MODE); break; - + case '8': + $link = substr($vars[FINAL_HREF],12); // strip 'index.php?s=' + break; default: throw new UserReportException ("Mode not implemented. internal render mode=$mode", ERROR_UNKNOWN_MODE); @@ -1321,7 +1329,7 @@ EOF; // if ($vars[NAME_BOOTSTRAP_BUTTON] == '0') { // $vars[NAME_EXTRA_CONTENT_WRAP] = '<button type="button" ' . $attributes . $onClick . '>'; // } else { - $vars[NAME_EXTRA_CONTENT_WRAP] = '<span ' . $attributes . $onClick . '>'; + $vars[NAME_EXTRA_CONTENT_WRAP] = '<span ' . $attributes . $onClick . '>'; $vars[NAME_BOOTSTRAP_BUTTON] = '0'; // } diff --git a/extension/qfq/qfq/report/Report.php b/extension/qfq/qfq/report/Report.php index 6c44a55ce5723e5c50cd22bcb170773563f3bad0..1afd6c4fd33f2b5b544f1e7d6e9ad9c83e760b59 100644 --- a/extension/qfq/qfq/report/Report.php +++ b/extension/qfq/qfq/report/Report.php @@ -668,6 +668,7 @@ class Report { * @return string rendered column * @throws CodeException * @throws DbException + * @throws DownloadException * @throws UserFormException * @throws UserReportException */ @@ -709,10 +710,10 @@ class Report { case COLUMN_PPAGEI: case COLUMN_PPAGEN: case COLUMN_PPAGES: - $lowerColumnName = strtolower($columnName); + $lowerColumnName = strtolower($columnName); $tokenizedValue = $this->doFixColPosPage($columnName, $columnValue); - $linkValue = $this->doPage($lowerColumnName, $tokenizedValue); - $content .= $this->link->renderLink($linkValue); + $linkValue = $this->doPage($lowerColumnName, $tokenizedValue); + $content .= $this->link->renderLink($linkValue); break; // Lowercase 'P' @@ -725,7 +726,7 @@ class Report { case COLUMN_PAGEN: case COLUMN_PAGES: $linkValue = $this->doPage($columnName, $columnValue); - $content .= $this->link->renderLink($linkValue); + $content .= $this->link->renderLink($linkValue); break; case COLUMN_PPDF: diff --git a/extension/qfq/qfq/store/FillStoreForm.php b/extension/qfq/qfq/store/FillStoreForm.php index 2ef50b3d85cff9ff42388d0e4aa0d950bb5ca116..1ad8daf06e508854531a30e6631a95da916c96f1 100644 --- a/extension/qfq/qfq/store/FillStoreForm.php +++ b/extension/qfq/qfq/store/FillStoreForm.php @@ -81,7 +81,7 @@ class FillStoreForm { // Preparation for Log, Debug $this->store->setVar(SYSTEM_FORM, $formName, STORE_SYSTEM); - $feSpecNative = $this->dbArray[$this->dbIndexQfq]->sql(SQL_FORM_ELEMENT_SIMPLE_ALL_CONTAINER, ROW_EXPECT_GE_1, [$formName], + $feSpecNative = $this->dbArray[$this->dbIndexQfq]->sql(SQL_FORM_ELEMENT_SIMPLE_ALL_CONTAINER, ROW_REGULAR, [$formName], 'Form or FormElements not found: ' . ERROR_FORM_NOT_FOUND); HelperFormElement::explodeParameterInArrayElements($feSpecNative, FE_PARAMETER); @@ -148,7 +148,9 @@ class FillStoreForm { * @param string $formMode * * @throws CodeException + * @throws DbException * @throws UserFormException + * @throws UserReportException */ public function process($formMode = FORM_SAVE) { @@ -263,7 +265,7 @@ class FillStoreForm { } // Check only if there is something. - if ($val !== '') { + if ($val !== '' && $formMode!=FORM_UPDATE) { $val = Sanitize::sanitize($val, $formElement[FE_CHECK_TYPE], $formElement[FE_CHECK_PATTERN], $formElement[FE_DECIMAL_FORMAT], SANITIZE_EXCEPTION); if ($formElement[FE_ENCODE] === FE_ENCODE_SPECIALCHAR) { diff --git a/javascript/src/DragAndDrop.js b/javascript/src/DragAndDrop.js new file mode 100644 index 0000000000000000000000000000000000000000..e20fbf57f29d6244769153d5ed8e4c78e662c2f2 --- /dev/null +++ b/javascript/src/DragAndDrop.js @@ -0,0 +1,271 @@ +/** + * @author Benjamin Baer <benjamin.baer@math.uzh.ch> + */ + +/* global $ */ +/* global EventEmitter */ +/* @depend QfqEvents.js */ +/* @depend Alert.js */ + +/** + * Qfq Namespace + * + * @namespace QfqNS + */ +var QfqNS = QfqNS || {}; + +(function (n) { + 'use strict'; + + /** + * Dragging and dropping area! + */ + n.DragAndDrop = function ($hook) { + this.$container = $hook; + this.eventEmitter = new EventEmitter(); + this.dropZones = []; + this.elements = []; + this.active = false; + this.api = $hook.data("dnd-api"); + this.draggedId = ""; + this.lastChild = ""; + this.$tempObject = {}; + }; + + n.DragAndDrop.prototype.on = n.EventEmitter.onMixin; + + n.DragAndDrop.prototype.buildDropArea = function(position, relatedId, otherPos) { + var that = this; + var $dropArea = {}; + + if (this.$container.data("columns")) { + $dropArea = $("<tr />", { + class: "qfqDropTarget" + }); + var $fluff = $("<td />", { + class: "qfqDropTarget", + colspan: this.$container.data("columns") + }); + $fluff.appendTo($dropArea); + } else { + $dropArea = $("<div />", { + class: "qfqDropTarget" + }); + } + + $dropArea.data("position", position); + $dropArea.data("related", relatedId); + $dropArea.data("other-pos", otherPos); + $dropArea.on("dragenter", function(e) { + e.preventDefault(); + $dropArea.addClass("qfqTargetDisplay"); + }); + $dropArea.on("dragover", function(e) { + e.preventDefault(); + e.originalEvent.dataTransfer.dropEffect = "move"; + }); + $dropArea.on("drop", function(e) { + e.originalEvent.preventDefault(); + that._moveObjectBefore(e, $dropArea); + }); + + return $dropArea; + }; + + n.DragAndDrop.prototype.setDropZones = function($objects) { + var that = this; + + $objects.each(function() { + var $dropZone = $(this); + $dropZone.on("dragenter", function(e) { + e.preventDefault(); + $dropZone.addClass("qfqTargetDisplay"); + e.Effect = "all"; + that._handleDragEnter(e); + }); + $dropZone.on("dragleave", function(e) { + e.preventDefault(); + $dropZone.removeClass("qfqTargetDisplay"); + that._handleDragLeave(e); + }); + $dropZone.on("dragover", function(e) { + e.preventDefault(); + e.stopPropagation(); + }); + $dropZone.on("drop", function(e) { + e.preventDefault(); + e.stopPropagation(); + that._dropHandler(e); + }); + $dropZone.css("z-index", 5); + that.dropZones.push($dropZone); + }); + }; + + n.DragAndDrop.prototype.setElements = function($objects) { + var that = this; + + $objects.each(function() { + var $element = $(this); + $element.prop("draggable", true); + $element.on("dragstart", function(e) { + that.draggedId = $element[0].id; + }); + that.elements.push($element); + }); + }; + + n.DragAndDrop.prototype._handleDragEnter = function(event) { + var $tempObject = $("#" + this.draggedId).clone(); + $tempObject.css("opacity", 0.5); + $tempObject.css("z-index", 0); + $tempObject.off(); + if (this.$tempObject[0]) { + if ($tempObject[0].id !== this.$tempObject[0].id) { + this.$tempObject = $tempObject; + this.$tempObject.appendTo($("#" + event.currentTarget.id)); + } + } else { + this.$tempObject = $tempObject; + this.$tempObject.appendTo($("#" + event.currentTarget.id)); + } + }; + + n.DragAndDrop.prototype._handleDragLeave = function(event) { + if(this.$tempObject[0]) { + this.$tempObject.remove(); + this.$tempObject = {}; + } + }; + + n.DragAndDrop.prototype.makeBasketCase = function() { + var dzSelector = this.$container.data("dnd-dropzone") || false; + var elSelector = this.$container.data("dnd-element") || false; + + if (elSelector) { + this.setElements($("." + elSelector)); + } + if (dzSelector) { + this.setDropZones($("." + dzSelector)); + } + }; + + n.DragAndDrop.prototype._dropHandler = function(event) { + if(this.$tempObject[0]) { + this.$tempObject.remove(); + this.$tempObject = {}; + } + $("#" + this.draggedId).appendTo($("#" + event.currentTarget.id)); + }; + + n.DragAndDrop.prototype._buildOrderDropZones = function($object, e) { + this.removeDropAreas(); + + if ($object[0].id !== this.draggedId) { + if ($object.data("dnd-position") !== $("#" + this.draggedId).data("dnd-position") + 1) { + var $dropArea = this.buildDropArea("before", $object.data("dnd-id"), $object.data("dnd-position")); + $object.before($dropArea); + } + + if ($object[0].id === this.lastChild) { + var $lastDrop = this.buildDropArea("after", $object.data("dnd-id"), $object.data("dnd-position")); + $lastDrop.appendTo(this.$container); + } + } + }; + + n.DragAndDrop.prototype.removeDropAreas = function() { + if (this.$container.data("column")) { + this.$container.children(".qfqTempTable").remove(); + } + this.$container.children(".qfqDropTarget").remove(); + }; + + n.DragAndDrop.prototype.makeSortable = function() { + var that = this; + var numberOfChildren = this.$container.children().length; + var count = 0; + + this.$container.children().each( function() { + count++; + + var child = $(this); + if (numberOfChildren === count) { + that.lastChild = child[0].id; + } + child.data("dnd-position", count); + child.prop("draggable", true); + child.on("dragstart", function(e) { + e.originalEvent.dataTransfer.setData("text", child[0].id); + that.draggedId = child[0].id; + that.active = true; + e.originalEvent.dataTransfer.effectAllowed = "move"; + }); + child.on("dragenter", function(e) { + if (that.active) { + that._buildOrderDropZones($(this), e); + } + }); + child.on("dragend", function() { + that.active = false; + that.removeDropAreas(); + }); + }); + }; + + n.DragAndDrop.prototype._moveObjectBefore = function(e, $hook) { + var id = e.originalEvent.dataTransfer.getData("text"); + var $object = $("#" + id); + var posTo = $hook.data("position"); + + if (posTo === "after") { + this.lastChild = $object[0].id; + } + + $hook.before(document.getElementById(id)); + this._buildOrderUpdate($object, $hook.data("position"), $hook.data("related"), $hook.data("other-pos")); + this.removeDropAreas(); + }; + + n.DragAndDrop.prototype._buildOrderUpdate = function($object, position, otherId, otherPos) { + var jObject = {}; + jObject.dragId = $object.data("dnd-id"); + jObject.dragPosition = $object.data("dnd-position"); + jObject.setTo = position; + jObject.hoverId = otherId; + jObject.hoverPosition = otherPos; + this._sendToAPI(jObject); + }; + + n.DragAndDrop.prototype._sendToAPI = function(object) { + var that = this; + $.getJSON(this.api, object, function(data) { + that._successHandler(data); + }); + }; + + n.DragAndDrop.prototype._successHandler = function(data) { + if (data.status === "error") { + var alert = new n.Alert({ + type: data.status, + message: data.message, + modal: true, + buttons: [{ + label: "Ok", eventName: "ok" + }] + }); + alert.show(); + console.error(data.message); + } else { + console.log("status:" + data.status + " message: " + data.message); + if (data.elementUpdate) { + if (!this.elementUpdate) { + this.elementUpdate = new n.ElementUpdate(); + } + this.elementUpdate.updateAll(data.elementUpdate); + } + } + }; + + +})(QfqNS); \ No newline at end of file diff --git a/javascript/src/TypeAhead.js b/javascript/src/TypeAhead.js index 204050c5421ef41af31ab8136f323f9e654af5d5..0a6e12b5410061523b3b5c3266790055fe553e1f 100644 --- a/javascript/src/TypeAhead.js +++ b/javascript/src/TypeAhead.js @@ -16,7 +16,6 @@ var QfqNS = QfqNS || {}; n.TypeAhead = {}; - /** * Coerce corejs-typeahead into our use-case. * @@ -219,5 +218,4 @@ var QfqNS = QfqNS || {}; } $element.typeahead('val', results[0].value); }; -})(QfqNS); - +})(QfqNS); \ No newline at end of file diff --git a/less/qfq-bs.css.less b/less/qfq-bs.css.less index e4ed45466dff97ef85131c4ba110b48b32360564..cc46c35341664afdbf39f378728dd317d425ad2a 100644 --- a/less/qfq-bs.css.less +++ b/less/qfq-bs.css.less @@ -110,16 +110,23 @@ i.@{spinner_class} { width: auto; } + .qfq-form-pill { + /* border-top-right-radius: 4px; - border-top-left-radius: 4px; + border-top-left-radius: 4px; */ + border: 1px solid #ccc; + border-top: none; } + .qfq-form-body { padding-top: 5px; padding-bottom: 5px; border-bottom-right-radius: 4px; border-bottom-left-radius: 4px; + border: 1px solid #ccc; + border-top: none; } /* adjust BS padding of input elements: center */ @@ -134,6 +141,21 @@ i.@{spinner_class} { padding-top: 4px; } +.qfq-form-title { + border: 1px solid #ccc; + border-radius: 10px 10px 0 0; + background-image: linear-gradient(to bottom, #fefefe 0, #dedede 100%); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075); + height: 45px; + font-size: 1.2em; + padding: 10px 15px; + font-weight: bold; +} + +.nav-pills>li>a { + border-radius: 0; +} + .qfq-color-white { background-color: #ffffff; } @@ -412,4 +434,14 @@ i.@{spinner_class} { .alert-interactive p.buttons { margin-top: 20px; text-align: center; +} + +.qfqDropTarget { + height: 50px; + margin: 1px; + border: 1px dashed #ccc; +} + +.qfqTargetDisplay { + border: 1px dashed #25adf1; } \ No newline at end of file diff --git a/mockup/dragAndDrop.html b/mockup/dragAndDrop.html new file mode 100644 index 0000000000000000000000000000000000000000..43a3d30db07a06fb505cb1904675ec9bf7d2e382 --- /dev/null +++ b/mockup/dragAndDrop.html @@ -0,0 +1,181 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <meta http-equiv="X-UA-Compatible" content="IE=edge"> + + <style type="text/css"> + .anyClass { + height: 40px; + background-color: #5cb85c; + line-height: 40px; + color: white; + text-align: center; + } + + .someClass { + min-height: 160px; + width: 160px; + border: 1px dashed #ccc; + margin: 15px; + padding-bottom: 30px; + } + + .anotherClass { + margin: 15px; + padding: 15px; + border-radius: 5px; + text-align: center; + background-color: #5cb85c; + color: #fff; + width: 130px; + } + </style> + + <link rel="stylesheet" href="../css/bootstrap.min.css"> + <link rel="stylesheet" href="../css/bootstrap-theme.min.css"> + <link rel="stylesheet" href="../css/jqx.base.css"> + <link rel="stylesheet" href="../css/jqx.bootstrap.css"> + <link rel="stylesheet" href="../extension/Resources/Public/Css/qfq-bs.css"> + <title>Input Mode Switcher</title> + +</head> +<body style="background-color: #f5f5f5;"> +<div class="container-fluid"> + <div class="row"> + <div class="col-md-10 "> + <div class="btn-toolbar pull-right" role="toolbar"> + <div class="btn-group" role="group"> + <button id="save-button" type="button" class="btn btn-default navbar-btn"><span + class="glyphicon glyphicon-ok"></span></button> + <button id="close-button" type="button" class="btn btn-default navbar-btn"><span + class="glyphicon glyphicon-remove"></span></button> + </div> + <div class="btn-group" role="group"> + <button id="delete-button" type="button" class="btn btn-default navbar-btn"><span + class="glyphicon glyphicon-trash"></span></button> + </div> + <div class="btn-group" role="group"> + <a id="form-new-button" href="personmock.html?s=badcaffe1" class="btn btn-default navbar-btn"><span + class="glyphicon glyphicon-plus"></span></a> + </div> + </div> + </div> + </div> + + <div class="row"> + <div class="col-md-3"></div> + <div class="col-md-6"> + + <div class="qfq-dnd-sort" data-dnd-api="http://something/bla"> + <div class="anyClass" id="e1" data-dnd-id="uno" data-dnd-value="10"> + Numbero Uno + </div> + <div class="anyClass" id="e2" data-dnd-id="deux"> + Numbero Deux + </div> + <div class="anyClass" id="e3" data-dnd-id="tre"> + Numbero Tre + </div> + <div class="anyClass" id="e4" data-dnd-id="quattro"> + Numbero Quattro + </div> + <div class="anyClass" id="e5" data-dnd-id="cinge"> + Numbero Cinge + </div> + <div class="anyClass" id="e6" data-dnd-id="siesta"> + Numbero Siesta + </div> + </div> + + <h2>Table</h2> + + <table class="table table-hover"> + <tbody class="qfq-dnd-sort" + data-dnd-api="typo3conf/ext/qfq/qfq/api/dragAndDrop.php?s=5b15109182850" + data-columns="3"> + <tr id="table-125" data-dnd-id="125"> + <td>125</td> + <td>eins</td> + <td>10</td> + </tr> + <tr id="table-126" data-dnd-id="126"> + <td>126</td> + <td>zwei</td> + <td>20</td> + </tr> + <tr id="table-128" data-dnd-id="128"> + <td>128</td> + <td>vier</td> + <td>30</td> + </tr> + <tr id="table-127" data-dnd-id="127"> + <td>127</td> + <td>drei</td> + <td>40</td> + </tr> + </tbody> + </table> + + <hr> + <h2>Playing with baskets</h2> + <div class="qfq-dnd" style="margin-top: 20px;" + data-dnd-api="http://somethingelse/bla"c + data-dnd-dropzone="someClass" + data-dnd-element="anotherClass" + > + <div class="col-md-6"> + <div class="someClass" id="dz1"> + + </div> + <div class="someClass" id="dz2"> + + </div> + </div> + + <div class="col-md-6"> + <div class="anotherClass" id="d1"> + Somebody + </div> + <div class="anotherClass" id="d2"> + Otherbody + </div> + <div class="anotherClass" id="d3"> + Thatbody + </div> + <div class="anotherClass" id="d4"> + Not me + </div> + <div class="anotherClass" id="d5"> + Otherguy + </div> + </div> + </div> + </div> + </div> +</div> +<p><br></p> +<script src="../js/jquery.min.js"></script> +<script src="../js/bootstrap.min.js"></script> +<script src="../js/validator.min.js"></script> +<script src="../js/EventEmitter.min.js"></script> +<script src="../js/qfq.debug.js"></script> + +<script type="text/javascript"> + $(function () { + + $('.qfq-dnd-sort').each(function() { + var dndObject = new QfqNS.DragAndDrop($(this)); + dndObject.makeSortable(); + }); + + $('.qfq-dnd').each(function() { + var zoni = new QfqNS.DragAndDrop($(this)); + zoni.makeBasketCase(); + }); + + }); +</script> +</body> +</html> \ No newline at end of file