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.