PageSourceSearch

https://www.asrvs.com/themes/theme-builder/js/compare.js?r=1790869111

js asrvs.com collected 2026-10-01 15:38:51 UTC 27,149 bytes, 637 lines download raw bytes

1/**
2 * Compare Units Functionality
3 * Allows users to compare up to 3 units side by side
4 */
5
6$(document).ready(function() {
7    // Compare functionality object
8    var Compare = {
9        units: [],
10        maxUnits: 3,
11        
12        // Initialize compare functionality
13        init: function() {
14            this.bindEvents();
15            this.loadFromStorage();
16        },
17        
18        // Bind event handlers
19        bindEvents: function() {
20            var self = this;
21            
22            // Compare button clicks
23            $(document).on('click', '.btn-compare', function(e) {
24                e.preventDefault();
25                var unitId = $(this).data('unit-id');
26                
27                if ($(this).hasClass('added')) {
28                    self.removeUnit(unitId);
29                } else {
30                    var unitData = {
31                        id: unitId,
32                        stock: $(this).data('unit-stock') || '',
33                        year: $(this).data('unit-year') || '',
34                        make: $(this).data('unit-make') || '',
35                        model: $(this).data('unit-model') || ''
36                    };
37                    self.addUnit(unitData);
38                }
39            });
40            
41            // Banner toggle
42            $(document).on('click', '#compare-toggle', function() {
43                self.toggleBanner();
44            });
45            
46            // Clear all button
47            $(document).on('click', '#clear-compare', function() {
48                self.clearAll();
49            });
50            
51            // Compare button in banner
52            $(document).on('click', '#compare-units', function() {
53                self.showComparisonModal();
54            });
55            
56            // Remove unit from banner
57            $(document).on('click', '.compare-unit-remove', function() {
58                var unitId = $(this).data('unit-id');
59                self.removeUnit(unitId);
60            });
61        },
62        
63        // Add unit to comparison
64        addUnit: function(unitData) {
65            if (this.units.length >= this.maxUnits) {
66                alert('You can only compare up to ' + this.maxUnits + ' units at a time.');
67                return;
68            }
69            
70            // Check if unit already exists
71            var exists = this.units.find(function(unit) {
72                return unit.id === unitData.id;
73            });
74            
75            if (exists) {
76                return;
77            }
78            
79            // Store basic unit data for banner display
80            var unitToStore = {
81                id: unitData.id,
82                stock: unitData.stock || '',
83                year: unitData.year || '',
84                make: unitData.make || '',
85                model: unitData.model || '',
86                // These will be populated from AJAX response later
87                image_url: null,
88                unit_name: null
89            };
90            
91            this.units.push(unitToStore);
92            this.updateUI();
93            this.saveToStorage();
94            this.showBanner();
95            
96            // Fetch unit image immediately when adding
97            this.fetchUnitImages();
98            
99            // Update button state
100            $('.btn-compare[data-unit-id="' + unitData.id + '"]').addClass('added');
101        },
102        
103        // Remove unit from comparison
104        removeUnit: function(unitId) {
105            this.units = this.units.filter(function(unit) {
106                return unit.id !== unitId;
107            });
108            
109            this.updateUI();
110            this.saveToStorage();
111            
112            // Update button state
113            $('.btn-compare[data-unit-id="' + unitId + '"]').removeClass('added');
114            
115            // Hide banner if no units
116            if (this.units.length === 0) {
117                this.hideBanner();
118            }
119        },
120        
121        // Clear all units
122        clearAll: function() {
123            this.units = [];
124            this.updateUI();
125            this.saveToStorage();
126            this.hideBanner();
127            
128            // Reset all button states
129            $('.btn-compare').removeClass('added');
130        },
131        
132        // Update UI elements
133        updateUI: function() {
134            var count = this.units.length;
135            $('#compare-count-text').text('Compare (' + count + '/' + this.maxUnits + ')');
136            
137            // Enable/disable compare button
138            if (count >= 2) {
139                $('#compare-units').prop('disabled', false);
140            } else {
141                $('#compare-units').prop('disabled', true);
142            }
143            
144            // Update units list in banner
145            this.updateUnitsList();
146        },
147        
148        // Update the units list in banner
149        updateUnitsList: function() {
150            var $list = $('#compare-units-list');
151            $list.empty();
152            
153            this.units.forEach(function(unit) {
154                var imageHtml = '';
155                if (unit.image_url) {
156                    imageHtml = '<img src="' + unit.image_url + '" alt="Unit Image" style="width:100%;height:100px;object-fit:cover;border-radius:3px;margin-bottom:8px;" />';
157                } else {
158                    imageHtml = '<div class="unit-placeholder-img" style="width:100%;height:100px;background:#f0f0f0;border-radius:3px;margin-bottom:8px;display:flex;align-items:center;justify-content:center;color:#999;">Image</div>';
159                }
160                
161                var unitTitle = unit.unit_name || (unit.year + ' ' + unit.make + ' ' + unit.model);
162                var stockText = unit.stock ? 'Stock: ' + unit.stock : '';
163                
164                var unitHtml = '<div class="compare-unit-item">' +
165                    '<button class="compare-unit-remove" data-unit-id="' + unit.id + '">&times;</button>' +
166                    imageHtml +
167                    '<h4>' + unitTitle + '</h4>' +
168                    '<div class="unit-details">' + stockText + '</div>' +
169                '</div>';
170                $list.append(unitHtml);
171            });
172        },
173        
174        // Show comparison banner
175        showBanner: function() {
176            $('#compare-banner').removeClass('hidden').addClass('show');
177        },
178        
179        // Hide comparison banner
180        hideBanner: function() {
181            $('#compare-banner').removeClass('show expanded').addClass('hidden');
182        },
183        
184        // Toggle banner expanded state
185        toggleBanner: function() {
186            $('#compare-banner').toggleClass('expanded');
187            
188            // Images are now fetched immediately when units are added,
189            // so no need to fetch again on banner expand
190        },
191        
192        // Fetch unit images and details for banner display
193        fetchUnitImages: function() {
194            var self = this;
195            var unitsNeedingData = this.units.filter(function(unit) {
196                return !unit.image_url; // Only fetch data for units we don't have images for
197            });
198            
199            if (unitsNeedingData.length === 0) {
200                return; // All units already have data
201            }
202            
203            var unitIds = unitsNeedingData.map(function(unit) {
204                return unit.id;
205            });
206            
207            // Make AJAX request to get basic unit data (reuse compare endpoint)
208            $.ajax({
209                url: WS_PATH + "modules/unit/includes/compare_ajax.php?r=" + Math.random(),
210                type: 'POST',
211                data: {
212                    do_what: 'get_comparison_data',
213                    unit_ids: JSON.stringify(unitIds)
214                },
215                success: function(response) {
216                    // Handle different response formats
217                    if (typeof response === 'string') {
218                        var parts = response.split('##');
219                        if (parts[0] === '0') {
220                            return; // Error, but don't break the banner
221                        }
222                        try {
223                            response = JSON.parse(parts[1] || response);
224                        } catch (e) {
225                            return; // Invalid response, but don't break the banner
226                        }
227                    }
228                    
229                    if (response && response.status === 'success' && response.units) {
230                        // Update our stored units with the fetched data
231                        response.units.forEach(function(fetchedUnit) {
232                            var storedUnit = self.units.find(function(unit) {
233                                return unit.id == fetchedUnit.unit_id;
234                            });
235                            if (storedUnit) {
236                                storedUnit.image_url = fetchedUnit.image_url;
237                                storedUnit.unit_name = fetchedUnit.unit_name;
238                            }
239                        });
240                        
241                        // Update the banner display with new images
242                        self.updateUnitsList();
243                        self.saveToStorage(); // Save the updated data
244                    }
245                },
246                error: function() {
247                    // Silently fail - banner will just show placeholder images
248                }
249            });
250        },
251        
252        // Show comparison modal
253        showComparisonModal: function() {
254            if (this.units.length < 2) {
255                alert('Please select at least 2 units to compare.');
256                return;
257            }
258            
259            this.loadComparisonData();
260        },
261        
262        // Load comparison data and show modal
263        loadComparisonData: function() {
264            var self = this;
265            var modalBody = $('#compare-modal-body');
266            
267            // Show loading message
268            modalBody.html('<div class="text-center"><i class="fa fa-spinner fa-spin"></i> Loading comparison data...</div>');
269            $('#compare-modal').modal('show');
270            
271            // Get unit IDs for AJAX call
272            var unitIds = this.units.map(function(unit) {
273                return unit.id;
274            });
275            
276            // Make AJAX request to fetch comparison data
277            $.ajax({
278                url: WS_PATH + "modules/unit/includes/compare_ajax.php?r=" + Math.random(),
279                type: 'POST',
280                data: {
281                    do_what: 'get_comparison_data',
282                    unit_ids: JSON.stringify(unitIds)
283                },
284                success: function(response) {
285                    // Handle different response formats
286                    if (typeof response === 'string') {
287                        // Handle legacy format: "0##Error message" or "1##Success message"
288                        var parts = response.split('##');
289                        if (parts[0] === '0') {
290                            modalBody.html('<div class="alert alert-danger">' + (parts[1] || 'Error loading comparison data.') + '</div>');
291                            return;
292                        }
293                        // If it's a string but starts with 1, try to parse as JSON
294                        try {
295                            response = JSON.parse(parts[1] || response);
296                        } catch (e) {
297                            modalBody.html('<div class="alert alert-danger">Invalid response format.</div>');
298                            return;
299                        }
300                    }
301                    
302                    if (response && response.status === 'success') {
303                        self.renderComparisonTable(response);
304                    } else {
305                        modalBody.html('<div class="alert alert-danger">Error loading comparison data. Please try again.</div>');
306                    }
307                },
308                error: function(xhr, textStatus, error) {
309                    console.error('AJAX Error:', error, xhr.responseText);
310                    modalBody.html('<div class="alert alert-danger">Error loading comparison data. Please try again.</div>');
311                }
312            });
313        },
314        
315        // Render comparison table
316        renderComparisonTable: function(response) {
317            var modalBody = $('#compare-modal-body');
318            
319            if (!response || !response.units || response.units.length === 0) {
320                modalBody.html('<div class="alert alert-danger">No unit data available for comparison.</div>');
321                return;
322            }
323            
324            var units = response.units;
325            var specs = response.specs || [];
326            var unitCount = units.length;
327            
328            // Create mobile-friendly tabbed interface
329            var mobileHtml = this.renderMobileComparison(units, specs);
330            var desktopHtml = this.renderDesktopComparison(units, specs, unitCount);
331            
332            modalBody.html(
333                '<div class="compare-mobile-view">' + mobileHtml + '</div>' +
334                '<div class="compare-desktop-view">
334' + desktopHtml + '</div>'
335            );
336            
337            // Initialize mobile functionality
338            this.initializeMobileComparison(units.length);
339        },
340        
341        // Render mobile tabbed comparison
342        renderMobileComparison: function(units, specs) {
343            var self = this;
344            var html = '';
345            
346            // Tab navigation
347            html += '<div class="compare-mobile-tabs">';
348            units.forEach(function(unit, index) {
349                var activeClass = index === 0 ? ' active' : '';
350                var unitTitle = unit.unit_name || (unit.model_year + ' ' + unit.make + ' ' + unit.model);
351                html += '<div class="compare-tab' + activeClass + '" data-tab="' + index + '">' + 
352                        '<span class="tab-title">' + unitTitle + '</span>' +
353                        '</div>';
354            });
355            html += '</div>';
356            
357            // Filter basic specs to only include those where ALL units have values
358            var basicSpecs = [
359                { label: 'Unit Name', field: 'unit_name' },
360                { label: 'Model Year', field: 'model_year' },
361                { label: 'Retail Price', field: 'retail_price_display' },
362                { label: 'Sales Price', field: 'sales_price_display' }
363            ];
364            
365            var filteredBasicSpecs = basicSpecs.filter(function(spec) {
366                return units.every(function(unit) {
367                    var value = unit[spec.field] || '';
368                    if (spec.field.includes('price_display') && (!value || value === '')) {
369                        return false;
370                    }
371                    return value && value !== '' && value !== 'N/A';
372                });
373            });
374            
375            // Filter custom specs to only include those where ALL units have values
376            var filteredSpecs = specs.filter(function(spec) {
377                return units.every(function(unit) {
378                    var specData = unit.specs && unit.specs[spec.code];
379                    var value = specData ? specData.value : '';
380                    return value && value !== '' && value !== 'N/A';
381                });
382            });
383            
384            // Tab content
385            html += '<div class="compare-mobile-content">';
386            units.forEach(function(unit, index) {
387                var activeClass = index === 0 ? ' active' : '';
388                html += '<div class="compare-tab-content' + activeClass + '" data-content="' + index + '">';
389                
390                // Unit header with image
391                html += '<div class="mobile-unit-header">';
392                if (unit.image_url) {
393                    html += '<img src="' + unit.image_url + '" alt="' + unit.unit_name + '" class="mobile-unit-image" />';
394                }
395                html += '<h3>' + (unit.unit_name || (unit.model_year + ' ' + unit.make + ' ' + unit.model)) + '</h3>';
396                html += '</div>';
397                
398                // Specs table for this unit
399                html += '<div class="mobile-specs-table">';
400                
401                // Basic specs (only those where all units have values)
402                filteredBasicSpecs.forEach(function(spec) {
403                    var value = unit[spec.field] || 'N/A';
404                    if (spec.field.includes('price_display') && (!value || value === '')) {
405                        value = 'N/A';
406                    }
407                    html += '<div class="mobile-spec-row">' +
408                            '<div class="mobile-spec-label">' + spec.label + '</div>' +
409                            '<div class="mobile-spec-value">' + value + '</div>' +
410                            '</div>';
411                });
412                
413                // Custom specs (only those where all units have values)
414                filteredSpecs.forEach(function(spec) {
415                    var specData = unit.specs && unit.specs[spec.code];
416                    var value = specData ? specData.value : 'N/A';
417                    html += '<div class="mobile-spec-row">' +
418                            '<div class="mobile-spec-label">' + spec.name + '</div>' +
419                            '<div class="mobile-spec-value">' + value + '</div>' +
420                            '</div>';
421                });
422                
423                html += '</div>'; // mobile-specs-table
424                html += '</div>'; // compare-tab-content
425            });
426            html += '</div>'; // compare-mobile-content
427            
428            return html;
429        },
430        
431        // Render desktop table comparison  
432        renderDesktopComparison: function(units, specs, unitCount) {
433            var tableHtml = '<table class="table compare-table units-' + unitCount + '">';
434            
435            // Header row with unit images and names (no "Unit" label)
436            tableHtml += '<tr class="unit-header-row">';
437            tableHtml += '<td class="spec-label unit-header-spacer"></td>'; // Empty spacer cell
438            units.forEach(function(unit) {
439                tableHtml += '<td class="unit-column unit-header">';
440                if (unit.image_url) {
441                    tableHtml += '<img src="' + unit.image_url + '" alt="' + unit.unit_name + '" />';
442                } else {
443                    tableHtml += '<div class="unit-placeholder-img" style="width:100%;max-width:200px;height:150px;background:#f0f0f0;border-radius:5px;margin:0 auto 10px;display:flex;align-items:center;justify-content:center;color:#999;">No Image</div>';
444                }
445                tableHtml += '<h4>' + (unit.unit_name || (unit.model_year + ' ' + unit.make + ' ' + unit.model)) + '</h4>';
446                tableHtml += '</td>';
447            });
448            tableHtml += '</tr>';
449            
450            // Basic info rows (using the correct field names from ride_units)
451            var basicSpecs = [
452                { label: 'Unit Name', field: 'unit_name' },
453                { label: 'Model Year', field: 'model_year' },
454                { label: 'Retail Price', field: 'retail_price_display' },
455                { label: 'Sales Price', field: 'sales_price_display' }
456            ];
457            
458            // Filter basic specs - only show if ALL units have the value
459            basicSpecs.forEach(function(spec) {
460                var hasAllValues = true;
461                
462                // Check if all units have this spec value
463                units.forEach(function(unit) {
464                    var value = unit[spec.field] || '';
465                    
466                    // Handle empty price displays
467                    if (spec.field.includes('price_display') && (!value || value === '')) {
468                        hasAllValues = false;
469                    } else if (!value || value === '' || value === 'N/A') {
470                        hasAllValues = false;
471                    }
472                });
473                
474                // Only add row if all units have values
475                if (hasAllValues) {
476                    tableHtml += '<tr class="spec-row">';
477                    tableHtml += '<td class="spec-label">' + spec.label + '</td>';
478                    units.forEach(function(unit) {
479                        var value = unit[spec.field] || 'N/A';
480                        
481                        // Handle empty price displays
482                        if (spec.field.includes('price_display') && (!value || value === '')) {
483                            value = 'N/A';
484                        }
485                        
486                        tableHtml += '<td class="spec-value">' + value + '</td>';
487                    });
488                    tableHtml += '</tr>';
489                }
490            });
491            
492            // Custom specification rows from database - only show if ALL units have the value
493            specs.forEach(function(spec) {
494                var hasAllValues = true;
495                
496                // Check if all units have this spec value
497                units.forEach(function(unit) {
498                    var specData = unit.specs && unit.specs[spec.code];
499                    var value = specData ? specData.value : '';
500                    if (!value || value === '' || value === 'N/A') {
501                        hasAllValues = false;
502                    }
503                });
504                
505                // Only add row if all units have values
506                if (hasAllValues) {
507                    tableHtml += '<tr class="spec-row">';
508                    tableHtml += '<td class="spec-label">' + spec.name + '</td>';  // spec.name is the display label
509                    units.forEach(function(unit) {
510                        var specData = unit.specs && unit.specs[spec.code];  // spec.code is the field name
511                        var value = specData ? specData.value : 'N/A';
512                        tableHtml += '<td class="spec-value">' + value + '</td>';
513                    });
514                    tableHtml += '</tr>';
515                }
516            });
517            
518            tableHtml += '</table>';
519            return tableHtml;
520        },
521        
522        // Initialize mobile comparison functionality
523        initializeMobileComparison: function(unitCount) {
524            var self = this;
525            
526            // Initialize swipe tracking variables
527            this.swipeStartX = 0;
528            this.swipeCurrentX = 0;
529            this.isDragging = false;
530            
531            // Tab click handlers
532            $(document).off('click', '.compare-tab').on('click', '.compare-tab', function() {
533                var tabIndex = parseInt($(this).data('tab'));
534                self.switchToTab(tabIndex);
535            });
536            
537            // Touch/swipe and mouse drag handlers
538            var contentArea = document.querySelector('.compare-mobile-content');
539            if (contentArea) {
540                // Touch events for mobile
541                contentArea.addEventListener('touchstart', function(e) {
542                    self.swipeStartX = e.touches[0].clientX;
543                    self.isDragging = true;
544                }, { passive: true });
545                
546                contentArea.addEventListener('touchmove', function(e) {
547                    if (!self.isDragging) return;
548                    self.swipeCurrentX = e.touches[0].clientX;
549                }, { passive: true });
550                
551                contentArea.addEventListener('touchend', function() {
552                    if (!self.isDragging) return;
553                    self.isDragging = false;
554                    self.handleSwipeEnd(unitCount);
555                }, { passive: true });
556                
557                // Mouse events for desktop testing
558                contentArea.addEventListener('mousedown', function(e) {
559                    self.swipeStartX = e.clientX;
560                    self.isDragging = true;
561                    e.preventDefault(); // Prevent text selection
562                });
563                
564                contentArea.addEventListener('mousemove', function(e) {
565                    if (!self.isDragging) return;
566                    self.swipeCurrentX = e.clientX;
567                });
568                
569                contentArea.addEventListener('mouseup', function() {
570                    if (!self.isDragging) return;
571                    self.isDragging = false;
572                    self.handleSwipeEnd(unitCount);
573                });
574                
575                // Handle mouse leaving the area
576                contentArea.addEventListener('mouseleave', function() {
577                    if (!self.isDragging) return;
578                    self.isDragging = false;
579                    self.handleSwipeEnd(unitCount);
580                });
581            }
582        },
583        
584        // Handle swipe/drag end logic
585        handleSwipeEnd: function(unitCount) {
586            var diffX = this.swipeStartX - this.swipeCurrentX;
587            var threshold = 50; // minimum swipe distance
588            
589            if (Math.abs(diffX) > threshold) {
590                var currentTab = $('.compare-tab.active').data('tab');
591                if (diffX > 0 && currentTab < unitCount - 1) {
592                    // Swipe left - next tab
593                    this.switchToTab(currentTab + 1);
594                } else if (diffX < 0 && currentTab > 0) {
595                    // Swipe right - previous tab
596                    this.switchToTab(currentTab - 1);
597                }
598            }
599        },
600        
601        // Switch to specific tab
602        switchToTab: function(tabIndex) {
603            // Update tab navigation
604            $('.compare-tab').removeClass('active');
605            $('.compare-tab[data-tab="' + tabIndex + '"]').addClass('active');
606            
607            // Update content
608            $('.compare-tab-content').removeClass('active');
609            $('.compare-tab-content[data-content="' + tabIndex + '"]').addClass('active');
610        },
611        
612        // Save to localStorage
613        saveToStorage: function() {
614            localStorage.setItem('compareUnits', JSON.stringify(this.units));
615        },
616        
617        // Load from localStorage
618        loadFromStorage: function() {
619            var stored = localStorage.getItem('compareUnits');
620            if (stored) {
621                this.units = JSON.parse(stored);
622                if (this.units.length > 0) {
623                    this.updateUI();
624                    this.showBanner();
625                    
626                    // Update button states
627                    this.units.forEach(function(unit) {
628                        $('.btn-compare[data-unit-id="' + unit.id + '"]').addClass('added');
629                    });
630                }
631            }
632        }
633    };
634    
635    // Initialize compare functionality
636    Compare.init();
637});

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.