以前紹介した、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