Open2

【Shopify】Dawn の JS を理解する

Eiji Saito | UnReact Inc.Eiji Saito | UnReact Inc.

やること

Shopify の無料テーマ「Dawn」の JS ファイル内のコードを理解

目的

Shopify テーマ開発

Dawn 内のJSファイル

Dawn には以下の18ファイルが存在する。

  • cart-notification.js
  • cart.js
  • customer.js
  • details-disclosure.js
  • details-modal.js
  • facets.js
  • global.js
  • media-gallery.js
  • password-modal.js
  • pickup-availability.js
  • predictive-search.js
  • product-form.js
  • product-modal.js
  • product-model.js
  • quick-add.js
  • share.js
  • show-more.js
  • theme-editor.js
Eiji Saito | UnReact Inc.Eiji Saito | UnReact Inc.

cart-notification.js

header.liquidでスニペットのcart-notification.liquidといっしょに読み込まれている。

cart-notification.js
class CartNotification extends HTMLElement {
  // コンストラクタ関数を定義
  constructor() {
    super();

    // id="cart-notification" のノードを this.notification に代入
    this.notification = document.getElementById('cart-notification');
    
    // <sticky-header></sticky-header> のノードを取得
    this.header = document.querySelector('sticky-header');

    // handleBodyClick 関数内で使用する this を CartNotification クラスに固定
    this.onBodyClick = this.handleBodyClick.bind(this);
    
    // escape キーが押されたら、CartNotification クラス内の close() 関数を実行
    this.notification.addEventListener('keyup', (evt) => evt.code === 'Escape' && this.close());
    
    // type="button" である<button></button>をすべて取得し、各ボタンがクリックされたとき close() を実行
    this.querySelectorAll('button[type="button"]').forEach((closeButton) =>
      closeButton.addEventListener('click', this.close.bind(this))
    );
  }

  // open() 関数を定義
  open() {
    // コンストラクタ関数で取得した this.notification ノードに 'animate' と 'active' というクラスを追加
    this.notification.classList.add('animate', 'active');

    // CSS の transion(アニメーション)が終了したときに実行
    this.notification.addEventListener('transitionend', () => {
      // this.notification ノードに フォーカス(選択された状態)
      this.notification.focus();
      // global.js の関数 trapForcus()
      trapFocus(this.notification);
    }, { once: true });

    //  <body></body> をクリックした時、onBodyClick() を実行
    document.body.addEventListener('click', this.onBodyClick);
  }

  // close() 関数を定義
  close() {
    // コンストラクタ関数で取得した this.notification ノードから active というクラスを削除
    this.notification.classList.remove('active');

    //  <body></body> をクリックした時、open() 関数内の addEventListener で登録したイベントを解除
    document.body.removeEventListener('click', this.onBodyClick);

    // global.js の関数 removeTrapForcus()
    removeTrapFocus(this.activeElement);
  }

  // renderContents() 関数を定義
  // product-form.jsで使用されている
  renderContents(parsedState) {

      // this.cartItemKeyを定義
      this.cartItemKey = parsedState.key;
      
      // getSectionsToRender() の返り値を forEach で繰り返し処理
      this.getSectionsToRender().forEach((section => {
        // section オブジェクト内の id を持っているノードにHTMLを挿入
        document.getElementById(section.id).innerHTML =
          // section オブジェクト内の id と selector を getSectionInnerHTML() 関数に渡す
          this.getSectionInnerHTML(parsedState.sections[section.id], section.selector);
      }));
      
      // this.header がある時 header.liquid ファイルで定義されている reveal() 関数を実行
      // reveal() {
      //    this.header.classList.add('shopify-section-header-sticky', 'animate');
      //    this.header.classList.remove('shopify-section-header-hidden');
      // }
      if (this.header) this.header.reveal();
      
      // open() 関数を実行  
      this.open();
  }

  // 3つのオブジェクトを持つ配列を返す getSectionsToRender() を定義
  // renderContent() 内で使用
  getSectionsToRender() {
    return [
      {
        id: 'cart-notification-product',
        selector: `[id="cart-notification-product-${this.cartItemKey}"]`,
      },
      {
        id: 'cart-notification-button'
      },
      {
        id: 'cart-icon-bubble'
      }
    ];
  }

  // HTML ノードを返す getSectionInnerHTML() 関数を定義
  // html と selector を受け取る
  // selector のデフォルト値は shopify-section 
  // querySelector(selector)で取得した中身を返す
  getSectionInnerHTML(html, selector = '.shopify-section') {
    return new DOMParser()
      .parseFromString(html, 'text/html')
      .querySelector(selector).innerHTML;
  }


  // イベント(evt)を受けとり target にイベントを発生させたオブジェクトへの参照を代入する
  // closest()は、CSSのクラスを受け取り、一番近い一致する要素を返す(自分自身 or 親要素)
  // close()関数を走らせる
  handleBodyClick(evt) {
    const target = evt.target;
    if (target !== this.notification && !target.closest('cart-notification')) {
      const disclosure = target.closest('details-disclosure, header-menu');
      this.activeElement = disclosure ? disclosure.querySelector('summary') : null;
      this.close();
    }
  }

  // activeElement に 引数の要素を代入する
  setActiveElement(element) {
    this.activeElement = element;
  }
}

customElements.define('cart-notification', CartNotification);