| 1 |
/** |
| 2 |
* NotificationX Elementor Form — submission handler. |
| 3 |
* |
| 4 |
* Intercepts the form submit, posts Name / Email / Message to the |
| 5 |
* notificationx/v1/popup-submit REST endpoint and shows a success or |
| 6 |
* error message. The selected NX campaign ID is read from data-nx-id. |
| 7 |
*/ |
| 8 |
( function () { |
| 9 |
'use strict'; |
| 10 |
|
| 11 |
// ── Per-session submission guard ────────────────────────────────────── |
| 12 |
// A visitor may submit a given campaign's form only once per browser |
| 13 |
// session; the flag is stored in sessionStorage keyed by campaign id. |
| 14 |
function sessionStore() { |
| 15 |
try { return window.sessionStorage; } catch ( e ) { return null; } |
| 16 |
} |
| 17 |
function submittedKey( nxId ) { |
| 18 |
return 'notificationx_form_submitted_' + nxId; |
| 19 |
} |
| 20 |
function hasSubmitted( nxId ) { |
| 21 |
var store = sessionStore(); |
| 22 |
try { return !! ( store && store.getItem( submittedKey( nxId ) ) ); } |
| 23 |
catch ( e ) { return false; } |
| 24 |
} |
| 25 |
function markSubmitted( nxId ) { |
| 26 |
var store = sessionStore(); |
| 27 |
try { if ( store ) store.setItem( submittedKey( nxId ), '1' ); } |
| 28 |
catch ( e ) {} |
| 29 |
} |
| 30 |
|
| 31 |
// Disable the form's submit button and show an "already submitted" notice. |
| 32 |
function lockForm( form, btn, msgEl ) { |
| 33 |
if ( btn ) btn.disabled = true; |
| 34 |
if ( msgEl && ! msgEl.textContent ) { |
| 35 |
msgEl.classList.add( 'is-success' ); |
| 36 |
msgEl.textContent = form.getAttribute( 'data-already' ) |
| 37 |
|| 'You have already submitted this form.'; |
| 38 |
} |
| 39 |
} |
| 40 |
|
| 41 |
// If the form is rendered inside an Exit Intent popup, ask the popup to |
| 42 |
// close itself (handled in ExitIntentPopup.tsx) shortly after a success. |
| 43 |
function closeExitIntent( form, nxId ) { |
| 44 |
if ( ! form.closest || ! form.closest( '.nx-exit-intent-overlay' ) ) return; |
| 45 |
setTimeout( function () { |
| 46 |
document.dispatchEvent( new CustomEvent( 'nx:exit-intent-close', { |
| 47 |
detail: { nxId: nxId }, |
| 48 |
} ) ); |
| 49 |
}, 1600 ); |
| 50 |
} |
| 51 |
|
| 52 |
function init( form ) { |
| 53 |
if ( ! form || form.__nxBound ) return; |
| 54 |
form.__nxBound = true; |
| 55 |
|
| 56 |
var msgEl = form.querySelector( '.nx-form-message' ); |
| 57 |
var btn = form.querySelector( '.nx-form-submit' ); |
| 58 |
var nxId = form.getAttribute( 'data-nx-id' ); |
| 59 |
|
| 60 |
// Already submitted this session? Lock the form up-front. |
| 61 |
if ( nxId && hasSubmitted( nxId ) ) { |
| 62 |
lockForm( form, btn, msgEl ); |
| 63 |
} |
| 64 |
|
| 65 |
form.addEventListener( 'submit', function ( ev ) { |
| 66 |
ev.preventDefault(); |
| 67 |
|
| 68 |
// Guard against repeat submissions within the same session. |
| 69 |
if ( nxId && hasSubmitted( nxId ) ) { |
| 70 |
lockForm( form, btn, msgEl ); |
| 71 |
return; |
| 72 |
} |
| 73 |
|
| 74 |
if ( msgEl ) { |
| 75 |
msgEl.textContent = ''; |
| 76 |
msgEl.classList.remove( 'is-success', 'is-error' ); |
| 77 |
} |
| 78 |
|
| 79 |
// Native required-field validation. |
| 80 |
if ( typeof form.checkValidity === 'function' && ! form.checkValidity() ) { |
| 81 |
form.reportValidity(); |
| 82 |
return; |
| 83 |
} |
| 84 |
|
| 85 |
var url = form.getAttribute( 'data-rest-url' ); |
| 86 |
if ( ! nxId || ! url ) return; |
| 87 |
|
| 88 |
var payload = { |
| 89 |
nx_id: nxId, |
| 90 |
timestamp: Math.floor( Date.now() / 1000 ), |
| 91 |
}; |
| 92 |
|
| 93 |
var nameEl = form.querySelector( '[name="name"]' ); |
| 94 |
var emailEl = form.querySelector( '[name="email"]' ); |
| 95 |
var messageEl = form.querySelector( '[name="message"]' ); |
| 96 |
|
| 97 |
if ( nameEl ) payload.name = nameEl.value; |
| 98 |
if ( emailEl ) payload.email = emailEl.value; |
| 99 |
if ( messageEl ) payload.message = messageEl.value; |
| 100 |
|
| 101 |
if ( btn ) btn.disabled = true; |
| 102 |
|
| 103 |
// popup-submit is a public endpoint (permission_callback => __return_true) |
| 104 |
// and never verifies a nonce server-side. We deliberately do NOT send an |
| 105 |
// X-WP-Nonce header: a baked-in wp_rest nonce goes stale in the Elementor |
| 106 |
// builder and on cached pages, and WordPress core would then reject the |
| 107 |
// request with "Cookie check failed" (rest_cookie_invalid_nonce). With no |
| 108 |
// nonce, core treats the submission as anonymous and lets it through. |
| 109 |
var headers = { 'Content-Type': 'application/json' }; |
| 110 |
|
| 111 |
fetch( url, { |
| 112 |
method: 'POST', |
| 113 |
headers: headers, |
| 114 |
body: JSON.stringify( payload ), |
| 115 |
} ) |
| 116 |
.then( function ( res ) { |
| 117 |
return res.json().then( function ( body ) { |
| 118 |
return { ok: res.ok && body && body.success, body: body }; |
| 119 |
} ).catch( function () { |
| 120 |
return { ok: res.ok, body: {} }; |
| 121 |
} ); |
| 122 |
} ) |
| 123 |
.then( function ( result ) { |
| 124 |
if ( result.ok ) { |
| 125 |
// Remember the submission for the rest of the session and |
| 126 |
// keep the form locked so it can't be sent again. |
| 127 |
markSubmitted( nxId ); |
| 128 |
if ( btn ) btn.disabled = true; |
| 129 |
if ( msgEl ) { |
| 130 |
msgEl.classList.add( 'is-success' ); |
| 131 |
msgEl.textContent = form.getAttribute( 'data-success' ) || 'Submitted.'; |
| 132 |
} |
| 133 |
form.reset(); |
| 134 |
// When the form lives inside an Exit Intent popup, dismiss it |
| 135 |
// after the success message has had a moment to register. |
| 136 |
closeExitIntent( form, nxId ); |
| 137 |
} else if ( msgEl ) { |
| 138 |
msgEl.classList.add( 'is-error' ); |
| 139 |
msgEl.textContent = ( result.body && result.body.message ) |
| 140 |
|| form.getAttribute( 'data-error' ) |
| 141 |
|| 'Something went wrong.'; |
| 142 |
} |
| 143 |
} ) |
| 144 |
.catch( function () { |
| 145 |
if ( ! msgEl ) return; |
| 146 |
msgEl.classList.add( 'is-error' ); |
| 147 |
msgEl.textContent = form.getAttribute( 'data-error' ) || 'Something went wrong.'; |
| 148 |
} ) |
| 149 |
.finally( function () { |
| 150 |
// Leave the button disabled once a submission has gone through; |
| 151 |
// only re-enable it after a failed attempt. |
| 152 |
if ( btn && ! ( nxId && hasSubmitted( nxId ) ) ) btn.disabled = false; |
| 153 |
} ); |
| 154 |
} ); |
| 155 |
} |
| 156 |
|
| 157 |
function bindAll( root ) { |
| 158 |
( root || document ).querySelectorAll( '.nx-form' ).forEach( init ); |
| 159 |
} |
| 160 |
|
| 161 |
if ( document.readyState === 'loading' ) { |
| 162 |
document.addEventListener( 'DOMContentLoaded', function () { bindAll(); } ); |
| 163 |
} else { |
| 164 |
bindAll(); |
| 165 |
} |
| 166 |
|
| 167 |
// Re-bind in the Elementor editor preview when widgets re-render. |
| 168 |
if ( window.jQuery ) { |
| 169 |
window.jQuery( window ).on( 'elementor/frontend/init', function () { |
| 170 |
if ( window.elementorFrontend && window.elementorFrontend.hooks ) { |
| 171 |
window.elementorFrontend.hooks.addAction( |
| 172 |
'frontend/element_ready/nx-form.default', |
| 173 |
function ( $scope ) { bindAll( $scope[ 0 ] ); } |
| 174 |
); |
| 175 |
} |
| 176 |
} ); |
| 177 |
} |
| 178 |
} )(); |
| 179 |
|