3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

以前紹介した、Shopify商品閲覧履歴の実装方法なのですが…少し難点がありまして、他の商品カードと同じスタイルで表示しようとすると、JSで処置を追加してあげないといけなくなります。
どうゆうことかというと、「販売開始から特定の期間だけ新着とか出したい。」みたいなことがあると、その表示をするための処理を別で組まないといけなくなります。
また、その処理に変更があった場合、商品一覧などに使っている処理と、最近チェックした商品の処理と2ヶ所変更して、2ヶ所確認して…。
めっちゃ面倒だぁーー!
ということで、商品一覧で使うようなカードをそのまま使う形のコードを紹介しようと思います。
仕組み自体は、色々なところで応用が効くかと思うので、いろんな箇所で利用されてみてください。

recently-product-card.liquidセクションファイルの作成

<div class="js-recentlySectionId" data-recently-section-id="{{ section.id }}"  style="display: none;">
  {% if template contains 'product' %}
    {% render 'product-card', product: product %}
  {% endif %}
</div>
{% schema %}
{
  "name": "最近チェックした商品テンプレート",
  "class": "カードの外側にあたるクラス名を指定"
}
{% endschema %}

まずは、recently-product-card.liquidファイルをセクションフォルダーに作成します。この時、スキーマのクラス名にカードの一つ外側にあたるクラス名を指定します。js-recentlySectionIdの

タグに商品カードの基準となるクラス名を指定します。
renderで読み込んでいるファイル名はテーマに合わせて変更してください。

theme.liquidファイル内で、recently-product-card.liquidを読み込む

{% section 'recently-product-card' %}
<script type='text/javascript'>Shopify.recentlySectionId = document.querySelector('.js-recentlySectionId').dataset.recentlySectionId;</script>

上記のコードをtheme.liquid内のなるべく下の方に読み込みます。
早い段階で読み込む必要がないので、なるべく下で大丈夫です。

assetsフォルダー内にjquery.products.jsを作成します

/**
 * Cookie plugin
 *
 * Copyright (c) 2006 Klaus Hartl (stilbuero.de)
 * Dual licensed under the MIT and GPL licenses:
 * http://www.opensource.org/licenses/mit-license.php
 * http://www.gnu.org/licenses/gpl.html
 *
 */

jQuery.cookie=function(b,j,m){if(typeof j!="undefined"){m=m||{};if(j===null){j="";m.expires=-1}var e="";if(m.expires&&(typeof m.expires=="number"||m.expires.toUTCString)){var f;if(typeof m.expires=="number"){f=new Date();f.setTime(f.getTime()+(m.expires*24*60*60*1000))}else{f=m.expires}e="; expires="+f.toUTCString()}var l=m.path?"; path="+(m.path):"";var g=m.domain?"; domain="+(m.domain):"";var a=m.secure?"; secure":"";document.cookie=[b,"=",encodeURIComponent(j),e,l,g,a].join("")}else{var d=null;if(document.cookie&&document.cookie!=""){var k=document.cookie.split(";");for(var h=0;h<k.length;h++){var c=jQuery.trim(k[h]);if(c.substring(0,b.length+1)==(b+"=")){d=decodeURIComponent(c.substring(b.length+1));break}}}return d}};

/**
 * Module to show Recently Viewed Products
 *
 * Copyright (c) 2014 Caroline Schnapp (11heavens.com)
 * Dual licensed under the MIT and GPL licenses:
 * http://www.opensource.org/licenses/mit-license.php
 * http://www.gnu.org/licenses/gpl.html
 *
 */

Shopify.Products = (function() {

  var config = {
    howManyToShow: 3,
    howManyToStoreInMemory: 10,
    wrapperId: 'recently-viewed-products',
    templateId: 'recently-viewed-product-template',
    onComplete: null,
    recentlySectionId: null
  };

  var productHandleQueue = [];
  var wrapper = null;
  var template = null;
  var shown = 0;

  var cookie = {
    configuration: {
      expires: 90,
      path: '/',
      domain: window.location.hostname
    },
    name: 'shopify_recently_viewed',
    write: function(recentlyViewed) {
      jQuery.cookie(this.name, recentlyViewed.join(' '), this.configuration);
    },
    read: function() {
      var recentlyViewed = [];
      var cookieValue = jQuery.cookie(this.name);
      if (cookieValue !== null) {
        recentlyViewed = cookieValue.split(' ');
      }
      return recentlyViewed;
    },
    destroy: function() {
      jQuery.cookie(this.name, null, this.configuration);
    },
    remove: function(productHandle) {
      var recentlyViewed = this.read();
      var position = jQuery.inArray(productHandle, recentlyViewed);
      if (position !== -1) {
        recentlyViewed.splice(position, 1);
        this.write(recentlyViewed);
      }
    }
  };

  var finalize = function() {
    wrapper.show();
    // If we have a callback.
    if (config.onComplete) {
      try { config.onComplete() } catch (error) { }
    }
  };

  var moveAlong = function() {
    if (productHandleQueue.length && shown < config.howManyToShow) {
      fetch(`/products/${productHandleQueue[0]}?sections=${config.recentlySectionId}`)
        .then((r) => r.json())
        .then(
          (d) => {
            // console.log(d);
            // console.log(d[`${config.recentlySectionId}`]);
            wrapper.append(d[`${config.recentlySectionId}`]);
            productHandleQueue.shift();
            shown++;
            moveAlong();
          },
          (e) => {
            console.log(e);
            cookie.remove(productHandleQueue[0]);
            productHandleQueue.shift();
            moveAlong();
          }
      );
    }
    else {
      finalize();
    }
  };

  return {

    resizeImage: function(src, size) {
      if (size == null) {
        return src;
      }

      if (size == 'master') {
        return src.replace(/http(s)?:/, "");
      }

      var match  = src.match(/\.(jpg|jpeg|gif|png|bmp|bitmap|tiff|tif)(\?v=\d+)?/i);

      if (match != null) {
        var prefix = src.split(match[0]);
        var suffix = match[0];

        return (prefix[0] + "_" + size + suffix).replace(/http(s)?:/, "")
      } else {
        return null;
      }
    },

    showRecentlyViewed: function(params) {
      var params = params || {};

      // Update defaults.
      jQuery.extend(config, params);

      // Read cookie.
      productHandleQueue = cookie.read();

      // Template and element where to insert.
      wrapper = jQuery('#' + config.wrapperId);

      // How many products to show.
      config.howManyToShow = Math.min(productHandleQueue.length, config.howManyToShow);

      // If we have any to show.
      if (config.howManyToShow && wrapper.length) {
        // Getting each product with an Ajax call and rendering it on the page.
        moveAlong();
      }
    },

    getConfig: function() {
      return config;
    },

    clearList: function() {
      cookie.destroy();
    },

    recordRecentlyViewed: function(params) {

      var params = params || {};

      // Update defaults.
      jQuery.extend(config, params);

      // Read cookie.
      var recentlyViewed = cookie.read();

      // If we are on a product page.
      if (window.location.pathname.indexOf('/products/') !== -1) {

        // What is the product handle on this page.
        var productHandle = window.location.pathname.match(/\/products\/([a-z0-9\-]+)/)[1];
        // In what position is that product in memory.
        var position = jQuery.inArray(productHandle, recentlyViewed);
        // If not in memory.
        if (position === -1) {
          // Add product at the start of the list.
          recentlyViewed.unshift(productHandle);
          // Only keep what we need.
          recentlyViewed = recentlyViewed.splice(0, config.howManyToStoreInMemory);
        }
        else {
          // Remove the product and place it at start of list.
          recentlyViewed.splice(position, 1);
          recentlyViewed.unshift(productHandle);
        }

        // Update cookie.
        cookie.write(recentlyViewed);

      }
    }
  };
})();

上記の内容をassetsフォルダ内に作成し、以下のコードをtheme.liquidのhead内の{{ content_for_header }}より下に追加する
※既に別でjQueryを読み込んでいる場合は、削除してください。

{%- comment -%} 最近チェックした商品 読み込み {%- endcomment -%}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
{{ "jquery.products.js" | asset_url | script_tag }}

main-product.liquidに以下のコードを書き込む

{%- comment -%} 最近チェックした商品 書き込み {%- endcomment -%}
<script type="text/javascript">
Shopify.Products.recordRecentlyViewed();
</script>

商品ページが読み込まれた時に、商品データをクッキーに入力するコードになります。

sectionsフォルダにrecently-prouct-slider.liquidを作成するし以下のコードを書き込む

{% comment -%}
------------------------------------
最近チェックした商品
------------------------------------
{%- endcomment -%}

<!-- product-recently-viewed -->
<div class="product-recently-viewed collection-slider">
  <div class="container">
    <h2 class="product-recently-viewed__title section__title">最近チェックした商品</h2>

    <ul id="js-recentlySliderBody" class="js-recentlySlider collection-slider__list product-recently-viewed__list">

      {% unless customer %}
        {%- comment -%} 未ログイン時 {%- endcomment -%}
        <p id="wishlist-empty-text"
          style="padding: 60px 0; text-align: center;min-height: 320px; display: flex; align-items: center;">
          まだ最近チェックした商品がありません
        </p>
      {% endunless %}

    </ul>
  </div>
</div>

{%- comment -%} ログイン時のみ発動 {%- endcomment -%}
{% if customer %}
  {%- comment -%} コントロールjs {%- endcomment -%}
  <script>
    window.addEventListener( 'load', function(){
      const recentlyViewController = (function(){

        // クッキーからデータを取得する
        let recentlyItems = jQuery.cookie('shopify_recently_viewed');

        // 閲覧履歴がある時 クッキーにデータがある時
        if(recentlyItems != null ) {
          Shopify.Products.showRecentlyViewed({
            howManyToShow: 8,// 表示する商品数を変更できます。
            wrapperId: 'js-recentlySliderBody',
            recentlySectionId: Shopify.recentlySectionId,
            onComplete: function() {
              // データが読み込まれた後発火させたい時

              // 非表示にしているアイテムを表示させる
              let lists = Array.from(document.querySelectorAll('li.recently-slider__item'));
              lists.forEach((item) => {
                item.style.display = "block";
              });
            }
          });
        } else {
          // 閲覧履歴がない時の処理 クッキーにデータがない時
          document.getElementById('js-recentlySliderBody').insertAdjacentHTML("afterbegin",`<p id="wishlist-empty-text" style="padding: 60px 0; text-align: center;min-height: 320px; display: flex; align-items: center;">まだ最近チェックした商品がありません</p>`);
        }

      })();
    }, false);
  </script>
{% endif %}

{% schema %}
{
  "name": "Product Recently Viewed",
  "settings": [
  ],
  "presets": [
    {
      "name": "Product Recently Viewed"
    }
  ]
}
{% endschema %}

注意点

商品詳細ページで読み込まれている商品カードは表示させないようにstyle="display:none;"を指定してあります。
なので、最近チェックした商品内の商品カードは、表示されるようにスタイルを指定してやる必要があります!

仕組み

どういう仕組みかというと、セクションのレンダリング機能を使用して商品カードを呼び出しています。
本家のjquery.products.jsの商品データを取得している処理を変更し、商品ページに読み込まれている商品に付随した商品カードデータを呼び出して、閲覧履歴内で表示しています。(fetch(/products/${productHandleQueue[0]}?sections=${config.recentlySectionId})の部分)
 
なので、最初に作成したrecently-product-card.liquid内で呼び出している{% render 'product-card', product: product %}をテーマ内で統一してあげることで、スタイルや特定の条件で表示するラベルのコントロールなどを一元管理することができるようになります。

まとめ

面倒くさがりが産んだ、仕組みですが、今回学んだjquery.product.jsの仕組みとセクションのレンダリングを使用することで、アプリを使わずに機能を拡張することができそうです。
例えば、商品一覧の商品カードをクリックして、商品詳細のモーダルが開くとか…。商品詳細でカートに追加ボタンを押すことでサイドからカートの中身が出てくるなど…。jquery.product.jsは可変してお気に入り機能にしている人がいるとかいないとか…。
また、一段とLiquidが面白くなりました!
機会があれば、その内容も記事にしてみようと思います。
 
あとは、お決まりの…
ARCHETYPでは、Shopifyのテーマ開発やアプリ開発をやりたいエンジニアを募集しております!
気になった方はお気軽にエントリーをお待ちしております!
https://www.archetyp.jp/recruit/
noteでブログもやってるので良かったらチェックしてください!
https://note.com/archetyp

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?