PageSourceSearch

https://www.magaseek.com/resources/js/ga4/datalayer/common.js?rev=…796ea3e02e6aa78625c45aa01_1790834713_497475

js magaseek.com collected 2026-10-02 03:15:52 UTC 35,372 bytes, 693 lines download raw bytes

1const ITEM_ID = 'item_id';
2const schemaParam = '&schema=' + $('#schema').val();
3const apiUrlBase = $('#api_url_base').val();
4const breadcrumbsUniqueNum = Math.floor(Date.now()/10800000).toString();
5
6// クリックイベント時に実行する関数
7const clickEvent = ({
8    setEventListener: function ( {sourceDataLayerEventName, eventType, targetElement} ) {
9        const pathName = commonEvent.getUrlPathName();
10        switch(pathName) {
11            case 'cart': {
12                
13                // カート画面で発生するイベント
14
15                // add_payment_info 支払い情報を送信したとき => /order/completeへ統合
16
17                // add_shipping_info 配送先情報を送信したとき => /order/completeへ統合
18
19                // remove_from_cart カートから削除 => $().removeCommodity()処理内へ結合
20                break;
21            }
22            case 'category':
23            case 'brand':
24            case 'favorite-brand':
25            case 'in-shop':
26            case 'search': {
27                // 商品一覧から商品をクリックするイベント
28                const targetCommodityElements = this.getTargetElements(targetElement.selectItem);
29                if (targetCommodityElements !== null) {
30                    this.addClickEvent(targetCommodityElements, sourceDataLayerEventName, null, eventType);
31                };
32                break;
33            }
34            case 'commodity': {
35                
36                // // カートに入れるイベント => jquery.commodity-pc.js へ統合
37
38                // お気に入り登録するイベント $(this).addFavorite('{${detail.skuCode}}')へ結合
39
40                break;
41            }
42            case 'other': {
43                // その他
44                break;
45            }
46            default: {
47                return false;
48            }
49        };
50    },
51    getTargetElements: function (targetElement) {
52        if (document.querySelectorAll(targetElement).length > 0) {
53            return document.querySelectorAll(targetElement);
54        } else {
55            return null
56        };
57    },
58    addClickEvent: function (targetArray, sourceDataLayerEventName, commodityCode, eventType) {
59        switch(eventType) {
60            case 'add_to_cart': {
61                targetArray.forEach((target) => {
62                    target.addEventListener('click', (target)=> {
63                        const skuCode = $(target)[0].currentTarget.dataset.skuCode;
64                        const sizeLabel = $(target)[0].currentTarget.dataset.sizeLabel;
65                        this.addToCart(commodityCode, sourceDataLayerEventName, skuCode, sizeLabel);
66                    });
67                });
68                return true;
69            }
70            case 'select_item': {
71                targetArray.forEach((target) => {
72                    target.addEventListener('click', (target)=> {
73                        if(commodityCode !== null) {
74                            this.selectItem(commodityCode, sourceDataLayerEventName);
75                        } else {
76                            const commodityCode = $($(target)[0].currentTarget).find('[data-skucode]').data('skucode');
77                            if (commodityCode.length === 0) return; 
78                            this.selectItem(commodityCode, sourceDataLayerEventName);
79                        }
80                    });
81                });
82                return true;
83            }
84            default: 
85                return false;
86        }
87    },
88    removeFromCart: function (sku, sourceDataLayerEventName, supplierBarCode) {
89        // dataLayerからソース元イベントのobjを探す
90        const data = window.dataLayer || null;
91        const viewItemData = data.filter((data, index, array) => {
92            if(array[index].event == sourceDataLayerEventName) return data;
93        });
94
95        // 指定イベントがない場合(クリックイベントとdataLayerが異なる場合)
96        if (viewItemData.length === 0) return false;
97
98        // 該当objのitemsデータを配列で取得する
99        // itemsの中からクリックした商品コードで詳細情報を取得する
100        // UA限定: item_variantを色名で計測する(view_cart.jsp)ため突合キーに使えない。SKU全体(item_sizesku)で突合する
101        const isUnderarmour = $('#schema').val() === 'underarmour';
102        const clickItem = viewItemData[0].ecommerce.items.filter((item) => {
103            // 突合キー: UAはitem_idにsku_supplierを出すため、別プロパティcommodityCodeを優先(無ければitem_id)
104            if((item.commodityCode || item.item_id) !== sku.slice(0, -3)) return false;
105            return isUnderarmour ? item.item_sizesku === sku : item.item_variant === sku.slice(-3);
106        });
107
108        // dataLayer内に指定の商品IDが取得できなかった場合
109        if (clickItem.length === 0) return false
110
111        // UA限定: item_id を選択SKUの supplier_bar_code(commodity_detail.supplier_bar_code) に上書き
112        if (supplierBarCode && $('#schema').val() === 'underarmour') clickItem[0].item_id = supplierBarCode;
113
114        // availability を明示的に付与(カートから削除する商品は在庫ありのため true 固定)
115        clickItem[0].availability = "true";
116
117        // クリックeventデータのobj生成し、itemsデータを結合
118        const initClickData = {
119          event: 'remove_from_cart',
120          ecommerce: {
121            value: clickItem[0].price,
122            currency: 'JPY',
123            items: clickItem
124          }
125        };
126        
127        // データレイヤーへセット
128        this.pushDataLayer(initClickData);
129
130    },
131    addBuylater: function (sku) {
132        // dataLayerからソース元イベントのobjを探す
133        const data = window.dataLayer || null;
134
135        // クリックeventデータのobj生成し、itemsデータを結合
136        const initClickData = {
137          event: 'add_buylater',
138          commodity_code: sku.slice(0, -3)
139        };
140        
141        // データレイヤーへセット
142        this.pushDataLayer(initClickData);
143
144    },
145    /**
146     * @param otherColorData 色違い商品(SPカート投入モーダル)の差し替え用データ。
147     *        {priceWithoutTax, saleFlg, itemName, skuSupplier, availability}
148     *        主商品のitemを複製する経路(setDefcolorCommodityData)でのみ使う。
149     */
150    addToCart: function (commodityCode, sourceDataLayerEventName, skuCode, sizeLabel, colorName, supplierCode, janCode, supplierBarCode, otherColorData ) {
151        if (typeof commodityCode === 'undefined' || commodityCode === null) return;
152
153        // dataLayerから指定イベントのobjを探す
154        const data = window.dataLayer || null;
155        const viewItemData = data.filter((data, index, array) => {
156            if(array[index].event == sourceDataLayerEventName) return data;
157        });
158
159        // 指定イベントがない場合(クリックイベントとdataLayerが異なる場合)
160        if (viewItemData.length === 0) return false;
161
162        // 該当objのitemsデータを取得する
163        let clickItem = viewItemData[0].ecommerce.items.filter((data, index, array) => {
164            // 突合キー: UAはitem_idにsku_supplierを出すため、別プロパティcommodityCodeを優先(無ければitem_id)
165            if((array[index].commodityCode || array[index].item_id) == commodityCode) return data;
166        });
167
168        // dataLayer内に指定の商品IDが取得できなかった場合
169        if (clickItem.length === 0) clickItem = setDefcolorCommodityData(skuCode, viewItemData[0].ecommerce.items);
170
171        // 属性"data-xxx"から値をセット
172        // 当該ページならこのまま、違う色ならスキップしたい★
173        if (typeof skuCode !== 'undefined' && clickItem.length !== 0) clickItem[0].item_variant = skuCode.slice(-3);
174        if (typeof skuCode !== 'undefined' && clickItem.length !== 0) clickItem[0].item_sizesku = skuCode;
175
176        if (typeof sizeLabel !== 'undefined' && clickItem.length !== 0) clickItem[0].item_sizeLabel = sizeLabel;
177        if (typeof colorName !== 'undefined' && clickItem.length !== 0) clickItem[0].item_colorName = colorName;
178        // if (typeof supplierCode !== 'undefined' && clickItem.length !== 0) clickItem[0].makerID = supplierCode;
179        if (typeof janCode !== 'undefined' && clickItem.length !== 0) clickItem[0].janCode = janCode;
180        // UA限定: size=item_sizeLabel, color=item_colorName を追加。item_variantも色名(item_colorName)に揃える
181        if (clickItem.length !== 0 && $('#schema').val() === 'underarmour') {
182          clickItem[0].size = clickItem[0].item_sizeLabel;
183          clickItem[0].color = clickItem[0].item_colorName;
184          clickItem[0].item_variant = clickItem[0].item_colorName;
185        }
186        // UA限定: item_id は commodity_header.sku_supplier で全ã
186‚¤ãƒ™ãƒ³ãƒˆçµ±ä¸€ã™ã‚‹æ–¹é‡ã®ãŸã‚ã€
187        //         supplier_bar_code(commodity_detail.supplier_bar_code)への上書きは行わない。
188        //         view_item(view_item.jsp)がセットしたsku_supplierをそのまま継承する。
189        //         引数supplierBarCodeは呼び出し元(jquery.commodity-pc.js等)の互換のため残している。
190
191
192        // クリックeventデータのobj生成し、itemsデータを結合
193        const initClickData = {
194          event: 'add_to_cart',
195          ecommerce: {
196            value: clickItem[0].price,
197            currency: 'JPY',
198            items: clickItem
199            }
200         };
201        
202        // データレイヤーへセット
203        this.pushDataLayer(initClickData);
204
205        clickItem = [];
206
207        // 内部関数
208        function getDefcolorIndex(skuCode, domElement) {
209            let result = null;
210            domElement.get().forEach((data, index) => {
211                if (data.children.length === 0) return;
212                let num = index;
213                Array.from(data.children).forEach(data => {
214                    if(data.classList.contains('sku_' + skuCode) == true) {
215                        result = num;
216                    } 
217                });
218            });
219            return result;
220        };
221        function getPrice(skuCode) {
222            if ($('[data-gtm-dom-label="defcolorCommodity"] [data-gtm-dom-label="cart-button-wrap"]').length === 0 ) return;
223            const index = getDefcolorIndex(skuCode, $('[data-gtm-dom-label="defcolorCommodity"] [data-gtm-dom-label="cart-button-wrap"]'));
224            if (typeof index === 'undefined' || index === null) return;
225            if ($('.sku_' + skuCode).parents('[data-gtm-dom-label="defcolorCommodity"]').find('[data-gtm-dom-label="price"]').length === 0) return;
226            let price = $('.sku_' + skuCode).parents('[data-gtm-dom-label="defcolorCommodity"]').find('[data-gtm-dom-label="price"]')[index].innerText;
227            price = parseInt(price.replace(/[^0-9]/g, ''));
228            return price;
229        };
230        function getColorName(skuCode) {
231            if ($('[data-gtm-dom-label="defcolorCommodity"] [data-gtm-dom-label="cart-button-wrap"]').length === 0 ) return null;
232            const index = getDefcolorIndex(skuCode, $('[data-gtm-dom-label="defcolorCommodity"] [data-gtm-dom-label="cart-button-wrap"]'));
233            if (typeof index === 'undefined' || index === null) return;
234            if ($('.sku_' + skuCode).parents('[data-gtm-dom-label="defcolorCommodity"]').find('[data-gtm-dom-label="color"]').length === 0) return;
235            let colorName = $('.sku_' + skuCode).parents('[data-gtm-dom-label="defcolorCommodity"]').find('[data-gtm-dom-label="color"]')[index].innerText;
236            return colorName;
237        };
238        function setDefcolorCommodityData(skuCode, items) {
239            const price = getPrice(skuCode);
240            const jsonItems = JSON.stringify(items);
241            const defcolorItems = JSON.parse(jsonItems);
242            defcolorItems[0].item_id = commodityCode;
243            if (typeof colorName === 'undefined') defcolorItems[0].item_colorName = getColorName(skuCode);
244            if (typeof price !== 'undefined') defcolorItems[0].price = price;
245            // UA限定: 複製元(主商品)の値が残る項目を色違いのものへ差し替える。
246            if ($('#schema').val() === 'underarmour') {
247                // commodityCode: 突合キー。主商品のコードが残ると別商品として扱われるため差し替える。
248                defcolorItems[0].commodityCode = commodityCode;
249                clickEvent.overwriteOtherColorItem(defcolorItems[0], otherColorData);
250            }
251            return defcolorItems;
252        };
253    },
254    /**
255     * SPカート投入モーダルの色違い商品用。主商品のitemを複製したあと、色違いごとに異なる項目を差し替える。
256     * 複製元の値が残ると「今開いている商品」の情報が送られてしまうため、呼び出し元がボタンのdata属性から渡す。
257     * otherColorData = {priceWithoutTax, saleFlg, itemName, skuSupplier, availability}
258     */
259    overwriteOtherColorItem: function (item, otherColorData) {
260        if (!item || !otherColorData) return;
261        // price: 表示価格はショップの税表記タイプで税込になり得るため、税抜専用値を使う
262        const price = parseInt(otherColorData.priceWithoutTax, 10);
263        if (!isNaN(price)) item.price = price;
264        if (otherColorData.saleFlg) item.sale_flg = otherColorData.saleFlg;
265        if (otherColorData.itemName) item.item_name = otherColorData.itemName;
266        if (otherColorData.availability) item.availability = otherColorData.availability;
267        // item_id: UAはsku_supplierで統一する。取得できない場合は複製元の値を残さずcommodityCodeのまま
268        if (otherColorData.skuSupplier) item.item_id = otherColorData.skuSupplier;
269    },
270    /**
271     * UA限定: genderCode(W/M/U/C/B)を item_category(第1階層)のラベルへ変換する。
272     * 対応表は view_item.jsp / purchase.jsp の c:choose と同じ内容。変更時は3箇所を揃えること。
273     * (view_item/purchaseはJSPレンダリング時に値が確定するためJSP側ã
273§åˆ†å²ã—ている)
274     */
275    toUaGenderCategoryName: function (genderCode) {
276        switch (genderCode) {
277            case 'W': return 'ウィメンズ';
278            case 'M': return 'メンズ';
279            case 'U': return 'ユニセックス';
280            case 'C':
281            case 'B': return 'キッズ・ベビー';
282            default: return '';
283        }
284    },
285    /** 税率(%)。下記 toPriceWithoutTax の割り戻しに使う。税率改定時はここだけ変更する。 */
286    TAX_RATE_PERCENT: 10,
287    /**
288     * 表示価格(税込)から税抜価格を割り戻す。UA限定の暫定対応。
289     * 検索一覧・お気に入り一覧は税抜専用値を持たないため、表示価格から逆算する。
290     * 注意: 税込を直接登録している商品(commodity_detail.unit_price_with_tax)や、
291     *       税込計算時の端数切り捨てにより、元の税抜と1円程度ずれる場合がある。
292     *       正確な値が必要になったら、各画面のデータソースに税抜値を追加して置き換えること。
293     */
294    toPriceWithoutTax: function (priceIncludingTax) {
295        const price = parseInt(priceIncludingTax, 10);
296        if (isNaN(price)) return undefined;
297        return Math.round(price / (1 + this.TAX_RATE_PERCENT / 100));
298    },
299    /**
300     * 元イベント(view_item/view_item_list等)がdataLayerに存在しない画面から add_to_cart を送る。
301     * 例: マイページのお気に入り一覧。addToCartは元イベントのitemsから商品を引くため、この画面では使えない。
302     * itemの中身は呼び出し元がDOMから組み立てて渡す。
303     * priceが数値で渡された場合のみ value を出力する(税区分が確定していない金額は送らないため)。
304     */
305    addToCartWithoutSourceEvent: function (item) {
306        return this.pushEcommerceWithoutSourceEvent('add_to_cart', item);
307    },
308    /**
309     * 元イベント(view_item/view_item_list等)がdataLayerに存在しない画面向けの共通送信処理。
310     * itemの中身は呼び出し元がDOM/APIレスポンスから組み立てて渡す。
311     * priceが数値で渡された場合のみ value を出力する(税区分が確定していない金額は送らないため)。
312     */
313    pushEcommerceWithoutSourceEvent: function (eventName, item) {
314        if (!item || !item.item_id) return false;
315
316        // priceはDOM属性由来で文字列になることがあるため数値へ正規化する。取れない場合はキーごと落とす
317        const price = parseInt(item.price, 10);
318        if (isNaN(price)) {
319            delete item.price;
320        } else {
321            item.price = price;
322        }
323
324        const ecommerce = {
325            currency: 'JPY',
326            items: [item]
327        };
328        if (!isNaN(price)) ecommerce.value = price;
329
330        this.pushDataLayer({ event: eventName, ecommerce: ecommerce });
331    },
332    /** @param otherColorData 色違い商品の差し替え用データ。詳細は addToCart / overwriteOtherColorItem を参照。 */
333    addToWishlist: function (skuCode, sourceDataLayerEventName, sizeLabel, janCode, supplierBarCode, colorName, otherColorData) {
334        // dataLayerから指定イベントのobjを探す
335        const data = window.dataLayer || null;
336        const viewItemData = data.filter((data, index, array) => {
337            if(array[index].event == sourceDataLayerEventName) return data;
338        });
339
340        // 指定イベントがない場合(クリックイベントとdataLayerが異なる場合)
341        if (viewItemData.length === 0) return false;
342
343        // 該当objのitemsデータを取得する
344        let clickItem = viewItemData[0].ecommerce.items.filter((data, index, array) => {
345            // 突合キー: UAはitem_idにsku_supplierを出すため、別プロパティcommodityCodeを優先(無ければitem_id)
346            if((array[index].commodityCode || array[index].item_id) == skuCode.slice(0, -3)) return data;
347        });
348
349        // UA限定: 色違い商品(SP商品詳細のカート投入モーダル)はview_itemのitemsに存在せず突合できないため、
350        //         主商品のitemを複製して発火させる。addToCartのsetDefcolorCommodityDataと同等の処置。
351        if (clickItem.length === 0 && $('#schema').val() === 'underarmour') {
352            clickItem = setOtherColorCommodityData(skuCode, viewItemData[0].ecommerce.items, otherColorData);
353        }
354
355        // dataLayer内に指定の商品IDが取得できなかった場合
356        if (clickItem.length ===0) return false;
357
358        // 属性"data-xxx"から値をセット
359        // UA限定: item_variantは色名で計測する。view_item(view_item.jsp)/view_item_list(view_item_list.jsp)で
360        //         色名をセット済みのため、色コード下3桁での上書きをスキップする
361        if (typeof skuCode !== 'undefined' && $('#schema').val() !== 'underarmour') clickItem[0].item_variant = skuCode.slice(-3);
362        if (typeof skuCode !== 'undefined') clickItem[0].item_sizesku = skuCode;
363        if (typeof sizeLabel !== 'undefined') clickItem[0].item_sizeLabel = sizeLabel;
364        // 色違い商品では複製元(主商品)の色名が残るため、呼び出し元から渡された色名で上書きする
365        if (colorName) clickItem[0].item_colorName = colorName;
366        // if (typeof supplierCode !== 'undefined') clickItem[0].makerID = supplierCode;
367        if (typeof janCode !== 'undefined') clickItem[0].janCode = janCode;
368        // UA限定: size=item_sizeLabel を追加(add_to_cartと同仕様)。view_itemはサイズ未選択のためsizeを持たない。
369        if ($('#schema').val() === 'underarmour') clickItem[0].size = clickItem[0].item_sizeLabel;
370        // UA限定: color/item_variantも色名(item_colorName)に揃える(add_to_cartと同仕様)
371        if (colorName && $('#schema').val() === 'underarmour') {
372          clickItem[0].color = clickItem[0].item_colorName;
373          clickItem[0].item_variant = clickItem[0].item_colorName;
374        }
375        // UA限定: item_id を選択SKUの supplier_bar_code(commodity_detail.supplier_bar_code) に上書き
376        if (supplierBarCode && $('#schema').val() === 'underarmour') clickItem[0].item_id = supplierBarCode;
377
378        // クリックeventデータのobj生成し、itemsデータを結合
379        const initClickData = {
380          event: 'add_to_wishlist',
381          ecommerce: {
382            value: clickItem[0].price,
383            currency: 'JPY',
384            items: clickItem
385            }
386         };
387
388        // データレイヤーへセット
389        this.pushDataLayer(initClickData);
390        // 呼び出し元が「送信できたか」を判定できるようにする(元イベントが無い画面でのフォールバック用)
391        return true;
392
393        // 内部関数
394        // 色違い商品用に主商品のitemを複製し、識別情報と価格を色違いのものへ差し替える
395        function setOtherColorCommodityData(skuCode, items, otherColorData) {
396            if (items.length === 0) return [];
397            const otherColorItems = JSON.parse(JSON.stringify(items)).slice(0, 1);
398            const otherColorCommodityCode = skuCode.slice(0, -3);
399            // item_id / commodityCode: 複製元(主商品)のコードが残ると別商品として扱われるため差し替える。
400            //                          item_idはsku_supplierが渡されればover
400writeOtherColorItemで上書きされる。
401            otherColorItems[0].item_id = otherColorCommodityCode;
402            otherColorItems[0].commodityCode = otherColorCommodityCode;
403            clickEvent.overwriteOtherColorItem(otherColorItems[0], otherColorData);
404            return otherColorItems;
405        };
406    },
407    selectItem: function (commodityCode, sourceDataLayerEventName) {
408        // dataLayerから指定イベントのobjを探す
409        const data = window.dataLayer || null;
410        const viewItemData = data.filter((data, index, array) => {
411            if(array[index].event == sourceDataLayerEventName) return data;
412        });
413
414        // 指定イベントがない場合(クリックイベントとdataLayerが異なる場合)
415        if (viewItemData.length === 0) return false;
416
417        // 該当objのitemsデータを取得する
418        const clickItem = viewItemData[0].ecommerce.items.filter((data, index, array) => {
419            // 突合キー: UAはitem_idにsku_supplierを出すため、別プロパティcommodityCodeを優先(無ければitem_id)
420            if((array[index].commodityCode || array[index].item_id) == commodityCode) return data;
421        });
422
423        // dataLayer内に指定の商品IDが取得できなかった場合
424        if (clickItem.length === 0) return false;
425
426        var ga4contentCategory = $('#ga4breadcrumbCategories').val();
427        var ga4searchString = $('#ga4searchString').val();
428        // クリックeventデータのobj生成し、itemsデータを結合
429        const initClickData = {
430          event: 'select_item',
431          ecommerce: {
432            value: clickItem[0].price,
433            currency: 'JPY',
434            items: clickItem
435            },
436            content_category:ga4contentCategory,
437            search_string:ga4searchString
438         };
439        
440        // データレイヤーへセット
441        this.pushDataLayer(initClickData);
442    },
443    pushDataLayer: function (dataLayer) {
444        const jsonDataLayer = JSON.stringify(dataLayer);
445        const objDataLayer = JSON.parse(jsonDataLayer);
446        window.dataLayer = window.dataLayer || [];
447        window.dataLayer.push(objDataLayer);
448    },
449});
450
451
452// CartJsonから商品情報を取得する際に実行する関数
453const viewEventUseCartJson = ({
454    commodityData: {},
455    dataLayerFn: async function (initData, needsItemCategory) {
456        this.commodityData = await setCommodityData();
457        if (!this.commodityData) return;
458        if(needsItemCategory) {
459            await viewEvent.dataLayerGenAsyncBreadcrumbsApi(viewEvent.filter(this.commodityData, ITEM_ID), this.commodityData, initData);
460        } else {
461            viewEvent.pushDataLayer(initData, this.commodityData);
462        }
463    },
464});
465
466// 画面表示時に実行する関数
467const viewEvent = ({
468    commodityData: setCommodityData(),
469    dataLayerFn: function (initData, needsItemCategory) {
470        if(needsItemCategory) {
471            this.dataLayerGenAsyncBreadcrumbsApi(this.filter(this.commodityData, ITEM_ID), this.commodityData, initData);
472        } else {
473            this.pushDataLayer(initData, this.commodityData);
474        }
475    },
476    dataLayerGenAsyncBreadcrumbsApi: async function (commodityIdList, commodityData, initData) {
477        const breadcrumbsSet = new Set();
478        // 突合キー: UAはitem_idにsku_supplierを出すため、commodityCode(view_itemで保持)があれば優先
479        const breadcrumbsIdList = commodityData.map((commodity) => commodity.get('commodityCode') || commodity.get(ITEM_ID));
480        for (const id of breadcrumbsIdList) {
481            breadcrumbsSet.add(await this.fetchBreadcrumbsApi(id, commodityData, initData));
482        };
483
484        // UAはパンくずの3階層目以降を item_category3〜5 に割り当てる(cat1=gender, cat2=アンダーアーマーはJSP側で保持)
485        // 対象イベント: view_item / purchase / view_cart(view_itemと仕様を揃える)
486        // view_cartのitemsは remove_from_cart と「あとで買う」のadd_to_wishlistにも継承される
487        const isUaCategoryShift = ($('#schema').val() === 'underarmour' && initData
488                && (initData.event === 'view_item' || initData.event === 'purchase'
489                        || initData.event === 'view_cart'));
490        const
491            breadcrumbsDataMap = this.setBreadcrumbsDataMap(breadcrumbsSet),
492            dataLayerItemCategory = this.setDataLayerItemCategory(breadcrumbsDataMap, isUaCategoryShift),
493            mergedCommodityData = this.setMergeData(dataLayerItemCategory, commodityData);
494            this.pushDataLayer(initData, mergedCommodityData);
495            if (typeof addShippingInitData !== 'undefined') this.copyDataLayerFn(addShippingInitData, 'purchase', {needsOrderId: false, needsShipping: false});
496            if (typeof addPaymentInitData !== 'undefined') this.copyDataLayerFn(addPaymentInitData, 'purchase', {needsOrderId: false, needsShipping: false});
497    },
498    filter: function (arrayMap, fillterKey) {
499        return arrayMap.map((data) => data.get(fillterKey))
500    },
501    setBreadcrumbsDataMap: function (BreadcrumbsSet) {
502        const resultMap = new Map();
503        Array.from(BreadcrumbsSet).forEach((fetchData, index) => {
504
505            // エラーレスポンスなどを除外
506            if (!fetchData || !fetchData.breadcrumbs || !fetchData.breadcrumbs.length) return;
507
508            const fetchDataList = new Array();
509            const hasBreadcrumbs = Boolean(fetchData.breadcrumbs.length > 0)
510            const ancestors = hasBreadcrumbs ? fetchData.breadcrumbs[0].ancestors : "";
511            const active = hasBreadcrumbs ? fetchData.breadcrumbs[0].category_name_pc : "";
512            if (ancestors.length > 0) {
513                ancestors.forEach(element => {
514                fetchDataList.push(element.category_name_pc);
515                });
516        };
517        if (active.length > 0) {
518            fetchDataList.push(active);
519        };
520        resultMap.set(fetchData.commodity_code, fetchDataList);
521        });
522        return resultMap
523    },
524    setDataLayerItemCategory: function (fetchDataMap, isUaCategoryShift) {
525        const resultMap = new Map();
526        fetchDataMap.forEach((categories, key) => {
527        const fetchDataFnMap = new Map();
528        if (isUaCategoryShift) {
529            // UA(view_item / purchase): パンくずの3階層目以降を item_category3〜5 に割り当て。
530            // 先頭2階層(ブランド/ジェンダー)は cat1=gender, cat2=アンダーアーマー と重複するためスキップ。
531            // item_category{N} ← categories[N-1] (cat3←categories[2] …)。無ければ空文字で cat5 まで必ず出力。
532            for (let level = 3; level <= 5; level++) {
533                fetchDataFnMap.set("item_category" + level, categories[level - 1] || "");
534            }
535        } else {
536            categories.forEach((categoryName, index) => {
537                const num = (index === 0) ? "" : index + 1;
538                fetchDataFnMap.set("item_category" + num, categoryName);
539            });
540        }
541        resultMap.set(key, fetchDataFnMap);
542        });
543        return resultMap;
544    },
545    setMergeData: function (fetchDataLayer, commodityData) {
546        const resultArray = [];
547        commodityData.forEach((commodity) => {
548            // 突合キー: UAはitem_idにsku_supplierを出すため、commodityCode(view_itemで保持)があれば優先
549            const itemId = commodity.get('commodityCode') || commodity.get(ITEM_ID);
550            const fetchData = fetchDataLayer.get(itemId); // 無ければ undefined
551            const mergedMap = fetchData
552                ? new Map([...commodity, ...fetchData])
553                : new Map(commodity); // マージなしでもcommodityをそのまま入れる
554            resultArray.push(mergedMap);
555        });
556        return resultArray;
557    },
558    fetchBreadcrumbsApi: async function (commodityCode, commodityData, initData) {
559        const result = $.ajax({
560            type: 'GET',
561            dataType: 'jsonp',
562            cache : true,
563            timeout : 30000,
564            url: apiUrlBase + '/commodity-breadcrumbs.jsonp?commodity_code=' + commodityCode + schemaParam,
565            jsonpCallback: 'ga4datalayer' + breadcrumbsUniqueNum
566        })
567        .then((res)=> {
568            if (!res.commodity_code.length === 0) {
569                const error = new Error('An error occurred while fetching the data.')
570                error.status = res.status;
571                throw error
572            }
573            return res;
574        })
575        .fail((jqXHR, textStatus, errorThrown) => {
576            const errorText = textStatus + ', ' + errorThrown;
577            let errorPreText = '';
578        
579            switch(jqXHR.status) {
580                case 400: errorPreText = '400 INVALID_TOKEN. '; break;
581                case 401: errorPreText = '401 UNAUTHORIZED. '; break;
582                case 500: errorPreText = '500 INTERNAL_SERVER_ERROR. '; break;
583                case 502: errorPreText = '502 BAD_GATEWAY. '; break;
584                case 404: errorPreText = '404 NOT_FOUND. '; break;
585                default: errorPreText = 'UNHANDLED_ERROR. '; break;
586            }
587        
588            // pushして終了(throwはしない)
589            this.pushDataLayer(initData, commodityData);
590        
591            // 必要に応じてログ出力(本番でもconsole.warnは出したくない場合はここを削除または条件付きで)
592            if (jqXHR.status === 500 || jqXHR.status === 502) {
593                console.warn('Breadcrumbs API error:', jqXHR.status, jqXHR.statusText);
594            }
595        
596            return {
597                error: true,
598                status: jqXHR.status || 0,
599                message: errorPreText + errorText
600            };
601        })
602        .catch((e) => {
603            // JSONPスクリプトの読み込み自体が失敗した場合
604            console.warn('Breadcrumbs API catch (network level error):', e);
605            this.pushDataLayer(initData, commodityData);
606            return {
607                error: true,
608                status: 998,
609                message: 'JSONP request failed (network level)'
610            };
611        });  
612        return result
613    },
614    copyDataLayerFn: function (initData, sourceDataLayerEventName, { needsOrderId = null, needsShipping = null } = {}) {
615        // dataLayerから指定イベントのobjを探す
616        const data = window.dataLayer || null;
617        const viewItemData = data.filter((data, index, array) => {
618            if(array[index].event == sourceDataLayerEventName) return data;
619        }) || null;
620        
621        // dataLayerから指定イベントを探せなければ中断
622        if (viewItemData === null) return;
623
624        // ecommerce情報をマージして、データレイヤー生成へ
625        initData.ecommerce = {...viewItemData[0].ecommerce, ...initData.ecommerce};
626
627        // オーダーIDが不要なら削除
628        if (needsOrderId === false) delete initData.ecommerce.transaction_id;
629
630        // 配送料が不要なら削除
631        if (needsShipping === false) delete initData.ecommerce.shipping;
632
633        this.pushDataLayer(initData);
634    },
635    pushDataLayer: function (dataLayer, commodityMapData = null) {
636        // 商品データがある場合、配列[{map},{map}...]からitemsオブジェクトを生成する
637        const items = (commodityMapData == null) ? null : commodityMapData.map((data) => Object.fromEntries(data));
638
639        // itemsがある場合、データレイヤーにセットする
640        if( items !== null) {
641          dataLayer.ecommerce.items = items;
642        }
643
644        // グローバル変数にセットする
645        window.dataLayer = window.dataLayer || [];
646        window.dataLayer.push(dataLayer);
647    }
648});
649
650const commonEvent = ({
651    getUrlPathName: function () {
652      const lang = $('#siteLanguage').val() ?? 'ja';
653      const pathName = (lang == 'ja') ? window.location.pathname : window.location.pathname.replace('/' + $('#siteLanguage').val() + '/', '/');
654      switch(true) {
655          case /^\/brand\/.*$/.test(pathName):
656            return 'brand';
657          case /^\/cart\/.*$/.test(pathName):
658            return 'cart';
659          case /^\/category\/.*$/.test(pathName):
660            return 'category';
661          case /^\/commodity\/.*$/.test(pathName):
662            return 'commodity';
663          case /(^\/favorite-brand\/.*$|^\/favorite-brand.*$)/.test(pathName):
664            return 'favorite-brand';
665          case /(^\/search.*$|^\/search\?.*$)/.test(pathName):
666            return 'search';
667          case /^\/in-shop\/.*$/.test(pathName):
668            return 'in-shop';
669          default:
670            return 'other';
671      }
672    },
673    getUrlPageName: function (id) {
674      switch(id) {
675          case 'brand':
676            return 'ブランド商品一覧ページ';
677          case 'cart':
678            return 'カート';
679          case 'category':
680            return 'カタログページ';
681          case 'commodity':
682            return '商品詳細ページ';
683          case 'favorite-brand':
684            return 'マイブランド商品一覧ページ';
685          case 'search':
686            return '検索結果ページ';
687          case 'in-shop':
688            return 'ショップ商品一覧ページ';
689          default:
690            return 'その他ページ';
691      }
692    }
693});

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.