1document.querySelectorAll("form:not(.filter-dropdown-wrapper)").forEach(elem => { 2 elem.addEventListener('submit', function (event) { 3 return handleFormSubmit(event, this); 4 }) 5}); 6 7$(document).ready(function () { 8 $("div.custom-form").each((index, element) => { 9 const formData = $(element).data(); 10 Object.keys(formData).forEach(key => { 11 if (formData[key] == null || formData[key] === "") { 12 delete formData[key]; 13 } 14 }); 15 16 const pageParams = Object.fromEntries( 17 new URLSearchParams(window.location.search) 18 ); 19 20 const mergedParams = { 21 ...pageParams, 22 ...formData 23 }; 24 25 const queryString = $.param(mergedParams); 26 27 $.get("/custom/ajax/form?" + queryString, (response) => { 28 $(element).html(response); 29 // if any col-md-6 elements are present, and there are odd number of them switch the last one to col-md-12 30 const colMd6Elements = $(element).find('.col-md-6'); 31 if (colMd6Elements.length > 0 && colMd6Elements.length % 2 !== 0) { 32 colMd6Elements.last().removeClass('col-md-6').addClass('col-md-12'); 33 } 34 // Initialize Parsley validation 35 const form = $(element).find("form"); 36 form.parsley(); 37 38 // Attach submit handler to the custom form 39 form.on('submit', function (event) { 40 return handleFormSubmit(event, this); 41 }); 42 43 //update hidden input PageURL and LeadSourceUrl to the current page URL 44 const pageUrl = window.location.href; 45 $(element).find('input[name="PageURL"]').val(pageUrl); 46 $(element).find('input[name="LeadSourceUrl"]').val(pageUrl); 47 }).fail(() => { 48 console.error("Failed to load custom form."); 49 }); 50 }); 51}); 52 53var gnixFormIsProcessing = false; 54function handleFormSubmit(event, cForm) { 55 if(gnixFormIsProcessing){ 56 return false; // form is already processing 57 } 58 if (window.jQuery && $(cForm).parsley().isValid()) { 59 event.preventDefault(); 60 gnixFormIsProcessing = true; 61 buttonIsProcessing(true, cForm); 62 63 let canadaPostalPattern = /[A-CEGHJ-NPRSTVXYa-ceghj-nprstvxy]\d[A-CEGHJ-NPRSTV-Za-ceghj-nprstv-z][ -]?\d[A-CEGHJ-NPRSTV-Za-ceghj-nprstv-z]\d/g; 64 if ($(cForm).find("[name=PostalCode]").length && $(cForm).find("[name=PostalCode]").val().length > 0 && canadaPostalPattern.test($(cForm).find("[name=PostalCode]").val().trim())) { 65 cForm.submit(); 66 gnixFormIsProcessing = false; 67 buttonIsProcessing("submitted", cForm); 68 } else { 69 fetchLeadOrigin(cForm).then(() => { 70 leadCleanUp(cForm); 71 if (window.grecaptcha && window.globalRecaptchaKey) { 72 grecaptcha.ready(function () { 73 grecaptcha.execute(window.globalRecaptchaKey, { 74 action: 'submit' 75 }).then(function (token) { 76 $(cForm).find('.g-recaptcha-response').val(token); 77 handleFormSubmission(event, cForm); 78 }); 79 }); 80 } else { 81 handleFormSubmission(event, cForm); 82 } 83 }).catch(() => { 84 }); 85 } 86 } 87 return false; 88} 89 90function leadCleanUp(cForm) { 91 if ($("input[name='Phone']", $(cForm)).length > 0) { 92 var cleanphone = $("input[name='Phone']", $(cForm)).val(); 93 $("input[name='Phone']", $(cForm)).val(cleanphone.replace(/\D/g, '')) 94 } 95 96 if ($("input[name='CleanHomePhone']", $(cForm)).length > 0) { 97 var cleanphone = $("input[name='CleanHomePhone']", $(cForm)).val(); 98 $("input[name='CleanHomePhone']", $(cForm)).val(cleanphone.replace(/\D/g, '')) 99 } 100 101 if ($("input[name='CleanMobilePhone']", $(cForm)).length > 0) { 102 var cleanphone = $("input[name='CleanMobilePhone']", $(cForm)).val(); 103 $("input[name='CleanMobilePhone']", $(cForm)).val(cleanphone.replace(/\D/g, '')) 104 } 105 106 var notesField = $("input[name=OpportunityNotes]", $(cForm)); 107 if (notesField.length == 0) { 108 notesField = $("textarea[name=OpportunityNotes]", $(cForm)); 109 } 110 111 if (notesField.length > 0) { 112 let messageField = $("textarea[name=MyMessage]", $(cForm)); 113 if (messageField.length == 0) { 114 messageField = $("textarea[name=Message]", $(cForm)); 115 } 116 117 var notesVal = ""; 118 119 if (messageField && messageField.length > 0) { 120 notesVal += messageField.val(); 121 } 122 123 $(".add-to-notes").each(function () { 124 if ($(this).find("input").attr("name") && $(this).find("input").attr("name") !== undefined) { 125 notesVal += "\n\n" + $(this).find("input").attr("name") + ": " + $(this).find("input").val(); 126 } 127 128 // Also check for select elements within .add-to-notes 129 if ($(this).find("textarea").attr("name") && $(this).find("textarea").attr("name") !== undefined) { 130 notesVal += "\n\n" + $(this).find("textarea").attr("name") + ": " + $(this).find("textarea").val(); 131 } 132 133 if ($(this).find("select").attr("name") && $(this).find("select").attr("name") !== undefined) { 134 notesVal += "\n\n" + $(this).find("select").attr("name") + ": " + $(this).find("select").val(); 135 } 136 }) 137 138 $(notesField).val(notesVal); 139 } 140} 141 142function handleFormSubmission(event, cForm) { 143 if (event.target.className.indexOf("external-form-handling") == -1) { 144 if (event.target.className.indexOf("gnix-ajax-form") > -1) { 145 submitViaAjax(cForm); 146 } else { 147 cForm.submit(); 148 } 149 } 150 gnixFormIsProcessing = false;
151 buttonIsProcessing("submitted", cForm); 152} 153 154function submitViaAjax(formElem) { 155 /* get the action attribute from the <form action=""> element */ 156 var $form = $(formElem), 157 url = $form.attr('action'); 158 /* Send the data using post with element id name and name2*/ 159 160 $form.children('[type=submit]').addClass("disabled"); 161 var origText = $form.children('[type=submit]').html(); 162 $form.children('[type=submit]').html(`<div class="spinner-border" role="status"><span class="sr-only">Loading...</span></div>`); 163 164 if (!url) { 165 url = window.location; 166 } 167 168 $.ajax({ 169 url: url, 170 type: 'POST', 171 data: $form.serialize() 172 }).done(function (result) { 173 $form.addClass("d-none"); 174 $form.parent().children("div.form-thank-you").removeClass("d-none"); 175 }).fail(function (result) { 176 // Try to parse the response text as JSON 177 try { 178 if (result.responseText) { 179 const errorResponse = JSON.parse(result.responseText); 180 handleServerValidationErrors($form, errorResponse); 181 } 182 } catch (e) { 183 console.error("Could not parse error response:", e); 184 } 185 $form.children('[type=submit]').removeClass("disabled"); 186 $form.children('[type=submit]').html(origText); 187 }).always(function (result) { 188 $form.children('[type=submit]').removeClass("disabled"); 189 $form.children('[type=submit]').html(origText); 190 }); 191} 192 193function buttonIsProcessing(state, cForm) { 194 var $btn = $(cForm).find('button[type=submit]'); 195 196 if (state === true) { 197 // Start processing: disable + show spinner 198 if (!$btn.data('original-html')) { 199 $btn.data('original-html', $btn.html()); 200 } 201 $btn.prop('disabled', true); 202 $btn.html('<span class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span>Processing...'); 203 204 } else if (state === false) { 205 // Reset: restore original 206 $btn.prop('disabled', false); 207 if ($btn.data('original-html')) { 208 $btn.html($btn.data('original-html')); 209 $btn.removeData('original-html'); 210 } 211 212 } else if (state === "submitted") { 213 // Final state: disable + show "Form Submitted" 214 if (!$btn.data('original-html')) { 215 $btn.data('original-html', $btn.html()); 216 } 217 $btn.prop('disabled', true); 218 $btn.text("Form Submitted"); // no spinner, just text 219 } 220} 221 222function handleServerValidationErrors($form, response) { 223 // Check if we have errors to display in the ValidationProblemDetails format 224 if (response && response.errors) { 225 // Get all error fields 226 const errorFields = Object.keys(response.errors); 227 228 // Check if the only error is related to g-recaptcha 229 const isOnlyCaptchaError = errorFields.length === 1 && errorFields[0] === "g-recaptcha"; 230 231 // Only show captcha modal if it's the only error 232 if (isOnlyCaptchaError) { 233 showCaptchaModal(); 234 } 235 236 // Process each error field 237 errorFields.forEach(function (fieldName) { 238 const errorMessages = response.errors[fieldName]; 239 240 if (fieldName && errorMessages && errorMessages.length > 0) { 241 // Find the field by name 242 const $field = $form.find(`[name="${fieldName}"]`); 243 244 if ($field.length > 0) { 245 // Get the Parsley instance for this field 246 const fieldInstance = $field.parsley(); 247 248 if (fieldInstance) { 249 // Create a unique error name to avoid conflicts 250 const errorName = fieldName + "-custom"; 251 252 // Remove any existing errors with this name 253 fieldInstance.removeError(errorName, { updateClass: true }); 254 255 // Add the new error message 256 fieldInstance.addError(errorName, { 257 message: errorMessages[0], // Use the first error message 258 updateClass: true 259 }); 260 261 // Remove existing event handlers to prevent duplicates 262 $field.off('focus.customError input.customError change.customError'); 263 264 // Add event handlers to clear errors when field is focused and then changed 265 $field.on('focus.customError', function () { 266 // Store reference to the field for the change event 267 $(this).data('focused', true); 268 }); 269 270 // Handle input/change events 271 $field.on('input.customError change.customError', function () { 272 // Only process if field was previously focused 273 if ($(this).data('focused') && $(this).val().trim() !== '') { 274 const fieldInstance = $(this).parsley(); 275 if (fieldInstance) { 276 // Remove the custom error 277 fieldInstance.removeError(errorName, { updateClass: true }); 278 279 // If no other errors exist, remove error class 280 if (fieldInstance.isValid()) { 281 $(this).removeClass('parsley-error'); 282 $(this).addClass('parsley-success'); 283 } 284 } 285 } 286 }); 287 } 288 } 289 } 290 }); 291 292 // Focus on the first field with an error 293 const $firstErrorField = $form.find('.parsley-error').first(); 294 if ($firstErrorField.length > 0) { 295 $firstErrorField.focus(); 296 } 297 } 298} 299 300async function fetchLeadOrigin(formElem) { 301 var hiddenInput = $(formElem).find('input[name="__leadOrigin"]'); 302 if (hiddenInput.length === 0) { 303 // If the hidden input doesn't exist, create it 304 hiddenInput = $('<input>', { 305 type: 'hidden', 306 name: '__leadOrigin' 307 }).appendTo(formElem); 308 } 309 310 try { 311 const response = await fetch("https://api.ipify.org/?format=json"); 312 const data = await response.json(); 313 if (data) { 314 // && data.serverVariables && data.serverVariables.RemoteIpAddress) { 315 let leadOrigin = btoa(data.ip); 316 hiddenInput.val(leadOrigin); 317 } else { 318 hiddenInput.val(btoa("0.0.0.0")); 319 } 320 } catch (error) { 321 hiddenInput.val(btoa("0.0.0.0")); 322 } 323}
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.