PageSourceSearch

https://www.lmtire-wheel.com/js/global.js?v=2.29.242

js lmtire-wheel.com collected 2026-10-04 13:26:31 UTC 22,668 bytes, 670 lines download raw bytes

1//Add any global functions to the rsEcommerce class.
2class rsEcommerce {
3
4    #vehicle = null;
5    #vehicleDescription = null;
6
7    events = {
8        "globalReady": "globalReady",
9        "openVehicle": "openVehicle",
10        "editVehicle": "editVehicle",
11        "newVehicle": "newVehicle",
12        "vehicleUpdate": "vehicleUpdate",
13        "vehicleDescriptionUpdate": "vehicleDescriptionUpdate"
14    }
15
16    constructor(options) {
17        this.ready = false;
18        this.init(options);
19    }
20
21    async init(options) {
22        this.key = options.key;
23        this.api = options.api;
24        this.version = options.version;
25        this.locationsUrl = options.locationsUrl;
26        this.selectLocationUrl = options.selectLocationUrl;
27        this.selectInstallerLocationUrl = options.selectInstallerLocationUrl;
28        this.vehicleDescriptionCacheKey = `rs-vehicle-description`;
29        const settings = await import(`/dynamicjs/settings?${this.version}`);
30        this.settings = settings._settings;
31        // Vehicle state is per-user and must bypass cached JS modules.
32        const vehicle = await import(`/dynamicjs/vehicle?v=${this.version}&t=${Date.now()}`);
33        this.vehicle = vehicle._vehicle.vehicle;
34        this.vehicleDescription = vehicle._vehicle.vehicleDescription;
35        this.ready = true;
36        document.dispatchEvent(new CustomEvent(this.events.globalReady));
37    }
38
39    get vehicle() {
40        return this.#vehicle;
41    }
42
43    set vehicle(value) {
44        this.#vehicle = value;
45        document.dispatchEvent(new CustomEvent(this.events.vehicleUpdate));
46    }
47
48    get vehicleDescription() {
49        return this.#vehicleDescription;
50    }
51
52    set vehicleDescription(value) {
53        this.#vehicleDescription = value;
54        if (value) localStorage.setItem(this.vehicleDescriptionCacheKey, JSON.stringify(value));
55        if (!value) localStorage.removeItem(this.vehicleDescriptionCacheKey);
56        document.dispatchEvent(new CustomEvent(this.events.vehicleDescriptionUpdate));
57    }
58
59    async apiFetchAsync(options) {
60
61        //options = {
62        //    "action": "",
63        //    "data": "",
64        //    "apiKey": ""
65        //}
66
67        const url = `${this.api}${options.action}`;
68        const data = JSON.stringify(options.data);
69        const response = await fetch(url, {
70            cache: "default",
71            method: "POST",
72            headers: {
73                "Accept": "application/json",
74                "Authorization": `ApiKey ${options && options.apiKey ? options.apiKey : this.key}`,
75                "Content-Type": "application/json"
76            },
77            body: data
78        });
79        const result = await response.json();
80        if (!result.Success) {
81            return console.warn(`
82                    Ridestyler request unsuccessful
83                    Code: ${result.Code}
84                    Options: ${JSON.stringify(options)}
85                `);
86        }
87        return result;
88    }
89
90    getNoConflictQueries(params, url) {
91        const urlObj = url ? new URL(url) : new URL(location.href);
92        const search = urlObj.searchParams;
93        if (!search) return new URLSearchParams();
94        for (const key of params) {
95            search.delete(key);
96        }
97        return search;
98    }
99
100    getArraySortedByProperty(arr, prop) {
101        if (!arr.length) return arr;
102        if (!prop) return arr;
103        arr.sort((a, b) => {
104            const aNum = Number(a[prop]);
105            const bNum = Number(b[prop]);
106            if (aNum < bNum) return -1;
107            if (aNum > bNum) return 1;
108            return 0;
109        });
110        return arr;
111    }
112
113    getDeepObjectEqualityState(obj1, obj2) {
114        const type1 = this.getPrimimtiveTypeOfObject(obj1);
115        const type2 = this.getPrimimtiveTypeOfObject(obj2);
116        if (type1 != type2) return false;
117        switch (type1) {
118            case "Array":
119                if (obj1.length != obj2.length) return false;
120                for (let i = 0; i < obj1.length; i++) {
121                    if (!this.getDeepObjectEqualityState(obj1[i], obj2[i])) return false;
122                }
123                return true;
124                break;
125            case "Object":
126                const keys1 = Object.keys(obj1);
127                const keys2 = Object.keys(obj2);
128                if (keys1.length != keys2.length) return false;
129                for (let i = 0; i < keys1.length; i++) {
130                    if (!this.getDeepObjectEqualityState(obj1[keys1[i]], obj2[keys2[i]])) return false;
131                }
132                return true;
133                break;
134            default:
135                if (obj1 === obj2) return true;
136                return false;
137        }
138    }
139
140    getPrimimtiveTypeOfObject(obj) {
141        switch (Object.prototype.toString.call(obj)) {
142            case "[object Array]":
143                return "Array";
144            case "[object Object]":
145                return "Object";
146            case "[object String]":
147                return "String";
148            case "[object Number]":
149                return "Number";
150            case "[object Boolean]":
151                return "Boolean";
152            case "[object BigInt]":
153                return "BigInt";
154            case "[object Undefined]":
155                return "Undefined";
156            case "[object Symbol]":
157                return "Symbol";
158            default:
159                return "Null";
160        }
161    }
162
163    getKeyByValue(object, value) {
164        return Object.keys(object).find(key => object[key] === value);
165    }
166
167    getObjectByKeyValuePair(array, keyValuePair) {
168        const [key, value] = Object.entries(keyValuePair)[0];
169        return array.find(pair => pair[key] === value);
170    }
171
172    getSettings(settings) {
173        if (!settings) return null;
174        for (let key in settings) {
175            switch (this.getPrimimtiveTypeOfObject(settings[key])) {
176                case "String":
177                    if (!/\,[\s]+/.test(settings[key])) break;
178                    const stringArray = settings[key].split(/\,[\s]+/);
179                    if (!stringArray.length) break;
180                    settings[key] = stringArray;
181                    break;
182            }
183        }
184        return settings;
185    }
186
187    async getStatesOptionListFetch(countryId) {
188        const url = `/customer/address/states/${countryId}`;
189        const request = await fetch(url, {
190            method: 'POST'
191        });
192        return await request.json();
193    }
194
195    parseDate(d) {
196        if (!d) return [
197            [`DateOfBirthDay`, ``],
198            [`DateOfBirthMonth`, ``],
199            [`DateOfBirthYear`, ``]
200        ];
201        const date = new Date(d);
202        return [
203            [`DateOfBirthDay`, date.getUTCDate()],
204            [`DateOfBirthMonth`, date.getUTCMonth() + 1],
205            [`DateOfBirthYear`, date.getUTCFullYear()]
206        ];
207    }
208
209    jaroDistance(s1, s2) {
210        //Convert to same case.
211        s1 = s1.toString().toLowerCase();
212        s2 = s2.toString().toLowerCase();
213
214        // If the strings are equal
215        if (s1 == s2) return 1.0;
216
217        // Length of two strings
218        const len1 = s1.length;
219        const len2 = s2.length;
220
221        // Maximum distance upto which matching
222        // is allowed
223        const max_dist = Math.floor(Math.max(len1, len2) / 2) - 1;
224
225        // Count of matches
226        let match = 0;
227
228        // Hash for matches
229        const hash_s1 = Array(s1.length).fill(0);
230        const hash_s2 = Array(s1.length).fill(0);
231
232        // Traverse through the first string
233        for (let i = 0; i < len1; i++) {
234
235            // Check if there is any matches
236            for (let j = Math.max(0, i - max_dist);
237                j < Math.min(len2, i + max_dist + 1); j++)
238
239                // If there is a match
240                if (s1[i] == s2[j] && hash_s2[j] == 0) {
241                    hash_s1[i] = 1;
242                    hash_s2[j] = 1;
243                    match++;
244                    break;
245                }
246        }
247
248        // If there is no match
249        if (match == 0)
250            return 0.0;
251
252        // Number of transpositions
253        let t = 0;
254
255        let point = 0;
256
257        // Count number of occurrences
258        // where two characters match but
259        // there is a third matched character
260        // in between the indices
261        for (let i = 0; i < len1; i++)
262            if (hash_s1[i]) {
263
264                // Find the next matched character
265                // in second string
266                while (hash_s2[point] == 0)
267                    point++;
268
269                if (s1[i] != s2[point++])
270                    t++;
271            }
272
273        t /= 2;
274
275        // Return the Jaro Similarity
276        return ((match) / (len1)
277            + (match) / (len2)
278            + (match - t) / (match))
279            / 3.0;
280    }
281
282    jaroWinklerSimilarity(s1, s2) {
283        s1 = s1.toString().toLowerCase();
284        s2 = s2.toString().toLowerCase();
285
286        if (s1 === s2) return 1.0;
287
288        let jaro_dist = this.jaroDistance(s1, s2);
289
290        // If the jaro Similarity is above a threshold
291        if (jaro_dist > 0.7) {
292
293            // Find the length of common prefix
294            let prefix = 0;
295
296            for (let i = 0; i < Math.min(s1.length, s2.length); i++) {
297
298                // If the characters match
299                if (s1[i] == s2[i]) prefix++;
300
301                // Else break
302                else break;
303            }
304
305            // Maximum of 4 characters are allowed in prefix
306            prefix = Math.min(4, prefix);
307
308            // Calculate jaro winkler Similarity
309            jaro_dist += 0.1 * prefix * (1 - jaro_dist);
310        }
311        return parseFloat(jaro_dist.toFixed(6));
312    }
313
314    stringMatch(s1, s2) {
315        s1 = s1.toLowerCase();
316        s2 = s2.toLowerCase();
317        const rx = new RegExp(s1);
318        return rx.test(s2) ? 1 : 0;
319    }
320
321    stringSimilarity(s1, s2) {
322        const reverse = s => { return s.split("").reverse().join("") };
323        const dist1 = this.jaroWinklerSimilarity(s1, s2);
324        const dist2 = this.jaroWinklerSimilarity(reverse(s1), reverse(s2));
325        const dist3 = this.stringMatch(s1, s2);
326        return (dist1 + dist2 + dist3) / 3;
327    }
328
329    getMediaBreakPoint() {
330        const prefix = getComputedStyle(document.documentElement).getPropertyValue('--prefix').replace(" ", "");
331        return getComputedStyle(document.documentElement).getPropertyValue(`--${prefix}breakpoint`).replace(" ", "");
332    }
333
334    async getRemoveVehicle() {
335        const request = await fetch("/Vehicle/Remove", {
336            method: 'POST',
337            headers: {
338                "Accept": "application/json",
339                "Content-Type": "application/json"
340            }
341        });
342        return await request;
343    }
344
345    getVehicleDescriptionCache() {
346        try {
347            const cache = localStorage.getItem(`rs-vehicle-description`);
348            if (!cache) return null;
349            return JSON.parse(cache);
350        } catch (e) { }
351        return null;
352    }
353
354    async getVehicleImagesAsync(config, color, suspensionHeight) {
355        if (!config && this.vehicle) config = this.vehicle.ConfigurationID;
356        if (!config) return null;
357        const images = [];
358        const vehicleDescription = await this.getVehicleDescriptionAsync(config);
359        if (vehicleDescription.HasSideImage) images.push(this.getVehicleImage("side", config, color, suspensionHeight));
360        if (vehicleDescription.HasAngledImage) images.push(this.getVehicleImage("angle", config, color, suspensionHeight));
361        return images;
362    }
363
364    getVehicleImage(angle, config, color, suspensionHeight) {
365        if (!this.settings) return null;
366        config = config ?? this.getCurrentVehicleConfig();
367
368        if (!config && this.vehicle) config = this.vehicle.ConfigurationID;
369        if (!color && this.vehicle) color = this.vehicle.PaintSchemeColor;
370        if (!suspensionHeight && this.vehicle) color = this.vehicle.SuspensionHeight;
371
372        const widths = this.settings.RsVehicleSelectionSettings.ImageWidths;
373        const heights = this.settings.RsVehicleSelectionSettings.ImageHeights;
374
375        if (!widths || !heights) return null;
376        const srcSets = [];
377        let src;
378
379        for (let i = 0; i < widths.length; i++) {
380            const width = widths[i];
381            const height = heights[i];
382
383            const renderImage = {
384                "VehicleConfiguration": config,
385                "PaintColor": color,
386                "SuspensionFront": suspensionHeight,
387                "SuspensionRear": suspensionHeight,
388                "Width": width,
389                "Height": height,
390                "positionx": 1,
391                "positiony": 2,
392                "Type": angle,
393                "Format": 1
394            };
395
396            let imageUrl = ridestyler ? ridestyler.ajax.url('Vehicle/Render', renderImage) : null;
397            if (i === widths.length - 1) src = imageUrl;
398            srcSets.push(`${imageUrl} ${width}w`);
399        }
400
401        return {
402            Src: src,
403            SrcSet: srcSets.join(", "),
404            Sizes: this.settings.RsVehicleSelectionSettings.ImageSizes,
405            Alt: this.vehicleDescription.FullDescription,
406            Fallback: this.vehicleImageFallback
407        }
408    }
409
410    async getVehicleDescriptionAsync(config) {
411        if (!config && this.vehicle) config = this.vehicle.ConfigurationID;
412        if (!config) return null;
413
414        // Return the current description if it matches before making an api request.
415        if (this.vehicleDescription != null && this.vehicleDescription.ConfigurationID === config) return this.vehicleDescription;
416
417        const result = await this.apiFetchAsync({
418            action: "Vehicle/GetDescriptions",
419            data: {
420                "VehicleConfigurations": [config]
421            }
422        });
423
424        this.vehicleDescription = result.Descriptions[0];
425        if (!this.vehicleDescription) return null;
426
427        return this.vehicleDescription;
428    }
429
430    getUUID() {
431        return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c =>
432            (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
433        )
434    }
435
436    goToHash() {
437        if (!location.hash) return;
438        const hash = location.hash;
439        location.hash = `null`;
440        setTimeout(e => {
441            location.hash = hash;
442        }, 1000);
443    }
444
445    async getCMSContent(url, section, hasWrapper) {
446        try {
447
448            const scriptUrl = new URL("/cms/render", location.origin);
449
450            if (url != null) scriptUrl.searchParams.append("url", url);
451            if (section != null) scriptUrl.searchParams.append("section", section);
452            if (hasWrapper != null) scriptUrl.searchParams.append("hasWrapper", hasWrapper);
453
454            const request = await fetch(scriptUrl.href);
455            if (!request.ok) throw new Error(`HTTP error! status: ${request.status}`);
456            const result = await request.text();
457            return result;
458
459        } catch (error) {
460            console.error('Error:', error);
461        }
462    }
463
464    appendScript(script) {
465
466        const parser = new DOMParser;
467        const parsed = parser.parseFromString(script, "text/html");
468        const nodes = [...parsed.head.childNodes, ...parsed.body.childNodes];
469
470        nodes.forEach(node => {
471            if (node.nodeName === 'SCRIPT') {
472                var script = document.createElement('script');
473                script.innerHTML = node.innerHTML;
474                document.body.appendChild(script);
475            } else {
476                document.body.appendChild(document.importNode(node, true));
477            }
478        });
479
480    }
481
482    fixDetailsAchors() {
483        if (!location.hash) return;
484        const details = document.querySelectorAll("details");
485        if (!details) return;
486        for (let d of details) {
487            const anchor = d.querySelector(location.hash);
488            if (!anchor) continue;
489            d.open = true;
490            anchor.scrollIntoView();
491        }
492    }
493
494    fixAllDetailsAchors() {
495        window.addEventListener("hashchange", this.fixDetailsAchors.bind(this));
496    }
497
498    getLocaleString(obj) {
499        if (!obj) return null;
500        const localeString = obj[_localStrings["locale.key.current"]];
501        const defaultLocaleString = obj[_localStrings["locale.key.default"]];
502        return localeString ? localeString : defaultLocaleString;
503    }
504
505    initVue(obj) {
506        document.addEventListener('customElementsInitialized', this.initVueHandler(obj));
507    }
508
509    async initVueHandler(obj) {
510        if (this.findNearestVueRoot(obj)) return;
511        if (!window.vueComponents) return;
512
513        const { createApp } = Vue;
514        const self = this;
515
516        const app = createApp({
517            async mounted() {
518                self.initVueMounted(self);
519            }
520        });
521
522        const imports = Object.entries(window.vueComponents).map(
523            async ([tag, path]) => {
524                const module = await import(`${path}?v=${this.version}`);
525                return { tag, component: module.default };
526            }
527        );
528
529        const components = await Promise.all(imports);
530
531        for (const { tag, component } of components) {
532            app.component(tag, component);
533        }
534
535        app.mount(obj);
536    }
537
538    async initVueMounted(self) {
539        clearTimeout(self.vueMountDebounce);
540        self.vueMountDebounce = setTimeout(async () => {
541            const vueMountedEvent = new CustomEvent("vuemounted");
542            self.goToHash();
543            self.fixAllDetailsAchors();
544            if (!self.footerScripts) {
545                self.footerScripts = await self.getCMSContent(window.location.pathname, "Scripts.Page");
546                self.appendScript(self.footerScripts);
547            }
548            document.dispatchEvent(vueMountedEvent);
549        }, 10);
550    }
551
552    findNearestVueRoot(node) {
553        for (let n = node; n; n = n.parentNode || (n.host ?? null)) {
554            if (n.__vue_app__ || n.__vue__) {
555                return n;
556            }
557        }
558        return null;
559    }
560}
561
562class RsStoreSetter {
563    constructor() {
564        this.init();
565    }
566
567    init() {
568        if (window.addEventListener) {
569            window.addEventListener("message", this.onMessage.bind(this), false);
570        }
571        else if (window.attachEvent) {
572            window.attachEvent("onmessage", this.onMessage.bind(this), false);
573        }
574    }
575
576    onMessage(e) {
577        // Check sender origin to be trusted
578        if (e.origin !== window.location.origin) return; //make sure its us talking
579
580        const data = e.data;
581
582        if (data.message == "selected") {
583            this.setLocation(data.selectedID);
584        }
585        if (data.message == "installer-selected") {
586            this.setInstallerLocation(data.selectedID);
587        }
588    }
589
590    async getLocationFetch(locationId) {
591        const headers = {};
592        const fd = new FormData();
593        fd.append("id", locationId);
594        fd.append("currentUrl", location.pathname);
595        const request = await fetch(rsGlobal.selectLocationUrl, {
596            method: "POST",
597            headers: headers,
598            body: fd
599        });
600        const json = await request.json();
601        return json;
602    }
603
604    async setLocation(locationId) {
605        try {
606            const result = await this.getLocationFetch(locationId);
607            if (!result.Success) {
608                console.error("Failed to fetch location: ", result);
609                return;
610            }
611            const searchParams = new URLSearchParams(window.location.search);
612            const lowercaseParams = new URLSearchParams(Array.from(searchParams, ([key, value]) => [key.toLowerCase(), value]));
613            const searchReturnURL = lowercaseParams.get("returnurl");
614            const returnUrl = result.ReturnUrl || searchReturnURL || window.location.href;
615            let url;
616            if (returnUrl.startsWith("http://") || returnUrl.startsWith("https://")) {
617                url = new URL(returnUrl);
618            } else {
619                url = new URL(returnUrl, window.location.origin);
620            }
621            url.hash = "";
622            url.search = "";
623            try {
624                localStorage.setItem("rs-store-confirmed", true);
625            } catch (e) { }
626            window.location.href = url.href;
627        } catch (error) {
628            console.error("An error occurred while setting the location:", error);
629        }
630    }
631
632    async setInstallerLocation(locationId) {
633        try {
634            const fd = new FormData();
635            fd.append("id", locationId);
636            const request = await fetch(rsGlobal.selectInstallerLocationUrl, {
637                method: "POST",
638                body: fd
639            });
640            const result = await request.json();
641            if (!result.Success) {
642                console.error("Failed to select installer location: ", result);
643                return false;
644            }
645
646            document.dispatchEvent(new CustomEvent("rs-installer-location-selected", {
647                detail: { locationId: locationId }
648            }));
649            return true;
650        } catch (error) {
651            console.error("An error occurred while selecting the installer location:", error);
652            return false;
653        }
654    }
655
656}
657
658const rsGlobal = new rsEcommerce({
659    "key": document.currentScript.getAttribute("key"),
660    "api": document.currentScript.getAttribute("api"),
661    "version": document.currentScript.getAttribute("version"),
662    "locationsUrl": document.currentScript.getAttribute("locations-url"),
663    "selectLocationUrl": document.currentScript.getAttribute("select-location-url"),
664    "selectInstallerLocationUrl": document.currentScript.getAttribute("select-installer-location-url")
665});
666
667const storeSetter = new RsStoreSetter();
668window.rsInstallerLocationSelector = {
669    setLocation: storeSetter.setInstallerLocation.bind(storeSetter)
670};

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.