参考URL

6. テーマファイルと設定スキーマ
settings_scheme.jsonでは最初、テーマ情報・テーマのテンプレート情報を持っている。
このファイルにはより多くの設定を追加することが可能。ナビゲーションの色をカスタマイザーで変更できる設定を付け加えるなど。
theme deployで手動アップデート
7. CSSフレームワークのインストール
CSSファイルはassetsフォルダに投下していく。
CSSファイルを読み込みたい場合は、Layout> theme.liquid> headの下側、{{application.css.liquid | ~~~}の上下適切な位置に該当ファイルを置く。
JSファイルの場合は、bodyタグの最後に置きたい場合はそちらに移動させる。
8.ヘッダー
theme.liquidのbodyのナビゲーション部分は、sectionフォルダにheader.liquid作ってコピペするなどして使うのが吉
{% section 'header' %}
シングルクオーテーションなのに注意
10. カスタムセクションの作成
{{ content_for_index }}
index.liquidで使用されているオブジェクトです。これにより、ホームページを動的にカスタマイズすることができます。
このオブジェクトを使用すると、カスタマイズページにセクションを追加するボタンが表示されます。

Scheme
Scheme tags are used to create settings for your sections and you can find these settings in your customizer page like below.(Schemeタグは、セクションの設定を作成するために使用され、これらの設定はカスタマイザーページで以下のように見ることができます。)

richtextのセクションを作るときの注意点
<p>タグは、settingsのdefaultで指定するので、HTMLでは<p>タグ除いた{{ section.settings.「richtext」 }}で書く(「richtext」は任意のid名)
{
"type": "richtext",
"id": "description",
"default": "<p>Example Paragraph</p>",
"label": "Heading Description"
}画像アップローダー
"settings": [
{
"type": "image_picker",
"id": "image",
"label": "Heading Background Title"
},HTMLで画像を出力するときは、if文で画像が空でないか確認する。
{% if section.settings.image != blank %}
<img src="{{ section.settings.image | img_url }}" alt="">
{% else %}
{% capture current %}{% cycle 1, 2 %}{% endcapture %}
{{ "lifestyle-" | append: current | placeholder_svg_tag: "placeholder-svg"}}
{% endif %}
ちなみに、lifestyle-2だとは以下のsvgイラスト

参考

11. コレクション内の製品の表示
Shopifyでコレクションを作ったら、商品をいくつか登録してみよう。
セクションフォルダ>featured-collection.liquidを作成し、"type": "collection"でセクションにコレクションの商品を表示するリストを作成することができる。
<section class="container py-4">
<h2 class="text-center my-3">{{ section.settings.title }}</h2>
<div class="row">
<!-- カスタマイズ画面で選択したコレクション内にある全ての商品のリストを取得してループ -->
{% for product in collections[section.settings.featured_collection].products %}
{% assign image = product.featured_media.preview_image %}
<div class="col">
<div class="card" style="width: 18rem;">
{% if image != blank %}
<img class="card-img-top" src="{{ image | img_url: 'medium' }}" alt="{{ product.title }}">
{% else %}
{{ 'product-1' | placeholder_svg_tag: 'card-img-top' }}
{% endif %}
<div class="card-body">
<h3><a href="{{ product.url }}">{{ product.title }}</a></h3>
<p>{{ product.price | money_without_trailing_zeros }}</p>
</div>
</div>
</div>
{% endfor %}
</div>
</section>
{% schema %}
{
"name": "Featured Collection",
"class": "featured-collection-section",
"settings": [
{
"type": "collection",
"id": "featured_collection",
"label": "Collection"
},
{
"type": "text",
"id": "title",
"default": "Featured Collection",
"label": "Title"
}
],
"presets": [
{
"category": "Collection",
"name":"Featured Collection"
}
]
}
{% endschema %}
ちなみに、
<img src="{{ product.featured_media.preview_image | img_url: 'medium' }}" alt="{{ product.title }}">の、featured_media.preview_imageは下のような感じ↓

12. 商品ページの作成
商品の詳細ページのデザインを変えるには、tepmlates>product.liquid
この講座では、すでにデフォルトのコードが書かれているが、全部削除しゼロから商品ページのコーディングを始める。
もともとあったコード↓ (product.liquid)
{% assign current_variant = product.selected_or_first_available_variant %}
{% assign featured_image = current_variant.featured_image | default: product.featured_image %}
<img src="{{ featured_image | img_url: 'large' }}" alt="{{ featured_image.alt | escape }}" id="ProductPhotoImg">
{% for image in product.images %}
<a href="{{ image.src | img_url: 'large' }}">
<img src="{{ image.src | img_url: 'compact' }}" alt="{{ image.alt | escape }}">
</a>
{% endfor %}
<h1>{{ product.title }}</h1>
<form action="/cart/add" method="post" enctype="multipart/form-data" id="AddToCartForm">
<select name="id" id="productSelect">
{% for variant in product.variants %}
{% if variant.available %}
<option value="{{ variant.id }}">
{{ variant.title }} - {{ variant.price | money_with_currency }}
</option>
{% else %}
<option disabled="disabled">
{{ variant.title }} - sold out
</option>
{% endif %}
{% endfor %}
</select>
{{ current_variant.price | money }}
<label for="Quantity">quantity</label>
<input type="number" id="Quantity" name="quantity" value="1" min="1">
<button type="submit" name="add" id="AddToCart">Add to cart</button>
</form>
<div>{{ product.description }}</div>
{% form %}について
事前定義されたフォームタイプは次のとおりです。
activate_customer_passwordcontactcurrencycustomercreate_customercustomer_addresscustomer_loginguest_loginnew_commentproductrecover_customer_passwordreset_customer_passwordstorefront_password

{% form 'activate_customer_password' %} のように使う。(今回使うのはproduct)
例えば以下の出力例↓
{% form 'product', product, class:"product-form", id:"AddToCartForm" %}
{% endform %}<form method="post" action="/cart/add" id="AddToCartForm" accept-charset="UTF-8" class="product-form" enctype="multipart/form-data">
<input type="hidden" name="form_type" value="product" />
<input type="hidden" name="utf8" value="✓" />
</form>{% form 'product', product,← 最後のproductは引数。formタグはクラスもIDもつけやすくて便利だね。
product.liquid
{% section 'product-template' %}sections>product-template.liquid (カスタマイザーで動的に内容変えられるようにセクションで作る)
{% assign current_product = product.selected_or_first_available_variant %}
{% assign product_image = current_product.featured_image | default: product.featured_image %}
<div class="container">
<div class="row">
<div class="col-md-6 col-12">
<img src="{{ product_image | img_url: 'large' }}" alt="{{ product_image.alt }}" class="img-fluid" id="ProductMainImage">
{% for image in product.images %}
<img src="{{ image.src | img_url: 'medium' }}" alt="{{ image.alt }}">
{% endfor %}
</div>
<div class="col-md-6 col-12">
<h1>{{ product.title }}</h1>
<p>{{ current_product.price | money_with_currency }}</p>
{% form 'product', product, class:"product-form", id:"AddToCartForm" %}{%- comment -%}productは引数{%- endcomment -%}
<div class="mb-3">
<!-- バリエーション選択フォーム -->
<select name="id" id="productSelect" class="form-select">
{% for variant in product.variants %}
{% if variant.available %}
<option value="{{ variant.id }}">
{{ variant.title }}
</option>
{% else %}
<option value="{{ variant.id }}" disabled="disabled">
{{ variant.title }}
</option>
{% endif %}
{% endfor %}
</select>
</div>
<div class="mb-3">
<!-- 数量選択フォーム -->
<input type="number" class="form-control" name="quantity" id="Quantity" value="1" min="1">
</div>
<!-- カートボタン -->
<button type="submit" name="add" id="AddToCart" class="btn btn-secondary btn-lg w-100 rounded-0">カートに追加</button>
{% if section.settings.dynamic_button_checkbox == true %}
<!-- チェックアウトボタン -->
{{ form | payment_button }}
{% endif %}
{% endform %}
</div>
</div>
</div>
{% schema %}
{
"name": "Product pages",
"settings": [
{
"type": "checkbox",
"id": "dynamic_button_checkbox",
"label": "Enable Dynamic Buttons",
"default": false
}
]
}
{% endschema %}
チェックボックス セクションの作り方
{% schema %}
{
"name": "Product pages",
"settings": [
{
"type": "checkbox",
"id": "dynamic_button_checkbox",
"label": "Enable Dynamic Buttons",
"default": false
}
]
}
{% endschema %}
こんな感じでチェックボックス使う↓
{% if section.settings.dynamic_button_checkbox == true %}
<!-- チェックアウトボタン -->
{{ form | payment_button }}
{% endif %}カートページの作成
cart.liquidを編集して作ります。
{% if cart.item_count > 0 %}
<div class="container">
<div class="row">
<p class="h2 my-5 text-center">Shopping Cart</p>
</div>
<div class="my-5">
<form action="/cart" method="POST" class="row" novalidate>
<div class="col-12 col-md-8">
<div class="card shadow">
<div class="card-body">
<table class="tabele table-borderless">
<thead>
<th colspan="2">Prod</th>
<th>Price</th>
<th>Qty</th>
<th>total</th>
</thead>
<tbody>
{% for item in cart.items %}
<tr>
<td>
<a href="{{ item.url | within: collections.all }}">
<img src="{{ item | img_url: 'small' }}" alt="{{ item.title | escape }}">
</a>
</td>
<td>
<a href="{{ item.url }}">{{ item.product.title }}</a>
<p class="small">{{ item.variant.title }}</p>
<a href="/cart/change?line={{ forloop.index }}&quantity=0">remove</a>
</td>
<td>{{ item.price | money }}</td>
<td>
<input type="number" name="updates[]" id="updates_{{ item.key }}" value="{{ item.quantity }}" min="0">
</td>
<td>
{% if item.original_line_price != item.line_price %}{{ item.original_line_price | money }}{% endif %}
{{ item.line_price | money }}
{% for discount in item.discounts %}{{ discount.title }}{% endfor %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<div class="col-12 col-md-4">
<div class="card shadow">
<div class="card-body">
<p class="h3">The total amount is</p>
<p>{{ cart.total_price | money }}</p>
<p>sub total : {{ cart.total_price | money }}</p>
<div class="d-grid gap-2">
<button type="submit" class="btn btn-primary" name="update">Update</button>
<button type="submit" class="btn btn-primary" name="checkout">Checkout</button>
</div>
</div>
</div>
</div>
</form>
</div>
</div>
{% else %}
<div class="container">
<div class="row">
<h2 class="text-center">Cart</h2>
<p>Cart is empty</p>
</div>
</div>
{% endif %}
IMG_URLのサイズまとめ
img_urlのサイズ一覧です。
| 名前 | 実際のサイズ(px) |
| pico | 16 × 16 |
| icon | 32 × 32 |
| thumb | 50 × 50 |
| small | 100 × 100 |
| compact | 160 × 160 |
| medium | 240 × 240 |
| large | 480 × 480 |
| grande | 600 × 600 |
| original | 1024 × 1024 |

14. カスタムページ(About UsとFAQ)
Lorem生成ページ 参考

JSONオブジェクトでエラーがでたときに使いたい検証系ページ↓

カスタマイズページで選択肢から選ぶタイプのセクションを追加する方法
sectionsにファイルを作成(page-template.liquid)、以下のスキーマを設定する
{% schema %}
{
"name": "ページ設定 動画14",
"settings": [
{
"type": "select",
"id": "container",
"label": "レイアウト",
"options": [
{
"value": "container",
"label": "Container"
},
{
"value": "container-fluid",
"label": "Container Fluid"
}
],
"default": "container"
}
]
}
{% endschema %}選択肢型のセクションはこう使うと便利↓
{% if section.settings.container == "container" %}
{% assign container_class = 'container' %}
{% else %}
{% assign container_class = 'container-fluid' %}
{% endif %}
<div class="{{ container_class }}">...</div>これで、選択肢によってかんたんにデザインを変更することができます。
ちなみにcontainerクラスは左右両側に余白ができますが、container-fluidクラスの場合、fluid(液体)のように目一杯広がります(余白ができない)(Bootstrap最速の入門書)
1つのセクションの中にじゃんじゃか設定項目を増やす方法
{% schema %}
{
"name": "ページ設定 動画14",
"settings": [
{
"type": "select",
(省略)
} ←ここに,{}追加したらじゃんじゃか設定項目入れることができる。
]
}
{% endschema %}{% schema %}
{
"name": "ページ設定 動画14",
"settings": [
{
"type": "select",
(省略)
},
{
"type": "checkbox",
(省略)
}
]
}
{% endschema %}↓こんな感じ

日付フォーマットについて
<small>{{ page.published_at | date: '%B %d, %Y' }}</small>| フォーマット文字列 | 出力形式 |
|---|---|
| %A | 曜日の名称(Sunday, Monday ... ) |
| %a | 曜日の省略名(Sun, Mon ... ) |
| %B | 月の名称(January, February ... ) |
| %b | 月の省略名(Jan, Feb ... ) |
| %c | 日付と時刻 |
| %d | 日(01-31) |
| %H | 24時間制の時(00-23) |
| %I | 12時間制の時(01-12) |
| %j | 年中の通算日(001-366) |
| %M | 分(00-59) |
| %m | 月を表す数字(01-12) |
| %p | 午前または午後(AM,PM) |
| %S | 秒(00-60) (60はうるう秒) |
| %U | 週を表す数。最初の日曜日が第1週の始まり(00-53) |
| %W | 週を表す数。最初の月曜日が第1週の始まり(00-53) |
| %w | 曜日を表す数。日曜日が0(0-6) |
| %X | 時刻 |
| %x | 日付 |
| %Y | 西暦を表す数 |
| %y | 西暦の下2桁(00-99) |
| %Z | タイムゾーン |
| %% | %自身 |
"%Y年 %m月 %d日"
とかで使えます。

blocks :[{ }] について
blocks: [{}] は画像や商品など数が増える可能性がある設定を記述する。
{% schema %}
{
"name": {
"ja": "セクション名"
},
"class": "セクションに自動で追加されるclass名",
"blocks": [
// 画像や商品など数が増える可能性がある設定を記述する
],
"settings": [
// 見出しやテキストなど数が増えない設定を記述する
],
"presets": [
{
"name": {
"ja": "管理画面のセクション一覧に表示されるセクション名"
},
"category": {
"ja": "管理画面のセクション一覧のグループに表示される名前"
}
}
]
}
{% endschema %}blocksは、以下のように記述します。
"blocks":[
{
"type": "custom",
"name": {
"ja": "画像"
},
"limit": 3,
"settings": [
{
"type": "image_picker",
"id": "image_picker",
"label": {
"ja": "画像"
}
}
]
}
],| プロパティ名 | 解説 |
| type | 基本的にcustomにしておけば大丈夫だと思います。公式ドキュメントを読んでも、blocksのtypeを何にすればいいのかイマイチ仕様がわかりませんでした…。 →Udemyでは、"You can set your own types of blocks type and your own type." → つまり、自分自身でタイプを作ることができるということか? |
| name | 管理画面に表示される設定の名前 |
| limit | 設定を複数登録する場合の上限数 |
| settings | settingsで設定を定義します。 |
settingsとblocksの違いですが、、見出しやテキストのような設定する値が1つの場合はsettings、商品や画像のような値を複数設定する可能性があるものはblocksにする…のような違いがあります。

blocksの配列には複数のオブジェクトを入れることができる
ちなみに、下のようにblocksの[ ]の中に、追加でオブジェクトを入れることができる。
"blocks":[
{
"type": "custom",
(省略)
},
{
"type": "image",
(省略)
}
],オブジェクトを追加すると、カスタマイズ画面で複数選択が可能になる。

上記はUdemy動画20回目の12分から説明がある。
出力する際は、if block.type =="" みたいな感じで分岐させて呼び出す。
blocksを呼び出す
blocksは値が複数(配列)ある場合があるので、forで回して値を呼び出します。
{% for block in section.blocks limit: section.blocks.size %}
{% if block.settings.image_picker != blank %}
{{ block.settings.image_picker | img_url: '300×' | img_tag }}
{% endif %}
{% endfor %}サンプルコードでは、上記のように呼び出しています。
- section.blocksオブジェクトをforで回しblockに代入
- if文でblock.settings.image_pickerオブジェクトだったら、その値を呼び出す
blocksを呼び出す 2
<div class="container">
<div class="accordion accordion-flush" id="accordionFAQ">
{% for block in section.blocks %}
<div class="accordion-item">
<h2 class="accordion-header" id="{{ block.id }}">{{ block.settings.title }}</h2>
</div>
{% endfor %}
</div>
</div>
{% schema %}
{
"name": "FAQ",
"blocks": [
{
"name": "FAQ Items",
"type": "faq",
"settings": [
{
"type": "text",
"id": "title",
"label": "Title(ラベル)",
"default": "FAQ Title Example"
},
{
"type": "richtext",
"id": "answer",
"label": "Answer"
}
]
}
]
}
{% endschema %}block.idは次のように出力される→id="54b0dc9a-2e1d-405d-baaf-d8f220748147"
block.idは、for文で作られる block ごとに違う一意のIDをもっている。(例:次のループでは、block.idはcefbb22d-cb0e-4105-913b-eed3f4cb958fでさっきと全く異なる)
blocks全体の上限を設ける max_blocks
blcoksのsettingsを作れる数に上限を設けるlimitがあるが、
max_blocksも、blockの追加に制限を加えることができます。
limitとの違いとしては、様々な種類のblockに対して適用でき、その合計数を制限します。
{% schema %}
{
"name": "Slideshow",
"max_blocks": 4,
"blocks": [
{
(省略)
}
]
}
{% endschema %}
とても大事な注意点
管理画面上でセクションのテキストや画像を設定する。
この行為を行うと、本番環境にアップされているsettings_data.jsonのファイルに、管理画面で行ったアクション(テキストの追加や画像の設定など)が反映されます。
例:画像を追加したら、その画像のURLなどの情報がsettings_data.jsonのファイルに追記されます
つまり、管理画面で色々操作をすると、その分、本番環境のsettings_data.jsonが更新されるので、ローカル環境のsettings_data.jsonと差分が生まれます。
もう何が言いたいのか分かるとは思いますが、差分がある状態で、watch中にローカル環境のsettings_data.jsonを何かしら変更して保存をすると、その内容で本番環境のsettings_data.jsonが上書きされてしまいます。
なので、管理画面で色々操作した内容が無くなってしまいます。
これを防ぐには、管理画面で操作をしたら、管理画面 > テーマ > アクション > コード編集するからsettings_data.jsonのコードをコピーしてローカル環境のsettings_data.jsonに反映させるしかないです。
このフローを忘れなければ、色々設定をしたにも関わらず、それがいつの間にか元に戻ってしまったという恐ろしいことは起きないと思います。
settingsのプロパティ解説
| プロパティ名 | 解説 |
| type | 設定の種類。一行のテキストボックスの場合はtext、複数行の場合はtextarea、チェックボックスの場合はcheckboxのように記述します。種類によって戻り値が異なり、textの場合は入力したテキスト、checkboxの場合はtrue/falseで返ってきます。他にも画像や商品、コレクションなども設定できます。詳しくは公式ドキュメントを参照ください。 |
| id | 一意のユニークな値を設定します。このid名を使い、liquid側で設定した値を呼び出します。変数名のようなイメージです。 |
| label | 管理画面に表示される設定の名前 |
| info | 管理画面に表示される設定の説明文 |
| default | 設定のデフォルト値typeがtextの場合、default: "デフォルトテキスト"のように記述しておくと、「デフォルトテキスト」が最初から入力された状態になります。 |
その他
- richtextタイプは textタイプと違って文字を太くしたり、リンクをつけられたり等できる。
15. コレクションページ
templates > collection.liquid
{% paginate collection.products by 2 %}
<h1>{{ collection.title }}</h1>
{% for product in collection.products %}
<div>
<a href="{{ product.url | within: collection }}">{{ product.title }}</a>
{{ product.price | money }}
{% unless product.available %}<br><strong>sold out</strong>{% endunless %}
<a href="{{ product.url | within: collection }}">
<img src="{{ product.featured_image.src | img_url: 'large' }}" alt="{{ product.featured_image.alt | escape }}">
</a>
</div>
{% else %}
<p>no matches</p>
{% endfor %}
{% if paginate.pages > 1 %}
{{ paginate | default_pagination }}
{% endif %}
{% endpaginate %}{% paginate collection.products by 2 %} ← 1ページに表示する商品の数
16. ページネーション
ページネートはこんな感じで使う。デフォルトは{{ paginate | default_pagination }}
{% paginate collection.products by 1 %}
(省略)
{% if paginate.pages > 1 %}
<nav aria-label="Page navigation">
<ul class="pagination justify-content-center">
{% if paginate.previous.is_link %}
<li class="page-item"><a href="{{ paginate.previous.url }}" class="page-link">{{ paginate.previous.title }}</a></li>
{% endif %}
<!-- 1ページ、2ページといったそれぞれのページパート -->
{% for part in paginate.parts %}
{% if part.is_link %}
<li class="page-item"><a href="{{ part.url }}" class="page-link">{{ part.title }}</a></li>
<!-- リンクがない→いまこのパートページにいる場合 -->
{% else %}
<li class="page-item disabled"><a href="{{ part.url }}" class="page-link">{{ part.title }}</a></li>
{% endif %}
{% endfor %}
{% if paginate.next.is_link %}
<li class="page-item"><a href="{{ paginate.next.url }}" class="page-link">{{ paginate.next.title }}</a></li>
{% endif %}
</ul>
</nav>
{% endif %}
{% endpaginate %}17. 製品の並べ替え
まず、並べ替え機能を実装したいページに以下のようなコードを実装(Bootstrap)
<div class="row justify-content-center">
<select name="sorting" id="sort_by" class="form-select form-select-sm w-25">
{% for option in collection.sort_options %}
<option value="{{ option.value }}"
{% if option.value == collection.sort_by%}
selected
{% endif %}
>{{ option.name }}</option>
{% endfor %}
</select>
</div>ただし、これだけでは動的に並べ替えを行うことはできないので、javascriptを変更する。
assets > application.js
if( document.getElementById('sort_by') != null){
document.querySelector('#sort_by').addEventListener('change', function(e) {
var url = new URL(window.location.href);
url.searchParams.set('sort_by', e.currentTarget.value);
window.location = url.href;
})
}18. ブログ一覧ページ
blog.liquid
スニペットを使用して、ページネーションを使い回す。
スターターファイルにはsnippetsフォルダがないので、新しくsnippetsフォルダを作成する。
綴に注意すること。
使いまわしたいコードをsnippetsフォルダの新規フォルダに書いたら、
{% render '読み込みたいファイル名' %}と記述する。(includeは現在非推奨)
19. 記事ページ
article.liquid
date: format: オプション
time_tagフィルタにformatパラメータを渡すことで、ストアフロントの言語の日付フォーマットを出力することができます。
利用可能なフォーマットは以下の通りです。
※ time_tagだけでなく、dateタグでも同様にformatが使える。
- abbreviated_date
- basic
- date
- date_at_time
- default
- on_date
{{ article.published_at | time_tag: format: 'date' }}<time datetime="2018-12-31T18:00:00Z">December 31, 2018</time>
さまざまな言語で表示されるformatオプションのプレビューを以下の表にまとめています。
| format | English | French | Japanese |
|---|---|---|---|
| abbreviated_date | Dec 31, 2018 | 31 déc 2018 | 2018年12月31日 |
| basic | 12/31/2018 | 31/12/2018 | 2018/12/31 |
| date | December 31, 2018 | 31 décembre 2018 | 2018年12月31日 |
| date_at_time | December 31, 2018 at 1:00 pm | 31 décembre 2018 à 13:00 | 2018年12月31日 13:00 |
| default | Monday, December 31, 2018 at 1:00 pm -0500 | lundi 31 décembre 2018 à 13:00 -0500 | 2018年12月31日(月曜日) 13:00 -0500 |
| on_date | on Dec 31, 2018 | le 31 déc 2018 | 2018年12月31日で |
| 以降、動画 | |||
| long | 4月 1, 2021 11:57 | ||
| short | 1 4月 11:57 |
日付の書式は、テーマのローカル設定で定義することも出来ます。←動画のはこれ
theme/locales/en.json
"date_formats": {
"month_day_year": "%B %d, %Y"
}etc.liquid
{{ article.published_at | time_tag: format: 'month_day_year' }}<time datetime="2016-02-24T14:47:51Z">February 24, 2016</time>
format のエラー
Liquid error: The format option 'month_day_year' is not a supported format.
上記がブログ記事画面に出力される場合は、'month_day_year'を任意のフォーマット名(abbreviated_dateなど)に変更する。
コメント機能について
コメント機能(if blog.comments_enabled?以下)を有効化するには、ブログ記事>ブログを管理する>該当ブログ記事>コメントにて、設定する(デフォルトでは無効になっている)
20. サイドバー
sections > sidebar.liquid
blocksを使って作成する。以前のblocksの説明部分を参照。
21. 404ページ
404.liquid
<h1>{{ 'general.404.title' | t }}</h1> とはなにか
404.liquidを開くと上記のような{{ 'general.404.title' | t }}といったコードが出てくる。
<h1>{{ 'general.404.title' | t }}</h1>
<p>{{ 'general.404.subtext_html' | t }}</p>これは、locales > en.default.json で設定されているのを呼び出したもの。
en.default.json
{
"general": {
"404": {
"title": "Not found",
"subtext_html": "The page you were looking for does not exist"
}
}
}翻訳フィルターで、ja.jsonのファイルを読み込み翻訳してくれます。
ja.json
{
"general": {
"404": {
"title": "ページが見つかりませんでした。"
}
}
}localesディレクトリには、言語ごとの翻訳jsonファイルを格納します。(日本語だとja.json)
jsonファイル上で翻訳箇所を定義すれば、管理画面の言語編集から修正することも可能です。
Shopify管理画面のテーマ言語エディタ(コード編集画面から入れる)
settings_data.jsonと同じく、管理画面から修正した内容は翻訳jsonファイルに保存される(ローカルのファイルには変更が反映されない)ので、管理画面経由で修正したものをローカルの値で上書きしないように、運用ルールを設けておくと良いでしょう。
デフォルトで作成されるのはen.default.jsonファイルだけなので、適宜、翻訳の定義を追加した段階で複製し、ja.jsonとして日本語の内容を追加します。

22. フッター
sections > footer-section.liquid
footer-section.liquidに、</body>上のスクリプトタグも入れてしまおう。
セクションで選べるtypeの種類解説
入力タイプの種類(基本)
| text | テキスト![]() {
"type": "text",
"id": "my_text_name",
"label": "テキストのラベル",
"default": "テキストの初期値"
} |
|---|---|
| textarea | テキストエリア![]() {
"type": "textarea",
"id": "my_textarea_name",
"label": "テキストエリアのラベル",
"default": "テキストエリアの初期値"
} |
| image_picker | 画像![]() {
"type": "image_picker",
"id": "my_image_picker_name",
"label": "画像のラベル"
} |
| radio | ラジオボックス![]() {
"type": "radio",
"id": "my_radio_name",
"label": "ラジオボタンのラベル",
"options": [
{ "value": "a", "label": "A"},
{ "value": "b", "label": "B"},
{ "value": "c", "label": "C"}
]
} |
| select | 選択![]() {
"type": "select",
"id": "my_select_name",
"label": "セレクトボックスのラベル",
"options": [
{ "value": "a", "label": "A"},
{ "value": "b", "label": "B"},
{ "value": "c", "label": "C"}
]
} |
| checkbox | チェックボックス![]() {
"type": "checkbox",
"id": "my_checkbox_name",
"label": "チェックボックスのラベル",
"default": true
} |
| range | レンジ![]() {
"type": "range",
"id": "my_range_name",
"min": 1,
"max": 10,
"step": 1,
"unit": "単位",
"label": "レンジのラベル",
"default": 2
} |
入力タイプの種類(リッチ)
次にリッチな入力を影響できる入力タイプを紹介していきます。最初のほうで紹介したカラーピッカーのように標準の入力よりもより操作性高く入力できるようになります。
| color | カラーピッカー![]() {
"type": "color",
"id": "my_color_name",
"label": "色のラベル",
"default": "#333"
} |
|---|---|
| video_url | 動画URL![]() {
"type": "video_url",
"id": "my_video_url_name",
"label": "動画URLのラベル",
"accept": ["youtube", "vimeo"]
} |
| richtext | リッチテキスト![]() {
"type": "richtext",
"id": "my_richtext_name",
"label": "リッチテキストのラベル",
"default": "<p><strong>リッチテキスト<\/strong>を挿入できます。</p>"
} |
| html | カスタムHTML![]() {
"type": "html",
"id": "my_html_name",
"label": "HTMLのラベル",
"default": "<p>HTMlタグを挿入できます。</p>"
} |
入力タイプの種類(Shopifyのアイテム)
そして、Shopifyの各コンテンツを設置するための入力タイプも標準でたくさん用意されています。
| font_picker | フォント選択![]() {
"type": "font_picker",
"id": "my_font_picker_name",
"label": "フォントのラベル",
"default": "helvetica_n4"
} |
|---|---|
| collection | コレクション![]() {
"type": "collection",
"id": "my_collection_name",
"label": "コレクションのラベル"
} |
| product | 商品詳細![]() {
"type": "product",
"id": "my_product_name",
"label": "商品詳細のラベル"
} |
| blog | ブログ(カテゴリー)![]() {
"type": "blog",
"id": "my_blog_name",
"label": "ブログのラベル"
} |
| page | 個別記事![]() {
"type": "page",
"id": "my_page_name",
"label": "記事のラベル"
} |
| link_list | メニュー![]() {
"type": "link_list",
"id": "my_link_list_name",
"label": "メニューのラベル"
} |
| url | URL(全ページ対象)![]() {
"type": "url",
"id": "my_url_name",
"label": "URLのラベル"
} |
| article | ブログ詳細![]() {
"type": "article",
"id": "my_article_name",
"label": "記事詳細のラベル"
} |
23. 検索フォーム
section.settings.image.alt について
{% schema %}の中で設定しなくても、カスタマイザーの画像を編集でaltを設定すれば、section.settings.image.alt を呼び出すことができる。
検索フォームのコード
<form action="/search" method="GET" role="search">
<input type="text" name="q" value="{{ search.terms | escape }}">
</form>上記で検索窓ができる。
検索結果画面
search.liquid
{{ }} の中で、クラス名を指定する
例)class="card-img-top"をつけたい。
{{ item.featured_image.src | img_url: 'medium' | img_tag: item.featured_image.alt, 'card-img-top' }}, 以降で、クラス名を指定できる。
<img src="//cdn.shopify.com/s/files/1/0558/0727/9277/products/villa-1_medium.jpg?v=1617000481" alt="Example Pants" class="card-img-top">25. 翻訳の追加
テーマ言語エディタ(管理画面で言語の翻訳作業ができるやつ)は、管理画面>コードを編集>Localsのファイルを開くと表示
管理画面>テーマのアクション>言語を編集から行ったほうが早い
Locales > en.default.json に関して
テーマをインストールしたとき、Localesには、en.default.jsonがインストールされている。
これは、ローカルのデフォルトは英語ですよーという意味だが、これを "ja.default.json"とするとデフォルトを日本語にすることができる。
となると、日本でテーマ開発をするなら、ja.default.json とするのがよいのだろうか。自分はよくわからない。
というのも、そもそもShopifyで無料・有料テーマを...
Localesファイルは、調べた感じ、テーマを国際化対応させるために作るファイルである。
→ 自分が作ったテーマを、海外の方にも使ってもらうために作るもの。だと思う。
→ このファイルを作ったら外国人がサイトを見たときに翻訳されているといった類のものではない。
テーマを導入するユーザーが、導入後に自分で入力する動的なコンテンツ以外はこっちですでに日本語化できることになる。
26. ログイン、ユーザー登録メニュー(ヘッダー)
header.liquid
{% if shop.customer_accounts_enabled %}
{% if customer %}
<a href="/account">account</a>
{{ 'log out' | customer_logout_link }}
{% else %}
{{ 'log in ' | customer_login_link }}
{{ 'register' | customer_register_link }}
{% endif %}
{% endif %}をいじる。
現状では、チェックアウトを完了するために顧客アカウントが必要な場合のみ、ヘッダー画面にログインなどが表示されるようになっている。
設定>チェックアウト>顧客アカウント から、「アカウントを無効化」「アカウントを任意化」「アカウントを必要とする」を選べる。
「アカウントを無効化」以外を選ぶと、"shop.customer_accounts_enabled" がtrueを返す。
27. ログインページ
templates > customers > login.liquid
基本的に、{% form %}を使うときは、{% for error in form.errors %}がセットでついてくる。
article.liquidのところで解説。
なので、{% for error in form.errors %}のところはスニペット化しておくのがおすすめ。(やらないのももちろんあり)
28. サインアップページ
register.liquid
29. アカウントページ
account.liquid
管理画面から注文を作成
注文は管理画面からでも作成することができる。
30. アドレスページ
addresses.liquid
application.js
やはりドキュメントは読むべし。

国名を選択すると選択した国の州・県を選択肢として選べるようにする方法
<div class="col-12 col-md-4 mb-3">
<label for="AddressCountryNew" class="form-label">国名</label>
<select id="AddressCountryNew" name="address[country]" class="form-control">
{{ all_country_option_tags }}
</select>
</div>
<div class="col-12 col-md-4 mb-3">
<label for="AddressProvinceNew" class="form-label">都道府県</label>
<select id="AddressProvinceNew" name="address[province]" class="form-control" disabled="disabled"></select>
</div>{{ all_country_option_tags }} は下のようにHTMLに出力される。
<option value="Iceland" data-provinces="[]">アイスランド</option>
<option value="Ireland" data-provinces="[["Carlow","
カーロウ州"],["Cavan","キャバン州"],
["Clare","クレア州"],["Cork","コーク州
"],["Donegal","ドニゴール州"],
["Dublin","ダブリン州"],["Galway","
ゴールウェイ州"],["Kerry","ケリー州"],
(以下省略)州・県がある国には、data-provinces属性に各州県が出力される。
application.js
if ( document.getElementById('AddressCountryNew') != null) {
document.getElementById('AddressCountryNew').addEventListener('change', function(e) {
var provinces = this.options[this.selectedIndex].getAttribute('data-provinces');
var provinceSelecter = document.getElementById('AddressProvinceNew');
var provinceArray = JSON.parse(provinces);
// console.log(provinceArray);
if (provinceArray.length < 1) {
provinceSelecter.setAttribute('disabled', 'disabled');
} else {
provinceSelecter.removeAttribute('disabled');
}
provinceSelecter.innerHTML = '';
var options = '';
for (var i = 0; i < provinceArray.length; i++) {
options += '<option value="' + provinceArray[i][0] + '">' + provinceArray[i][1] + '</option>'
}
provinceSelecter.innerHTML = options;
})
}ちなみに、11行目の、//consle.log(provinceArray); の出力例(日本)
0: (2) ["Aichi", "愛知県"]
1: (2) ["Akita", "秋田県"]
2: (2) ["Aomori", "青森県"]
3: (2) ["Chiba", "千葉県"]
4: (2) ["Ehime", "愛媛県"]
5: (2) ["Fukui", "福井県"]
6: (2) ["Fukuoka", "福岡県"]
7: (2) ["Fukushima", "福島県"]
8: (2) ["Gifu", "岐阜県"]
9: (2) ["Gunma", "群馬県"]
10: (2) ["Hiroshima", "広島県"](以下省略)(課題)郵便番号で検索をかけられるようにする。
住所情報を削除する方法
<div class="card">
<div class="card-body">
{{ address | format_address }}
</div>
<div class="card-footer">
<form
action="/account/addresses/{{ address.id }}"
method="POST"
data-config-message="Delete Address"
>
<input type="hidden" name="_method" value="delete">
<button class="btn btn-danger">削除</button>
</form>
</div>
</div>削除する際の確認画面を設ける必要がある。
31. 注文ページ
order.liquid
もともとあったコード
<!-- /templates/customers/order.liquid -->
<h2>Billing Address</h2>
<p><span>Payment Status:</span> <span class="status_{{ order.financial_status }}">{{ order.financial_status }}</span></p>
<p>{{ order.billing_address.name }}</p>
<p>{{ order.billing_address.company }}</p>
<p>{{ order.billing_address.street }}</p>
<p>{{ order.billing_address.city }}, {{ order.billing_address.province }}</p>
<p>{{ order.billing_address.country }} {{ order.billing_address.zip }}</p>
<p>{{ order.billing_address.phone }}</p>
<h2>Shipping Address</h2>
<p><span>Fulfillment Status:</span><span class="status_{{ order.fulfillment_status }}">{{ order.fulfillment_status }}</span></p>
<p>{{ order.shipping_address.name }}</p>
<p>{{ order.shipping_address.company }}</p>
<p>{{ order.shipping_address.street }}</p>
<p>{{ order.shipping_address.city }}, {{ order.shipping_address.province }}</p>
<p>{{ order.shipping_address.country }} {{ order.shipping_address.zip }}</p>
<p>{{ order.shipping_address.phone }}</p>
<h2>Order Items</h2>
<table>
{% for line_item in order.line_items %}
<tr>
<td>{{ line_item.title | link_to: line_item.product.url }}</td>
<td>{{ line_item.sku }}</td>
<td>{{ line_item.original_price | money }}</td>
<td>{{ line_item.quantity }}</td>
<td>{{ line_item.line_price | money }}</td>
</tr>
{% endfor %}
</table>
32. パスワードリセット & アカウント有効化ページ
アカウントを有効化させる
まだ、アカウントが有効になっていない(メールアドレスに届いたメールから有効化リンクをクリックしていない)場合は、管理画面>顧客管理>顧客画面>(右上)「アカウントの招待を送信する」から有効かメールを再送する
アカウント有効化ページ
アカウントを作った際に送られるメールから「アカウントを有効化する」リンクをクリックして表示されるページ。パスワードを登録して有効化完了となる。
acrivate_account.liquid
初期
<!-- /templates/customers/activate_account.liquid -->
{% form 'activate_customer_password' %}
{{ form.errors | default_errors }}
<div class="password">
<label for="password">Password</label>
<input type="password" name="customer[password]" />
</div>
<div class="password_confirm">
<label for="password_confirmation">Password Confirmation</label>
<input type="password" name="customer[password_confirmation]" />
</div>
<div class="action_bottom">
<input type="submit" value="Activate Account" />
<span>or</span>
<input type="submit" name="decline" value="Decline Invitation" />
</div>
{% endform %}リセットパスワードページ
まずは、ログインページに「リセットパスワード」機能を実装(login.liquid, application.js)
<!-- /templates/customers/login.liquid -->
<div class="container py-5 my-5">
<div class="login-form">
{% form 'customer_login' %}
(省略)
{% endform %}
<a href="#recover" id="forgetPassword" class="text-center">パスワードをお忘れですか?</a>
</div>
<div id="forget_password_form" class="forget-password-form d-none">
{% form 'recover_customer_password' %}
{% render 'form-error', form: form %}
<div class="col-12">
<label for="resetPassword" class="form-label">Email アドレス</label>
<input class="form-control" type="email" name="email" id="resetPassword">
</div>
<input type="submit" value="パスワードをリセット" class="btn btn-secondary">
{% endform %}
</div>
</div>if (document.getElementById('forgetPassword') != null ) {
document.getElementById('forgetPassword').addEventListener('click', function(e) {
// document.getElementById('forget_password_form').className.replace(/\bd-none\b/, 'd-block');
const element = document.querySelector('#forget_password_form');
if(element.classList.contains('d-none')) {
element.classList.remove('d-none');
element.classList.add('d-block');
}
})
}正規表現 参考
RegExpoについて 参考

reset_password.liquid 初期状態
<!-- /templates/customers/reset_password.liquid -->
{% form 'reset_customer_password' %}
{{ form.errors | default_errors }}
<label for="password">Password</label>
<input type="password" value="" name="customer[password]" size="16" />
<label for="password_confirmation">Password Confirmation</label>
<input
type="password"
value=""
name="customer[password_confirmation]"
size="16"
/>
<input type="submit" value="Reset Password" />
{% endform %}パスワードに制限を加える
inputタグにsize属性を追加する。
33. 次にやるべきこと
- liquidフィルターなど、文法の確認
- テーマを最適化する
- 再利用可能なコンポーネントをスニペット化する
- product-card.liquidなど
- 再利用可能なコンポーネントをスニペット化する
- Bootstrapのマスター
34. 言語選択機能をショップに追加する(アプリ不使用)
ショップにサイトで表示したい言語の選択肢をドロップダウンで表示する方法
管理画面>設定>ストアの言語>翻訳された言語「言語を追加する」をクリック→公開するをクリック
試しに英語を追加すると、(shopifyサイトのURL/en)へのプレビューリンクが作成される。
ちなみにページ右側の「言語を追加する」からも同じようにできる。
言語選択画面を実装
sections に translation.liquidを作成し、theme.liquidで読み込む。
translation.liquid
{% form 'localization', id: "localization_form_tag", class: "dropup" %}
<div class="localeBtn">{{ 'globe.png' | asset_img_url: '50px' | img_tag }}</div>
<div class="dropup-content">
{% for locale in shop.published_locales %}
<a href="#" id="localeItem" lang="{{ locale.iso_code }}">{{ locale.endonym_name }}</a>
{% endfor %}
</div>
<input type="hidden" name="locale_code" id="localeCode" value="{{ form.current_locale.iso_code }}">
{% endform %}application.js
var localeItems = document.querySelectorAll('#localeItem');
if (localeItems.length > 0 ) {
localeItems.forEach(item => {
item.addEventListener("click", event => {
document.getElementById('localeCode').value = item.getAttribute('lang');
document.getElementById('localization_form_tag').submit();
})
})
}言語を選択できる機能が実装できたら、表示したい言語を選択してみる。
言語を選択すると、(サイトURL/en)といったページになり、かつ(lang="en")と変化してくれる。
localesフォルダに選択した言語のファイルがある場合、そのファイルを読み込んで表示してくれる。
locales > en.json
{
"general": {
"404": {
"title": "Not Found",
"subtext_html": "The page you looking for is not exist."
},
"Cart": {
"title": "Cart"
},
"Layout": {
"login_text": "Login",
"register_text": "Register",
"account_text": "User"
}
}
}つまり、localesファイルはテーマ利用者だけでなく、サイト利用者にも意味があった!
疑問:
作成者がカスタマイズ画面で書き込んだ文字はこれだと変換されないので、別途やはりアプリを使う必要があるのではないか?
36. ストアの支払い方法のリストを表示する
何で支払うことが可能か。そのアイコンを表示してくれるリストを出力してくれる。
<div class="container justify-content-center d-flex">
{% for payment_type in shop.enabled_payment_types %}
<img src="{{ payment_type | payment_type_img_url }}">
{% endfor %}
</div>ただし、支払い方法がそもそも追加されていないと出てこないので注意。





















