1// Product Compare Functionality 2// Wrapped in IIFE to avoid global conflicts 3(function() { 4 'use strict'; 5 6 // Only run on website pages, not desk 7 if (frappe.boot && frappe.boot.setup_complete === false) { 8 return; 9 } 10 11 var MAX_ITEMS = 4; 12 var STORAGE_KEY = 'csl_compare_items'; 13 14 function getItems() { 15 try { 16 var items = localStorage.getItem(STORAGE_KEY); 17 return items ? JSON.parse(items) : []; 18 } catch (e) { 19 return []; 20 } 21 } 22 23 function saveItems(items) { 24 try { 25 localStorage.setItem(STORAGE_KEY, JSON.stringify(items)); 26 } catch (e) { 27 console.error('[CSL Compare] Error saving items:', e); 28 } 29 } 30 31 function addItem(itemData) { 32 var items = getItems(); 33 34 // Check if already exists 35 if (items.find(function(item) { return item.item_code === itemData.item_code; })) { 36 frappe.show_alert({ 37 message: __('This product is already in your compare list'), 38 indicator: 'orange' 39 }); 40 return false; 41 } 42 43 // Check max limit 44 if (items.length >= MAX_ITEMS) { 45 frappe.show_alert({ 46 message: __('You can compare up to {0} products at a time', [MAX_ITEMS]), 47 indicator: 'orange' 48 }); 49 return false; 50 } 51 52 items.push(itemData); 53 saveItems(items); 54 updateWidget(); 55 updateButtonStates(); 56 57 frappe.show_alert({ 58 message: __('Product added to compare list'), 59 indicator: 'green' 60 }); 61 62 return true; 63 } 64 65 function removeItem(itemCode) { 66 var items = getItems(); 67 items = items.filter(function(item) { return item.item_code !== itemCode; }); 68 saveItems(items); 69 updateWidget(); 70 updateButtonStates(); 71 72 frappe.show_alert({ 73 message: __('Product removed from compare list'), 74 indicator: 'blue' 75 }); 76 } 77 78 function clearAll() { 79 saveItems([]); 80 updateWidget(); 81 updateButtonStates(); 82 } 83 84 function createWidget() { 85 // Check if widget already exists 86 if (document.getElementById('compare-widget')) return; 87 88 var widgetHtml = 89 '<div class="compare-widget" id="compare-widget">' + 90 '<div class="compare-widget-header">' + 91 '<h5>' + __('Compare Products') + '</h5>' + 92 '<span class="compare-count">0</span>' + 93 '</div>' + 94 '<div class="compare-widget-body">' + 95 '<div class="compare-items-list"></div>' + 96 '</div>' + 97 '<div class="compare-widget-footer">' + 98 '<a href="/compare" class="btn btn-primary compare-now-btn">' + __('Compare Now') + '</a>' + 99 '<button class="btn btn-outline compare-clear-all">' + __('Clear All') + '</button>' + 100 '</div>' + 101 '</div>'; 102 103 $('body').append(widgetHtml); 104 } 105 106 function updateWidget() { 107 var items = getItems(); 108 var $widget = $('#compare-widget'); 109 var $itemsList = $widget.find('.compare-items-list'); 110 var $count = $widget.find('.compare-count'); 111 112 // Update count 113 $count.text(items.length); 114 115 // Show/hide widget 116 if (items.length > 0) { 117 $widget.addClass('show'); 118 } else { 119 $widget.removeClass('show'); 120 } 121 122 // Render items 123 $itemsList.empty(); 124 items.forEach(function(item) { 125 var itemHtml = 126 '<div class="compare-item" data-item-code="' + item.item_code + '">' + 127 '<img class="compare-item-image" src="' + (item.image || '/assets/webshop/images/cart-empty-state.png') + '" alt="' + item.name + '">' + 128 '<span class="compare-item-name">' + item.name + '</span>' + 129 '<button class="compare-item-remove" data-item-code="' + item.item_code + '" title="' + __('Remove') + '">' +
130 '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">' + 131 '<line x1="18" y1="6" x2="6" y2="18"></line>' + 132 '<line x1="6" y1="6" x2="18" y2="18"></line>' + 133 '</svg>' + 134 '</button>' + 135 '</div>'; 136 $itemsList.append(itemHtml); 137 }); 138 } 139 140 function updateButtonStates() { 141 var items = getItems(); 142 var itemCodes = items.map(function(item) { return item.item_code; }); 143 144 // Update only compare-specific buttons (more specific selector) 145 $('.compare-action[data-item-code]').each(function() { 146 var $btn = $(this); 147 var itemCode = $btn.data('item-code'); 148 149 if (itemCodes.indexOf(itemCode) !== -1) { 150 $btn.addClass('active'); 151 $btn.find('.btn-label').text(__('Added')); 152 } else { 153 $btn.removeClass('active'); 154 $btn.find('.btn-label').text(__('Compare')); 155 } 156 }); 157 } 158 159 function bindEvents() { 160 // Use specific class for compare buttons to avoid conflicts 161 // Don't use stopPropagation to allow other handlers to work 162 $(document).on('click.cslcompare', '.compare-action[data-item-code]', function(e) { 163 e.preventDefault(); 164 165 var $btn = $(this); 166 var itemCode = $btn.data('item-code'); 167 168 if (!itemCode) return; 169 170 // Check if already in compare list 171 var items = getItems(); 172 var existingItem = items.find(function(item) { return item.item_code === itemCode; }); 173 174 if (existingItem) { 175 // Remove from compare 176 removeItem(itemCode); 177 } else { 178 // Add to compare 179 var itemData = { 180 item_code: itemCode, 181 name: $btn.data('item-name') || $btn.closest('.product-details').find('.product-title').first().text().trim() || '', 182 image: $btn.data('item-image') || '', 183 route: $btn.data('item-route') || window.location.pathname 184 }; 185 addItem(itemData); 186 } 187 }); 188 189 // Remove item from widget 190 $(document).on('click.cslcompare', '.compare-item-remove', function(e) { 191 e.preventDefault(); 192 e.stopPropagation(); 193 var itemCode = $(this).data('item-code'); 194 removeItem(itemCode); 195 }); 196 197 // Clear all button 198 $(document).on('click.cslcompare', '.compare-clear-all', function(e) { 199 e.preventDefault(); 200 clearAll(); 201 }); 202 } 203 204 function init() { 205 createWidget(); 206 bindEvents(); 207 updateWidget(); 208 updateButtonStates(); 209 } 210 211 // Initialize when DOM is ready 212 if (typeof frappe !== 'undefined' && frappe.ready) { 213 frappe.ready(function() { 214 // Small delay to ensure other scripts have loaded 215 setTimeout(init, 100); 216 }); 217 } else { 218 $(document).ready(function() { 219 setTimeout(init, 100); 220 }); 221 } 222 223 // Expose minimal API for external use 224 window.cslCompare = { 225 add: addItem, 226 remove: removeItem, 227 clear: clearAll, 228 getItems: getItems, 229 updateStates: updateButtonStates 230 }; 231 232})();
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.