/** * RedCommerce Multi-Step CF7 v2 * Checkmark animation · Review panel · Retry button * Test mode: add ?rc_test=1 to skip validation. */ function rcInitAll() { // Only init containers that haven't been initialized yet. // CF7 page-cache AJAX replaces the form HTML, so new elements won't have the flag. document.querySelectorAll( '.rc-multistep:not([data-rc-init])' ).forEach( function ( c ) { c.setAttribute( 'data-rc-init', '1' ); initMultistep( c ); } ); } document.addEventListener( 'DOMContentLoaded', rcInitAll ); // Fallback: some themes (Salient/WPBakery) delay script execution past DOMContentLoaded. window.addEventListener( 'load', rcInitAll ); // CF7 replaces form HTML from page cache — re-init any new .rc-multistep elements. document.addEventListener( 'wpcf7:init', rcInitAll ); function trackFormEvent( eventType ) { var cfg = window.rcPopupConfig || {}; if ( ! cfg.ajaxUrl || ! cfg.trackNonce ) { return; } var attrJson = window.rcPopupAttrJson ? window.rcPopupAttrJson() : ''; var data = 'action=rc_popup_track' + '&nonce=' + encodeURIComponent( cfg.trackNonce ) + '&event=' + encodeURIComponent( eventType ) + ( attrJson ? '&attr=' + encodeURIComponent( attrJson ) : '' ); if ( navigator.sendBeacon && ( eventType === 'form_abandon' ) ) { var fd = new FormData(); fd.append( 'action', 'rc_popup_track' ); fd.append( 'nonce', cfg.trackNonce ); fd.append( 'event', eventType ); if ( attrJson ) { fd.append( 'attr', attrJson ); } navigator.sendBeacon( cfg.ajaxUrl, fd ); } else { var xhr = new XMLHttpRequest(); xhr.open( 'POST', cfg.ajaxUrl, true ); xhr.setRequestHeader( 'Content-Type', 'application/x-www-form-urlencoded' ); xhr.send( data ); } } function initMultistep( container ) { // Unique token for this init call. Stamped on every choice item so that if // this container is re-initialized (e.g. after CF7 page-cache reload), the // previous init's click handlers become no-ops (token mismatch → early return). var instanceToken = 'rc-' + Date.now() + '-' + Math.random().toString( 36 ).slice( 2 ); container.setAttribute( 'data-rc-instance', instanceToken ); var steps = container.querySelectorAll( '.rc-step' ); var circles = container.querySelectorAll( '.rc-step-circle' ); var lines = container.querySelectorAll( '.rc-step-line-fill' ); var btnNext = container.querySelector( '.rc-btn-next' ); var btnBack = container.querySelector( '.rc-btn-back' ); var btnText = btnNext && btnNext.querySelector( '.rc-btn-text' ); var successMsg = container.querySelector( '.rc-success-message' ); var btnGroup = container.querySelector( '.rc-btn-group' ); var totalSteps = steps.length; var current = 0; var inReview = false; var reviewPanel = null; var testMode = /[?&]rc_test=1/.test( window.location.search ); var formStarted = false; var formCompleted = false; var autoAdvancing = false; var DEBUG = /[?&]rc_debug=1/.test( window.location.search ); if ( ! totalSteps || ! btnNext ) { return; } // ── Debug: log step contents ──────────────────────────────────────── if ( DEBUG ) { console.group( '[RC] initMultistep — ' + totalSteps + ' steps, ' + circles.length + ' circles' ); steps.forEach( function ( s, i ) { var inputs = s.querySelectorAll( 'input:not(.rc-choice-radio):not([type="hidden"]):not([type="submit"])' ); var selects = s.querySelectorAll( 'select' ); var texts = s.querySelectorAll( 'textarea' ); var choices = s.querySelectorAll( '.rc-choice-group' ); console.log( '[RC] Step ' + (i+1) + ': ' + inputs.length + ' inputs, ' + selects.length + ' selects, ' + texts.length + ' textareas, ' + choices.length + ' rc_choice groups' ); } ); console.groupEnd(); } // Hide paragraphs wrapping the hidden CF7 submit button (browsers without :has() support). container.querySelectorAll( '.rc-step input[type="submit"], .rc-step .wpcf7-submit' ).forEach( function ( sub ) { var p = sub.closest( 'p' ); if ( p ) { p.style.cssText = 'display:none!important;margin:0!important;'; } } ); // Force-hide all steps with inline !important so no theme CSS can interfere. steps.forEach( function ( s ) { setStepVisible( s, false ); } ); detectThemeStyles( container ); showStep( 0 ); // Guard: Salient/WPBakery JS can reset inline styles after we set them. // Watch for any style attribute change in the container and re-enforce the // active step AND its child

/wrap elements. var stepGuard = false; var guardObserver = new MutationObserver( function () { if ( stepGuard ) { return; } var active = container.querySelector( '.rc-step.active' ); if ( ! active ) { return; } // Check the step itself. var broken = false; var disp = active.style.getPropertyValue( 'display' ); var prio = active.style.getPropertyPriority( 'display' ); if ( disp !== 'block' || prio !== 'important' ) { broken = true; } // Also check child

and wrap elements — Salient's scroll-reveal // hides these even when the parent step is fine. if ( ! broken ) { var kids = active.querySelectorAll( 'p, .wpcf7-form-control-wrap' ); for ( var k = 0; k < kids.length; k++ ) { var s = kids[ k ].style; if ( s.opacity === '0' || s.visibility === 'hidden' || s.display === 'none' ) { broken = true; break; } } } if ( broken ) { stepGuard = true; setStepVisible( active, true ); setTimeout( function () { stepGuard = false; }, 100 ); } // Guard: if Salient re-wraps a native select with Select2, strip it again. active.querySelectorAll( 'select[data-rc-native]' ).forEach( function ( sel ) { if ( sel.classList.contains( 'select2-hidden-accessible' ) ) { if ( window.jQuery ) { var $s = window.jQuery( sel ); if ( $s.data( 'select2' ) ) { try { $s.select2( 'destroy' ); } catch(e){} } } var p = sel.parentNode; if ( p ) { p.querySelectorAll( '.select2-container' ).forEach( function(c){ c.remove(); } ); } sel.classList.remove( 'select2-hidden-accessible' ); sel.removeAttribute( 'aria-hidden' ); sel.style.cssText = 'display:block!important;visibility:visible!important;width:100%!important;opacity:1!important;position:static!important;clip:auto!important;'; } } ); } ); guardObserver.observe( container, { attributes: true, attributeFilter: [ 'style', 'class' ], childList: true, subtree: true } ); // CF7 AJAX events var cf7Form = container.closest( 'form' ); if ( cf7Form ) { cf7Form.addEventListener( 'wpcf7mailsent', onMailSent ); cf7Form.addEventListener( 'wpcf7mailfailed', onMailFailed ); // Hide CF7 default response output — we use our own success message var cfResp = cf7Form.querySelector( '.wpcf7-response-output' ); if ( cfResp ) { cfResp.style.display = 'none'; } // Prevent CF7 from resetting the form after success — it causes a visual flash. cf7Form.addEventListener( 'wpcf7submit', function ( e ) { if ( successMsg && successMsg.classList.contains( 'active' ) ) { e.stopImmediatePropagation(); // Re-hide any CF7 response output that appears. setTimeout( function () { var resp = cf7Form.querySelector( '.wpcf7-response-output' ); if ( resp ) { resp.style.display = 'none'; } }, 50 ); } } ); } btnNext.addEventListener( 'click', onNextClick ); if ( btnBack ) { btnBack.addEventListener( 'click', onBackClick ); } // Track abandon when popup close button is clicked after form has been started. var popupCloseBtn = document.getElementById( 'rc-popup-close' ); if ( popupCloseBtn && container.closest( '#rc-popup' ) ) { popupCloseBtn.addEventListener( 'click', function () { if ( formStarted && ! formCompleted ) { trackFormEvent( 'form_abandon' ); } } ); } // Track abandon on page leave (sendBeacon for reliability). window.addEventListener( 'beforeunload', function () { if ( formStarted && ! formCompleted ) { trackFormEvent( 'form_abandon' ); } } ); container.addEventListener( 'keydown', function ( e ) { if ( e.key === 'Enter' ) { e.preventDefault(); btnNext.click(); } } ); container.addEventListener( 'input', clearError ); // ── rc_choice card selection ───────────────────────────────────────── container.querySelectorAll( '.rc-choice-group' ).forEach( function ( group ) { group.querySelectorAll( '.rc-choice-item' ).forEach( function ( item ) { // Stamp the item with this init's token. If re-initialized later, // the stamp changes and this closure's handler becomes a no-op. item.setAttribute( 'data-rc-owner', instanceToken ); item.addEventListener( 'click', function () { // Only handle clicks owned by THIS init instance. if ( item.getAttribute( 'data-rc-owner' ) !== instanceToken ) { return; } if ( ! container.contains( item ) ) { return; } // Remove selected from siblings in same group. group.querySelectorAll( '.rc-choice-item' ).forEach( function ( sib ) { sib.classList.remove( 'selected' ); } ); item.classList.add( 'selected' ); // Explicitly check the hidden radio so :checked state is set in DOM. var radio = item.querySelector( '.rc-choice-radio' ); if ( radio ) { radio.checked = true; } // Clear any validation error on the group. var w = group.closest( '.wpcf7-form-control-wrap' ); if ( w ) { w.classList.remove( 'wpcf7-not-valid' ); var tip = w.querySelector( '.wpcf7-not-valid-tip' ); if ( tip ) { tip.remove(); } } group.classList.remove( 'wpcf7-not-valid' ); // Auto-advance: if the current step contains only rc_choice groups // (no text/email/tel inputs), move to next step automatically. // Re-query steps so we work with the live container DOM. var step = container.querySelectorAll( '.rc-step' )[ current ]; if ( step && step.contains( group ) && container.contains( group ) ) { var otherInputs = step.querySelectorAll( 'input:not(.rc-choice-radio):not([type="hidden"]):not([type="submit"]), select, textarea' ); if ( DEBUG ) { console.log( '[RC] rc_choice click — step ' + (current+1) + ', otherInputs=' + otherInputs.length + ', autoAdvancing=' + autoAdvancing ); } if ( otherInputs.length === 0 && ! autoAdvancing ) { autoAdvancing = true; if ( DEBUG ) { console.log( '[RC] → auto-advance queued from step ' + (current+1) ); } setTimeout( function () { onNextClick(); autoAdvancing = false; }, 120 ); } } } ); } ); } ); // ── Event handlers ────────────────────────────────────────────────── function onNextClick() { if ( DEBUG ) { console.log( '[RC] onNextClick — current=' + current + ', inReview=' + inReview ); } if ( inReview ) { submitForm(); return; } if ( ! testMode && ! validateCurrentStep( steps[ current ] ) ) { if ( DEBUG ) { console.log( '[RC] → validation failed for step ' + (current+1) ); } return; } if ( current === 0 && ! formStarted ) { formStarted = true; trackFormEvent( 'form_start' ); } // Single-step form: animate to confirmation step, then submit. if ( totalSteps === 2 && container.querySelector( '.rc-step-confirmation' ) ) { animateComplete( 0, function () { showStep( 1 ); submitForm(); } ); return; } // Capture next index by value — prevents closure bugs when // onNextClick fires more than once before the callback runs. var next = current + 1; if ( DEBUG ) { console.log( '[RC] → advancing to step ' + (next+1) ); } if ( current < totalSteps - 1 ) { animateComplete( current, function () { showStep( next ); } ); } else { animateComplete( current, showReview ); } } function onBackClick() { if ( inReview ) { hideReview(); return; } if ( current > 0 ) { clearStepFields( steps[ current ] ); showStep( current - 1 ); } } function clearStepFields( stepEl ) { stepEl.querySelectorAll( 'input:not([type=hidden]):not([type=submit]), select, textarea' ).forEach( function ( field ) { if ( field.type === 'radio' || field.type === 'checkbox' ) { field.checked = false; } else { field.value = ''; } } ); stepEl.querySelectorAll( '.rc-choice-item' ).forEach( function ( item ) { item.classList.remove( 'selected' ); } ); stepEl.querySelectorAll( '.wpcf7-not-valid' ).forEach( function ( el ) { el.classList.remove( 'wpcf7-not-valid' ); } ); stepEl.querySelectorAll( '.wpcf7-not-valid-tip' ).forEach( function ( tip ) { tip.remove(); } ); } function onMailSent() { formCompleted = true; trackFormEvent( 'form_complete' ); if ( typeof gtag === 'function' ) { gtag( 'event', 'booking_request', { event_category: 'leads', event_label: 'contact_form' }); } // Success already visible (single-step fires it during transition). if ( successMsg && successMsg.classList.contains( 'active' ) ) { return; } showSuccess(); } function onMailFailed() { trackFormEvent( 'form_fail' ); btnNext.disabled = false; if ( btnBack ) { btnBack.disabled = false; } if ( btnText ) { btnText.textContent = 'Verstuur'; } // Show CF7 error message if ( cf7Form ) { var resp = cf7Form.querySelector( '.wpcf7-response-output' ); if ( resp ) { resp.style.display = ''; } } } // ── Step navigation ───────────────────────────────────────────────── function setStepVisible( stepEl, visible ) { if ( ! stepEl ) { return; } if ( visible ) { stepEl.style.setProperty( 'display', 'block', 'important' ); stepEl.style.setProperty( 'visibility', 'visible', 'important' ); stepEl.style.setProperty( 'height', 'auto', 'important' ); stepEl.style.setProperty( 'max-height', 'none', 'important' ); stepEl.style.setProperty( 'overflow', 'visible', 'important' ); stepEl.style.setProperty( 'opacity', '1', 'important' ); stepEl.style.setProperty( 'clip', 'auto', 'important' ); stepEl.style.setProperty( 'position', 'static', 'important' ); stepEl.style.setProperty( 'transform', 'none', 'important' ); stepEl.style.setProperty( 'clip-path', 'none', 'important' ); stepEl.removeAttribute( 'data-animate-in-effect' ); // Force-show

wrappers — Salient hides them inside .wpcf7-form fieldset stepEl.querySelectorAll( 'p, span.wpcf7-form-control-wrap, .wpcf7-form-control-wrap' ).forEach( function ( el ) { // Skip the

wrapping the CF7 submit button — it must stay hidden. if ( el.tagName === 'P' && ( el.querySelector( 'input[type="submit"], .wpcf7-submit' ) ) ) { el.style.setProperty( 'display', 'none', 'important' ); el.style.setProperty( 'margin', '0', 'important' ); return; } el.style.setProperty( 'display', 'block', 'important' ); el.style.setProperty( 'visibility', 'visible', 'important' ); el.style.setProperty( 'opacity', '1', 'important' ); el.style.setProperty( 'height', 'auto', 'important' ); // Enforce tight spacing so theme margins don't create large gaps. if ( el.tagName === 'P' ) { el.style.setProperty( 'margin-top', '0', 'important' ); el.style.setProperty( 'margin-bottom', '10px', 'important' ); el.style.setProperty( 'padding-bottom', '0', 'important' ); } } ); } else { stepEl.style.setProperty( 'display', 'none', 'important' ); stepEl.style.setProperty( 'visibility', 'hidden', 'important' ); stepEl.style.setProperty( 'height', '0', 'important' ); stepEl.style.setProperty( 'overflow', 'hidden', 'important' ); stepEl.style.setProperty( 'opacity', '0', 'important' ); } } function showStep( index ) { if ( DEBUG ) { console.log( '[RC] showStep(' + index + '), current was ' + current ); } // Re-query live — WPBakery/Salient may have replaced inner DOM nodes. steps = container.querySelectorAll( '.rc-step' ); circles = container.querySelectorAll( '.rc-step-circle' ); lines = container.querySelectorAll( '.rc-step-line-fill' ); // Remove .active BEFORE hiding so the observer does not try to restore it. steps[ current ].classList.remove( 'active' ); setStepVisible( steps[ current ], false ); steps[ index ].classList.add( 'active' ); setStepVisible( steps[ index ], true ); current = index; updateProgress(); updateButtons(); scrollToForm(); // Re-enforce visibility after scroll — Salient's scroll-reveal animation // detects newly visible elements and resets their opacity/transform. // It only fires ONCE per element, which is why back→next works on retry. var target = steps[ index ]; requestAnimationFrame( function () { setStepVisible( target, true ); } ); setTimeout( function () { setStepVisible( target, true ); }, 50 ); setTimeout( function () { setStepVisible( target, true ); }, 200 ); setTimeout( function () { setStepVisible( target, true ); }, 500 ); // Fix Select2/Fancy Select — strip it and use native