PageSourceSearch

https://commercial.budgettruck.com/DesktopModules/BudgetTruck.ContactUs/Scripts/ViewModel.js?cdv=207

js budgettruck.com collected 2026-10-02 06:19:07 UTC 23,698 bytes, 586 lines download raw bytes

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.