diff --git a/Documentation/Form.rst b/Documentation/Form.rst index ec71de3f7552331c94e8df574e0a535bbfae73a2..37f03ac115386aafa52f1ca1d98dbba1f3cdb438 100644 --- a/Documentation/Form.rst +++ b/Documentation/Form.rst @@ -1289,7 +1289,9 @@ Type: date * Range datetime: '1000-01-01' to '9999-12-31' or '0000-00-00'. (http://dev.mysql.com/doc/refman/5.5/en/datetime.html) * Optional: - * *FormElement.parameter.dateFormat*: yyyy-mm-dd | dd.mm.yyyy + * *FormElement.parameter.dateFormat*: YYYY-MM-DD | DD.MM.YYYY + +Actually datetimepicker is used as default. For more options see :ref:`Installation_datetimepicker` Type: datetime ^^^^^^^^^^^^^^ @@ -1299,10 +1301,11 @@ Type: datetime * *FormElement.parameter*: - * *dateFormat* = yyyy-mm-dd | dd.mm.yyyy + * *dateFormat* = YYYY-MM-DD | DD.MM.YYYY * *showSeconds* = 0|1 - shows the seconds. Independent if the user specifies seconds, they are displayed '1' or not '0'. * *showZero* = 0|1 - For an empty timestamp, With '0' nothing is displayed. With '1' the string '0000-00-00 00:00:00' is displayed. +Actually datetimepicker is used as default. For more options see :ref:`Installation_datetimepicker` Type: extra ^^^^^^^^^^^ @@ -1944,6 +1947,7 @@ Type: time * *showSeconds* = `0|1` - shows the seconds. Independent if the user specifies seconds, they are displayed '1' or not '0'. * *showZero* = `0|1` - For an empty timestamp, With '0' nothing is displayed. With '1' the string '00:00[:00]' is displayed. +Actually datetimepicker is used as default. For more options see :ref:`Installation_datetimepicker` .. _`input-upload`: Type: upload diff --git a/Documentation/Installation.rst b/Documentation/Installation.rst index ccbda94b4e35ee674aa335c0bc67b38eabd494ff..69632be84f0ae44386d16588020503940b2745fa 100644 --- a/Documentation/Installation.rst +++ b/Documentation/Installation.rst @@ -246,12 +246,10 @@ Setup CSS & JS file05 = typo3conf/ext/qfq/Resources/Public/Css/qfq-bs.css file06 = typo3conf/ext/qfq/Resources/Public/Css/tablesorter-bootstrap.css file07 = typo3conf/ext/qfq/Resources/Public/Css/font-awesome.min.css + file08 = typo3conf/ext/qfq/Resources/Public/Css/bootstrap-datetimepicker.min.css # Only needed in case FullCalendar is used - file08 = typo3conf/ext/qfq/Resources/Public/Css/fullcalendar.min.css - - # Only needed in case FormElement 'datetime'/'date' is used - file09 = typo3conf/ext/qfq/Resources/Public/Css/bootstrap-datetimepicker.min.css + file09 = typo3conf/ext/qfq/Resources/Public/Css/fullcalendar.min.css } page.includeJS { @@ -268,17 +266,16 @@ Setup CSS & JS file11 = typo3conf/ext/qfq/Resources/Public/JavaScript/jquery.tablesorter.pager.min.js file12 = typo3conf/ext/qfq/Resources/Public/JavaScript/widget-columnSelector.min.js file13 = typo3conf/ext/qfq/Resources/Public/JavaScript/widget-output.min.js + file14 = typo3conf/ext/qfq/Resources/Public/JavaScript/bootstrap-datetimepicker.min.js # Only needed in case FormElement 'annotate' is used. - file14 = typo3conf/ext/qfq/Resources/Public/JavaScript/fabric.min.js - file15 = typo3conf/ext/qfq/Resources/Public/JavaScript/qfq.fabric.min.js + file15 = typo3conf/ext/qfq/Resources/Public/JavaScript/fabric.min.js + file16 = typo3conf/ext/qfq/Resources/Public/JavaScript/qfq.fabric.min.js - # Only needed in case FullCalendar is used - file16 = typo3conf/ext/qfq/Resources/Public/JavaScript/moment.min.js - file17 = typo3conf/ext/qfq/Resources/Public/JavaScript/fullcalendar.min.js + # Only needed in case FullCalendar is used. + file17 = typo3conf/ext/qfq/Resources/Public/JavaScript/moment.min.js + file18 = typo3conf/ext/qfq/Resources/Public/JavaScript/fullcalendar.min.js - # Only needed in case FormElement 'datetime'/'date' is used - file18 = typo3conf/ext/qfq/Resources/Public/JavaScript/bootstrap-datetimepicker.min.js } @@ -296,6 +293,19 @@ As first option both can be inserted to the setup of the main Template like the Second option is to use the UZH CD template. +Following configurations can be set over FormElement.parameter: + +dateFormat = *DD.MM.YYYY HH:mm:ss* | *MM-DD-YYYY HH:mm* | *dddd DD.MM.YYYY HH:mm* -> DD:day of month,MM:month value,YYYY:year value,HH:24h,hh:12h AM-PM,mm:minutes,ss:seconds,dddd:written day of week +dateDaysOfWeekEnabled = *0,1,6* -> 0:sunday,1:monday,2:tuesday,3:wednesday,4:thursday,5:friday,6:saturday +dateLocale = *en* | *de* -> Set language +min = *03.05.2022* -> minDate that can be selected +max = *23.07.2022* -> maxDate that can be selected +dateViewModeDefault = *days* | *months* | *years* +clearMe = *0* | *1* -> show clear button +dateShowCalendarWeeks = *false* | *true* +dateUseCurrentDatetime = *false* | *true* +datetimeSideBySide = *false* | *true* -> Show time right to date + .. _form-editor: FormEditor @@ -310,6 +320,10 @@ Setup a *report* to manage all *forms*: file=_formEditor +* Twig version of FormEditor is available too :: + + file=_formEditorTwig + .. _install-checklist: Installation: Check List diff --git a/Documentation/System.rst b/Documentation/System.rst index 6cf15afecb5dc06b0316121c69285b862ba3f14a..e7bc48cac0b9acc9be63b3a7187e0391690c3df2 100644 --- a/Documentation/System.rst +++ b/Documentation/System.rst @@ -125,6 +125,10 @@ to edit `AutoCron` jobs:: } +Or you can use the following code in a separate QFQ record for the twig version of autoCron:: + + file=_autoCronTwig + Usage ^^^^^ diff --git a/extension/Classes/Core/AbstractBuildForm.php b/extension/Classes/Core/AbstractBuildForm.php index 08fc872de2391bdfeaf301a4940deaf02f6db0a4..e7be03ecba8873c2b9ae818e93fdb59992be2949 100644 --- a/extension/Classes/Core/AbstractBuildForm.php +++ b/extension/Classes/Core/AbstractBuildForm.php @@ -1488,11 +1488,21 @@ abstract class AbstractBuildForm { $formElement[FE_TYPE] = 'hidden'; } } + // If type password is selected then type text with own class will be taken to fake password over CSS + if($formElement[FE_TYPE] === 'password'){ + $formElement[FE_TYPE] = 'text'; + $class .= ' qfq-password'; + } $attribute .= HelperFormElement::getAttributeList($formElement, [FE_TYPE, 'size']); $attribute .= Support::doAttribute('value', htmlentities($value), false); // $attribute .= Support::doAttribute('value', htmlentities($value, ENT_QUOTES, 'UTF-8'), false); } + // Set for password to give choice of generated password for user and not autofill password field. Deprecated, we dont use type password anymore. + // if($formElement[FE_TYPE] === 'password') { + // $attribute .= Support::doAttribute('autocomplete', 'new-password'); + // } + $attribute .= HelperFormElement::getAttributeList($formElement, [FE_INPUT_AUTOCOMPLETE, 'autofocus', 'placeholder']); $formElement[FE_CHECK_PATTERN] = Sanitize::getInputCheckPattern($formElement[FE_CHECK_TYPE], $formElement[FE_CHECK_PATTERN] @@ -1542,6 +1552,11 @@ abstract class AbstractBuildForm { $input .= $formElement[FE_INPUT_EXTRA_BUTTON_INFO]; } + //Generate an empty input type text to ignore autocomplete in other elements. deprecated because not using type password anymore. +// if($formElement[FE_TYPE] === 'password'){ +// $input = '<input type="text" style="display:none;">'.$input; +// } + return $input; } diff --git a/extension/Classes/Core/BuildFormBootstrap.php b/extension/Classes/Core/BuildFormBootstrap.php index cdd7ff2c7b2c7d40595adfc44d39d10891e399fa..3e45bb7b5a38740be3be3cc9a4b65b5cf35ef2e0 100644 --- a/extension/Classes/Core/BuildFormBootstrap.php +++ b/extension/Classes/Core/BuildFormBootstrap.php @@ -245,6 +245,7 @@ class BuildFormBootstrap extends AbstractBuildForm { $form = false; $url = ''; $status = ''; + $requiredNew = ''; switch ($this->formSpec[F_NAME]) { case 'form': @@ -253,6 +254,8 @@ class BuildFormBootstrap extends AbstractBuildForm { case 'formElement': if (false !== ($formId = $this->store->getVar(FE_FORM_ID, STORE_SIP . STORE_RECORD))) { $row = $this->dbArray[$this->dbIndexQfq]->sql("SELECT `f`.`name` FROM `Form` AS f WHERE `id`=" . $formId, ROW_EXPECT_1); + $rowRequiredNew = $this->dbArray[$this->dbIndexQfq]->sql("SELECT `f`.`requiredParameterNew` FROM `Form` AS f WHERE `id`=" . $formId, ROW_EXPECT_1); + $requiredNew = current($rowRequiredNew); $form = current($row); } break; @@ -264,7 +267,9 @@ class BuildFormBootstrap extends AbstractBuildForm { $toolTip = "Form not 'form' or 'formElement'"; $status = 'disabled'; } else { - $requiredNew = $this->store->getVar(F_REQUIRED_PARAMETER_NEW, STORE_RECORD . STORE_EMPTY); + if($requiredNew === ''){ + $requiredNew = $this->store->getVar(F_REQUIRED_PARAMETER_NEW, STORE_RECORD . STORE_EMPTY); + } if (trim($requiredNew) !== '') { $toolTip = "Form has 'required new' parameters and therefore cannot be previewed."; $status = 'disabled'; diff --git a/extension/Classes/Core/Database/Database.php b/extension/Classes/Core/Database/Database.php index f78282980165641f512d915bcee53b44f84f1c4a..8dfb9dbb894bbc1c2eb47bac00a9b8b5bdccb320 100644 --- a/extension/Classes/Core/Database/Database.php +++ b/extension/Classes/Core/Database/Database.php @@ -441,7 +441,6 @@ class Database { case 'SHOW': case 'DESCRIBE': case 'EXPLAIN': - case 'CALL': if (false === ($result = $this->mysqli_stmt->get_result())) { throw new \DbException( json_encode([ERROR_MESSAGE_TO_USER => 'Error DB execute', ERROR_MESSAGE_TO_DEVELOPER => '[ mysqli: ' . $this->mysqli_stmt->errno . ' ] ' . $this->mysqli_stmt->error . $specificMessage]), @@ -454,6 +453,19 @@ class Database { $count = $stat[DB_NUM_ROWS]; $msg = 'Read rows: ' . $stat[DB_NUM_ROWS]; break; + case 'CALL': + $result = $this->mysqli_stmt->get_result(); + $queryType = QUERY_TYPE_SELECT; + If($result === false){ + $stat[DB_NUM_ROWS] = 0; + $msg = 'Read rows: ' . $stat[DB_NUM_ROWS] . '(No SELECT statement)'; + }else{ + $this->mysqli_result = $result; + $stat[DB_NUM_ROWS] = $this->mysqli_result->num_rows; + $msg = 'Read rows: ' . $stat[DB_NUM_ROWS]; + } + $count = $stat[DB_NUM_ROWS]; + break; case 'REPLACE': case 'INSERT': $queryType = QUERY_TYPE_INSERT; diff --git a/extension/Classes/Core/Helper/SessionCookie.php b/extension/Classes/Core/Helper/SessionCookie.php index 2de3eb2c699e1e989fa39d390c1dbc468e9051f3..0c7fe559ba9193efa978710a130bcea3e00bed7a 100644 --- a/extension/Classes/Core/Helper/SessionCookie.php +++ b/extension/Classes/Core/Helper/SessionCookie.php @@ -71,10 +71,18 @@ class SessionCookie { // $this->arrCookieString[] = "name:$key,value:$value,url:$domain,path:$path"; // qfqpdf seems to have problems if 'domain' is specified: it hangs by fetching the website. Skip domain. + // qfqpdf generates another cookie for pdf (SIPs then not reachable) if the given domain doesnt equal what is set in cookie params. Domain default from qfqpdf is without dot. We need to give the domain with previous dot. $this->arrQfqPdfCookie[] = "name:$key,value:$value"; } + $linesForWkhtml = ''; + for($i = 0; $i < count($wkhtml); $i++){ + $linesForWkhtml .= $wkhtml[$i]['name'] . "=".$wkhtml[$i]['value']. "; domain=".$wkhtml[$i]['url']."; "."path=".$wkhtml[$i]['path'].";"; + if($i+1 < count($wkhtml)){ + $linesForWkhtml .= "\n"; + } + } - file_put_contents($this->pathFileNameCookie, json_encode(['cookies' => $wkhtml]), FILE_APPEND); + file_put_contents($this->pathFileNameCookie, $linesForWkhtml, FILE_APPEND); } /** diff --git a/extension/Classes/Core/Store/Session.php b/extension/Classes/Core/Store/Session.php index 7671274d511824b131cd1e2e56c5599b75e73436..cd471facd22b74dfb2c65706226cd0ba205c13da 100644 --- a/extension/Classes/Core/Store/Session.php +++ b/extension/Classes/Core/Store/Session.php @@ -58,13 +58,14 @@ class Session // Needed expire date for header() method $expireDate = date("D, d-M-Y H:i:s", strtotime('+2 days')). ' GMT'; + // More information about previous dots in domains: https://stackoverflow.com/questions/348282/php-cookie-domain-subdomain-control if(PHP_VERSION_ID < 70300) { - session_set_cookie_params($lifetime, $path.';SameSite='.$samesite, $_SERVER['HTTP_HOST'], $secure, $httponly); + session_set_cookie_params($lifetime, $path.';SameSite='.$samesite, null, $secure, $httponly); } else { session_set_cookie_params([ 'lifetime' => $lifetime, 'path' => $path, - 'domain' => $_SERVER['HTTP_HOST'], + 'domain' => null, 'secure' => $secure, 'httponly' => $httponly, 'samesite' => $samesite diff --git a/extension/Resources/Private/Report/autoCronTwig.qfqr b/extension/Resources/Private/Report/autoCronTwig.qfqr new file mode 100644 index 0000000000000000000000000000000000000000..7a221385e92fdb813a6bb6e6b13ffb7dd77b26b5 --- /dev/null +++ b/extension/Resources/Private/Report/autoCronTwig.qfqr @@ -0,0 +1,57 @@ +dbIndex={{indexQfq:Y}} +form={{form:S}} + +1.sql = SELECT id, status,comment, lastRun, lastStatus, nextRun, frequency, inProgress +FROM Cron WHERE '{{form:SE}}' = '' ORDER BY id + +1.twig = {% if store.sip.form == '' %} +<table class='table table-hover qfq-table-50'> + <thead class="qfq-sticky"><tr> + <tr> + <th data-sorter="false" class="filter-false"> + {{ ('p:'~store.typo3.pageAlias~'|s|N|U:form=cron') | qfqlink }} + </th> + <th>ID</th> + <th>Next run</th> + <th>Frequency</th> + <th>Comment</th> + <th>Last run</th> + <th>In Progress</th> + <th>Status</th> + <th></th> + </tr> + </thead> + <tbody> +{% for row in result %} + {% set tr_class = "" %} + {% set tr_title = "" %} + {% if row.status != "enable" %} + {% set tr_class = "text-muted" %} + {% elseif row.lastStatus[:5] == "Error" %} + {% set tr_class = "danger" %} + {% set tr_title = "Status: Error" %} + {% elseif row.inProgress != 0 and (row.inProgress|date('U') < "-10 minutes"|date('U')) %} + {% set tr_class = "warning" %} + {% set tr_title = "in Progress > 10mins" %} + {% endif %} + + <tr class="{{tr_class}}" title="{{tr_title}}"> + <td>{{ ('p:'~store.typo3.pageAlias~'|s|E|U:form=cron&r='~row.id) | qfqlink }}</td> + <td>{{ row.id }}</td> + <td>{{ row.nextRun != 0 ? row.nextRun|date("Y-m-d H:i:s") : "-" }}</td> + <td>{{ row.frequency }} </td> + <td>{{ row.comment }} </td> + <td>{{ row.lastRun != 0 ? row.lastRun|date("Y-m-d H:i:s") : "-" }}</td> + <td>{{ row.inProgress != 0 ? (("now"|date("U")-row.inProgress|date("U"))//60~" minutes") : "-" }}</td> + <td>{{ row.lastStatus[:40] }} </td> + <td> + {{ ('u:/typo3conf/ext/qfq/Classes/Api/delete.php'~ + '|s|D|q:Delete Cronjob '~row.id~'?'~ + '|U:_modeAnswer=html&_targetUrl=/index.php?id='~store.typo3.pageAlias~ + '&form=cron&r='~row.id) | qfqlink }} + </td> + </tr> +{% endfor %} + </tbody> +</table> +{% endif %} diff --git a/extension/Resources/Private/Report/formEditorTwig.qfqr b/extension/Resources/Private/Report/formEditorTwig.qfqr new file mode 100644 index 0000000000000000000000000000000000000000..97f13b1555670f231cc1c3d420580518965c2983 --- /dev/null +++ b/extension/Resources/Private/Report/formEditorTwig.qfqr @@ -0,0 +1,108 @@ +# +# Form +# +# a) List of forms: {{form:S}}='', {{formIdHistory:S}}='' +# b) Edit Form: {{form:S}} - Open form {{form:S}} with record {{r:S}} - typically the FormEditor +# c) Use history of a given form: {{formIdHistory:S}} +# +# {{form:S}} +# {{formIdHistory:S0}} - usage history of form '{{formIdHistory:S}}' + +form={{form:SE}} + +dbIndex={{indexQfq:Y}} + +10.sql = SELECT f.id + , f.name + , f.title + , f.tableName + , COUNT(fsl.id) AS submit_count + , MIN(fsl.created) AS submit_first + , MAX(fsl.created) AS submit_last + , GROUP_CONCAT(DISTINCT fsl.pageId ORDER BY fsl.pageId) AS submit_pages +FROM Form AS f +LEFT JOIN FormSubmitLog AS fsl ON fsl.formId=f.id +WHERE '{{form:SE}}'='' AND {{formIdHistory:S0}}=0 +GROUP BY f.id ORDER BY f.name + +10.twig = {% if result|length > 0 %} +<table class="table table-hover qfq-table-50 tablesorter tablesorter-filter" id="{{store.typo3.pageAlias}}-form"> +<thead class="qfq-sticky"><tr> + <th data-sorter="false" class="filter-false"> + <div class="btn-group" role="group"> + {{ ('p:'~store.typo3.pageAlias~'|s|N|U:form=form') | qfqlink }} + {{ ('p:'~store.typo3.pageAlias~'|s|G:glyphicon-th-list|o:Edit as JSON|U:form=formJson') | qfqlink }} + </div> + </th> + <th>Name</th> + <th>Title</th> + <th>Table</th> + <th>#</th> + <th><em>Frist</em></th> + <th><em>Last</em></th> + <th><em>PageId</em></th> +</tr></thead> +<tbody> + +{% for row in result %} +<tr> + <td> + {{ ('p:'~store.typo3.pageAlias~'|s|E|U:form=form&r='~row.id) | qfqlink }} + {{ ('p:'~store.typo3.pageAlias~'|s|G:glyphicon-th-list|o:Edit as JSON|U:form=formJson&r='~row.id) | qfqlink }} + </td> + <td>{{ row.name }} <span class="text-muted">({{ row.id }})</span></td> + <td> + {{ row.title[:50] }}{%- if row.title|length > 50 -%} + <span class="qfq-more-text">{{ row.title[50:] }}</span> + {% endif %} + </td> + <td>{{ row.tableName }}</td> + <td> + {% if row.submit_count == 0 %} + {% set mode = "|r:3" %} + {% else %} + {% set mode = "" %} + {% endif %} + {{ ('p:'~store.typo3.pageAlias~mode~'|U:formIdHistory='~row.id~'|s|b|t:<span class="badge">'~row.submit_count~'</span>') | qfqlink }} + </td> + <td><em>{{ row.submit_first ? row.submit_first|date('Y-m-d') : "-" }}</em></td> + <td><em>{{ row.submit_last ? (date(row.submit_last).diff(date()).days~" days ago") : "-" }}</em></td> + <td><em>{{ row.submit_pages }}</em></td> +</tr> +{% endfor %} +</tbody></table> +{% endif %} + + + + +20.sql = SELECT f.name + , fsl.feUser + , fsl.recordId + , fsl.pageId + , fsl.created + FROM FormSubmitLog AS fsl + LEFT JOIN Form AS f + ON fsl.formId=f.id + WHERE fsl.formId={{formIdHistory:S0}} + ORDER BY fsl.created DESC + +20.twig = {% if result|length > 0 %} +<h3>Submit History for {{ result.0.name }}</h3> +<a href="javascript:history.back()">Back</a> +<table class="table table-hover qfq-table-50 tablesorter tablesorter-filter" id="{{store.typo3.pageAlias}}-formHistory"> +<thead class="qfq-sticky"><tr><th>Form</th><th>feUser</th><th>recordId</th><th>pageId</th><th>Submit</th></tr></thead> +<tbody> +{% for row in result %} +<tr> + {% for col in row %} + <td>{{ col }}</td> + {% endfor %} +</tr> +{% else %} + {# this should never happen, as we do not provide a link if there is no history #} + <tr><td></td><td></td><td></td><td>no history found</td><td></td><td></td><td></td><td></td><td></td></tr> +{% endfor %} +</tbody></table> +<a href="javascript:history.back()">Back</a> +{% endif %} diff --git a/javascript/src/BSTabs.js b/javascript/src/BSTabs.js index 096a51fad2ccfb448b672bf0acf6e2db02c24403..fdb1955510add0f7fd32e8130a5d08f4141715b7 100644 --- a/javascript/src/BSTabs.js +++ b/javascript/src/BSTabs.js @@ -35,6 +35,8 @@ var QfqNS = QfqNS || {}; this.tabs = {}; this.currentTab = this.getActiveTabFromDOM(); this.eventEmitter = new EventEmitter(); + this.currentFormName = $('#' + this.tabId + ' .active a[data-toggle="tab"]')[0].hash.slice(1).split("_")[0]; + this.currentRecordId = $('#' + this.tabId + ' a[data-toggle="tab"]')[0].id.split("-")[2]; // Fill this.tabs this.fillTabInformation(); @@ -111,7 +113,6 @@ var QfqNS = QfqNS || {}; n.BSTabs.prototype.tabShowHandler = function (event) { n.Log.debug('Enter: BSTabs.tabShowHandler()'); this.currentTab = event.target.hash.slice(1); - n.Log.debug("BSTabs.tabShowHandler(): invoke user handler(s)"); this.eventEmitter.emitEvent('bootstrap.tab.shown', n.EventEmitter.makePayload(this, null)); this.removeDot(this.currentTab); diff --git a/javascript/src/Main.js b/javascript/src/Main.js index 4a47dc81d9f1121282926e603f595579aeff5a67..9d620e89dfb6bcf1fd947acaa8c88117acd5e557 100644 --- a/javascript/src/Main.js +++ b/javascript/src/Main.js @@ -6,7 +6,7 @@ Function.prototype.bind = Function.prototype.bind || function (thisp) { var fn = this; return function () { - return fn.apply(thisp, arguments); + return fn.apply(thisp, arguments); }; }; @@ -16,15 +16,15 @@ $(document).ready( function () { (function (n) { try { - var tablesorterController = new n.TablesorterController(); - $('.tablesorter').each(function(i) { - tablesorterController.setup($(this), i); - }); // end .each() + var tablesorterController = new n.TablesorterController(); + $('.tablesorter').each(function (i) { + tablesorterController.setup($(this), i); + }); // end .each() - $('.tablesorter-filter').addClass('qfq-skip-dirty'); - } catch (e) { - console.log(e); - } + $('.tablesorter-filter').addClass('qfq-skip-dirty'); + } catch (e) { + console.log(e); + } $('.qfq-auto-grow').each(function() { var minHeight = $(this).attr("rows") * 14 + 18; @@ -80,7 +80,7 @@ $(document).ready( function () { html: "×" }); if (myInput.val() == '' || myInput.is('[disabled=disabled]')) { - closeButton.addClass("hidden"); + closeButton.addClass("hidden"); } closeButton.on("click", function(e) { myInput.val(''); @@ -94,7 +94,7 @@ $(document).ready( function () { closeButton.addClass("hidden"); } }); - + }); @@ -128,17 +128,19 @@ $(document).ready( function () { $('.qfq-datepicker').each(function() { var dates = {}; var datesToFormat = ["minDate", "maxDate"]; + var correctAttributeNames = ["mindate", "maxdate"]; for(var i = 0; i < datesToFormat.length; i++) { var date = false; - if($(this).data(datesToFormat[i])) { - var dateArray = $(this).data(datesToFormat[i]).split("."); - date = dateArray[1] + "." + dateArray[0] + "." + dateArray[2]; + if($(this).data(correctAttributeNames[i])) { + var dateArray = $(this).data(correctAttributeNames[i]).split("."); + date = dateArray[1] + "/" + dateArray[0] + "/" + dateArray[2]; } dates[datesToFormat[i]] = date; } + var options = { locale: $(this).data("locale") || "en", - daysOfWeekDisabled: $(this).data("days-of-week-disabled") || [0,6], + daysOfWeekDisabled: $(this).data("days-of-week-disabled") || [], minDate: dates.minDate, maxDate: dates.maxDate, format: $(this).data("format") || "DD.MM.YYYY HH:mm", @@ -146,12 +148,12 @@ $(document).ready( function () { showClear: ($(this).data("show-clear-button") !== undefined) ? $(this).data("show-clear-button") : true, calendarWeeks: ($(this).data("show-calendar-weeks") !== undefined) ? $(this).data("show-calendar-weeks") : false, useCurrent: ($(this).data("use-current-datetime") !== undefined) ? $(this).data("use-current-datetime") : false, - sideBySide: ($(this).data("datetime-side-by-side") !== undefined) ? $(this).data("datetime-side-by-side") : false + sideBySide: ($(this).data("datetime-side-by-side") !== undefined) ? $(this).data("datetime-side-by-side") : false, }; var currentDatePicker = $(this).datetimepicker(options); - - + + }); diff --git a/javascript/src/QfqPage.js b/javascript/src/QfqPage.js index 6914b6efb69a1da33e550204e7b0bd2d4542dd6b..ec3f21b5382ecdc97fff0a2f09a204a171d7badb 100644 --- a/javascript/src/QfqPage.js +++ b/javascript/src/QfqPage.js @@ -45,7 +45,36 @@ var QfqNS = QfqNS || {}; try { this.bsTabs = new n.BSTabs(this.settings.tabsId); + var storedFormInfos = []; + + // get current state from session storage + if(sessionStorage.getItem("formInfos") !== null) { + storedFormInfos = JSON.parse(sessionStorage.getItem("formInfos")); + } + + var currentForm = this.bsTabs.currentFormName; + var currentRecordId = this.bsTabs.currentRecordId; + + var actualIndex = -1; + var indexNr = 0; + if(storedFormInfos.length !== 0){ + if(storedFormInfos[0] !== ''){ + storedFormInfos.forEach(function callback(element){ + if(element === currentForm && storedFormInfos[indexNr+2] === currentRecordId){ + actualIndex = indexNr; + } + indexNr++; + }); + } + } + var currentState = this.settings.pageState.getPageState(); + + // load from sessionStorage or from path given hash if not empty + if(actualIndex !== -1 && location.hash === "") { + currentState = storedFormInfos[actualIndex+1]; + } + if (currentState !== "") { this.bsTabs.activateTab(currentState); n.PageTitle.setSubTitle(this.bsTabs.getTabName(currentState)); @@ -151,6 +180,53 @@ var QfqNS = QfqNS || {}; } var currentTabId = obj.target.getCurrentTab(); n.Log.debug('Saving state: ' + currentTabId); + + // Implementation save current state in session storage + var storedFormInfos = []; + + if(sessionStorage.getItem("formInfos") !== null){ + storedFormInfos = JSON.parse(sessionStorage.getItem("formInfos")); + } + + var currentForm = obj.target.currentFormName; + var currentRecordId = obj.target.currentRecordId; + + var actualIndex = -1; + var indexNr = 0; + if(storedFormInfos.length !== 0) { + if(storedFormInfos[0] !== ''){ + storedFormInfos.forEach(function callback(element){ + if(element === currentForm && storedFormInfos[indexNr + 2] === currentRecordId){ + actualIndex = indexNr; + } + indexNr++; + }); + } + } + + // fill sessionStorage, there are 3 ways for filling the sessionStorage: 1.If empty - first time filling, 2.If there is anything - add it to them, 3 + // 1.If array from storage is empty - fill it first time + if(storedFormInfos.length === 0){ + storedFormInfos[0] = currentForm; + storedFormInfos[1] = currentTabId; + storedFormInfos[2] = currentRecordId; + + // 2.If there is anything in storage but not the actual opened forms - add this new information to the existing array + }else if(actualIndex === -1) { + storedFormInfos[indexNr] = currentForm; + storedFormInfos[indexNr + 1] = currentTabId; + storedFormInfos[indexNr + 2] = currentRecordId; + + // 3.If actual openend form is included in sessionStorage - only change the array values of the existing informations + }else{ + storedFormInfos[actualIndex] = currentForm; + storedFormInfos[actualIndex + 1] = currentTabId; + storedFormInfos[actualIndex + 2] = currentRecordId; + } + + // Set sessionStorage with customized array + sessionStorage.setItem("formInfos" , JSON.stringify(storedFormInfos)); + n.PageTitle.setSubTitle(obj.target.getTabName(currentTabId)); this.settings.pageState.setPageState(currentTabId, n.PageTitle.get()); }; diff --git a/javascript/src/TypeAhead.js b/javascript/src/TypeAhead.js index c7a391f20db7b85ac5f920918daf6999cbbfca77..a01db3ba8b7680074647bfd2803084c782ac60e0 100644 --- a/javascript/src/TypeAhead.js +++ b/javascript/src/TypeAhead.js @@ -393,4 +393,16 @@ var QfqNS = QfqNS || {}; } $element.typeahead('val', results[0].value); }; -})(QfqNS); \ No newline at end of file +})(QfqNS); + +// fix for safari to make the right input field clickable. Safari doesn't get the right sequence of z-index from the two typeahead input fields which are overlaid. +// z-index can not be set in qfq because the input field is generated by typeahead.bundle.min.js, changes are needed to do at the end of DOM after everything is loaded. +$(window).on("load",function() { + $(document).ready(function(){ + $(".tt-input").each(function(){ + if($(this).css("z-index") !== "auto"){ + $(this).prev().css("z-index",1); + } + }); + }); +}); \ No newline at end of file diff --git a/less/qfq-bs.css.less b/less/qfq-bs.css.less index 42c062faa5050d6dda6e1b229398eb9f0876713a..8b68d78db99f420a71dd9bdd644151d568ba92c1 100644 --- a/less/qfq-bs.css.less +++ b/less/qfq-bs.css.less @@ -1345,3 +1345,14 @@ thead.qfq-sticky td { .qfq-badge-inverse:hover { background-color: #1a1a1a; } + +@font-face { + font-family: 'password'; + font-style: normal; + font-weight: 400; + src: url(https://jsbin-user-assets.s3.amazonaws.com/rafaelcastrocouto/password.ttf); +} + +input.qfq-password { + font-family: 'password'; +} \ No newline at end of file