1$(document).ready(function () { 2 $.fn.serializeFormJSON = function () { 3 var o = {}; 4 var a = this.serializeArray(); 5 $.each(a, function () { 6 if (o[this.name]) { 7 if (!o[this.name].push) { 8 o[this.name] = [o[this.name]]; 9 } 10 o[this.name].push(this.value || ""); 11 } else { 12 o[this.name] = this.value || ""; 13 } 14 }); 15 return o; 16 }; 17 18 $.fn.showBs = function () { 19 $(this).removeClass("d-none"); 20 }; 21 22 $.fn.hideBs = function () { 23 $(this).addClass("d-none"); 24 }; 25 26 $.fn.resetForm = function () { 27 $(this)[0].reset(); 28 }; 29 30 $.fn.updateProgress = function (progress) { 31 if (progress <= 100) { 32 $(this).attr("aria-valuenow", progress); 33 $(this) 34 .find(".progress-bar") 35 .css("width", progress + "%"); 36 } 37 }; 38}); 39 40//d-none 41 42class Utils { 43 constructor() {} 44 45 static modalShow(selector) { 46 const el = document.querySelector(selector); 47 const modal = bootstrap.Modal.getOrCreateInstance(el); 48 modal.show(); 49 } 50 51 static modalHide(selector) { 52 const el = document.querySelector(selector); 53 const modal = bootstrap.Modal.getInstance(el); 54 if (modal) { 55 if (el.contains(document.activeElement)) { 56 document.activeElement.blur(); // remove focus from inside the modal 57 } 58 modal.hide(); 59 } 60 } 61 62 static joinStringsWith(glue, items) { 63 const filtered = items.filter(function (item) { 64 return item !== undefined && item !== null && item.trim() !== ""; 65 }); 66 if (filtered.length === 0) { 67 return "N/A"; 68 } 69 return filtered.join(glue); 70 } 71 72 static joinStringsWithAndReturnNull(glue, items) { 73 const filtered = items.filter(function (item) { 74 return item !== undefined && item !== null && item.trim() !== ""; 75 }); 76 if (filtered.length === 0) { 77 return null; 78 } 79 return filtered.join(glue); 80 } 81 82 static strOrNA(item) { 83 if (item !== undefined && item !== null && item.trim() !== "") { 84 return item.trim(); 85 } 86 return "N/A"; 87 } 88 89 static isNullOrEmpty(item) { 90 if (item === undefined || item === null || item.trim() === "") { 91 return true; 92 } 93 return false; 94 } 95 96 static getLeadingZeros(str) { 97 str = str + ""; 98 if (str.length >= 5) { 99 return str; 100 } 101 return "0".repeat(5 - str.length) + str; 102 } 103 104 105 static formatPhone(phone, isHtml) { 106 if (phone === undefined || phone === null || phone.trim() === "") { 107 return "N/A"; 108 } 109 110 if (isHtml) { 111 let formattedPhone = Utils.escapeHtml(phone).replace(/[^0-9+]/g, ""); 112 //add dashes to the phone number for better readability 113 if (formattedPhone.length === 10) { 114 formattedPhone = 115 formattedPhone.substring(0, 3) + 116 "-" + 117 formattedPhone.substring(3, 6) + 118 "-" + 119 formattedPhone.substring(6); 120 } else if (formattedPhone.length === 11 && formattedPhone.startsWith("1")) { 121 formattedPhone = 122 formattedPhone.substring(0, 1) + 123 "-" + 124 formattedPhone.substring(1, 4) + 125 "-" + 126 formattedPhone.substring(4, 7) + 127 "-" + 128 formattedPhone.substring(7); 129 } 130 131 return "<a href=\"tel:" + phone.replace(/[^0-9+]/g, "") + "\" class=\"text-nowrap\">" + formattedPhone + "</a>"; 132 } 133 134 return Utils.escapeHtml(phone); 135 } 136 137 static makeVisible(selector) { 138 $(selector).removeClass("d-none"); 139 //$(selector).addClass("d-block"); 140 } 141 142 static makeInvisible(selector) { 143 //$(selector).removeClass("d-block"); 144 $(selector).addClass("d-none"); 145 } 146 147 static setupFiles(path, subfolder, selector = ".async-file") { 148 //let selector = ".async-file"; 149 $(selector).filepond({ 150 allowMultiple: false, 151 allowImagePreview: false, 152 maxFileSize: "30MB", 153 credits: "", 154 maxFiles: 1, 155 allowDrop: false, 156 required: true, 157 labelIdle: 158 " <span class='filepond--label-action''> Browse your files</span>", 159 server: { 160 process: function ( 161 fieldName, 162 file, 163 metadata, 164 load, 165 error, 166 progress, 167 abort, 168 transfer, 169 options, 170 ) { 171 const request = new XMLHttpRequest(); 172 $.ajax({ 173 type: "POST", 174 url: path, 175 data: { name: file.name, folder: subfolder }, 176 success: function (ajaxData, textStatus, jqXHR) { 177 if (textStatus === "success") { 178 if (ajaxData.status === "success") { 179 console.log(ajaxData); 180 // fieldName is the name of the input field 181 // file is the actual file object to send 182 const formData = new FormData(); 183 184 request.open( 185 ajaxData.attributes.method, 186 ajaxData.attributes.action, 187 ); 188 189 for (const property in ajaxData.inputs) { 190 formData.append(property, ajaxData.inputs[property]); 191 } 192 193 formData.append("file", file, file.name); 194 195 // Should call the progress method to update the progress to 100% before calling load 196 // Setting computable to false switches the loading indicator to infinite mode 197 request.upload.onprogress = (e) => { 198 progress(e.lengthComputable, e.loaded, e.total); 199 //console.log(e); 200 }; 201 202 // Should call the load method when done and pass the returned server file id 203 // this server file id is then used later on when reverting or restoring a file 204 // so your server knows which file to return without exposing that info to the client 205 request.onload = function () { 206 if (request.status >= 200 && request.status < 300) { 207 //load(request.responseText); 208 load(ajaxData.inputs.key); 209 //console.log(this); 210 } else { 211 // Can call the error method if something is wrong, should exit after 212 error("oh no"); 213 } 214 }; 215 request.send(formData); 216 } else { 217 } 218 } 219 }, 220 dataType: "json", 221 }); 222 223 return { 224 abort: function () { 225 request.abort(); 226 abort(); 227 }, 228 }; 229 }, 230 remove: (source, load, error) => { 231 load(); 232 }, 233 revert: (uniqueFileId, load, error) => { 234 //console.log("revert", uniqueFileId); 235 //console.log(this); 236 load(); 237 }, 238 }, 239 }); 240 $(selector).on("FilePond:removefile", function () { 241 //console.log("FilePond:removefile"); 242 if ($(this).next(".d-none").length > 0) { 243 $(this).next(".d-none").val(""); 244 } 245 }); 246 $(selector).on("FilePond:processfile", function () { 247 //console.log("FilePond:processfile"); 248 if ($(this).find("input[type='hidden']").length > 0) { 249 let value = $(this).find("input[type='hidden']").val(); 250 $(this).next(".d-none").val(value); 251 $(this).next(".d-none").parsley().reset(); 252 } 253 }); 254 $(selector).on("FilePond:updatefiles", function () { 255 //console.log("FilePond:updatefiles"); 256 if ($(this).find("input[type='hidden']").length > 0) { 257 let value = $(this).find("input[type='hidden']").val(); 258 $(this).next(".d-none").val(value); 259 $(this).next(".d-none").parsley().reset(); 260 } 261 }); 262 } 263 264 static setupFilesForTempWithCallbacks( 265 path, 266 selector = ".async-file", 267 callback = () => {}, 268 ) { 269 $(selector).filepond({ 270 allowMultiple: false, 271 allowImagePreview: false, 272 maxFileSize: "30MB", 273 credits: "", 274 maxFiles: 1, 275 allowDrop: false, 276 required: true, 277 labelIdle: 278 " <span class='filepond--label-action''> Browse your files</span>", 279 server: { 280 process: function ( 281 fieldName, 282 file, 283 metadata, 284 load, 285 error, 286 progress, 287 abort, 288 transfer, 289 options, 290 ) { 291 const request = new XMLHttpRequest(); 292 request.open("POST", path); 293 const formData = new FormData(); 294 295 formData.append("files[]", file, file.name); 296 297 // Should call the progress method to update the progress to 100% before calling load 298 // Setting computable to false switches the loading indicator to infinite mode 299 request.upload.onprogress = (e) => { 300 progress(e.lengthComputable, e.loaded, e.total); 301 //console.log(e); 302 }; 303 304 // Should call the load method when done and pass the returned server file id 305 // this server file id is then used later on when reverting or restoring a file 306 // so your server knows which file to return without exposing that info to the client 307 request.onload = function () { 308 if (request.status >= 200 && request.status < 300) { 309 const retData = JSON.parse(this.responseText); 310 console.log(retData); 311 load(retData.id); 312 313 if (typeof callback === "function") { 314 callback(retData.id); 315 } 316 } else { 317 // Can call the error method if something is wrong, should exit after 318 error("oh no"); 319 } 320 }; 321 request.send(formData); 322 return { 323 abort: function () { 324 request.abort(); 325 abort(); 326 }, 327 }; 328 }, 329 remove: (source, load, error) => { 330 load(); 331 }, 332 revert: (uniqueFileId, load, error) => { 333 //console.log("revert", uniqueFileId); 334 //console.log(this); 335 load(); 336 }, 337 }, 338 }); 339 $(selector).on("FilePond:removefile", function () { 340 //console.log("FilePond:removefile"); 341 if ($(this).next(".d-none").length > 0) { 342 $(this).next(".d-none").val(""); 343 } 344 }); 345 $(selector).on("FilePond:processfile", function () { 346 //console.log("FilePond:processfile"); 347 if ($(this).find("input[type='hidden']").length > 0) { 348 let value = $(this).find("input[type='hidden']").val(); 349 $(this).next(".d-none").val(value); 350 $(this).next(".d-none").parsley().reset(); 351 } 352 }); 353 $(selector).on("FilePond:updatefiles", function () { 354 //console.log("FilePond:updatefiles"); 355 if ($(this).find("input[type='hidden']").length > 0) { 356 let value = $(this).find("input[type='hidden']").val(); 357 $(this).next(".d-none").val(value); 358 $(this).next(".d-none").parsley().reset(); 359 } 360 }); 361 } 362 363 static resetFilesByClass(selector) { 364 $("." + selector).each(function () { 365 const pond = FilePond.find($(this)[0]); 366 pond.removeFiles(); 367 }); 368 } 369 370 static resetFileById(selector) { 371 const pond = FilePond.find($("#" + selector)[0]); 372 pond.removeFiles(); 373 } 374 375 //this is the id of the FilePond element 376 static getFileInfoById(fileElementId) { 377 const selector = "#" + fileElementId; 378 const filePondFile = FilePond.find($(selector)[0]).getFile(); 379 if (filePondFile) { 380 const fileInfo = { 381 fileExtension: filePondFile.fileExtension, 382 fileSize: filePondFile.fileSize, 383 fileType: filePondFile.fileType, 384 filename: filePondFile.filename, 385 filenameWithoutExtension: filePondFile.filenameWithoutExtension, 386 id: filePondFile.id, 387 origin: filePondFile.origin, 388 serverId: filePondFile.serverId, 389 }; 390 return fileInfo; 391 } 392 return null; 393 } 394 395 //this is the id of the FilePond element 396 static initFileWithData(fileElementId, fileInfo) { 397 const selector = "#" + fileElementId; 398 if (fileInfo) { 399 FilePond.find($(selector)[0]).removeFile(); 400 FilePond.find($(selector)[0]) 401 .addFile(fileInfo.serverId, { 402 type: "local", 403 file: { 404 name: fileInfo.filename, 405 size: fileInfo.fileSize, 406 type: fileInfo.fileType, 407 }, 408 }) 409 .then(function (file) { 410 $(selector).next("input.d-none").val(fileInfo.serverId); 411 }) 412 .catch(function (error) {}); 413 } 414 } 415 416 static parseDataToCSV(dataArray, filename) { 417 const csv = Papa.unparse(dataArray); 418 const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" }); 419 const link = document.createElement("a"); 420 if (link.download !== undefined) { 421 // feature detection 422 const url = URL.createObjectURL(blob); 423 link.setAttribute("href", url); 424 link.setAttribute("download", filename); 425 link.style.visibility = "hidden"; 426 document.body.appendChild(link); 427 link.click(); 428 document.body.removeChild(link); 429 } 430 } 431 432 static escapeHtml(s) { 433 return String(s ?? "") 434 .replace(/&/g, "&") 435 .replace(/</g, "<") 436 .replace(/>/g, ">") 437 .replace(/"/g, """) 438 .replace(/'/g, "'"); 439 } 440 441 static unEscapeHtml(s) { 442 return String(s ?? "") 443 .replace(/ /g, ' ').replace(/\u00A0/g, ' '); 444 } 445 446 447 static initPopovers(root) { 448 // Dispose existing (prevents duplicates) 449 root.querySelectorAll('[data-bs-toggle="popover"]').forEach((el) => { 450 const inst = bootstrap.Popover.getInstance(el); 451 if (inst) inst.dispose(); 452 }); 453 454 // Create new 455 root.querySelectorAll('[data-bs-toggle="popover"]').forEach((el) => { 456 new bootstrap.Popover(el, { 457 container: "body", 458 trigger: "hover focus", // or "click" 459 html: false, 460 }); 461 }); 462 } 463} 464 465class Wizard { 466 constructor(rootId, linkClass, nextBtnId, prevBtnId) { 467 this.rootId = rootId; 468 this.linkClass = linkClass; 469 this.numTabs = $("#" + this.rootId).find(".nav-item").length; 470 this.nextBtnId = nextBtnId; 471 this.prevBtnId = prevBtnId; 472 //Make the first tab active 473 $("." + this.linkClass).each(function (index) { 474 if (index === 0) { 475 $(this).addClass("active"); 476 $(this).removeClass("locked"); 477 } else { 478 $(this).removeClass("active"); 479 $(this).addClass("locked"); 480 } 481 }); 482 483 //Needed to prevent clicking on locked tabs 484 $("." + this.linkClass).on("show.bs.tab", function (event) { 485 if ($(this).hasClass("locked")) { 486 event.preventDefault(); 487 } 488 }); 489 } 490 491 getCurrentTabIndex() {
492 let currentTab = $("#" + this.rootId).find(".nav-link.active"); 493 let currentIndex = currentTab.parent().index(); 494 return currentIndex; 495 } 496 497 gotoNext() { 498 let currentTab = $("#" + this.rootId).find(".nav-link.active"); 499 let nextParent = currentTab.parent().next(); 500 if (nextParent.length > 0) { 501 let nextTab = nextParent.find(".nav-link").get(0); 502 $(nextTab).removeClass("locked"); // unlock next 503 $(currentTab).addClass("locked"); // lock current 504 new bootstrap.Tab(nextTab).show(); // move forward 505 if (this.prevBtnId) { 506 $("#" + this.prevBtnId).showBs(); 507 } 508 } 509 } 510 511 gotoPrev() { 512 let currentTab = $("#" + this.rootId).find(".nav-link.active"); 513 let prevParent = currentTab.parent().prev(); 514 let currentIndex = currentTab.parent().index(); 515 if (prevParent.length > 0) { 516 let prevTab = prevParent.find(".nav-link").get(0); 517 $(prevTab).removeClass("locked"); // unlock previous 518 $(currentTab).addClass("locked"); // lock current 519 new bootstrap.Tab(prevTab).show(); // move backward 520 if (currentIndex === 1) { 521 if (this.prevBtnId) { 522 $("#" + this.prevBtnId).hideBs(); 523 } 524 } 525 } 526 } 527}
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.