1/** 2 * TechBBQ Subscribe â custom form submission via REST â HubSpot. 3 */ 4( function () { 5 'use strict'; 6 7 const SELECTORS = { 8 root: '.techbbq-subscribe', 9 form: '.techbbq-subscribe__form-inner', 10 firstname: '[name="firstname"]', 11 email: '[name="email"]', 12 lists: '[name="lists[]"]', 13 consent: '[name="website_permission"]', 14 hp: '[name="hp_field"]', 15 submit: '.techbbq-input-with-btn__submit', 16 message: '.techbbq-subscribe__message', 17 }; 18 19 const setMessage = ( root, text, type ) => { 20 const message = root.querySelector( SELECTORS.message ); 21 22 if ( ! message ) { 23 return; 24 } 25 26 message.hidden = ! text; 27 message.textContent = text || ''; 28 message.classList.remove( 29 'techbbq-subscribe__message--success', 30 'techbbq-subscribe__message--error' 31 ); 32 33 if ( text ) { 34 message.classList.add( 35 'techbbq-subscribe__message--' + ( type === 'success' ? 'success' : 'error' ) 36 ); 37 } 38 }; 39 40 const clearFieldErrors = ( form ) => { 41 form.querySelectorAll( '.techbbq-subscribe__field-error' ).forEach( ( node ) => { 42 node.remove(); 43 } ); 44 }; 45 46 const setFieldError = ( field, text ) => { 47 if ( ! field || ! text ) { 48 return; 49 } 50 51 const error = document.createElement( 'span' ); 52 error.className = 'techbbq-subscribe__field-error'; 53 error.textContent = text; 54 field.insertAdjacentElement( 'afterend', error ); 55 }; 56 57 const getConfig = ( root ) => { 58 const raw = root.dataset.subscribeConfig; 59 60 if ( ! raw ) { 61 return null; 62 } 63 64 try { 65 return JSON.parse( raw ); 66 } catch ( error ) { 67 return null; 68 } 69 }; 70 71 const getSelectedLists = ( form ) => 72 Array.from( form.querySelectorAll( `${ SELECTORS.lists }:checked` ) ).map( 73 ( input ) => input.value 74 ); 75 76 const validateClient = ( form, config ) => { 77 clearFieldErrors( form ); 78 79 const firstname = form.querySelector( SELECTORS.firstname ); 80 const email = form.querySelector( SELECTORS.email ); 81 const messages = config.messages || {}; 82 let valid = true; 83 84 if ( ! firstname?.value.trim() ) { 85 setFieldError( firstname?.closest( '.techbbq-input' ), messages.nameRequired ); 86 valid = false; 87 } 88 89 if ( ! email?.value.trim() || ! email.checkValidity() ) { 90 setFieldError( email?.closest( '.techbbq-input' ), messages.emailInvalid ); 91 valid = false; 92 } 93 94 if ( 0 === getSelectedLists( form ).length ) { 95 setFieldError( 96 form.querySelector( '.techbbq-subscribe__options' ), 97 messages.listsRequired 98 ); 99 valid = false; 100 } 101 102 // GDPR: the user must actively tick this. Never default it to true. 103 if ( ! form.querySelector( SELECTORS.consent )?.checked ) { 104 setFieldError( 105 form.querySelector( '.techbbq-subscribe__consent' ), 106 messages.consentRequired 107 ); 108 valid = false; 109 } 110 111 return valid; 112 }; 113 114 const handleSubmit = async ( event ) => { 115 event.preventDefault(); 116 117 const form = event.currentTarget; 118 const root = form.closest( SELECTORS.root ); 119 const config = getConfig( root ); 120 121 if ( ! root || ! config?.endpoint ) { 122 return; 123 } 124 125 setMessage( root, '', '' ); 126 127 if ( ! validateClient( form, config ) ) { 128 return; 129 } 130 131 root.classList.add( 'is-submitting' ); 132 133 const payload = { 134 firstname: form.querySelector( SELECTORS.firstname )?.value.trim() || '', 135 email: form.querySelector( SELECTORS.email )?.value.trim() || '', 136 lists: getSelectedLists( form ), 137 portal_id: config.portalId, 138 form_id: config.formId, 139 page_uri: config.pageUri || window.location.href, 140 page_name: config.pageName || document.title, 141 website_permission: true === form.querySelector( SELECTORS.consent )?.checked, 142 hp_field: form.querySelector( SELECTORS.hp )?.value || '', 143 }; 144 145 try { 146 const response = await fetch( config.endpoint, { 147 method: 'POST', 148 headers: { 149 'Content-Type': 'application/json', 150 }, 151 body: JSON.stringify( payload ), 152 } ); 153 154 const data = await response.json().catch( () => ( {} ) ); 155 156 if ( ! response.ok ) { 157 const message = 158 data?.message || 159 config.messages?.genericError || 160 'Unable to submit the form. Please try again.'; 161 setMessage( root, message, 'error' ); 162 return; 163 } 164 165 form.reset(); 166 const techbbqList = form.querySelector( `${ SELECTORS.lists }[value="techbbq"]` ); 167 168 if ( techbbqList ) { 169 techbbqList.checked = true; 170 } 171 172 setMessage( 173 root, 174 data?.message || config.messages?.success || 'Thank you for subscribing.', 175 'success' 176 ); 177 } catch ( error ) { 178 setMessage( 179 root, 180 config.messages?.genericError || 'Unable to submit the form. Please try again.', 181 'error' 182 ); 183 } finally { 184 root.classList.remove( 'is-submitting' ); 185 } 186 }; 187 188 const initWidget = ( root ) => { 189 if ( ! root || root.dataset.subscribeInit === 'true' ) { 190 return; 191 } 192 193 const form = root.querySelector( SELECTORS.form ); 194 195 if ( ! form ) { 196 return; 197 } 198 199 form.addEventListener( 'submit', handleSubmit ); 200 root.dataset.subscribeInit = 'true'; 201 }; 202 203 const boot = () => { 204 document.querySelectorAll( SELECTORS.root ).forEach( initWidget ); 205 }; 206 207 if ( document.readyState === 'loading' ) { 208 document.addEventListener( 'DOMContentLoaded', boot ); 209 } else { 210 boot(); 211 } 212 213 const registerElementorHook = () => { 214 if ( ! window.elementorFrontend?.hooks ) { 215 return; 216 } 217 218 window.elementorFrontend.hooks.addAction( 219 'frontend/element_ready/techbbq-subscribe.default', 220 ( $scope ) => { 221 const root = $scope[0]?.querySelector( SELECTORS.root ) || $scope[0]; 222 223 if ( root ) { 224 initWidget( root ); 225 } 226 } 227 ); 228 }; 229 230 window.addEventListener( 'elementor/frontend/init', registerElementorHook ); 231 registerElementorHook(); 232}() );
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.