PageSourceSearch

https://techbbq.dk/wp-content/themes/techbbq/assets/js/subscribe.js?ver=1.4.165

js techbbq.dk collected 2026-10-02 17:09:57 UTC 5,574 bytes, 232 lines download raw bytes

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.