PageSourceSearch

https://www.aperol.com/wdf-common/buildingblocks/clicktobuy/script-v5.js

js aperol.com collected 2026-09-24 18:17:30 UTC 17,888 bytes, 479 lines download raw bytes

1class clickToBuy {
2    constructor() {
3        this.structure = document.querySelector('.sn_click_to_buy');
4        this.siteWrapper = document.querySelector('.sn_site_wrapper');
5
6        this.markets = window.CTB_MARKETS || [];
7        this.products = window.CTB_PRODUCTS || [];
8        this.retailers = window.CTB_RETAILERS || {};
9        this.onlineRetailers = window.CTB_ONLINE_RETAILERS_DATA || [];
10        this.selectedLocalization = window.CTB_FIRST_MARKET_INT || null; //NULL only if it's not international, at the beginning.
11        
12        this.dynamicOnlineRetailers = [];
13
14        this.isStrictLocalization = this.markets.length === 1 && this.markets[0].code !== 'en'; // true single market, false international
15
16        this.marketFilterWrapper = document.querySelectorAll('.ctb_market_filter_wrapper');
17        this.productFilterWrapper = document.querySelector('.ctb_product_filter_wrapper');
18
19        this.marketSelects = document.querySelectorAll('.ctb__market_selector');
20        this.productSelects = document.querySelectorAll('.ctb__product_selector');
21
22        this.selectedMarket = null;
23        this.selectedProduct = null;
24
25        this.lastProdIDFromCTA = 0;
26
27        this.currentLoc = this.structure?.getAttribute('data-lang') || null;
28        this.first = true;
29    }
30
31    async fetchOnlineProducts() {
32        if (this.isStrictLocalization || !this.selectedLocalization) return;
33    
34        const cacheKey = this.selectedLocalization;
35
36        try {
37            let data = null;
38            const cached = false;
39            
40            if (cached) {
41                data = JSON.parse(cached);
42            } else {
43                let url = '/wp-json/wdf-data/v1/online-products';
44                url += `?localization=${encodeURIComponent(this.selectedLocalization)}`;
45
46                const response = await fetch(url, {
47                    credentials: 'same-origin'
48                });
49
50                if (!response.ok) {
51                    throw new Error(`HTTP ${response.status}`);
52                }
53
54                data = await response.json();
55
56                this.products = data.products || [];
57                this.retailers = data.retailers || {};
58                this.onlineRetailers = data.online_retailers_data || [];
59                
60                sessionStorage.setItem(
61                    'ctb_online_' + cacheKey,
62                    JSON.stringify(data)
63                );
64            }
65
66            this.dynamicOnlineRetailers = data.online_retailers_data || [];
67
68        } catch (error) {
69            console.error('[C2B FETCH ERROR]', error);
70        }
71    }
72
73    async start(first = true) {
74        this.first = first;
75
76        if (!this.structure) return;
77
78        if (this.isStrictLocalization) {
79            this.marketFilterWrapper.forEach(div => {
80                div.style.display = 'none';
81            });
82
83            this.selectedMarket = this.markets[0];
84        }
85        else {
86            if (!this.selectedLocalization && this.markets.length > 0) {
87                this.selectedLocalization = this.markets[0].code;
88            }
89            await this.fetchOnlineProducts();
90        }
91
92        this.buildMarketSelects();
93        this.buildProductSelects();
94
95        if (first) {
96            this.addEventListeners();
97        }
98    }
99
100    buildMarketSelects() {
101        this.marketSelects.forEach(select => this.buildMarketSelect(select));
102    }
103
104    buildMarketSelect(select) {
105        const menu = select.querySelector('.sn_click_to_buy_select_menu_in')
106        const label = select.querySelector('.sn_click_to_buy_select_label');
107        
108        menu.innerHTML = '';
109
110        if (!this.selectedMarket) {
111            if (!this.isStrictLocalization && this.selectedLocalization) {
112                const match = this.markets.find(
113                    m => m.code === this.selectedLocalization
114                );
115
116                if (match) {
117                    this.selectedMarket = match;
118                    this.selectedLocalization = match.code;
119                }
120            }
121        }
122
123        this.markets.forEach((market, index) => {
124            if(market.code != 'int'){
125                const el = document.createElement('div');
126                el.className = 'sn_click_to_buy_select_menu_i';
127                el.dataset.code = market.code;
128                el.innerText = market.country.label ?? market.label;
129
130                el.addEventListener('click', async () => {
131                    this.selectedMarket = market;
132                    this.selectedLocalization = market.code;
133                    
134                    this.selectedProduct = null;
135                    label.innerText = market.country.label ?? market.label;
136                    label.dataset.code = market.code;
137
138                    select.querySelector('.sn_click_to_buy_select_menu').classList.add('ctb_hide');
139                    
140                    if (!this.isStrictLocalization) {
141                        await this.fetchOnlineProducts();
142                    }
143                    
144                    this.buildProductSelects();
145                    this.populateRetailers();
146                    this.triggerEvent(select);
147                });
148
149                menu.appendChild(el);
150
151                if (!this.selectedMarket && index === 0) {
152                    this.selectedMarket = market;
153                    this.selectedLocalization = market.code;
154                    label.innerText = market.country.label ?? market.label;
155                    label.dataset.code = market.code;
156                }
157            }
158        });
159
160        if (this.selectedMarket) {
161            label.innerText = this.selectedMarket.country.label ?? this.selectedMarket.label;
162            label.dataset.code = this.selectedMarket.code;
163        }
164
165        this.triggerEvent(select);
166    }
167
168    /* ============================
169       PRODUCT SELECT
170    ============================ */
171    buildProductSelects() {
172        this.productSelects.forEach(select => this.buildProductSelect(select));
173    }
174
175    buildProductSelect(select) {
176        const menu = select.querySelector('.sn_click_to_buy_select_menu_in');
177        const label = select.querySelector('.sn_click_to_buy_select_label');
178        
179        const labelAllProducts = this.productFilterWrapper?.getAttribute('data-product-label-all') ?? 'All';
180
181        menu.innerHTML = '';
182
183        this.selectedProduct = null;
184
185        label.innerText = labelAllProducts;
186        label.dataset.id = '0';
187
188        const all = document.createElement('div');
189        all.className = 'sn_click_to_buy_select_menu_i';
190        all.innerText = labelAllProducts;
191        all.dataset.id = '0';
192
193        all.addEventListener('click', () => {
194            this.selectedProduct = null;
195            label.innerText = labelAllProducts;
196            label.dataset.id = '0';
197            select.querySelector('.sn_click_to_buy_select_menu').classList.add('ctb_hide');
198            this.populateRetailers();
199        });
200
201        menu.appendChild(all);
202
203        this.products
204            .filter(p =>
205                !this.selectedMarket?.code ||
206                p.localizations?.includes(this.selectedMarket.code)
207            )
208            .forEach(product => {
209                if(product.sap == -1){
210                    return;
211                }
212                const el = document.createElement('div');
213                el.className = 'sn_click_to_buy_select_menu_i';
214                el.innerText = product.title;
215                el.dataset.id = product.id;
216
217                el.addEventListener('click', () => {
218                    this.selectedProduct = product;
219                    label.innerText = product.title;
220                    label.dataset.id = product.id;
221
222                    select.querySelector('.sn_click_to_buy_select_menu').classList.add('ctb_hide');
223                    this.populateRetailers();
224                });
225
226                menu.appendChild(el);
227        });
228
229        if (this.lastProdIDFromCTA != 0) {
230
231            const product = this.products.find(
232                p => String(p.id) === String(this.lastProdIDFromCTA)
233            );
234
235            if (product) {
236                this.selectedProduct = product;
237                label.innerText = product.title;
238                label.dataset.id = product.id;
239
240                select
241                    .querySelector('.sn_click_to_buy_select_menu')
242                    ?.classList.add('ctb_hide');
243
244                this.populateRetailers();
245            }
246
247        } else {
248            this.populateRetailers();
249        }
250    }
251
252    /* ============================
253       RETAILERS
254    ============================ */
255    retailerMatchesFilters(retailer, productId = null) {
256        if (this.selectedMarket?.code) {
257            if (!retailer.localizations?.includes(this.selectedMarket.code)) {
258                return false;
259            }
260        }
261
262        if (productId && this.selectedProduct) {
263            return productId === this.selectedProduct.id;
264        }
265
266        return true;
267    }
268
269    populateRetailers() {
270        const wrapper = this.structure.querySelector('.retailer_wrapper');
271        let html = '';
272
273        const marketCode = this.selectedMarket?.code || null;
274        if (!this.selectedProduct) {
275            const allRetailersSource = (!this.isStrictLocalization && this.dynamicOnlineRetailers.length)
276                ? this.dynamicOnlineRetailers
277                : this.onlineRetailers;
278                
279            allRetailersSource.forEach(retailer => {
280                if (
281                    marketCode &&
282                    (!retailer.localizations || !retailer.localizations.includes(marketCode))
283                ) {
284                    return;
285                }
286
287                html += this.buildRetailerCard(retailer, false);
288            });
289
290        } else {
291            // CASE PRODUCT FILTERED
292            
293            let productRetailers = [];
294            
295            if (this.retailers[this.selectedProduct.id]) {
296                productRetailers = this.retailers[this.selectedProduct.id];
297            }
298            
299            productRetailers.forEach(retailer => {
300                if (
301                    marketCode &&
302                    (!retailer.localizations || !retailer.localizations.includes(marketCode))
303                ) {
304                    return;
305                }
306
307                html += this.buildRetailerCard(retailer, true);
308            });
309        }
310
311        wrapper.innerHTML = html || '<p class="ctb_empty">No retailers available for this market</p>';
312
313        wrapper.querySelectorAll('body:not(.coachella-lp) .ctb_retailer_cta a').forEach(a => {
314            a.addEventListener('click', () => {
315
316                const marketName = this.selectedMarket?.country?.label ?? this.selectedMarket.label;
317                window.dispatchEvent(new CustomEvent('sn:bb-event', {
318                    detail: {
319                        event: 'buy_now',
320                        action_name: 'click-on-retailer',
321                        bbname: 'click-to-buy',
322                        bbcontext: 'drawer',
323                        content: a.innerText,
324                        language: this.currentLoc,
325                        retailer: a.closest('.ctb_retailer')
326                            ?.querySelector('.ctb_retailer_name')?.innerText,
327                        product: this.selectedProduct?.title || 'All',
328                        market: marketName || '',
329                        destination: a.href
330                        
331                    }
332                }));
333            });
334        });
335    }
336
337    buildRetailerCard(retailer, hasProductLink = true) {
338        const wrapper = this.structure.querySelector('.retailer_wrapper');
339        const ctaLabel = wrapper.getAttribute('data-cta-label') ?? "Buy now"; 
340        const link = hasProductLink && retailer.product_url
341            ? retailer.product_url
342            : retailer.website || retailer.product_url;
343
344        return `
345        <div class="ctb_retailer">
346            ${retailer.logo ? `
347            <div class="ctb_retailer_logo">
348                <img src="${retailer.logo}" alt="${retailer.name}">
349            </div>` : ''}
350            <div class="ctb_retailer_action">
351                <div class="ctb_retailer_name">${retailer.name}</div>
352                <div class="ctb_retailer_cta">
353                    <a href="${link}" target="_blank">
354                        ${ctaLabel}
355                    </a>
356                </div>
357            </div>
358        </div>`;
359    }
360
361    /* ============================
362       OPEN / CLOSE
363    ============================ */
364    open() {
365        this.siteWrapper?.classList.add('_blur');
366        this.structure.classList.add('_open');
367    }
368
369    close() {
370        this.siteWrapper?.classList.remove('_blur');
371        this.structure.classList.remove('_open');
372        localStorage.removeItem('ctb_product');
373    }
374
375    /* ============================
376       EVENTS
377    ============================ */
378    addEventListeners() {
379        document.querySelectorAll('[data-click-to-buy-cta]').forEach(el => {
380            
381            el.addEventListener('click', e => {
382                e.preventDefault();
383                this.lastProdIDFromCTA = el.getAttribute('ctb-product-id');
384                let bbcontext = e.currentTarget.getAttribute('data-bbcontext') ?? 'blocks';
385                let actionName = 'open-drawer-from-page';
386
387                if(bbcontext == 'blocks'){
388                    bbcontext = 'product-page';
389                }
390                else if(bbcontext == 'header'){
391                    actionName = 'open-drawer-from-header';
392                }
393                const ctaText = el.innerText.trim();
394                this.start(false);
395                this.open();
396                if (!document.body.classList.contains('coachella-lp')) {
397                    if (el.getAttribute('data-gtma-click') === 'click-to-buy-cta-party-planner') {
398                        var peopleValue = el.getAttribute('data-people-value') || '';
399                        var perPersonValue = el.getAttribute('data-per-person-value') || '';
400                        window.dispatchEvent(new CustomEvent('sn:bb-event', {
401                            detail: {
402                                'event': 'buy_now',
403                                'action_name': 'open-drawer-from-party-planner',
404                                'bbname': 'click-to-buy-cta',
405                                'bbcontext': 'drawer',
406                                'language': this.currentLoc,
407                                'param1': peopleValue,
408                                'param2': perPersonValue
409                            }
410                        }));
411                    } else {
412
413                        if(this.lastProdIDFromCTA == 0){
414                            window.dispatchEvent(new CustomEvent('sn:bb-event', {
415                                detail: {
416                                    event: 'buy_now',
417                                    action_name: actionName,
418                                    bbname: 'click-to-buy-cta',
419                                    bbcontext: bbcontext,
420                                    language: this.currentLoc,
421                                    event: "click_cta",
422                                    destination: "script",
423                                    content: ctaText,
424                                    is_custom_event: false
425                                }
426                            }));
427                        }
428                        else{
429                            window.dispatchEvent(new CustomEvent('sn:bb-event', {
430                                detail: {
431                                    event: 'buy_now',
432                                    action_name: actionName,
433                                    bbname: 'click-to-buy-cta',
434                                    bbcontext: bbcontext,
435                                    language: this.currentLoc,
436                                    event: "buy_now",
437                                    destination: "script",
438                                    content: ctaText
439                                }
440                            }));
441                        }
442                    }
443                }
444            });
445        });
446
447        document.querySelectorAll('.sn_click_to_buy_close').forEach(el =>
448            el.addEventListener('click', () => this.close())
449        );
450
451        this.marketSelects.forEach(select => {
452            select.querySelector('.sn_click_to_buy_select_toggle')
453                .addEventListener('click', () =>
454                    select.querySelector('.sn_click_to_buy_select_menu').classList.toggle('ctb_hide')
455                );
456        });
457
458        this.productSelects.forEach(select => {
459            select.querySelector('.sn_click_to_buy_select_toggle')
460                .addEventListener('click', () =>
461                    select.querySelector('.sn_click_to_buy_select_menu').classList.toggle('ctb_hide')
462                );
463        });
464    }
465
466    triggerEvent(el) {
467        el.dispatchEvent(new Event('change'));
468    }
469
470    checkCtbParameters() {
471        return new URLSearchParams(window.location.search).get('ctb') === 'true';
472    }
473}
474
475/* ============================
476   START
477============================ */
478const ctb = new clickToBuy();
479ctb.start(true);

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.