PageSourceSearch

https://www.blackbaud.com/wp-content/plugins/blackbaud-blocks/bloc…/acf-final-plea-form/acf-final-plea-form.js

js blackbaud.com collected 2026-10-01 07:13:57 UTC 10,507 bytes, 328 lines download raw bytes

1/**
2 * Final Plea Marketo multi-step form initializer.
3 *
4 * Paginates direct `.mktoForm > .mktoFormRow` elements when a form matches the
5 * multi-step authoring convention (multiple top-level rows containing fieldsets).
6 */
7(function (window) {
8   'use strict';
9
10   var FIELD_ROW_SELECTOR = '.mktoForm > .mktoFormRow';
11   var BUTTON_ROW_SELECTOR = '.mktoForm > .mktoButtonRow';
12   var BUTTON_SELECTOR = '.mktoButtonRow .mktoButton';
13   var LOCAL_FRAGMENT_ATTR = 'data-form-local-fragment';
14   var STEP_ATTR = 'data-multistep-step';
15   var INSTANCE_ATTR = 'data-multistep-instance';
16   var INIT_ATTR = 'data-multistep-initialized';
17
18   function arrayify(list) {
19      return Array.prototype.slice.call(list || []);
20   }
21
22   function getFieldsetStepRows(formEl) {
23      return arrayify(formEl.querySelectorAll(':scope > .mktoFormRow')).filter(function (row) {
24         return !!row.querySelector('fieldset');
25      });
26   }
27
28   function isMultiStepMarketoForm(formEl) {
29      if (!formEl) {
30         return false;
31      }
32
33      return getFieldsetStepRows(formEl).length > 1;
34   }
35
36   function collectRequiredFields(formEl, configFields) {
37      var fields = [];
38
39      if (configFields && configFields.length) {
40         configFields.forEach(function (fieldDesc) {
41            var refEl = formEl.querySelector("[name='" + fieldDesc.name + "']");
42            if (!refEl) {
43               return;
44            }
45
46            var label = formEl.querySelector("[for='" + fieldDesc.name + "']");
47            if (label && label.parentNode) {
48               label.parentNode.classList.add('mktoRequiredField');
49            }
50
51            fields.push({
52               name: fieldDesc.name,
53               message: fieldDesc.message || 'This field is required.',
54               label: label || null,
55               refEl: refEl
56            });
57         });
58
59         return fields;
60      }
61
62      arrayify(formEl.querySelectorAll('.mktoRequiredField')).forEach(function (wrap) {
63         var label = wrap.querySelector('label.mktoLabel');
64         if (!label) {
65            return;
66         }
67
68         var name = label.getAttribute('for');
69         if (!name) {
70            return;
71         }
72
73         var refEl = formEl.querySelector("[name='" + name + "']");
74         if (!refEl) {
75            return;
76         }
77
78         fields.push({
79            name: name,
80            message: 'This field is required.',
81            label: label,
82            refEl: refEl
83         });
84      });
85
86      return fields;
87   }
88
89   function validateEmailInStep(formEl, currentStep) {
90      var emailInput = currentStep.querySelector('[name="Email"], [type="email"]');
91      if (!emailInput) {
92         return true;
93      }
94
95      var emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
96      var parent = emailInput.parentElement;
97
98      if (!parent) {
99         return emailRegex.test(emailInput.value);
100      }
101
102      var existingError = parent.querySelector('.mktoError');
103      if (existingError) {
104         existingError.remove();
105      }
106
107      if (emailRegex.test(emailInput.value)) {
108         return true;
109      }
110
111      var errorDiv = document.createElement('div');
112      errorDiv.classList.add('mktoError');
113
114      var errorWrap = document.createElement('div');
115      errorWrap.classList.add('mktoErrorArrowWrap');
116      errorDiv.appendChild(errorWrap);
117
118      var errorArrow = document.createElement('div');
119      errorArrow.classList.add('mktoErrorArrow');
120      errorWrap.appendChild(errorArrow);
121
122      var errorMessage = document.createElement('div');
123      errorMessage.classList.add('mktoErrorMsg');
124      errorMessage.textContent = 'Invalid Email Format';
125      errorMessage.tabIndex = '-1';
126      errorMessage.setAttribute('role', 'alert');
127      errorDiv.appendChild(errorMessage);
128
129      parent.appendChild(errorDiv);
130      return false;
131   }
132
133   function injectStepImages(formEl, stepImages) {
134      if (!stepImages) {
135         return;
136      }
137
138      Object.keys(stepImages).forEach(function (stepIdx) {
139         var row = formEl.querySelector('.mktoFormRow[' + STEP_ATTR + '="' + stepIdx + '"]');
140         if (!row || row.querySelector('.step-img-container')) {
141            return;
142         }
143
144         var container = document.createElement('div');
145         container.classList.add('step-img-container');
146
147         var img = document.createElement('img');
148         img.src = stepImages[stepIdx];
149         img.alt = stepIdx === '0' ? 'Form Step 1' : 'Form Step 2';
150
151         container.appendChild(img);
152         row.prepend(container);
153      });
154   }
155
156   function initializeMarketoMultiStep(form, options) {
157      options = options || {};
158
159      var formEl = form.getFormElem()[0];
160      if (!formEl || formEl.hasAttribute(INIT_ATTR) || !isMultiStepMarketoForm(formEl)) {
161         return false;
162      }
163
164      var instanceId = options.instanceId || ('multistep-' + Date.now() + '-' + Math.floor(Math.random() * 100000));
165      var wrapperEl = options.wrapperEl || null;
166      var buttons = options.buttons || {};
167      var prevLabel = buttons.prev && buttons.prev.label ? buttons.prev.label : 'Back';
168      var nextLabel = buttons.next && buttons.next.label ? buttons.next.label : 'Next';
169      var prevDisabled = !!(buttons.prev && buttons.prev.disabled);
170      var requiredFields = collectRequiredFields(formEl, options.requiredFields);
171
172      formEl.setAttribute(INIT_ATTR, 'true');
173      formEl.setAttribute(INSTANCE_ATTR, instanceId);
174      formEl.classList.add('is-multi-step');
175
176      if (wrapperEl) {
177         wrapperEl.classList.add('is-multi-step');
178      }
179
180      var fieldRows = formEl.querySelectorAll(FIELD_ROW_SELECTOR);
181      var submitButtonRow = formEl.querySelector(BUTTON_ROW_SELECTOR);
182      if (!fieldRows.length || !submitButtonRow) {
183         return false;
184      }
185
186      var submitButton = submitButtonRow.querySelector(BUTTON_SELECTOR);
187      if (!submitButton) {
188         return false;
189      }
190
191      var styleEl = document.createElement('style');
192      styleEl.setAttribute('data-multistep-styles', instanceId);
193      (formEl.parentNode || document.head).appendChild(styleEl);
194      var dynableSheet = styleEl.sheet;
195      var formSelector = '.mktoForm.is-multi-step[' + INSTANCE_ATTR + '="' + instanceId + '"]';
196
197      arrayify(fieldRows).forEach(function (row, rowIdx) {
198         var rowPos = {
199            isFirst: rowIdx === 0,
200            isLast: rowIdx === fieldRows.length - 1
201         };
202
203         row.setAttribute(STEP_ATTR, String(rowIdx));
204
205         var navButtonRow = rowPos.isLast ? submitButtonRow : submitButtonRow.cloneNode(true);
206         var newRowAxis = row.nextSibling;
207         var nextEnabled = !rowPos.isLast;
208         var prevEnabled = !rowPos.isFirst && !prevDisabled;
209         var newButtonAxis;
210         var newButtonTmpl;
211         var navButtons = {};
212
213         if (nextEnabled) {
214            navButtons.next = navButtonRow.querySelector(BUTTON_SELECTOR);
215         }
216
217         if (prevEnabled) {
218            newButtonTmpl = newButtonAxis = navButtons.next || submitButton;
219            navButtons.prev = newButtonTmpl.cloneNode(true);
220         }
221
222         Object.keys(navButtons).forEach(function (dir) {
223            navButtons[dir].type = 'button';
224            navButtons[dir].setAttribute('data-dir', dir);
225            navButtons[dir].innerHTML = dir === 'prev' ? prevLabel : nextLabel;
226         });
227
228         if (nextEnabled) {
229            row.parentNode.insertBefore(navButtonRow, newRowAxis);
230         }
231
232         if (prevEnabled) {
233            newButtonAxis.parentNode.insertBefore(navButtons.prev, newButtonAxis);
234         }
235
236         navButtonRow.addEventListener('click', function (e) {
237            if (e.target.tagName !== 'BUTTON' || e.target.type !== 'button') {
238               return;
239            }
240
241            var direction = e.target.getAttribute('data-dir');
242            if (direction === 'next') {
243               if (!validateEmailInStep(formEl, row)) {
244                  return;
245               }
246               if (!isCustomValid(form, formEl, requiredFields, row)) {
247                  return;
248               }
249            }
250
251            setActiveStep(formEl, row, direction, rowIdx);
252         });
253
254         dynableSheet.insertRule(
255            [
256               formSelector + '[' + LOCAL_FRAGMENT_ATTR + '="' + rowIdx + '"] > .mktoFormRow[' + STEP_ATTR + '="' + rowIdx + '"],',
257               formSelector + '[' + LOCAL_FRAGMENT_ATTR + '="' + rowIdx + '"] > .mktoFormRow[' + STEP_ATTR + '="' + rowIdx + '"] + .mktoButtonRow'
258            ].join(' ') + ' { display: block !important; }',
259            0
260         );
261      });
262
263      form.onValidate(function (native) {
264         return isCustomValid(form, formEl, requiredFields, formEl, native);
265      });
266
267      formEl.setAttribute(LOCAL_FRAGMENT_ATTR, '0');
268      injectStepImages(formEl, options.stepImages);
269
270      if (typeof options.onInitialized === 'function') {
271         options.onInitialized(form, formEl);
272      }
273
274      return true;
275   }
276
277   function setActiveStep(formEl, current, dir, fallbackIndex) {
278      var currentIndex;
279
280      if (current instanceof HTMLElement && current.hasAttribute(STEP_ATTR)) {
281         currentIndex = parseInt(current.getAttribute(STEP_ATTR), 10);
282      } else if (!isNaN(fallbackIndex)) {
283         currentIndex = fallbackIndex;
284      } else {
285         currentIndex = -1;
286      }
287
288      if (dir === 'prev') {
289         currentIndex -= 1;
290      } else {
291         currentIndex += 1;
292      }
293
294      if (currentIndex < 0) {
295         currentIndex = 0;
296      }
297
298      formEl.setAttribute(LOCAL_FRAGMENT_ATTR, String(currentIndex));
299   }
300
301   function isCustomValid(form, formEl, requiredFields, currentStep, native) {
302      form.submittable(false);
303
304      var currentValues = form.getValues();
305      var currentUnfilled = requiredFields.filter(function (fieldDesc) {
306         if (!fieldDesc.refEl || !currentStep.contains(fieldDesc.refEl)) {
307            return false;
308         }
309
310         return !currentValues[fieldDesc.name] || (fieldDesc.refEl.type === 'checkbox' && currentValues[fieldDesc.name] === 'no');
311      });
312
313      if (currentUnfilled.length) {
314         if (!native) {
315            form.showErrorMessage(currentUnfilled[0].message, window.MktoForms2.$(currentUnfilled[0].refEl));
316         }
317         return false;
318      }
319
320      form.submittable(true);
321      return true;
322   }
323
324   window.BlackbaudMarketoMultiStep = {
325      isMultiStepMarketoForm: isMultiStepMarketoForm,
326      initializeMarketoMultiStep: initializeMarketoMultiStep
327   };
328})(window);

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.