1(function () { 2 'use strict'; 3 4 // Guard: prevent the script running twice if somehow enqueued or concatenated twice. 5 if (window._brandoUtmInit) { return; } 6 window._brandoUtmInit = true; 7 8 // Cookie lifetime comes from PHP settings (wp_localize_script). Fall back to 90 days. 9 // v1.5.0 10 var COOKIE_DAYS = (window.brandoUtmTrackerSettings && window.brandoUtmTrackerSettings.cookieDays) 11 ? parseInt(window.brandoUtmTrackerSettings.cookieDays, 10) 12 : 90; 13 var COOKIE_PREFIX = 'gf-utm-'; 14 15 // Search engine hostnames. Key = substring to match in hostname, Value = display name. 16 var SEARCH_ENGINES = { 17 'google': 'Google', 18 'bing': 'Bing', 19 'yahoo': 'Yahoo', 20 'duckduckgo': 'DuckDuckGo', 21 'baidu': 'Baidu', 22 'yandex': 'Yandex', 23 'ecosia': 'Ecosia', 24 'ask.com': 'Ask', 25 }; 26 27 // Social / referral hostnames. Key = exact hostname without www., Value = display name. 28 var SOCIAL_DOMAINS = { 29 'facebook.com': 'Facebook', 30 'fb.com': 'Facebook', 31 'instagram.com': 'Instagram', 32 'linkedin.com': 'LinkedIn', 33 'lnkd.in': 'LinkedIn', 34 'twitter.com': 'X (Twitter)', 35 'x.com': 'X (Twitter)', 36 't.co': 'X (Twitter)', 37 'youtube.com': 'YouTube', 38 'youtu.be': 'YouTube', 39 'tiktok.com': 'TikTok', 40 'vm.tiktok.com': 'TikTok', 41 'snapchat.com': 'Snapchat', 42 'reddit.com': 'Reddit', 43 'out.reddit.com': 'Reddit', 44 'pinterest.com': 'Pinterest', 45 'threads.net': 'Threads', 46 }; 47 48 // ------------------------------------------------------------------------- 49 // Cookie helpers 50 // ------------------------------------------------------------------------- 51 52 function getCookie(name) { 53 return Cookies.get(COOKIE_PREFIX + name); 54 } 55 56 function setCookie(name, value) { 57 if (value !== null && value !== undefined && value !== '') { 58 Cookies.set(COOKIE_PREFIX + name, String(value), { expires: COOKIE_DAYS }); 59 } 60 } 61 62 function getHostname(url) { 63 try { 64 return new URL(url).hostname.replace(/^www\./, ''); 65 } catch (e) { 66 return ''; 67 } 68 } 69 70 // ------------------------------------------------------------------------- 71 // Source label builder 72 // 73 // Priority: 74 // 1. utm_source (with medium-aware labels for Google CPC, Facebook Paid, etc.) 75 // 2. Platform click IDs (gclid, fbclid, ttclid, msclkid, li_fat_id, twclid) 76 // 3. Referrer â search engine organic 77 // 4. Referrer â known social domain 78 // 5. Referrer â unknown domain â "Referral: hostname" 79 // 6. Nothing â "Direct" 80 // ------------------------------------------------------------------------- 81 82 function buildSource(params, referrer) { 83 if (params.utm_source) { 84 var src = params.utm_source; 85 var srcL = src.toLowerCase(); 86 var medium = (params.utm_medium || '').toLowerCase(); 87 var paidMedia = ['cpc', 'ppc', 'paid', 'paidsocial', 'paid_social', 'social_paid']; 88 var organicMedia = ['organic']; 89 var socialMedia = ['social', 'social-organic']; 90 91 var isPaid = paidMedia.indexOf(medium) !== -1; 92 var isOrganic = organicMedia.indexOf(medium) !== -1; 93 var isSocial = socialMedia.indexOf(medium) !== -1; 94 95 // Google-specific labels 96 if (srcL === 'google' || srcL === 'google_ads' || srcL === 'google ads') { 97 if (isPaid) return 'Google CPC'; 98 if (isOrganic) return 'Google Organic'; 99 if (medium === 'display') return 'Google Display'; 100 if (medium === 'video') return 'Google Video'; 101 if (medium) return 'Google / ' + params.utm_medium; 102 return 'Google'; 103 } 104 105 // Facebook / Meta-specific labels 106 if (srcL === 'facebook' || srcL === 'fb' || srcL === 'meta') { 107 if (isPaid || isSocial) return 'Facebook Paid'; 108 if (isOrganic) return 'Facebook Organic'; 109 if (medium) return 'Facebook / ' + params.utm_medium; 110 return 'Facebook'; 111 } 112 113 // Instagram 114 if (srcL === 'instagram' || srcL === 'ig') { 115 if (isPaid || isSocial) return 'Instagram Paid'; 116 if (isOrganic) return 'Instagram Organic'; 117 return 'Instagram'; 118 } 119 120 // Generic fallback for any other utm_source 121 if (isPaid) return src + ' Ads'; 122 if (isOrganic) return src + ' Organic'; 123 if (medium) return src + ' / ' + params.utm_medium; 124 return src; 125 } 126 127 // Click ID inference (platform pixels strip referrer header on redirect) 128 if (params.gclid || params.gbraid) { return 'Google Ads'; } 129 if (params.fbclid) { return 'Facebook Ads'; } 130 if (params.ttclid) { return 'TikTok Ads'; } 131 if (params.msclkid) { return 'Microsoft Ads'; } 132 if (params.li_fat_id) { return 'LinkedIn Ads'; } 133 if (params.twclid) { return 'X (Twitter) Ads'; } 134 135 // Referrer-based detection 136 if (referrer) { 137 var hostname = getHostname(referrer); 138 var ownHostname = window.location.hostname.replace(/^www\./, ''); 139 140 // Same-site navigation is not a referral. 141 if (hostname && hostname === ownHostname) { 142 return 'Direct'; 143 } 144 145 if (hostname) { 146 // Search engines â Organic 147 for (var engine in SEARCH_ENGINES) { 148 if (hostname.indexOf(engine) !== -1) { 149 return SEARCH_ENGINES[engine] + ' Organic'; 150 } 151 } 152 // Social networks 153 for (var domain in SOCIAL_DOMAINS) { 154 if (hostname === domain || hostname.slice(-(domain.length + 1)) === '.' + domain) { 155 return SOCIAL_DOMAINS[domain]; 156 } 157 } 158 // Unknown referral 159 return 'Referral: ' + hostname; 160 } 161 } 162 163 return 'Direct'; 164 } 165 166 // ------------------------------------------------------------------------- 167 // Channel classifier â maps a detailed source label to a broad channel. 168 // Paid | Organic | Social | Direct | Referral | Other 169 // ------------------------------------------------------------------------- 170 171 var SOCIAL_PLATFORMS = [ 172 'Facebook', 'Instagram', 'LinkedIn', 'X (Twitter)', 'YouTube', 173 'TikTok', 'Snapchat', 'Reddit', 'Pinterest', 'Threads', 174 ]; 175
176 function buildChannel(source) { 177 if (isPaidCampaign(source)) { return 'Paid'; } 178 if (source.indexOf('Organic') !== -1) { return 'Organic'; } 179 for (var i = 0; i < SOCIAL_PLATFORMS.length; i++) { 180 if (source.indexOf(SOCIAL_PLATFORMS[i]) !== -1) { return 'Social'; } 181 } 182 if (source === 'Direct') { return 'Direct'; } 183 if (source.indexOf('Referral:') === 0) { return 'Referral'; } 184 return 'Other'; 185 } 186 187 // ------------------------------------------------------------------------- 188 // Read Google Analytics client ID from the _ga cookie. 189 // Works for both GA4 and Universal Analytics â both use the same cookie 190 // format: GA1.X.XXXXXXXXXX.XXXXXXXXXX (client ID = last two segments). 191 // Falls back to ga.getAll() for Universal Analytics tracker objects. 192 // ------------------------------------------------------------------------- 193 194 function readGAClientId() { 195 var gaCookie = Cookies.get('_ga') || ''; 196 if (gaCookie) { 197 var parts = gaCookie.split('.'); 198 if (parts.length >= 4) { return parts.slice(-2).join('.'); } 199 } 200 try { 201 if (typeof ga === 'function' && typeof ga.getAll === 'function') { 202 var trackers = ga.getAll(); 203 if (trackers.length > 0) { return trackers[0].get('clientId') || ''; } 204 } 205 } catch (e) {} 206 return ''; 207 } 208 209 // ------------------------------------------------------------------------- 210 // Device type detection â returns 'Mobile', 'Tablet', or 'Desktop'. 211 // ------------------------------------------------------------------------- 212 213 function detectDeviceType() { 214 var ua = navigator.userAgent; 215 if (/(tablet|ipad|playbook|silk)|(android(?!.*mobi))/i.test(ua)) { return 'Tablet'; } 216 if (/Mobile|Android|iP(hone|od)|IEMobile|BlackBerry|Kindle|Silk-Accelerated|(hpw|web)OS|Opera M(obi|ini)/i.test(ua)) { return 'Mobile'; } 217 return 'Desktop'; 218 } 219 220 // ------------------------------------------------------------------------- 221 // Is a source label a paid campaign (vs organic / direct / referral)? 222 // Used to decide whether to add the source to the Campaign Attribution field. 223 // ------------------------------------------------------------------------- 224 225 var PAID_PLATFORMS = [ 226 'Google CPC', 'Google Display', 'Google Video', 'Google Ads', 'Google PMax / Broad', 227 'Facebook Paid', 'Facebook Ads', 228 'Instagram Paid', 'Instagram Ads', 229 'TikTok Ads', 230 'Microsoft Ads', 231 'LinkedIn Ads', 232 'X (Twitter) Ads', 233 'Reddit Ads', 234 'Snapchat Ads', 235 'Pinterest Ads', 236 ]; 237 238 function isPaidCampaign(source) { 239 // Exact match against known paid labels first. 240 for (var i = 0; i < PAID_PLATFORMS.length; i++) { 241 if (source === PAID_PLATFORMS[i]) { return true; } 242 } 243 // Catch-all for "[anything] Ads", "[anything] CPC", "[anything] Paid". 244 return (/\s(Ads|CPC|Paid)$/).test(source); 245 } 246 247 // Append a paid source to the accumulated campaign-touches cookie if not already present. 248 function recordCampaignTouch(source) { 249 if (!isPaidCampaign(source)) { return; } 250 var existing = getCookie('campaign-touches') || ''; 251 var touchList = existing ? existing.split(', ') : []; 252 if (touchList.indexOf(source) === -1) { 253 touchList.push(source); 254 setCookie('campaign-touches', touchList.join(', ')); 255 } 256 } 257 258 // ------------------------------------------------------------------------- 259 // Does the current page load carry trackable signals? 260 // Returns true when there are UTM params, click IDs, or an external referrer. 261 // "Direct" navigation (no signals) should not overwrite a previously captured 262 // paid/organic source. 263 // ------------------------------------------------------------------------- 264 265 function hasTrackingSignals(params, referrer) { 266 if (params.utm_source || params.utm_medium || params.utm_campaign || 267 params.utm_term || params.utm_content || params.utm_id) { return true; } 268 if (params.gclid || params.gbraid || params.fbclid || params.ttclid || 269 params.msclkid || params.li_fat_id || params.twclid) { return true; } 270 if (referrer) { 271 var hostname = getHostname(referrer); 272 var ownHostname = window.location.hostname.replace(/^www\./, ''); 273 if (hostname && hostname !== ownHostname) { return true; } 274 } 275 return false;
276 } 277 278 // ------------------------------------------------------------------------- 279 // Visit counter â called on every page load but only increments once per 280 // browser session. sessionStorage clears when the tab/browser closes, so 281 // browsing multiple pages in one sitting still counts as a single visit. 282 // ------------------------------------------------------------------------- 283 284 function trackVisit() { 285 // sessionStorage persists for one browser session and clears on close. 286 // Wrap in try/catch â some privacy modes restrict storage APIs. 287 try { 288 if (sessionStorage.getItem('brando-utm-session')) { 289 return; 290 } 291 sessionStorage.setItem('brando-utm-session', '1'); 292 } catch (e) { 293 // sessionStorage unavailable â fall back to a session cookie (no expiry = tab-scoped). 294 if (getCookie('_session')) { return; } 295 Cookies.set(COOKIE_PREFIX + '_session', '1'); // no expires = session cookie 296 } 297 298 var today = new Date().toISOString().slice(0, 10); // YYYY-MM-DD 299 var count = parseInt(getCookie('visit-count') || '0', 10) + 1; 300 var days = parseInt(getCookie('visit-days') || '0', 10); 301 var lastDate = getCookie('last-visit-date'); 302 303 if (!lastDate || lastDate !== today) { 304 days += 1; 305 } 306 307 setCookie('visit-count', count); 308 setCookie('visit-days', days); 309 setCookie('last-visit-date', today); 310 } 311 312 // ------------------------------------------------------------------------- 313 // Main capture logic â runs on every page load 314 // ------------------------------------------------------------------------- 315 316 function capture() { 317 var urlParams = new URLSearchParams(window.location.search); 318 319 var params = { 320 utm_source: urlParams.get('utm_source'), 321 utm_medium: urlParams.get('utm_medium'), 322 utm_campaign: urlParams.get('utm_campaign'), 323 utm_term: urlParams.get('utm_term'), 324 utm_content: urlParams.get('utm_content'), 325 utm_id: urlParams.get('utm_id'), 326 gclid: urlParams.get('gclid'), 327 gbraid: urlParams.get('gbraid'), 328 fbclid: urlParams.get('fbclid'), 329 ttclid: urlParams.get('ttclid'), 330 msclkid: urlParams.get('msclkid'), 331 li_fat_id: urlParams.get('li_fat_id'), 332 twclid: urlParams.get('twclid'), 333 // Google Ads ValueTrack parameters â add to your final URL suffix as 334 // &campaignid={campaignid}&adgroupid={adgroupid} 335 campaignid: urlParams.get('campaignid'), 336 adgroupid: urlParams.get('adgroupid'), 337 }; 338 339 var referrer = document.referrer || ''; 340 341 // If a click ID is present but referrer is missing (cross-domain redirect 342 // strips it), infer the origin platform so source detection still works. 343 if (!referrer) { 344 if (params.gclid || params.gbraid) { referrer = 'https://google.com/'; } 345 else if (params.fbclid) { referrer = 'https://facebook.com/'; } 346 else if (params.ttclid) { referrer = 'https://www.tiktok.com/'; } 347 else if (params.msclkid) { referrer = 'https://bing.com/'; } 348 else if (params.li_fat_id) { referrer = 'https://linkedin.com/'; } 349 else if (params.twclid) { referrer = 'https://x.com/'; } 350 } 351 352 // Always count the visit. 353 trackVisit(); 354 355 var source = buildSource(params, referrer); 356 357 // PMax / broad-match detection: Google paid click + campaign ID present + no keyword. 358 // PMax campaigns never populate {keyword} even when it is in the URL suffix. 359 // Standard search campaigns almost always return a keyword, so an empty utm_term 360 // alongside a campaignid strongly indicates PMax (or an uncategorised broad match). 361 if ((source === 'Google CPC' || source === 'Google Ads') && params.campaignid && !params.utm_term) { 362 source = 'Google PMax / Broad'; 363 } 364 365 var channel = buildChannel(source); 366 var isFirstVisit = !getCookie('first-source'); 367 368 // Always accumulate paid campaign touches regardless of first/last-touch logic. 369 recordCampaignTouch(source); 370 371 // ---- Identity / device cookies â set on every page load ---------------- 372 373 // Unique visitor ID â generated once and never overwritten. 374 if (!getCookie('visitor-id')) { 375 var vid = Math.random().toString(36).substr(2, 9) + '-' + Date.now().toString(36); 376 setCookie('visitor-id', vid); 377 } 378 379 // First-seen date â set once on the very first visit (drives days-to-convert). 380 if (!getCookie('first-seen')) { 381 setCookie('first-seen', new Date().toISOString().slice(0, 10)); 382 } 383 384 // Device type â always refreshed in case the visitor switches devices. 385 setCookie('device-type', detectDeviceType()); 386 387 // User agent â always refresh (captures the current browser/device). 388 setCookie('user-agent', navigator.userAgent); 389
390 // Facebook Pixel cookies â copy if the Pixel has set them. 391 var fbcVal = Cookies.get('_fbc') || ''; 392 var fbpVal = Cookies.get('_fbp') || ''; 393 if (fbcVal) { setCookie('fbc', fbcVal); } 394 if (fbpVal) { setCookie('fbp', fbpVal); } 395 396 // GA client ID â set once (stable for the lifetime of the browser profile). 397 if (!getCookie('ga-client-id')) { 398 var gaCid = readGAClientId(); 399 if (gaCid) { setCookie('ga-client-id', gaCid); } 400 } 401 402 // ---- Attribution cookies â first-touch / last-touch -------------------- 403 404 if (isFirstVisit) { 405 // First touch â set once and never overwrite. 406 setCookie('first-source', source); 407 setCookie('first-channel', channel); 408 setCookie('first-utm_campaign', params.utm_campaign); 409 setCookie('first-utm_medium', params.utm_medium); 410 411 // Also seed the last-touch cookies on first visit. 412 setCookie('source', source); 413 setCookie('channel', channel); 414 setCookie('referrer', referrer || 'Direct'); 415 setCookie('source-url', window.location.href); 416 setCookie('utm_source', params.utm_source); 417 setCookie('utm_medium', params.utm_medium); 418 setCookie('utm_campaign', params.utm_campaign); 419 setCookie('utm_term', params.utm_term); 420 setCookie('utm_content', params.utm_content); 421 setCookie('utm_id', params.utm_id); 422 setCookie('campaign-id', params.campaignid); 423 setCookie('adgroup-id', params.adgroupid); 424 425 } else if (hasTrackingSignals(params, referrer)) { 426 // Subsequent visit with a trackable signal â update last-touch only. 427 // "Direct" return visits do NOT overwrite a paid/organic attribution. 428 setCookie('source', source); 429 setCookie('channel', channel); 430 setCookie('referrer', referrer); 431 setCookie('source-url', window.location.href); 432 setCookie('utm_source', params.utm_source); 433 setCookie('utm_medium', params.utm_medium); 434 setCookie('utm_campaign', params.utm_campaign); 435 setCookie('utm_term', params.utm_term); 436 setCookie('utm_content', params.utm_content); 437 setCookie('utm_id', params.utm_id); 438 setCookie('campaign-id', params.campaignid); 439 setCookie('adgroup-id', params.adgroupid); 440 } 441 } 442 443 // ------------------------------------------------------------------------- 444 // Fill hidden GF form fields from cookies 445 // ------------------------------------------------------------------------- 446 447 function fillFields() { 448 var fieldMap = { 449 'campaign-touches': '.gf-utm-campaign-touches', 450 'source': '.gf-utm-source', 451 'first-source': '.gf-utm-first-source', 452 'channel': '.gf-utm-channel', 453 'first-channel': '.gf-utm-first-channel', 454 'visit-count': '.gf-utm-visit-count', 455 'visit-days': '.gf-utm-visit-days', 456 'referrer': '.gf-utm-referrer', 457 'source-url': '.gf-utm-source-url', 458 'utm_source': '.gf-utm-utm_source', 459 'utm_medium': '.gf-utm-utm_medium', 460 'utm_campaign': '.gf-utm-utm_campaign', 461 'utm_term': '.gf-utm-utm_term', 462 'utm_content': '.gf-utm-utm_content', 463 'utm_id': '.gf-utm-utm_id', 464 'user-agent': '.gf-utm-user-agent', 465 'fbc': '.gf-utm-fbc', 466 'fbp': '.gf-utm-fbp', 467 'ga-client-id': '.gf-utm-ga-client-id', 468 'visitor-id': '.gf-utm-visitor-id', 469 'campaign-id': '.gf-utm-campaign-id', 470 'adgroup-id': '.gf-utm-adgroup-id', 471 // v1.7.0 472 'device-type': '.gf-utm-device-type', 473 'first-utm_campaign': '.gf-utm-first-utm_campaign', 474 'first-utm_medium': '.gf-utm-first-utm_medium', 475 }; 476 477 for (var key in fieldMap) { 478 if (!Object.prototype.hasOwnProperty.call(fieldMap, key)) { continue; } 479 var value = getCookie(key); 480 if (!value && (key === 'source' || key === 'first-source' || key === 'channel' || key === 'first-channel')) { 481 value = 'Direct'; 482 } 483 if (!value) { continue; } 484 var selector = fieldMap[key]; 485 document.querySelectorAll(selector + ' input[type="text"]').forEach(function (input) { 486 input.value = value; 487 }); 488 } 489 490 // Days to convert â compute from first-seen date cookie. 491 var firstSeen = getCookie('first-seen'); 492 if (firstSeen) { 493 try { 494 var msDay = 86400000; 495 // Parse as UTC midnight to avoid timezone drift. 496 var seenMs = new Date(firstSeen + 'T00:00:00Z').getTime(); 497 var todayMs = new Date(new Date().toISOString().slice(0, 10) + 'T00:00:00Z').getTime(); 498 var daysAgo = Math.max(0, Math.round((todayMs - seenMs) / msDay)); 499 document.querySelectorAll('.gf-utm-days-to-convert input[type="text"]').forEach(function (input) {
500 input.value = String(daysAgo); 501 }); 502 } catch (e) {} 503 } 504 505 // Converting page URL â the page where this form is being submitted. 506 document.querySelectorAll('.gf-utm-converting-page input[type="text"]').forEach(function (input) { 507 input.value = window.location.href; 508 }); 509 510 // Form load time â filled once when the form first renders (spam detection fallback). 511 // Never overwritten so the original render time is preserved on multi-page forms. 512 document.querySelectorAll('.gf-utm-form-load-time input[type="text"]').forEach(function (input) { 513 if (!input.value) { 514 input.value = Date.now().toString(); 515 } 516 }); 517 } 518 519 // ------------------------------------------------------------------------- 520 // Form view tracking â fires once per browser session per form as soon as 521 // the form is visible on the page. Uses the same sessionStorage guard as 522 // form starts so page refreshes and multi-page AJAX advances don't inflate 523 // the count. 524 // ------------------------------------------------------------------------- 525 526 function trackFormView(formId) { 527 var key = 'brando-fview-' + formId; 528 try { 529 if (sessionStorage.getItem(key)) { return; } 530 sessionStorage.setItem(key, '1'); 531 } catch (e) { 532 if (getCookie('_fview_' + formId)) { return; } 533 Cookies.set(COOKIE_PREFIX + '_fview_' + formId, '1'); 534 } 535 if (!window.brandoUtmTrackerSettings || !window.brandoUtmTrackerSettings.ajaxUrl || !window.brandoUtmTrackerSettings.viewNonce) { return; } 536 var body = new URLSearchParams({ 537 action: 'brando_track_form_view', 538 nonce: window.brandoUtmTrackerSettings.viewNonce, 539 form_id: formId, 540 source: getCookie('source') || 'Direct', 541 }); 542 fetch(window.brandoUtmTrackerSettings.ajaxUrl, { method: 'POST', credentials: 'same-origin', body: body }) 543 .catch(function () {}); 544 } 545 546 function bindFormViewTracking() { 547 document.querySelectorAll('.gform_wrapper form').forEach(function (form) { 548 var match = form.id ? form.id.match(/gform_(\d+)/) : null; 549 var formId = match ? match[1] : '0'; 550 if (!formId || formId === '0') { return; } 551 trackFormView(formId); 552 }); 553 } 554 555 // ------------------------------------------------------------------------- 556 // Form start tracking â fires once per browser session per form on first 557 // focus inside any Gravity Forms form field. 558 // ------------------------------------------------------------------------- 559 560 function trackFormStart(formId) { 561 var key = 'brando-fstart-' + formId; 562 try { 563 if (sessionStorage.getItem(key)) { return; } 564 sessionStorage.setItem(key, '1'); 565 } catch (e) { 566 if (getCookie('_fstart_' + formId)) { return; } 567 Cookies.set(COOKIE_PREFIX + '_fstart_' + formId, '1'); 568 } 569 if (!window.brandoUtmTrackerSettings || !window.brandoUtmTrackerSettings.ajaxUrl || !window.brandoUtmTrackerSettings.startNonce) { return; } 570 var body = new URLSearchParams({ 571 action: 'brando_track_form_start', 572 nonce: window.brandoUtmTrackerSettings.startNonce, 573 form_id: formId, 574 source: getCookie('source') || 'Direct', 575 }); 576 fetch(window.brandoUtmTrackerSettings.ajaxUrl, { method: 'POST', credentials: 'same-origin', body: body }) 577 .catch(function () {}); 578 } 579 580 function bindFormStartTracking() { 581 document.querySelectorAll('.gform_wrapper form').forEach(function (form) { 582 var match = form.id ? form.id.match(/gform_(\d+)/) : null; 583 var formId = match ? match[1] : '0'; 584 if (!formId || formId === '0') { return; } 585 var inputs = form.querySelectorAll('input:not([type=hidden]):not([type=submit]):not([type=button]), textarea, select'); 586 inputs.forEach(function (input) { 587 input.addEventListener('focus', function onFocus() { 588 input.removeEventListener('focus', onFocus); 589 trackFormStart(formId); 590 }); 591 }); 592 }); 593 } 594 595 // ------------------------------------------------------------------------- 596 // Bootstrap 597 // ------------------------------------------------------------------------- 598 599 capture(); 600 601 if (document.readyState === 'loading') { 602 document.addEventListener('DOMContentLoaded', function () { 603 fillFields(); 604 bindFormViewTracking(); 605 bindFormStartTracking(); 606 }); 607 } else { 608 fillFields(); 609 bindFormViewTracking(); 610 bindFormStartTracking(); 611 } 612 613 // Re-run after GF renders or re-renders (AJAX / multi-page forms). 614 // bindFormViewTracking uses sessionStorage so it won't double-count. 615 document.addEventListener('gform/post_render', function () { 616 fillFields(); 617 bindFormViewTracking(); 618 bindFormStartTracking(); 619 }); 620 if (typeof jQuery !== 'undefined') { 621 jQuery(document).on('gform_post_render', function () { 622 fillFields(); 623 bindFormViewTracking(); 624 bindFormStartTracking(); 625 }); 626 } 627})();
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.