1/* ========================================================================== 2 Lead-capture forms ([data-lead-form]). 3 4 These submit to HubSpot through the shared seam MW.submitLead (lead-submit.js), 5 the same path the Schedule and Contact forms use. Each form declares which 6 HubSpot form it maps to with a data-hs-form attribute, e.g. 7 8 <form data-lead-form data-hs-form="homepage_guide" ...> 9 10 That value is the key in HS_FORMS (lead-submit.js), which holds the HubSpot 11 form GUID and the field map. The follow-up email that actually delivers the 12 guide is configured inside HubSpot on that form. A form with no data-hs-form 13 (or an unknown one) falls back to the no-network stub in submitLead, so it 14 still validates and confirms without sending. 15 16 Validation is deliberately hand-rolled rather than left to the browser: 17 the native bubbles are unstyled, disappear on scroll, and only show one 18 error at a time. Messages go in .field__error, which is wired to the input 19 through aria-describedby so a screen reader announces them. 20 ========================================================================== */ 21 22(function () { 23 'use strict'; 24 25 var EMAIL = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/; 26 var HONEYPOT = 'website'; 27 28 function errorNode(form, name) { 29 return form.querySelector('[data-error-for="' + name + '"]'); 30 } 31 32 function messageFor(input) { 33 var value = input.value.trim(); 34 if (!value) { 35 // A form that is not the guide offer sets its own wording with 36 // data-required-message, so the error names the field it is under. 37 return input.getAttribute('data-required-message') 38 || (input.type === 'email' 39 ? 'Enter your email so we know where to send the guide.' 40 : 'Enter your first name.'); 41 } 42 if (input.type === 'email' && !EMAIL.test(value)) { 43 return 'That email address does not look right.'; 44 } 45 return ''; 46 } 47 48 function validate(form) { 49 var inputs = Array.prototype.slice.call(form.querySelectorAll('[required]')); 50 var firstBad = null; 51 52 inputs.forEach(function (input) { 53 var msg = messageFor(input); 54 var slot = errorNode(form, input.name); 55 if (slot) slot.textContent = msg; 56 input.setAttribute('aria-invalid', msg ? 'true' : 'false'); 57 if (msg && !firstBad) firstBad = input; 58 }); 59 60 if (firstBad) firstBad.focus(); 61 return !firstBad; 62 } 63 64 // Collects the fields and submits through the shared HubSpot seam. Returns 65 // the promise from MW.submitLead so the caller can confirm only on success. 66 function send(form) { 67 var payload = { form_type: form.getAttribute('data-hs-form') || '' }; 68 Array.prototype.forEach.call(form.elements, function (el) { 69 if (el.name && el.type !== 'submit') payload[el.name] = (el.value || '').trim(); 70 }); 71 delete payload[HONEYPOT]; 72 if (window.MW && typeof MW.submitLead === 'function') { 73 return Promise.resolve(MW.submitLead(payload)); 74 } 75 return Promise.resolve({ ok: true }); 76 } 77 78 function confirmSent(form) { 79 var done = document.createElement('div'); 80 done.className = 'lead-form__done'; 81 done.setAttribute('role', 'status'); 82 done.setAttribute('tabindex', '-1'); 83 84 var msg = document.createElement('p'); 85 msg.className = 'lead-form__done-msg'; 86 msg.textContent = form.getAttribute('data-done-message') 87 || 'Thanks! Your copy of the guide is on its way to your inbox.'; 88 done.appendChild(msg); 89 90 // When the form carries the guide file itself (data-guide-url), deliver it 91 // straight from the page rather than depending on a follow-up email. Opens 92 // in a new tab because the PDF is hosted on a different origin. 93 var guideUrl = form.getAttribute('data-guide-url'); 94 if (guideUrl) { 95 var link = document.createElement('a'); 96 link.className = 'btn btn--block'; 97 link.href = guideUrl; 98 link.target = '_blank'; 99 link.rel = 'noopener'; 100 link.textContent = 'Open the guide'; 101 done.appendChild(link); 102 } 103 104 form.parentNode.replaceChild(done, form); 105 done.focus(); 106 } 107 108 function showSendError(form) { 109 // Surface a general failure on the email field's error slot (every lead 110 // form has one) so the visitor knows to retry. 111 var email = form.querySelector('input[type="email"]'); 112 var slot = email && errorNode(form, email.name); 113 if (slot) slot.textContent = 'Something went wrong sending that. Please try again.'; 114 } 115 116 Array.prototype.forEach.call( 117 document.querySelectorAll('[data-lead-form]'), 118 function (form) { 119 // Clear a field's error as soon as it is fixed, rather than making the 120 // visitor submit again to find out. 121 Array.prototype.forEach.call(form.querySelectorAll('[required]'), function (input) { 122 input.addEventListener('input', function () { 123 var slot = errorNode(form, input.name); 124 if (slot && slot.textContent && !messageFor(input)) { 125 slot.textContent = ''; 126 input.setAttribute('aria-invalid', 'false'); 127 } 128 }); 129 }); 130 131 form.addEventListener('submit', function (e) { 132 e.preventDefault(); 133 134 // Honeypot filled means a bot: show the same confirmation without 135 // sending, so it gets no signal either way. 136 var hp = form.elements[HONEYPOT]; 137 if (hp && hp.value.trim()) { confirmSent(form); return; } 138 139 // Turns on the invalid styling only once a submit has been attempted. 140 form.classList.add('is-validated'); 141 if (!validate(form)) return; 142 143 var btn = form.querySelector('button[type="submit"]'); 144 var label = btn ? btn.textContent : ''; 145 if (btn) { btn.disabled = true; btn.textContent = 'Sendingâ¦'; } 146 147 send(form).then(function () { 148 confirmSent(form); 149 }).catch(function () { 150 if (btn) { btn.disabled = false; btn.textContent = label; } 151 showSendError(form); 152 }); 153 }); 154 } 155 ); 156})();
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.