1import { Controller } from "@hotwired/stimulus" 2 3export default class extends Controller { 4 static targets = ["step", "prevButton", "nextButton", "submitButton", "nonFormContent"] 5 6 connect() { 7 this.currentStep = 1 8 this.totalSteps = this.stepTargets.length 9 this.updateButtons() 10 } 11 12 next() { 13 if (this.validateCurrentStep() && this.currentStep < this.totalSteps) { 14 this.stepTargets[this.currentStep - 1].classList.add('hidden') 15 this.stepTargets[this.currentStep].classList.remove('hidden') 16 this.currentStep++ 17 this.updateButtons() 18 19 // Scroll to the top of the form container for better user experience 20 this.scrollToFormTop() 21 22 // If we're now on the electives step (step 4), trigger electives loading 23 if (this.currentStep === 4) { 24 this.loadElectivesIfNeeded() 25 } 26 } 27 } 28 29 prev() { 30 if (this.currentStep > 1) { 31 this.stepTargets[this.currentStep - 1].classList.add('hidden') 32 this.stepTargets[this.currentStep - 2].classList.remove('hidden') 33 this.currentStep-- 34 this.updateButtons() 35 36 // Scroll to the top of the form container for better user experience 37 this.scrollToFormTop() 38 39 // If we've navigated back to the electives step, ensure electives are loaded 40 if (this.currentStep === 4) { 41 this.loadElectivesIfNeeded() 42 } 43 } 44 } 45 46 scrollToFormTop() { 47 // Find the form container or the first step element 48 const formContainer = this.element.closest('form') || this.stepTargets[0].closest('form') || this.element 49 50 // Scroll to the top of the form container with smooth behavior 51 formContainer.scrollIntoView({ behavior: 'smooth', block: 'start' }) 52 } 53 54 loadElectivesIfNeeded() { 55 // Find the qualification controller to load electives 56 const qualificationController = this.application.getControllerForElementAndIdentifier( 57 document.querySelector('[data-controller*="qualification"]'), 58 'qualification' 59 ) 60 61 // If we found the controller and it has the loadElectives method, call it 62 if (qualificationController && typeof qualificationController.loadElectives === 'function') { 63 console.log('Wizard controller: triggering electives load for step 4') 64 qualificationController.loadElectives(true) // Force reload 65 } 66 } 67 68 validateCurrentStep() { 69 const currentStepEl = this.stepTargets[this.currentStep - 1] 70 const requiredFields = currentStepEl.querySelectorAll('select[required], input[required]') 71 let isValid = true 72 73 requiredFields.forEach(field => { 74 if (!field.value) { 75 isValid = false 76 this.showError(field) 77 } else { 78 this.clearError(field) 79 } 80 }) 81 82 // Special validation for electives 83 if (this.currentStep === 4) { 84 const checkedElectives = currentStepEl.querySelectorAll('input[type="checkbox"]:checked').length 85 const compulsoryElectives = currentStepEl.querySelectorAll('.compulsory-elective').length 86 const totalElectives = checkedElectives + compulsoryElectives 87 88 if (totalElectives < 2) { 89 alert("Please select at least 2 elective subjects in total") 90 isValid = false 91 } 92 } 93 94 return isValid 95 } 96 97 showError(field) { 98 field.classList.add('border-red-500') 99 const errorMsg = document.createElement('p') 100 errorMsg.className = 'text-red-500 text-sm mt-1' 101 errorMsg.textContent = 'This field is required' 102 103 // Remove any existing error message 104 this.clearError(field) 105 106 field.parentNode.appendChild(errorMsg) 107 } 108 109 clearError(field) { 110 field.classList.remove('border-red-500') 111 const existingError = field.parentNode.querySelector('.text-red-500') 112 if (existingError) { 113 existingError.remove() 114 } 115 } 116 117 updateButtons() { 118 if (this.currentStep === 1) { 119 this.prevButtonTarget.classList.add('hidden') 120 } else { 121 this.prevButtonTarget.classList.remove('hidden') 122 } 123 124 if (this.currentStep === this.totalSteps) { 125 this.nextButtonTarget.classList.add('hidden') 126 this.submitButtonTarget.classList.remove('hidden') 127 } else { 128 this.nextButtonTarget.classList.remove('hidden') 129 this.submitButtonTarget.classList.add('hidden') 130 } 131 132 this.updateNonFormContentVisibility() 133 } 134 135 updateNonFormContentVisibility() { 136 if (!this.hasNonFormContentTarget) return 137 138 const shouldHide = this.currentStep > 1
139 this.nonFormContentTargets.forEach(element => { 140 element.classList.toggle('hidden', shouldHide) 141 }) 142 } 143}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.