PageSourceSearch

https://www.kokkaibinran.jp/base/js/utillity.js?cacheKey=202609b

js kokkaibinran.jp collected 2026-09-27 21:19:28 UTC 121,152 bytes, 2,820 lines download raw bytes

1var Sys = jQuery.noConflict(true);
2
3/**
4 * ユーザーエージェント
5 */
6Sys.fn.userAgent = function(){
7    return window.navigator.userAgent.toLowerCase();
8};
9
10Sys(document).ready(function()
11{
12    // 属性値のサポート確認
13    var supportsInputAttribute = function (attr) {
14        var input = document.createElement('input');
15        return attr in input;
16    };
17
18    // placeholder 属性の処理
19    if(supportsInputAttribute('placeholder')){
20        Sys(".placeholder").hide();
21    }
22
23    Sys("form").submit(function(){
24        // 二重投稿防止
25        Sys(this).find("input[type=submit]").attr("disabled","disabled");
26        window.addEventListener('pageshow', (event) => {
27          if (event.persisted) {
28            const submitButton = this.querySelector("input[type=submit]");
29            if (submitButton) {
30              submitButton.disabled = false;
31            }
32          }
33        });
34    });
35});
36
37// 機能パーツ「会員ログイン」のチェックボックスIDの連番を振り直す
38(function () {
39    function assignAutoLoginIds() {
40        var containers = document.querySelectorAll('.sysFuncMemberLogin');
41        containers.forEach(function (container, index) {
42            var checkbox = container.querySelector('input[id^="is_use_auto_login"]');
43            if (!checkbox) {
44                return;
45            }
46            var newId = 'is_use_auto_login' + index;
47            if (checkbox.id !== newId) {
48                checkbox.id = newId;
49            }
50            container.querySelectorAll('label[for]').forEach(function (label) {
51                var currentFor = label.getAttribute('for');
52                if (!currentFor || !/^is_use_auto_login\d*$/.test(currentFor)) {
53                    return;
54                }
55                if (currentFor !== newId) {
56                    label.setAttribute('for', newId);
57                }
58            });
59        });
60    }
61
62    function runAutoLoginIdAssignment() {
63        assignAutoLoginIds();
64    }
65
66    window.ensureUniqueAutoLoginIds = function ensureUniqueAutoLoginIds() {
67        runAutoLoginIdAssignment();
68    };
69
70    if (document.readyState === 'loading') {
71        document.addEventListener('DOMContentLoaded', runAutoLoginIdAssignment);
72    } else {
73        runAutoLoginIdAssignment();
74    }
75
76    window.addEventListener('onSysAsyncElementLoaded', runAutoLoginIdAssignment);
77})();
78
79/**
80 * アコーディオン
81 */
82Sys(document).ready(function()
83{
84    Sys(document).on('click', '.sysAccordion', function(){
85        Sys(this).next().toggle();
86        Sys(this).toggleClass('sysAccordionHide');
87    });
88});
89
90/**
91 * カルーセル
92 */
93Sys(document).ready(function()
94{
95    // 左右ナビ
96    Sys(".sysCarrouselCtl div:first").click(function(){
97        var parent_left      = Sys(".sysCarrousel").parent().offset().left;
98        var parent_width     = Sys(".sysCarrousel").parent().width();
99        var carrousel_left   = Sys(".sysCarrousel").offset().left;
100        var carrousel_width  = Sys(".sysCarrousel").width();
101        if(carrousel_left != parent_left){
102            var offset = "-=" + parent_width + "px";
103            Sys(".sysCarrousel").animate({right: offset});
104        }
105    });
106    Sys(".sysCarrouselCtl div:last").click(function(){
107        var parent_left      = Sys(".sysCarrousel").parent().offset().left;
108        var parent_width     = Sys(".sysCarrousel").parent().width();
109        var carrousel_left   = Sys(".sysCarrousel").offset().left;
110        var carrousel_width  = Sys(".sysCarrousel").width();
111        if(carrousel_left + carrousel_width != parent_width + parent_left){
112            var offset = "+=" + parent_width + "px";
113            Sys(".sysCarrousel").animate({right: offset});
114        }
115    });
116    window.onresize = function() {
117        Sys(".sysCarrousel").animate({right: "0px"});
118    }
119
120    // 中央ナビ
121    Sys(".sysCarrousel div").each(function(){
122        Sys(".sysCarrouselCtl div:nth-child(2)").append("●");
123    });
124});
125
126/**
127 * 商品一覧の表示方法切り替え
128 */
129Sys(function () {
130    Sys(document).on('click', '.sysItemListDisp [name="view_type"]', function () {
131        changeViewType(Sys(this).val());
132        Sys.cookie('item_list_view_type', Sys(this).val(), {secure:true, sameSite: "lax"});
133    });
134
135    var cookieViewType = Sys.cookie('item_list_view_type');
136    changeViewType(cookieViewType);
137    Sys(".sysItemListDisp [name='view_type']").val([cookieViewType]);
138
139    function changeViewType(viewType) {
140        var target = Sys('.sysThumbnail, .sysList');
141        target.removeClass('sysList');
142        target.removeClass('sysThumbnail');
143        target.addClass(viewType == '1' ? 'sysThumbnail' : 'sysList');
144    }
145});
146
147/**
148 * 住所検索
149 */
150Sys(function() {
151    var Driver = new Array();
152    Driver.searchAddress = function(Event) {
153        var zipcode = Sys(Event.target).parents('form').find('input[name=zipcode]').val();
154        if (zipcode.length == 7) {
155            Sys.post('/api/front/getAddressFromZipcode', { zipcode:zipcode }, function(data) {
156                var param = eval('('+data+')');
157                if (param.pref_state_id == '' ) {
158                    Sys('.SysInvalidZipcodeError').text('無効な郵便番号です。');
159                }
160                if (param.pref_state_id != '') {
161                    Sys('[name=pref_state_id]').val(param.pref_state_id);
162                    Sys('.SysInvalidZipcodeError').text('');
163                }
164                if (param.address1 != '') {
165                    Sys('[name=address1]').val(param.address1);
166                }
167                if (param.address2 != '') {
168                    Sys('[name=address2]').val(param.address2);
169                }
170            });
171        }
172        Event.stopPropagation();
173        Event.preventDefault();
174    };
175
176    Sys('[name=searchAddress]').off('click');
177    Sys('[name=searchAddress]').on('click', Driver.searchAddress);
178
179    Sys('[name=zipcode]').off('keyup');
180    Sys('[name=zipcode]').on('keyup', Driver.searchAddress);
181    Sys('[name=zipcode]').on('input', Driver.searchAddress);
182});
183
184/**
185 * フリーフォーム内住所検索
186 */
187Sys(function() {
188    var Driver = new Array();
189    Driver.getAddress = function(Event) {
190        var zipcode = Sys('[name=getAddress]').siblings('input').val();
191        if (zipcode.length == 7) {
192            Sys.post('/api/front/getAddressFromZipcode', { zipcode:zipcode }, function(data) {
193                var param = eval('('+data+')');
194                if (param.pref_state_id != '') {
195                    Sys('[name$="[item_type]"][value="pref"]').siblings('.sysFormField').children('select').val(param.pref_state_id);
196                }
197                if (param.address1 != '') {
198                    Sys('[name$="[item_type]"][value="address1"]').siblings('.sysFormField').children('input').val(param.address1);
199                }
200                if (param.address2 != '') {
201                    Sys('[name$="[item_type]"][value="address2"]').siblings('.sysFormField').children('input').val(param.address2);
202                }
203            });
204        }
205        Event.stopPropagation();
206        Event.preventDefault();
207    };
208
209    Sys('[name=getAddress]').off('click');
210    Sys('[name=getAddress]').on('click', Driver.getAddress);
211
212    Sys('[name=getAddress]').siblings('input').off('keyup');
213    Sys('[name=getAddress]').siblings('input').on('keyup', Driver.getAddress);
214    Sys('[name=getAddress]').siblings('input').on('input', Driver.getAddress);
215});
216
217/**
218 * フリーフォーム内会社住所検索
219 */
220Sys(function() {
221    var Driver = new Array();
222    Driver.getCompanyAddress = function(Event) {
223        var zipcode = Sys('[name=getCompanyAddress]').siblings('input').val();
224        if (zipcode.length == 7) {
225            Sys.post('/api/front/getAddressFromZipcode', { zipcode:zipcode }, function(data) {
226                var param = eval('('+data+')');
227                if (param.pref_state_id != '') {
228                    Sys('[name$="[item_type]"][value="company_pref"]').siblings('.sysFormField').children('select').val(param.pref_state_id);
229                }
230                if (param.address1 != '') {
231                    Sys('[name$="[item_type]"][value="company_address1"]').siblings('.sysFormField').children('input').val(param.address1);
232                }
233                if (param.address2 != '') {
234                    Sys('[name$="[item_type]"][value="company_address2"]').siblings('.sysFormField').children('input').val(param.address2);
235                }
236            });
237        }
238        Event.stopPropagation();
239        Event.preventDefault();
240    };
241
242    Sys('[name=getCompanyAddress]').off('click');
243    Sys('[name=getCompanyAddress]').on('click', Driver.getCompanyAddress);
244
245    Sys('[name=getCompanyAddress]').siblings('input').off('keyup');
246    Sys('[name=getCompanyAddress]').siblings('input').on('keyup', Driver.getCompanyAddress);
247    Sys('[name=getCompanyAddress]').siblings('input').on('input', Driver.getCompanyAddress);
248});
249
250/**
251 * ログイン状況
252 *
253 * Sys().isMemberLogin()
254 */
255Sys.fn.isMemberLogin = function(){
256    if (Sys("body").is("[data-member-login='true']")) {
257        return true;
258    } else {
259        return false;
260    }
261};
262
263/**
264 * 商品詳細ページ、商品画像(サムネイル)
265 */
266(function () {
267    const SELECTOR = 'img, video';
268    const ItemImageList = {
269        init() {
270            // 最初の画像・動画をサムネイルとして大きく表示
271            document.querySelectorAll('.sysImageList').forEach(list => {
272                const firstMedia = list.querySelector(SELECTOR);
273                if (firstMedia) {
274                    ItemImageList.updateMain(firstMedia);
275                }
276            });
277        },
278
279        updateMain(current) {
280            if (!(current instanceof Element)) return;
281
282            const parent = current.closest('.sysItemImages');
283            if (!parent) return;
284
285            parent.querySelectorAll('li.sysCurrent').forEach(li => li.classList.remove('sysCurrent'));
286            const p = current.parentElement;
287            if (p) p.classList.add('sysCurrent');
288
289            const isVideo = current.tagName && current.tagName.toLowerCase() === 'video';
290            const mainMedia = document.createElement(isVideo ? 'video' : 'img');
291            const src   = current.getAttribute('src')   || '';
292            const title = current.getAttribute('title') || '';
293            const alt   = current.getAttribute('alt') || current.getAttribute('aria-label') || '';
294            const poster = current.getAttribute('poster') || '';
295
296            // img・videoタグ共通の属性対応
297            if (src)   mainMedia.setAttribute('src', src);
298            if (title) mainMedia.setAttribute('title', title);
299            if (!isVideo && alt) {
300                mainMedia.setAttribute('alt', alt);
301            } else if (isVideo && alt) {
302                mainMedia.setAttribute('aria-label', alt);
303            }
304
305            // videoタグの動画再生の属性対応
306            if (isVideo) {
307                mainMedia.muted = true;
308                mainMedia.playsInline = true;
309                mainMedia.autoplay = true;
310                mainMedia.loop = true;
311                mainMedia.preload = 'metadata';
312                mainMedia.setAttribute('muted', '');
313                mainMedia.setAttribute('playsinline', '');
314                mainMedia.setAttribute('autoplay', '');
315                mainMedia.setAttribute('loop', '');
316                mainMedia.setAttribute('preload', 'metadata');
317                // autoplay時のちらつき回避のため、初期表示ではposterを付与しない
318
319                // デフォルトの動画タグのイベントを無視して親のイベントを発火 (モーダル画面表示対応)
320                // コントロール操作が必要になったら外す前提の対応。
321                mainMedia.style.pointerEvents = 'none';
322            }
323
324            const a = document.createElement('a');
325            if (src) a.setAttribute('href', src);
326            a.appendChild(mainMedia);
327
328            const mainWrap = parent.querySelector('.sysMainImage');
329            if (mainWrap) {
330                mainWrap.replaceChildren(a);
331                if (isVideo) {
332                    // append後に確実に再生を開始
333                    mainMedia.play().catch(function () {
334                        // autoplay制限で再生失敗した場合のみposterを後付けして黒画面を避ける
335                        if (poster) {
336                            mainMedia.setAttribute('poster', poster);
337                        }
338                    });
339                }
340            }
341
342            const desc = parent.querySelector('.sysImageDescription');
343            if (desc) {
344                desc.innerHTML = alt || '';
345            }
346        }
347    };
348
349    document.addEventListener('DOMContentLoaded', () => {
350        ItemImageList.init();
351
352        document.addEventListener('click', (e) => {
353            const media = e.target.closest(`.sysImageList ${SELECTOR}`);
354            if (!media) return;
355            ItemImageList.updateMain(media);
356        });
357    });
358})();
359
360/**
361 * 商品詳細ページ、バリエーション画像 → サムネイル&メイン画像の連動切替
362 * .sysImageList 内のサムネイルをクリックさせる形で、既存の選択状態(sysCurrent)更新と
363 * メイン画像差替を同時に行う。thumbnail 表示メソッドの場合のã
363¿å‹•作する。
364 *  - 掛け合わせて表示+カートボタンON:行クリックで切替(カート投入操作は除く)
365 *  - 各項目を分けて表示:画像側ラジオ変更で切替、画像未設定の選択肢で復帰
366 */
367(function () {
368    const VariationMainImage = {
369        _defaultMedia: null,
370        _isShowingVariation: false,
371
372        _findThumbByPath(path) {
373            const list = document.querySelector('.sysImageList');
374            if (!list) return null;
375            const items = list.querySelectorAll('li');
376            for (const li of items) {
377                const media = li.querySelector('img, video');
378                if (media && media.getAttribute('src') === path) {
379                    return media;
380                }
381            }
382            return null;
383        },
384
385        _captureDefault() {
386            const list = document.querySelector('.sysImageList');
387            if (!list) return;
388            const currentLi = list.querySelector('li.sysCurrent');
389            this._defaultMedia = currentLi ? currentLi.querySelector('img, video') : null;
390        },
391
392        set(path) {
393            if (!path) return;
394            const matching = this._findThumbByPath(path);
395            if (!matching) return;
396            if (!this._isShowingVariation) {
397                this._captureDefault();
398                this._isShowingVariation = true;
399            }
400            matching.click();
401        },
402
403        restore() {
404            if (!this._isShowingVariation) return;
405            if (this._defaultMedia) {
406                this._defaultMedia.click();
407            }
408            this._isShowingVariation = false;
409        },
410    };
411
412    document.addEventListener('change', (e) => {
413        if (!(e.target instanceof Element)) return;
414        const radio = e.target.closest('input[type="radio"][data-variation-image-select="1"]');
415        if (!radio) return;
416        const path = radio.getAttribute('data-variation-image-path');
417        if (path) {
418            VariationMainImage.set(path);
419        } else {
420            VariationMainImage.restore();
421        }
422    });
423})();
424
425/**
426 * 商品詳細ページ、バリエーション(各項目を分けて表示)
427 *
428 * 選択中の選択肢名を項目ラベル横(.sysVariationImageSelectLabelValue)に表示する。
429 * 画像選択肢(画像のみで文字を持たない)・テキストカードどちらの選択にも対応。
430 * 初期値はサーバ側で描画済みのため、ここでは変更時の更新のみ行う。
431 */
432(function () {
433    // 取扱なし組み合わせの判定用マップを取得する。
434    function getHandlingMap(root) {
435        if (root._variationHandlingMap !== undefined) {
436            return root._variationHandlingMap;
437        }
438        const raw = root.getAttribute('data-variation-handling-map') || '{}';
439        try {
440            root._variationHandlingMap = JSON.parse(raw);
441        } catch (e) {
442            root._variationHandlingMap = {};
443        }
444        return root._variationHandlingMap;
445    }
446
447    // 組み合わせごとの在庫切れ判定用マップを取得する。
448    function getStockMap(root) {
449        if (root._variationStockMap !== undefined) {
450            return root._variationStockMap;
451        }
452        const raw = root.getAttribute('data-variation-stock-map') || '{}';
453        try {
454            root._variationStockMap = JSON.parse(raw);
455        } catch (e) {
456            root._variationStockMap = {};
457        }
458        return root._variationStockMap;
459    }
460
461    // 在庫マップの値が「在庫切れ」を表しているか判定する。
462    function isSoldOutValue(value) {
463        return value === 1 || value === true || value === '1';
464    }
465
466    // 指定されたバリエーション項目内で現在選択されているradioを取得する。
467    function getCheckedRadio(root, selectIndex) {
468        return root.querySelector('input[type="radio"][data-variation-select-index="' + selectIndex + '"]:checked');
469    }
470
471    // 指定されたバリエーション項目が画面上に存在するか判定する。
472    function hasSelectIndex(root, selectIndex) {
473        return root.querySelector('input[type="radio"][data-variation-select-index="' + selectIndex + '"]') !== null;
474    }
475
476    // 取扱なしマップの値が「取扱なし」を表しているか判定する。
477    function isNotHandledValue(value) {
478        return value === 1 || value === true || value === '1';
479    }
480
481    // 対象radioの選択肢が、相手軸の現在選択に対して取扱なしかを判定する(相手軸連動の斜線表示に使う)。
482    // 在庫切れ(isCombinationOutOfStock)と同じロジックで、選択肢自体は選択可能なまま。
483    function isCombinationNotHandled(root, radio) {
484        const map = getHandlingMap(root);
485        const selectIndex = radio.getAttribute('data-variation-select-index');
486
487        // 相手軸がない(1軸商品)の場合は、その選択肢自体の取扱なしを見る。
488        if (!hasSelectIndex(root, '2')) {
489            const value = map[radio.value] ? map[radio.value][''] : undefined;
490            return value === undefined ? false : isNotHandledValue(value);
491        }
492
493        // 相手軸の現在選択を取得し、その組み合わせの取扱なしフラグを見る。
494        const otherIndex = selectIndex === '1' ? '2' : '1';
495        const otherRadio = getCheckedRadio(root, otherIndex);
496        if (!otherRadio) {
497            return false;
498        }
499        const option1 = selectIndex === '1' ? radio.value : otherRadio.value;
500        const option2 = selectIndex === '1' ? otherRadio.value : radio.value;
501        if (map[option1] === undefined || map[option1][option2] === undefined) {
502            return false;
503        }
504        return isNotHandledValue(map[option1][option2]);
505    }
506
507    // 対象radioの選択肢が、相手軸の現在選択に対して在庫切れかを判定する(相手軸連動の斜線表示に使う)。
508    function isCombinationOutOfStock(root, radio) {
509        const map = getStockMap(root);
510        const selectIndex = radio.getAttribute('data-variation-select-index');
511
512        // 相手軸がない(1軸商品)の場合は、その選択肢自体の在庫を見る。
513        if (!hasSelectIndex(root, '2')) {
514            const value = map[radio.value] ? map[radio.value][''] : undefined;
515            return value === undefined ? false : isSoldOutValue(value);
516        }
517
518        // 相手軸の現在選択を取得し、その組み合わせの在庫切れフラグを見る。
519        const otherIndex = selectIndex === '1' ? '2' : '1';
520        const otherRadio = getCheckedRadio(root, otherIndex);
521        if (!otherRadio) {
522            return false;
523        }
524        const option1 = selectIndex === '1' ? radio.value : otherRadio.value;
525        const option2 = selectIndex === '1' ? otherRadio.value : radio.value;
526        if (map[option1] === undefined || map[option1][option2] === undefined) {
527            return false;
528        }
529        return isSoldOutValue(map[option1][option2]);
530    }
531
532    // 対象radioの選択肢を含む組み合わせが、どの相手軸でも全て取扱なしかを判定する(相手軸の現在選択に依存しない)。
533    function isAllCombinationNotHandled(root, radio) {
534        const map = getHandlingMap(root);
535        const selectIndex = radio.getAttribute('data-variation-select-index');
536
537        if (!hasSelectIndex(root, '2')) {
538            const value = map[radio.value] ? map[radio.value][''] : undefined;
539            return value === undefined ? false : isNotHandledValue(value);
540        }
541
542        // 1つでも取扱ありの組み合わせがあれば取扱なし扱いにしない。
543        let matched = false;
544        if (selectIndex === '1') {
545            const select1Map = map[radio.value];
546            if (select1Map === undefined) {
547                return false;
548            }
549            for (const option2 in select1Map) {
550                matched = true;
551                if (!isNotHandledValue(select1Map[option2])) {
552                    return false;
553                }
554            }
555        } else {
556            for (const option1 in map) {
557                if (map[option1] === undefined || map[option1][radio.value] === undefined) {
558                    continue;
559                }
560                matched = true;
561                if (!isNotHandledValue(map[option1][radio.value])) {
562                    return false;
563                }
564            }
565        }
566        return matched;
567    }
568
569    // 対象radioの選択肢を含む取扱ありの組み合わせが、全て在庫切れかを判定する(相手軸の現在選択に依存しない)。
570    // 1つでも在庫のある組み合わせがあれば在庫切れ扱いにしない。
571    function isAllCombinationOutOfStock(root, radio) {
572        const stockMap = getStockMap(root);
573        const handlingMap = getHandlingMap(root);
574        const selectIndex = radio.getAttribute('data-variation-select-index');
575
576        if (!hasSelectIndex(root, '2')) {
577            const notHandled = handlingMap[radio.value] ? handlingMap[radio.value][''] : undefined;
578            if (notHandled !== undefined && isNotHandledValue(notHandled)) {
579                return false;
580            }
581            const value = stockMap[radio.value] ? stockMap[radio.value][''] : undefined;
582            return value === undefined ? false : isSoldOutValue(value);
583        }
584
585        // 取扱なしは在庫判定の対象外とする。
586        let matched = false;
587        if (selectIndex === '1') {
588            const select1Stock = stockMap[radio.value];
589            if (select1Stock === undefined) {
590                return false;
591            }
592            for (const option2 in select1Stock) {
593                const notHandled = handlingMap[radio.value] ? handlingMap[radio.value][option2] : undefined;
594                if (notHandled !== undefined && isNotHandledValue(notHandled)) {
595                    continue;
596                }
597                matched = true;
598                if (!isSoldOutValue(select1Stock[option2])) {
599                    return false;
600                }
601            }
602        } else {
603            for (const option1 in stockMap) {
604                if (stockMap[option1] === undefined || stockMap[option1][radio.value] === undefined) {
605                    continue;
606                }
607                const notHandled = handlingMap[option1] ? handlingMap[option1][radio.value] : undefined;
608                if (notHandled !== undefined && isNotHandledValue(notHandled)) {
609                    continue;
610                }
611                matched = true;
612                if (!isSoldOutValue(stockMap[option1][radio.value])) {
613                    return false;
614                }
615            }
616        }
617        return matched;
618    }
619
620    // 現在選択中の組み合わせが在庫切れか判定する(カート投入可否の判定に使う)。
621    function isCurrentCombinationOutOfStock(root) {
622        const map = getStockMap(root);
623        const select1Radio = getCheckedRadio(root, '1');
624        if (!select1Radio) {
625            return false;
626        }
627        const option1 = select1Radio.value;
628        const option2 = hasSelectIndex(root, '2')
629            ? (getCheckedRadio(root, '2') ? getCheckedRadio(root, '2').value : '')
630            : '';
631        if (hasSelectIndex(root, '2') && !option2) {
632            return false;
633        }
634        if (map[option1] === undefined || map[option1][option2] === undefined) {
635            return false;
636        }
637        return isSoldOutValue(map[option1][option2]);
638    }
639
640    // 現在選択中の組み合わせが取扱なしか判定する(カート投入可否の判定に使う)。
641    function isCurrentCombinationNotHandled(root) {
642        const map = getHandlingMap(root);
643        const select1Radio = getCheckedRadio(root, '1');
644        if (!select1Radio) {
645            return false;
646        }
647        const option1 = select1Radio.value;
648        const option2 = hasSelectIndex(root, '2')
649            ? (getCheckedRadio(root, '2') ? getCheckedRadio(root, '2').value : '')
650            : '';
651        if (hasSelectIndex(root, '2') && !option2) {
652            return false;
653        }
654        if (map[option1] === undefined || map[option1][option2] === undefined) {
655            return false;
656        }
657        return isNotHandledValue(map[option1][option2]);
658    }
659
660    // 選択中の選択肢名を項目ラベルに反映する。
661    function updateLabels(root) {
662        root.querySelectorAll('.sysVariationImageSelectGroup').forEach((group) => {
663            const label = group.querySelector('.sysVariationImageSelectLabelValue');
664            if (!label) return;
665            const radio = group.querySelector('input[type="radio"]:checked');
666            label.textContent = radio ? (radio.getAttribute('data-option-name') || '') : '';
667        });
668    }
669
670    // 在庫切れ・取扱なしに応じて斜線表示を更新する。選択は可能なまま(disableしない)。
671    // 画像軸(カラー等)は相手軸の選択に依存せず、買える組み合わせが1つも無い場合のみ斜線にする。
672    // 非画像軸(サイズ等)は選択中の相手軸との組み合わせで判定する。
673    function updateDisabledState(root) {
674        root.querySelectorAll('.sysVariationImageSelectGroup input[type="radio"]').forEach((radio) => {
675            const label = radio.closest('.sysVariationImageSelectOption');
676            const isImageSide = radio.closest('.sysVariationImageSelectGroupImage') !== null;
677            const isOutOfStock = isImageSide
678                ? isAllCombinationOutOfStock(root, radio)
679                : isCombinationOutOfStock(root, radio);
680            const isNotHandled = isImageSide
681                ? isAllCombinationNotHandled(root, radio)
682                : isCombinationNotHandled(root, radio);
683
684            radio.setAttribute('data-variation-out-of-stock', isOutOfStock ? '1' : '0');
685            radio.setAttribute('data-variation-not-handled', isNotHandled ? '1' : '0');
686            if (label) {
687                // 斜線(打ち消し)の見た目は在庫切れ・取扱なしの両方に付ける。
688                label.classList.toggle('sysVariationImageSelectOptionDisabled', isOutOfStock || isNotHandled);
689                label.classList.toggle('sysVariationImageSelectOptionOutOfStock', isOutOfStock);
690                label.classList.toggle('sysVariationImageSelectOptionNotHandled', isNotHandled);
691            }
692        });
693
694        updateLabels(root);
695    }
696
697    // 現在選択中の組み合わせが取扱なし・在庫切れの場合、カート投入ボタンを押下不可にする。
698    function updateCartButtonState(root) {
699        const form = root.closest('form') || document;
700        const cartButton = form.querySelector('.sysCartButton');
701        if (!cartButton) {
702            return;
703        }
704        const isNotHandled = isCurrentCombinationNotHandled(root);
705        const isOutOfStock = isCurrentCombinationOutOfStock(root);
706        cartButton.disabled = isNotHandled || isOutOfStock;
707        cartButton.classList.toggle('sysVariationCombinationNotHandled', isNotHandled);
708        cartButton.classList.toggle('sysVariationCombinationOutOfStock', isOutOfStock);
709    }
710
711    // 選択肢のdisabled状態と、現在の組み合わせに応じたカートボタンの状態を整える。
712    function refreshVariationImageSelects(root) {
713        updateDisabledState(root);
714        updateCartButtonState(root);
715    }
716
717    // 初期表示・Ajax差し替え後のバリエーション画像UIを初期化する。
718    function initializeVariationImageSelects(context) {
719        context.querySelectorAll('.sysVariationImageSelects').forEach((root) => {
720            refreshVariationImageSelects(root);
721        });
722    }
723
724    document.addEventListener('change', (e) => {
725        if (!(e.target instanceof Element)) return;
726        const radio = e.target.closest('.sysVariationImageSelectGroup input[type="radio"]');
727
728        if (!radio || !radio.checked) return;
729        const root = radio.closest('.sysVariationImageSelects');
730
731        if (!root) return;
732
733        // 選択肢のdisabled状態(固定判定)とカートボタンの状態を更新する。
734        refreshVariationImageSelects(root);
735    }, true);
736
737    // JS読込時点でDOMContentLoaded済みの場合でも、初期disabled状態を必ず整える。
738    if (document.readyState === 'loading') {
739        document.addEventListener('DOMContentLoaded', () => initializeVariationImageSelects(document));
740    } else {
741        initializeVariationImageSelects(document);
742    }
743
744    // Ajax差し替え後の商品詳細パーツにも同じ初期化を適用する。
745    addEventListener('onSysAsyncElementLoaded', () => initializeVariationImageSelects(document));
746})();
747
748/**
749 * 商品詳細ページ、商品画像(スライド)
750 */
751Sys.event.add(window, "load", function(){
752    var ItemImagesSlide = {};
753    ItemImagesSlide._size = 0;
754    ItemImagesSlide._now = 1;
755    ItemImagesSlide._slide_width = 0;
756    ItemImagesSlide._getCurrentMedia = function(index){
757        var target = Sys(".sysItemImagesSlideList li:eq(" + index + ")");
758        if (!target.length) {
759            return null;
760        }
761        var media = target.find('img, video').eq(0);
762        return media.length ? media : null;
763    };
764    ItemImagesSlide._updateImageDescription = function(){
765        var eq = ItemImagesSlide._now -1;
766        var media = ItemImagesSlide._getCurrentMedia(eq);
767        var description = media ? (media.attr("alt") || media.attr('aria-label') || '') : '';
768        Sys(".sysItemImagesSlideCtl + .sysImageDescription").html(description);
769    };
770    ItemImagesSlide._slide = function(way){
771        var slide = {marginLeft:way + "=" + ItemImagesSlide._slide_width + "px"};
772        Sys(".sysItemImagesSlideList").animate(slide,"normal");
773    }
774    ItemImagesSlide.init = function(){
775        Sys(".sysItemImagesSlideList").each(function(){
776            Sys(this).attr("data-size", Sys("li", this).length);
777            ItemImagesSlide._size = Sys("li", this).length;
778        });
779
780        if (ItemImagesSlide._size == 0) {
781            return null;
782        }
783
784        ItemImagesSlide._slide_width = Sys(".sysItemImagesSlide").width();
785        Sys(".sysItemImagesSlideList").width(ItemImagesSlide._slide_width * ItemImagesSlide._size);
786        var image_width = 100 / ItemImagesSlide._size;
787        Sys(".sysItemImagesSlideList li").width(image_width + "%");
788
789        var height = Sys(".sysItemImagesSlide").height();
790        Sys(".sysItemImagesSlideList li").height(height);
791        Sys(".sysItemImagesSlidePrev").height(height);
792        Sys(".sysItemImagesSlidePrev").css({lineHeight: height + "px"});
793        Sys(".sysItemImagesSlideNext").height(height);
794        Sys(".sysItemImagesSlideNext").css({lineHeight: height + "px"});
795        Sys(".sysItemImagesSlideList li").height(height);
796        Sys(".sysItemImagesSlideList li").css({lineHeight: height + "px"});
797
798        ItemImagesSlide._updateImageDescription();
799
800        if (ItemImagesSlide._now <= 1) {
801            Sys(".sysItemImagesSlidePrev span").addClass("sysItemImagesSlideNone");
802        }
803        if (ItemImagesSlide._size <= 1) {
804            Sys(".sysItemImagesSlideNext span").addClass("sysItemImagesSlideNone");
805        }
806    };
807    ItemImagesSlide.next = function(){
808        if (Sys(this).is(".sysItemImagesSlideNone")) {
809            return null;
810        }
811        ItemImagesSlide._now++;
812        ItemImagesSlide._slide("-");
813        ItemImagesSlide._updateImageDescription();
814        if (ItemImagesSlide._now >= ItemImagesSlide._size) {
815            Sys(this).addClass("sysItemImagesSlideNone");
816        }
817        if (ItemImagesSlide._now > 1) {
818            Sys('.sysItemImagesSlidePrev span').removeClass("sysItemImagesSlideNone");
819        }
820    };
821    ItemImagesSlide.prev = function(){
822        if (Sys(this).is(".sysItemImagesSlideNone")) {
823            return null;
824        }
825        ItemImagesSlide._now--;
826        ItemImagesSlide._slide("+");
827        ItemImagesSlide._updateImageDescription();
828        if (ItemImagesSlide._now <= 1) {
829            Sys(this).addClass("sysItemImagesSlideNone");
830        }
831        if (ItemImagesSlide._now < ItemImagesSlide._size) {
832            Sys('.sysItemImagesSlideNext span').removeClass("sysItemImagesSlideNone");
833        }
834    };
835    ItemImagesSlide.init();
836    Sys(window).resize(ItemImagesSlide.init);
837    Sys(document).on('click', '.sysItemImagesSlidePrev span', ItemImagesSlide.prev);
838    Sys(document).on('click', '.sysItemImagesSlideNext span', ItemImagesSlide.next);
839});
840
841/**
842 * セッションストレージに値をセット
843 * @param {string} key セッションキー
844 * @param {object} saveObject 保持したい内容
845 */
846function setSessionStorage(key, saveObject) {
847    if (window.sessionStorage) {
848        try {
849            sessionStorage.setItem(key, JSON.stringify(saveObject));
850        } catch (err) {
851            // エラーが発生した場合は処理をスルーする
852            // safariのプライベートブラウズがlocalStorageに対応していないため
853            console.error(err);
854        }
855    }
856}
857
858/**
859 * セッションストレージから値を取得
860 * @param {string} key セッションキー
861 * @return {string|boolean} 値、もしくはfalseを返す
862 */
863function getSessionStorage(key) {
864    var savedObject = sessionStorage[key];
865    if (savedObject === undefined) {
866        return false;
867    }
868    savedObject = JSON.parse(savedObject);
869    if (savedObject.expire === false || savedObject.expire > (new Date).getTime()) {
870        return savedObject.value;
871    } else {
872        sessionStorage.removeItem(key);
873        return false;
874    }
875}
876
877/**
878 * レンタル商品
879 */
880Sys(function() {
881    //レンタルカレンダ関連の親classがなければレンタル画面とは見なさない
882    if (Sys('.sysRentalSchedules').length === 0) {
883        return;
884    }
885
886    var sysRentalSchedulesDays = [];
887
888    // セッションから選択日付を取得
889    var sessionSelectedDate = getSessionStorage("sessionSelectedDate");
890    var selectedDate;
891    if (sessionSelectedDate) {
892        selectedDate = sessionSelectedDate;
893    }
894
895    // セッションから現在日付修正月数を取得(セッションから取得できなければdata-monthmodifyから取得)
896    var sessionCurrentMonthModify = getSessionStorage("sessionCurrentMonthModify");
897    var currentMonthModify = sessionCurrentMonthModify ? sessionCurrentMonthModify : Sys("[data-monthmodify]").attr("data-monthmodify");
898
899    // セッションから選択日付を取得できた場合
900    if (selectedDate) {
901
902        // カレンダーを再取得
903        getRentalCalendar();
904        // レンタル開始日を取得してフォーム値にセット(<input type="hidden">の項目)
905        Sys("[name='rental_start_date']").val(selectedDate);
906        // 一旦カレンダー上から、レンタル開始日の属性を削除
907        Sys(".sysRentalSchedulesCalendar td").removeAttr('data-rental-select-date');
908        // クリックした日付にレンタル開始日の属性をセット
909        Sys(this).attr('data-rental-select-date', 'selected');
910        // 「発送日」等の項目を表示
911        Sys(".sysRentalSendDay, .sysRentalReachDay, .sysRentalUseDays, .sysRentalReturnDay").show();
912        // カレンダー上の日付に項目ごとのクラスのセットや、「発送日:2016年03月18日」等の日付を変更
913        setRentalSchedules(selectedDate);
914        updateRetailPrice();
915    }
916
917    Sys(".sysCalendarLoading").hide();
918
919    // レンタル利用可能日をクリックした時
920    Sys(document).on("click", ".sysRentalSchedulesValid:not(.reserved)", function() {
921        // クリックした日付(レンタル開始日)を取得
922        selectedDate = Sys(this).attr("data-rentalstartdate");
923        var sessionExpire = 1800;
924        var sessionUpperLimit = (new Date).getTime() + (sessionExpire * 1000);
925        // レンタル開始日をセッションに保存
926        var sessionSelectedDateObj = {
927            expire: sessionUpperLimit,
928            value:  selectedDate
929        };
930        setSessionStorage("sessionSelectedDate", sessionSelectedDateObj);
931        // レンタル表示月 - 当月の値をセッションに保存
932        var sessionCurrentMonthModifyObj = {
933            expire: sessionUpperLimit,
934            value: currentMonthModify
935        };
936        setSessionStorage("sessionCurrentMonthModify", sessionCurrentMonthModifyObj);
937        // レンタル開始日を取得してフォーム値にセット(<input type="hidden">の項目)
938        Sys("[name='rental_start_date']").val(selectedDate);
939        // 一旦カレンダー上から、レンタル開始日の属性を削除
940        Sys(".sysRentalSchedulesCalendar td").removeAttr('data-rental-select-date');
941        // クリックした日付にレンタル開始日の属性をセット
942        Sys(this).attr('data-rental-select-date', 'selected');
943        // 「発送日」等の項目を表示
944        Sys(".sysRentalSendDay, .sysRentalReachDay, .sysRentalUseDays, .sysRentalReturnDay").show();
945        // カレンダー上の日付に項目ごとのクラスのセットや、「発送日:2016年03月18日」等の日付を変更
946        setRentalSchedules(selectedDate);
947        updateRetailPrice();
948    });
949
950    // 選択されているレンタル開始日をクリックされた場合、表示リセット
951    Sys(document).on("click", "[data-rental-select-date='selected']", reset);
952
953    // 延長日が変更された時
954    Sys(document).on("change", "[name='rental_extend_date']", function(){
955        // レンタル開始日を取得
956        selectedDate = Sys("[name='rental_start_date']").val();
957        // レンタル開始日を指定していれば、カレンダー上の日付の色変更や「発送日:2016年03月18日」等の日付を変更
958        if (selectedDate) {
959            setRentalSchedules(selectedDate);
960        }
961        updateRetailPrice();
962    });
963
964    /**
965     * 次月ボタン押下イベント
966     */
967    Sys(document).on("click", ".sysNextCalendar", function(){
968        currentMonthModify = parseInt(currentMonthModify) + 1;
969        getRentalCalendar();
970    });
971
972    /**
973     * 前月ボタン押下イベント
974     */
975    Sys(document).on("click", ".sysPrevCalendar", function(){
976        currentMonthModify = parseInt(currentMonthModify) - 1;
977        getRentalCalendar();
978    });
979
980    /**
981     * レンタルカレンダーの年月プルダウン変更イベント
982     */
983    Sys(document).on("change", ".sysMonthSelect", function(){
984        if (Sys(this).val()) {
985            currentMonthModify = Sys(this).val();
986            getRentalCalendar();
987        }
988    });
989
990    function number_format(number) {
991        number = new String(number).replace(/,/g, "");
992        while (number != (number = number.replace(/^(-?\d+)(\d{3})/, "$1,$2")));
993        return number;
994    }
995
996    /**
997     * カレンダーを非同期で取得
998     */
999    function getRentalCalendar() {
1000
1001        // カレンダーを隠してLoadingを表示
1002        Sys(".sysRentalSchedulesCalendars").hide();
1003        Sys(".sysCalendarLoading").fadeIn();
1004
1005        // カレンダーを非同期で取得
1006        var url = "/api/front/item/getRentalScheduleCalendar";
1007        url += "?item_no=" + Sys(".sysRentalSchedules [data-itemno]").attr("data-itemno");
1008        url += "&month_modify=" + currentMonthModify;
1009        Sys.get(url, function(html){
1010
1011            // Loadingを隠してカレンダーを表示
1012            Sys(".sysCalendarLoading").hide();
1013            Sys(".sysRentalSchedulesCalendars").fadeIn();
1014
1015            // sysRentalSchedulesCalendars配下を一度削除して差し替える
1016            var parent = Sys(".sysRentalSchedulesCalendars").parent();
1017            Sys(parent).children('.sysRentalSchedulesCalendars').remove();
1018            Sys(parent).append(Sys(html));
1019
1020            // レンタル開始日を取得
1021            selectedDate = Sys("[name='rental_start_date']").val();
1022            // レンタル開始日を指定していれば、カレンダー上の日付の色変更や「発送日:2016年03月18日」等の日付を変更
1023            if (selectedDate) {
1024                // カレンダーの日付にレンタル開始日の属性をセット
1025                Sys("[data-rentalstartdate='" + selectedDate + "']").attr('data-rental-select-date', 'selected');
1026                setRentalSchedules(selectedDate);
1027            }
1028
1029            // プルダウンを選択
1030            Sys('.sysMonthSelect').val(currentMonthModify);
1031        });
1032    }
1033
1034    /**
1035     * レンタルのスケジュールをブラウザにセットする
1036     * @param {string} selectedDate レンタル開始日(例:2016-03-17)
1037     */
1038    function setRentalSchedules(selectedDate) {
1039        // 一旦レンタル用のクラスを全て外す
1040        Sys(".sysRentalSchedulesCalendar td").removeClass('sysRentalSchedulesSend sysRentalSchedulesReach sysRentalSchedulesDays sysRentalSchedulesReturn sysRentalSchedulesExtend');
1041
1042        // レンタル開始日のクラス名を取得
1043        var rentalStartDateClassName = Sys("[data-rentalstartdate='" + selectedDate + "']").attr("class");
1044        // レンタル開始日がレンタル不可日であればレンタルのスケジュールをブラウザからクリアする(セッションは残す)
1045        if (rentalStartDateClassName === "sysRentalSchedulesInvalid" || rentalStartDateClassName === "sysRentalSchedulesReserved") {
1046            reset();
1047        } else {
1048            /** レンタル開始日がレンタル可能日であればレンタルのスケジュールをブラウザにセットする */
1049            // 「○年○月○日」に変換
1050            var selectedDateAry = selectedDate.split("-");
1051            var selectedDateToString = selectedDateAry[0] + "年" + selectedDateAry[1] + "月" + selectedDateAry[2] + "日";
1052
1053            /** 発送日 */
1054            // 設定を取得(例:3日前なら「-3」)
1055            var sendDay = parseInt(Sys("[data-rentalsendday]").attr("data-rentalsendday"), 10);
1056            // 発送日を設定している場合
1057            if (sendDay) {
1058                var sendDateObj = createDateObj(selectedDate, sendDay);
1059                // 「発送日:」に年月日をセット
1060                Sys(".sysRentalSendDay .sysRentalDate").html(toStringDate(sendDateObj));
1061                // クラスをセット
1062                var sendDate = sendDateObj.year + "-" + sendDateObj.month + "-" + sendDateObj.day;
1063                Sys("[data-rentalstartdate='" + sendDate + "']").addClass("sysRentalSchedulesSend");
1064            }
1065
1066            /** お届け予定日 */
1067            // 設定を取得(例:1日前なら「-1」)
1068            var reachDay = parseInt(Sys("[data-rentalreachday]").attr("data-rentalreachday"), 10);
1069            // お届け予定日を設定している場合
1070            if (reachDay) {
1071                var reachDateObj = createDateObj(selectedDate, reachD
1071ay);
1072                // 「お届け予定日:」に年月日をセット
1073                Sys(".sysRentalReachDay .sysRentalDate").html(toStringDate(reachDateObj));
1074                // クラスをセット
1075                var reachDate = reachDateObj.year + "-" + reachDateObj.month + "-" + reachDateObj.day;
1076                Sys("[data-rentalstartdate='" + reachDate + "']").addClass("sysRentalSchedulesReach");
1077            }
1078
1079            /** ご利用日 */
1080            var usePeriodToString;
1081            var useDate;
1082            // ご利用日の日数を取得(例:1日なら「1」)
1083            var useDayPeriod = parseInt(Sys("[data-rentalusedays]").attr('data-rentalusedays'), 10);
1084            // クラスをセット(レンタル開始日の日付だけ)
1085            Sys("[data-rentalstartdate='" + selectedDate + "']").addClass("sysRentalSchedulesDays");
1086            // レンタル不可日(商品個別)のクラス名を定義
1087            var rentalSchedulesReserved = "sysRentalSchedulesReserved sysRentalSchedulesDays";
1088            // レンタル不可日(選択不可日)のクラス名を定義
1089            var rentalSchedulesInvalid = "sysRentalSchedulesInvalid sysRentalSchedulesDays";
1090            // ご利用日が1日を超える場合
1091            if (useDayPeriod > 1) {
1092                // ご利用期間の最終日を取得
1093                var useEndDateObj = createDateObj(selectedDate, useDayPeriod - 1);
1094                // 「○年○月○日〜△年△月△日」にする
1095                usePeriodToString = selectedDateToString + "~" + toStringDate(useEndDateObj);
1096                var useDateObj;
1097                // クラスをセット
1098                for (i = 1; i < useDayPeriod; i++) {
1099                    useDateObj = createDateObj(selectedDate, i);
1100                    useDate = useDateObj.year + "-" + useDateObj.month + "-" + useDateObj.day;
1101                    Sys("[data-rentalstartdate='" + useDate + "']").addClass("sysRentalSchedulesDays");
1102                }
1103            }
1104            // ご利用日が1日だけならレンタル開始日のみご利用日とする
1105            usePeriodToString = usePeriodToString || selectedDateToString;
1106            // 1日の場合開始日、複数日の場合最終日を代入
1107            useDate = useDate || selectedDate;
1108            // ご利用日を「ご利用日:」にセット
1109            Sys(".sysRentalUseDays .sysRentalDate").html(usePeriodToString);
1110
1111            // レンタル延長期間を取得(例:2日延長なら「2」)
1112            var rentalExtendPeriod = parseInt(Sys("[name='rental_extend_date']").val(), 10);
1113            // 延長する場合
1114            if (rentalExtendPeriod) {
1115                var extendDate;
1116                var extendDateObj;
1117                // クラスをセット
1118                for (i = 1; i <= rentalExtendPeriod; i++) {
1119                    extendDateObj = createDateObj(useDate, i);
1120                    extendDate = extendDateObj.year + "-" + extendDateObj.month + "-" + extendDateObj.day;
1121                    Sys("[data-rentalstartdate='" + extendDate + "']").addClass("sysRentalSchedulesExtend");
1122                }
1123            }
1124
1125            /** 返却日 */
1126            // 設定を取得(例:3日後なら「3」)
1127            var returnDay = parseInt(Sys("[data-rentalreturnday]").attr('data-rentalreturnday'), 10);
1128            // 返却日を設定している場合
1129            if (returnDay) {
1130                var returnDateObj = createDateObj(selectedDate, rentalExtendPeriod + returnDay);
1131                // 「返却日:」に年月日をセット
1132                Sys(".sysRentalReturnDay .sysRentalDate").html(toStringDate(returnDateObj));
1133                // クラスをセット
1134                var returnDate = returnDateObj.year + "-" + returnDateObj.month + "-" + returnDateObj.day;
1135                Sys("[data-rentalstartdate='" + returnDate + "']").addClass("sysRentalSchedulesReturn");
1136            }
1137        }
1138    }
1139
1140    /**
1141     * 受け取った日数で加減算した年月日をオブジェクトで返す
1142     * @param {string} selectedDay ご利用開始日(例:2016-03-28)
1143     * @param {int} addDay 加減算する日数(例:2日前なら「-2」、1日後なら「1」)
1144     * @return {{year: number, month: string, day: string}}
1145     */
1146    function createDateObj(selectedDay, addDay) {
1147        // ご利用開始日を['年', '月', '日']の配列にする
1148        var selectedDayArr = selectedDay.split("-");
1149        // 日付を加減算した後、Dateオブジェクトに変換
1150        var dateObj = modifyDate(parseInt(selectedDayArr[0], 10), parseInt(selectedDayArr[1], 10),
1151                        parseInt(selectedDayArr[2], 10), parseInt(addDay, 10));
1152        // Dateオブジェクトから年を4桁で取得
1153        var year  = dateObj.getFullYear();
1154        // Dateオブジェクトから月を2桁で取得(1月なら「01」)
1155        var month = ('00' + (dateObj.getMonth() + 1)).slice(-2);
1156        // Dateオブジェクトから日を2桁で取得(1日なら「01」)
1157        var day  = ('00' + dateObj.getDate()).slice(-2);
1158
1159        return {year:year, month:month, day:day};
1160    }
1161
1162    /**
1163     * 年月日のオブジェクトを「○年○月○日」に変換
1164     * @param {object} dayObj {{year: number, month: string, day: string}}
1165     * @returns {string}
1166     */
1167    function toStringDate(dayObj) {
1168        return dayObj.year + "年" + dayObj.month + "月" + dayObj.day + "日";
1169    }
1170
1171    // @TODO parseInt()に基数を設定する
1172    function updateRetailPrice() {
1173        if (Sys(".sysRetailPriceValue").length == 0) {
1174            return null;
1175        }
1176
1177        var new_price;
1178        var new_price_with_tax;
1179        var selectedDate = Sys("[data-rental-select-date='selected']");
1180        if (selectedDate.attr("data-rentalprice")) {
1181            new_price = parseInt(selectedDate.attr("data-rentalprice"));
1182            new_price_with_tax = parseInt(selectedDate.attr("data-rentalpricewithtax"));
1183        } else {
1184            new_price = parseInt(Sys("[data-originprice]").attr("data-originprice"));
1185            new_price_with_tax = parseInt(Sys("[data-originpricewithtax]").attr("data-originpricewithtax"));
1186        }
1187
1188        // レンタル期間の延長料金
1189        var extend_price = parseInt(Sys("[name='rental_extend_date'] option:selected").attr("data-rentalextendfee"));
1190        var extend_price_with_tax = parseInt(Sys("[name='rental_extend_date'] option:selected").attr("data-rentalextendfeewithtax"));
1191
1192        new_price = new_price + extend_price;
1193        new_price = number_format(new_price);
1194
1195        new_price_with_tax = new_price_with_tax + extend_price_with_tax;
1196        new_price_with_tax = number_format(new_price_with_tax);
1197
1198        Sys(".sysRetailPrice").each(function(){
1199            var target = Sys(this).find(".sysRetailPriceValue");
1200            var price_text = target.html();
1201            if (target.attr("data-taxincludednonincludeddisplaysetting") == 1) {
1202                // 税込時、税抜価格と税込価格の両方を表示
1203                target.html(price_text.replace(/[0-9,]+/, new_price));
1204                var price_with_tax_text = target.find(".sysRetailPriceTaxIncluded").html();
1205                target.find(".sysRetailPriceTaxIncluded").html(price_with_tax_text.replace(/[0-9,]+/, new_price_with_tax));
1206            } else if (target.attr("data-taxincludeddisplaysetting") == 1) {
1207                // 税抜
1208                target.html(price_text.replace(/[0-9,]+/, new_price));
1209            } else {
1210                // 税込
1211                target.html(price_text.replace(/[0-9,]+/, new_price_with_tax));
1212            }
1213        });
1214    }
1215
1216    /**
1217     * 選択状態のレンタル開始日をリセット
1218     */
1219    function reset() {
1220        var rentalStartDateClassName = Sys("[data-rentalstartdate='" + selectedDate + "']").attr("class");
1221        // 選択した日付がレンタル可能な日だった場合はセッションから選択日・カレンダー表示月を削除
1222        if (rentalStartDateClassName === "sysRentalSchedulesValid sysRentalSchedulesDays") {
1223            sessionStorage.removeItem("sessionSelectedDate");
1224            sessionStorage.removeItem("sessionCurrentMonthModify");
1225        }
1226        // 「発送日」等の項目を非表示、日付も空にする
1227        Sys(".sysRentalSendDay, .sysRentalReachDay, .sysRentalUseDays, .sysRentalReturnDay").hide();
1228        Sys(".sysRentalScheduleInfo .sysRentalDate").text("");
1229        // レンタル開始日の属性を外す
1230        Sys(this).removeAttr('data-rental-select-date');
1231        // レンタルスケジュール関連のクラスを外す
1232        Sys(".sysRentalSchedulesCalendar td").removeClass("sysRentalSchedulesSend sysRentalSchedulesReach sysRentalSchedulesDays sysRentalSchedulesReturn sysRentalSchedulesExtend");
1233        Sys("[name='rental_start_date']").val("");
1234        Sys(".sysRetailPrice").each(function(){
1235            var origin_price = Sys("[data-originprice]").attr("data-originprice");
1236            origin_price = number_format(origin_price);
1237            var origin_price_with_tax = Sys("[data-originpricewithtax]").attr("data-originpricewithtax");
1238            origin_price_with_tax = number_format(origin_price_with_tax);
1239            var target = Sys(this).find(".sysRetailPriceValue");
1240            if (Sys(this).find(".sysRetailPriceValue").length > 0) {
1241                var price_text = target.html();
1242                if (target.attr("data-taxincludednonincludeddisplaysetting") == 1) {
1243                    target.html(price_text.replace(/[0-9,]+/, origin_price));
1244                    var price_with_tax_text = target.find(".sysRetailPriceTaxIncluded").html();
1245                    target.find(".sysRetailPriceTaxIncluded").html(price_with_tax_text.replace(/[0-9,]+/, origin_price_with_tax));
1246                } else if (target.attr("data-taxincludeddisplaysetting") == 1) {
1247                    target.html(price_text.replace(/[0-9,]+/, origin_price));
1248                } else {
1249                    target.html(price_text.replace(/[0-9,]+/, origin_price_with_tax));
1250                }
1251            }
1252
1253        });
1254    }
1255
1256    // month に1足す必要あり
1257    function modifyDate(year, month, day, add) {
1258        var dt = new Date(year, month - 1, day);
1259        var baseSec = dt.getTime();
1260        var addSec = add * 86400000;
1261        var targetSec = baseSec + addSec;
1262        dt.setTime(targetSec);
1263        return dt;
1264    }
1265});
1266
1267/**
1268 * 商品バリエーション初期処理
1269*/
1270Sys(function () {
1271    addEventListener('onSysAsyncElementLoaded', () => {
1272      document.querySelectorAll(".sysCartInForm").forEach(function (variationForm) {
1273        variationForm.addEventListener("change", () => {
1274          const combineForm = variationForm.querySelector("#variation");
1275          if (combineForm !== null && !isNaN(combineForm.value) && combineForm.value !== undefined) {
1276            variationForm.dispatchEvent(new CustomEvent('variationUpdated', { detail: { variationNo: combineForm.value } }));
1277            return;
1278          }
1279
1280          const tableForm = variationForm.querySelector("input[id='variationTable']:checked");
1281          if (tableForm !== null && !isNaN(tableForm.value) && tableForm.value !== undefined) {
1282            variationForm.dispatchEvent(new CustomEvent('variationUpdated', { detail: { variationNo: tableForm.value } }));
1283          }
1284
1285          const option1 = variationForm.querySelector("#variation_select1, input[type='radio'][name='variation[selects][select1OptionNo][value]']:checked");
1286          const option2 = variationForm.querySelector("#variation_select2, input[type='radio'][name='variation[selects][select2OptionNo][value]']:checked");
1287          if (option1 !== null && !isNaN(option1.value) && option1.value !== undefined) {
1288            var option1Value = option1.value;
1289            var option2Value = null;
1290            if (option2 !== null && !isNaN(option2.value) && option2.value !== undefined) {
1291              var option2Value = option2.value;
1292            }
1293            variationForm.dispatchEvent(new CustomEvent('variationUpdated', {
1294              detail: {
1295                variationSelect1: option1Value,
1296                variationSelect2: option2Value,
1297              }
1298            }));
1299          }
1300        });
1301
1302        variationForm.addEventListener('variationUpdated', (event) => {
1303          getVariationPrice(
1304            event.detail.variationNo,
1305            event.detail.variationSelect1,
1306            event.detail.variationSelect2,
1307          ).then((variationPrices) => {
1308            setVariationPriceIfSuccess(variationPrices);
1309            variationForm.dispatchEvent(new CustomEvent("variationPriceUpdated"));
1310          })
1311            .catch((error) => {
1312              console.error(error);
1313              setVariationPriceIfError();
1314            });
1315        });
1316      });
1317    })
1318
1319  const getVariationPrice = async (variationNo, optionNo1, optionNo2) => {
1320    var payload = [];
1321    if (variationNo) {
1322      payload = {
1323        variation_no: variationNo,
1324      };
1325    } else if (optionNo2) {
1326      payload = {
1327        "option_no1": optionNo1,
1328        "option_no2": optionNo2,
1329      };
1330    } else {
1331      payload = {
1332        "option_no1": optionNo1,
1333      };
1334    }
1335
1336    const endpoint = variationNo ? "/api/front/item/getVariationPriceByVariationNo" : "/api/front/item/getVariationPriceByOptionNo";
1337    const query = new URLSearchParams(payload);
1338    const uri = endpoint + "?" + query;
1339    const response = await fetch(
1340      uri,
1341      {
1342        method: "GET",
1343        headers: {
1344          "X-Requested-With": "XMLHttpRequest"
1345        },
1346      }
1347    ).then((response) => {
1348      if (!response.ok) return null;
1349      return response.json();
1350    });
1351
1352    return {
1353      yamiPrice: response.yami_price,
1354      retailPrice: response.retail_price,
1355      resolvedRetailPrice: response.resolved_retail_price,
1356      taxIncludedYamiPrice: response.tax_included_yami_price,
1357      taxIncludedRetailPrice: response.tax_included_retail_price,
1358      taxIncludedResolvedRetailPrice: response.tax_included_resolved_retail_price,
1359      isYamiCategory: response.is_yami_category,
1360      isUseYamiPrice: response.is_use_yami_price,
1361      isUseTradeRate: response.is_use_trade_rate,
1362      discountRate: response.discount_rate,
1363      isUseVariationPrice: response.is_use_variation_price,
1364    };
1365  }
1366
1367    /**
1368     * Ajax通信成功時にバリエーション価格をセットする
1369     * @param resp
1370     */
1371    function setVariationPriceIfSuccess(resp) {
1372        var yamiPrice = resp.yamiPrice;
1373        var retailPrice = resp.retailPrice;
1374        var resolvedRetailPrice = resp.resolvedRetailPrice;
1375        var taxIncludedYamiPrice = resp.taxIncludedYamiPrice;
1376        var taxIncludedRetailPrice = resp.taxIncludedRetailPrice;
1377        var taxIncludedResolvedRetailPrice = resp.taxIncludedResolvedRetailPrice;
1378        // isYamiCategory は商品単位での判定のため、選択バリエーションでの闇市適用可否は isUseYamiPrice で判断する
1379        var isUseYamiPrice = resp.isUseYamiPrice;
1380        var isUseTradeRate = resp.isUseTradeRate;
1381        var discountRate = resp.discountRate;
1382        var isUseVariationPrice = resp.isUseVariationPrice;
1383
1384        if (isUseVariationPrice === '0') {
1385            // バリエーション価格を使用しない場合は何もしない
1386            return false;
1387        }
1388
1389        // 販売価格表示文言
1390        var retailPriceMsg = "";
1391        // 闇市価格表示文言
1392        var yamiPriceMsg = "";
1393        // 取引種別適用後価格表示文言
1394        var resolvedRetailPriceMsg = "";
1395
1396        var $retailPriceElem = Sys(".sysRetailPriceValue");
1397        var taxation = Sys("[name='data-taxation']").val();
1398
1399        if ($retailPriceElem.attr("data-taxincludednonincludeddisplaysetting") === "1") {
1400            // 税込時に税抜価格と税込価格の両方を表示する
1401            retailPriceMsg =
1402                formatNumber(retailPrice) + "円"
1403                + "<span class ='sysRetailPriceTaxIncluded'>(税込 " + formatNumber(taxIncludedRetailPrice + "円)</span>");
1404            yamiPriceMsg =
1405                formatNumber(yamiPrice) + "円"
1406                + "<span class ='sysRetailPriceTaxIncluded'>(税込 " + formatNumber(taxIncludedYamiPrice + "円)</span>");
1407            resolvedRetailPriceMsg =
1408                formatNumber(resolvedRetailPrice) + "円"
1409                + "<span class ='sysRetailPriceTaxIncluded'>(税込 " + formatNumber(taxIncludedResolvedRetailPrice + "円)</span>");
1410        } else if ($retailPriceElem.attr("data-taxincludeddisplaysetting") === "1") {
1411            // 税抜表示する
1412            retailPriceMsg = formatNumber(retailPrice) + "円" + taxation;
1413            yamiPriceMsg = formatNumber(yamiPrice) + "円" + taxation;
1414            resolvedRetailPriceMsg = formatNumber(resolvedRetailPrice) + "円" + taxation;
1415        } else {
1416            // 税込表示する
1417            retailPriceMsg = formatNumber(taxIncludedRetailPrice) + "円" + taxation;
1418            yamiPriceMsg = formatNumber(taxIncludedYamiPrice) + "円" + taxation;
1419            resolvedRetailPriceMsg = formatNumber(taxIncludedResolvedRetailPrice) + "円" + taxation;
1420        }
1421
1422        Sys(".sysVariationRetailPriceValue").html(retailPriceMsg);
1423        // 適用後価格(闇市 > 取引種別)。それ以外は空欄に戻す
1424        if (isUseYamiPrice) {
1425            Sys(".sysVariationYamiPriceValue").html(yamiPriceMsg);
1426            Sys(".sysYamiPriceName").html("→適用後価格");
1427        } else if (isUseTradeRate) {
1428            Sys(".sysVariationYamiPriceValue").html(resolvedRetailPriceMsg);
1429            Sys(".sysYamiPriceName").html("→ " + discountRate + "%引き");
1430        } else {
1431            Sys(".sysVariationYamiPriceValue").html("");
1432            Sys(".sysYamiPriceName").html("");
1433        }
1434
1435        if (isUseYamiPrice) {
1436            Sys("span[data-retail-price]").attr("data-retail-price", taxIncludedYamiPrice);
1437        } else if (isUseTradeRate) {
1438            // 取引種別の掛け率/個別価格を反映した適用後単価
1439            // 注: 既存の通常価格分岐は同名 hidden input の参照セレクタが誤っており(name='data-tax-after-discount'
1440            // を要素属性として走査している)、くれなã
1440„カスタム設定の判定が常に false になる既知バグがある。
1441            // 本分岐のみ正しいセレクタに揃える。
1442            if (Sys("input[name='data-tax-after-discount']").val() === "1") {
1443                // くれないカスタム設定
1444                Sys("span[data-retail-price]").attr("data-retail-price", resolvedRetailPrice);
1445            } else {
1446                Sys("span[data-retail-price]").attr("data-retail-price", taxIncludedResolvedRetailPrice);
1447            }
1448        } else {
1449            if (Sys("input[data-tax-after-discount]").attr("data-tax-after-discount") === "1") {
1450                // くれないカスタム設定
1451                Sys("span[data-retail-price]").attr("data-retail-price", retailPrice);
1452            } else {
1453                Sys("span[data-retail-price]").attr("data-retail-price", taxIncludedRetailPrice);
1454            }
1455        }
1456    }
1457
1458    /**
1459     * Ajax通信失敗時にバリエーション価格をセットする
1460     */
1461    function setVariationPriceIfError() {
1462        Sys(".sysVariationRetailPriceValue").html("バリエーション価格の取得に失敗しました。");
1463        Sys(".sysVariationYamiPriceValue").html("バリエーション価格の取得に失敗しました。");
1464    }
1465
1466    /**
1467     * 数値をカンマ区切りする
1468     * @param num
1469     * @return {string | *}
1470     */
1471    function formatNumber(num) {
1472        num = num.toString().replace(/,/g, "");
1473        while (num !== (num = num.replace(/^(-?\d+)(\d{3})/, "$1,$2")));
1474        return num;
1475    }
1476});
1477
1478/**
1479 * 商品詳細ページ、価格まわりの諸々
1480 */
1481Sys(function() {
1482    addEventListener('onSysAsyncElementLoaded', () => {
1483        if (isVariationSelected() && Sys("input[data-use-variation-price]").attr("data-use-variation-price") === "1") {
1484            // 初期表示時にバリエーションが選択されている かつ バリエーション価格を使用する場合
1485            getDiscountPrice(1);
1486        }
1487
1488        document.querySelectorAll(".sysCartInForm").forEach((cartInForm) => {
1489            if (cartInForm.querySelector(".sysSubtotalPrice") !== null) {
1490                subTotal(Sys(cartInForm));
1491            }
1492        });
1493
1494        if (Sys('#sysItemDetail #sysGetPoint').length) {
1495            getPoint(this);
1496        }
1497
1498        if (isVariationSelected() && Sys('.variation_stock_quantity').length) {
1499            // 選択状態の時だけ、在庫数を表示
1500            getVariationStock(this, '');
1501        }
1502
1503        document.querySelectorAll(".sysCartInForm").forEach((cartInForm) => {
1504            Sys(cartInForm).change(function (e) {
1505                if (Sys(".sysDiscountedItemPrice").length || Sys(".sysDiscountPrice").length) {
1506                    getDiscountPrice(Sys("[name='num']", this).val());
1507                }
1508
1509                if (cartInForm.querySelector(".sysSubtotalPrice") !== null) {
1510                    subTotal(this);
1511                }
1512
1513                if (Sys('#sysItemDetail #sysGetPoint').length) {
1514                    getPoint(this);
1515                }
1516
1517                if (Sys('.variation_stock_quantity').length) {
1518                    getVariationStock(this, e.target.id);
1519                }
1520            });
1521        });
1522    });
1523
1524
1525    /**
1526     * バリエーション選択肢が選択されているか
1527     * @return {*}
1528     */
1529    function isVariationSelected() {
1530        return Sys('#variation').val()
1531            || Sys('#variation_select1').val() || Sys('#variation_select2').val()
1532            || Sys("input[type='radio'][name='variation[selects][select1OptionNo][value]']:checked").val()
1533            || Sys("input[type='radio'][name='variation[selects][select2OptionNo][value]']:checked").val()
1534            || Sys("input[id='variationTable']:checked").val();
1535    }
1536
1537    /**
1538     * 割引価格を取得
1539     */
1540    function getDiscountPrice(num) {
1541        Sys('.sysDiscountedItemPrice > span').html("Loading...");
1542        Sys('.sysDiscountPrice > span').html("Loading...");
1543        if (num === "undefined" || isNaN(num)) {
1544            num = Sys("[name='num']", this).val();
1545        }
1546        Sys.ajax({
1547            type: "GET",
1548            url: "/api/front/item/getDiscountItemPrice",
1549            data: {
1550                "itemNo": Sys("[name='itemNo']").val(),
1551                'num': num,
1552            },
1553            dataType: "json",
1554            success: function(resp) {
1555                var discountPriceText = resp.discount_price_text;
1556                var discountPrice = resp.discount_price;
1557                Sys(".sysCartInForm").each(function() {
1558                    Sys('.sysDiscountPrice > span').html(discountPriceText);
1559                    Sys('#sysDiscountPrice').attr('data-discount-price', discountPrice);
1560                });
1561                discountedPrice(num);
1562            },
1563            error: function() {
1564                Sys('#sysDiscountPrice').html("割引価格の取得に失敗しました。");
1565            }
1566        });
1567    }
1568
1569    /**
1570     * 商品詳細ページ、割引後価格反映
1571     */
1572    function discountedPrice(num) {
1573      var retail_price = Sys("[data-retail-price]").attr("data-retail-price");
1574      var taxation = Sys('[name=data-taxation]').val();
1575      var price = retail_price;
1576      Sys("option:selected, input:checked", this).each(function(){
1577          var adjust = 0;
1578          var rate = Sys(this).attr("data-rate");
1579          var length_or_persent = Sys(this).attr("data-length-or-persent");
1580          var calculation_type = Sys(this).attr("data-calculation-type");
1581          var excluded_selects = Sys(this).attr("data-excluded-selects");
1582          if (length_or_persent == 1) {
1583              var other_option_price = 0;
1584              if(calculation_type == 1) {
1585                  Sys(this).parents(".sysCartInForm").find("option:selected, input:checked").each(function(){
1586                      var other_option_rate = Sys(this).attr("data-rate");
1587                      var other_option_length_or_persent = Sys(this).attr("data-length-or-persent");
1588                      var other_option_calculation_type = Sys(this).attr("data-calculation-type");
1589                      var other_option_select_no = Sys(this).attr("data-select-no");
1590
1591                      if(other_option_calculation_type == 0) {
1592                          var flag = true;
1593                          if (excluded_selects){
1594                              var excluded_selects_obj = Sys.parseJSON(excluded_selects);
1595                              for(var i = 0; i < excluded_selects_obj.length; i++){
1596                                  if(excluded_selects_obj[i] == other_option_select_no){
1597                                      flag = false;
1598                                      break;
1599                                  }
1600                              }
1601                          }
1602                          if(flag) {
1603                              if (other_option_length_or_persent == 1) {
1604                                  other_option_price += parseInt(retail_price) * parseInt(other_option_rate) / 100;
1605                              } else {
1606                                  other_option_price += parseInt(other_option_rate);
1607                              }
1608                          }
1609                      }
1610                  });
1611              }
1612              adjust = Math.ceil((parseInt(retail_price) + parseInt(other_option_price)) * parseInt(rate) / 100);
1613          } else {
1614              adjust = rate;
1615          }
1616          if (typeof(adjust) != "undefined") {
1617              price = parseInt(price) + parseInt(adjust);
1618          }
1619      });
1620      var data_discount = document.getElementById("sysDiscountPrice")
1621      if (!data_discount) {
1622          data_discount = document.getElementById("sysHiddenDiscountPrice");
1623      }
1624      if (data_discount) {
1625          price = price * num;
1626          price = price - data_discount.getAttribute("data-discount-price");
1627          price = Math.floor(price / num);
1628      }
1629
1630      price = addTaxAfterDiscount(data_discount, price, price, num);
1631      price = String(price).replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1,');
1632      if (Sys(".sysRetailPrice").is(":contains('円')")) {
1633              Sys(".sysDiscountedItemPrice > span").html(price + "円" + taxation);
1634      } else {
1635              Sys(".sysDiscountedItemPrice > span").html( "&yen;" + price + taxation);
1636      }
1637    }
1638
1639
1640    function addTaxAfterDiscount(data_discount, price, single_item_price, num){
1641        if (Sys('[name=data-tax-after-discount]').val() == 1) {
1642            price = parseInt(price);
1643            var tax_percentage = Sys('[name=data-tax-percentage]').val()  / 100;
1644            if (data_discount) {
1645                var tax_price = Math.floor(price * tax_percentage);
1646            } else {
1647                var tax_price = Math.floor(single_item_price * tax_percentage);
1648                tax_price = tax_price * num;
1649            }
1650            price = price + tax_price;
1651        }
1652        return price;
1653    }
1654
1655    /**
1656     * 獲得予定ポイントを算出
1657     */
1658    function getPoint(element) {
1659        var data = {
1660            'itemNo' : Sys("[name='itemNo']", element).val(),
1661            'num': Sys('[name=num]', element).val(),
1662            'options': getOptions(element),
1663            'variationViewType': Sys('[name=variation_view_type]', element).val(),
1664            'variations': getVariations(element),
1665        };
1666        Sys.ajax({
1667            'type': 'GET',
1668            'url': '/api/front/item/getPoint',
1669            'data': data,
1670        }).done(function (resp) {
1671            var point = resp.point;
1672            if (point > 0) {
1673                var message = resp.is_login
1674                    ? '獲得ポイント:<span class="get_point">' + point + '</span>'
1675                    : '<span class="message">会員登録して購入すると<span class="get_point">' + point + 'ポイント</span>獲得できます。</span>';
1676                Sys(element).find('#sysGetPoint').html(message);
1677            } else {
1678                Sys(element).find('#sysGetPoint').hide();
1679            }
1680        }).fail(function () {
1681            Sys(element).find('#sysGetPoint').html('<span class="get_point">獲得予定ポイントを表示できません</span>');
1682        });
1683    }
1684
1685    /**
1686     * 商品詳細ページ、小計反映
1687     */
1688    if (Sys("#sysSubtotal").length) {
1689        Sys(".sysCartInForm").each(function() {
1690            var self = this;
1691            // フォームの変更を検知して、小計を再計算
1692            Sys(self).on('click', '.sysRentalSchedulesValid', function() {
1693                subTotal(self);
1694            });
1695        });
1696    }
1697
1698    /**
1699     * 小計を算出
1700     */
1701    function subTotal(element) {
1702        Sys.ajax({
1703            type: 'GET',
1704            url: '/api/front/template/subtotal',
1705            contentType: 'application/json',
1706            data: {
1707                'itemNo': Sys("[name='itemNo']", element).val(),
1708                'num': Sys('[name=num]', element).val() || 0,
1709                'options': getOptions(element),
1710                'variationViewType': Sys('[name=variation_view_type]', element).val(),
1711                'variations': getVariations(element),
1712                'rentalStartDate': Sys("[data-rental-select-date='selected']").attr("data-rentalstartdate"),
1713                'rentalExtend': Sys("[name='rental_extend_date'] option:selected").val(),
1714            },
1715        }).done(function (resp) {
1716            var title = Sys(element).find("[data-subtotal-name]").attr("data-subtotal-name");
1717            Sys(element).find(".sysSubtotalPrice").html(title + resp['subTotal']);
1718        }).fail(function (resp) {
1719            Sys(element).find(".sysSubtotalPrice").html('小計の取得に失敗しました。');
1720        }).always(function (resp) {
1721            //
1722        });
1723    }
1724
1725    /**
1726     * 選択中のバリエーション在庫数を取得
1727     */
1728    function getVariationStock(element, id) {
1729        // 初期表示時はイベント発火のid要素は空指定
1730        if ((id == '' && Sys("[name='variation_view_type']").val() !== '2')
1731        || (Sys("[name='variation_view_type']").val()=='0' && id=='variation')
1732        || (Sys("[name='variation_view_type']").val()=='1' && (id =='variation_select1' || id=='variation_select2'))
1733        || (Sys("[name='variation_view_type']").val()=='2' && id=='variationTable')
1734        ) {
1735            Sys('.sysStockQuantity > .variation_stock_quantity').html("Loading...");
1736        }
1737
1738        var variationNoTable = Sys("input[id='variationTable']:checked").val();
1739        var data = {
1740            'item_no': Sys("[name='itemNo']", element).val(),
1741            'option_no1': Sys('#variation_select1').val() || Sys("input[type='radio'][name='variation[selects][select1OptionNo][value]']:checked").val(),
1742            'option_no2': Sys('#variation_select2').val() || Sys("input[type='radio'][name='variation[selects][select2OptionNo][value]']:checked").val(),
1743            'variation_no': Sys('#variation').val(),
1744            'variation_no_table': variationNoTable == undefined ? 0 : variationNoTable,
1745            'variation_view_type': Sys('[name=variation_view_type]', element).val(),
1746        };
1747
1748        Sys.ajax({
1749            'type': 'GET',
1750            'url': '/api/front/item/getVariationStockQuantity',
1751            'data': data,
1752        }).done(function (resp) {
1753            var json = resp;
1754            if (json.length == 0) {
1755                Sys('.variation_stock_quantity').html("在庫の取得に失敗しました。");
1756            }
1757            if (json.variationStockQuantity <= json.stockDisplayQuantity) {
1758                if (json.variationStockQuantity === 0) {
1759                    Sys('.variation_stock_quantity').html("×");
1760                } else {
1761                    Sys('.variation_stock_quantity').html("△");
1762                }
1763            } else {
1764                if (json.stockDisplayMethod === 2) {
1765                    Sys('.variation_stock_quantity').html("○");
1766                } else if (json.stockDisplayMethod === 1) {
1767                    Sys('.variation_stock_quantity').html(json.variationStockQuantity);
1768                }
1769            }
1770        }).fail(function (resp) {
1771            Sys('.variation_stock_quantity').html("在庫の取得に失敗しました。");
1772        });
1773    }
1774
1775
1776
1777
1778
1779    /**
1780     * 選択中のオプション選択肢を取得
1781     */
1782    function getOptions(element) {
1783        var options = [];
1784        Sys('#sysSelections option:selected, #sysSelections input:checked', element).each(function () {
1785            options.push(Sys(this).val());
1786        });
1787        return options;
1788    }
1789
1790    /**
1791     * 選択中のバリエーション選択肢を取得
1792     */
1793    function getVariations(element) {
1794        var variationViewType = Sys('[name=variation_view_type]', element).val();
1795        var variations = [];
1796        // 各項目を掛け合わせて表示
1797        if (variationViewType == 0) {
1798            if (Sys('#variation', element).val()) {
1799                // カートに入れるボタン非表示
1800                variations.push(Sys('#variation', element).val());
1801            } else {
1802                // カートに入れるボタン表示
1803                // 新デザイン(バリエーション画像)はコンテナidが #sysVariationWithImage のため両方を対象にする
1804                variations.push(Sys('#sysVariation input[name="variation[value]"], #sysVariationWithImage input[name="variation[value]"]', element).val());
1805            }
1806        }
1807        // 各項目を分けて表示
1808        if (variationViewType == 1) {
1809            Sys('#sysVariation option:selected, #sysVariation input[type="radio"][name^="variation[selects]"]:checked, #sysVariationWithImage option:selected, #sysVariationWithImage input[type="radio"][name^="variation[selects]"]:checked', element).each(function () {
1810                variations.push(Sys(this).val());
1811            });
1812        }
1813        // 表組み(テーブル)表示
1814        if (variationViewType == 2) {
1815            if (Sys('#sysVariation input[name="variation[value]"]:checked, #sysVariationWithImage input[name="variation[value]"]:checked', element).val() != null) {
1816                variations.push(Sys('#sysVariation input[name="variation[value]"]:checked, #sysVariationWithImage input[name="variation[value]"]:checked', element).val());
1817            } else {
1818                variations.push(Sys('#sysVariation input[name="variation[value]"], #sysVariationWithImage input[name="variation[value]"]', element).val());
1819            }
1820        }
1821        return variations;
1822    }
1823
1824    /**
1825     * 商品詳細ページの対応
1826     * 定期購入・闇市を考慮していないので要注意
1827     */
1828    if (Sys("#sysSubtotal").length || Sys(".sysRetailPrice").length) {
1829        Sys(".sysCartInForm").change(addSetPrice);
1830    }
1831
1832    function addSetPrice() {
1833        var item_no = Sys("[name='itemNo']").val();
1834        var num = Sys("[name='num']", this).val();
1835        var taxation = "(税込)";
1836        Sys.ajax({
1837            type: "GET",
1838            url: "/api/front/item/getItemSetPrice",
1839            data: {
1840                "item_no": item_no,
1841                "num": num,
1842            },
1843            dataType: "json",
1844            error: function() {
1845            },
1846            success: function(data) {
1847                if (data.length === 0) {return;}
1848                if (data.sub_item_price == 'current') {
1849                    if (Sys('.sysSetRetailPrice')[0] ||
1850                        Sys('.sysSetSubtotalPrice')[0]) {
1851                        Sys(".sysSetSubtotalPrice").remove();
1852                        Sys(".sysSetRetailPrice").remove();
1853                    }
1854                    return;
1855                }
1856
1857                price = String(data.sub_total_price).replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1,');
1858                item_price  = String(data.sub_item_price).replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1,');
1859                var set_price = "セット価格" + Sys("[data-subtotal-name]").attr("data-subtotal-name");
1860                var set_item_price = "セット単価";
1861
1862                if (Sys(".sysRetailPrice").is(":contains('円')")) {
1863                        set_item_price += item_price + "円";
1864                        set_price += price + "円";
1865                } else {
1866                        set_item_price += "&yen;" + item_price;
1867                        set_price += "&yen;" + price;
1868                }
1869
1870                set_item_price += taxation;
1871                set_price += taxation;
1872
1873                if (Sys('.sysSetRetailPrice')[0] ||
1874                    Sys('.sysSetSubtotalPrice')[0]) {
1875                    Sys(".sysSetSubtotalPrice").html(set_price);
1876                    Sys(".sysSetRetailPrice").html(set_item_price);
1877                } else {
1878                    Sys(".sysSubtotalPrice").after("<span class='sysSetSubtotalPrice'>" + set_price + "</span>");
1879                    Sys(".sysRetailPriceValue").after("<span class='sysSetRetailPrice'>" + set_item_price + "</span>");
1880                }
1881            }
1882        });
1883    }
1884});
1885
1886/**
1887 * 商品詳細ページの受取方法選択
1888 */
1889Sys(function () {
1890    addEventListener('onSysAsyncElementLoaded', () => {
1891        var receiveStoreDetails = window.receiveStoreDetails;
1892        var hasReceiveStoreGroup = window.hasReceiveStoreGroup;
1893
1894        var receiveStoreGroupSelect = Sys("#receiveStoreGroup");
1895        var receiveStoreSelect = Sys("#receiveStoreSelect");
1896        var receiveDateSelect = Sys("#receiveDateSelect");
1897        var receiveTimeSelect = Sys("#receiveTimeSelect");
1898        var storeDescription = Sys("#storeDescription");
1899
1900        var selectedReceiveStoreId = null;
1901        var sessionCartReceiveDate = '';
1902        var sessionCartReceiveTime = '';
1903
1904        if (window.sessionCartReceiveConditions) {
1905            selectedReceiveStoreId = window.sessionCartReceiveConditions.selectedReceiveStoreId;
1906            sessionCartReceiveDate = window.sessionCartReceiveConditions.selectedReceiveDate;
1907            sessionCartReceiveTime = window.sessionCartReceiveConditions.selectedReceiveTime;
1908        }
1909
1910        // セレクトボックスの初期表示
1911        var placeholderOption = '<option value="">--選択してください--</option>';
1912
1913        onReceiveStoreGroupChange();
1914        onReceiveStoreChange();
1915        onReceiveDateChange();
1916        onReceiveTimeChange();
1917
1918        if (selectedReceiveStoreId) {
1919            // カート内に店舗受取の商品がある場合
1920            displayMatchedReceiveStore();
1921            return;
1922        }
1923
1924        if (hasReceiveStoreGroup) {
1925            initReceiveStoreGroupSelect();
1926        } else {
1927            initReceiveStoreSelect();
1928        }
1929
1930        // カート内で店舗が選択済みの店舗を表示する
1931        function displayMatchedReceiveStore() {
1932            var matchedStore = null;
1933
1934            receiveStoreDetails.some(function (group) {
1935                return group.receiveStores.some(function (store) {
1936                    if (store.id == selectedReceiveStoreId) {
1937                        matchedStore = store;
1938                        return true;
1939                    }
1940                    return false;
1941                });
1942            });
1943
1944            var container = Sys('.selectedReceiveStore');
1945            container.empty();
1946
1947            if (matchedStore) {
1948                // カートで選択済みの店舗で購入できる商品なら店舗名を表示
1949                var label = Sys('<div>').addClass('sysReceiveStoreLabel').text('店舗名:');
1950                var name = Sys('<span>').addClass('sysSelectedReceiveStoreName').text(matchedStore.name);
1951                var hiddenInput = Sys('<input type="hidden">').attr('name', 'receiveStoreId').val(matchedStore.id);
1952
1953                container.append(label).append(name).append(hiddenInput);
1954
1955                updateReceiveDateOptions(matchedStore, sessionCartReceiveDate);
1956                updateReceiveTimeOptions(matchedStore, sessionCartReceiveTime);
1957                updateStoreDescription(matchedStore);
1958            } else {
1959                var warning = Sys('<div>').html(
1960                    'カートで選択中の店舗では受け取れない商品です<br>変更したい場合はカート内の商品を削除してください'
1961                );
1962                container.append(warning);
1963            }
1964        }
1965
1966        // 店舗グループのセレクトボックスを表示する
1967        function initReceiveStoreGroupSelect() {
1968            receiveStoreGroupSelect.html(placeholderOption);
1969
1970            receiveStoreDetails.forEach(function (group, i) {
1971                var text = group.receiveStoreGroup || 'その他';
1972                receiveStoreGroupSelect.append(Sys('<option>').val(i).text(text));
1973            });
1974
1975            // 選択肢が1件だけなら自動選択
1976            if (receiveStoreDetails.length === 1) {
1977                receiveStoreGroupSelect.val(0).trigger('change');
1978            }
1979        };
1980
1981        // 店舗グループのセレクトボックスを表示しない
1982        function initReceiveStoreSelect() {
1983            var group = null;
1984            //店舗グループが未設定の店舗しかない場合
1985            if (Array.isArray(receiveStoreDetails)) {
1986                group = receiveStoreDetails.find(function (group) {
1987                    return group.receiveStoreGroup === "";
1988                });
1989            }
1990            //店舗グループの設定がない場合
1991            if (group && Array.isArray(group.receiveStores)) {
1992                group.receiveStores.forEach(function (store) {
1993                    var option = Sys("<option>").val(store.id).text(store.name);
1994                    receiveStoreSelect.append(option);
1995                });
1996                // 選択肢が1件だけなら自動選択
1997                if (group.receiveStores.length === 1) {
1998                    receiveStoreSelect.val(group.receiveStores[0].id);
1999                    receiveStoreSelect.trigger("change");
2000                }
2001            }
2002        };
2003
2004        // 店舗グループの変更
2005        function onReceiveStoreGroupChange() {
2006            receiveStoreGroupSelect.on("change", function () {
2007                var selectedIndex = parseInt(Sys(this).val(), 10);
2008                receiveStoreSelect.html(placeholderOption);
2009                if (isNaN(selectedIndex) || !receiveStoreDetails[selectedIndex]) return;
2010
2011                var stores = receiveStoreDetails[selectedIndex].receiveStores;
2012
2013                stores.forEach(function (store) {
2014                    var option = Sys("<option>").val(store.id).text(store.name);
2015                    receiveStoreSelect.append(option);
2016                });
2017
2018                // 選択肢が1つだけなら自動選択
2019                if (stores.length === 1) {
2020                    receiveStoreSelect.val(stores[0].id).trigger("change");
2021                }
2022            });
2023        }
2024
2025        // 受取店舗の更新
2026        function onReceiveStoreChange() {
2027            receiveStoreSelect.on("change", function () {
2028                var currentReceiveStoreId = Sys(this).val();
2029                var selectedIndex = receiveStoreGroupSelect.val();
2030
2031                var selectedGroup = null;
2032                if (hasReceiveStoreGroup) {
2033                    if (selectedIndex !== "" && receiveStoreDetails[selectedIndex]) {
2034                        selectedGroup = receiveStoreDetails[selectedIndex];
2035                    }
2036                } else {
2037                    // 店舗グループが存在しない場合
2038                    selectedGroup = receiveStoreDetails.find(function (group) {
2039                        return group.receiveStoreGroup === "";
2040                    });
2041                }
2042
2043                var receiveStoreDetail = null;
2044
2045                if (selectedGroup) {
2046                    receiveStoreDetail = selectedGroup.receiveStores.find(function (store) {
2047                        return store.id == currentReceiveStoreId;
2048                    });
2049                }
2050
2051                updateReceiveDateOptions(receiveStoreDetail, sessionCartReceiveDate);
2052                updateReceiveTimeOptions(receiveStoreDetail, sessionCartReceiveTime);
2053                updateStoreDescription(receiveStoreDetail);
2054            });
2055        }
2056
2057        // 受取日の更新
2058        function updateReceiveDateOptions(receiveStoreDetail, sessionCartReceiveDate) {
2059            receiveDateSelect.html(placeholderOption);
2060            if (receiveStoreDetail) {
2061                const receiveDates = Object.entries(receiveStoreDetail.receiveDates);
2062                receiveDates.forEach(function ([date, label]) {
2063                    var option = Sys('<option>').val(date).text(label);
2064                    if (date == sessionCartReceiveDate) {
2065                        option.prop('selected', true);
2066                    }
2067                    receiveDateSelect.append(option);
2068                });
2069
2070                // 選択肢が1つだけなら自動選択
2071                if (receiveDates.length === 1) {
2072                    receiveDateSelect.val(receiveDates[0][0]).trigger("change");
2073                }
2074            }
2075        }
2076
2077        // 受取時間の更新
2078        function updateReceiveTimeOptions(receiveStoreDetail, sessionCartReceiveTime) {
2079            receiveTimeSelect.html(placeholderOption);
2080            if (receiveStoreDetail) {
2081                receiveStoreDetail.receiveTimes.forEach(function (time) {
2082                    var option = Sys('<option>').val(time).text(time);
2083                    if (time == sessionCartReceiveTime) {
2084                        option.prop('selected', true);
2085                    }
2086                    receiveTimeSelect.append(option);
2087                });
2088
2089                // 選択肢が1つだけなら自動選択
2090                if (receiveStoreDetail.receiveTimes.length === 1) {
2091                    receiveTimeSelect.val(receiveStoreDetail.receiveTimes[0]).trigger("change");
2092                }
2093            }
2094        }
2095
2096        // 備考の更新
2097        function updateStoreDescription(receiveStoreDetail) {
2098            storeDescription.html('');
2099            if (receiveStoreDetail && receiveStoreDetail.description_for_site) {
2100                storeDescription.html(receiveStoreDetail.description_for_site);
2101            }
2102        }
2103
2104        // 受取日の変更
2105        function onReceiveDateChange() {
2106            receiveDateSelect.on("change", function () {
2107                if (!sessionCartReceiveDate) return;
2108                validateReceiveDateSelection();
2109            });
2110        }
2111
2112        //カート内の受取日と異なる受取日が選択された場合に注釈を表示
2113        function validateReceiveDateSelection() {
2114            var currentSelectedReceiveDate = receiveDateSelect.val();
2115            var receiveDateChangeMessage = receiveDateSelect.next(".receiveDateChangeMessage");
2116
2117            if (currentSelectedReceiveDate != sessionCartReceiveDate){
2118                if (receiveDateChangeMessage.length == 0) {
2119                    var $errorMsg = Sys('<p class="receiveDateChangeMessage">').text("カート内の商品の受取日も同時に変更されます");
2120                    receiveDateSelect.after($errorMsg);
2121                }
2122            } else {
2123                if (receiveDateChangeMessage.length > 0) {
2124                    receiveDateChangeMessage.remove();
2125                }
2126            }
2127        }
2128
2129        // 受取時間の変更
2130        function onReceiveTimeChange() {
2131            receiveTimeSelect.on("change", function () {
2132                if (!sessionCartReceiveDate) return;
2133                validateReceiveTimeSelection();
2134            });
2135        }
2136
2137        //カート内の受取時間と異なる受取時間が選択された場合に注釈を表示
2138        function validateReceiveTimeSelection() {
2139            var currentSelectedReceiveTime = receiveTimeSelect.val();
2140            var receiveTimeChangeMessage = receiveTimeSelect.next(".receiveTimeChangeMessage");
2141
2142            if (currentSelectedReceiveTime != sessionCartReceiveTime) {
2143                if (receiveTimeChangeMessage.length == 0) {
2144                    var $errorMsg = Sys('<p class="receiveTimeChangeMessage">').text("カート内の商品の受取時間も同時に変更されます");
2145                    receiveTimeSelect.after($errorMsg);
2146                }
2147            } else {
2148                if (receiveTimeChangeMessage.length > 0) {
2149                    receiveTimeChangeMessage.remove();
2150                }
2151            }
2152        }
2153    });
2154});
2155
2156/**
2157 * 商品詳細ページの受取方法選択の表示切替
2158 */
2159Sys(function () {
2160    addEventListener('onSysAsyncElementLoaded', () => {
2161        function toggleContent() {
2162
2163            var receiveAtStoreContent = Sys('#receiveAtStoreContent');
2164            var socialGiftContent = Sys('#socialGiftContent');
2165
2166            var receiveAtStoreType = window.receiveAtStoreType;
2167            var isOnlySocialGift = window.isOnlySocialGift
2168
2169            if (isOnlySocialGift) {
2170                receiveAtStoreContent.hide();
2171                socialGiftContent.show();
2172                return;
2173            }
2174
2175            if (receiveAtStoreType == 3) {
2176                receiveAtStoreContent.show();
2177                socialGiftContent.hide();
2178                return;
2179            }
2180
2181            var isDelivery = Sys('#delivery').prop('checked');
2182            var isReceiveAtStore = Sys('#receiveAtStore').prop('checked');
2183            var isSocialGift = Sys('#socialGift').prop('checked');
2184
2185            if (isDelivery) {
2186                receiveAtStoreContent.hide();
2187                socialGiftContent.hide();
2188            }
2189            else if (isReceiveAtStore) {
2190                receiveAtStoreContent.show();
2191                socialGiftContent.hide();
2192            } else if (isSocialGift) {
2193                receiveAtStoreContent.hide();
2194                socialGiftContent.show();
2195            } else {
2196                receiveAtStoreContent.hide();
2197                socialGiftContent.hide();
2198            }
2199        }
2200        Sys('#receiveAtStore, #delivery, #socialGift').on('change', toggleContent);
2201        toggleContent();
2202    });
2203});
2204
2205/**
2206 * 商品詳細ページの配送予定日
2207 */
2208Sys(function() {
2209    document.querySelectorAll(".sysDeliveryDateForm").forEach((deliveryDateForm) => {
2210        Sys(deliveryDateForm).change(function () {
2211            // 値が変更された時の処理
2212            deliveryDate(this);
2213        });
2214    });
2215
2216    /**
2217     * 配送予定日を取得
2218     */
2219    function deliveryDate(element) {
2220        Sys.ajax({
2221            type: 'GET',
2222            url: '/api/front/template/deliveryDate',
2223            contentType: 'application/json',
2224            data: {
2225                'itemNo': Sys("[name='itemNo']", element).val(),
2226                'prefStateId': Sys("#prefStateId option:selected", element).val(),
2227            },
2228        }).done(function (resp) {
2229            var sysDeliveryDateString = Sys(element).find(".sysDeliveryDateString");
2230            var sysNoAvailableDatesMessage = Sys(element).find(".sysDeliveryDateNoAvailableDatesMessage");
2231
2232            if (resp['deliveryDate'] != '') {
2233                Sys(element).find(".sysDeliveryDateEarliestDeliveryDate").text(resp['deliveryDate']);
2234                sysDeliveryDateString.show();
2235                sysNoAvailableDatesMessage.hide();
2236            } else {
2237                sysDeliveryDateString.hide();
2238                sysNoAvailableDatesMessage.show();
2239            }
2240        }).fail(function (resp) {
2241            Sys(element).find(".sysDeliveryDateString").hide();
2242            Sys(element).find(".sysDeliveryDateNoAvailableDatesMessage").hide();
2243            Sys(".sysDeliveryDateArea").html("配送予定日の取得に失敗しました");
2244        });
2245    }
2246});
2247/**
2248 * 商品の数量の+-
2249 */
2250Sys(function(){
2251    Sys('[data-num-plus]').off('click');
2252    Sys('[data-num-minus]').off('click');
2253    Sys('[name=num]').off('change');
2254
2255    Sys(document).on('click', '[data-num-plus]', function() {
2256        var mythis =  Sys(this).parents(".sysCartInForm");
2257        var num =  mythis.find('[name*=num]');
2258        if (num.children().last().val() != num.val()) {
2259            num.val(parseInt(num.val())+1);
2260            num.change();
2261        }
2262    });
2263
2264    Sys(document).on('click', '[data-num-minus]', function() {
2265        var mythis =  Sys(this).parents(".sysCartInForm");
2266        var num =  mythis.find('[name*=num]');
2267        if (num.children().first().val() != num.val()) {
2268            num.val(parseInt(num.val())-1);
2269            num.change();
2270        }
2271    });
2272
2273});
2274
2275/**
2276 * カートに入れる非同期処理
2277 *
2278 * IE はクロスドメインの ajax できないので割愛
2279 * .sysSelectCartButton .sysCartInButton .sysAddGoCartButton .sysGoCartButton
2280 */
2281Sys(function(){
2282    Sys(document).on('click', '.sysSelectCartButton, .sysCartInButton', function(event){
2283        if (Sys().userAgent().indexOf('msie') != -1) { return null; }
2284
2285        event.preventDefault();
2286        event.stopPropagation();
2287        var context = this;
2288
2289        var loader = Sys("<div><img src='/base/img/ajax-loader-indicator.gif' style=\"width: 16px; height: 16px;\" /></div>");
2290        Sys(document).ajaxSend(function() {
2291            Sys(context).before(loader);
2292        });
2293        Sys(document).ajaxComplete(function() {
2294            loader.remove();
2295            Sys(document).off("ajaxSend");
2296        });
2297        if (Sys(context).is('.sysSelectCartButton')) {
2298            if (Sys(context).is('.sysItemSelection')) {
2299                Sys.ajax(clickSelectCartButtonWithItemSelection(context));
2300            }
2301            if (Sys(context).is(".sysStockTypeVariation")) {
2302                Sys.ajax(clickSelectCartButtonWithStockTypeVariation(context));
2303            }
2304        } else if (Sys(context).is(".sysCartInButton")) {
2305            Sys.ajax(clickCartInButton(context));
2306        }
2307    });
2308
2309    // 個別商品オプション
2310    var clickSelectCartButtonWithItemSelection = function(context) {
2311        var isReorder = Sys(context).is('.sysReorderButton') ? 1 : 0;
2312        var itemNo = Sys(context).siblings('input[name="itemNo"]').val();
2313        var data = {
2314            url: '/api/front/template/formSelection',
2315            is_reorder: isReorder,
2316            item_no: itemNo,
2317            ajax_flag: 1
2318        };
2319
2320        return {
2321            type: 'GET',
2322            url: data['url'],
2323            data: data,
2324            success: function(response) {
2325                // 闇市商品のため選択肢を取得できない場合はメッセージを出す
2326                var errorMessage = response.sysErrorMessage;
2327                if (!errorMessage) {
2328                    var toggleButton = Sys(context).parents('form').find('.sysItemSelection');
2329                    toggleButton.val('カートに入れる');
2330                    toggleButton.removeClass('sysSelectCartButton');
2331                    if (Sys(context).parents('form').find('[name=ajaxFlag]').val() != 0) {
2332                        toggleButton.addClass('sysCartInButton');
2333                    }
2334                    toggleButton.before('<div class="sysAddSelection">' + response + '</div>');
2335                    if (window.fromOpc) {
2336                        Sys(context).parents('form').append('<input name="fromOpc" value="true" type="hidden" />');
2337                        Sys(context).parents('form').append('<input name="skipToUpdateConsitencyKey" value="true" type="hidden"/>');
2338                    }
2339                } else {
2340                    // 闇市商品の場合はメッセージを出す
2341                    var message = Sys('<p class="sysCartInDone"><span>' + errorMessage + '</span></p>');
2342                    Sys(context).before(message);
2343                    setTimeout(function() {
2344                        message.fadeOut('slow', function() { message.remove(); });
2345                    }, 3000);
2346                }
2347            },
2348            error: function(response) {
2349                catchCartInDoneError(context, response);
2350            }
2351        };
2352    };
2353
2354    // バリエーション在庫
2355    var clickSelectCartButtonWithStockTypeVariation = function(context) {
2356        var isReorder = Sys(context).is('.sysReorderButton') ? 1 : 0;
2357        var itemNo = Sys(context).siblings('input[name="itemNo"]').val();
2358
2359        var data = {
2360            is_reorder: isReorder,
2361            item_no: itemNo,
2362            ajax_flag: 1
2363        };
2364        return {
2365            type: 'GET',
2366            url: '/api/front/template/formVariationViewType/0',
2367            data: data,
2368            success: function(response) {
2369                // 闇市商品のため選択肢を取得できない場合はメッセージを出す
2370                var errorMessage = response.sysErrorMessage;
2371                if (!errorMessage) {
2372                    var toggleButton = Sys(context).parents('form').find('.sysStockTypeVariation');
2373                    toggleButton.val('カートに入れる');
2374                    toggleButton.removeClass('sysSelectCartButton');
2375                    if (Sys(context).parents('form').find('[name=ajaxFlag]').val() != 0) {
2376                        toggleButton.addClass('sysCartInButton');
2377                    }
2378
2379                    toggleButton.before('<div class="sysAddVariation">' + response + '</div>');
2380
2381                    var $variation = Sys(context).closest("form").find("div.sysAddVariation > select#variation");
2382                    if ($variation.val()) {
2383                        updateVariationPriceByVariationNo($variation.val(), $variation);
2384                    }
2385
2386                    $variation.on("change", function () {
2387                        if (Sys(this).val()) {
2388                            updateVariationPriceByVariationNo(Sys(this).val(), Sys(this));
2389                        }
2390                    });
2391
2392                    if (window.fromOpc) {
2393                        Sys(context).parents('form').append('<input name="fromOpc" value="true" type="hidden" />');
2394                        Sys(context).parents('form').append('<input name="skipToUpdateConsitencyKey" value="true" type="hidden"/>');
2395                    }
2396                } else {
2397                    // 闇市商品の場合はメッセージを出す
2398                    var message = Sys('<p class="sysCartInDone"><span>' + errorMessage + '</span></p>');
2399                    Sys(context).before(message);
2400                    setTimeout(function() {
2401                        message.fadeOut('slow', function() { message.remove(); });
2402                    }, 3000);
2403                }
2404            },
2405            error: function(response) {
2406                catchCartInDoneError(context, response);
2407            }
2408        };
2409    }
2410
2411    /**
2412     * カートに入れるボタンを押下
2413     */
2414    // 商品詳細ページでファイルを添付してカートインã
2414§ãã‚‹ã‚ˆã†ã€FormDataを用いてmultipart/form-dataで送信する
2415    var clickCartInButton = function(context) {
2416        var form = Sys(context).parents('form')[0];
2417        var formData = new FormData(form);
2418
2419        // バリエーション個別フォームの場合、ファイルinputがform属性でメインフォームに
2420        // 紐づいているためFormDataに含まれない。手動で追加する。
2421        var fileInput = document.getElementById('sysAttachedFileInput');
2422        if (fileInput && fileInput.files.length > 0 && !formData.has('attachedFile')) {
2423            formData.append('attachedFile', fileInput.files[0]);
2424        }
2425
2426        return {
2427            type: 'POST',
2428            url: '/api/front/cart/cartIn',
2429            data: formData,
2430            contentType: false, // FormDataが自動設定するmultipart/form-dataを、jQueryがapplication/x-www-urlencodedに上書きするのを防ぐ
2431            processData: false, // FormDataオブジェクトをURLエンコード文字列に変換させない
2432            success: function(response) {
2433                error_message = response;
2434                if (error_message == null || (Array.isArray(error_message) && error_message.length === 0)) {
2435                    var message = Sys("<p class='sysCartInDone'><span>カートに入りました。</span></p>");
2436                    // ボタンの切り替え
2437                    changeCartButton(Sys(context).parents("form"));
2438                    // 非同期の際カートに入れた時の商品オプション、バリエーション在庫項目とボタン文字
2439                    var boolVariation = Sys(context).is('.sysStockTypeVariation');
2440                    var boolSelection = Sys(context).is('.sysItemSelection');
2441                    var boolReorder = Sys(context).is('.sysReorderButton');
2442                    var ToggleButton = Sys(context).parents("form").find(".sysCartInButton");
2443                    if (boolReorder) {
2444                        ToggleButton.val("再注文する");
2445                        if (Sys(context).parents("form").find(".sysAddVariation").length) {
2446                            Sys(context).parents("form").find(".sysAddVariation").remove();
2447                        }
2448                        if (Sys(context).parents("form").find(".sysAddSelection").length) {
2449                            Sys(context).parents("form").find(".sysAddSelection").remove();
2450                        }
2451                        ToggleButton.removeClass("sysCartInButton").addClass("sysSelectCartButton");
2452                    } else if (boolVariation || boolSelection) {
2453                        if (Sys(context).parents("form").find(".sysAddVariation").length) {
2454                            Sys(context).parents("form").find(".sysAddVariation").remove();
2455                            ToggleButton.val("バリエーション選択");
2456                        }
2457                        if (Sys(context).parents("form").find(".sysAddSelection").length) {
2458                            Sys(context).parents("form").find(".sysAddSelection").remove();
2459                            ToggleButton.val("オプション選択");
2460                        }
2461                        ToggleButton.removeClass("sysCartInButton").addClass("sysSelectCartButton");
2462                    }
2463
2464                    dispatchEvent(new CustomEvent('cartupdated'));
2465                    if (window.fromOpc) {
2466                        app.onCartin();
2467                    }
2468                } else {
2469                    var message = Sys("<p class='sysCartInDone'><span>" + error_message.join('<br>') + "</span></p>");
2470                }
2471                Sys(context).before(message);
2472                setTimeout(function(){
2473                    message.fadeOut('slow', function(){ message.remove(); });
2474                }, 3000);
2475            },
2476            error: function(response) {
2477                catchCartInDoneError(context, response);
2478            }
2479        };
2480    };
2481
2482    var catchCartInDoneError = function(context, response) {
2483        resp = JSON.parse(response.responseText);
2484        let message = '';
2485        if (resp && resp.message) {
2486            message = Sys("<p class='sysCartInDone'><span>"+resp.message+"</span></p>");
2487        } else {
2488            message = Sys("<p class='sysCartInDone'><span>エラーが発生しました。</span></p>");
2489        }
2490        Sys(context).before(message);
2491        setTimeout(function(){
2492            message.fadeOut('slow', function(){ message.remove(); });
2493        }, 2000);
2494    };
2495
2496    /**
2497     * ボタンを切り替える(カートに入れる → カートを見る)
2498     */
2499    function changeCartButton(element) {
2500        var cartInButton = element.find(".sysCartInButton");
2501        if (!cartInButton.is(".sysAddGoCartButton")) {
2502            cartInButton.hide();
2503        }
2504        if (element.find(".sysGoCartButton").length == 0) {
2505            var goCartButton = Sys('<input>', {
2506                type: 'submit',
2507                value: 'カートを見る',
2508                class: 'sysCartButton sysGoCartButton'
2509            });
2510            var wrappedButton = Sys('<div></div>').append(goCartButton);
2511            Sys(cartInButton).after(wrappedButton);
2512        }
2513    }
2514
2515    /**
2516     * ボタンを切り替える(カートを見るボタン押下)
2517     */
2518    Sys(document).on('click', '.sysGoCartButton', function(e) {
2519        e.preventDefault();
2520        window.location.href = '/cart/basket.html';
2521    });
2522
2523    /**
2524     * 数値をカンマ区切りする
2525     * @param num 数値
2526     * @return {string | *}
2527     */
2528    function formatNumber(num) {
2529        num = num.toString().replace(/,/g, "");
2530        while (num !== (num = num.replace(/^(-?\d+)(\d{3})/, "$1,$2")));
2531        return num;
2532    }
2533
2534    /**
2535     * バリエーション価格を更新する
2536     * @param variationNo バリエーションNo
2537     */
2538    function updateVariationPriceByVariationNo(variationNo, variationSelect) {
2539        Sys.ajax({
2540            type: "GET",
2541            url: '/api/front/item/getVariationPriceByVariationNo',
2542            async: false,
2543            data: {
2544                "variation_no": variationNo
2545            }
2546        })
2547        .done(function (resp) {
2548          var isUseVariationPrice = resp.is_use_variation_price;
2549          if (isUseVariationPrice === '0') {
2550              // バリエーション価格を使用しない場合は何もしない
2551              return false;
2552          }
2553
2554          var $li = variationSelect.closest("li");
2555          var taxation = $li.find("input[name='displayed_taxation']").val();
2556          // 闇市表示設定(Category/Search のみ付与。商品ブロックは属性なし=false 扱い)
2557          var isDisplayYamiPrice = $li.attr("data-is-display-yami-price") === "1";
2558
2559          // 取引種別を silently 反映した価格(闇市は含めない)
2560          // 商品ブロックや Category/Search 通常モードは闇市を表に出さない方針のためこちらを使う
2561          var silentPrice = resp.is_use_trade_rate
2562              ? resp.tax_included_resolved_retail_price
2563              : resp.tax_included_retail_price;
2564          var silentPriceMsg = formatNumber(silentPrice) + "円" + taxation;
2565
2566          // 適用後価格(闇市 > 取引種別 > 通常 の優先順)
2567          // 闇市は is_display_yami_price ON のときのみ採用する
2568          var effectivePrice;
2569          if (resp.is_use_yami_price && isDisplayYamiPrice) {
2570              effectivePrice = resp.tax_included_yami_price;
2571          } else if (resp.is_use_trade_rate) {
2572              effectivePrice = resp.tax_included_resolved_retail_price;
2573          } else {
2574              effectivePrice = resp.tax_included_retail_price;
2575          }
2576          var effectivePriceMsg = formatNumber(effectivePrice) + "円" + taxation;
2577
2578          // 元価格(打ち消し線併記用)
2579          var retailPriceMsg = formatNumber(resp.tax_included_retail_price) + "円" + taxation;
2580
2581          // 商品ブロック (闇市非表示方針、取引種別のみ silently 反映)
2582          $li.find("div.sysItemPrice").html(
2583              '<span class="sysItemBlockVariationPrice">' + silentPriceMsg + '</span>'
2584          );
2585
2586          // Category/Search 通常モード (sysRetailPrice のみ存在、適用後表示なし)
2587          // 闇市は出さず、取引種別 silently 反映
2588          $li.find("div.sysRetailPrice .sysItemBlockVariationPrice").html(silentPriceMsg);
2589
2590          // Category/Search 適用後モード (sysOriginRetailPrice 打ち消し線 + 適用後)
2591          $li.find("div.sysOriginRetailPrice .sysItemBlockVariationPrice").html(retailPriceMsg);
2592          $li.find(".sysItemBlockVariationYamiPrice").html(effectivePriceMsg);
2593      })
2594      .fail(function () {
2595          variationSelect.closest("li").find("div.sysItemPrice").html(
2596              '<span class="sysItemBlockVariationPrice">' + "バリエーション価格の取得に失敗しました。" + '</span>'
2597          );
2598      });
2599    }
2600});
2601
2602/**
2603 * 商品画像モーダル表示
2604 */
2605Sys(function(){
2606
2607    var ItemImageList = {};
2608    ItemImageList.mainclick = function(event){
2609        event.preventDefault();
2610        event.stopPropagation();
2611
2612        var contents = Sys("<div>").attr('class', 'sysModalItemImages');
2613        var main = Sys("<div>").attr('class', 'sysModalMainImage');
2614        var list = Sys("<ul>").attr('class', 'sysModalImageList');
2615
2616        var parent = Sys(this).closest('.sysItemImages');
2617        Sys(parent).find("li").each(function(){
2618            var media = Sys(this).find('img, video').eq(0);
2619            if (!media.length) {
2620                return;
2621            }
2622            var isVideo = media.prop('tagName') && media.prop('tagName').toLowerCase() === 'video';
2623            var srcAttr = media.attr('src');
2624            var titleAttr = media.attr('title') || '';
2625            var altAttr = media.attr('alt') || media.attr('aria-label') || '';
2626            var listItem = Sys("<li>");
2627            if (isVideo) {
2628                var posterAttr = media.attr('poster') || '';
2629                var playbackProps = {muted: true, playsInline: true, preload: 'metadata'};
2630                var videoAttrs = {
2631                    'src': srcAttr,
2632                    'title': titleAttr,
2633                    'aria-label': altAttr
2634                };
2635                if (posterAttr) {
2636                    videoAttrs['poster'] = posterAttr;
2637                }
2638                var video = Sys("<video>").attr(videoAttrs).attr(playbackProps).prop(playbackProps);
2639                listItem.append(video);
2640            } else {
2641                var img = Sys("<img>").attr({
2642                    'src': srcAttr,
2643                    'title': titleAttr,
2644                    'alt': altAttr
2645                });
2646                listItem.append(img);
2647            }
2648            list.append(listItem);
2649
2650        });
2651        contents.append(main);
2652        contents.append(list);
2653        ModalWindow.open(contents);
2654        var index = Sys(parent).find(".sysCurrent").index();
2655        ModalItemImageList.init(index);
2656        //location.hash = 'openItemImage';
2657    };
2658
2659    var ModalItemImageList = {};
2660    ModalItemImageList.init = function(index){
2661        Sys('.sysModalImageList img, .sysModalImageList video').eq(index).trigger("click");
2662    };
2663    ModalItemImageList.click = function(){
2664        ModalItemImageList.updateMain(Sys(this));
2665    };
2666    ModalItemImageList.updateMain = function(current){
2667        var parent = current.closest('.sysModalItemImages');
2668        parent.find('li').removeClass('sysCurrent');
2669        current.parent().addClass('sysCurrent');
2670        var isVideo = current.prop('tagName') && current.prop('tagName').toLowerCase() === 'video';
2671        var mainMedia;
2672        if (isVideo) {
2673            var posterAttr = current.attr('poster') || '';
2674            var playbackProps = {muted: true, playsInline: true, autoplay: true, loop: true, preload: 'metadata'};
2675            var videoAttrs = {
2676                'src': current.attr('src'),
2677                'title': current.attr('title') || '',
2678                'aria-label': current.attr('aria-label') || ''
2679            };
2680            // autoplay時のちらつき回避のため、初期表示ではposterを付与しない
2681            mainMedia = Sys('<video/>').attr(videoAttrs).attr(playbackProps).prop(playbackProps);
2682        } else {
2683            mainMedia = Sys('<img/>').attr({
2684                'src': current.attr('src'),
2685                'title':  current.attr('title'),
2686                'alt': current.attr('alt')
2687            });
2688        }
2689        parent.find('.sysModalMainImage').html(mainMedia);
2690        if (isVideo) {
2691            var videoElement = mainMedia.get(0);
2692            if (videoElement && typeof videoElement.play === 'function') {
2693                videoElement.play().catch(function () {
2694                    // autoplay制限で再生失敗した場合のみposterを後付けして黒画面を避ける
2695                    if (posterAttr) {
2696                        videoElement.setAttribute('poster', posterAttr);
2697                    }
2698                });
2699            }
2700        }
2701        //parent.find('.sysImageDescription').html(current.attr('alt'));
2702    };
2703    Sys(document).on('click', '.sysMainImageModal img, .sysMainImageModal video, .sysMainImageModal', ItemImageList.mainclick);
2704    Sys(document).on('click', '.sysModalImageList img, .sysModalImageList video', ModalItemImageList.click);
2705});
2706
2707/**
2708 * モーダル表示
2709 *
2710 */
2711Sys(function(){
2712    ModalWindow = new Array();
2713
2714    ModalWindow.bind = function(){
2715        Sys(document).on('click', "a[href='#closeModalWindow']", function(){
2716            ModalWindow.close();
2717            return false;
2718        });
2719        Sys(document).on('click', "#sysModalWindow", function(event){
2720            if (Sys(event.target).is('#sysModalWindow')) {
2721                ModalWindow.close();
2722                return false;
2723            }
2724        });
2725        Sys(window).on('hashchange', function(){
2726            if (location.hash == '' ) {
2727                ModalWindow.close();
2728            }
2729        });
2730    };
2731
2732    ModalWindow.isOpen = false;
2733
2734    ModalWindow.open = function(html){
2735        var modalWindow = Sys("<div>").attr('id', 'sysModalWindow');
2736        var close_btn = Sys('<a>');
2737        close_btn.attr('href', '#closeModalWindow');
2738        close_btn.addClass('sysRemoveElement');
2739        var content = Sys("<div>").addClass('sysModalContent');
2740        content.append(close_btn);
2741        var body = Sys("<div>").addClass('sysModalBody').html(html);
2742        content.append(body);
2743        modalWindow.append(content);
2744        Sys('body').append(modalWindow);
2745        //Sys.disableMouseScroll();
2746        this.isOpen = true;
2747    };
2748
2749    ModalWindow.close = function(){
2750        Sys('#sysModalWindow').remove();
2751        this.isOpen = false;
2752        Sys('body').trigger('modalClose');
2753    };
2754
2755    ModalWindow.content = function(html){
2756        return Sys('#sysModalWindow .sysModalBody').html(html);
2757    };
2758
2759    ModalWindow.bind();
2760});
2761
2762/* MDL(OPCは別の専用JSファイルに記載)
2763 ------------------------------------------*/
2764Sys(function() {
2765    /* テキストボックス */
2766    Sys('input[type="text"].mdl_required').blur(function () {
2767        checkMdlRequiredInput(Sys(this));
2768    });
2769
2770    /* セレクトボックスの場合は checkMdlRequiredSelect() を適宜呼び出してください(changeイベントがぶつかるため) */
2771});
2772
2773/**
2774 * 必須チェック(入力系)
2775 * @param {jQuery} ele :チェック対象の要素(<input>や<select>など)
2776 */
2777function checkMdlRequiredInput(ele)
2778{
2779    if (ele.val() === '') {
2780        ele.parent().addClass('is-invalid');
2781        ele.parent().find('.mdl-textfield__error').text('入力してください。');
2782    }
2783}
2784
2785/**
2786 * 必須チェック(選択系)
2787 * @param {jQuery} ele :チェック対象の要素(<input>や<select>など)
2788 * @param {jQuery} eleParent :.is-invalidを付けたい要素(eleとmdlErrorEleが入っている親要素)
2789 * @param {jQuery} mdlErrorEle :エラー内容を表示する要素
2790 */
2791function checkMdlRequiredSelect(ele, eleParent, mdlErrorEle)
2792{
2793    if (ele.val() === '') {
2794        eleParent.addClass('is-invalid');
2795        mdlErrorEle.text('選択してください。');
2796    }
2797}
2798
2799/**
2800 * <input type="hidden">の要素を作成して追加
2801 * @param {string}        name :id属性、name属性に入れたい文字列
2802 * @param {string|number} val :値
2803 * @param {element}       addEle :<input>を入れたい要素
2804 *
2805 */
2806function setInputHiddenEle(name, val, addEle)
2807{
2808    var selector = 'input[name="' + name + '"]';
2809    var ele = document.querySelector(selector);
2810    if (ele === null) {
2811        // 要素がまだなければ作る(トークン取得2回目以降は既に作成されている)
2812        ele = document.createElement('input');
2813        ele.type = 'hidden';
2814        ele.name = name;
2815    }
2816    ele.value = val;
2817
2818    // 確認画面では追加箇所が1ページ上で2箇所あるためjQueryで対応
2819    Sys(addEle).append(Sys(ele));
2820}

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.