diff --git a/Build/Sources/TypeScript/backend/ajax-data-handler.ts b/Build/Sources/TypeScript/backend/ajax-data-handler.ts
index e47b38f100b7..2614ac620040 100644
--- a/Build/Sources/TypeScript/backend/ajax-data-handler.ts
+++ b/Build/Sources/TypeScript/backend/ajax-data-handler.ts
@@ -24,7 +24,7 @@ import Notification from './notification';
import RegularEvent from '@typo3/core/event/regular-event';
enum Identifiers {
- hide = '.t3js-record-hide',
+ hide = 'button[data-datahandler-action="visibility"]',
delete = '.t3js-record-delete',
icon = '.t3js-icon',
}
@@ -106,23 +106,9 @@ class AjaxDataHandler {
// @todo: Many extensions rely on this behavior but it's misplaced in AjaxDataHandler. Move into recordlist.ts and deprecate in v11.
private initialize(): void {
// HIDE/UNHIDE: click events for all action icons to hide/unhide
- new RegularEvent('click', (e: Event, anchorElement: HTMLElement): void => {
+ new RegularEvent('click', (e: Event, element: HTMLButtonElement): void => {
e.preventDefault();
-
- const iconElement = anchorElement.querySelector(Identifiers.icon);
- const rowElement = anchorElement.closest('tr[data-uid]');
- const params = anchorElement.dataset.params;
-
- // add a spinner
- this._showSpinnerIcon(iconElement);
-
- // make the AJAX call to toggle the visibility
- this.process(params).then((result: ResponseInterface): void => {
- if (!result.hasErrors) {
- // adjust overlay icon
- this.toggleRow(rowElement);
- }
- });
+ this.handleVisibilityToggle(element);
}).delegateTo(document, Identifiers.hide);
// DELETE: click events for all action icons to delete
@@ -153,55 +139,80 @@ class AjaxDataHandler {
}).delegateTo(document, Identifiers.delete);
}
- /**
- * Toggle row visibility after record has been changed
- */
- private toggleRow(rowElement: Element): void {
- const anchorElement = rowElement.querySelector(Identifiers.hide) as HTMLElement;
- const table = (anchorElement.closest('table[data-table]') as HTMLTableElement).dataset.table;
- const params = anchorElement.dataset.params;
- let nextParams;
- let nextState;
- let iconName;
-
- if (anchorElement.dataset.state === 'hidden') {
- nextState = 'visible';
- nextParams = params.replace('=0', '=1');
- iconName = 'actions-edit-hide';
- } else {
- nextState = 'hidden';
- nextParams = params.replace('=1', '=0');
- iconName = 'actions-edit-unhide';
- }
- anchorElement.dataset.state = nextState;
- anchorElement.dataset.params = nextParams;
+ private handleVisibilityToggle(element: HTMLButtonElement): void
+ {
+ const rowElement = element.closest('tr[data-uid]');
- const iconElement = anchorElement.querySelector(Identifiers.icon);
- Icons.getIcon(iconName, Icons.sizes.small).then((icon: string): void => {
- iconElement.replaceWith(document.createRange().createContextualFragment(icon));
- });
+ // Show spinner
+ const iconElement = element.querySelector(Identifiers.icon);
+ this._showSpinnerIcon(iconElement);
- // Set overlay for the record icon
- const recordIcon = rowElement.querySelector('.col-icon ' + Identifiers.icon);
- if (nextState === 'hidden') {
- Icons.getIcon('miscellaneous-placeholder', Icons.sizes.small, 'overlay-hidden').then((icon: string): void => {
- const iconFragment = document.createRange().createContextualFragment(icon);
- recordIcon.append(iconFragment.querySelector('.icon-overlay'));
- });
- } else {
- recordIcon.querySelector('.icon-overlay').remove();
- }
+ // Get Settings from element
+ const settings = {
+ table: element.dataset.datahandlerTable,
+ uid: element.dataset.datahandlerUid,
+ field: element.dataset.datahandlerField,
+ visible: (element.dataset.datahandlerStatus === 'visible')
+ };
+
+ const params = {
+ data: {
+ [settings.table]: {
+ [settings.uid]: {
+ [settings.field]: settings.visible
+ ? element.dataset.datahandlerHiddenValue
+ : element.dataset.datahandlerVisibleValue
+ }
+ }
+ }
+ };
- const animationEvent = new RegularEvent('animationend', (): void => {
- rowElement.classList.remove('record-pulse');
- animationEvent.release();
- });
- animationEvent.bindTo(rowElement);
- rowElement.classList.add('record-pulse');
+ // Submit Data
+ this.process(params).then((result: ResponseInterface): void => {
+ if (!result.hasErrors) {
+ // Inverse current state
+ settings.visible = !(settings.visible);
+ element.setAttribute('data-datahandler-status', settings.visible ? 'visible' : 'hidden');
+
+ const elementLabel = settings.visible
+ ? element.dataset.datahandlerVisibleLabel
+ : element.dataset.datahandlerHiddenLabel;
+ element.setAttribute('title', elementLabel);
+
+ const elementIconIdentifier = settings.visible
+ ? element.dataset.datahandlerVisibleIcon
+ : element.dataset.datahandlerHiddenIcon;
+ const iconElement = element.querySelector(Identifiers.icon);
+ Icons.getIcon(elementIconIdentifier, Icons.sizes.small).then((icon: string): void => {
+ iconElement.replaceWith(document.createRange().createContextualFragment(icon));
+ });
- if (table === 'pages') {
- AjaxDataHandler.refreshPageTree();
- }
+ // Set overlay for the record icon
+ const recordIcon = rowElement.querySelector('.col-icon ' + Identifiers.icon);
+ if (settings.visible) {
+ recordIcon.querySelector('.icon-overlay').remove();
+ } else {
+
+ Icons.getIcon('miscellaneous-placeholder', Icons.sizes.small, 'overlay-hidden').then((icon: string): void => {
+ const iconFragment = document.createRange().createContextualFragment(icon);
+ recordIcon.append(iconFragment.querySelector('.icon-overlay'));
+ });
+ }
+
+ // Animate row
+ const animationEvent = new RegularEvent('animationend', (): void => {
+ rowElement.classList.remove('record-pulse');
+ animationEvent.release();
+ });
+ animationEvent.bindTo(rowElement);
+ rowElement.classList.add('record-pulse');
+
+ // Refresh Pagetree
+ if (settings.table === 'pages') {
+ AjaxDataHandler.refreshPageTree();
+ }
+ }
+ });
}
/**
diff --git a/typo3/sysext/backend/Classes/RecordList/DatabaseRecordList.php b/typo3/sysext/backend/Classes/RecordList/DatabaseRecordList.php
index 5d5285ccd952..d1892e2b0de1 100644
--- a/typo3/sysext/backend/Classes/RecordList/DatabaseRecordList.php
+++ b/typo3/sysext/backend/Classes/RecordList/DatabaseRecordList.php
@@ -1531,29 +1531,43 @@ public function makeControl($table, $row)
if (!$permsEdit || $isDeletePlaceHolder || $this->isRecordCurrentBackendUser($table, $row)) {
$hideAction = $this->spaceIcon;
} else {
- $hideTitle = htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:hide' . ($table === 'pages' ? 'Page' : '')));
- $unhideTitle = htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:unHide' . ($table === 'pages' ? 'Page' : '')));
+ $visibleTitle = $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:hide' . ($table === 'pages' ? 'Page' : ''));
+ $visibleIcon = 'actions-edit-hide';
+ $visibleValue = '0';
+ $hiddenTitle = $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:unHide' . ($table === 'pages' ? 'Page' : ''));
+ $hiddenIcon = 'actions-edit-unhide';
+ $hiddenValue = '1';
if ($row[$hiddenField] ?? false) {
- $params = 'data[' . $table . '][' . $rowUid . '][' . $hiddenField . ']=0';
- $hideAction = '';
+ $titleLabel = $hiddenTitle;
+ $iconIdentifier = $hiddenIcon;
+ $status = 'hidden';
} else {
- $params = 'data[' . $table . '][' . $rowUid . '][' . $hiddenField . ']=1';
- $hideAction = '';
+ $titleLabel = $visibleTitle;
+ $iconIdentifier = $visibleIcon;
+ $status = 'visible';
}
+ $attributesString = GeneralUtility::implodeAttributes(
+ [
+ 'class' => 'btn btn-default',
+ 'type' => 'button',
+ 'title' => $titleLabel,
+ 'data-datahandler-action' => 'visibility',
+ 'data-datahandler-table' => $table,
+ 'data-datahandler-uid' => $rowUid,
+ 'data-datahandler-field' => $hiddenField,
+ 'data-datahandler-status' => $status,
+ 'data-datahandler-visible-label' => $visibleTitle,
+ 'data-datahandler-visible-value' => $visibleValue,
+ 'data-datahandler-visible-icon' => $visibleIcon,
+ 'data-datahandler-hidden-label' => $hiddenTitle,
+ 'data-datahandler-hidden-value' => $hiddenValue,
+ 'data-datahandler-hidden-icon' => $hiddenIcon,
+ ],
+ true
+ );
+ $hideAction = '';
}
$this->addActionToCellGroup($cells, $hideAction, 'hide');
}
diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/ajax-data-handler.js b/typo3/sysext/backend/Resources/Public/JavaScript/ajax-data-handler.js
index 9cea0d2ecf95..38493eb72b36 100644
--- a/typo3/sysext/backend/Resources/Public/JavaScript/ajax-data-handler.js
+++ b/typo3/sysext/backend/Resources/Public/JavaScript/ajax-data-handler.js
@@ -10,4 +10,4 @@
*
* The TYPO3 project - inspiring people to share!
*/
-import{BroadcastMessage}from"@typo3/backend/broadcast-message.js";import AjaxRequest from"@typo3/core/ajax/ajax-request.js";import DocumentService from"@typo3/core/document-service.js";import{SeverityEnum}from"@typo3/backend/enum/severity.js";import BroadcastService from"@typo3/backend/broadcast-service.js";import Icons from"@typo3/backend/icons.js";import Modal from"@typo3/backend/modal.js";import Notification from"@typo3/backend/notification.js";import RegularEvent from"@typo3/core/event/regular-event.js";var Identifiers;!function(e){e.hide=".t3js-record-hide",e.delete=".t3js-record-delete",e.icon=".t3js-icon"}(Identifiers||(Identifiers={}));class AjaxDataHandler{constructor(){DocumentService.ready().then((()=>{this.initialize()}))}static refreshPageTree(){top.document.dispatchEvent(new CustomEvent("typo3:pagetree:refresh"))}static call(e){return new AjaxRequest(TYPO3.settings.ajaxUrls.record_process).withQueryArguments(e).get().then((async e=>await e.resolve()))}process(e,t){return AjaxDataHandler.call(e).then((e=>{if(e.hasErrors&&this.handleErrors(e),t){const a={...t,hasErrors:e.hasErrors},r=new BroadcastMessage("datahandler","process",a);BroadcastService.post(r);const n=new CustomEvent("typo3:datahandler:process",{detail:{payload:a}});document.dispatchEvent(n)}return e}))}initialize(){new RegularEvent("click",((e,t)=>{e.preventDefault();const a=t.querySelector(Identifiers.icon),r=t.closest("tr[data-uid]"),n=t.dataset.params;this._showSpinnerIcon(a),this.process(n).then((e=>{e.hasErrors||this.toggleRow(r)}))})).delegateTo(document,Identifiers.hide),new RegularEvent("click",((e,t)=>{e.preventDefault();const a=Modal.confirm(t.dataset.title,t.dataset.message,SeverityEnum.warning,[{text:t.dataset.buttonCloseText||TYPO3.lang["button.cancel"]||"Cancel",active:!0,btnClass:"btn-default",name:"cancel"},{text:t.dataset.buttonOkText||TYPO3.lang["button.delete"]||"Delete",btnClass:"btn-warning",name:"delete"}]);a.addEventListener("button.clicked",(e=>{"cancel"===e.target.getAttribute("name")?a.hideModal():"delete"===e.target.getAttribute("name")&&(a.hideModal(),this.deleteRecord(t))}))})).delegateTo(document,Identifiers.delete)}toggleRow(e){const t=e.querySelector(Identifiers.hide),a=t.closest("table[data-table]").dataset.table,r=t.dataset.params;let n,o,s;"hidden"===t.dataset.state?(o="visible",n=r.replace("=0","=1"),s="actions-edit-hide"):(o="hidden",n=r.replace("=1","=0"),s="actions-edit-unhide"),t.dataset.state=o,t.dataset.params=n;const c=t.querySelector(Identifiers.icon);Icons.getIcon(s,Icons.sizes.small).then((e=>{c.replaceWith(document.createRange().createContextualFragment(e))}));const i=e.querySelector(".col-icon "+Identifiers.icon);"hidden"===o?Icons.getIcon("miscellaneous-placeholder",Icons.sizes.small,"overlay-hidden").then((e=>{const t=document.createRange().createContextualFragment(e);i.append(t.querySelector(".icon-overlay"))})):i.querySelector(".icon-overlay").remove();const d=new RegularEvent("animationend",(()=>{e.classList.remove("record-pulse"),d.release()}));d.bindTo(e),e.classList.add("record-pulse"),"pages"===a&&AjaxDataHandler.refreshPageTree()}deleteRecord(e){const t=e.dataset.params;let a=e.querySelector(Identifiers.icon);this._showSpinnerIcon(a);const r=e.closest("table[data-table]"),n=r.dataset.table,o=e.closest("tr[data-uid]"),s=parseInt(o.dataset.uid,10),c={component:"datahandler",action:"delete",table:n,uid:s};this.process(t,c).then((t=>{if(Icons.getIcon("actions-edit-delete",Icons.sizes.small).then((t=>{a=e.querySelector(Identifiers.icon),a.replaceWith(document.createRange().createContextualFragment(t))})),!t.hasErrors){const t=e.closest(".recordlist"),a=t.querySelector(".recordlist-heading-title"),c=r.querySelector('[data-l10nparent="'+s+'"]')?.closest("tr[data-uid]");if(void 0!==c&&(new RegularEvent("transitionend",(()=>{c.remove()})).bindTo(c),c.classList.add("record-deleted")),new RegularEvent("transitionend",(()=>{o.remove(),0===r.querySelectorAll("tbody tr").length&&t.remove()})).bindTo(o),o.classList.add("record-deleted"),"0"===e.dataset.l10parent||""===e.dataset.l10parent){const e=parseInt(a.querySelector(".t3js-table-total-items").textContent,10);a.querySelector(".t3js-table-total-items").textContent=(e-1).toString()}"pages"===n&&AjaxDataHandler.refreshPageTree()}}))}handleErrors(e){for(const t of e.messages)Notification.error(t.title,t.message)}_showSpinnerIcon(e){Icons.getIcon("spinner-circle-dark",Icons.sizes.small).then((t=>{e.replaceWith(document.createRange().createContextualFragment(t))}))}}export default new AjaxDataHandler;
\ No newline at end of file
+import{BroadcastMessage}from"@typo3/backend/broadcast-message.js";import AjaxRequest from"@typo3/core/ajax/ajax-request.js";import DocumentService from"@typo3/core/document-service.js";import{SeverityEnum}from"@typo3/backend/enum/severity.js";import BroadcastService from"@typo3/backend/broadcast-service.js";import Icons from"@typo3/backend/icons.js";import Modal from"@typo3/backend/modal.js";import Notification from"@typo3/backend/notification.js";import RegularEvent from"@typo3/core/event/regular-event.js";var Identifiers;!function(e){e.hide='button[data-datahandler-action="visibility"]',e.delete=".t3js-record-delete",e.icon=".t3js-icon"}(Identifiers||(Identifiers={}));class AjaxDataHandler{constructor(){DocumentService.ready().then((()=>{this.initialize()}))}static refreshPageTree(){top.document.dispatchEvent(new CustomEvent("typo3:pagetree:refresh"))}static call(e){return new AjaxRequest(TYPO3.settings.ajaxUrls.record_process).withQueryArguments(e).get().then((async e=>await e.resolve()))}process(e,t){return AjaxDataHandler.call(e).then((e=>{if(e.hasErrors&&this.handleErrors(e),t){const a={...t,hasErrors:e.hasErrors},r=new BroadcastMessage("datahandler","process",a);BroadcastService.post(r);const n=new CustomEvent("typo3:datahandler:process",{detail:{payload:a}});document.dispatchEvent(n)}return e}))}initialize(){new RegularEvent("click",((e,t)=>{e.preventDefault(),this.handleVisibilityToggle(t)})).delegateTo(document,Identifiers.hide),new RegularEvent("click",((e,t)=>{e.preventDefault();const a=Modal.confirm(t.dataset.title,t.dataset.message,SeverityEnum.warning,[{text:t.dataset.buttonCloseText||TYPO3.lang["button.cancel"]||"Cancel",active:!0,btnClass:"btn-default",name:"cancel"},{text:t.dataset.buttonOkText||TYPO3.lang["button.delete"]||"Delete",btnClass:"btn-warning",name:"delete"}]);a.addEventListener("button.clicked",(e=>{"cancel"===e.target.getAttribute("name")?a.hideModal():"delete"===e.target.getAttribute("name")&&(a.hideModal(),this.deleteRecord(t))}))})).delegateTo(document,Identifiers.delete)}handleVisibilityToggle(e){const t=e.closest("tr[data-uid]"),a=e.querySelector(Identifiers.icon);this._showSpinnerIcon(a);const r={table:e.dataset.datahandlerTable,uid:e.dataset.datahandlerUid,field:e.dataset.datahandlerField,visible:"visible"===e.dataset.datahandlerStatus},n={data:{[r.table]:{[r.uid]:{[r.field]:r.visible?e.dataset.datahandlerHiddenValue:e.dataset.datahandlerVisibleValue}}}};this.process(n).then((a=>{if(!a.hasErrors){r.visible=!r.visible,e.setAttribute("data-datahandler-status",r.visible?"visible":"hidden");const a=r.visible?e.dataset.datahandlerVisibleLabel:e.dataset.datahandlerHiddenLabel;e.setAttribute("title",a);const n=r.visible?e.dataset.datahandlerVisibleIcon:e.dataset.datahandlerHiddenIcon,s=e.querySelector(Identifiers.icon);Icons.getIcon(n,Icons.sizes.small).then((e=>{s.replaceWith(document.createRange().createContextualFragment(e))}));const o=t.querySelector(".col-icon "+Identifiers.icon);r.visible?o.querySelector(".icon-overlay").remove():Icons.getIcon("miscellaneous-placeholder",Icons.sizes.small,"overlay-hidden").then((e=>{const t=document.createRange().createContextualFragment(e);o.append(t.querySelector(".icon-overlay"))}));const i=new RegularEvent("animationend",(()=>{t.classList.remove("record-pulse"),i.release()}));i.bindTo(t),t.classList.add("record-pulse"),"pages"===r.table&&AjaxDataHandler.refreshPageTree()}}))}deleteRecord(e){const t=e.dataset.params;let a=e.querySelector(Identifiers.icon);this._showSpinnerIcon(a);const r=e.closest("table[data-table]"),n=r.dataset.table,s=e.closest("tr[data-uid]"),o=parseInt(s.dataset.uid,10),i={component:"datahandler",action:"delete",table:n,uid:o};this.process(t,i).then((t=>{if(Icons.getIcon("actions-edit-delete",Icons.sizes.small).then((t=>{a=e.querySelector(Identifiers.icon),a.replaceWith(document.createRange().createContextualFragment(t))})),!t.hasErrors){const t=e.closest(".recordlist"),a=t.querySelector(".recordlist-heading-title"),i=r.querySelector('[data-l10nparent="'+o+'"]')?.closest("tr[data-uid]");if(void 0!==i&&(new RegularEvent("transitionend",(()=>{i.remove()})).bindTo(i),i.classList.add("record-deleted")),new RegularEvent("transitionend",(()=>{s.remove(),0===r.querySelectorAll("tbody tr").length&&t.remove()})).bindTo(s),s.classList.add("record-deleted"),"0"===e.dataset.l10parent||""===e.dataset.l10parent){const e=parseInt(a.querySelector(".t3js-table-total-items").textContent,10);a.querySelector(".t3js-table-total-items").textContent=(e-1).toString()}"pages"===n&&AjaxDataHandler.refreshPageTree()}}))}handleErrors(e){for(const t of e.messages)Notification.error(t.title,t.message)}_showSpinnerIcon(e){Icons.getIcon("spinner-circle-dark",Icons.sizes.small).then((t=>{e.replaceWith(document.createRange().createContextualFragment(t))}))}}export default new AjaxDataHandler;
\ No newline at end of file