Skip to content

Commit 134d0f9

Browse files
[BUGFIX] Do not delete records via AJAX anymore
Deleting records in the record list is done via AJAX since TYPO3 v7. Since then, there have been major flaws that cannot get fixed with the current state of the record list: * if dependent records shown in the same view get deleted as well, the record stays in place * if a page translation is deleted, all localizations remain in place until a reload * if a page translation is deleted, the localization dropdown to create a new translation is not updated To fix those issues, the record list and its involved sub-components need a major overhaul. Therefore, deleting records via AJAX is not possible anymore, the full record list module gets reloaded after triggering a delete request. Resolves: #106288 Releases: main, 13.4 Change-Id: I45003605f32c80018087a88c8ac67eb36622a38a Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/88444 Tested-by: core-ci <typo3@b13.com> Reviewed-by: Benjamin Franzke <ben@bnf.dev> Tested-by: Christian Kuhn <lolli@schwarzbu.ch> Tested-by: Andreas Kienast <akienast@scripting-base.de> Tested-by: Benjamin Franzke <ben@bnf.dev> Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch> Reviewed-by: Andreas Kienast <akienast@scripting-base.de>
1 parent 45a9f72 commit 134d0f9

File tree

5 files changed

+17
-132
lines changed

5 files changed

+17
-132
lines changed

Build/Sources/Sass/component/_recordlist.scss

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,6 @@
6868
&.record-pulse {
6969
animation: record-pulse 1s ease-out 0s 1 normal none;
7070
}
71-
72-
&.record-deleted {
73-
opacity: 0 !important;
74-
}
7571
}
7672
}
7773

Build/Sources/TypeScript/backend/recordlist.ts

Lines changed: 0 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@ import { MultiRecordSelectionSelectors } from '@typo3/backend/multi-record-selec
2020
import { selector } from '@typo3/core/literals';
2121
import ResponseInterface from '@typo3/backend/ajax-data-handler/response-interface';
2222
import AjaxDataHandler from '@typo3/backend/ajax-data-handler';
23-
import Modal from '@typo3/backend/modal';
24-
import { SeverityEnum } from '@typo3/backend/enum/severity';
2523

2624
interface IconIdentifier {
2725
collapse: string;
@@ -36,12 +34,6 @@ interface RecordlistIdentifier {
3634
editMultiple: string;
3735
icons: IconIdentifier;
3836
}
39-
interface DataHandlerEventPayload {
40-
action: string;
41-
component: string;
42-
table: string;
43-
uid: number;
44-
}
4537
interface EditRecordsConfiguration extends ActionConfiguration {
4638
tableName: string;
4739
columnsOnly: Array<string>;
@@ -72,11 +64,9 @@ class Recordlist {
7264
new RegularEvent('click', this.onEditMultiple).delegateTo(document, this.identifier.editMultiple);
7365
new RegularEvent('click', this.disableButton).delegateTo(document, this.identifier.localize);
7466
new RegularEvent('click', this.toggleVisibility).delegateTo(document, this.identifier.hide);
75-
new RegularEvent('click', this.deleteRecord).delegateTo(document, this.identifier.delete);
7667
DocumentService.ready().then((): void => {
7768
this.registerPaginationEvents();
7869
});
79-
new RegularEvent('typo3:datahandler:process', this.handleDataHandlerResult.bind(this)).bindTo(document);
8070

8171
// multi record selection events
8272
new RegularEvent('multiRecordSelection:action:edit', this.onEditMultiple).bindTo(document);
@@ -296,117 +286,6 @@ class Recordlist {
296286
});
297287
};
298288

299-
private readonly deleteRecord = (event: Event, target: HTMLButtonElement): void => {
300-
event.preventDefault();
301-
302-
const modal = Modal.confirm(target.dataset.title, target.dataset.message, SeverityEnum.warning, [
303-
{
304-
text: target.dataset.buttonCloseText || TYPO3.lang['button.cancel'] || 'Cancel',
305-
active: true,
306-
btnClass: 'btn-default',
307-
name: 'cancel',
308-
},
309-
{
310-
text: target.dataset.buttonOkText || TYPO3.lang['button.delete'] || 'Delete',
311-
btnClass: 'btn-warning',
312-
name: 'delete',
313-
},
314-
]);
315-
modal.addEventListener('button.clicked', (e: Event): void => {
316-
if ((e.target as HTMLInputElement).getAttribute('name') === 'cancel') {
317-
modal.hideModal();
318-
} else if ((e.target as HTMLInputElement).getAttribute('name') === 'delete') {
319-
modal.hideModal();
320-
target.disabled = true;
321-
322-
const buttonIconElement = target.querySelector('.t3js-icon');
323-
Icons.getIcon('spinner-circle', Icons.sizes.small).then((icon: string): void => {
324-
buttonIconElement.replaceWith(document.createRange().createContextualFragment(icon));
325-
});
326-
327-
const tableElement = target.closest('table[data-table]') as HTMLTableElement;
328-
const table = tableElement.dataset.table === 'pages_translated' ? 'pages' : tableElement.dataset.table;
329-
const rowElement = target.closest('tr[data-uid]') as HTMLTableRowElement;
330-
const uid = parseInt(rowElement.dataset.uid, 10);
331-
332-
// Use AjaxDataHandler to delete record. This dispatches the event `typo3:datahandler:process`.
333-
const eventData = { component: 'datahandler', action: 'delete', table, uid };
334-
AjaxDataHandler.process({
335-
cmd: {
336-
[table]: {
337-
[uid]: {
338-
delete: true
339-
}
340-
}
341-
}
342-
}, eventData).then((result: ResponseInterface): void => {
343-
if (result.hasErrors) {
344-
// @todo: the controller should not send a positive status if there are errors...
345-
target.disabled = false;
346-
347-
// revert to the old class
348-
Icons.getIcon('actions-edit-delete', Icons.sizes.small).then((icon: string): void => {
349-
const buttonIconElement = target.querySelector('.t3js-icon');
350-
buttonIconElement.replaceWith(document.createRange().createContextualFragment(icon));
351-
})
352-
}
353-
}).catch((): void => {
354-
target.disabled = false;
355-
356-
// revert to the old class
357-
Icons.getIcon('actions-edit-delete', Icons.sizes.small).then((icon: string): void => {
358-
const buttonIconElement = target.querySelector('.t3js-icon');
359-
buttonIconElement.replaceWith(document.createRange().createContextualFragment(icon));
360-
})
361-
});
362-
}
363-
});
364-
};
365-
366-
private handleDataHandlerResult(e: CustomEvent): void {
367-
const payload = e.detail.payload;
368-
if (payload.hasErrors) {
369-
return;
370-
}
371-
372-
if (payload.action === 'delete') {
373-
this.deleteRow(payload);
374-
}
375-
}
376-
377-
private readonly deleteRow = (payload: DataHandlerEventPayload): void => {
378-
const tableElement = document.querySelector(`table[data-table="${payload.table}"]`) as HTMLTableElement;
379-
const panel = tableElement.closest('.recordlist') as HTMLElement;
380-
const panelHeading = panel.querySelector('.recordlist-heading') as HTMLElement;
381-
const rowElement = tableElement.querySelector(`tr[data-uid="${payload.uid}"]`) as HTMLElement;
382-
const translatedRowElements = tableElement.querySelectorAll<HTMLElement>(`[data-l10nparent="${payload.uid}"]`);
383-
384-
translatedRowElements.forEach((translatedRowElement: HTMLTableRowElement): void => {
385-
new RegularEvent('transitionend', (): void => {
386-
translatedRowElement.remove();
387-
}).bindTo(translatedRowElement);
388-
translatedRowElement.classList.add('record-deleted');
389-
});
390-
391-
new RegularEvent('transitionend', (): void => {
392-
rowElement.remove();
393-
394-
if (tableElement.querySelectorAll('tbody tr').length === 0) {
395-
panel.remove();
396-
}
397-
}).bindTo(rowElement);
398-
rowElement.classList.add('record-deleted');
399-
400-
if (rowElement.dataset.l10nparent === '0' || rowElement.dataset.l10nparent === '') {
401-
const count = parseInt(panelHeading.querySelector('.t3js-table-total-items').textContent, 10);
402-
panelHeading.querySelector('.t3js-table-total-items').textContent = (count - 1).toString();
403-
}
404-
405-
if (payload.table === 'pages') {
406-
top.document.dispatchEvent(new CustomEvent('typo3:pagetree:refresh'));
407-
}
408-
};
409-
410289
private readonly registerPaginationEvents = (): void => {
411290
document.querySelectorAll('.t3js-recordlist-paging').forEach((trigger: HTMLInputElement) => {
412291
trigger.addEventListener('keyup', (e: KeyboardEvent) => {

typo3/sysext/backend/Classes/RecordList/DatabaseRecordList.php

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -694,7 +694,7 @@ public function getTable($table)
694694
// Header line is drawn
695695
$theData = [];
696696
if ($this->disableSingleTableView) {
697-
$theData[$titleCol] = $tableTitle . ' (<span class="t3js-table-total-items">' . $totalItems . '</span>)';
697+
$theData[$titleCol] = $tableTitle . ' (<span>' . $totalItems . '</span>)';
698698
} else {
699699
$icon = $this->table // @todo separate table header from contract/expand link
700700
? $this->iconFactory
@@ -705,7 +705,7 @@ public function getTable($table)
705705
->getIcon('actions-view-table-expand', IconSize::SMALL)
706706
->setTitle($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:expandView'))
707707
->render();
708-
$theData[$titleCol] = $this->linkWrapTable($table, $tableTitle . ' (<span class="t3js-table-total-items">' . $totalItems . '</span>) ' . $icon);
708+
$theData[$titleCol] = $this->linkWrapTable($table, $tableTitle . ' (<span>' . $totalItems . '</span>) ' . $icon);
709709
}
710710
$tableActions = '';
711711
$tableHeader = $theData[$titleCol];
@@ -1857,16 +1857,27 @@ public function makeControl($table, $row)
18571857

18581858
$deleteActionAttributes = GeneralUtility::implodeAttributes([
18591859
'type' => 'button',
1860-
'class' => 'btn btn-default t3js-record-delete',
1860+
'class' => 'btn btn-default t3js-modal-trigger',
18611861
'title' => $linkTitle,
1862+
'data-severity' => 'warning',
18621863
'aria-label' => $linkTitle,
18631864
'aria-haspopup' => 'dialog',
18641865
'data-button-ok-text' => $linkTitle,
18651866
'data-button-close-text' => $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:cancel'),
1866-
'data-message' => $warningText,
1867+
'data-bs-content' => $warningText,
1868+
'data-uri' => (string)$this->uriBuilder->buildUriFromRoute('tce_db', [
1869+
'cmd' => [
1870+
$table => [
1871+
$row['uid'] => [
1872+
'delete' => true,
1873+
],
1874+
],
1875+
],
1876+
'redirect' => $this->listURL(),
1877+
]),
18671878
'data-title' => $titleText,
18681879
], true, true);
1869-
$deleteAction = '<button ' . $deleteActionAttributes . '>' . $icon . '</button>';
1880+
$deleteAction = '<button ' . $deleteActionAttributes . '>' . $icon . '</a>';
18701881
} else {
18711882
$deleteAction = $this->spaceIcon;
18721883
}

typo3/sysext/backend/Resources/Public/Css/backend.css

Lines changed: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)