PageSourceSearch

https://www.ikea.com.do/webroot/conjurer/js/Comparator.js?v=1088

js ikea.com.do collected 2026-10-04 00:54:56 UTC 19,361 bytes, 544 lines download raw bytes

1var Comparator = {
2    // ============================================================
3    // STATE / CONFIG
4    // ============================================================
5    items: [],
6    itemsImg: [],
7    backendItems: [],
8    _popstateHandler: null,
9    _observer: null,
10
11    selectors: {
12        checkbox: 'input[type="checkbox"].comparator_checkbox',
13        comparator: '#comparator',
14        onlyOneItemsSelected: '[data-target="onlyOneItemsSelected"]',
15        pluralES: '[data-target="pluralES"]',
16        maxItemsSelected: '[data-target="maxItemsSelected"]',
17        compareButton: '[data-action="comparatItems"]',
18        quantityItemsSelected: '[data-target="quantityItemsSelected"]',
19        selectedImages: '[data-target="selectedImages"]',
20        productList: '[data-target="productList"]',
21        blockProductList: '[data-target="blockProductList"]',
22        sidebarActionsBar: '.skp-sidebar-actions__bar',
23        noArticles: '[data-no-articles]'
24    },
25
26    // ============================================================
27    // MAIN INIT / BIND EVENTS
28    // ============================================================
29    main: function () {
30        Comparator.resetAndRefreshUI();
31        Comparator.recuperateItems();
32
33        $(document).on("change", ".comparator_checkbox", Comparator.controlChecks);
34        $(document).on("click", '[data-action="closeComparator"]', Comparator.closeComparator);
35        $(document).on("click", '[data-action="comparatItems"]', Comparator.goCompare);
36        $(document).on("click", '[data-action="removeItemProduct"]', Comparator.removeItemProduct);
37
38        Comparator.bindCheckboxObserver();
39    },
40
41    init: function (items) {
42        Comparator.backendItems = items || [];
43        Comparator.bindComparatorBackActions();
44        Comparator.updateMeasureRows(Comparator.backendItems);
45    },
46
47    // ============================================================
48    // HELPERS / UTILS
49    // ============================================================
50    normalizeId: function (id) {
51        return String(id)
52            .replace(/^comparator_/, "")
53            .replace(/_\d+$/, "")
54            .replace(/\./g, "");
55    },
56
57    getAllCheckboxes: function () {
58        return document.querySelectorAll(Comparator.selectors.checkbox);
59    },
60
61    // ============================================================
62    // UI REFRESH (CENTRALIZED)
63    // ============================================================
64    refreshUI: function () {
65        Comparator.updateChatBotPosition();
66        Comparator.updateComparatorUI();
67        Comparator.updateMaxItemsClass();
68        Comparator.getItemsCompare();
69    },
70
71    // ============================================================
72    // BACK NAVIGATION / HISTORY STATE
73    // ============================================================
74    bindComparatorBackActions: function () {
75        function handleBackAction() {
76            $('#editSelection').attr('loading', true);
77            $.busyLoadFull('show');
78
79            const uniquePartnumbers = [
80                ...new Set(
81                    [...document.querySelectorAll('[data-partnumber]')]
82                        .map(el => el.getAttribute('data-partnumber'))
83                        .filter(Boolean)
84                )
85            ];
86
87            const filteredItems = Comparator.backendItems.filter(item =>
88                uniquePartnumbers.includes(String(item.partnumber))
89            );
90
91            localStorage.setItem('itemsComparator', JSON.stringify(filteredItems));
92            window.history.go(-1);
93        }
94
95        $('#editSelection')
96            .off('click.comparatorBack')
97            .on('click.comparatorBack', function (e) {
98                e.preventDefault();
99                handleBackAction();
100            });
101
102        // BREADCRUMB LAST LINK (KEEP SELECTION)
103        const breadcrumbLinks = document.querySelectorAll('ol.breadcrumb a.breadcrumb-link');
104
105        if (breadcrumbLinks.length) {
106            const lastLink = breadcrumbLinks[breadcrumbLinks.length - 1];
107
108            $(lastLink)
109                .off('click.comparatorBreadcrumb')
110                .on('click.comparatorBreadcrumb', function (e) {
111                    e.preventDefault();
112
113                    handleBackAction();
114                });
115        }
116
117        if (Comparator._popstateHandler) {
118            window.removeEventListener('popstate', Comparator._popstateHandler);
119        }
120
121        Comparator._popstateHandler = function () {
122            handleBackAction();
123        };
124
125        window.addEventListener('popstate', Comparator._popstateHandler);
126
127        if (!history.state || !history.state.comparator) {
128            history.pushState({ comparator: true }, '', window.location.href);
129        }
130    },
131
132    // ============================================================
133    // RECOVER ITEMS FROM LOCALSTORAGE
134    // ============================================================
135    recuperateItems: function () {
136        const items = JSON.parse(localStorage.getItem('itemsComparator')) || [];
137        localStorage.removeItem('itemsComparator');
138
139        if (!items.length) return;
140
141        const ids = items.map(item => Comparator.normalizeId(item.partnumber));
142        const pending = new Set(ids);
143
144        let attempts = 0;
145        const maxAttempts = 20;
146
147        const interval = setInterval(() => {
148            pending.forEach(id => {
149                const checkbox = document.getElementById('comparator_' + id);
150
151                if (checkbox) {
152                    if (!Comparator.items.includes(id)) {
153                        checkbox.checked = true;
154                        checkbox.dispatchEvent(new Event('change', { bubbles: true }));
155                    }
156                    pending.delete(id);
157                }
158            });
159
160            attempts++;
161
162            if (!pending.size || attempts >= maxAttempts) {
163                clearInterval(interval);
164            }
165        }, 300);
166    },
167
168    // ============================================================
169    // UI: CLOSE / RESET
170    // ============================================================
171    closeComparator: function (e) {
172        e.preventDefault();
173
174        Comparator.items = [];
175        Comparator.itemsImg = [];
176
177        Comparator.getAllCheckboxes().forEach(cb => {
178            cb.checked = false;
179            cb.disabled = false;
180        });
181
182        Comparator.refreshUI();
183    },
184
185    resetAndRefreshUI: function () {
186        setTimeout(() => {
187            Comparator.getAllCheckboxes().forEach(cb => {
188                cb.checked = false;
189                cb.disabled = false;
190            });
191
192            Comparator.items = [];
193            Comparator.itemsImg = [];
194
195            Comparator.refreshUI();
196        }, 100);
197    },
198
199    // ============================================================
200    // UI: CHATBOT POSITION / SIDEBAR
201    // ============================================================
202    updateChatBotPosition: function () {
203        const personalWebShopper = document.querySelector('personal-web-shopper');
204        const chatBot = personalWebShopper?.shadowRoot?.getElementById('app');
205        const sidebarActionsBar = document.querySelector(Comparator.selectors.sidebarActionsBar);
206
207        const hasItems = Comparator.items.length >= 1;
208
209        if (chatBot) {
210            chatBot.style.bottom = hasItems ? '5rem' : '0';
211        }
212
213        if (sidebarActionsBar) {
214            sidebarActionsBar.classList.toggle('backToTopComparator', hasItems);
215        }
216    },
217
218    // ============================================================
219    // UI: MAIN COMPARATOR BLOCK
220    // ============================================================
221    updateComparatorUI: function () {
222        const comparator = document.querySelector(Comparator.selectors.comparator);
223        const onlyOneItemsSelected = document.querySelector(Comparator.selectors.onlyOneItemsSelected);
224        const pluralES = document.querySelector(Comparator.selectors.pluralES);
225        const compareButton = document.querySelector(Comparator.selectors.compareButton);
226
227        const count = Comparator.items.length;
228
229        if (comparator) {
230            comparator.toggleAttribute("hidden", count < 1);
231        }
232
233        if (onlyOneItemsSelected) {
234            onlyOneItemsSelected.toggleAttribute("hidden", count !== 1);
235        }
236
237        if (pluralES) {
238            pluralES.toggleAttribute("hidden", count < 2);
239        }
240
241        if (compareButton) {
242            compareButton.toggleAttribute("disabled", count < 2);
243        }
244    },
245
246    // ============================================================
247    // UI: SELECTED IMAGES
248    // ============================================================
249    updateSelectedImages: function (imgItems) {
250        const wrapper = document.querySelector(Comparator.selectors.selectedImages);
251        if (!wrapper) return;
252
253        wrapper.innerHTML = "";
254
255        if (!imgItems?.length) return;
256
257        wrapper.innerHTML = imgItems
258            .map(img => `
259                <wc-aspect-ratio ratio="square">
260                    <img 
261                        src="${img}" 
262                        alt="" 
263                        loading="lazy" 
264                        class="selectedItemImagen"
265                        onerror="this.src='/webroot/img/icons/comparator-no-image.png';"
266                    >
267                </wc-aspect-ratio>
268            `)
269            .join("");
270    },
271
272    // ============================================================
273    // UI: PRODUCT LIST CLASS (items-X)
274    // ============================================================
275    updateMaxItemsClass: function () {
276        const productList = document.querySelector(Comparator.selectors.productList);
277        const blockProductList = document.querySelector(Comparator.selectors.blockProductList);
278
279        if (!productList || !blockProductList) return;
280
281        const itemsCount = blockProductList.querySelectorAll('[data-name="article"]').length;
282
283        [...productList.classList]
284            .filter(cls => cls.startsWith("items-"))
285            .forEach(cls => productList.classList.remove(cls));
286
287        productList.classList.add('items-' + itemsCount);
288    },
289
290    // ============================================================
291    // UI: UPDATE ONLY MEASURE ROWS (GROUPED BY MEASURE NAME)
292    // ============================================================
293    updateMeasureRows: function (items) {
294        if (!items?.length) return;
295
296        const measureRowsContainer = document.querySelector('[data-target="measureRows"]');
297        const isSpi = measureRowsContainer?.dataset?.id === "1";
298        if (!measureRowsContainer) return;
299
300        // --- 1. Obtener lista única de nombres de medidas (case insensitive) ---
301        const measureNames = []; // guardamos siempre en lowercase
302
303        items.forEach(item => {
304            (item.measure || []).forEach(measure => {
305                const name = measure?.name?.trim();
306                if (!name) return;
307
308                const normalizedName = name.toLowerCase();
309
310                if (!measureNames.includes(normalizedName)) {
311                    measureNames.push(normalizedName);
312                }
313            });
314        });
315
316        // --- 2. Construir HTML agrupado por nombre ---
317        let html = '';
318
319        measureNames.forEach(measureName => {
320            const formattedName = measureName.charAt(0).toUpperCase() + measureName.slice(1);
321
322            html += `<tr data-target="rowMeasure">`;
323
324            items.forEach(item => {
325                const measureObj = (item.measure || []).find(measure => {
326                    const currentName = measure?.name?.trim();
327                    return currentName && currentName.toLowerCase() === measureName;
328                });
329
330                if (measureObj) {
331                    html += `
332                    <td data-partnumber="${item.partnumber}">
333                        <p class="font-weight-bold">${formattedName}</p>
334                        <p>${isSpi ? measureObj.metric : measureObj.imperial}</p>
335                    </td>
336                `;
337                } else {
338                    html += `
339                    <td data-partnumber="${item.partnumber}">
340                        -
341                    </td>
342                `;
343                }
344            });
345
346            html += `</tr>`;
347        });
348
349        // --- 3. Reemplazar SOLO las filas de medidas ---
350        measureRowsContainer.innerHTML = html;
351    },
352
353    // ============================================================
354    // OBSERVER: KEEP CHECKBOXES SYNC WHEN DOM RERENDERS
355    // ============================================================
356    bindCheckboxObserver: function () {
357        /**
358        * Este observer existe para cuando el listado de productos se re-renderiza (filtros, búsqueda, paginación...).
359        * En esos casos los checkbox se recrean en el DOM y pierden su estado visual.
360        *
361        * La fuente de verdad NO es el DOM, es Comparator.items.
362        * Por tanto:
363        *  - re-marcamos los checkbox que estén en Comparator.items
364        *  - re-aplicamos la regla de disabled según Comparator.items.length
365        */
366        let rafScheduled = false;
367
368        const sync = () => {
369            const checkboxes = Comparator.getAllCheckboxes();
370            const selected = new Set(Comparator.items);
371            const maxReached = Comparator.items.length >= 5;
372
373            checkboxes.forEach(cb => {
374                const id = Comparator.normalizeId(cb.id);
375                cb.checked = selected.has(id);
376                cb.disabled = maxReached ? !selected.has(id) : false;
377            });
378        };
379
380        if (Comparator._observer) {
381            Comparator._observer.disconnect();
382        }
383
384        Comparator._observer = new MutationObserver(() => {
385            if (rafScheduled) return;
386
387            rafScheduled = true;
388            requestAnimationFrame(() => {
389                rafScheduled = false;
390                sync();
391            });
392        });
393
394        Comparator._observer.observe(document.body, {
395            childList: true,
396            subtree: true
397        });
398
399        sync();
400    },
401
402    // ============================================================
403    // CHECKBOX CONTROL / MAX 5 ITEMS RULE
404    // ============================================================
405    controlChecks: function (e) {
406        const itemId = Comparator.normalizeId(this.id);
407
408        if (this.checked) {
409            if (Comparator.items.length < 5 && !Comparator.items.includes(itemId)) {
410                Comparator.items.push(itemId);
411                Comparator.itemsImg.push(Comparator.getCheckedImage(e));
412            } else {
413                this.checked = false;
414            }
415        } else {
416            const index = Comparator.items.indexOf(itemId);
417            if (index > -1) {
418                Comparator.items.splice(index, 1);
419                Comparator.itemsImg.splice(index, 1);
420            }
421        }
422
423        const maxReached = Comparator.items.length >= 5;
424
425        Comparator.getAllCheckboxes().forEach(cb => {
426            if (!cb.checked) cb.disabled = maxReached;
427            else cb.disabled = false;
428        });
429
430        Comparator.refreshUI();
431    },
432
433    // ============================================================
434    // REMOVE ENERGY BLOCK IF EMPTY
435    // ============================================================
436    removeEnergyEfficiencyBlock: function () {
437        const wrappers = document.querySelectorAll('.energyWrappers');
438
439        const shouldRemove = [...wrappers].every(wrapper => {
440            const energy = wrapper.dataset.energy;
441            return !energy || energy.trim() === '';
442        });
443
444        if (shouldRemove) {
445            document.querySelector('.blockEnergy')?.remove();
446        }
447    },
448
449    // ============================================================
450    // ACTION: REMOVE PRODUCT FROM COMPARATOR PAGE
451    // ============================================================
452    removeItemProduct: function (e) {
453        e.preventDefault();
454
455        const partnumber = this.getAttribute("data-partnumber");
456        if (!partnumber) return;
457
458        // --- 1. Eliminar del DOM todos los elementos con ese partnumber ---
459        document.querySelectorAll(`[data-partnumber="${partnumber}"]`).forEach(el => el.remove());
460
461        // --- 2. Eliminar también del array backendItems ---
462        Comparator.backendItems = Comparator.backendItems.filter(item =>
463            String(item.partnumber) !== String(partnumber)
464        );
465
466        // --- 3. Re-crear SOLO las filas de medidas con los items restantes ---
467        Comparator.updateMeasureRows(Comparator.backendItems);
468
469        // --- 4. Si ya no queda ningún producto, ocultar bloques ---
470        const remaining = document.querySelectorAll('[data-partnumber]');
471
472        if (!remaining.length) {
473            document.querySelectorAll('[data-articles]').forEach(el => el.remove());
474            document.querySelector(Comparator.selectors.noArticles)?.removeAttribute("hidden");
475        }
476
477        Comparator.refreshUI();
478        Comparator.removeEnergyEfficiencyBlock();
479    },
480
481
482    // ============================================================
483    // GET IMAGE FROM CHECKED ITEM
484    // ============================================================
485    getCheckedImage: function (event) {
486        const target = event.originalEvent?.target;
487        if (!target) return null;
488
489        const id = Comparator.normalizeId(target.id);
490        const link = document.querySelector(`a[data-item="${id}"]`);
491        if (!link) return null;
492
493        const image = link.querySelector('.default_image') || link.querySelector('.card-img-top');
494        if (!image) return null;
495
496        return image.dataset.bg || image.dataset.src || null;
497    },
498
499    // ============================================================
500    // UI: ITEMS COUNTER + UPDATE SELECTED IMAGES
501    // ============================================================
502    getItemsCompare: function () {
503        const counter = document.querySelector(Comparator.selectors.quantityItemsSelected);
504
505        if (counter) {
506            counter.textContent = Comparator.items.length + "/5";
507        }
508
509        Comparator.updateComparatorUI();
510        Comparator.updateSelectedImages(Comparator.itemsImg);
511    },
512
513    // ============================================================
514    // ACTION: GO TO COMPARATOR PAGE
515    // ============================================================
516    goCompare: function (e) {
517        e.preventDefault();
518
519        const button = this;
520
521        $(button).attr('loading', true);
522        $.busyLoadFull('show');
523
524        $.ajax({
525            async: true,
526            type: "POST",
527            data: { items: Comparator.items },
528            dataType: "json",
529            url: validateURL.URL() + '/comparator/goCompare',
530            success: function (result) {
531                if (!result.success) return;
532
533                window.location.href = result.url_redirect;
534            },
535        });
536    },
537};
538
539// ============================================================
540// DOCUMENT READY
541// ============================================================
542$(document).ready(function () {
543    Comparator.main();
544});

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.