PageSourceSearch

https://remotivatejobs.com/wp-content/themes/front-child/assets/js/calendly-popup.js?ver=1.26.10.08.4

js remotivatejobs.com collected 2026-10-08 23:52:39 UTC 8,590 bytes, 273 lines download raw bytes

1/**
2 * Every Calendly link on the site opens as Calendly's popup widget instead of
3 * a new tab, and a booked SALES call is reported to the dataLayer.
4 *
5 * WHY
6 * A plain link sends the visitor to calendly.com, where they book and usually
7 * never come back. The booking still reaches us — Calendly's webhook tells the
8 * server (wp-job-manager-applications/calendly-webhook.php) — but a server
9 * request has no browser, so Google Ads never hears about it and booked calls
10 * could only be credited by uploading gclids by hand. Inside the popup the
11 * visitor stays on our page, and Calendly posts `calendly.event_scheduled` to
12 * it the moment they book. That message is what this file turns into a
13 * `strategy_call_booked` dataLayer event for GTM.
14 *
15 * WHICH BOOKINGS COUNT
16 * Every calendly.com link opens as a popup — employers' interview links shown
17 * to candidates included — but only the sales-call event types listed by
18 * front_calendly_sales_call_slugs() report a conversion. A candidate booking an
19 * interview is not a sale.
20 *
21 * WHY NOT rjhf.track()
22 * rjhf.track() would also POST the event to the lead endpoint and move the lead
23 * to call_booked from the browser. The webhook already does that, after
24 * checking Calendly's signature and the event type; a browser copy would be a
25 * second writer anyone could call. So this pushes to the dataLayer and the DOM
26 * only, the same way funnel-purchase-events.php reports a sale the server has
27 * already recorded.
28 *
29 * ATTRIBUTION
30 * rjhf-analytics.js rewrites Calendly hrefs in the CAPTURE phase of the click,
31 * adding the stored first-touch UTMs and the gclid (as salesforce_uuid). This
32 * listens in the bubble phase, so it reads the already-decorated href, and the
33 * popup passes the URL's query string through to the webhook unchanged.
34 *
35 * FALLBACK
36 * Modified clicks (Ctrl/Cmd/Shift/middle) keep their normal new-tab behaviour.
37 * If Calendly's widget script cannot load (blocked by an extension, offline),
38 * the link opens the ordinary way, so a booking is never lost to this file —
39 * it just goes untracked in the browser, as every booking did before.
40 */
41(function (window, document) {
42	'use strict';
43
44	var CFG = window.RJ_CALENDLY || {};
45	var WIDGET_JS = 'https://assets.calendly.com/assets/external/widget.js';
46	var WIDGET_CSS = 'https://assets.calendly.com/assets/external/widget.css';
47
48	// Calendly's own product pages, which are not booking pages.
49	var RESERVED = ['app', 'login', 'signup', 'pricing', 'blog', 'resources', 'integrations', 'about', 'legal', 'help'];
50
51	var loading = null;
52	var failed = false;
53
54	// The booking link most recently opened: its event-type slug and when.
55	var current = null;
56	// The product the funnel's own CTA tracking attached to the same click.
57	var lastIntent = null;
58	// Invitee URIs already reported, so a repeated message cannot double-count.
59	var reported = {};
60
61	function bookingUrl(href) {
62		var url;
63
64		try {
65			url = new URL(href, window.location.href);
66		} catch (e) {
67			return null;
68		}
69
70		if (url.hostname !== 'calendly.com' && url.hostname !== 'www.calendly.com') {
71			return null;
72		}
73
74		var parts = url.pathname.split('/').filter(Boolean);
75
76		if (!parts.length || RESERVED.indexOf(parts[0]) !== -1) {
77			return null;
78		}
79
80		return url;
81	}
82
83	function slugFor(url) {
84		var parts = url.pathname.split('/').filter(Boolean);
85
86		return parts.length ? parts[parts.length - 1].toLowerCase() : '';
87	}
88
89	function widgetReady() {
90		return !!(window.Calendly && typeof window.Calendly.initPopupWidget === 'function');
91	}
92
93	function loadWidget() {
94		if (widgetReady()) {
95			return Promise.resolve();
96		}
97
98		if (failed) {
99			return Promise.reject(new Error('Calendly widget unavailable.'));
100		}
101
102		if (loading) {
103			return loading;
104		}
105
106		loading = new Promise(function (resolve, reject) {
107			var css = document.createElement('link');
108			css.rel = 'stylesheet';
109			css.href = WIDGET_CSS;
110			document.head.appendChild(css);
111
112			var script = document.createElement('script');
113			script.src = WIDGET_JS;
114			script.async = true;
115			script.onload = function () {
116				if (widgetReady()) {
117					resolve();
118				} else {
119					reject(new Error('Calendly widget did not initialise.'));
120				}
121			};
122			script.onerror = function () {
123				reject(new Error('Calendly widget failed to load.'));
124			};
125			document.head.appendChild(script);
126
127			// A script stalled by a slow network would otherwise leave the click
128			// doing nothing at all.
129			window.setTimeout(function () {
130				reject(new Error('Calendly widget timed out.'));
131			}, 8000);
132		});
133
134		loading.catch(function () {
135			failed = true;
136		});
137
138		return loading;
139	}
140
141	function openNormally(link, url) {
142		// Still within the click's user activation in practice (the script
143		// fails fast when blocked), so the new tab is normally allowed; if the
144		// browser refuses it, navigate this tab instead rather than do nothing.
145		if (link.target === '_blank') {
146			var win = window.open(url, '_blank');
147			if (win) {
148				win.opener = null;
149				return;
150			}
151		}
152
153		window.location.assign(url);
154	}
155
156	document.addEventListener('click', function (e) {
157		if (e.defaultPrevented || e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) {
158			return;
159		}
160
161		var link = e.target.closest && e.target.closest('a[href]');
162		var url = link ? bookingUrl(link.href) : null;
163
164		if (!url) {
165			return;
166		}
167
168		e.preventDefault();
169
170		var href = url.toString();
171		current = { slug: slugFor(url), at: Date.now() };
172
173		loadWidget().then(function () {
174			window.Calendly.initPopupWidget({ url: href });
175		}, function () {
176			openNormally(link, href);
177		});
178	});
179
180	// The wizard's "Talk to a Pro first" and funnel-cta-tracking.js both emit
181	// strategy_call_clicked with the product the call is about — more precise
182	// than the page's own declaration on /hire/, which covers every tier.
183	document.addEventListener('rjhf:strategy_call_clicked', function (e) {
184		var product = e.detail && e.detail.product;
185		if (product) {
186			lastIntent = { product: String(product), at: Date.now() };
187		}
188	});
189
190	function productFor(booking) {
191		if (lastIntent && Math.abs(lastIntent.at - booking.at) < 3000) {
192			return lastIntent.product;
193		}
194
195		return (CFG.slugProducts || {})[booking.slug] || CFG.product || '';
196	}
197
198	window.addEventListener('message', function (e) {
199		if (e.origin !== 'https://calendly.com' || !e.data || e.data.event !== 'calendly.event_scheduled') {
200			return;
201		}
202
203		var payload = e.data.payload || {};
204		var invitee = (payload.invitee && payload.invitee.uri) || '';
205
206		if (invitee) {
207			if (reported[invitee]) {
208				return;
209			}
210			reported[invitee] = true;
211		}
212
213		if (!current || (CFG.salesSlugs || []).indexOf(current.slug) === -1) {
214			return;
215		}
216
217		var attribution = (window.rjhf && typeof window.rjhf.attribution === 'function') ? window.rjhf.attribution() : {};
218		var event = {
219			event: 'strategy_call_booked',
220			funnel_product: productFor(current),
221			funnel_page: CFG.page || '',
222			funnel_landing_page: attribution.landing_page || '',
223			funnel_source: attribution.utm_source || '',
224			calendly_event_type: current.slug
225		};
226
227		// Staff bookings still open the popup; they just aren't reported, the
228		// same rule as the container itself and the purchase events.
229		if (CFG.report) {
230			window.dataLayer = window.dataLayer || [];
231			window.dataLayer.push(event);
232		}
233
234		document.dispatchEvent(new CustomEvent('rjhf:strategy_call_booked', { detail: event }));
235	});
236
237	// Fetch the widget ahead of the click wherever the page already has a
238	// booking link, so the popup opens instantly — a mobile tap leaves no time
239	// to load it between touch and click. Links rendered later (Alpine modals)
240	// load it on first hover/focus/touch, or on the click itself.
241	function hasBookingLink() {
242		var links = document.querySelectorAll('a[href*="calendly.com/"]');
243		for (var i = 0; i < links.length; i++) {
244			if (bookingUrl(links[i].href)) {
245				return true;
246			}
247		}
248		return false;
249	}
250
251	function warm(e) {
252		var link = e.target && e.target.closest ? e.target.closest('a[href*="calendly.com/"]') : null;
253		if (link && bookingUrl(link.href)) {
254			loadWidget().catch(function () {});
255		}
256	}
257
258	document.addEventListener('pointerover', warm, { passive: true });
259	document.addEventListener('focusin', warm);
260	document.addEventListener('touchstart', warm, { passive: true });
261
262	function preload() {
263		if (hasBookingLink()) {
264			loadWidget().catch(function () {});
265		}
266	}
267
268	if (document.readyState === 'loading') {
269		document.addEventListener('DOMContentLoaded', preload);
270	} else {
271		preload();
272	}
273})(window, document);

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.