PageSourceSearch

https://www.themailworks.com/static/js/lead-form.js?v=16a09b2ba6

js themailworks.com collected 2026-10-08 10:02:12 UTC 5,981 bytes, 156 lines download raw bytes

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.