Skip to content
Snippets Groups Projects
Commit fa39b4d5 authored by Oliver Bartsch's avatar Oliver Bartsch Committed by Benni Mack
Browse files

[TASK] Use multi record selection in recycler module

The multi record selection component is now also used
in the recycler module. This increases consistency, as
custom checkbox logic could be removed.

Also the recyclers' JavaScript implementation of the
selection actions is simplified. To interact with the
multi record selection component, some events are used.
This also means, the functionality stays the same, while
most of it is now handled by the Multi record selection
component.

Resolves: #94926
Releases: master
Change-Id: I57d91d070bfa75c01d4cb87dfd0c5576d111dcfa
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/70678


Tested-by: default avatarcore-ci <typo3@b13.com>
Tested-by: default avatarJochen <rothjochen@gmail.com>
Tested-by: default avatarSimon Schaufelberger <simonschaufi+typo3@gmail.com>
Tested-by: default avatarBenni Mack <benni@typo3.org>
Reviewed-by: default avatarJochen <rothjochen@gmail.com>
Reviewed-by: default avatarSimon Schaufelberger <simonschaufi+typo3@gmail.com>
Reviewed-by: default avatarBenni Mack <benni@typo3.org>
parent 162e6d47
Branches
Tags
No related merge requests found
...@@ -18,6 +18,7 @@ import DeferredAction = require('TYPO3/CMS/Backend/ActionButton/DeferredAction') ...@@ -18,6 +18,7 @@ import DeferredAction = require('TYPO3/CMS/Backend/ActionButton/DeferredAction')
import Modal = require('TYPO3/CMS/Backend/Modal'); import Modal = require('TYPO3/CMS/Backend/Modal');
import Notification = require('TYPO3/CMS/Backend/Notification'); import Notification = require('TYPO3/CMS/Backend/Notification');
import Severity = require('TYPO3/CMS/Backend/Severity'); import Severity = require('TYPO3/CMS/Backend/Severity');
import RegularEvent from 'TYPO3/CMS/Core/Event/RegularEvent';
enum RecyclerIdentifiers { enum RecyclerIdentifiers {
searchForm = '#recycler-form', searchForm = '#recycler-form',
...@@ -28,9 +29,10 @@ enum RecyclerIdentifiers { ...@@ -28,9 +29,10 @@ enum RecyclerIdentifiers {
recyclerTable = '#itemsInRecycler', recyclerTable = '#itemsInRecycler',
paginator = '#recycler-index nav', paginator = '#recycler-index nav',
reloadAction = 'a[data-action=reload]', reloadAction = 'a[data-action=reload]',
massUndo = 'button[data-action=massundo]', undo = 'a[data-action=undo]',
massDelete = 'button[data-action=massdelete]', delete = 'a[data-action=delete]',
toggleAll = '.t3js-toggle-all', massUndo = 'button[data-multi-record-selection-action=massundo]',
massDelete = 'button[data-multi-record-selection-action=massdelete]',
} }
/** /**
...@@ -46,7 +48,6 @@ class Recycler { ...@@ -46,7 +48,6 @@ class Recycler {
itemsPerPage: TYPO3.settings.Recycler.pagingSize, itemsPerPage: TYPO3.settings.Recycler.pagingSize,
}; };
public markedRecordsForMassAction: Array<string> = []; public markedRecordsForMassAction: Array<string> = [];
public allToggled: boolean = false;
/** /**
* Reloads the page tree * Reloads the page tree
...@@ -77,7 +78,6 @@ class Recycler { ...@@ -77,7 +78,6 @@ class Recycler {
$reloadAction: $(RecyclerIdentifiers.reloadAction), $reloadAction: $(RecyclerIdentifiers.reloadAction),
$massUndo: $(RecyclerIdentifiers.massUndo), $massUndo: $(RecyclerIdentifiers.massUndo),
$massDelete: $(RecyclerIdentifiers.massDelete), $massDelete: $(RecyclerIdentifiers.massDelete),
$toggleAll: $(RecyclerIdentifiers.toggleAll),
}; };
} }
...@@ -127,10 +127,10 @@ class Recycler { ...@@ -127,10 +127,10 @@ class Recycler {
}); });
// clicking "recover" in single row // clicking "recover" in single row
this.elements.$recyclerTable.on('click', '[data-action=undo]', this.undoRecord); new RegularEvent('click', this.undoRecord).delegateTo(document, RecyclerIdentifiers.undo);
// clicking "delete" in single row // clicking "delete" in single row
this.elements.$recyclerTable.on('click', '[data-action=delete]', this.deleteRecord); new RegularEvent('click', this.deleteRecord).delegateTo(document, RecyclerIdentifiers.delete);
this.elements.$reloadAction.on('click', (e: JQueryEventObject): void => { this.elements.$reloadAction.on('click', (e: JQueryEventObject): void => {
e.preventDefault(); e.preventDefault();
...@@ -199,14 +199,9 @@ class Recycler { ...@@ -199,14 +199,9 @@ class Recycler {
}); });
// checkboxes in the table // checkboxes in the table
this.elements.$toggleAll.on('click', (): void => { new RegularEvent('checkbox:state:changed', this.handleCheckboxStateChanged).bindTo(document);
this.allToggled = !this.allToggled; new RegularEvent('multiRecordSelection:action:massundo', this.undoRecord).bindTo(document);
$('input[type="checkbox"]').prop('checked', this.allToggled).trigger('change'); new RegularEvent('multiRecordSelection:action:massdelete', this.deleteRecord).bindTo(document);
});
this.elements.$recyclerTable.on('change', 'tr input[type=checkbox]', this.handleCheckboxSelects);
this.elements.$massUndo.on('click', this.undoRecord);
this.elements.$massDelete.on('click', this.deleteRecord);
} }
/** /**
...@@ -230,8 +225,8 @@ class Recycler { ...@@ -230,8 +225,8 @@ class Recycler {
/** /**
* Handles the clicks on checkboxes in the records table * Handles the clicks on checkboxes in the records table
*/ */
private handleCheckboxSelects = (e: JQueryEventObject): void => { private handleCheckboxStateChanged = (e: Event): void => {
const $checkbox = $(e.currentTarget); const $checkbox = $(e.target);
const $tr = $checkbox.parents('tr'); const $tr = $checkbox.parents('tr');
const table = $tr.data('table'); const table = $tr.data('table');
const uid = $tr.data('uid'); const uid = $tr.data('uid');
...@@ -239,29 +234,20 @@ class Recycler { ...@@ -239,29 +234,20 @@ class Recycler {
if ($checkbox.prop('checked')) { if ($checkbox.prop('checked')) {
this.markedRecordsForMassAction.push(record); this.markedRecordsForMassAction.push(record);
$tr.addClass('warning');
} else { } else {
const index = this.markedRecordsForMassAction.indexOf(record); const index = this.markedRecordsForMassAction.indexOf(record);
if (index > -1) { if (index > -1) {
this.markedRecordsForMassAction.splice(index, 1); this.markedRecordsForMassAction.splice(index, 1);
} }
$tr.removeClass('warning');
} }
if (this.markedRecordsForMassAction.length > 0) { if (this.markedRecordsForMassAction.length > 0) {
if (this.elements.$massUndo.hasClass('disabled')) { this.elements.$massUndo.find('span.text').text(
this.elements.$massUndo.removeClass('disabled').removeAttr('disabled'); this.createMessage(TYPO3.lang['button.undoselected'], [this.markedRecordsForMassAction.length])
} );
if (this.elements.$massDelete.hasClass('disabled')) { this.elements.$massDelete.find('span.text').text(
this.elements.$massDelete.removeClass('disabled').removeAttr('disabled'); this.createMessage(TYPO3.lang['button.deleteselected'], [this.markedRecordsForMassAction.length])
} );
const btnTextUndo = this.createMessage(TYPO3.lang['button.undoselected'], [this.markedRecordsForMassAction.length]);
const btnTextDelete = this.createMessage(TYPO3.lang['button.deleteselected'], [this.markedRecordsForMassAction.length]);
this.elements.$massUndo.find('span.text').text(btnTextUndo);
this.elements.$massDelete.find('span.text').text(btnTextDelete);
} else { } else {
this.resetMassActionButtons(); this.resetMassActionButtons();
} }
...@@ -272,10 +258,9 @@ class Recycler { ...@@ -272,10 +258,9 @@ class Recycler {
*/ */
private resetMassActionButtons(): void { private resetMassActionButtons(): void {
this.markedRecordsForMassAction = []; this.markedRecordsForMassAction = [];
this.elements.$massUndo.addClass('disabled').attr('disabled', true);
this.elements.$massUndo.find('span.text').text(TYPO3.lang['button.undo']); this.elements.$massUndo.find('span.text').text(TYPO3.lang['button.undo']);
this.elements.$massDelete.addClass('disabled').attr('disabled', true);
this.elements.$massDelete.find('span.text').text(TYPO3.lang['button.delete']); this.elements.$massDelete.find('span.text').text(TYPO3.lang['button.delete']);
document.dispatchEvent(new Event('multiRecordSelection:actions:hide'));
} }
/** /**
...@@ -350,12 +335,12 @@ class Recycler { ...@@ -350,12 +335,12 @@ class Recycler {
}); });
} }
private deleteRecord = (e: JQueryEventObject): void => { private deleteRecord = (e: Event): void => {
if (TYPO3.settings.Recycler.deleteDisable) { if (TYPO3.settings.Recycler.deleteDisable) {
return; return;
} }
const $tr = $(e.currentTarget).parents('tr'); const $tr = $(e.target).parents('tr');
const isMassDelete = $tr.parent().prop('tagName') !== 'TBODY'; // deleteRecord() was invoked by the mass delete button const isMassDelete = $tr.parent().prop('tagName') !== 'TBODY'; // deleteRecord() was invoked by the mass delete button
let records: Array<string>; let records: Array<string>;
let message: string; let message: string;
...@@ -389,8 +374,8 @@ class Recycler { ...@@ -389,8 +374,8 @@ class Recycler {
]); ]);
} }
private undoRecord = (e: JQueryEventObject): void => { private undoRecord = (e: Event): void => {
const $tr = $(e.currentTarget).parents('tr'); const $tr = $(e.target).parents('tr');
const isMassUndo = $tr.parent().prop('tagName') !== 'TBODY'; // undoRecord() was invoked by the mass delete button const isMassUndo = $tr.parent().prop('tagName') !== 'TBODY'; // undoRecord() was invoked by the mass delete button
let records: Array<string>; let records: Array<string>;
...@@ -501,9 +486,6 @@ class Recycler { ...@@ -501,9 +486,6 @@ class Recycler {
if (reloadPageTree) { if (reloadPageTree) {
Recycler.refreshPageTree(); Recycler.refreshPageTree();
} }
// Reset toggle state
this.allToggled = false;
}); });
}, },
complete: () => { complete: () => {
......
...@@ -156,6 +156,7 @@ class RecyclerModuleController ...@@ -156,6 +156,7 @@ class RecyclerModuleController
{ {
$this->pageRenderer->addInlineSettingArray('Recycler', $this->getJavaScriptConfiguration($request->getAttribute('normalizedParams'))); $this->pageRenderer->addInlineSettingArray('Recycler', $this->getJavaScriptConfiguration($request->getAttribute('normalizedParams')));
$this->pageRenderer->addInlineLanguageLabelFile('EXT:recycler/Resources/Private/Language/locallang.xlf'); $this->pageRenderer->addInlineLanguageLabelFile('EXT:recycler/Resources/Private/Language/locallang.xlf');
$this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/MultiRecordSelection');
if ($this->isAccessibleForCurrentUser) { if ($this->isAccessibleForCurrentUser) {
$this->moduleTemplate->getDocHeaderComponent()->setMetaInformation($this->pageRecord); $this->moduleTemplate->getDocHeaderComponent()->setMetaInformation($this->pageRecord);
} }
......
<html xmlns:be="http://typo3.org/ns/TYPO3/CMS/Backend/ViewHelpers" data-namespace-typo3-fluid="true"> <html xmlns:be="http://typo3.org/ns/TYPO3/CMS/Backend/ViewHelpers" data-namespace-typo3-fluid="true">
<tr data-uid="{record.uid}" data-table="{record.table}" data-recordtitle="{record.title}" data-parent-deleted="{record.isParentDeleted}"> <tr data-uid="{record.uid}" data-table="{record.table}" data-recordtitle="{record.title}" data-parent-deleted="{record.isParentDeleted}">
<td class="nowrap"> <td class="nowrap">
<label class="btn btn-default btn-checkbox"> <span class="form-check form-toggle">
<input type="checkbox"> <input class="form-check-input t3js-multi-record-selection-check" type="checkbox">
<span class="t3-icon fa"></span> </span>
</label>
</td> </td>
<td class="nowrap">{record.tableTitle}</td> <td class="nowrap">{record.tableTitle}</td>
<td class="nowrap"><f:format.raw>{record.icon}</f:format.raw> {record.title}</td> <td class="nowrap"><f:format.raw>{record.icon}</f:format.raw> {record.title}</td>
......
...@@ -27,14 +27,12 @@ ...@@ -27,14 +27,12 @@
</div> </div>
</div> </div>
</form> </form>
<f:render section="buttons" arguments="{allowDelete: allowDelete}"/> <f:render section="multiRecordSelectionActions" arguments="{allowDelete: allowDelete}"/>
<div class="table-fit"> <div class="table-fit mb-0">
<table class="table table-hover" id="itemsInRecycler"> <table class="table table-hover" id="itemsInRecycler">
<thead> <thead>
<tr> <tr>
<th> <th><f:render section="multiRecordSelectionCheckboxActions" /></th>
<button type="button" class="btn btn-default t3js-toggle-all"><core:icon identifier="actions-document-select" /></button>
</th>
<th><f:translate key="table.header.recordtype" /></th> <th><f:translate key="table.header.recordtype" /></th>
<th><f:translate key="table.header.record" /></th> <th><f:translate key="table.header.record" /></th>
<th><f:translate key="table.header.tstamp" /></th> <th><f:translate key="table.header.tstamp" /></th>
...@@ -47,27 +45,61 @@ ...@@ -47,27 +45,61 @@
</tbody> </tbody>
</table> </table>
</div> </div>
<f:render section="buttons" arguments="{allowDelete: allowDelete}"/> <f:render section="multiRecordSelectionActions" arguments="{allowDelete: allowDelete}"/>
<nav> <nav>
</nav> </nav>
</div> </div>
</f:section> </f:section>
<f:section name="buttons"> <f:section name="multiRecordSelectionCheckboxActions">
<div class="form-group"> <div class="btn-group dropdown position-static">
<button class="btn btn-default disabled" data-action="massundo" disabled> <button type="button" class="btn btn-borderless dropdown-toggle" data-bs-target="multi-record-selection-check-actions" data-bs-toggle="dropdown" data-bs-boundary="window" aria-expanded="false">
<core:icon identifier="actions-edit-undo" /> <core:icon identifier="content-special-div" size="small" />
<span class="text">
<f:translate key="button.undo" />
</span>
</button> </button>
<f:if condition="{allowDelete}"> <ul id="multi-record-selection-check-actions" class="dropdown-menu">
<button class="btn btn-default disabled" data-action="massdelete" disabled> <li>
<core:icon identifier="actions-edit-delete" /> <button type="button" class="btn btn-link dropdown-item disabled" data-multi-record-selection-check-action="check-all" title="{f:translate(key: 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.checkAll')}">
<span class="text"> <core:icon identifier="actions-check-square" size="small" /> <f:translate key="LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.checkAll" />
<f:translate key="button.delete" /> </button>
</span> </li>
</button> <li>
</f:if> <button type="button" class="btn btn-link dropdown-item disabled" data-multi-record-selection-check-action="check-none" title="{f:translate(key: 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.uncheckAll')}">
<core:icon identifier="actions-square" size="small" /> <f:translate key="LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.uncheckAll" />
</button>
</li>
<li>
<button type="button" class="btn btn-link dropdown-item" data-multi-record-selection-check-action="toggle" title="{f:translate(key: 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.toggleSelection')}">
<core:icon identifier="actions-document-select" size="small" /> <f:translate key="LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.toggleSelection" />
</button>
</li>
</ul>
</div>
</f:section>
<f:section name="multiRecordSelectionActions">
<div class="multi-record-selection-actions-wrapper">
<div class="row row-cols-auto align-items-center g-2 t3js-multi-record-selection-actions hidden">
<div class="col">
<strong><f:translate key="LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.selection"/></strong>
</div>
<div class="col">
<button class="btn btn-default btn-sm" data-multi-record-selection-action="massundo" data-multi-record-selection-action-config='{"idField": "uid"}'>
<core:icon identifier="actions-edit-undo" />
<span class="text">
<f:translate key="button.undo" />
</span>
</button>
</div>
<f:if condition="{allowDelete}">
<div class="col">
<button class="btn btn-default btn-sm" data-multi-record-selection-action="massdelete" data-multi-record-selection-action-config='{"idField": "uid"}'>
<core:icon identifier="actions-edit-delete" />
<span class="text">
<f:translate key="button.delete" />
</span>
</button>
</div>
</f:if>
</div>
</div> </div>
</f:section> </f:section>
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment