1$(function () { 2 // Store uploaded files in a global variable 3 const renderedOnLink = $("body").attr("data-form-link"); 4 let isPreview = false; 5 let isFormPreview = false; 6 let formCompleteObject = {}; 7 let isFrameLoad = false; 8 const urlParams = new URLSearchParams(window.location.search); 9 const recordTokenId = urlParams.get("recordToken"); 10 11 window.addEventListener("message", function (event) { 12 if (event.data.type === "EditRecord") { 13 window.currentRecordId = event.data.recordId; 14 } 15 }); 16 17 let autoSaveToDB = false; 18 const draftToken = urlParams.get("draftToken"); 19 const renderMode = urlParams.get("renderMode"); 20 21 // Utility functions for draft management 22 const getAutoSaveKey = (formId) => `formAutoSave_${formId}`; 23 24 const saveToLocalStorage = (formId, data, currentPage = 0, metadata) => { 25 // if (autoSaveToDB) return; 26 try { 27 localStorage.setItem( 28 getAutoSaveKey(formId), 29 JSON.stringify({ 30 data: data, 31 currentPage: currentPage, 32 metadata: metadata, 33 savedAt: new Date().toISOString(), 34 }) 35 ); 36 } catch (e) { 37 console.error("Error saving draft to localStorage", e); 38 } 39 }; 40 41 const loadDraftFromLocalStorage = (formId) => { 42 try { 43 const draft = localStorage.getItem(getAutoSaveKey(formId)); 44 return draft ? JSON.parse(draft) : null; 45 } catch (e) { 46 console.error("Error loading draft from localStorage", e); 47 return null; 48 } 49 }; 50 51 // Email validation function 52 const validateEmailInput = (formId) => { 53 const emailInput = document.getElementById(`email-input-${formId}`); 54 const errorElement = document.getElementById(`email-error-${formId}`); 55 const emailValue = emailInput.value.trim(); 56 57 // Clear previous errors 58 errorElement.style.display = "none"; 59 60 // Check if empty 61 if (!emailValue) { 62 errorElement.textContent = "Please enter an email address"; 63 errorElement.style.display = "block"; 64 return false; 65 } 66 67 // Check if valid format 68 const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/; 69 if (!emailRegex.test(emailValue)) { 70 errorElement.textContent = "Please enter a valid email address"; 71 errorElement.style.display = "block"; 72 return false; 73 } 74 75 return true; 76 }; 77 78 const getMultiLookup = (question, value) => { 79 const ids = Array.from(new Set(value || [])); // unique IDs 80 const key = `${question?.lookupObject}_${question?.lookupFieldName}`; 81 // If key not found, return ids as-is 82 if (!window.searchItems?.hasOwnProperty(key)) { 83 return ids; 84 } 85 let searchList = window.searchItems[key] || []; 86 // ð¹ Make searchList unique by _id 87 searchList = Object.values( 88 searchList.reduce((acc, item) => { 89 acc[item._id] = item; // overwrite duplicates with same _id 90 return acc; 91 }, {}) 92 ); 93 // Optionally update window.searchItems[key] with cleaned list 94 window.searchItems[key] = searchList; 95 // ð¹ Filter by selected ids 96 return searchList.filter((item) => ids.includes(item._id)); 97 }; 98 99 const getLookup = (question, value) => { 100 const key = `${question?.lookupObject}_${question?.lookupFieldName}`; 101 // Check if window.searchItems has the key 102 if (!window.searchItems?.hasOwnProperty(key)) { 103 return value; // Return the value as-is 104 } 105 return window.searchItems[key]?.find((f) => f._id === value); 106 }; 107 108 const getAddress = (question, value) => { 109 if (!value) return value; 110 const cityField = window.searchItems["Contact_City"]?.find((f) => f._id === value?.cityDropdown); 111 return { ...value, city: cityField?.searchname }; 112 }; 113 114 const getDropdown = (question, value) => { 115 if(value == "other" && question.showOtherItem) return value; 116 if (!question?.choicesByUrl?.url?.includes("/getlistitems")) return value; 117 return question?.choicesFromUrl?.find((f) => f.value === value); 118 }; 119 120 const getTagbox = (question, value) => { 121 if (!question?.choicesByUrl?.url?.includes("/getlistitems")) return value; 122 const ids = value || []; 123 const searchList = question?.choicesFromUrl || []; 124 if(ids.includes("other")) { 125 let otherItem = new Survey.ItemValue("other", "other"); 126 searchList.push(otherItem) 127 } 128 return searchList.filter((item) => ids.includes(item.value)); 129 }; 130 131 // Email sending handler 132 const handleEmailSend = async (resumeLink, formId, emailInputId, successWrapperId,survey, dfToken) => { 133 // Validate first 134 if (!validateEmailInput(formId)) { 135 return; 136 } 137 138 try { 139 const emailInput = document.getElementById(emailInputId); 140 const email = emailInput.value.trim(); 141 const fields = loadDraftFromLocalStorage(formId); 142 let surveyData = {...fields.data}
143 survey.getAllQuestions().forEach((question) => { 144 const val = question.value; 145 146 switch (question.getType()) { 147 case "multilookupfield": 148 surveyData[question.valueName] = getMultiLookup(question, val); 149 break; 150 151 case "lookupfield": 152 surveyData[question.valueName] = getLookup(question, val); 153 break; 154 155 case "address": 156 surveyData[question.valueName] = val?.cityDropdown ? getAddress(question, val) : val; 157 break; 158 159 case "dropdown": 160 surveyData[question.valueName] = getDropdown(question, val); 161 break; 162 163 case "tagbox": 164 surveyData[question.valueName] = getTagbox(question, val); 165 break; 166 167 case "paneldynamic": 168 surveyData[question.valueName] = (question.panels || []).map((panel) => { 169 let rowData = {}; 170 (panel.questions || []).forEach((el) => { 171 const innerVal = el.value; 172 173 switch (el.getType()) { 174 case "multilookupfield": 175 rowData[el.valueName] = getMultiLookup(el, innerVal); 176 break; 177 178 case "lookupfield": 179 rowData[el.valueName] = getLookup(el, innerVal); 180 break; 181 182 case "address": 183 rowData[el.valueName] = innerVal?.cityDropdown ? getAddress(el, innerVal) : innerVal; 184 break; 185 186 case "dropdown": 187 rowData[el.valueName] = getDropdown(el, innerVal); 188 break; 189 190 case "tagbox": 191 rowData[el.valueName] = getTagbox(el, innerVal); 192 break; 193 194 case "file": 195 rowData[el.valueName] = innerVal ? innerVal : []; 196 break; 197 198 default: 199 rowData[el.valueName] = innerVal; 200 } 201 }); 202 return rowData; 203 }); 204 break; 205 206 default: 207 surveyData[question.valueName] = val; 208 } 209 }); 210 const emailData = { 211 email: email, 212 resumeLink: resumeLink, 213 formId: formId, 214 fields: surveyData, 215 draftToken: dfToken 216 }; 217 218 const response = await $.ajax({ 219 url: `/api/form/drafts/send-link-via-email`, 220 method: "POST", 221 contentType: "application/json", 222 data: JSON.stringify(emailData), 223 dataType: "json", 224 }); 225 226 if (response.message !== "success") { 227 return swal({ 228 title: "Error", 229 text: response?.message, 230 icon: "error", 231 }); 232 } 233 234 // Show success message 235 document.getElementById(successWrapperId).style.display = "block"; 236 emailInput.value = ""; 237 // localStorage.removeItem(getAutoSaveKey(formId)); 238 return true; 239 // survey.clear(); 240 } catch (error) { 241 swal({ 242 title: "Error", 243 text: "An unexpected error occurred. Try again later or contact support.", 244 icon: "error", 245 }); 246 console.error("Email send error:", error); 247 } 248 }; 249 250 const triggerDraftSavedPopup = (resumeLink, data, survey, dfToken) => { 251 const now = new Date(); 252 const day = String(now.getDate()).padStart(2, "0");
253 const month = String(now.getMonth() + 1).padStart(2, "0"); 254 const year = now.getFullYear(); 255 const hours = String(now.getHours()).padStart(2, "0"); 256 const minutes = String(now.getMinutes()).padStart(2, "0"); 257 const seconds = String(now.getSeconds()).padStart(2, "0"); 258 const currentDate = `${day}-${month}-${year} ${hours}:${minutes}:${seconds}`; 259 260 const showEmailOption = data?.Settings?.Email?.DraftNotification?.SaveEmailProgressEnabled; 261 const formId = data?.FormID; 262 263 const modal = ` 264 <div class="save-draft-feature"> 265 <div class="popup-bx"> 266 <!-- Centered Larger Green Circle Icon --> 267 <div> 268 <svg width="90" height="90" viewBox="0 0 90 90" fill="none" xmlns="http://www.w3.org/2000/svg"> 269 <rect width="90" height="90" rx="45" fill="#DCF4EC" /> 270 <rect x="12" y="12" width="66" height="66" rx="33" fill="#15C082" /> 271 <path 272 d="M60.0728 39.313L43.3048 56.0798C42.0632 57.3217 40.0489 57.3217 38.8061 56.0798L30.1504 47.4234C28.9082 46.1815 28.9082 44.167 30.1504 42.9249C31.3928 41.6825 33.4068 41.6825 34.6487 42.9244L41.0561 49.332L55.5738 34.8143C56.8162 33.5719 58.8304 33.5728 60.0723 34.8143C61.3142 36.0564 61.3142 38.0701 60.0728 39.313Z" 273 fill="white" /> 274 </svg> 275 276 </div> 277 278 <h2 class="large">Progress Saved.</h2> 279 <p class="font-weight-600">${currentDate}</p> 280 281 <p class="text-left large">You can return to this form later using the link below:</p> 282 283 ${ 284 showEmailOption 285 ? ` 286 <!-- Toggle Options --> 287 <div class="radio-toggle"> 288 <label class="custom-radio-button"> 289 <input type="radio" name="share-method" checked onclick=" 290 document.getElementById('copy-container').style.display = 'block'; 291 document.getElementById('email-container').style.display = 'none'; 292 document.getElementById('email-success-wrapper-${formId}').style.display = 'none'; 293 "> 294 <span class="custom-radio-mark"></span> 295 <span class="ml-1">Copy Link</span> 296 </label> 297 298 <label class="custom-radio-button"> 299 <input type="radio" name="share-method" onclick=" 300 document.getElementById('copy-container').style.display = 'none'; 301 document.getElementById('email-container').style.display = 'block'; 302 document.getElementById('copy-success-wrapper').style.display = 'none'; 303 "> 304 <span class="custom-radio-mark"></span> 305 <span class="ml-1">Email Link</span> 306 </label> 307 </div> 308 ` 309 : "" 310 } 311 312 <!-- Copy Section --> 313 <div id="copy-container"> 314 <div class="input-wrapper"> 315 <input readonly value="${resumeLink}"/> 316 <button onclick=" 317 navigator.clipboard.writeText('${resumeLink}').then(() => { 318 const msgWrapper = document.getElementById('copy-success-wrapper'); 319 if (msgWrapper) msgWrapper.style.display = 'block'; 320 }) 321 " class="copy-link-btn"> 322 <svg width="12" height="15" viewBox="0 0 12 15" fill="none" xmlns="http://www.w3.org/2000/svg"> 323 <path 324 d="M7.80815 2.47656H1.61552C1.03445 2.47656 0.5625 2.97882 0.5625 3.5972V13.053C0.5625 13.6714 1.03445 14.1736 1.61552 14.1736H7.80815C8.38922 14.1736 8.86117 13.6714 8.86117 13.053V3.5972C8.85844 2.97882 8.38649 2.47656 7.80815 2.47656ZM8.12187 13.0501C8.12187 13.2359 7.98001 13.3868 7.80542 13.3868H1.61279C1.4382 13.3868 1.29634 13.2359 1.29634 13.0501V3.5972C1.29634 3.4114 1.4382 3.26043 1.61279 3.26043H7.80542C7.98001 3.26043 8.12187 3.4114 8.12187 3.5972V13.0501Z" 325 fill="white" /> 326 <path 327 d="M10.1831 0H3.99052C3.40945 0 2.9375 0.502256 2.9375 1.12064C2.9375 1.33838 3.10118 1.51257 3.30578 1.51257C3.51039 1.51257 3.67407 1.33838 3.67407 1.12064C3.67407 0.934835 3.81593 0.783868 3.99052 0.783868H10.1831C10.3577 0.783868 10.4996 0.934835 10.4996 1.12064V10.5764C10.4996 10.7622 10.3577 10.9132 10.1831 10.9132C9.97854 10.9132 9.81486 11.0874 9.81486 11.3051C9.81486 11.5229 9.97854 11.697 10.1831 11.697C10.7642 11.697 11.2362 11.1948 11.2362 10.5764V1.12064C11.2362 0.502256 10.7642 0 10.1831 0Z" 328 fill="white" /> 329 </svg> 330 331 Copy Link 332 </button> 333 </div>
334 </div> 335 336 <!-- Copy Confirmation --> 337 <div id="copy-success-wrapper"> 338 <svg width="13" height="15" viewBox="0 0 13 15" fill="none" xmlns="http://www.w3.org/2000/svg"> 339 <path 340 d="M8.17401 15H2.41299C1.08243 15 0 13.9486 0 12.6562V4.7168C0 3.42442 1.08243 2.37305 2.41299 2.37305H8.17401C9.50458 2.37305 10.587 3.42442 10.587 4.7168V12.6562C10.587 13.9486 9.50458 15 8.17401 15ZM2.41299 3.54492C1.74777 3.54492 1.2065 4.07066 1.2065 4.7168V12.6562C1.2065 13.3024 1.74777 13.8281 2.41299 13.8281H8.17401C8.83924 13.8281 9.38051 13.3024 9.38051 12.6562V4.7168C9.38051 4.07066 8.83924 3.54492 8.17401 3.54492H2.41299ZM13 11.1914V2.34375C13 1.05137 11.9176 0 10.587 0H3.89095C3.55775 0 3.2877 0.262299 3.2877 0.585938C3.2877 0.909576 3.55775 1.17188 3.89095 1.17188H10.587C11.2522 1.17188 11.7935 1.69762 11.7935 2.34375V11.1914C11.7935 11.515 12.0636 11.7773 12.3968 11.7773C12.73 11.7773 13 11.515 13 11.1914Z" 341 fill="#0F6848" /> 342 </svg> 343 344 <strong class="url-txt">URL Copied!</strong> 345 <div class="small ml-lg-4"> 346 You can bookmark this link or save it somewhere safe to continue your form later. 347 </div> 348 </div> 349 350 <!-- Email Section --> 351 <div id="email-container"> 352 <div class="input-wrapper"> 353 <input type="email" id="email-input-${formId}" placeholder="Enter Email Address" 354 /> 355 <button id="send-email-btn-${formId}" 356 class="copy-link-btn"> 357 <svg width="17" height="15" viewBox="0 0 17 15" fill="none" xmlns="http://www.w3.org/2000/svg"> 358 <path 359 d="M15.7301 0.319198C15.5782 0.175857 15.3865 0.0752849 15.1768 0.0288894C14.967 -0.0175061 14.7476 -0.00786795 14.5434 0.0567106L1.39306 4.16619C1.1593 4.23917 0.956423 4.38008 0.81385 4.56846C0.671277 4.75684 0.596423 4.9829 0.600146 5.21386C0.603868 5.44481 0.685974 5.66865 0.834554 5.85291C0.983133 6.03716 1.19046 6.17226 1.42646 6.23859L7.27344 7.88321C7.36534 7.90889 7.44897 7.95562 7.51678 8.01919C7.58459 8.08276 7.63444 8.16116 7.66183 8.24732L9.41577 13.7286C9.48586 13.9502 9.62974 14.1449 9.82634 14.2843C10.0229 14.4236 10.262 14.5004 10.5084 14.5033L10.5266 14.5035C10.77 14.5041 11.0074 14.4325 11.2048 14.299C11.4023 14.1655 11.5497 13.9769 11.626 13.7602L16.0098 1.43169C16.0787 1.24028 16.089 1.03459 16.0396 0.837962C15.9901 0.641328 15.8829 0.461614 15.7301 0.319198ZM10.5266 13.4165L8.77261 7.93497C8.74347 7.85744 8.70804 7.78211 8.66663 7.70966L12.075 4.51415C12.1288 4.46368 12.1715 4.40377 12.2006 4.33784C12.2298 4.2719 12.2448 4.20124 12.2448 4.12987C12.2448 4.0585 12.2298 3.98783 12.2006 3.9219C12.1715 3.85596 12.1288 3.79606 12.075 3.74559C12.0212 3.69513 11.9573 3.6551 11.8869 3.62779C11.8166 3.60047 11.7412 3.58642 11.6651 3.58642C11.589 3.58642 11.5136 3.60047 11.4433 3.62779C11.3729 3.6551 11.309 3.69513 11.2552 3.74559L7.84689 6.9411C7.76961 6.90231 7.68928 6.86913 7.60659 6.84184L1.75965 5.19752L14.9103 1.08775L10.5266 13.4165ZM1.05989 11.5798C0.951176 11.6817 0.803732 11.7389 0.649991 11.7389C0.496251 11.7389 0.348808 11.6817 0.240097 11.5798C0.131386 11.4779 0.0703125 11.3396 0.0703125 11.1955C0.0703125 11.0514 0.131386 10.9131 0.240097 10.8112L3.13881 8.09367C3.24752 7.99175 3.39496 7.93449 3.5487 7.93449C3.70244 7.93449 3.84989 7.99175 3.9586 8.09367C4.06731 8.19558 4.12838 8.33381 4.12838 8.47794C4.12838 8.62208 4.06731 8.76031 3.9586 8.86222L1.05989 11.5798ZM7.43705 11.3547C7.4909 11.4052 7.53361 11.4651 7.56275 11.531C7.5919 11.597 7.6069 11.6676 7.6069 11.739C7.6069 11.8104 7.5919 11.881 7.56275 11.947C7.53361 12.0129 7.4909 12.0728 7.43705 12.1233L4.53834 14.8408C4.48451 14.8913 4.42061 14.9313 4.35028 14.9586C4.27995 14.9859 4.20457 15 4.12844 15C4.05232 15 3.97694 14.9859 3.90661 14.9586C3.83628 14.9313 3.77238 14.8913 3.71855 14.8408C3.66472 14.7904 3.62202 14.7305 3.59289 14.6645C3.56376 14.5986 3.54876 14.5279 3.54876 14.4565C3.54876 14.3852 3.56376 14.3145 3.59289 14.2486C3.62202 14.1826 3.66472 14.1227 3.71855 14.0723L6.61726 11.3547C6.67108 11.3042 6.73498 11.2642 6.80531 11.2369C6.87564 11.2096 6.95102 11.1955 7.02715 11.1955C7.10328 11.1955 7.17867 11.2096 7.249 11.2369C7.31933 11.2642 7.38323 11.3042 7.43705 11.3547ZM4.52135 11.5585L1.64246 14.2947C1.589 14.3455 1.52539 14.3859 1.45526 14.4137C1.38513 14.4415 1.30985 14.456 1.23373 14.4565C1.1576 14.457 1.08212 14.4434 1.01159 14.4166C0.941057 14.3897 0.876859 14.3501 0.822659 14.3C0.768459 14.2498 0.72532 14.1902 0.695703 14.1245C0.666086 14.0587 0.650573 13.9881 0.650048 13.9168C0.649524 13.8454 0.663998 13.7746 0.692645 13.7085C0.721293 13.6424 0.763552 13.5822 0.817009 13.5314L3.6959 10.7953C3.80398 10.6932 3.95083 10.6354 4.10427 10.6346C4.2577 10.6339 4.4052 10.6902 4.51444 10.7912C4.62368 10.8922 4.68575 11.0297 4.68705 11.1735C4.68834 11.3173 4.62876 11.4558 4.52135 11.5585Z" 360 fill="white" /> 361 </svg> 362 363 Send 364 </button> 365 </div> 366 <div id="email-error-${formId}" 367 style="color: #dc3545; font-size: 12px; margin-top: 4px; display: none; text-align: left; padding-left: 4px;"> 368 Please enter a valid email address 369 </div>
370 </div> 371 372 <!-- Email Confirmation --> 373 <div id="email-success-wrapper-${formId}" style=" 374 background: #d4edda; 375 border: 1px solid #c3e6cb; 376 padding: 10px 12px; 377 border-radius: 4px; 378 display: none; 379 margin-bottom: 12px; 380 text-align: left; 381 margin-top: 20px; 382 "> 383 <strong class="url-txt"> 384 <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> 385 <path 386 d="M15.6817 1.21221C15.5382 1.0678 15.3572 0.966474 15.1591 0.919731C14.9609 0.872988 14.7537 0.882698 14.5608 0.94776L2.13999 5.08802C1.9192 5.16155 1.72758 5.30351 1.59291 5.4933C1.45825 5.6831 1.38755 5.91085 1.39107 6.14354C1.39458 6.37623 1.47213 6.60174 1.61247 6.78738C1.75281 6.97302 1.94863 7.10912 2.17154 7.17595L7.69416 8.83289C7.78096 8.85876 7.85996 8.90584 7.924 8.96989C7.98805 9.03393 8.03513 9.11293 8.061 9.19973L9.71765 14.7221C9.78385 14.9453 9.91974 15.1415 10.1054 15.2819C10.2911 15.4223 10.5169 15.4996 10.7497 15.5026L10.7668 15.5028C10.9967 15.5033 11.221 15.4312 11.4075 15.2967C11.594 15.1622 11.7332 14.9722 11.8053 14.7539L15.9458 2.33304C16.0109 2.1402 16.0207 1.93297 15.974 1.73486C15.9273 1.53676 15.826 1.3557 15.6817 1.21221ZM10.7668 14.4076L9.11016 8.88504C9.08264 8.80692 9.04917 8.73104 9.01006 8.65803L12.2293 5.43859C12.2802 5.38775 12.3205 5.32739 12.348 5.26096C12.3755 5.19453 12.3897 5.12333 12.3897 5.05143C12.3897 4.97953 12.3755 4.90833 12.348 4.8419C12.3205 4.77548 12.2802 4.71512 12.2293 4.66428C12.1785 4.61343 12.1181 4.5731 12.0517 4.54559C11.9853 4.51807 11.9141 4.50391 11.8422 4.50391C11.7703 4.50391 11.6991 4.51807 11.6327 4.54559C11.5662 4.5731 11.5059 4.61343 11.455 4.66428L8.2358 7.88372C8.16281 7.84464 8.08693 7.81121 8.00883 7.78372L2.48625 6.12708L14.9074 1.98652L10.7668 14.4076ZM1.8253 12.5571C1.72262 12.6598 1.58336 12.7175 1.43815 12.7175C1.29293 12.7175 1.15367 12.6598 1.05099 12.5571C0.94831 12.4545 0.890625 12.3152 0.890625 12.17C0.890625 12.0248 0.94831 11.8855 1.05099 11.7828L3.7889 9.04492C3.89158 8.94224 4.03084 8.88456 4.17605 8.88456C4.32126 8.88456 4.46053 8.94224 4.56321 9.04492C4.66589 9.1476 4.72357 9.28687 4.72357 9.43208C4.72357 9.57729 4.66589 9.71656 4.56321 9.81924L1.8253 12.5571ZM7.84869 12.3304C7.89955 12.3812 7.9399 12.4416 7.96743 12.508C7.99495 12.5745 8.00912 12.6457 8.00912 12.7176C8.00912 12.7895 7.99495 12.8607 7.96743 12.9271C7.9399 12.9935 7.89955 13.0539 7.84869 13.1047L5.11079 15.8426C5.05995 15.8935 4.99959 15.9338 4.93316 15.9613C4.86673 15.9888 4.79553 16.003 4.72363 16.003C4.65173 16.003 4.58053 15.9888 4.51411 15.9613C4.44768 15.9338 4.38732 15.8935 4.33648 15.8426C4.28563 15.7918 4.2453 15.7314 4.21779 15.665C4.19027 15.5986 4.17611 15.5274 4.17611 15.4555C4.17611 15.3836 4.19027 15.3124 4.21779 15.2459C4.2453 15.1795 4.28563 15.1192 4.33648 15.0683L7.07438 12.3304C7.12521 12.2796 7.18557 12.2392 7.252 12.2117C7.31843 12.1842 7.38963 12.17 7.46154 12.17C7.53344 12.17 7.60465 12.1842 7.67108 12.2117C7.73751 12.2392 7.79786 12.2796 7.84869 12.3304ZM5.09475 12.5358L2.37556 15.2924C2.32506 15.3436 2.26498 15.3843 2.19874 15.4123C2.1325 15.4403 2.0614 15.4549 1.9895 15.4554C1.9176 15.4559 1.8463 15.4422 1.77968 15.4152C1.71307 15.3881 1.65243 15.3482 1.60124 15.2977C1.55004 15.2472 1.5093 15.1871 1.48132 15.1209C1.45335 15.0547 1.4387 14.9836 1.4382 14.9117C1.4377 14.8398 1.45138 14.7685 1.47843 14.7018C1.50549 14.6352 1.54541 14.5746 1.5959 14.5234L4.31509 11.7668C4.41717 11.6639 4.55587 11.6057 4.7008 11.6049C4.84572 11.604
3861 4.98503 11.6609 5.08821 11.7626C5.1914 11.8644 5.25002 12.0029 5.25125 12.1478C5.25247 12.2927 5.19619 12.4323 5.09475 12.5358Z" 387 fill="#0F6848" /> 388 </svg> 389 Email sent</strong> 390 <div class="small ml-lg-4"> 391 You can use the link provided in the email to continue your form later. 392 </div> 393 </div> 394 395 <!-- Close Button --> 396 <div> 397 <button id="save-draft-feature-closeBtn" onclick="swal.close();"> 398 <svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg"> 399 <path 400 d="M6.49674 8.5L0.415039 2.41829C-0.138346 1.86491 -0.138346 0.968424 0.415039 0.415039C0.968424 -0.138346 1.86491 -0.138346 2.41829 0.415039L8.5 6.49674L14.5817 0.415039C15.1351 -0.138346 16.0316 -0.138346 16.585 0.415039C17.1383 0.968424 17.1383 1.86491 16.585 2.41829L10.5033 8.5L16.585 14.5817C17.1383 15.1351 17.1383 16.0316 16.585 16.585C16.3083 16.8617 15.9458 17 15.5833 17C15.2209 17 14.8584 16.8617 14.5817 16.585L8.5 10.5033L2.41829 16.585C2.1416 16.8617 1.77913 17 1.41667 17C1.0542 17 0.691732 16.8617 0.415039 16.585C-0.138346 16.0316 -0.138346 15.1351 0.415039 14.5817L6.49674 8.5Z" 401 fill="#CCCCCC" /> 402 </svg> 403 404 </button> 405 </div> 406 </div> 407 </div> 408 409 `; 410 swal({
411 buttons: false, 412 closeOnClickOutside: false, 413 closeOnEsc: false, 414 content: { 415 element: "div", 416 attributes: { 417 innerHTML: modal, 418 }, 419 }, 420 onOpen: () => { 421 if (showEmailOption) { 422 document.getElementById("copy-container").style.display = "block"; 423 document.getElementById("email-container").style.display = "none"; 424 } 425 426 $(document) 427 .off("click", `#send-email-btn-${formId}`) 428 .on("click", `#send-email-btn-${formId}`, async function () { 429 await handleEmailSend(resumeLink, formId, `email-input-${formId}`, `email-success-wrapper-${formId}`,survey, dfToken); 430 }); 431 432 $(document).on("input", `#email-input-${formId}`, function () { 433 document.getElementById(`email-error-${formId}`).style.display = "none"; 434 }); 435 }, 436 }); 437 438 setTimeout(() => { 439 // Add class to the .swal-content wrapper 440 const swalModal = document.querySelector(".swal-modal"); 441 if (swalModal) { 442 swalModal.classList.add("my-swal-wrapper"); 443 } 444 }); 445 446 $(document).on("click", `#save-draft-feature-closeBtn`, async function () { 447 // survey.clear(); 448 // localStorage.removeItem(getAutoSaveKey(formId)); 449 }); 450 451 $(document).on("click", `#send-email-btn-${formId}`, async function () { 452 await handleEmailSend(resumeLink, formId, `email-input-${formId}`, `email-success-wrapper-${formId}`,survey, dfToken); 453 // survey.clear(); 454 }); 455 }; 456 457 const resolveBaseEndpoint = (formId) => { 458 let suffix = ""; 459 const paths = window.location.pathname.split("/"); 460 const search = window.location.search; 461 const query = new URLSearchParams(search); 462 if (paths.length && paths[2] && paths[2] === "preview") { 463 isPreview = true; 464 suffix = `/${paths[1]}/`; 465 } 466 if (query.get("renderMode") == "Preview") { 467 isFormPreview = true; 468 return `${suffix}/api/form/metadata/${formId}?type=${renderedOnLink ? "link" : "embed"}&renderMode=Preview`; 469 } 470 return `${suffix}/api/form/metadata/${formId}?type=${renderedOnLink ? "link" : "embed"}`; 471 }; 472 473 const Makeid = (length) => { 474 let result = ""; 475 let characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; 476 let charactersLength = characters.length; 477 for (let i = 0; i < length; i++) { 478 result += characters.charAt(Math.floor(Math.random() * charactersLength)); 479 } 480 return result; 481 }; 482 483 const verifyAndGetGoogleCaptchaToken = () => { 484 let siteKey = document.querySelector('meta[name="g-captcha-site-key"]') ? document.querySelector('meta[name="g-captcha-site-key"]').content : ""; 485 return new Promise((res, rej) => { 486 grecaptcha.ready(() => { 487 try { 488 grecaptcha 489 .execute(siteKey) 490 .then((token) => { 491 return res(token); 492 }) 493 .catch(() => { 494 swal({ text: "Google catcha key is not valid", icon: "error" }); 495 }); 496 } catch (err) { 497 swal({ text: "Google catcha key is not valid", icon: "error" }); 498 } 499 }); 500 }); 501 }; 502 503 function htmlToText(html) { 504 var temp = document.createElement("div"); 505 temp.innerHTML = html; 506 507 // Select all elements with the class 'secondary-label' 508 temp.querySelectorAll(".secondary-label").forEach((element) => { 509 element.remove(); 510 }); 511 // Return text content (or use innerText for visible text only) 512 return temp.textContent.trim(); // Or use temp.innerText if needed. 513 } 514 // Replace 'my-class' with the class you want to remove 515 516 Survey.FunctionFactory.Instance.register("randomId", (prefix) => { 517 const newPrefix = prefix[0] ? prefix[0] : ""; 518 const caseType = prefix[1] ? prefix[1] : "upper"; 519 const uid = Math.random().toString(36).substring(2, 8) + Math.random().toString(36).substring(2, 8); 520 const finalUID = caseType === "upper" ? uid.toUpperCase() : uid.toLowerCase(); 521 return `${newPrefix}${finalUID}`; 522 }); 523 524 window.React = { createElement: SurveyUI.createElement, Component: SurveyUI.Component, useEffect: SurveyUI.useEffect, useState: SurveyUI.useState }; 525 window.searchItems = {}; 526 SurveyUI.ReactElementFactory.Instance.registerElement("MyComponent", (props) => { 527 const getSearchName = () => { 528 if (props.question?.lookupObject) { 529 console.log(window.searchItems[props.question.lookupObject + "_" + props.question.lookupFieldName]); 530 return window.searchItems[props.question.lookupObject + "_" + props.question.lookupFieldName]?.find((i) => i._id == props.item.value)?.searchname || props.item.value || ""; 531 } 532 return ""; 533 //return window.searchItems. 534 }; 535 return React.createElement("div", { 536 className: "dynamic-content", 537 dangerouslySetInnerHTML: { __html: props.item.jsonObj.label ? props.item.jsonObj.label : getSearchName() }, 538 }); 539 }); 540 541 $("[data-form-id]").each(async function (i, el) { 542 let uploadedFiles = {};
543 const formId = $(this).attr("data-form-id"); 544 545 const mapping = { 546 Prefix: "prefixDropdown", 547 "First Name": "firstName", 548 "Middle Name": "middleName", 549 "Last Name": "lastName", 550 "St. Address 1": "streetAddressLine1", 551 "St. Address 2": "streetAddressLine2", 552 City: "cityDropdown", 553 State: "state", 554 Country: "country", 555 "Postal Code": "postalCode", 556 }; 557 558 const data = await $.ajax({ 559 url: resolveBaseEndpoint(formId), 560 method: "GET", 561 contentType: "application/json", 562 }); 563 if (!data) return; 564 let surveyData = {}; 565 window.tpSurveyFormData = data; 566 567 // Enable Captcha 568 const survey = new Survey.Model(data.Metadata.Content); 569 570 const showDraftStatusPopup = (type, message, canContinue = false) => { 571 let iconBgColor, iconTextColor, iconChar, buttonBg, buttonText; 572 573 switch (type) { 574 case "new-question": 575 iconBgColor = "#075da8"; 576 iconTextColor = "white"; 577 iconChar = `<svg width="90" height="90" viewBox="0 0 90 90" fill="none" xmlns="http://www.w3.org/2000/svg"> 578 <rect width="90" height="90" rx="45" fill="#075DA8" fill-opacity="0.1"/> 579 <rect x="12" y="12" width="66" height="66" rx="33" fill="#075DA8"/> 580 <g clip-path="url(#clip0_2465_2559)"> 581 <path d="M45 32C48.4478 32 51.7544 33.3696 54.1924 35.8076C56.6304 38.2456 58 41.5522 58 45C58 48.4478 56.6304 51.7544 54.1924 54.1924C51.7544 56.6304 48.4478 58 45 58C41.5522 58 38.2456 56.6304 35.8076 54.1924C33.3696 51.7544 32 48.4478 32 45C32 41.5522 33.3696 38.2456 35.8076 35.8076C38.2456 33.3696 41.5522 32 45 32ZM45 61C49.2435 61 53.3131 59.3143 56.3137 56.3137C59.3143 53.3131 61 49.2435 61 45C61 40.7565 59.3143 36.6869 56.3137 33.6863C53.3131 30.6857 49.2435 29 45 29C40.7565 29 36.6869 30.6857 33.6863 33.6863C30.6857 36.6869 29 40.7565 29 45C29 49.2435 30.6857 53.3131 33.6863 56.3137C36.6869 59.3143 40.7565 61 45 61ZM42.5 50C41.6687 50 41 50.6688 41 51.5C41 52.3312 41.6687 53 42.5 53H47.5C48.3312 53 49 52.3312 49 51.5C49 50.6688 48.3312 50 47.5 50H47V44.5C47 43.6687 46.3312 43 45.5 43H42.5C41.6687 43 41 43.6687 41 44.5C41 45.3312 41.6687 46 42.5 46H44V50H42.5ZM45 41C45.5304 41 46.0391 40.7893 46.4142 40.4142C46.7893 40.0391 47 39.5304 47 39C47 38.4696 46.7893 37.9609 46.4142 37.5858C46.0391 37.2107 45.5304 37 45 37C44.4696 37 43.9609 37.2107 43.5858 37.5858C43.2107 37.9609 43 38.4696 43 39C43 39.5304 43.2107 40.0391 43.5858 40.4142C43.9609 40.7893 44.4696 41 45 41Z" fill="white"/> 582 </g> 583 <defs> 584 <clipPath id="clip0_2465_2559"> 585 <rect width="32" height="32" fill="white" transform="translate(29 29)"/> 586 </clipPath> 587 </defs> 588 </svg> 589 `; 590 buttonBg = "#075da8"; 591 buttonText = `<svg width="16" height="18" viewBox="0 0 16 18" fill="none" xmlns="http://www.w3.org/2000/svg"> 592 <path d="M8.61834 11.3978L7.48216 10.2616C7.34849 10.1279 7.14798 10.1279 7.01431 10.2616C6.88065 10.3953 6.88065 10.5958 7.01431 10.7294L8.38442 12.0995C8.45126 12.1664 8.55151 12.1998 8.61834 12.1998C8.7186 12.1998 8.78543 12.1664 8.85226 12.0995L11.6259 9.32591C11.7596 9.19224 11.7596 8.99173 11.6259 8.85806C11.4922 8.7244 11.2917 8.7244 11.1581 8.85806L8.61834 11.3978Z" fill="white"/> 593 <path d="M15.5402 14.3101L13.2678 12.0377C13.4349 11.6367 13.5686 11.2023 13.602 10.7678C13.7022 9.63164 13.3681 8.49545 12.6329 7.6266C12.2319 7.12534 11.7306 6.75775 11.1625 6.49041V4.75272C11.1625 3.9507 10.4942 3.28236 9.65874 3.28236H8.75647V2.81451C8.75647 2.31325 8.35547 1.91225 7.82079 1.91225H6.81827C6.65119 1.37757 6.14993 0.976562 5.58183 0.976562C4.98032 0.976562 4.51248 1.37757 4.34539 1.87883H3.34287C2.8082 1.91225 2.40719 2.31325 2.40719 2.81451V3.28236H1.50492C0.669492 3.28236 0.00114639 3.9507 0.00114639 4.75272V15.5465C-0.0322709 16.3485 0.669492 17.0169 1.50492 17.0169H9.65874C10.4942 17.0169 11.1625 16.3485 11.1625 15.5131V14.4437L13.2678 16.549C13.8693 17.1505 14.8718 17.1839 15.5068 16.549C16.1417 15.9141 16.1751 14.9116 15.5402 14.3101ZM12.1316 8.02761C12.7331 8.76279 13.0339 9.73189 12.9336 10.6676C12.8 12.0377 11.9311 13.1739 10.7281 13.6751C10.7281 13.6751 10.7281 13.6751 10.6947 13.6751C10.16 13.909 9.59191 13.9759 8.95698 13.9425C6.95194 13.742 5.515 11.9708 5.7155 9.96581C5.81575 9.03013 6.25018 8.19469 6.91852 7.59318C6.91852 7.59318 6.91852 7.59318 6.95194 7.55977C7.68712 6.92484 8.68964 6.59067 9.69216 6.69092C10.0597 6.72434 10.3939 6.82459 10.7281 6.95826C11.2628 7.22559 11.7306 7.55977 12.1316 8.02761ZM3.07554 2.81451C3.07554 2.68084 3.20921 2.58059 3.34287 2.58059H4.64615C4.84665 2.58059 4.98032 2.44692 4.98032 2.24642C4.98032 1.91225 5.24766 1.67833 5.58183 1.67833C5.91601 1.67833 6.21676 1.94566 6.21676 2.24642C6.21676 2.44692 6.35043 2.58059 6.55093 2.58059H7.85421C7.98788 2.58059 8.12155 2.68084 8.12155 2.81451V4.41854C8.12155 4.55221 7.98788 4.65246 7.85421 4.65246H3.34287C3.20921 4.65246 3.07554 4.55221 3.07554 4.41854V2.81451ZM9.65874 16.3485H1.50492C1.03708 16.3485 0.636075 15.9809 0.636075 15.5465V4.75272C0.636075 4.31829 1.00366 3.9507 1.47151 3.9507H2.37377V4.41854C2.37377 4.9198 2.77478 5.32081 3.30946 5.32081H7.82079C8.32205 5.32081 8.75647 4.9198 8.75647 4.41854V3.9507H9.65874C10.1266 3.9507 10.4942 4.31829 10.4942 4.7
5935272V6.22308C10.2603 6.15624 9.99291 6.08941 9.72558 6.08941C8.52255 5.95574 7.41978 6.35675 6.55093 7.05851H2.54086C2.34036 7.05851 2.20669 7.19218 2.20669 7.39268C2.20669 7.59318 2.34036 7.72685 2.54086 7.72685H5.91601C5.68208 8.02761 5.515 8.32836 5.34791 8.69595H2.57428C2.37377 8.69595 2.2401 8.82962 2.2401 9.03013C2.2401 9.23063 2.37377 9.3643 2.57428 9.3643H5.18083C5.14741 9.5648 5.08057 9.76531 5.08057 9.96581V10.3334H2.57428C2.37377 10.3334 2.2401 10.4671 2.2401 10.6676C2.2401 10.8681 2.37377 11.0017 2.57428 11.0017H5.08057C5.11399 11.3359 5.21424 11.6367 5.31449 11.9708H2.57428C2.37377 11.9708 2.2401 12.1045 2.2401 12.305C2.2401 12.5055 2.37377 12.6392 2.57428 12.6392H5.64867C5.84917 13.0068 6.11651 13.3075 6.41726 13.5749H2.57428C2.37377 13.5749 2.2401 13.7085 2.2401 13.9091C2.2401 14.1096 2.37377 14.2432 2.57428 14.2432H7.41978C7.88762 14.4771 8.35547 14.6108 8.89014 14.6776C9.42482 14.7445 9.9595 14.6776 10.4942 14.544V15.5799C10.5276 15.9475 10.1266 16.3485 9.65874 16.3485ZM15.0723 16.0812C14.7048 16.4488 14.1367 16.4488 13.7691 16.0812L11.6304 13.9759C12.165 13.6417 12.5995 13.2073 12.9336 12.6726L15.0389 14.7779C15.4399 15.1455 15.4399 15.7136 15.0723 16.0812Z" fill="white"/> 594 </svg> 595 Review Form`; 596 break; 597 598 case "removed-question": 599 iconBgColor = "#FFC107"; 600 iconTextColor = "white"; 601 iconChar = `<svg width="90" height="90" viewBox="0 0 90 90" fill="none" xmlns="http://www.w3.org/2000/svg"> 602 <rect width="90" height="90" rx="45" fill="#FABC2F" fill-opacity="0.1"/> 603 <rect x="12" y="12" width="66" height="66" rx="33" fill="#FABC2F"/> 604 <rect x="41" y="29.4453" width="7" height="20.2222" rx="3.5" fill="white"/> 605 <rect x="41" y="53.5547" width="7" height="7" rx="3.5" fill="white"/> 606 </svg> 607 `; 608 buttonBg = "#FFC107"; 609 buttonText = ` 610 <svg width="16" height="14" viewBox="0 0 16 14" fill="none" xmlns="http://www.w3.org/2000/svg" style="vertical-align: middle; margin-right: 6px;"> 611 <path d="M9.79375 0.0293723C10.6062 0.191858 11.1344 0.982412 10.9719 1.79484L10.9 2.15106C10.7344 2.98536 10.4281 3.77904 10 4.50085H14.5C15.3281 4.50085 16 5.17266 16 6.00071C16 6.57879 15.6719 7.08187 15.1906 7.33185C15.5312 7.60682 15.75 8.02866 15.75 8.50049C15.75 9.23168 15.225 9.841 14.5344 9.97223C14.6719 10.2003 14.75 10.4659 14.75 10.7503C14.75 11.4159 14.3156 11.9814 13.7156 12.1752C13.7375 12.2783 13.75 12.3876 13.75 12.5001C13.75 13.3282 13.0781 14 12.25 14H9.20312C8.60938 14 8.03125 13.825 7.5375 13.4969L6.33437 12.6939C5.5 12.1377 5 11.2002 5 10.1972V9.00045V7.50058V6.72252C5 5.81011 5.41563 4.95081 6.125 4.37898L6.35625 4.19462C7.18437 3.53218 7.75 2.60102 7.95625 1.56361L8.02812 1.20739C8.19063 0.394965 8.98125 -0.133113 9.79375 0.0293723ZM1 5.0008H3C3.55312 5.0008 4 5.44764 4 6.00071V13.0001C4 13.5532 3.55312 14 3 14H1C0.446875 14 0 13.5532 0 13.0001V6.00071C0 5.44764 0.446875 5.0008 1 5.0008Z" fill="white"/> 612 </svg> 613 Acknowledge 614 `; 615 break; 616 617 case "error": 618 iconBgColor = "#DC3545"; 619 iconTextColor = "white"; 620 iconChar = `<svg width="90" height="90" viewBox="0 0 90 90" fill="none" xmlns="http://www.w3.org/2000/svg"> 621 <rect width="90" height="90" rx="45" fill="#E73047" fill-opacity="0.1"/> 622 <rect x="12" y="12" width="66" height="66" rx="33" fill="#E74A3B"/> 623 <path fill-rule="evenodd" clip-rule="evenodd" d="M44.7644 52.3334C44.2442 52.3334 43.7991 51.8895 43.7088 51.2789L41.0479 33.3322C40.7134 31.072 42.1735 29 44.1005 29H45.4271C47.3541 29 48.8142 31.072 48.4797 33.3322L45.8189 51.2789C45.7298 51.8895 45.2847 52.3334 44.7644 52.3334Z" fill="white"/> 624 <path fill-rule="evenodd" clip-rule="evenodd" d="M47.197 59.0829C47.197 57.7423 46.1032 56.6484 44.7626 56.6484C43.422 56.6484 42.3281 57.7423 42.3281 59.0829C42.3281 60.4235 43.422 61.5173 44.7626 61.5173C46.1032 61.5173 47.197 60.4248 47.197 59.0829Z" fill="white"/> 625 </svg> 626 `; 627 buttonBg = "#DC3545"; 628 buttonText = `<svg width="15" height="16" viewBox="0 0 15 16" fill="none" xmlns="http://www.w3.org/2000/svg">
629 <path d="M14.0913 8.55746C13.601 8.48285 13.1656 8.81891 13.0953 9.29802C12.9187 10.5014 12.3633 11.5993 11.4891 12.4736C9.26405 14.6986 5.64634 14.6968 3.42218 12.4736C1.19846 10.2494 1.19846 6.63038 3.42218 4.40666C4.25868 3.5706 5.31468 3.02549 6.49653 2.82705C7.00478 2.7306 7.55121 2.71392 8.11818 2.77817C8.89666 2.86778 9.62126 3.12607 10.2825 3.50531L9.13986 3.70041C8.6625 3.78229 8.34102 4.23569 8.42334 4.71349C8.49619 5.14117 8.86735 5.44326 9.28731 5.44326C9.33619 5.44326 9.38587 5.4394 9.43642 5.4308L12.4996 4.9071C12.7284 4.86769 12.9333 4.73912 13.0679 4.54884C13.2025 4.359 13.2556 4.12326 13.2161 3.89358L12.6916 0.83082C12.611 0.353462 12.1636 0.0337311 11.6785 0.113861C11.2011 0.195747 10.8796 0.649584 10.962 1.12738L11.1 1.93351C10.2505 1.45694 9.31346 1.14862 8.31785 1.034C7.57219 0.949128 6.84882 0.972737 6.18672 1.09956C4.6624 1.35496 3.27737 2.06937 2.18109 3.16557C-0.72703 6.07368 -0.72703 10.8057 2.18109 13.7146C3.6351 15.169 5.54559 15.8959 7.45564 15.8959C9.36612 15.8959 11.2756 15.1691 12.7302 13.7146C13.8727 12.572 14.5995 11.133 14.8317 9.55342C14.9021 9.07343 14.5704 8.62767 14.0913 8.55746Z" fill="white"/> 630 </svg> 631 Try Again`; 632 break; 633 634 case "resume": 635 iconBgColor = "#075da8"; 636 iconTextColor = "white"; 637 iconChar = `<svg width="90" height="90" viewBox="0 0 90 90" fill="none" xmlns="http://www.w3.org/2000/svg"> 638 <rect width="90" height="90" rx="45" fill="#075da8" fill-opacity="0.1"/> 639 <rect x="12" y="12" width="66" height="66" rx="33" fill="#075da8"/> 640 <circle cx="45" cy="45" r="20" stroke="white" stroke-width="3"/> 641 <path d="M45 30V45H60" stroke="white" stroke-width="3" stroke-linecap="round"/> 642 <path d="M60 45L51 36" stroke="white" stroke-width="3" stroke-linecap="round"/> 643 <path d="M60 45L51 54" stroke="white" stroke-width="3" stroke-linecap="round"/> 644 </svg>`; 645 buttonBg = "#075da8"; 646 buttonText = "Continue"; 647 break; 648 649 default: 650 return; 651 } 652 653 const modalContent = ` 654 <div style=" 655 text-align: center; 656 "> 657 <div class="mb-3"> 658 ${iconChar} 659 </div> 660 661 <p class="text-dark mb-4 pb-1">${message}</p> 662 663 <button onclick="swal.close(); ${canContinue ? "" : "location.reload();"}" class="Button-modal" style="background-color: ${buttonBg};"> 664 ${buttonText} 665 </button> 666 <button id="save-draft-feature-closeBtn" onclick="swal.close();"> 667 <svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg"> 668 <path d="M6.49674 8.5L0.415039 2.41829C-0.138346 1.86491 -0.138346 0.968424 0.415039 0.415039C0.968424 -0.138346 1.86491 -0.138346 2.41829 0.415039L8.5 6.49674L14.5817 0.415039C15.1351 -0.138346 16.0316 -0.138346 16.585 0.415039C17.1383 0.968424 17.1383 1.86491 16.585 2.41829L10.5033 8.5L16.585 14.5817C17.1383 15.1351 17.1383 16.0316 16.585 16.585C16.3083 16.8617 15.9458 17 15.5833 17C15.2209 17 14.8584 16.8617 14.5817 16.585L8.5 10.5033L2.41829 16.585C2.1416 16.8617 1.77913 17 1.41667 17C1.0542 17 0.691732 16.8617 0.415039 16.585C-0.138346 16.0316 -0.138346 15.1351 0.415039 14.5817L6.49674 8.5Z" fill="#CCCCCC"/> 669 </svg> 670 671 </button> 672 </div> 673 `; 674 675 swal({
676 buttons: false, 677 closeOnClickOutside: false, 678 closeOnEsc: false, 679 content: { 680 element: "div", 681 attributes: { 682 innerHTML: modalContent, 683 }, 684 }, 685 }); 686 687 $(document).on("click", `#save-draft-feature-closeBtn`, async function () { 688 if (type === "error") { 689 const currentUrl = window.location.href; 690 const cleanUrl = currentUrl.split("?draftToken")[0]; 691 window.location.href = cleanUrl; 692 } 693 }); 694 695 setTimeout(() => { 696 // Add class to the .swal-content wrapper 697 const swalModal = document.querySelector(".swal-modal"); 698 if (swalModal) { 699 swalModal.classList.add("my-swal-wrapper"); 700 } 701 }); 702 }; 703 704 const compareMetadata = (currentMetadata, draftMetadata) => { 705 if (!currentMetadata || !draftMetadata) { 706 return { changed: true, lastOperation: "unknown" }; 707 } 708 709 const currentQuestions = {}; 710 const draftQuestions = {}; 711 const changeHistory = []; 712 713 currentMetadata.Content.pages.forEach((page) => { 714 page.elements?.forEach((el) => { 715 const key = el.valueName || el.name; 716 currentQuestions[key] = true; 717 }); 718 }); 719 720 draftMetadata.Content.pages.forEach((page) => { 721 page.elements?.forEach((el) => { 722 const key = el.valueName || el.name; 723 draftQuestions[key] = true; 724 }); 725 }); 726 727 const addedQuestions = []; 728 const removedQuestions = []; 729 730 for (const key in currentQuestions) { 731 if (!draftQuestions[key]) { 732 addedQuestions.push(key); 733 changeHistory.push({ key, operation: "added" }); 734 } 735 } 736 737 for (const key in draftQuestions) { 738 if (!currentQuestions[key]) { 739 removedQuestions.push(key); 740 changeHistory.push({ key, operation: "removed" }); 741 } 742 } 743 744 const lastChange = changeHistory.length > 0 ? changeHistory[changeHistory.length - 1].operation : "none"; 745 746 return { 747 changed: addedQuestions.length > 0 || removedQuestions.length > 0, 748 addedQuestions, 749 removedQuestions, 750 hasAddedQuestions: addedQuestions.length > 0, 751 hasRemovedQuestions: removedQuestions.length > 0, 752 lastOperation: lastChange, 753 removedPagesMap: removedQuestions.map((q) => draftQuestions[q]?.page?.name), 754 }; 755 }; 756 757 // Function to compare metadata and check for changes 758 // const compareMetadata = (currentMetadata, draftMetadata) => { 759 // if (!currentMetadata || !draftMetadata) return { changed: true }; 760 761 // const currentQuestions = {}; 762 // const draftQuestions = {}; 763 764 // // Extract all questions from current form 765 // currentMetadata.Content.pages.forEach(page => { 766 // page.elements?.forEach(el => { 767 // currentQuestions[el.valueName || el.name] = { ...el, page }; 768 // }); 769 // }); 770 // draftMetadata.Content.pages.forEach(page => { 771 // page.elements?.forEach(el => { 772 // draftQuestions[el.valueName || el.name] = { ...el, page }; 773 // }); 774 // }); 775 776 // // Check for added questions 777 // const addedQuestions = Object.keys(currentQuestions).filter( 778 // key => !draftQuestions[key] 779 // ); 780 781 // // Check for removed questions 782 // const removedQuestions = Object.keys(draftQuestions).filter( 783 // key => !currentQuestions[key] 784 // ); 785 786 // return { 787 // changed: addedQuestions.length > 0 || removedQuestions.length > 0, 788 // addedQuestions, 789 // removedQuestions, 790 // // Add these flags for easier checking 791 // hasAddedQuestions: addedQuestions.length > 0, 792 // hasRemovedQuestions: removedQuestions.length > 0, 793 // removedPagesMap: removedQuestions.map(q => draftQuestions[q]?.page?.name) 794 // }; 795 // }; 796 797 // Scroll helper 798 const scrollToQuestion = (questionName) => { 799 setTimeout(() => { 800 let el = document.querySelector(`[name="${questionName}"]`) || document.querySelector(`[data-name="${questionName}"]`) || Array.from(document.querySelectorAll("*")).find((e) => e.name === questionName || e.dataset?.name === questionName); 801 802 if (el) { 803 console.log("â Scrolling to:", questionName); 804 el.scrollIntoView({ behavior: "smooth", block: "center" }); 805 } else { 806 console.warn("â ï¸ Could not find element to scroll to:", questionName); 807 } 808 }, 300); 809 }; 810 811 // Get page index of question 812 const getPageIndexOfQuestion = (survey, questionName) => { 813 for (let i = 0; i < survey.pages.length; i++) { 814 if (survey.pages[i].elements.some((q) => q.name === questionName)) { 815 return i; 816 } 817 } 818 return 0; 819 }; 820 821 // Get last question on a given page 822 const getLastQuestionOnPage = (page) => { 823 if (!page || !page.elements?.length) return null; 824 return page.elements[page.elements.length - 1].name; 825 }; 826 827 // Find fallback question when a removed question exists 828 const findFallbackQuestionFromRemoved = (survey, removedPagesMap) => { 829 for (let pageName of removedPagesMap) { 830 const page = survey.pages.find((p) => p.name === pageName); 831 if (page && page.elements.length > 0) { 832 return getLastQuestionOnPage(page); 833 } 834 } 835 836 // fallback to last question of previous page 837 for (let i = survey.pages.length - 1; i >= 0; i--) { 838 if (survey.pages[i].elements.length > 0) { 839 return getLastQuestionOnPage(survey.pages[i]); 840 } 841 } 842 return null; 843 }; 844 845 // Load draft 846 if (draftToken) { 847 try { 848 // Call API to get draft data 849 const draftResponse = await $.ajax({ 850 url: `/api/form/${formId}/draft/${draftToken}`, 851 method: "GET", 852 contentType: "application/json", 853 dataType: "json", 854 }); 855 856 if (draftResponse.status === 200) { 857 const draftData = draftResponse.data; 858 859 // Compare metadata 860 const comparison = compareMetadata(data.Metadata, draftData.Metadata); 861 862 // Load form data 863 survey.data = draftData.FormData; 864 if (draftData.Files) {
865 Object.keys(draftData.Files).forEach((key) => { 866 uploadedFiles[key] = draftData.Files[key].map((fileInfo) => ({ 867 ...fileInfo, 868 // Add file property if needed for immediate use 869 name: fileInfo.FileName, 870 })); 871 }); 872 } 873 874 // Create question lookup 875 const currentQuestionMap = {}; 876 survey.pages.forEach((p) => { 877 p.elements.forEach((el) => { 878 currentQuestionMap[el.name] = el; 879 }); 880 }); 881 882 // Ordered list 883 const orderedQuestionNames = []; 884 survey.pages.forEach((p) => { 885 p.elements.forEach((el) => orderedQuestionNames.push(el.name)); 886 }); 887 888 // Get last answered question 889 const answeredKeys = Object.keys(draftData.FormData || {}); 890 let lastAnsweredQuestion = null; 891 for (let i = answeredKeys.length - 1; i >= 0; i--) { 892 if (currentQuestionMap[answeredKeys[i]]) { 893 lastAnsweredQuestion = answeredKeys[i]; 894 break; 895 } 896 } 897 898 let targetQuestion = lastAnsweredQuestion; 899 900 // If removed, find closest fallback 901 if (comparison.hasRemovedQuestions) { 902 targetQuestion = findFallbackQuestionFromRemoved(survey, comparison.removedPagesMap); 903 console.warn("ð Scrolling to fallback due to removal:", targetQuestion); 904 } else if (!targetQuestion || comparison.removedQuestions.includes(targetQuestion)) { 905 targetQuestion = orderedQuestionNames.at(-1); // last fallback 906 } 907 908 // Decide page to load 909 let initialPageIndex = 0; 910 if (comparison.hasAddedQuestions) { 911 initialPageIndex = 0; 912 } else if (comparison.hasRemovedQuestions && targetQuestion) { 913 initialPageIndex = getPageIndexOfQuestion(survey, targetQuestion); 914 } else if (!comparison.changed && typeof draftData.CurrentPage === "number") { 915 initialPageIndex = draftData.CurrentPage; 916 } else { 917 initialPageIndex = getPageIndexOfQuestion(survey, targetQuestion); 918 } 919 survey.currentPageNo = initialPageIndex; 920 921 let scrolled = false; 922 923 survey.onAfterRenderPage.add(() => { 924 if (scrolled) return; 925 if (comparison.lastOperation === "added") { 926 showDraftStatusPopup("new-question", "A new question has been added to the form since your last visit. Kindly review the form again.", true); 927 survey.currentPageNo = 0; 928 } else if (comparison.lastOperation === "removed") { 929 showDraftStatusPopup("removed-question", "A question you previously answered has been removed from the form. Your response to that question will not be included in your submission.", true); 930 scrollToQuestion(targetQuestion); 931 } 932 // if (comparison.hasAddedQuestions) { 933 // showDraftStatusPopup( 934 // "new-question", 935 // "A new question has been added to the form since your last visit. Kindly review the form again.", 936 // true 937 // ); 938 // survey.currentPageNo = 0; 939 // } else if (comparison.hasRemovedQuestions) { 940 // showDraftStatusPopup( 941 // "removed-question", 942 // "A question you previously answered has been removed from the form. Your response to that question will not be included in your submission.", 943 // true 944 // ); 945 // scrollToQuestion(targetQuestion); 946 // } 947 else { 948 showDraftStatusPopup("resume", "Let's pick up right where you left off!", true); 949 setTimeout(() => scrollToQuestion(targetQuestion), 500); 950 } 951 scrolled = true; 952 }); 953 954 survey.onCurrentPageChanged.add(() => { 955 if (!scrolled && !(comparison.hasAddedQuestions && comparison.hasRemovedQuestions)) { 956 scrollToQuestion(targetQuestion); 957 scrolled = true; 958 } 959 }); 960 } 961 } catch (error) { 962 if (error.status === 404) { 963 showDraftStatusPopup("error", "The draft form link you provided is invalid. This may be because the link is incorrect or the form has already been submitted. Please check the link and try again.", false); 964 } else { 965 showDraftStatusPopup("error", "An unexpected error occurred while saving your progress. Please try again later or contact support if the issue persists.", false); 966 } 967 } 968 } 969 // AUTO-RECOVERY FUNCTIONALITY 970 const loadDraftData = () => { 971 // Only load from localStorage if we don't have a draft token 972 if (!draftToken) { 973 const draft = loadDraftFromLocalStorage(formId); 974 if (draft && draft.data) { 975 survey.data = draft.data; 976 if (draft.currentPage >= 0 && draft.currentPage < survey.visiblePages.length) { 977 survey.currentPageNo = draft.currentPage; 978 } 979 } 980 } 981 }; 982 loadDraftData(); 983 984 const saveDraftToDatabase = async () => { 985 if (urlParams.has("renderMode") || autoSaveToDB) return; 986 try { 987 if (!navigator.onLine) { 988 return showDraftStatusPopup("error", "Weâve encountered network error, reconnect to the internet and try saving again.", false); 989 } 990 // Get the current auto-saved data 991 const autoSavedData = loadDraftFromLocalStorage(formId); 992 if (!autoSavedData || !autoSavedData.data) { 993 return swal({ text: "No draft data to save", icon: "info" }); 994 } 995 996 const formData = new FormData(); 997 // Append files to FormData
998 Object.keys(uploadedFiles).forEach((key) => { 999 uploadedFiles[key].forEach((fileObj) => { 1000 formData.append(key, fileObj.file, fileObj.file.name); 1001 }); 1002 delete surveyData[key]; 1003 }); 1004 // Prepare the draft data 1005 const draftData = { 1006 formData: survey.data, 1007 // formData: autoSaveToDB ? survey.data : autoSavedData.data, 1008 currentPage: survey.currentPageNo, 1009 metadata: autoSavedData.metadata, 1010 form_id: data._id, 1011 draftToken: draftToken ? draftToken : null, 1012 formId: formId, 1013 }; 1014 1015 formData.append("draftData", JSON.stringify(draftData)); 1016 // Call the API 1017 const response = await $.ajax({ 1018 url: `/api/form/${formId}/draft`, 1019 method: "POST", 1020 data: formData, 1021 contentType: false, 1022 processData: false, 1023 }); 1024 1025 if (response.status === "success") { 1026 // Store the token locally 1027 // if(!autoSaveToDB){ 1028 1029 let resumeLink = ""; 1030 const currentUrl = new URL(window.location.href); 1031 if (currentUrl.searchParams.has("draftToken")) { 1032 // Replace existing draftToken 1033 currentUrl.searchParams.set("draftToken", response.data.draftToken); 1034 resumeLink = currentUrl.toString(); 1035 } else { 1036 resumeLink = `${window.location.href}?draftToken=${response.data.draftToken}`; 1037 } 1038 let dfToken = response.data.draftToken; 1039 triggerDraftSavedPopup(resumeLink, data, survey, dfToken); 1040 // } 1041 autoSaveToDB = true; 1042 return response.data; 1043 } else { 1044 throw new Error(response.message || "Failed to save draft"); 1045 } 1046 } catch (error) { 1047 console.error("Draft save error:", error); 1048 swal({ 1049 title: "Error", 1050 text: "Failed to save draft. Please try again.", 1051 icon: "error", 1052 }); 1053 throw error; 1054 } 1055 }; 1056 1057 survey.onValueChanged.add((sender, options) => { 1058 if (options.question.getType() === "phonenumber" && options.question.itl) { 1059 const fullNumber = options.question.itl.getNumber(); 1060 options.question.value = fullNumber; // update SurveyJS value 1061 sender.data[options.question.valueName] = fullNumber; // update sender.data (important for localStorage) 1062 } 1063 if (!recordTokenId && data.SaveProgressEnabled && (renderMode === null || renderMode !== "Preview")) { 1064 const filteredData = {...sender.data}; 1065 sender.getAllQuestions().forEach(question => { 1066 if (question.getType() === "paneldynamic" && filteredData[question.valueName]) { 1067 const panelData = filteredData[question.valueName]; 1068 1069 if (Array.isArray(panelData)) { 1070 // Process each panel instance 1071 filteredData[question.valueName] = panelData.map(panelInstance => { 1072 const filteredInstance = { ...panelInstance }; 1073 // Find file questions within this panel 1074 question.templateElements.forEach(templateQuestion => { 1075 if (templateQuestion.getType() === "file") { 1076 const fileValueName = templateQuestion.valueName; 1077 if (filteredInstance[fileValueName]) { 1078 // Remove file data 1079 delete filteredInstance[fileValueName]; 1080 } 1081 } 1082 }); 1083 return filteredInstance; 1084 }); 1085 } 1086 } else if (question.getType() === "file") { 1087 // Remove regular file questions 1088 delete filteredData[question.valueName]; 1089 } 1090 }); 1091 saveToLocalStorage(formId, filteredData, sender.currentPageNo, data.Metadata); 1092 // saveToLocalStorage(formId, sender.data, sender.currentPageNo, data.Metadata); 1093 // autoSaveToDB ? saveDraftToDatabase() : saveToLocalStorage(formId, sender.data, sender.currentPageNo, data.Metadata); 1094 } 1095 }); 1096 1097 survey.onCurrentPageChanged.add((sender) => { 1098 if (!recordTokenId && data.SaveProgressEnabled && (renderMode === null || renderMode !== "Preview")) { 1099 saveToLocalStorage(formId, sender.data, sender.currentPageNo, data.Metadata); 1100 // autoSaveToDB ? saveDraftToDatabase() : saveToLocalStorage(formId, sender.data, sender.currentPageNo, data.Metadata); 1101 } 1102 }); 1103 1104 // Clear storage on complete 1105 // survey.onComplete.add(() => { 1106 // localStorage.removeItem(getAutoSaveKey(formId)); 1107 // localStorage.removeItem(`formDraft_${formId}`); 1108 // }); 1109 1110 const resetForm = () => { 1111 if (Object.keys(survey.data).length === 0) { 1112 return swal({ 1113 icon: "warning", 1114 title: "Nothing to Reset", 1115 text: "There is no saved data to reset in this form.", 1116 buttons: { 1117 ok: { 1118 text: "OK", 1119 className: "swal-button--confirm", 1120 }, 1121 }, 1122 }); 1123 } 1124 1125 const modalContent = ` 1126 <div style="text-align: center; padding: 10px 20px;"> 1127 <div class="mb-3"> 1128 <svg width="90" height="90" viewBox="0 0 90 90" fill="none" xmlns="http://www.w3.org/2000/svg"> 1129 <rect width="90" height="90" rx="45" fill="#FFC107" fill-opacity="0.1"/> 1130 <rect x="12" y="12" width="66" height="66" rx="33" fill="#FFC107"/> 1131 <path fill-rule="evenodd" clip-rule="evenodd" d="M44.7644 52.3334C44.2442 52.3334 43.7991 51.8895 43.7088 51.2789L41.0479 33.3322C40.7134 31.072 42.1735 29 44.1005 29H45.4271C47.3541 29 48.8142 31.072 48.4797 33.3322L45.8189 51.2789C45.7298 51.8895 45.2847 52.3334 44.7644 52.3334Z" fill="white"/> 1132 <path fill-rule="evenodd" clip-rule="evenodd" d="M47.197 59.0829C47.197 57.7423 46.1032 56.6484 44.7626 56.6484C43.422 56.6484 42.3281 57.7423 42.3281 59.0829C42.3281 60.4235 43.422 61.5173 44.7626 61.5173C46.1032 61.5173 47.197 60.4248 47.197 59.0829Z" fill="white"/> 1133 </svg>
1134 </div> 1135 <p class="text-dark mb-4 pb-1" style="font-weight: 500;">Youâre about to reset the previously filled data, all form data which has not been submitted will be lost</p> 1136 <div style="display: flex; justify-content: center; gap: 10px;"> 1137 <button id="noBtn" class="Button-modal" style="background-color: #6c757d; color: white;">Cancel</button> 1138 <button id="yesBtn" class="Button-modal" style="background-color: #FFC107; color: white;">Proceed</button> 1139 </div> 1140 <button id="save-draft-feature-closeBtn" style="margin-top: 15px; background: transparent; border: none;"> 1141 <svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg"> 1142 <path d="M6.49674 8.5L0.415039 2.41829C-0.138346 1.86491 -0.138346 0.968424 0.415039 0.415039C0.968424 -0.138346 1.86491 -0.138346 2.41829 0.415039L8.5 6.49674L14.5817 0.415039C15.1351 -0.138346 16.0316 -0.138346 16.585 0.415039C17.1383 0.968424 17.1383 1.86491 16.585 2.41829L10.5033 8.5L16.585 14.5817C17.1383 15.1351 17.1383 16.0316 16.585 16.585C16.3083 16.8617 15.9458 17 15.5833 17C15.2209 17 14.8584 16.8617 14.5817 16.585L8.5 10.5033L2.41829 16.585C2.1416 16.8617 1.77913 17 1.41667 17C1.0542 17 0.691732 16.8617 0.415039 16.585C-0.138346 16.0316 -0.138346 15.1351 0.415039 14.5817L6.49674 8.5Z" fill="#CCCCCC"/> 1143 </svg> 1144 </button> 1145 </div> 1146 `; 1147 1148 swal({
1149 buttons: false, 1150 closeOnClickOutside: false, 1151 closeOnEsc: false, 1152 content: { 1153 element: "div", 1154 attributes: { 1155 innerHTML: modalContent, 1156 }, 1157 }, 1158 }); 1159 1160 $(document) 1161 .off("click", "#yesBtn") 1162 .on("click", "#yesBtn", function () { 1163 localStorage.removeItem(getAutoSaveKey(formId)); 1164 survey.clear(); 1165 uploadedFiles = {}; 1166 survey 1167 .getAllQuestions() 1168 .filter((q) => q.getType() === "datepicker") 1169 .forEach((q) => { 1170 if (q.datepicker) { 1171 q.datepicker.setDate(null); // picker 1172 q.datepicker.element.value = ""; // <input> 1173 } 1174 }); 1175 swal.close(); 1176 document.querySelectorAll(".iti input").forEach((input) => { 1177 input.value = ""; 1178 }); 1179 }); 1180 1181 $(document) 1182 .off("click", "#noBtn") 1183 .on("click", "#noBtn", function () { 1184 swal.close(); 1185 }); 1186 1187 $(document) 1188 .off("click", "#save-draft-feature-closeBtn") 1189 .on("click", "#save-draft-feature-closeBtn", function () { 1190 swal.close(); 1191 }); 1192 }; 1193 1194 if ((data.SaveProgressEnabled || data?.SaveProgressEnabled === undefined) && !recordTokenId) { 1195 survey.addNavigationItem({ 1196 id: "sv-nav-save-draft", 1197 title: "Save Progress", 1198 action: saveDraftToDatabase, 1199 css: "sd-btn--secondary", 1200 innerCss: "sd-btn sd-nav-save-draft", 1201 visibleIndex: 2, 1202 }); 1203 // Clear Fields button as hyperlink style 1204 survey.addNavigationItem({ 1205 id: "sv-nav-clear-fields", 1206 title: "Reset", 1207 // action: function() { 1208 // localStorage.removeItem(getAutoSaveKey(formId)); 1209 // survey.clear(); 1210 // }, 1211 action: resetForm, 1212 css: "sd-btn--link", // This makes it look like a link 1213 innerCss: "sd-nav-clear-fields sd-clear-btn-link", // Custom class for styling 1214 // visibleIndex: 1, 1215 }); 1216 1217 const resetButton = survey.navigationBar.actions.find((action) => action.id === "sv-nav-clear-fields"); 1218 1219 // if(completeButton) completeButton.visibleIndex = 2; 1220 // if(saveDraftButton) saveDraftButton.visibleIndex = 1; 1221 if (resetButton) resetButton.visibleIndex = 3; 1222 } 1223 1224 survey.applyTheme(data.Metadata.Theme); 1225 1226 if (recordTokenId) { 1227 try { 1228 const recordData = await $.ajax({ 1229 url: `/api/form/${formId}/${recordTokenId}`, 1230 method: "GET", 1231 contentType: "application/json", 1232 }); 1233 if (recordData?.status === "error") { 1234 return swal({ text: "Invalid Record Token", icon: "error" }); 1235 } 1236 const { _id, UserAgent, UpdatedBy, UpdatedDate, CreatedAt, recordToken, tokenExpiry, ...cleanedData } = recordData?.data || {}; 1237 if (recordData?.data && cleanedData) { 1238 isFrameLoad = true; 1239 survey.data = cleanedData; 1240 window.currentRecordId = _id; 1241 1242 // ð¹ Only show file names (no upload binding) 1243 survey.getAllQuestions().forEach((q) => { 1244 if (q.getType() === "file" && cleanedData[q.valueName]) { 1245 let savedFiles = cleanedData[q.valueName]; 1246 if (!Array.isArray(savedFiles)) savedFiles = [savedFiles]; 1247 q.value = savedFiles.map((file) => ({ name: file.FileName || file.name || "Unknown file" })); 1248 } 1249 if (q.getType() === "paneldynamic" && cleanedData[q.valueName]) { 1250 let panelData = cleanedData[q.valueName]; 1251 if (Array.isArray(panelData)) { 1252 panelData.forEach((panelItem, panelIndex) => { 1253 // Loop over each inner question in template 1254 q.templateElements.forEach((innerQ) => { 1255 if (innerQ.getType() === "file" && panelItem[innerQ.valueName]) { 1256 let savedFiles = panelItem[innerQ.valueName]; 1257 if (!Array.isArray(savedFiles)) savedFiles = [savedFiles]; 1258 // set file value in the correct panel row 1259 q.panels[panelIndex].getQuestionByName(innerQ.name).value = savedFiles.map((file) => ({ 1260 name: file.FileName || file.name || "Unknown file", 1261 })); 1262 } 1263 }); 1264 }); 1265 } 1266 } 1267 }); 1268 } 1269 } catch (err) { 1270 console.error("Error fetching record by token:", err); 1271 } 1272 } 1273 1274 function renderCaptcha() { 1275 const captchaQuestion = { 1276 name: "Captcha", 1277 isRequired: true, 1278 requiredErrorText: "Please complete the captcha", 1279 type: "system-captcha", 1280 }; 1281 const pageCount = survey.pages.length; 1282 if (pageCount > 0) { 1283 const lastPage = survey.pages[pageCount - 1]; 1284 const captchaQuest = Survey.Serializer.createClass(captchaQuestion.type); 1285 captchaQuest.fromJSON(captchaQuestion); 1286 lastPage.addQuestion(captchaQuest); 1287 } 1288 } 1289 1290 if (data.Captcha == 1) renderCaptcha(); 1291 1292 // Custom Error Handling 1293 survey.onSettingQuestionErrors.add((sender, options) => { 1294 const question = options.question; 1295 if (question.value && question.itl && question.getType() === "phonenumber") { 1296 question.clearErrors(); 1297 let isValidNo = true; 1298 let validKeyChars = [48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 32, 40, 41, 43, 45]; 1299 for (const c of question.value) { 1300 if (!validKeyChars.includes(c.charCodeAt(0))) { 1301 options.errors.push(new Survey.CustomError(`Invalid number.`)); 1302 isValidNo = false;
1303 break; 1304 } 1305 } 1306 1307 if (isValidNo && !question.itl.isValidNumber()) { 1308 const errorMap = ["Invalid number", "Invalid country code", "Number too short", "Number too long"]; 1309 const errorCode = question.itl.getValidationError(); 1310 const errorMessage = errorMap[errorCode] || "Invalid number"; 1311 !options.errors.length && options.errors.push(new Survey.CustomError(errorMessage)); 1312 } 1313 1314 if (isValidNo) question.value = question.itl.getNumber(); 1315 } 1316 if (question.getType() === "termsconditions" && question.value === false) { 1317 options.errors.push(new Survey.CustomError("Response required.")); 1318 } 1319 1320 if (question.getType() === "emailfield" && question.value) { 1321 const emailRegex = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@(([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,})$/; 1322 if (!emailRegex.test(question.value)) { 1323 options.errors.push(new Survey.CustomError(`Invalid email.`)); 1324 } 1325 } 1326 1327 if (question.value && question.inputType === "tel") { 1328 question.clearErrors(); 1329 let validKeyChars = [48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 32, 40, 41, 43, 45]; 1330 for (const c of question.value) { 1331 if (!validKeyChars.includes(c.charCodeAt(0))) { 1332 options.errors.push(new Survey.CustomError(`Invalid number.`)); 1333 break; 1334 } 1335 } 1336 } 1337 }); 1338 1339 survey.onServerValidateQuestions.add(async (_, options) => { 1340 try { 1341 if (formCompleteObject.navigateToUrl) _.navigateToUrl = formCompleteObject.navigateToUrl; 1342 if (formCompleteObject.navigateToUrlOnCondition) _.navigateToUrlOnCondition = formCompleteObject.navigateToUrlOnCondition; 1343 surveyData = {}; 1344 const cleanUploadedFiles = {}; 1345 Object.assign(surveyData, _.data); 1346 1347 const formLastPage = _.isLastPage; 1348 1349 if (!isPreview && _.isLastPage && !isFormPreview) { 1350 const formData = new FormData(); 1351 // const surveyData = { ...options.data }; 1352 // options.allowComplete = false; 1353 // Append files to FormData 1354 Object.keys(uploadedFiles).forEach((key) => { 1355 const files = uploadedFiles[key].map((file) => file.file); 1356 delete surveyData[key]; 1357 files.forEach((file) => file && formData.append(key, file, file.name)); 1358 }); 1359 1360 // const isAllFileMetaValid = Object.values(uploadedFiles).every((fileArray) => 1361 // fileArray.every( 1362 // (fileObj) => 1363 // fileObj?.FileName && 1364 // fileObj?.Path && 1365 // fileObj?.fieldname && 1366 // fileObj?.Ext 1367 // ) 1368 // ); 1369 1370 if (draftToken) { 1371 // Object.keys(uploadedFiles).forEach((key) => { 1372 // cleanUploadedFiles[key] = uploadedFiles[key].map(({ file, ...rest }) => rest); 1373 // }); 1374 Object.keys(uploadedFiles).forEach((key) => { 1375 const fileArray = uploadedFiles[key]; 1376 1377 if (Array.isArray(fileArray)) { 1378 // Separate valid and invalid files 1379 const draftFiles = fileArray.filter((fileObj) => fileObj?.FileName && fileObj?.Path); 1380 const formFiles = fileArray.filter((fileObj) => !(fileObj?.FileName && fileObj?.Path)); 1381 // If there are valid files, store them in cleanUploadedFiles 1382 if (draftFiles.length > 0) { 1383 cleanUploadedFiles[key] = draftFiles.map(({ file, ...rest }) => rest); 1384 } 1385 1386 // Replace uploadedFiles[key] with only the invalid files 1387 if (formFiles.length > 0) { 1388 uploadedFiles[key] = formFiles; 1389 } else { 1390 // If no invalid files, remove the key entirely 1391 delete uploadedFiles[key]; 1392 } 1393 } 1394 }); 1395 } 1396 1397 // If captcha 1398 if (data.Captcha != 0) { 1399 if (data.Captcha == 1) { 1400 const key = $(el).find("[data-system-captcha-token]").attr("data-system-captcha-token"); 1401 surveyData["Captcha"] = `${surveyData["Captcha"]}:${key}`; 1402 } else { 1403 surveyData.Captcha = await verifyAndGetGoogleCaptchaToken(); 1404 } 1405 } 1406 survey.getAllQuestions().forEach((question) => { 1407 const val = question.value; 1408 1409 switch (question.getType()) { 1410 case "multilookupfield": 1411 surveyData[question.valueName] = getMultiLookup(question, val); 1412 break; 1413 1414 case "lookupfield": 1415 surveyData[question.valueName] = getLookup(question, val); 1416 break; 1417 1418 case "address": 1419 surveyData[question.valueName] = val?.cityDropdown ? getAddress(question, val) : val; 1420 break; 1421 1422 case "dropdown": 1423 surveyData[question.valueName] = getDropdown(question, val); 1424 break; 1425 1426 case "tagbox": 1427 surveyData[question.valueName] = getTagbox(question, val); 1428 break; 1429 1430 case "file": 1431 if (val && val.length > 0) surveyData[question.valueName] = val; 1432 break; 1433 1434 case "paneldynamic": 1435 surveyData[question.valueName] = (question.panels || []).map((panel) => { 1436 let rowData = {};
1437 (panel.questions || []).forEach((el) => { 1438 const innerVal = el.value; 1439 1440 switch (el.getType()) { 1441 case "multilookupfield": 1442 rowData[el.valueName] = getMultiLookup(el, innerVal); 1443 break; 1444 1445 case "lookupfield": 1446 rowData[el.valueName] = getLookup(el, innerVal); 1447 break; 1448 1449 case "address": 1450 rowData[el.valueName] = innerVal?.cityDropdown ? getAddress(el, innerVal) : innerVal; 1451 break; 1452 1453 case "dropdown": 1454 if(innerVal == "other") { 1455 rowData[el.valueName + "-Comment"] = el.comment 1456 } 1457 rowData[el.valueName] = getDropdown(el, innerVal); 1458 break; 1459 1460 case "radiogroup": 1461 if(innerVal == "other") { 1462 rowData[el.valueName + "-Comment"] = el.comment 1463 } 1464 rowData[el.valueName] = innerVal; 1465 break; 1466 1467 case "checkbox": 1468 if(innerVal.includes("other")){ 1469 rowData[el.valueName + "-Comment"] = el.comment 1470 } 1471 rowData[el.valueName] = innerVal; 1472 break; 1473 1474 case "tagbox": 1475 if(innerVal.includes("other")){ 1476 rowData[el.valueName + "-Comment"] = el.comment 1477 } 1478 rowData[el.valueName] = getTagbox(el, innerVal); 1479 break; 1480 1481 case "file": 1482 rowData[el.valueName] = innerVal ? innerVal : []; 1483 break; 1484 1485 default: 1486 rowData[el.valueName] = innerVal; 1487 } 1488 }); 1489 return rowData; 1490 }); 1491 break; 1492 1493 default: 1494 surveyData[question.valueName] = val; 1495 } 1496 }); 1497 1498 if (isFrameLoad && window.currentRecordId) { 1499 formData.append("recordId", window.currentRecordId); 1500 } 1501 1502 if (draftToken) formData.append("files", JSON.stringify(cleanUploadedFiles)); 1503 formData.append("fields", JSON.stringify(surveyData)); 1504 formData.append("draftToken", draftToken ? draftToken : ""); 1505 $("#tp--form-loader").show(); 1506 $("body").addClass("overflow-hidden"); 1507 // AJAX request to send FormData 1508 const response = await $.ajax({ 1509 url: `/api/form/${formId}`, 1510 method: "post", 1511 data: formData, 1512 cache: false, 1513 contentType: false, 1514 processData: false, 1515 }); 1516 if (response.status === "success") { 1517 // â Close parent popup if iframe mode 1518 if (isFrameLoad && window.parent) { 1519 window.parent.postMessage({ type: "CloseIframe" }, "*"); 1520 } 1521 if (recordTokenId) { 1522 $("body").removeClass("overflow-hidden"); 1523 $("#tp--form-loader").hide(); 1524 return swal({ text: "Record(s) updated", icon: "success", buttons: fal
1524se, timer: 2000 }); 1525 } 1526 } 1527 1528 $("body").removeClass("overflow-hidden"); 1529 $("#tp--form-loader").hide(); 1530 Object.assign(surveyData, {}); 1531 if (["error", "conflict", "captcha_required", "captcha_mismatch", "limitReached"].includes(response.status)) { 1532 options.complete(); 1533 _.clear(false, false); 1534 await survey.onScrollingElementToTop.add((sender, options) => { 1535 options.cancel = true; 1536 }); 1537 } 1538 1539 switch (response.status) { 1540 case "error": 1541 return swal({ text: response.message ? response.message : "Something went wrong!.", icon: "error" }); 1542 case "conflict": 1543 return swal({ 1544 // text: "This form has been submitted. It cannot be submitted more than once.", 1545 text: response?.message || "This form has been submitted. It cannot be submitted more than once.", 1546 buttons: { 1547 ok: { 1548 text: "OK", 1549 className: "confirm-btn", 1550 }, 1551 }, 1552 }); 1553 case "captcha_required": 1554 return swal({ text: "You must complete the security question." }); 1555 case "limitReached": 1556 return swal({ text: response.message }); 1557 case "captcha_mismatch": 1558 if (data.Captcha == 1) { 1559 options.errors["Captcha"] = "Invalid captcha value"; 1560 return swal({ text: "Invalid captcha value.", icon: "error" }); 1561 } else { 1562 return swal({ text: "We have detected unusual activity from your system. This form will not be submitted." }); 1563 } 1564 break; 1565 } 1566 } 1567 options.complete(); 1568 1569 if (formLastPage) { 1570 uploadedFiles = {}; 1571 let SubmissionMessage = _.locCompletedHtml.renderedText || _.locCompletedHtml.renderedHtml; 1572 _.clear(true, false); 1573 $(".survey-captcha input.txtCaptch").val(""); 1574 setTimeout(() => { 1575 _.clear(true, false); 1576 }, 100); 1577 if (isFormPreview) { 1578 generateFormResults(surveyData); 1579 } 1580 await survey.onScrollingElementToTop.add((sender, options) => { 1581 options.cancel = true; 1582 }); 1583 SubmissionMessage = SubmissionMessage.replace(/<[^>]+>/g, ""); 1584 if (!_.navigateToUrl && !(_.navigateToUrlOnCondition && _.navigateToUrlOnCondition.length) && !isFormPreview) { 1585 localStorage.removeItem(getAutoSaveKey(formId)); 1586 localStorage.removeItem(`formDraft_${formId}`); 1587 return swal({ text: SubmissionMessage, icon: "success" }); 1588 } 1589 } 1590 } catch (error) { 1591 $("body").removeClass("overflow-hidden"); 1592 $("#tp--form-loader").hide(); 1593 console.error(error.responseJSON, error); 1594 1595 if (_.navigateToUrl) formCompleteObject["navigateToUrl"] = _.navigateToUrl; 1596 if (_.navigateToUrlOnCondition) formCompleteObject["navigateToUrlOnCondition"] = _.navigateToUrlOnCondition; 1597 _.navigateToUrl = ""; 1598 _.navigateToUrlOnCondition = []; 1599 1600 if (error.responseJSON && error.responseJSON.status == "error") { 1601 options.complete(); 1602 _.clear(false, false); 1603 await survey.onScrollingElementToTop.add((sender, options) => { 1604 options.cancel = true; 1605 }); 1606 return swal({ text: error.responseJSON.data ? error.responseJSON.data[0] : "Something went wrong!.", icon: "error" }); 1607 } else { 1608 if (error.toString().includes("ReferenceError: grecaptcha is not defined")) { 1609 options.complete(); 1610 _.clear(false, false); 1611 await survey.onScrollingElementToTop.add((sender, options) => { 1612 options.cancel = true; 1613 }); 1614 return swal({ text: "Google captcha is invalid.", icon: "error" }); 1615 } else { 1616 if (error.responseJSON && error.responseJSON.message === "Draft not found") { 1617 return showDraftStatusPopup("error", "The draft form link you provided is invalid. This may be because the link is incorrect or the form has already been submitted. Please check the link and try again.", false); 1618 } 1619 return swal({ text: "Something went wrong!.", icon: "error" }); 1620 } 1621 } 1622 } 1623 }); 1624 1625 // survey.onComplete.add(async (sender, options) => { 1626 // let SubmissionMessage = sender.locCompletedHtml.renderedText; 1627 // sender.clear(true, false); 1628 // await survey.onScrollingElementToTop.add((sender, options) => { 1629 // options.cancel = true; 1630 // }) 1631 // SubmissionMessage = SubmissionMessage.replace(/<[^>]+>/g, ''); 1632 // return swal({ text: SubmissionMessage, icon: 'success' }) 1633 // }) 1634 1635 function formatFileSize(bytes) { 1636 const sizes = ["Bytes", "KB", "MB", "GB"]; 1637 let i = 0; 1638 while (bytes >= 1024 && i < sizes.length - 1) { 1639 bytes /= 1024; 1640 i++; 1641 } 1642 return `${bytes.toFixed(i > 1 ? 2 : 0)} ${sizes[i]}`; 1643 } 1644 1645 survey.onUploadFiles.add((_, options) => { 1646 const isPanelField = options.question.parent.getType() === "panel"; 1647 1648 let uniqueKey = options.question.valueName; 1649 let panelInfo = null; 1650 1651 if (isPanelField) { 1652 let panel = options.question.parent; // panel row instance 1653 let parentPanelDynamic = panel?.parentQuestion || panel?.parentPanel; // real dynamic panel question 1654 let panelIndex = null; 1655 let panelName = null; 1656 1657 // â Get correct index safely 1658 if (parentPanelDynamic?.panels && Array.isArray(parentPanelDynamic.panels)) { 1659 panelIndex = parentPanelDynamic.panels.indexOf(panel); 1660 } 1661 1662 // â Use actual dynamic panel question name 1663 panelName = parentPanelDynamic?.name || panel?.name || "PanelDynamic"; 1664 1665 // Fallback if index still not found 1666 if (panelIndex === -1 || panelIndex === null || panelIndex === undefined) { 1667 panelIndex = parentPanelDynamic?.currentIndex ?? 0; 1668 } 1669 1670 panelInfo = { panelName, panelIndex, valueName: options.question.valueName }; 1671 uniqueKey = `PANELFILE_${panelName}_${panelIndex}_${options.question.valueName}`; 1672 } 1673 1674 1675 const files = options.files.map((file) => ({ 1676 file, 1677 ...(isPanelField && { 1678 isPanelField: true, 1679 panelName: panelInfo.panelName, 1680 panelIndex: panelInfo.panelIndex, 1681 originalFilename: file.name,
1682 name: `${Makeid(6)}-${file.name.replace(/[^a-zA-Z0-9-_.]/g, "-")}`, 1683 }), 1684 })); 1685 1686 if (isPanelField) { 1687 uploadedFiles[uniqueKey] = [...(uploadedFiles[uniqueKey] || []), ...files]; 1688 } else { 1689 uploadedFiles[options.question.valueName] = [ 1690 ...(uploadedFiles[options.question.valueName] || []), 1691 ...files, 1692 ]; 1693 } 1694 options.callback(files); 1695 1696 for (const file of files) { 1697 let fileType = file.file.name.split(".").pop().toLowerCase(); 1698 let fileMime = file.file.type; 1699 let isAccepted = !options.question.acceptedTypes; 1700 let acceptedFormats = []; 1701 if (!options.question.acceptedTypes?.includes(",") && options.question.acceptedTypes?.split("*").length - 1 > 1) { 1702 // for 'audio/*video/*' case 1703 acceptedFormats = []; 1704 } else { 1705 acceptedFormats = options.question.acceptedTypes 1706 ?.split(",") 1707 ?.map((i) => i.trim()) 1708 ?.map((j) => (j.startsWith(".") ? j.substring(1).toLowerCase() : j.toLowerCase())); 1709 } 1710 1711 if (acceptedFormats?.length) { 1712 isAccepted = 1713 acceptedFormats.includes(fileType) || 1714 acceptedFormats.some( 1715 (acc) => 1716 acc.endsWith("/*") && // check for audio/* etc. 1717 fileMime && 1718 fileMime.toLowerCase().startsWith(acc.split("/")[0] + "/") 1719 ); 1720 } 1721 1722 if (!isAccepted) { 1723 options.question.removeFile(file.file.name); 1724 options.question.clearErrors(); 1725 options.question.addError(new Survey.CustomError(`Invalid file type. Please upload ${options.question.acceptedTypes} file type`)); 1726 } else if (options.question.maximumfilesize && file.file.size > options.question.maximumfilesize) { 1727 options.question.removeFile(file.file.name); 1728 options.question.clearErrors(); 1729 options.question.addError(new Survey.CustomError(`The file size should not exceed ${formatFileSize(options.question.maximumfilesize)}.`)); 1730 } 1731 // }) 1732 } 1733 1734 setTimeout(() => { 1735 document.querySelectorAll(".sd-file__preview-item .sd-file__sign a[download]").forEach((link) => { 1736 link.removeAttribute("href"); // Remove download link 1737 link.style.pointerEvents = "none"; // Disable clicking 1738 link.style.cursor = "pointer"; // Change cursor 1739 }); 1740 }); 1741 }); 1742 1743 function stripRandomPrefix(fileName) { 1744 if (!fileName) return ""; 1745 const parts = fileName.split("-"); 1746 parts.shift(); // Remove the random prefix (first 6-char segment) 1747 return parts.join("-"); 1748 } 1749 1750 function normalizeFileNameForComparison(fileName) { 1751 return fileName.replace(/[^a-zA-Z0-9]/g, "").toLowerCase(); 1752 } 1753 1754 survey.onClearFiles.add((_, options) => { 1755 const files = uploadedFiles[options.question.valueName]; 1756 const question = options.question; 1757 const isPanelField = question.parent.getType() === "panel"; 1758 1759 if (isPanelField) { 1760 // Identify the panel structure 1761 const panel = question.parent; 1762 const parentQuestion = panel.parentQuestion; 1763 const panelName = parentQuestion?.name || "PanelDynamic"; 1764 const panelIndex = parentQuestion.panels.indexOf(panel); 1765 // Use question.name instead of valueName for consistent key 1766 const baseFieldName = options.question.valueName; 1767 const fieldKey = `PANELFILE_${panelName}_${panelIndex}_${baseFieldName}`; 1768 const files = uploadedFiles[fieldKey] 1769 if (Array.isArray(files)) { 1770 if (!options.fileName) { 1771 // uploadedFiles[fieldKey] = files.filter((f) => options?.value?.findIndex((v) => v.name !== f?.file?.name) !== -1); 1772 uploadedFiles[fieldKey] = []; 1773 } else { 1774 uploadedFiles[fieldKey] = files.filter((f) => options?.fileName !== f?.file?.name); 1775 } 1776 uploadedFiles[fieldKey] = uploadedFiles[fieldKey].filter((f) => { 1777 if (options.fileName && f?.FileName) { 1778 const stripped = stripRandomPrefix(f?.FileName || ""); 1779 const normalizedStored = normalizeFileNameForComparison(stripped); 1780 const normalizedTarget = normalizeFileNameForComparison(options.fileName); 1781 return normalizedStored !== normalizedTarget; 1782 } else { 1783 return !(f?.FileName && f?.Path); 1784 } 1785 }); 1786 } else { 1787 uploadedFiles[fieldKey] = []; 1788 } 1789 } else { 1790 if (Array.isArray(files)) { 1791 if (!options.fileName) { 1792 // uploadedFiles[options.question.valueName] = files.filter((f) => options?.value?.findIndex((v) => v.name !== f?.file?.name) !== -1); 1793 uploadedFiles[options.question.valueName] = []; 1794 } else { 1795 uploadedFiles[options.question.valueName] = files.filter((f) => options?.fileName !== f?.file?.name); 1796 } 1797 uploadedFiles[options.question.valueName] = uploadedFiles[options.question.valueName].filter((f) => { 1798 if (options.fileName && f?.FileName) { 1799 const stripped = stripRandomPrefix(f?.FileName || ""); 1800 const normalizedStored = normalizeFileNameForComparison(stripped); 1801 const normalizedTarget = normalizeFileNameForComparison(options.fileName); 1802 return normalizedStored !== normalizedTarget; 1803 } else { 1804 return !(f?.FileName && f?.Path); 1805 } 1806 }); 1807 } else { 1808 uploadedFiles[options.question.valueName] = []; 1809 } 1810 } 1811 console.log(uploadedFiles); 1812 return options.callback("success"); 1813 }); 1814 survey.onChoicesLazyLoad.add((_, options) => { 1815 //console.log(options.question.name) 1816 if (options.question.getType() === "dropdown" && options.question.name === "cityDropdown") { 1817 const url = `/api/getlookuplist`; 1818 let params = { 1819 objectname: "Contact", 1820 fieldname: "City", 1821 skip: options.skip, 1822 searchStr: options.filter, 1823 }; 1824 sendRequest(url, params, (data, total) => { 1825 options.setItems(data, data.length); 1826 if (!window.searchItems["Contact_City"]) window.searchItems["Contact_City"] = []; 1827 data.forEach((d) => { 1828 window.searchItems["Contact_City"].push({ _id: d.value, searchname: d.text }); 1829 }); 1830 }); 1831 } 1832 if ((options.question.getType() === "dropdown" && options.question.name === "lookupfield") || (options.question.getType() === "tagbox" && options.question.name === "multilookupfield")) { 1833 const lookupFieldName = options.question.lookupFieldName; 1834 const lookupObject = options.question.lookupObject; 1835 const url = `/api/getlookuplist`; 1836 let params = { 1837 objectname: lookupObject, 1838 fieldname: lookupFieldName, 1839 skip: options.skip, 1840 searchStr: options.filter, 1841 }; 1842 if (options.filter) { 1843 if (!$(".sv-list__container").find(".sv-loader").length) $(".sv-list__container").prepend("<div class='sv-loader'></div>"); 1844 //$(".sv-list__container").prepend("<div class='sv-loader'></div>"); 1845 $(".sv-list__container").find(".sv-list").addClass("hide-sv-list");
1846 $(".sv-list__empty-container").removeClass("sv-dropdown-popup2"); 1847 sendRequest(url, params, (data, total) => { 1848 options.setItems(data, data.length); 1849 if (!window.searchItems[lookupObject + "_" + lookupFieldName]) window.searchItems[lookupObject + "_" + lookupFieldName] = []; 1850 data.forEach((d) => { 1851 window.searchItems[lookupObject + "_" + lookupFieldName].push({ _id: d.value, searchname: d.text }); 1852 }); 1853 1854 $(".sv-list__container").find(".sv-loader").remove(); 1855 $(".sv-list__container").find(".sv-list").removeClass("hide-sv-list"); 1856 1857 if (!data.length) $(".sv-list__empty-container").addClass("sv-dropdown-popup2"); 1858 else $(".sv-list__empty-container").removeClass("sv-dropdown-popup2"); 1859 }); 1860 } //else { 1861 //options.setItems([], 0); 1862 // $(".sv-list__container").find(".sv-loader").remove(); 1863 // $(".sv-list__container").find(".sv-list").removeClass("hide-sv-list"); 1864 //$(".sv-list__empty-container").removeClass("sv-dropdown-popup2"); 1865 //options.setItems([],0); 1866 //} 1867 } 1868 }); 1869 1870 survey.onGetChoiceDisplayValue.add(async (_, options) => { 1871 try { 1872 if (options.question.getType() === "dropdown" && options.question.name === "cityDropdown") { 1873 // const url = `/api/getlookuplist`; 1874 const url = `/api/getmultiplelookuplist`; 1875 let params = { 1876 objectname: "Contact", 1877 fieldname: "City", 1878 skip: options.skip || 0, 1879 searchStr: options.filter || "", 1880 recordIds: options.values, 1881 }; 1882 const data = await sendRequest2(url, params); 1883 const name = data.find((i) => i._id == options.question.value)?.searchname; 1884 const key = "Contact" + "_" + "City"; 1885 window.searchItems[key] = window.searchItems[key] || []; 1886 data.forEach((d) => { 1887 window.searchItems[key].push({ _id: d._id, searchname: d.searchname }); 1888 }); 1889 options.setItems([name]); 1890 } 1891 1892 if ((options.question.getType() === "dropdown" && options.question.name === "lookupfield") || (options.question.getType() === "tagbox" && options.question.name === "multilookupfield")) { 1893 const lookupObject = options.question.lookupObject; 1894 const lookupFieldName = options.question.lookupFieldName; 1895 const url = `/api/getmultiplelookuplist`; 1896 let params = { 1897 objectname: lookupObject, 1898 fieldname: lookupFieldName, 1899 recordIds: options.values, 1900 }; 1901 1902 // if (window.searchItems[lookupObject + "_" + lookupFieldName] && options.values && options.values.length) { 1903 // let items = window.searchItems[lookupObject + "_" + lookupFieldName]?.filter((i) => options.values.includes(i._id)).map((d) => d.searchname) || []; 1904 // //return window.searchItems. 1905 // options.setItems(items); 1906 // return; 1907 // } 1908 1909 let data = await sendRequest2(url, params); 1910 // if((options.question.getType() === "dropdown" && options.question.name === "lookupfield")){ 1911 const key = lookupObject + "_" + lookupFieldName; 1912 window.searchItems[key] = window.searchItems[key] || []; 1913 data?.forEach((d) => { 1914 window.searchItems[key].push({ _id: d._id, searchname: d.searchname }); 1915 }); 1916 // } 1917 data = data.map((d) => d.searchname); 1918 options.setItems(data); 1919 } 1920 } catch (error) { 1921 console.error("Error fetching choice display values:", error); 1922 options.setItems([]); 1923 } 1924 }); 1925 1926 const sendRequest2 = async (url, params) => { 1927 try { 1928 const response = await fetch(url, { 1929 method: "POST", 1930 headers: { 1931 "Content-Type": "application/json", 1932 }, 1933 body: JSON.stringify(params), 1934 }); 1935 1936 if (!response || response.ok != true) { 1937 return []; 1938 } 1939 const data = await response.json(); 1940 1941 return data.data; 1942 } catch (error) { 1943 console.error("Error in sendRequest:", error); 1944 return []; // Return an empty array on failure to maintain application flow 1945 } 1946 }; 1947 1948 function sendRequest(url, params, onloadSuccessCallback) { 1949 const xhr = new XMLHttpRequest(); 1950 xhr.open("POST", url); 1951 //xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); 1952 xhr.setRequestHeader("Content-Type", "application/json"); 1953 xhr.onload = () => { 1954 if (xhr.status === 200) { 1955 let data = JSON.parse(xhr.response); 1956 Survey.Serializer.findProperty("address", "cityExtraData").value = JSON.stringify(data.data); 1957 data = data.data.map((d) => { 1958 return { value: d._id, text: htmlToText(d.name), label: d.name }; 1959 //return { value: d._id, text: d.searchname, label: d.name }; 1960 }); 1961 onloadSuccessCallback(data); 1962 return; 1963 } 1964 onloadSuccessCallback([]); 1965 }; 1966 // const bodyParams = JSON.stringify(params); 1967 xhr.send(JSON.stringify(params)); 1968 } 1969 // let removeAfterPrefilled = false;
1970 // let updateArray, 1971 // fieldNames = [], 1972 // searchObj = {}, 1973 // surveyFields = [], 1974 // sectionObject = [], 1975 // sectionIdsMap = {}; 1976 1977 // if (data?.Settings?.Integration?.Optimiser?.update) { 1978 // updateArray = data.Settings.Integration.Optimiser.update; 1979 // fieldNames = updateArray.flatMap((field) => field.UniqueFields.map((j) => j.FormFieldName.replace(/^.*\((.*?)\)$/, '$1'))); 1980 // fieldNames = fieldNames.map((m) => mapping[m] || m) 1981 // } 1982 1983 // function checkUniqueFields(surveyFields, lastAddedField) { 1984 // // objects where all unique fields are present in surveyFields 1985 // const completedObjects = updateArray.filter(object => 1986 // object.UniqueFields.every(field => { 1987 // let processedName = field.FormFieldName.replace(/^.*\((.*?)\)$/, '$1').trim() // $1 extract bracket () string and replace 1988 // return surveyFields.includes(mapping[processedName] || processedName) 1989 // } ) 1990 // ); 1991 1992 // if (completedObjects.length === 0) return null; 1993 1994 // // Function to find a related object by "record _id" 1995 // const findRecordIdObject = (obj) => 1996 // updateArray.filter(object => 1997 // object.UniqueFields.some(field => 1998 // field.FormFieldName.endsWith("record _id") && 1999 // field.FormFieldName.split(" ")[0] === obj.ObjectName 2000 // ) 2001 // ); 2002 2003 // let selectedObject; 2004 2005 // if (completedObjects.length === 1) { 2006 // selectedObject = completedObjects[0]; 2007 // } else { 2008 // // If multiple objects are complete, prioritize the one completed due to lastAddedField 2009 // selectedObject = completedObjects.find(object => 2010 // object.UniqueFields.some(field => { 2011 // let processedName = field.FormFieldName.trim().replace(/^.*\((.*?)\)$/, '$1').trim() 2012 // return (mapping[processedName] || processedName) === lastAddedField 2013 // } 2014 // ) 2015 // ) || null; 2016 // } 2017 2018 // if (selectedObject) { 2019 // sectionObject = [selectedObject]; 2020 // for (const obj of sectionObject) { 2021 // const recordIdObject = findRecordIdObject(obj); 2022 // if (recordIdObject) { 2023 // sectionObject.push(...recordIdObject) 2024 // } 2025 // } 2026 // return true; 2027 // } 2028 2029 // return null; 2030 // } 2031 2032 // survey.onValueChanged.add(async (sender, options) => { 2033 // setTimeout(async () => { 2034 // const quest = options.question; 2035 // const questName = ["address", "fullname"].includes(quest.getType()) ? quest.currentElement : quest.name; 2036 // if (data.Settings.Integration.Optimiser.status === "Disable" || !updateArray) return; 2037 // if (!fieldNames.includes(questName)) return; 2038 2039 // surveyFields.push(questName); 2040 // if(quest.getType() == "phonenumber" && (options.value != quest?.itl?.getNumber()) ){ 2041 // searchObj[options.name] = await quest?.itl && quest.itl.getNumber(); 2042 // } 2043 // else searchObj[options.name] = options.value; 2044 2045 // if (!checkUniqueFields(surveyFields, questName)) return; 2046 2047 // const formData = new FormData(); 2048 // formData.append("fields", JSON.stringify(searchObj)); 2049 // formData.append("updateArray", JSON.stringify(updateArray)); 2050 // formData.append("sectionObject", JSON.stringify(sectionObject)); 2051 // formData.append("sectionIdsMap", JSON.stringify(sectionIdsMap)); 2052 2053 // const response = await $.ajax({ 2054 // url: `/api/form/checkformdataexist/${formId}`, 2055 // method: "post", 2056 // data: formData, 2057 // cache: false, 2058 // contentType: false, 2059 // processData: false, 2060 // }); 2061 // if(response.data){ 2062 // removeAfterPrefilled = true; 2063 // let mergedData = {...survey.data}; 2064 // Object.keys(response.data).forEach((f) => { 2065 // if(mergedData[f] && typeof response.data[f] == "object" && !Array.isArray(response.data[f])) { // for multi-fields like address 2066 // mergedData[f] = {...mergedData[f], ...response.data[f]} 2067 // } else { 2068 // mergedData[f] = response.data[f] 2069 // } 2070 // }) 2071 // survey.data = mergedData; 2072 // } 2073 // if(response.allFormFieldsName.length && removeAfterPrefilled) { 2074 // response.allFormFieldsName.forEach(field => { 2075 // const match = field.match(/^(.*?)\s*\((.*?)\)\s*$/); 2076 2077 // if (match) { // for multi-input question like address 2078 // const outerFieldName = match[1].trim(); 2079 // const innerFieldName = match[2].trim(); 2080 2081 // const innerQuestion = survey.getQuestionByName(outerFieldName) 2082 // ?.contentPanel 2083 // ?.getQuestionByName(innerFieldName); 2084 2085 // if (innerQuestion && innerQuestion.value) innerQuestion.value = null; 2086 2087 // } else { 2088 // const question = survey.getQuestionByName(field); 2089 // if (question && question.value) question.value = null; 2090 // } 2091 // }); 2092 // } 2093 // }, 100) 2094 // }); 2095
2096 survey.getAllQuestions().forEach((quest) => { 2097 if (quest.getType() == "datepicker") { 2098 if (quest.currentDate && !quest.visible) { 2099 setTimeout(() => { 2100 const currentDate = moment(new Date(), "YYYY/MM/DD").format(quest.dateFormat || "DD/MM/YYYY"); 2101 quest.value = currentDate; 2102 }, 1000); 2103 } 2104 } 2105 if (quest.getType() == "file") { 2106 const maximumfilesize = quest.jsonObj.maximumfilesize; 2107 Survey.Serializer.addProperty("file", { 2108 visibleIndex: 16, 2109 name: "maximumfilesize", 2110 type: "number", 2111 category: "general", 2112 displayName: "Maximum file size (in bytes)", 2113 default: 0, 2114 }); 2115 let maximumSize = maximumfilesize ? maximumfilesize : quest.maxSize; 2116 // surveyElement.setPropertyValue("maximumfilesize", maximumSize); 2117 quest.maximumfilesize = maximumSize; 2118 2119 Survey.Serializer.findProperty("file", "maxSize").visible = false; 2120 quest.setPropertyValue("maxSize", 0); 2121 } 2122 }); 2123 2124 survey.onAfterRenderQuestion.add((sender, options) => { 2125 if (options.question.getType() == "text") { 2126 let input = options.htmlElement.querySelector("input"); 2127 input.disabled = options.question.readOnly ? true : false; 2128 if (input.type === "date" && options.question.value && recordTokenId) { 2129 const formatted = moment(options.question.value).format("YYYY-MM-DD"); 2130 console.log("Formatted date:", formatted); 2131 options.question.value = formatted; // update SurveyJS value 2132 input.value = formatted; // sync UI 2133 } 2134 2135 if (input.type === "datetime-local" && options.question.value && recordTokenId) { 2136 const formatted = moment(options.question.value).format("YYYY-MM-DDTHH:mm"); 2137 console.log("Formatted datetime:", formatted); 2138 options.question.value = formatted; // update SurveyJS value 2139 input.value = formatted; // sync UI 2140 } 2141 } 2142 const draft = localStorage.getItem(getAutoSaveKey(formId)); 2143 if (options.question.getType() === "phonenumber" && options.question.value && (draftToken || draft !== null)) { 2144 setTimeout(() => { 2145 const input = options.htmlElement.querySelector("input"); 2146 if (input && window.intlTelInput) { 2147 options.question.itl = window.intlTelInput(input, { 2148 initialCountry: "auto", 2149 geoIpLookup: function (success, failure) { 2150 success("in"); // or dynamic detection 2151 }, 2152 utilsScript: "https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.8/js/utils.js", 2153 }); 2154 // â IMPORTANT: sync saved number (sets flag correctly) 2155 if (options.question.value) { 2156 options.question.itl.setNumber(options.question.value); 2157 } 2158 } 2159 }, 100); 2160 } 2161 if (options.question.getType() === "datepicker") { 2162 if (recordTokenId) { 2163 setTimeout(() => { 2164 if (options.question.value) { 2165 const value = options.question.value; 2166 // Detect if it's an ISO string (contains 'T' and '-') 2167 const isISODate = typeof value === "string" && value.includes("T") && value.includes("-"); 2168 2169 let parsedDate; 2170 2171 if (isISODate) { 2172 parsedDate = moment(value, moment.ISO_8601, true); 2173 } else { 2174 parsedDate = moment(value, ["DD/MM/YYYY", "MM/DD/YYYY"], true); 2175 } 2176 2177 // If still invalid, try last fallback 2178 if (!parsedDate.isValid()) { 2179 parsedDate = moment(value); // loose parse as last resort 2180 } 2181 2182 const formattedDate = parsedDate.isValid() ? parsedDate.format(options.question.dateFormat || "DD/MM/YYYY") : value; // fallback to original if invalid 2183 2184 options.question.datepicker.setDate(formattedDate); 2185 } 2186 }, 500); 2187 } 2188 if (options.question.value && (draftToken || draft != null)) { 2189 setTimeout(() => { 2190 options.question.datepicker.setDate(options.question.value); 2191 }, 1000); 2192 } else if (options.question.currentDate) { 2193 setTimeout(() => { 2194 const currentDate = moment(new Date(), "YYYY/MM/DD").format(options.question.dateFormat || "DD/MM/YYYY"); 2195 options.question.datepicker.setDate(currentDate); 2196 }, 1000); 2197 } 2198 } 2199 if (options.question.getType() === "address" && recordTokenId && options.question.value) { 2200 const countryValue = options.question.value.country; // e.g. "India" 2201 const contentPanel = options.question.contentPanel; 2202 const countryQuestion = contentPanel?.getQuestionByName("country"); 2203 if (countryQuestion && countryValue) { 2204 const waitForChoices = () => { 2205 console.log("one"); 2206 if (countryQuestion.choicesFromUrl && countryQuestion.choicesFromUrl.length > 0) { 2207 const matched = countryQuestion.choicesFromUrl.find((choice) => choice.text?.toLowerCase() === countryValue?.toLowerCase() || choice.value?.toLowerCase() === countryValue?.toLowerCase()); 2208 console.log(matched, "matched"); 2209 if (matched) { 2210 countryQuestion.value = matched.value; 2211 } 2212 } else { 2213 setTimeout(waitForChoices, 100); 2214 } 2215 }; 2216 waitForChoices(); 2217 } 2218 } 2219 if (options.question.getType() === "file" && recordTokenId) { 2220 options.question.readOnly = true; 2221 2222 // Make the file input completely non-clickable 2223 const fileWrapper = options.htmlElement.querySelector(".sd-file"); 2224 if (fileWrapper) { 2225 fileWrapper.style.pointerEvents = "none"; 2226 fileWrapper.style.opacity = "0.6"; 2227 } 2228 } 2229 2230 2231 switch (options.question.name) { 2232 case "cityDropdown": 2233 if (data.Settings && data.Settings.Integration && data.Settings.Integration.Optimiser) { 2234 options.question.visible = true; 2235 } else { 2236 options.question.visible = false;
2237 } 2238 break; 2239 case "city": 2240 if (data.Settings && data.Settings.Integration && data.Settings.Integration.Optimiser) { 2241 options.question.visible = false; 2242 } else { 2243 options.question.visible = true; 2244 } 2245 break; 2246 case "state": 2247 case "country": 2248 if (data.Settings && data.Settings.Integration && data.Settings.Integration.Optimiser) { 2249 options.question.readOnly = true; 2250 } else { 2251 options.question.readOnly = false; 2252 } 2253 break; 2254 default: 2255 break; 2256 } 2257 }); 2258 2259 if (recordTokenId) { 2260 survey.completeText = "Save"; // Change the complete button text 2261 } 2262 2263 $(`[data-form-id="${data.FormID}"]`).Survey({ model: survey }); 2264 2265 const $deviceToolbar = $(".device-toolbar"); 2266 const $deviceSimulator = $(".device-simulator"); 2267 const $previewWrapper = $(".preview-wrapper"); 2268 const $formResults = $("#form-results"); 2269 const $formName = $("toolbar-form-name"); 2270 const $formResultsHeaderButtons = $formResults.find(".header button"); 2271 const $resTable = $formResults.find(".content #res_table"); 2272 const $resJson = $formResults.find(".content #res_json"); 2273 2274 const dims = { 2275 iPhoneSE: { height: 375, width: 667, parentH: 407, parentW: 795 }, 2276 iPhone15: { height: 393, width: 852, parentH: 425, parentW: 980 }, 2277 iPhone15Plus: { height: 430, width: 932, parentH: 462, parentW: 1060 }, 2278 iPadMini: { height: 614.4, width: 819.2, parentH: 646.4, parentW: 947.2 }, 2279 iPadAir: { height: 820, width: 1180, parentH: 852, parentW: 1308 }, 2280 AndroidPhone: { height: 360, width: 682.667, parentH: 392, parentW: 810.667 }, 2281 AndroidTablet: { height: 553.333, width: 853.333, parentH: 594.333, parentW: 1010.333 }, 2282 MSSurface: { height: 768, width: 1366, parentH: 800, parentW: 1494 }, 2283 desktop: null, // special case 2284 }; 2285 2286 let currentDevice = "desktop"; 2287 let currentOrientation = "landscape"; 2288 2289 function applyDimension(device, orientation) { 2290 // Reset any inline sizing first 2291 $deviceSimulator.add($deviceSimulator.find("> div")).css({ width: "", height: "" }); 2292 2293 if (device === "desktop") { 2294 $deviceSimulator.removeClass("simulator"); 2295 $previewWrapper.removeClass("simulator"); 2296 $("#btn-toggle-orientation").prop("disabled", true); 2297 return; 2298 } 2299 // non-desktop 2300 $deviceSimulator.addClass("simulator"); 2301 $previewWrapper.addClass("simulator"); 2302 $("#btn-toggle-orientation").prop("disabled", false); 2303 2304 const cfg = dims[device]; 2305 if (!cfg) return; 2306 2307 let w = cfg.width, 2308 h = cfg.height; 2309 let parentW = cfg.parentW, 2310 parentH = cfg.parentH; 2311 if (orientation === "portrait") { 2312 [w, h] = [h, w]; 2313 [parentW, parentH] = [parentH, parentW]; 2314 } 2315 $deviceSimulator.find("> div").css({ width: `${w}px`, height: `${h}px` }); 2316 $deviceSimulator.css({ width: `${parentW}px`, height: `${parentH}px` }); 2317 } 2318 2319 if (typeof isFormPreview !== "undefined" && isFormPreview) { 2320 $deviceToolbar.addClass("simulator"); 2321 $deviceSimulator.addClass("simulator"); 2322 $deviceToolbar.find(".toolbar-form-name").text(data.Name); 2323 applyDimension(currentDevice, currentOrientation); 2324 } 2325 2326 // Device selection handler 2327 $deviceToolbar.on("click", ".select-device", function () { 2328 $deviceToolbar.find(".select-device").removeClass("active"); 2329 $(this).addClass("active"); 2330 currentDevice = $(this).data("device"); 2331 2332 let dropdownIconBtn = $deviceToolbar.find("#dropdownIconBtn > img"); 2333 if (currentDevice == "desktop") { 2334 dropdownIconBtn.attr("src", `../public/images/desktop.svg`); 2335 } else if (["iPhoneSE", "iPhone15", "iPhone15Plus", "AndroidPhone"].includes(currentDevice)) { 2336 dropdownIconBtn.attr("src", `../public/images/phone.svg`); 2337 } else { 2338 dropdownIconBtn.attr("src", `../public/images/tablet.svg`); 2339 } 2340 applyDimension(currentDevice, currentOrientation); 2341 }); 2342 2343 // Orientation toggle 2344 $deviceToolbar.on("click", "#btn-toggle-orientation", function () { 2345 currentOrientation = currentOrientation === "landscape" ? "portrait" : "landscape"; 2346 applyDimension(currentDevice, currentOrientation); 2347 }); 2348 2349 // Show invisible elements toggle 2350 $deviceToolbar.on("click", "#show-invisible-elements", function () { 2351 survey.showInvisibleElements = !survey.showInvisibleElements; 2352 $(this).toggleClass("active"); 2353 }); 2354 2355 // Close button 2356 $deviceToolbar.on("click", "#close-btn", function () { 2357 window.close(); 2358 }); 2359 2360 $previewWrapper.on("click", ".preview-form-again button", function () { 2361 $(".preview-wrapper .sd-body").show(); 2362 $(".preview-form-again").hide(); 2363 $deviceToolbar.find("#show-invisible-elements").show(); 2364 $formResults.hide(); 2365 }); 2366 2367 function generateFormResults(dataObj) { 2368 $(".preview-wrapper .sd-body").hide(); 2369 $(".preview-wrapper .sv-components-row").prepend(` 2370 <div class="preview-form-again" style="padding: 96px; width: 100%; display: none"> 2371 <div class="d-flex flex-column justify-content-center align-items-center"> 2372 <p>Thank you for completing the form</p> 2373 <button class="btn font-weight-bold bg-light py-3 px-5">Preview Form Again</button> 2374 </div>
2375 </div>`); 2376 2377 // Build form results rows safely 2378 const rows = Object.entries(dataObj) 2379 .map(([key, value]) => { 2380 const question = survey.getQuestionByValueName(key); 2381 const title = question ? question.title || question.name : key; 2382 return ` 2383 <tr> 2384 <td style="padding-left: 44px; font-weight: 600">${title}</td> 2385 <td>${typeof value == "object" ? JSON.stringify(value) : value}</td> 2386 </tr>`; 2387 }) 2388 .join(""); 2389 2390 const tableHtml = ` 2391 <table class="table"> 2392 <thead style="background: #f3f3f3; font-size: 12px"> 2393 <tr> 2394 <td class="px-4">Question title</td> 2395 <td>Display Value</td> 2396 </tr> 2397 </thead> 2398 <tbody style="font-size: 13px"> 2399 ${rows} 2400 </tbody> 2401 </table>`; 2402 2403 $(`.preview-form-again`).show(); 2404 $formResults.show(); 2405 $resTable.html(tableHtml); 2406 $resJson.html(`<pre>${JSON.stringify(dataObj, null, 2)}</pre>`).hide(); 2407 2408 $formResultsHeaderButtons.removeClass("selected-btn"); 2409 $formResultsHeaderButtons.filter("#table").addClass("selected-btn"); 2410 $deviceToolbar.find("#show-invisible-elements").hide(); 2411 $("html, body").animate({ scrollTop: 0 }, "fast"); 2412 } 2413 2414 $formResultsHeaderButtons.click(function () { 2415 $formResultsHeaderButtons.removeClass("selected-btn"); 2416 $(this).addClass("selected-btn"); 2417 if (this.id === "table") { 2418 $resJson.hide(); 2419 $resTable.show(); 2420 } else { 2421 $resTable.hide(); 2422 $resJson.show(); 2423 } 2424 }); 2425 if (data.IsResponseLimitFilled) { 2426 $("form .sd-body").hide(); // hide form body 2427 $("form .sd-container-modern").css({ display: "flex", "flex-direction": "column", height: "100vh" }); 2428 $("form .sv-components-row").css({ flex: 1 }); 2429 $("form .sv-components-row").prepend(` 2430 <div class="limit_response" style="padding: 96px; width: 100%"> 2431 <div class="d-flex flex-column justify-content-center align-items-center"> 2432 <h5 class="sd-title">${data.ResponseLimitMessage}</h5> 2433 </div> 2434 </div>`); 2435 } 2436 }); 2437});
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.