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

[TASK] Allow components to manually change checkbox state

Components, using the multi record selection,
introduced in #94906, usually do not have to
change any checkbox state manually. The multi
record selection component is taking care of it.

However, there might be cases when a consuming
component needs to change checkbox state on its
own. An example is the workspaces module, which
may changes checkbox state of child records when
their parent record got changed. See: #94404.

To prevent the components from overriding each
other, the multi record selection now respects
a special data attribute `manually-changed`.
If set on a checkbox element, the multi record
selection component will not touch this checkbox,
while executing its (mass) actions.

Resolves: #94948
Releases: master
Change-Id: I35b07ca45cab4b96d3eea93ff45426702a1532d7
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/70713


Tested-by: default avatarcore-ci <typo3@b13.com>
Tested-by: default avatarJochen <rothjochen@gmail.com>
Tested-by: default avatarBenni Mack <benni@typo...>
parent b78ab775
Branches
Tags
No related merge requests found
......@@ -61,8 +61,8 @@ class MultiRecordSelection {
}
private static changeCheckboxState(checkbox: HTMLInputElement, check: boolean): void {
if (checkbox.checked === check) {
// Return if state did not change
if (checkbox.checked === check || checkbox.dataset.manuallyChanged) {
// Return in case state did not change or another component has already changed it
return;
}
checkbox.checked = check;
......@@ -146,6 +146,21 @@ class MultiRecordSelection {
});
}
/**
* The manually changed attribute can be set by components, using
* this module while implementing custom logic to change checkbox
* state. To not cancel each others action, all actions in this
* module respect this attribute before changing checkbox state.
* Therefore, this method is called prior to every action in
* this module, which changes checkbox states. Otherwise old
* state would may led to misbehaviour.
*/
private static unsetManuallyChangedAttribute(): void {
MultiRecordSelection.getCheckboxes().forEach((checkbox: HTMLInputElement): void => {
checkbox.removeAttribute('data-manually-changed');
});
}
constructor() {
DocumentService.ready().then((): void => {
MultiRecordSelection.restoreTemporaryState();
......@@ -229,6 +244,11 @@ class MultiRecordSelection {
return;
}
// Unset manually changed attribute so we can be sure, in case this is
// set on a checkbox, while executing the requested action, the checkbox
// was already changed by another component.
MultiRecordSelection.unsetManuallyChangedAttribute();
// Perform requested action
switch (target.dataset.multiRecordSelectionCheckAction) {
case CheckboxActions.checkAll:
......@@ -250,6 +270,9 @@ class MultiRecordSelection {
// Unknown action
Notification.warning('Unknown checkbox action');
}
// To prevent possible side effects we simply clean up and unset the attribute here again
MultiRecordSelection.unsetManuallyChangedAttribute();
}).delegateTo(document, [Selectors.checkboxActionsSelector, Buttons.checkboxActionButton].join(' '));
}
......
......@@ -10,4 +10,4 @@
*
* The TYPO3 project - inspiring people to share!
*/
define(["require","exports","TYPO3/CMS/Backend/Notification","TYPO3/CMS/Core/DocumentService","TYPO3/CMS/Core/Event/RegularEvent"],(function(e,t,c,o,n){"use strict";var i,s,a,l,r;!function(e){e.actionsSelector=".t3js-multi-record-selection-actions",e.checkboxSelector=".t3js-multi-record-selection-check",e.checkboxActionsSelector="#multi-record-selection-check-actions"}(i||(i={})),function(e){e.actionButton="button[data-multi-record-selection-action]",e.checkboxActionButton="button[data-multi-record-selection-check-action]",e.checkboxActionsToggleButton='button[data-bs-target="multi-record-selection-check-actions"]'}(s||(s={})),function(e){e.edit="edit"}(a||(a={})),function(e){e.checkAll="check-all",e.checkNone="check-none",e.toggle="toggle"}(l||(l={})),function(e){e.any="",e.checked=":checked",e.unchecked=":not(:checked)"}(r||(r={}));class d{static getCheckboxes(e=r.any){return document.querySelectorAll(i.checkboxSelector+e)}static changeCheckboxState(e,t){e.checked!==t&&(e.checked=t,e.dispatchEvent(new Event("checkbox:state:changed",{bubbles:!0,cancelable:!1})))}static getReturnUrl(e){return""===e&&(e=top.list_frame.document.location.pathname+top.list_frame.document.location.search),encodeURIComponent(e)}static restoreTemporaryState(){const e=d.getCheckboxes(r.checked);if(!e.length)return;e.forEach(e=>{e.closest("tr").classList.add("success")});const t=document.querySelectorAll(i.actionsSelector);t.length&&t.forEach(e=>e.classList.remove("hidden"))}static toggleActionsState(){const e=document.querySelectorAll(i.actionsSelector);if(!e.length)return;if(!d.getCheckboxes(r.checked).length)return void e.forEach(e=>e.classList.add("hidden"));e.forEach(e=>e.classList.remove("hidden"));const t=document.querySelectorAll([i.actionsSelector,s.actionButton].join(" "));t.length&&t.forEach(e=>{if(!e.dataset.multiRecordSelectionActionConfig)return;const t=JSON.parse(e.dataset.multiRecordSelectionActionConfig);if(!t.idField)return;e.classList.add("disabled");const c=d.getCheckboxes(r.checked);for(let o=0;o<c.length;o++)if(c[o].closest("tr").dataset[t.idField]){e.classList.remove("disabled");break}})}constructor(){o.ready().then(()=>{d.restoreTemporaryState(),this.registerActions(),this.registerActionsEventHandlers(),this.registerCheckboxActions(),this.registerToggleCheckboxActions(),this.registerDispatchCheckboxStateChangedEvent(),this.registerCheckboxStateChangedEventHandler()})}registerActions(){new n("click",(e,t)=>{const o=d.getCheckboxes(r.checked);if(t.dataset.multiRecordSelectionAction&&o.length)switch(t.dataset.multiRecordSelectionAction){case a.edit:e.preventDefault();const n=JSON.parse(t.dataset.multiRecordSelectionActionConfig||"");if(!n||!n.idField||!n.table)break;const i=[];o.forEach(e=>{const t=e.closest("tr");null!==t&&t.dataset[n.idField]&&i.push(t.dataset[n.idField])}),i.length?window.location.href=top.TYPO3.settings.FormEngine.moduleUrl+"&edit["+n.table+"]["+i.join(",")+"]=edit&returnUrl="+d.getReturnUrl(n.returnUrl||""):c.warning("The selected elements can not be edited.");break;default:t.dispatchEvent(new Event("multiRecordSelection:action:"+t.dataset.multiRecordSelectionAction,{bubbles:!0,cancelable:!1}))}}).delegateTo(document,[i.actionsSelector,s.actionButton].join(" ")),d.toggleActionsState()}registerActionsEventHandlers(){new n("multiRecordSelection:actions:show",()=>{const e=document.querySelectorAll(i.actionsSelector);e&&e.forEach(e=>e.classList.remove("hidden"))}).bindTo(document),new n("multiRecordSelection:actions:hide",()=>{const e=document.querySelectorAll(i.actionsSelector);e&&e.forEach(e=>e.classList.add("hidden"))}).bindTo(document)}registerCheckboxActions(){new n("click",(e,t)=>{e.preventDefault();const o=d.getCheckboxes();if(t.dataset.multiRecordSelectionCheckAction&&o.length)switch(t.dataset.multiRecordSelectionCheckAction){case l.checkAll:o.forEach(e=>{d.changeCheckboxState(e,!0)});break;case l.checkNone:o.forEach(e=>{d.changeCheckboxState(e,!1)});break;case l.toggle:o.forEach(e=>{d.changeCheckboxState(e,!e.checked)});break;default:c.warning("Unknown checkbox action")}}).delegateTo(document,[i.checkboxActionsSelector,s.checkboxActionButton].join(" "))}registerDispatchCheckboxStateChangedEvent(){new n("change",(e,t)=>{t.dispatchEvent(new Event("checkbox:state:changed",{bubbles:!0,cancelable:!1}))}).delegateTo(document,i.checkboxSelector)}registerCheckboxStateChangedEventHandler(){new n("checkbox:state:changed",e=>{const t=e.target;t.checked?t.closest("tr").classList.add("success"):t.closest("tr").classList.remove("success"),d.toggleActionsState()}).bindTo(document)}registerToggleCheckboxActions(){new n("click",()=>{const e=document.querySelector('button[data-multi-record-selection-check-action="'+l.checkAll+'"]');null!==e&&e.classList.toggle("disabled",!d.getCheckboxes(r.unchecked).length);const t=document.querySelector('button[data-multi-record-selection-check-action="'+l.checkNone+'"]');null!==t&&t.classList.toggle("disabled",!d.getCheckboxes(r.checked).length)}).delegateTo(document,s.checkboxActionsToggleButton)}}return new d}));
\ No newline at end of file
define(["require","exports","TYPO3/CMS/Backend/Notification","TYPO3/CMS/Core/DocumentService","TYPO3/CMS/Core/Event/RegularEvent"],(function(e,t,c,o,n){"use strict";var i,a,s,l,r;!function(e){e.actionsSelector=".t3js-multi-record-selection-actions",e.checkboxSelector=".t3js-multi-record-selection-check",e.checkboxActionsSelector="#multi-record-selection-check-actions"}(i||(i={})),function(e){e.actionButton="button[data-multi-record-selection-action]",e.checkboxActionButton="button[data-multi-record-selection-check-action]",e.checkboxActionsToggleButton='button[data-bs-target="multi-record-selection-check-actions"]'}(a||(a={})),function(e){e.edit="edit"}(s||(s={})),function(e){e.checkAll="check-all",e.checkNone="check-none",e.toggle="toggle"}(l||(l={})),function(e){e.any="",e.checked=":checked",e.unchecked=":not(:checked)"}(r||(r={}));class d{static getCheckboxes(e=r.any){return document.querySelectorAll(i.checkboxSelector+e)}static changeCheckboxState(e,t){e.checked===t||e.dataset.manuallyChanged||(e.checked=t,e.dispatchEvent(new Event("checkbox:state:changed",{bubbles:!0,cancelable:!1})))}static getReturnUrl(e){return""===e&&(e=top.list_frame.document.location.pathname+top.list_frame.document.location.search),encodeURIComponent(e)}static restoreTemporaryState(){const e=d.getCheckboxes(r.checked);if(!e.length)return;e.forEach(e=>{e.closest("tr").classList.add("success")});const t=document.querySelectorAll(i.actionsSelector);t.length&&t.forEach(e=>e.classList.remove("hidden"))}static toggleActionsState(){const e=document.querySelectorAll(i.actionsSelector);if(!e.length)return;if(!d.getCheckboxes(r.checked).length)return void e.forEach(e=>e.classList.add("hidden"));e.forEach(e=>e.classList.remove("hidden"));const t=document.querySelectorAll([i.actionsSelector,a.actionButton].join(" "));t.length&&t.forEach(e=>{if(!e.dataset.multiRecordSelectionActionConfig)return;const t=JSON.parse(e.dataset.multiRecordSelectionActionConfig);if(!t.idField)return;e.classList.add("disabled");const c=d.getCheckboxes(r.checked);for(let o=0;o<c.length;o++)if(c[o].closest("tr").dataset[t.idField]){e.classList.remove("disabled");break}})}static unsetManuallyChangedAttribute(){d.getCheckboxes().forEach(e=>{e.removeAttribute("data-manually-changed")})}constructor(){o.ready().then(()=>{d.restoreTemporaryState(),this.registerActions(),this.registerActionsEventHandlers(),this.registerCheckboxActions(),this.registerToggleCheckboxActions(),this.registerDispatchCheckboxStateChangedEvent(),this.registerCheckboxStateChangedEventHandler()})}registerActions(){new n("click",(e,t)=>{const o=d.getCheckboxes(r.checked);if(t.dataset.multiRecordSelectionAction&&o.length)switch(t.dataset.multiRecordSelectionAction){case s.edit:e.preventDefault();const n=JSON.parse(t.dataset.multiRecordSelectionActionConfig||"");if(!n||!n.idField||!n.table)break;const i=[];o.forEach(e=>{const t=e.closest("tr");null!==t&&t.dataset[n.idField]&&i.push(t.dataset[n.idField])}),i.length?window.location.href=top.TYPO3.settings.FormEngine.moduleUrl+"&edit["+n.table+"]["+i.join(",")+"]=edit&returnUrl="+d.getReturnUrl(n.returnUrl||""):c.warning("The selected elements can not be edited.");break;default:t.dispatchEvent(new Event("multiRecordSelection:action:"+t.dataset.multiRecordSelectionAction,{bubbles:!0,cancelable:!1}))}}).delegateTo(document,[i.actionsSelector,a.actionButton].join(" ")),d.toggleActionsState()}registerActionsEventHandlers(){new n("multiRecordSelection:actions:show",()=>{const e=document.querySelectorAll(i.actionsSelector);e&&e.forEach(e=>e.classList.remove("hidden"))}).bindTo(document),new n("multiRecordSelection:actions:hide",()=>{const e=document.querySelectorAll(i.actionsSelector);e&&e.forEach(e=>e.classList.add("hidden"))}).bindTo(document)}registerCheckboxActions(){new n("click",(e,t)=>{e.preventDefault();const o=d.getCheckboxes();if(t.dataset.multiRecordSelectionCheckAction&&o.length){switch(d.unsetManuallyChangedAttribute(),t.dataset.multiRecordSelectionCheckAction){case l.checkAll:o.forEach(e=>{d.changeCheckboxState(e,!0)});break;case l.checkNone:o.forEach(e=>{d.changeCheckboxState(e,!1)});break;case l.toggle:o.forEach(e=>{d.changeCheckboxState(e,!e.checked)});break;default:c.warning("Unknown checkbox action")}d.unsetManuallyChangedAttribute()}}).delegateTo(document,[i.checkboxActionsSelector,a.checkboxActionButton].join(" "))}registerDispatchCheckboxStateChangedEvent(){new n("change",(e,t)=>{t.dispatchEvent(new Event("checkbox:state:changed",{bubbles:!0,cancelable:!1}))}).delegateTo(document,i.checkboxSelector)}registerCheckboxStateChangedEventHandler(){new n("checkbox:state:changed",e=>{const t=e.target;t.checked?t.closest("tr").classList.add("success"):t.closest("tr").classList.remove("success"),d.toggleActionsState()}).bindTo(document)}registerToggleCheckboxActions(){new n("click",()=>{const e=document.querySelector('button[data-multi-record-selection-check-action="'+l.checkAll+'"]');null!==e&&e.classList.toggle("disabled",!d.getCheckboxes(r.unchecked).length);const t=document.querySelector('button[data-multi-record-selection-check-action="'+l.checkNone+'"]');null!==t&&t.classList.toggle("disabled",!d.getCheckboxes(r.checked).length)}).delegateTo(document,a.checkboxActionsToggleButton)}}return new d}));
\ No newline at end of file
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