From ca2a2567a10cd9536856baf6a4abdbf18b850647 Mon Sep 17 00:00:00 2001
From: Andreas Kienast <a.fernandez@scripting-base.de>
Date: Thu, 21 Mar 2024 14:28:03 +0100
Subject: [PATCH] [TASK] linkvalidator: Calculate option toggle state on render
 time
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This commit changes how the "Toggle all" and option and action button
states are determined. Previously, the state was determined via
JavaScript, leading to an unexpected rendering change when the module
has loaded.

To avoid this, the state is now calculated in the controller as the
necessary metrics are already known.

In the same run, `optionByType.checked` is now a simple bool flag
instead of holding arbitrary HTML attributes.

Resolves: #103454
Releases: main
Change-Id: I2da39063dbb9161873274dadddcc3b0384e36321
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/83560
Tested-by: Benjamin Franzke <ben@bnf.dev>
Tested-by: core-ci <typo3@b13.com>
Reviewed-by: Benjamin Franzke <ben@bnf.dev>
Reviewed-by: Benni Mack <benni@typo3.org>
Tested-by: Andreas Kienast <a.fernandez@scripting-base.de>
Reviewed-by: Jörg Bösche <typo3@joergboesche.de>
Reviewed-by: Andreas Kienast <a.fernandez@scripting-base.de>
Tested-by: Benni Mack <benni@typo3.org>
---
 Build/Sources/TypeScript/linkvalidator/linkvalidator.ts   | 2 --
 .../Classes/Controller/LinkValidatorController.php        | 8 +++++++-
 .../Resources/Private/Partials/CheckOptions.html          | 4 ++--
 .../Resources/Private/Templates/Backend/CheckLinks.html   | 2 +-
 .../Resources/Private/Templates/Backend/Report.html       | 2 +-
 .../Resources/Public/JavaScript/linkvalidator.js          | 2 +-
 6 files changed, 12 insertions(+), 8 deletions(-)

diff --git a/Build/Sources/TypeScript/linkvalidator/linkvalidator.ts b/Build/Sources/TypeScript/linkvalidator/linkvalidator.ts
index c3ff0487de06..667589b7b848 100644
--- a/Build/Sources/TypeScript/linkvalidator/linkvalidator.ts
+++ b/Build/Sources/TypeScript/linkvalidator/linkvalidator.ts
@@ -29,8 +29,6 @@ enum Identifier {
  */
 class Linkvalidator {
   constructor() {
-    this.toggleTriggerCheckBox();
-    this.toggleActionButton();
     this.initializeEvents();
   }
 
diff --git a/typo3/sysext/linkvalidator/Classes/Controller/LinkValidatorController.php b/typo3/sysext/linkvalidator/Classes/Controller/LinkValidatorController.php
index e970df84ab62..62a555dc938c 100644
--- a/typo3/sysext/linkvalidator/Classes/Controller/LinkValidatorController.php
+++ b/typo3/sysext/linkvalidator/Classes/Controller/LinkValidatorController.php
@@ -427,6 +427,7 @@ class LinkValidatorController
     {
         $brokenLinksInformation = $this->linkAnalyzer->getLinkCounts();
         $options = [
+            'anyOptionChecked' => false,
             'totalCountLabel' => $this->getLanguageService()->sL('LLL:EXT:linkvalidator/Resources/Private/Language/Module/locallang.xlf:overviews.nbtotal'),
             'totalCount' => $brokenLinksInformation['total'] ?: '0',
             'optionsByType' => [],
@@ -436,14 +437,19 @@ class LinkValidatorController
             if (!in_array($type, $linkTypes, true)) {
                 continue;
             }
+            $isChecked = !empty($this->checkOpt[$prefix][$type]);
+            if ($isChecked) {
+                $options['anyOptionChecked'] = true;
+            }
             $options['optionsByType'][$type] = [
                 'id' => $prefix . '_SET_' . $type,
                 'name' => $prefix . '_SET[' . $type . ']',
                 'label' => $this->getLanguageService()->sL('LLL:EXT:linkvalidator/Resources/Private/Language/Module/locallang.xlf:hooks.' . $type) ?: $type,
-                'checked' => !empty($this->checkOpt[$prefix][$type]) ? ' checked="checked"' : '',
+                'checked' => $isChecked,
                 'count' => (!empty($brokenLinksInformation[$type]) ? $brokenLinksInformation[$type] : '0'),
             ];
         }
+        $options['allOptionsChecked'] = array_filter($options['optionsByType'], static fn(array $option): bool => !$option['checked']) === [];
         return $options;
     }
 
diff --git a/typo3/sysext/linkvalidator/Resources/Private/Partials/CheckOptions.html b/typo3/sysext/linkvalidator/Resources/Private/Partials/CheckOptions.html
index 5d84e8a3ebaa..6941ddd082ee 100644
--- a/typo3/sysext/linkvalidator/Resources/Private/Partials/CheckOptions.html
+++ b/typo3/sysext/linkvalidator/Resources/Private/Partials/CheckOptions.html
@@ -13,7 +13,7 @@
         <tr>
             <th>
                 <div class="form-check form-switch">
-                    <input id="options-by-type-toggle-all" class="options-by-type-toggle-all form-check-input" type="checkbox" title="{f:translate(key: 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.toggleall')}">
+                    <input id="options-by-type-toggle-all" class="options-by-type-toggle-all form-check-input" type="checkbox" title="{f:translate(key: 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.toggleall')}" {f:if(condition: '{options.allOptionsChecked}', then: 'checked')}>
                     <label class="form-check-label" for="options-by-type-toggle-all"><f:translate key="LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.toggleall"/></label>
                 </div>
             </th>
@@ -25,7 +25,7 @@
             <tr>
                 <td>
                     <div class="form-check form-switch">
-                        <input type="checkbox" class="form-check-input options-by-type" value="1" id="{optionByType.id}" name="{optionByType.name}" {optionByType.checked}/>
+                        <input type="checkbox" class="form-check-input options-by-type" value="1" id="{optionByType.id}" name="{optionByType.name}" {f:if(condition: '{optionByType.checked}', then: 'checked')} />
                         <label class="form-check-label" for="{optionByType.id}">{optionByType.label}</label>
                     </div>
                 </td>
diff --git a/typo3/sysext/linkvalidator/Resources/Private/Templates/Backend/CheckLinks.html b/typo3/sysext/linkvalidator/Resources/Private/Templates/Backend/CheckLinks.html
index d7f8eff2c167..765a9e84e82e 100644
--- a/typo3/sysext/linkvalidator/Resources/Private/Templates/Backend/CheckLinks.html
+++ b/typo3/sysext/linkvalidator/Resources/Private/Templates/Backend/CheckLinks.html
@@ -32,7 +32,7 @@
                         type="submit"
                         class="btn btn-default t3js-linkvalidator-action-button"
                         name="updateLinkList"
-                        disabled
+                        {f:if(condition: '!{options.anyOptionChecked}', then: 'disabled')}
                         value="{f:translate(key: 'LLL:EXT:linkvalidator/Resources/Private/Language/Module/locallang.xlf:label_update')}"
                         data-notification-message="{f:translate(key: 'LLL:EXT:linkvalidator/Resources/Private/Language/Module/locallang.xlf:label_update-link-list')}"
                     />
diff --git a/typo3/sysext/linkvalidator/Resources/Private/Templates/Backend/Report.html b/typo3/sysext/linkvalidator/Resources/Private/Templates/Backend/Report.html
index fc1f7d767fc3..d6198e24ec00 100644
--- a/typo3/sysext/linkvalidator/Resources/Private/Templates/Backend/Report.html
+++ b/typo3/sysext/linkvalidator/Resources/Private/Templates/Backend/Report.html
@@ -32,7 +32,7 @@
                         type="submit"
                         class="btn btn-default t3js-linkvalidator-action-button"
                         name="refreshLinkList"
-                        disabled
+                        {f:if(condition: '!{options.anyOptionChecked}', then: 'disabled')}
                         value="{f:translate(key: 'LLL:EXT:linkvalidator/Resources/Private/Language/Module/locallang.xlf:label_refresh')}"
                         data-notification-message="{f:translate(key: 'LLL:EXT:linkvalidator/Resources/Private/Language/Module/locallang.xlf:label_refresh-link-list')}"
                     />
diff --git a/typo3/sysext/linkvalidator/Resources/Public/JavaScript/linkvalidator.js b/typo3/sysext/linkvalidator/Resources/Public/JavaScript/linkvalidator.js
index 352c73aea4f7..f2ee8f45f760 100644
--- a/typo3/sysext/linkvalidator/Resources/Public/JavaScript/linkvalidator.js
+++ b/typo3/sysext/linkvalidator/Resources/Public/JavaScript/linkvalidator.js
@@ -10,4 +10,4 @@
  *
  * The TYPO3 project - inspiring people to share!
  */
-import Notification from"@typo3/backend/notification.js";import RegularEvent from"@typo3/core/event/regular-event.js";var Selectors,Identifier;!function(e){e.actionButtonSelector=".t3js-linkvalidator-action-button",e.toggleAllLinktypesSelector='.t3js-linkvalidator-settings input[type="checkbox"].options-by-type-toggle-all',e.linktypesSelector='.t3js-linkvalidator-settings input[type="checkbox"].options-by-type'}(Selectors||(Selectors={})),function(e){e.toggleAllLinktypesId="options-by-type-toggle-all"}(Identifier||(Identifier={}));class Linkvalidator{constructor(){this.toggleTriggerCheckBox(),this.toggleActionButton(),this.initializeEvents()}static allCheckBoxesAreChecked(e){const t=Array.from(e);return e.length===t.filter((e=>e.checked)).length}toggleActionButton(){document.querySelector(Selectors.actionButtonSelector)?.toggleAttribute("disabled",!document.querySelectorAll('input[type="checkbox"]:checked').length)}toggleTriggerCheckBox(){const e=document.querySelectorAll(Selectors.linktypesSelector);document.getElementById(Identifier.toggleAllLinktypesId).checked=Linkvalidator.allCheckBoxesAreChecked(e)}initializeEvents(){new RegularEvent("change",((e,t)=>{const o=document.querySelectorAll(Selectors.linktypesSelector),l=!Linkvalidator.allCheckBoxesAreChecked(o);o.forEach((e=>{e.checked=l})),t.checked=l,this.toggleActionButton()})).delegateTo(document,Selectors.toggleAllLinktypesSelector),new RegularEvent("change",(()=>{this.toggleTriggerCheckBox(),this.toggleActionButton()})).delegateTo(document,Selectors.linktypesSelector),new RegularEvent("click",((e,t)=>{Notification.success(t.dataset.notificationMessage||"Event triggered","",2)})).delegateTo(document,Selectors.actionButtonSelector)}}export default new Linkvalidator;
\ No newline at end of file
+import Notification from"@typo3/backend/notification.js";import RegularEvent from"@typo3/core/event/regular-event.js";var Selectors,Identifier;!function(e){e.actionButtonSelector=".t3js-linkvalidator-action-button",e.toggleAllLinktypesSelector='.t3js-linkvalidator-settings input[type="checkbox"].options-by-type-toggle-all',e.linktypesSelector='.t3js-linkvalidator-settings input[type="checkbox"].options-by-type'}(Selectors||(Selectors={})),function(e){e.toggleAllLinktypesId="options-by-type-toggle-all"}(Identifier||(Identifier={}));class Linkvalidator{constructor(){this.initializeEvents()}static allCheckBoxesAreChecked(e){const t=Array.from(e);return e.length===t.filter((e=>e.checked)).length}toggleActionButton(){document.querySelector(Selectors.actionButtonSelector)?.toggleAttribute("disabled",!document.querySelectorAll('input[type="checkbox"]:checked').length)}toggleTriggerCheckBox(){const e=document.querySelectorAll(Selectors.linktypesSelector);document.getElementById(Identifier.toggleAllLinktypesId).checked=Linkvalidator.allCheckBoxesAreChecked(e)}initializeEvents(){new RegularEvent("change",((e,t)=>{const o=document.querySelectorAll(Selectors.linktypesSelector),l=!Linkvalidator.allCheckBoxesAreChecked(o);o.forEach((e=>{e.checked=l})),t.checked=l,this.toggleActionButton()})).delegateTo(document,Selectors.toggleAllLinktypesSelector),new RegularEvent("change",(()=>{this.toggleTriggerCheckBox(),this.toggleActionButton()})).delegateTo(document,Selectors.linktypesSelector),new RegularEvent("click",((e,t)=>{Notification.success(t.dataset.notificationMessage||"Event triggered","",2)})).delegateTo(document,Selectors.actionButtonSelector)}}export default new Linkvalidator;
\ No newline at end of file
-- 
GitLab