https://commercial.budgettruck.com/DesktopModules/BudgetTruck.ContactUs/Scripts/ViewModel.js?cdv=207
1function ContactUsViewModel(sf, userName) { 2 var self = this; 3 // Submit Button state observables 4 self.submittedOnce = ko.observable(false); 5 self.isHighlighted = ko.observable(false); 6 // Observables 7 self.accountNo = ko.observable(""); 8 self.accountName = ko.observable(""); 9 self.name = ko.observable(""); 10 //self.isUserPresent = ko.observable(""); 11 self.email = ko.observable(""); 12 //self.phone = ko.observable(""); 13 self.accountLoading = ko.observable(false); 14 self.accountError = ko.observable(''); 15 16 self.phone = ko.observable("").extend({ phoneMask: true }); 17 self.contactMethod = ko.observable("Phone"); 18 self.subjects = ko.observableArray([ 19 "Select", 20 "BTR Commercial Reservations (888-NeedTrk)", 21 "BTR Customer Service (for rentals paid with cash/check/debit/credit card)", 22 "BTR Customer Service (for questions about a Direct Bill invoice)", 23 "I need help with my Certificate of Insurance (COI)", 24 "I have a question for my account manager", 25 "I need to update my account information" 26 ]); 27 self.selectedSubject = ko.observable(); 28 self.message = ko.observable(""); 29 30 // Dirty tracking 31 self.nameTouched = ko.observable(false); 32 self.emailTouched = ko.observable(false); 33 self.phoneTouched = ko.observable(false); 34 self.subjectTouched = ko.observable(false); 35 self.messageTouched = ko.observable(false); 36 37 // Error messages 38 self.nameError = ko.observable(""); 39 self.emailError = ko.observable(""); 40 self.phoneError = ko.observable(""); 41 self.subjectError = ko.observable(""); 42 self.messageError = ko.observable(""); 43 44 // File management 45 self.attachedFileNames = ko.observable(""); 46 self.fileError = ko.observable(""); 47 self.fileData = ko.observableArray([]); 48 49 // Confirmation message 50 self.visible = ko.observable(false); 51 self.successText = ko.observable(""); 52 self.isVisible = ko.observable(true); 53 54 self.showSubjectContainer = ko.observable(false); // hide by default 55 //self.showSubjectContainer(true); 56 57 /// function to get Username /// 58 59 60 self.UserName = ko.observable(userName || ''); 61 62 self.items = ko.observable(); // if the API returns more info, adapt type 63 self.error = ko.observable(null); 64 65 // computed boolean to show/hide div 66 self.isUserPresent = ko.pureComputed(function () { 67 var u = self.UserName(); 68 if (!u) return false; 69 u = String(u).trim(); 70 if (!u) return false; 71 var lower = u.toLowerCase(); 72 if (lower === 'null' || lower === 'undefined') return false; 73 return true; 74 }); 75 76 // computed boolean to show/hide div 77 self.isSubjectVisible = ko.pureComputed(function () { 78 var u = self.UserName(); 79 if (!u) return false; 80 u = String(u).trim(); 81 if (!u) return false; 82 var lower = u.toLowerCase(); 83 if (lower === 'null' || lower === 'undefined') return false; 84 return true; 85 }); 86 87 self.toggleFooterByUsername = function () { 88 var footer = document.getElementById('dnn_FooterRowLeft'); 89 if (!footer) return; 90 91 if (self.isUserPresent()) { 92 footer.style.display = ''; 93 } else { 94 footer.style.display = 'none'; 95 } 96 }; 97 98 //console.log('ContactUsViewModel constructed. UserName:', self.UserName()); 99 //console.log('ContactUsViewModel constructed. isUserPresent:', self.isUserPresent()); 100 //console.log('ContactUsViewModel constructed. isSubjectVisible:', self.isSubjectVisible()); 101 ////////////////// 102 103 104 // Optional info per subject 105 const optionalInfoMap = { 106 "Select": { Hours: "", Phone: "", Email: "" }, 107 "BTR Commercial Reservations (888-NeedTrk)": { Hours: "M-F: 6a-8p Sa: 7a-6p Su: 8a-5p CST", Phone: "(888) 633-3875 x1, x1", Email: "[email protected]" }, 108 "BTR Customer Service (for rentals paid with cash/check/debit/credit card)": { Hours: "M-Sa: 7:30a-7p Su: 8a-5p CST", Phone: "(800) 283-4382 x1, x5", Email: "[email protected]" }, 109 "BTR Customer Service (for questions about a Direct Bill invoice)": { Hours: "M-F: 7a-4p CST", Phone: "(800) 840-4998", Email: "[email protected]" }, 110 "I need help with my Certificate of Insurance (COI)": { Hours: "M-F: 7:30a-5p CST", Phone: "(800) 971-1205", Email: "[email protected]" }, 111 "I have a question for my account manager": { Hours: "", Phone: "", Email: "" }, 112 "I need to update my account information": { Hours: "", Phone: "", Email: "" } 113 }; 114 115 self.optionalInfo = ko.computed(() => optionalInfoMap[self.selectedSubject()] || {}); 116 self.showOptionalSection = ko.computed(() => { 117 const info = self.optionalInfo(); 118 return !!(info.Hours || info.Phone || info.Email); 119 });
120 121 // Dirty validation subscribers 122 self.name.subscribe(val => { 123 if (self.nameTouched()) self.validate(); 124 }); 125 126 self.email.subscribe(val => { 127 if (self.emailTouched()) self.validate(); 128 }); 129 130 self.phone.subscribe(val => { 131 if (self.phoneTouched()) self.validate(); 132 }); 133 134 self.selectedSubject.subscribe(val => { 135 if (self.subjectTouched()) self.validate(); 136 }); 137 138 self.message.subscribe(val => { 139 if (self.messageTouched()) self.validate(); 140 }); 141 142 self.stripHtml = function (input) { 143 const doc = new DOMParser().parseFromString(input, 'text/html'); 144 return doc.body.textContent || ""; 145 }; 146 147 // Email validation 148 self.isValidEmail = email => { 149 const re = /^(?=.{5,128}$)(?![.])(?!.*\.\.)(?![.-])[a-zA-Z0-9._%+-]{1,64}@(?=.{3,63}$)(?!.*--)(?!-)[a-zA-Z0-9-]{1,63}(?:\.[a-zA-Z0-9]*[a-zA-Z][a-zA-Z0-9]*)+$/; 150 return re.test(email); 151 }; 152 153 self.isValidAlphaNumInput = inputText => { 154 const re = /^[a-zA-Z\s.'-]+$/; 155 return re.test(inputText); 156 }; 157 158 self.allowOnlyDigits = function (data, event) { 159 const key = event.which || event.keyCode; 160 161 // Allow: Backspace (8), Tab (9), Delete (46), Arrow keys (37-40) 162 if ( 163 key === 8 || key === 9 || key === 46 || 164 (key >= 37 && key <= 40) 165 ) { 166 return true; 167 } 168 169 // Disallow any key pressed with Shift 170 if (event.shiftKey) { 171 event.preventDefault(); 172 return false; 173 } 174 175 // Allow: Digits 0-9 (top row: 48â57, numpad: 96â105) 176 if ((key >= 48 && key <= 57) || (key >= 96 && key <= 105)) { 177 return true; 178 } 179 180 // For everything else: block input 181 event.preventDefault(); 182 return false; 183 }; 184 185 //// Ftech Account No ////// 186 // Call this to get account number and then details if needed 187 self.fetchAccountInfo = function (username) { 188 var u = (typeof username === 'string') ? username : (self.UserName && typeof self.UserName === 'function' ? self.UserName() : ''); 189 if (!u || String(u).trim() === '') { 190 console.warn("fetchAccountInfo: empty username, skipping."); 191 return $.Deferred().resolve(null).promise(); 192 } 193 194 self.accountError(''); 195 self.accountLoading(true); 196 197 // Build GetAccountNumber URL 198 var base = (sf && typeof sf.getServiceRoot === 'function') 199 ? sf.getServiceRoot("BudgetTruck.WebAPI") + "Accounts/GetAccountNumber" 200 : "/DesktopModules/BudgetTruck.WebAPI/API/Accounts/GetAccountNumber"; 201 var url = base + "?userName=" + encodeURIComponent(u); 202 203 //console.log("fetchAccountInfo requesting:", url); 204 return $.ajax({ 205 url: url, 206 type: "GET", 207 dataType: "json", 208 beforeSend: function (xhr) { 209 if (sf && typeof sf.setModuleHeaders === 'function') { 210 try { sf.setModuleHeaders(xhr); } catch (ex) { console.warn("sf.setModuleHeaders error", ex); } 211 } 212 } 213 }).done(function (result) { 214 //console.log("GetAccountNumber raw result:", result); 215 216 // normalize envelopes / array 217 var data = result; 218 if (data && data.d) data = data.d; 219 if (data && data.value) data = data.value; 220 if (typeof data === "string") { 221 try { data = JSON.parse(data); } catch (e) { /* leave as-is */ } 222 } 223 224 var item = null; 225 if (Array.isArray(data)) { 226 if (data.length > 0) item = data[0]; 227 } else if (data && typeof data === 'object') { 228 item = data; 229 } else if (result && Array.isArray(result) && result.length > 0) { 230 item = result[0]; 231 } 232 233 if (!item) { 234 self.accountError("No account info returned for user."); 235 self.accountNo(""); 236 self.accountName(""); 237 return; 238 } 239 240 // pull common key names 241 var acctNo = item.AccountNumber || item.accountNumber || item.AccountNo || item.accountNo || ""; 242 var acctType = item.AccountType || item.accountType || item.Type || ""; 243 244 self.accountNo(String(acctNo || ""));
245 // try to set accountName from this response if present 246 var acctName = item.AccountName || item.accountName || item.Account || item.Name || ""; 247 if (acctName && String(acctName).trim() !== "") { 248 self.accountName(String(acctName)); 249 } else { 250 // accountName missing â call details API 251 if (acctNo) { 252 self.fetchAccountDetails(acctNo, acctType); 253 } else { 254 // can't fetch details without account number 255 self.accountError("Account number missing; cannot get details."); 256 } 257 } 258 }).fail(function (jqXHR, textStatus, errorThrown) { 259 console.error("GetAccountNumber failed:", textStatus, errorThrown, jqXHR && jqXHR.responseText); 260 self.accountError("Unable to fetch account info. " + (jqXHR && jqXHR.status ? ("Status: " + jqXHR.status) : "")); 261 }).always(function () { 262 self.accountLoading(false); 263 }); 264 }; 265 266 // Call GetAccountDetails to get full account data (AccountName, etc.) 267 self.fetchAccountDetails = function (accountNo, accountType) { 268 if (!accountNo) { 269 console.warn("fetchAccountDetails: missing accountNo"); 270 return $.Deferred().resolve(null).promise(); 271 } 272 273 self.accountError(''); 274 self.accountLoading(true); 275 276 var base = (sf && typeof sf.getServiceRoot === 'function') 277 ? sf.getServiceRoot("BudgetTruck.WebAPI") + "Accounts/GetAccounts" 278 : "/DesktopModules/BudgetTruck.WebAPI/API/Accounts/GetAccounts"; 279 280 //var url = base + "?accountNo=" + encodeURIComponent(accountNo) + "&accountType=" + encodeURIComponent(accountType || ""); 281 var url = base + "?accountNo=" + encodeURIComponent(accountNo); 282 283 //console.log("fetchAccountDetails ->", url); 284 285 return $.ajax({ 286 url: url, 287 type: "GET", 288 dataType: "json", 289 beforeSend: function (xhr) { 290 if (sf && typeof sf.setModuleHeaders === 'function') { 291 try { sf.setModuleHeaders(xhr); } catch (e) { console.warn("sf.setModuleHeaders error", e); } 292 } 293 } 294 }).done(function (result) { 295 //console.log("GetAccountDetails raw result:", result); 296 297 // Normalize envelopes 298 var data = result; 299 if (data && data.d) data = data.d; 300 if (data && data.value) data = data.value; 301 if (typeof data === "string") { 302 try { data = JSON.parse(data); } catch (e) { /* ignore parse error */ } 303 } 304 305 // Pick first item if array 306 var item = null; 307 if (Array.isArray(data)) { 308 item = data.length > 0 ? data[0] : null; 309 } else if (data && typeof data === 'object') { 310 item = data; 311 } else if (Array.isArray(result) && result.length > 0) { 312 item = result[0]; 313 } 314 315 // Extract account name (primary path: ACCOUNTDETAIL.CPRN_NM) 316 var acctName = ""; 317 318 if (item) { 319 // Try the ACCOUNTDETAIL wrapper first 320 //var acctWrap = item.ACCOUNTDETAIL || item.accountdetail || item.AccountDetail || item.accountDetail; 321 // if (acctWrap && typeof acctWrap === 'object') { 322 acctName = item.AccountName || ""; 323 //} 324 325 // Fallbacks if wrapper not present or name empty 326 acctName = acctName || item.AccountName || item.AccountName || item.AccountName || item.accountName || item.Name || item.name || ""; 327 } 328 329 self.accountName(String(acctName || "")); 330 // Optionally update accountNo if response has canonical number 331 var respNo = (item && (item.AccountNumber || item.AccountNumber || item.AccountNumber || item.accountNumber || null)); 332 if (respNo) self.accountNo(String(respNo)); 333 334 //console.log("Parsed accountName:", self.accountName(), "accountNo:", self.accountNo()); 335 }).fail(function (jqXHR, textStatus, errorThrown) { 336 console.error("GetAccountDetails failed:", textStatus, errorThrown, jqXHR && jqXHR.responseText); 337 self.accountError("Unable to fetch account details. " + (jqXHR && jqXHR.status ? "Status: " + jqXHR.status : "")); 338 }).always(function () { 339 self.accountLoading(false); 340 }); 341 }; 342 343 if (self.UserName && typeof self.UserName === 'function' && self.UserName()) { 344 self.fetchAccountInfo(self.UserName()); 345 } 346 347 348 349 if (self.UserName && typeof self.UserName.subscribe === 'function') { 350 self.UserName.subscribe(function (newVal) { 351 if (newVal && String(newVal).trim() !== '') { 352 self.fetchAccountInfo(newVal); 353 } else { 354 self.accountNo(""); 355 self.accountName(""); 356 self.accountError(""); 357 } 358 }); 359 } 360 361 362 363 364 // Validate (or clear) subject only when visible 365 self.validateSubject = function () { 366 var visible = !!self.isSubjectVisible(); // call the computed 367 //console.log("Subject visible:", visible); 368 369 if (visible) { 370 var sel = (typeof self.selectedSubject === 'function') ? self.selectedSubject() : self.selectedSubject; 371 if (!sel || String(sel).trim() === '' || String(sel).trim().toLowerCase() === 'select') { 372 self.subjectError("Subject is required"); 373 } else { 374 self.subjectError(""); 375 } 376 } else { 377 // ensure subject error cleared when section not visible 378 self.subjectError(""); 379 } 380 }; 381 382
383 ///////////////////////////////end fetch account no////// 384 385 // Form validation 386 self.validate = () => { 387 self.nameError(""); 388 self.emailError(""); 389 self.phoneError(""); 390 self.messageError(""); 391 self.validateSubject(); 392 const nameVal = self.stripHtml(self.name().trim()); 393 const msgVal = self.stripHtml(self.message().trim()); 394 395 if (!nameVal) self.nameError("Name is required."); 396 else if (nameVal.length > 50) self.nameError("Name cannot exceed 50 characters."); 397 else if (!self.isValidAlphaNumInput(nameVal)) self.nameError("Name can only contain letters and standard name punctuation."); 398 399 if (!self.email().trim()) self.emailError("Email is required."); 400 else if (!self.isValidEmail(self.email().trim())) self.emailError("Invalid email format."); 401 else if (self.email().trim().length > 128) self.emailError("Email cannot exceed 128 characters."); 402 403 const rawPhn = self.phone().trim().replace(/\D/g, ''); 404 if (self.phone.raw().trim() && !/^\d{10}$/.test(self.phone.raw().trim())) self.phoneError("Phone must be 10 digits."); 405 else if (rawPhn && rawPhn.length !== 10) self.phoneError('Enter a valid 10-digit phone number'); 406 else self.phoneError(''); 407 if (self.isSubjectVisible && self.isSubjectVisible()) { 408 if (!self.selectedSubject() || self.selectedSubject().trim() === "Select") self.subjectError("Subject is required."); 409 } 410 411 if (!msgVal) self.messageError("Message is required."); 412 else if (msgVal.length > 2000) self.messageError("Message cannot exceed 2000 characters."); 413 414 if (self.isSubjectVisible && self.isSubjectVisible()) { 415 416 return !(self.nameError() || self.emailError() || self.phoneError() || self.messageError() || self.subjectError()); 417 } 418 else { 419 return !(self.nameError() || self.emailError() || self.phoneError() || self.messageError()); 420 } 421 422 }; 423 424 // Submit form 425 self.isFormValid = ko.computed(() => { 426 self.name(); 427 self.email(); 428 self.phone(); 429 430 if (self.isSubjectVisible && self.isSubjectVisible()) { 431 self.selectedSubject(); 432 } 433 self.message(); 434 435 if (self.isSubjectVisible && self.isSubjectVisible()) { 436 return !self.nameError() && !self.emailError() && !self.phoneError() && !self.subjectError() && !self.messageError(); 437 } 438 439 return !self.nameError() && !self.emailError() && !self.phoneError() && !self.messageError(); 440 }); 441 442 self.submitForm = function (_, event) { 443 if (event) event.preventDefault(); 444 if (!self.validate()) return false; 445 446 if (self.submittedOnce()) return; 447 self.submittedOnce(true); 448 449 const rawPhn = self.phone.raw().trim().replace(/\D/g, ''); 450 const formData = new FormData(); 451 formData.append("AccountNo", self.accountNo().trim()); 452 formData.append("AccountName", self.accountName().trim()); 453 formData.append("Name", self.stripHtml(self.name().trim())); 454 formData.append("EMail", self.email().trim()); 455 formData.append("Phone", rawPhn); 456 457 if (self.isSubjectVisible && self.isSubjectVisible()) { 458 formData.append("Subject", self.selectedSubject()); 459 } 460 formData.append("Message", self.stripHtml(self.message().trim())); 461 formData.append("ContactMethod", self.contactMethod()); 462 formData.append("FileNames", self.attachedFileNames()); 463 464 if (self.fileData() && self.fileData().length > 0) { 465 self.fileData().forEach(f => formData.append("file", f)); 466 } 467 468 const baseServicePath = sf.getServiceRoot("BudgetTruck.WebAPI") + "ContactUs/"; 469 $.ajax({
470 url: baseServicePath + "ConfirmMail", 471 type: "POST", 472 data: formData, 473 processData: false, 474 contentType: false, 475 beforeSend: function (xhr) { sf.setModuleHeaders(xhr); }, 476 }).done(function (result) { 477 if (result) { 478 self.successText("Thank you for contacting us.\nWe typically respond within 2 business days."); 479 self.visible(true); 480 self.isVisible(false); 481 self.resetForm(); 482 } else { 483 self.successText("Unexpected error. Please try again."); 484 self.visible(true); 485 self.isVisible(true); 486 } 487 }).fail(function () { 488 self.successText("Unexpected error. Please try again."); 489 self.visible(true); 490 self.submittedOnce(false); 491 }); 492 493 return false; 494 }; 495 496 self.resetForm = function () { 497 //self.accountNo(""); 498 //self.accountName(""); 499 if (self.UserName && typeof self.UserName === 'function' && self.UserName()) { 500 self.fetchAccountInfo(self.UserName()); 501 } 502 self.name(""); self.nameTouched(false); 503 self.email(""); self.emailTouched(false); 504 self.phone(""); self.phoneTouched(false); 505 if (self.isSubjectVisible && self.isSubjectVisible()) { 506 self.selectedSubject("Select"); self.subjectTouched(false); 507 } 508 self.message(""); self.messageTouched(false); 509 self.attachedFileNames(""); 510 self.fileData([]); 511 self.nameError(""); 512 self.emailError(""); 513 self.phoneError(""); 514 if (self.isSubjectVisible && self.isSubjectVisible()) { 515 self.subjectError(""); 516 } 517 self.messageError(""); 518 self.fileError(""); 519 520 const fileInput = document.getElementById("file-input"); 521 if (fileInput) fileInput.value = null; 522 523 // Clear touched flags 524 self.nameTouched(false); 525 self.emailTouched(false); 526 self.phoneTouched(false); 527 if (self.isSubjectVisible && self.isSubjectVisible()) { 528 self.subjectTouched(false); 529 } 530 self.messageTouched(false); 531 532 self.isVisible(true); 533 }; 534 535 self.openFileDialog = () => document.getElementById("file-input").click(); 536 537 self.readableFileSize = bytes => (bytes / (1024 * 1024)).toFixed(2) + " MB"; 538 539 self.fileChanged = function (_, event) { 540 const files = event.target.files; 541 if (!files || files.length === 0) return; 542 543 const allowedTypes = [ 544 "text/plain", "application/vnd.ms-excel", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", 545 "application/msword", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", 546 "image/png", "image/jpeg", "application/pdf" 547 ]; 548 549 const validFiles = [], fileNames = [], errorMessages = []; 550 for (let i = 0; i < files.length; i++) { 551 const file = files[i]; 552 if (!allowedTypes.includes(file.type)) { 553 errorMessages.push("Invalid file type: " + file.name); 554 continue; 555 } 556 if (file.size <= 0 || file.size > 2 * 1024 * 1024) { 557 errorMessages.push("File size must be between 1 byte and 2MB: " + file.name); 558 continue; 559 } 560 validFiles.push(file); 561 fileNames.push(file.name); 562 } 563 564 if (errorMessages.length > 0) { 565 self.fileError(errorMessages.join("<br/>")); 566 } else { 567 self.fileError(""); 568 self.fileData(validFiles); 569 self.attachedFileNames(fileNames.join(", ")); 570 } 571 event.target.value = ''; // reset file input 572 }; 573 574 self.removeFile = function () { 575 self.fileData([]); 576 self.attachedFileNames(""); 577 self.fileError(""); 578 }; 579 580 self.closeConfirmation = () => { 581 self.visible(false); 582 self.resetForm(); 583 }; 584 585 self.divStyle = ko.pureComputed(() => self.isHighlighted() ? { backgroundColor: 'yellow', fontWeight: 'bold' } : {}); 586}
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.