From 4b681fd3228b768da138ddc002dd5d4700febea2 Mon Sep 17 00:00:00 2001
From: Andreas Fernandez <a.fernandez@scripting-base.de>
Date: Fri, 17 Apr 2020 16:42:34 +0200
Subject: [PATCH] [BUGFIX] Reset click events in buttons of MultiStepWizard
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

When the MultiStepWizard is used with additional click events bound to
the next or prev button, those event got stacked as the buttons don't
change per step.

To fix this issue, all click events are removed after switching a step
and the original click event to switch the step is bound again.

Furthermore the 'savePath' for duplication of a form is now correctly
set if there is only one folder available and therefore no select element
present in the modal.

Resolves: #91094
Releases: master
Change-Id: Ic95d2222441c8e1e8cf9fb46e5e7437d4e4ed85e
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/64214
Tested-by: Oliver Bartsch <bo@cedev.de>
Tested-by: TYPO3com <noreply@typo3.com>
Tested-by: Christian Eßl <indy.essl@gmail.com>
Reviewed-by: Oliver Bartsch <bo@cedev.de>
Reviewed-by: Christian Eßl <indy.essl@gmail.com>
---
 .../Public/TypeScript/MultiStepWizard.ts      | 33 ++++++++++++-------
 .../Public/JavaScript/MultiStepWizard.js      |  2 +-
 .../Backend/FormManager/ViewModel.js          |  3 +-
 3 files changed, 25 insertions(+), 13 deletions(-)

diff --git a/Build/Sources/TypeScript/backend/Resources/Public/TypeScript/MultiStepWizard.ts b/Build/Sources/TypeScript/backend/Resources/Public/TypeScript/MultiStepWizard.ts
index 474975afa1d7..7b337868ca24 100644
--- a/Build/Sources/TypeScript/backend/Resources/Public/TypeScript/MultiStepWizard.ts
+++ b/Build/Sources/TypeScript/backend/Resources/Public/TypeScript/MultiStepWizard.ts
@@ -258,17 +258,8 @@ class MultiStepWizard {
    */
   private initializeEvents(): void {
     let $modal = this.setup.$carousel.closest('.modal');
-    let $modalFooter = $modal.find('.modal-footer');
-    let $nextButton = $modalFooter.find('button[name="next"]');
-    let $prevButton = $modalFooter.find('button[name="prev"]');
-
-    $nextButton.on('click', (): void => {
-      this.setup.$carousel.carousel('next');
-    });
-
-    $prevButton.on('click', (): void => {
-      this.setup.$carousel.carousel('prev');
-    });
+    this.initializeSlideNextEvent($modal);
+    this.initializeSlidePrevEvent($modal);
 
     // Event fires when the slide transition is invoked
     this.setup.$carousel.on('slide.bs.carousel', (evt: any): void => {
@@ -301,6 +292,22 @@ class MultiStepWizard {
     });
   }
 
+  private initializeSlideNextEvent($modal: JQuery) {
+    let $modalFooter = $modal.find('.modal-footer');
+    let $nextButton = $modalFooter.find('button[name="next"]');
+    $nextButton.off().on('click', (): void => {
+      this.setup.$carousel.carousel('next');
+    });
+  }
+
+  private initializeSlidePrevEvent($modal: JQuery) {
+    let $modalFooter = $modal.find('.modal-footer');
+    let $prevButton = $modalFooter.find('button[name="prev"]');
+    $prevButton.off().on('click', (): void => {
+      this.setup.$carousel.carousel('prev');
+    });
+  }
+
   /**
    * All changes after applying the next-button
    *
@@ -308,6 +315,8 @@ class MultiStepWizard {
    * @private
    */
   private nextSlideChanges($modal: JQuery): void {
+    this.initializeSlideNextEvent($modal);
+
     let $modalTitle = $modal.find('.modal-title');
     let $modalFooter = $modal.find('.modal-footer');
     let $modalButtonGroup = $modal.find('.modal-btn-group');
@@ -361,6 +370,8 @@ class MultiStepWizard {
    * @private
    */
   private prevSlideChanges($modal: JQuery): void {
+    this.initializeSlidePrevEvent($modal);
+
     let $modalTitle = $modal.find('.modal-title');
     let $modalFooter = $modal.find('.modal-footer');
     let $modalButtonGroup = $modal.find('.modal-btn-group');
diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/MultiStepWizard.js b/typo3/sysext/backend/Resources/Public/JavaScript/MultiStepWizard.js
index 79bf63ba89f4..91365918d750 100644
--- a/typo3/sysext/backend/Resources/Public/JavaScript/MultiStepWizard.js
+++ b/typo3/sysext/backend/Resources/Public/JavaScript/MultiStepWizard.js
@@ -10,4 +10,4 @@
  *
  * The TYPO3 project - inspiring people to share!
  */
-define(["require","exports","./Enum/Severity","jquery","./Modal","./Severity","./Icons"],(function(t,e,s,i,r,a,l){"use strict";class n{constructor(){this.setup={slides:[],settings:{},forceSelection:!0,$carousel:null},this.originalSetup=i.extend(!0,{},this.setup)}set(t,e){return this.setup.settings[t]=e,this}addSlide(t,e,i="",r=s.SeverityEnum.info,a,l){const n={identifier:t,title:e,content:i,severity:r,progressBarTitle:a,callback:l};return this.setup.slides.push(n),this}addFinalProcessingSlide(t){return t||(t=()=>{this.dismiss()}),l.getIcon("spinner-circle",l.sizes.default,null,null).then(e=>{let s=i("<div />",{class:"text-center"}).append(e);this.addSlide("final-processing-slide",top.TYPO3.lang["wizard.processing.title"],s[0].outerHTML,a.info,null,t)})}show(){let t=this.generateSlides(),e=this.setup.slides[0];r.confirm(e.title,t,e.severity,[{text:top.TYPO3.lang["wizard.button.cancel"],active:!0,btnClass:"btn-default pull-left",name:"cancel",trigger:()=>{this.getComponent().trigger("wizard-dismiss")}},{text:top.TYPO3.lang["wizard.button.prev"],btnClass:"btn-"+a.getCssClass(e.severity),name:"prev"},{text:top.TYPO3.lang["wizard.button.next"],btnClass:"btn-"+a.getCssClass(e.severity),name:"next"}],["modal-multi-step-wizard"]),this.addButtonContainer(),this.addProgressBar(),this.initializeEvents(),this.getComponent().on("wizard-visible",()=>{this.runSlideCallback(e,this.setup.$carousel.find(".item").first())}).on("wizard-dismissed",()=>{this.setup=i.extend(!0,{},this.originalSetup)})}getComponent(){return null===this.setup.$carousel&&this.generateSlides(),this.setup.$carousel}dismiss(){r.dismiss()}lockNextStep(){let t=this.setup.$carousel.closest(".modal").find('button[name="next"]');return t.prop("disabled",!0),t}unlockNextStep(){let t=this.setup.$carousel.closest(".modal").find('button[name="next"]');return t.prop("disabled",!1),t}lockPrevStep(){let t=this.setup.$carousel.closest(".modal").find('button[name="prev"]');return t.prop("disabled",!0),t}unlockPrevStep(){let t=this.setup.$carousel.closest(".modal").find('button[name="prev"]');return t.prop("disabled",!1),t}triggerStepButton(t){let e=this.setup.$carousel.closest(".modal").find('button[name="'+t+'"]');return e.length>0&&!0!==e.prop("disabled")&&e.trigger("click"),e}blurCancelStep(){let t=this.setup.$carousel.closest(".modal").find('button[name="cancel"]');return t.blur(),t}initializeEvents(){let t=this.setup.$carousel.closest(".modal"),e=t.find(".modal-footer"),s=e.find('button[name="next"]'),a=e.find('button[name="prev"]');s.on("click",()=>{this.setup.$carousel.carousel("next")}),a.on("click",()=>{this.setup.$carousel.carousel("prev")}),this.setup.$carousel.on("slide.bs.carousel",e=>{"left"===e.direction?this.nextSlideChanges(t):this.prevSlideChanges(t)}).on("slid.bs.carousel",t=>{let e=this.setup.$carousel.data("currentIndex"),s=this.setup.slides[e];this.runSlideCallback(s,i(t.relatedTarget)),this.setup.forceSelection&&this.lockNextStep()});let l=this.getComponent();l.on("wizard-dismiss",this.dismiss),r.currentModal.on("hidden.bs.modal",()=>{l.trigger("wizard-dismissed")}).on("shown.bs.modal",()=>{l.trigger("wizard-visible")})}nextSlideChanges(t){let e=t.find(".modal-title"),s=t.find(".modal-footer"),i=t.find(".modal-btn-group"),r=s.find('button[name="next"]'),l=this.setup.$carousel.data("currentSlide")+1,n=this.setup.$carousel.data("currentIndex")+1;e.text(this.setup.slides[n].title),this.setup.$carousel.data("currentSlide",l),this.setup.$carousel.data("currentIndex",n),l>=this.setup.$carousel.data("realSlideCount")?(r.text(this.getProgressBarTitle(this.setup.$carousel.data("currentIndex"))),s.find(".progress-bar.first-step").width("100%").text(this.getProgressBarTitle(this.setup.$carousel.data("currentIndex"))),s.find(".progress-bar.last-step").width("0%").text(""),this.setup.forceSelection=!1):(s.find(".progress-bar.first-step").width(this.setup.$carousel.data("initialStep")*l+"%").text(this.getProgressBarTitle(n)),s.find(".progress-bar.step").width("0%").text(""),i.slideDown()),r.removeClass("btn-"+a.getCssClass(this.setup.slides[n-1].severity)).addClass("btn-"+a.getCssClass(this.setup.slides[n].severity)),t.removeClass("modal-severity-"+a.getCssClass(this.setup.slides[n-1].severity)).addClass("modal-severity-"+a.getCssClass(this.setup.slides[n].severity))}prevSlideChanges(t){let e=t.find(".modal-title"),s=t.find(".modal-footer"),i=t.find(".modal-btn-group"),r=s.find('button[name="next"]'),a=this.setup.$carousel.data("currentSlide")-1,l=this.setup.$carousel.data("currentIndex")-1;this.setup.$carousel.data("currentSlide",a),this.setup.$carousel.data("currentIndex",l),e.text(this.setup.slides[l].title),s.find(".progress-bar.last-step").width(this.setup.$carousel.data("initialStep")+"%").text(this.getProgressBarTitle(this.setup.$carousel.data("slideCount")-1)),r.text(top.TYPO3.lang["wizard.button.next"]),1===a?(s.find(".progress-bar.first-step").width(this.setup.$carousel.data("initialStep")*a+"%").text(this.getProgressBarTitle(0)),s.find(".progress-bar.step").width(this.setup.$carousel.data("initialStep")+"%").text(this.getProgressBarTitle(l+1)),i.slideUp()):(s.find(".progress-bar.first-step").width(this.setup.$carousel.data("initialStep")*a+"%").text(this.getProgressBarTitle(l)),this.setup.forceSelection=!0)}getProgressBarTitle(t){let e;return e=null===this.setup.slides[t].progressBarTitle?0===t?top.TYPO3.lang["wizard.progressStep.start"]:t>=this.setup.$carousel.data("slideCount")-1?top.TYPO3.lang["wizard.progressStep.finish"]:top.TYPO3.lang["wizard.progressStep"]+String(t+1):this.setup.slides[t].progressBarTitle,e}runSlideCallback(t,e){"function"==typeof t.callback&&t.callback(e,this.setup.settings,t.identifier)}addProgressBar(){let t,e=this.setup.$carousel.find(".item").length-1,s=Math.max(1,e),r=this.setup.$carousel.closest(".modal").find(".modal-footer");if(t=Math.round(100/s),this.setup.$carousel.data("initialStep",t).data("slideCount",s).data("realSlideCount",e).data("currentIndex",0).data("currentSlide",1),s>1){r.prepend(i("<div />",{class:"progress"}));for(let e=0;e<this.setup.slides.length;++e){let s;s=0===e?"progress-bar first-step":e===this.setup.$carousel.data("slideCount")-1?"progress-bar last-step inactive":"progress-bar step inactive",r.find(".progress").append(i("<div />",{role:"progressbar",class:s,"aria-valuemin":0,"aria-valuenow":t,"aria-valuemax":100}).width(t+"%").text(this.getProgressBarTitle(e)))}}}addButtonContainer(){this.setup.$carousel.closest(".modal").find(".modal-footer .btn").wrapAll('<div class="modal-btn-group" />')}generateSlides(){if(null!==this.setup.$carousel)return this.setup.$carousel;let t='<div class="carousel slide" data-ride="carousel" data-interval="false"><div class="carousel-inner" role="listbox">';for(let e=0;e<this.setup.slides.length;++e){let s=this.setup.slides[e],i=s.content;"object"==typeof i&&(i=i.html()),t+='<div class="item" data-slide="'+s.identifier+'" data-step="'+e+'">'+i+"</div>"}return t+="</div></div>",this.setup.$carousel=i(t),this.setup.$carousel.find(".item").first().addClass("active"),this.setup.$carousel}}let o;try{window.opener&&window.opener.TYPO3&&window.opener.TYPO3.MultiStepWizard&&(o=window.opener.TYPO3.MultiStepWizard),parent&&parent.window.TYPO3&&parent.window.TYPO3.MultiStepWizard&&(o=parent.window.TYPO3.MultiStepWizard),top&&top.TYPO3&&top.TYPO3.MultiStepWizard&&(o=top.TYPO3.MultiStepWizard)}catch(t){}return o||(o=new n,"undefined"!=typeof TYPO3&&(TYPO3.MultiStepWizard=o)),o}));
\ No newline at end of file
+define(["require","exports","./Enum/Severity","jquery","./Modal","./Severity","./Icons"],(function(t,e,s,i,r,a,l){"use strict";class n{constructor(){this.setup={slides:[],settings:{},forceSelection:!0,$carousel:null},this.originalSetup=i.extend(!0,{},this.setup)}set(t,e){return this.setup.settings[t]=e,this}addSlide(t,e,i="",r=s.SeverityEnum.info,a,l){const n={identifier:t,title:e,content:i,severity:r,progressBarTitle:a,callback:l};return this.setup.slides.push(n),this}addFinalProcessingSlide(t){return t||(t=()=>{this.dismiss()}),l.getIcon("spinner-circle",l.sizes.default,null,null).then(e=>{let s=i("<div />",{class:"text-center"}).append(e);this.addSlide("final-processing-slide",top.TYPO3.lang["wizard.processing.title"],s[0].outerHTML,a.info,null,t)})}show(){let t=this.generateSlides(),e=this.setup.slides[0];r.confirm(e.title,t,e.severity,[{text:top.TYPO3.lang["wizard.button.cancel"],active:!0,btnClass:"btn-default pull-left",name:"cancel",trigger:()=>{this.getComponent().trigger("wizard-dismiss")}},{text:top.TYPO3.lang["wizard.button.prev"],btnClass:"btn-"+a.getCssClass(e.severity),name:"prev"},{text:top.TYPO3.lang["wizard.button.next"],btnClass:"btn-"+a.getCssClass(e.severity),name:"next"}],["modal-multi-step-wizard"]),this.addButtonContainer(),this.addProgressBar(),this.initializeEvents(),this.getComponent().on("wizard-visible",()=>{this.runSlideCallback(e,this.setup.$carousel.find(".item").first())}).on("wizard-dismissed",()=>{this.setup=i.extend(!0,{},this.originalSetup)})}getComponent(){return null===this.setup.$carousel&&this.generateSlides(),this.setup.$carousel}dismiss(){r.dismiss()}lockNextStep(){let t=this.setup.$carousel.closest(".modal").find('button[name="next"]');return t.prop("disabled",!0),t}unlockNextStep(){let t=this.setup.$carousel.closest(".modal").find('button[name="next"]');return t.prop("disabled",!1),t}lockPrevStep(){let t=this.setup.$carousel.closest(".modal").find('button[name="prev"]');return t.prop("disabled",!0),t}unlockPrevStep(){let t=this.setup.$carousel.closest(".modal").find('button[name="prev"]');return t.prop("disabled",!1),t}triggerStepButton(t){let e=this.setup.$carousel.closest(".modal").find('button[name="'+t+'"]');return e.length>0&&!0!==e.prop("disabled")&&e.trigger("click"),e}blurCancelStep(){let t=this.setup.$carousel.closest(".modal").find('button[name="cancel"]');return t.blur(),t}initializeEvents(){let t=this.setup.$carousel.closest(".modal");this.initializeSlideNextEvent(t),this.initializeSlidePrevEvent(t),this.setup.$carousel.on("slide.bs.carousel",e=>{"left"===e.direction?this.nextSlideChanges(t):this.prevSlideChanges(t)}).on("slid.bs.carousel",t=>{let e=this.setup.$carousel.data("currentIndex"),s=this.setup.slides[e];this.runSlideCallback(s,i(t.relatedTarget)),this.setup.forceSelection&&this.lockNextStep()});let e=this.getComponent();e.on("wizard-dismiss",this.dismiss),r.currentModal.on("hidden.bs.modal",()=>{e.trigger("wizard-dismissed")}).on("shown.bs.modal",()=>{e.trigger("wizard-visible")})}initializeSlideNextEvent(t){t.find(".modal-footer").find('button[name="next"]').off().on("click",()=>{this.setup.$carousel.carousel("next")})}initializeSlidePrevEvent(t){t.find(".modal-footer").find('button[name="prev"]').off().on("click",()=>{this.setup.$carousel.carousel("prev")})}nextSlideChanges(t){this.initializeSlideNextEvent(t);let e=t.find(".modal-title"),s=t.find(".modal-footer"),i=t.find(".modal-btn-group"),r=s.find('button[name="next"]'),l=this.setup.$carousel.data("currentSlide")+1,n=this.setup.$carousel.data("currentIndex")+1;e.text(this.setup.slides[n].title),this.setup.$carousel.data("currentSlide",l),this.setup.$carousel.data("currentIndex",n),l>=this.setup.$carousel.data("realSlideCount")?(r.text(this.getProgressBarTitle(this.setup.$carousel.data("currentIndex"))),s.find(".progress-bar.first-step").width("100%").text(this.getProgressBarTitle(this.setup.$carousel.data("currentIndex"))),s.find(".progress-bar.last-step").width("0%").text(""),this.setup.forceSelection=!1):(s.find(".progress-bar.first-step").width(this.setup.$carousel.data("initialStep")*l+"%").text(this.getProgressBarTitle(n)),s.find(".progress-bar.step").width("0%").text(""),i.slideDown()),r.removeClass("btn-"+a.getCssClass(this.setup.slides[n-1].severity)).addClass("btn-"+a.getCssClass(this.setup.slides[n].severity)),t.removeClass("modal-severity-"+a.getCssClass(this.setup.slides[n-1].severity)).addClass("modal-severity-"+a.getCssClass(this.setup.slides[n].severity))}prevSlideChanges(t){this.initializeSlidePrevEvent(t);let e=t.find(".modal-title"),s=t.find(".modal-footer"),i=t.find(".modal-btn-group"),r=s.find('button[name="next"]'),a=this.setup.$carousel.data("currentSlide")-1,l=this.setup.$carousel.data("currentIndex")-1;this.setup.$carousel.data("currentSlide",a),this.setup.$carousel.data("currentIndex",l),e.text(this.setup.slides[l].title),s.find(".progress-bar.last-step").width(this.setup.$carousel.data("initialStep")+"%").text(this.getProgressBarTitle(this.setup.$carousel.data("slideCount")-1)),r.text(top.TYPO3.lang["wizard.button.next"]),1===a?(s.find(".progress-bar.first-step").width(this.setup.$carousel.data("initialStep")*a+"%").text(this.getProgressBarTitle(0)),s.find(".progress-bar.step").width(this.setup.$carousel.data("initialStep")+"%").text(this.getProgressBarTitle(l+1)),i.slideUp()):(s.find(".progress-bar.first-step").width(this.setup.$carousel.data("initialStep")*a+"%").text(this.getProgressBarTitle(l)),this.setup.forceSelection=!0)}getProgressBarTitle(t){let e;return e=null===this.setup.slides[t].progressBarTitle?0===t?top.TYPO3.lang["wizard.progressStep.start"]:t>=this.setup.$carousel.data("slideCount")-1?top.TYPO3.lang["wizard.progressStep.finish"]:top.TYPO3.lang["wizard.progressStep"]+String(t+1):this.setup.slides[t].progressBarTitle,e}runSlideCallback(t,e){"function"==typeof t.callback&&t.callback(e,this.setup.settings,t.identifier)}addProgressBar(){let t,e=this.setup.$carousel.find(".item").length-1,s=Math.max(1,e),r=this.setup.$carousel.closest(".modal").find(".modal-footer");if(t=Math.round(100/s),this.setup.$carousel.data("initialStep",t).data("slideCount",s).data("realSlideCount",e).data("currentIndex",0).data("currentSlide",1),s>1){r.prepend(i("<div />",{class:"progress"}));for(let e=0;e<this.setup.slides.length;++e){let s;s=0===e?"progress-bar first-step":e===this.setup.$carousel.data("slideCount")-1?"progress-bar last-step inactive":"progress-bar step inactive",r.find(".progress").append(i("<div />",{role:"progressbar",class:s,"aria-valuemin":0,"aria-valuenow":t,"aria-valuemax":100}).width(t+"%").text(this.getProgressBarTitle(e)))}}}addButtonContainer(){this.setup.$carousel.closest(".modal").find(".modal-footer .btn").wrapAll('<div class="modal-btn-group" />')}generateSlides(){if(null!==this.setup.$carousel)return this.setup.$carousel;let t='<div class="carousel slide" data-ride="carousel" data-interval="false"><div class="carousel-inner" role="listbox">';for(let e=0;e<this.setup.slides.length;++e){let s=this.setup.slides[e],i=s.content;"object"==typeof i&&(i=i.html()),t+='<div class="item" data-slide="'+s.identifier+'" data-step="'+e+'">'+i+"</div>"}return t+="</div></div>",this.setup.$carousel=i(t),this.setup.$carousel.find(".item").first().addClass("active"),this.setup.$carousel}}let d;try{window.opener&&window.opener.TYPO3&&window.opener.TYPO3.MultiStepWizard&&(d=window.opener.TYPO3.MultiStepWizard),parent&&parent.window.TYPO3&&parent.window.TYPO3.MultiStepWizard&&(d=parent.window.TYPO3.MultiStepWizard),top&&top.TYPO3&&top.TYPO3.MultiStepWizard&&(d=top.TYPO3.MultiStepWizard)}catch(t){}return d||(d=new n,"undefined"!=typeof TYPO3&&(TYPO3.MultiStepWizard=d)),d}));
\ No newline at end of file
diff --git a/typo3/sysext/form/Resources/Public/JavaScript/Backend/FormManager/ViewModel.js b/typo3/sysext/form/Resources/Public/JavaScript/Backend/FormManager/ViewModel.js
index f22f55d01b65..c55ef06644b4 100644
--- a/typo3/sysext/form/Resources/Public/JavaScript/Backend/FormManager/ViewModel.js
+++ b/typo3/sysext/form/Resources/Public/JavaScript/Backend/FormManager/ViewModel.js
@@ -612,11 +612,12 @@ define(['jquery',
           nextButton.on('click', function(e) {
             Icons.getIcon('spinner-circle', Icons.sizes.default, null, null).then(function(markup) {
               MultiStepWizard.set('confirmationDuplicateFormName', that.data('formName'));
-              MultiStepWizard.set('savePath', $(getDomElementIdentifier('duplicateFormSavePath') + ' option:selected', modal).val());
 
               if (folders.length > 1) {
+                MultiStepWizard.set('savePath', $(getDomElementIdentifier('duplicateFormSavePath') + ' option:selected', modal).val());
                 MultiStepWizard.set('confirmationDuplicateFormSavePath', $(getDomElementIdentifier('duplicateFormSavePath') + ' option:selected', modal).text());
               } else {
+                MultiStepWizard.set('savePath', folders[0]['value']);
                 MultiStepWizard.set('confirmationDuplicateFormSavePath', folders[0]['label']);
               }
 
-- 
GitLab