HubL - 条件分岐
本ページではHubLにおける条件分岐の基本を解説します。「何がtrue/falseとなるのか?」というところから、さまざまなケース別の条件分岐の書き方まで紹介しています。
目次
truthyな値、falsyな値を知る
ここで扱う型は
- Boolean(真偽値)
- Number(数値)
- String(文字列)
- List(リスト)
- Dictionary(ディクショナリ)
です。以下の例は、全てfalseとなります。判定方法は、いずれもif文に変数名だけを渡すシンプルなものです。
{% set bool = false %} {% set num = 0 %} {% set str = '' %} {% set list = [] %} {% set dict = {} %} {# 判定方法のコード例 #} {% if bool %} boolはtrue {% else %} boolはfalse {# こちらが出力される #} {% endif %}
次のコードは全てtrueとなります。
{% set bool = true %} {% set num = -1 %} {% set str = '1' %} {% set list = [1] %} {% set dict = { 'hoge': 1, 'fuga': 2 } %}
boolはtrueとなって当然ですね。listとdictに関しても、要素が1つでもあればtrueとなることがわかります。厳密に判定するのであればlengthフィルタを使用したいところですが、とりあえずこれで必要十分です。
数値に関しても、falsyなのは0のみであり、その他の値はマイナスの値、自然数含め全てtruthyです。ちなみに-0もtruthyです。
気をつけたいのは文字列です。JavaScriptでもありがちな話ですが、文字列で'false'や'0'とした場合、truthyとなります。以下はいずれもtrueと判定されます。
{% set str = 'false' %} {% set str = '0' %}
数値のような文字列に関しては、roundフィルタを使用することにより、数値型へとキャストできます。次のコードはfalsyです。
{% set str = '0' %} {% if str|round %} strはtrue {% else %} strはfalse {# こちらが出力される #} {% endif %}
条件分岐の書き方の基本
次に、いろいろな条件分岐の書き方を紹介します。どの例においても、値が数値型「2」である変数hogeを使用します。
{# サンプルとして使用する変数 #} {% set hoge = 2 %}
if文、elif文、else文、unless文
まずは基本のif文です。→公式ドキュメント:If statements
{% if hoge == 2 %} hogeは2 {% elif hoge == 3 %} hogeは3 {% else %} hogeは他の数値 {% endif %}
使用出来る比較演算子は下記の通りです。これは他のプログラミング言語と大差ないため、詳しくは説明しません。
==、!=、>、>=、<、<=
なおifと==が「〜であれば」を表すのに対し、「〜でなければ」を表すunless文というものも存在します。次のコードは、unless文の中のコードが表示されます。
{% unless hoge == 3 %} hogeは3ではない {# hogeは2のため、表示される #} {% endunless %}
ただしunless文は、基本的にif文と!=を使用することで等価となります。コード規約的な話になりますが、ややこしさを助長するだけなので基本的にunless文は使わない方がよいでしょう。
{% if hoge != 3 %} hogeは3ではない {# hogeは2のため、表示される #} {% endif %}
なおもう1つ等価の書き方として、is演算子とequalto、論理演算子のnotを組み合わせる方法もあります。しかし、この程度の例ではかえって冗長な書き方なので推奨しません。
{% if not hoge is equalto 3 %} hogeは3ではない {# hogeは2のため、表示される #} {% endif %}
論理演算子
1つの文で複数の式を組み合わせるには、論理演算子を使用します。これも、他のプログラミング言語と大差ありません。HubLの場合は記号ではなく、英単語を使用します。例として、値が3である変数fugaを追加します。→公式ドキュメント:Logical
{% set hoge = 2 %} {% set fuga = 3 %} {%- if hoge == 2 and fuga == 3 -%} hogeは2 かつ fugaは3 {%- endif -%} {%- if hoge == 2 or fuga == 3 -%} hogeは2 または fugaは3 {%- endif -%}
真偽値を反転するには、notを使用します。また公式ドキュメントには載っていませんが、一応「!」を使用して真偽値を反転することもできます。
{% set piyo = false %} {% if not piyo %} {# 「piyoがtrueであれば」の反転 → 「piyoがfalseであれば」 #} piyoはfalse {# 表示される #} {% endif %} {% if !piyo %} piyoはfalse {# 表示される #} {% endif %}
ただし、notを使用した条件分岐はえてして冗長になりがちです。多くの場合はnotを使用せず、通常のif文で記述することができます。
{% set hoge = 2 %} {% set fuga = 3 %} {%- if not (hoge == 4) -%} hogeは4ではない {%- endif -%} ↓ {%- if hoge != 4 -%} hogeは4ではない {%- endif -%} {%- if not (hoge == 4 and fuga == 5) -%} hogeは4ではない かつ fugaは5ではない {%- endif -%} ↓ {%- if hoge != 4 and fuga != 5 -%} hogeは4ではない かつ fugaは5ではない {%- endif -%} {%- if not (hoge == 4) or not (fuga == 5) -%} hogeは4ではない または fugaは5ではない {%- endif -%} ↓ {%- if hoge != 4 or fuga != 5 -%} hogeは4ではない または fugaは5ではない {%- endif -%}
ifchanged
HubLがサポートしている少し変わった条件分岐文に、ifchangedというものがあります。これは「前回ifchangedで評価した変数の値と比べて、値が変わっていれば文の中のコードを実行する」というものです。初回は必ず実行されます。コードのイメージとしては次の通りです。
{% set foo = 'foo' %} {% ifchanged foo %} fooが変わりました1 {# 初回なので実行される #} {% endifchanged %} {% ifchanged foo %} fooが変わりました2 {# 変数値に変更がないため、実行されない #} {% endifchanged %} {% set foo = 'bar' %} {% ifchanged foo %} fooが変わりました3 {# 変数値に変更があったので、実行される #} {% endifchanged %}
条件分岐をスッキリ書く(In-line ifと三項演算子、論理演算子)
In-line ifまたは三項演算子を利用することによって、条件分岐を1行でスッキリと書くことができます。その場ですぐ出力したい場合と、変数にセットしたい場合に分けて紹介します。→公式ドキュメント:In-line if statements
その場で値を出力する場合{% set is_blue = true %} {# In-line if #} <p class="{{ 'color-blue' if is_blue else 'color-red' }}">青の文字</p> {# 三項演算子 #} <p class="{{ is_blue ? 'color-blue' : 'color-red' }}">青の文字</p>変数にセットする場合
{% set is_blue = true %} {# In-line if #} {% set color_class = 'color-blue' if is_blue else 'color-red' %} <p class="{{ color_class }}">青の文字</p> {# 三項演算子 #} {% set color_class = is_blue ? 'color-blue' : 'color-red' %} <p class="{{ color_class }}">青の文字</p>
実務としては、私はよくカスタムモジュールのリンクフィールドのパラメーターを受け取るときに、よく三項演算子を利用します。
{% set is_blank = true %} {% set is_noopener = true %} {# 三項演算子を利用しない場合 #} {% set ex_attr = '' %} {%- if is_blank -%} {% set ex_attr = ex_attr + 'target="_blank"' %} {%- endif -%} {%- if is_noopener -%} {% set ex_attr = ex_attr + 'rel="noopener"' %} {%- endif -%} <a href="#" {{ ex_attr }}>hoge</a> {# 三項演算子を利用した場合 #} {% set ex_attr = is_blank ? 'target="_blank"' : '' %} {% set ex_attr = is_noopener ? ex_attr + 'rel="noopener"' : ex_attr %} <a href="#" {{ ex_attr }}>hoge</a>
等価ではありませんが、似たケースでよく用いるものにdefaultフィルタがあります。ケースに応じて、1番スッキリ書ける方法を選択するとよいでしょう。
また変数の値をそのまま利用できる場合は、論理演算子 ||
を利用することもできます。以下のコードは「html_tagに値があればそれを、無ければpを使用する」というコードです。
{% set tag = html_tag || 'p' %}
isを利用したさまざまな式評価
論理演算子のisと他のキーワードを組み合わせると、さまざまな式評価を行うことが可能になります。全ての場合において、返値はBooleanのtrueまたはfalseです。→公式ドキュメント:Expression tests
変数・型の判定
変数が宣言されているか?(defined, undefined)
{% set bar = '' %} {{ bar is defined }} {# true #} {{ lorem is undefined }} {# 変数loremを宣言してないため、undefinedになる→true #}
nullか?(none)
{{ lorem is none }} {# true undefinedでもtrueとなる #} {% set lorem = null %} {{ lorem is none }} {# true #} {% set lorem = '' %} {{ lorem is none }} {# false #}
trueになるか?(truthy)
{% set lorem = 'lorem' %} {{ lorem is truthy }} {# true #}
数値型か?(number)
{% set lorem = 0 %} {{ lorem is number }} {# true #}
文字列型か?(string)
{% set lorem = 'lorem' %} {{ lorem is string }} {# true #}
反復可能か?(iterable, sequence)
{% set list = [1, 2, 3] %} {% set dict = { hoge: 'hoge', fuga: 'fuga', piyo: 'piyo' } %} {{ list is iterable }} {# true #} {{ list is sequence }} {# true #} {{ dict.items() is iterable }} {# true #} {{ dict.items() is sequence }} {# true #}
本来ディクショナリはiterable, sequenceともにfalseになりますが、「.items()」メソッドを利用すると反復可能になり、iterable, sequenceの式評価はtrueとなります。このコードは公式ドキュメントのKey, value pairs in loopsの記載に基づきます。
ディクショナリか?(mapping)
{% set dict = { hoge: 'hoge', fuga: 'fuga', piyo: 'piyo' } %} {{ dict is mapping }} {# true #}
リスト
リストが特定の要素を含んでいるか?(containing, within)
{% set numbers = [1, 2, 3] %} {{ numbers is containing 2 }} {# true #} {{ 2 is within numbers }} {# true #} {% set strings = ['hoge', 'fuga', 'piyo'] %} {{ strings is containing 'fuga' }} {# true #} {{ 'fuga' is within strings }} {# true #}
リストが指定した要素を全て含んでいるか?(containingall)
{% set numbers = [1, 2, 3] %} {{ numbers is containingall [1, 3] }} {# true #} {% set strings = ['hoge', 'fuga', 'piyo'] %} {{ strings is containingall ['hoge', 'fuga'] }} {# true #}
数値
偶数か?奇数か?(even, odd)
{% set num = 2 %} {{ num is even }} {# true #} {% set num = 3 %} {{ num is odd }} {# true #}
指定した数値で割りきれるか?(divisibleby)
{% set num = 3 %} {{ num is divisibleby 3 }} {# true #} {{ num is divisibleby 2 }} {# false #}
文字列
全て小文字か?大文字か?(lower, upper)
{% set str = 'loremipsum' %} {{ str is lower }} {# true #} {% set str = 'LOREMIPSUM' %} {{ str is upper }} {# true #}
半角スペースは小文字、大文字どちらにも該当しないようなので、これをtrueにするのは難しい気がします。
特定の文字列を含むか?(string_containing)
{% set str = 'lorem ipsum' %} {{ str is string_containing 'sum' }} {# true #}
特定の文字列から始まるか?(string_startingwith)
{% set str = 'lorem ipsum' %} {{ str is string_startingwith 'lorem' }} {# true #}
その他
2つの値は等しいか?(equalto, sameas)
{% set str = 'lorem' %} {{ str is equalto 'lorem' }} {# true #} {{ str is sameas 'lorem' }} {# false #} {{ str == 'lorem' }} {# true #} {% set str2 = 'lorem' %} {{ str is sameas str2 }} {# true #} {{ str == str2 }} {# true #}
equaltoは==と等価です。対してsameasは変数同士を比較するものであるため、値が同じであっても、変数と文字列を比較するとfalseになります。ただし、結局多くの場合は==で書き換えが可能です。
予約変数を使用した条件分岐
forループの中で使用できる条件分岐
forループ内では多くの予約変数が用意されており、それらを駆使してさまざまな条件分岐を行うことができます。全ては紹介しませんが、代表的なものを紹介します。公式ドキュメント:Loop properties
{%- for item in items -%} {% if loop.first %} 最初のループ時のみに実行 {% endif %} {% if loop.last %} 最後のループ時のみに実行 {% endif %} {% if loop.index is even %} 偶数ループ毎に実行 {% endif %} {% if loop.index is odd %} 奇数ループ毎に実行 {% endif %} {% if loop.index is divisibleby 5 %} 5ループ毎に実行 {% endif %} {%- endfor -%}
ブログにまつわる条件分岐
ブログかどうか?(ウェブサイトページ/ランディングページでないか?)
{%- if group.id -%} ブログである {%- endif -%}
group変数にはブログにまつわる多くの情報が格納されています。全てを取得する必要はありませんので、そのうちidのみをチェックし、idが取得できればブログとみなすことができます。ウェブサイトページなどの場合は、nullが返ります。
アーカイブ(一覧)ページかどうか?
公式ドキュメント:If is_listing_view statement
{% if is_listing_view %} 一覧ページである {% else %} 詳細ページである {% endif %}
タグアーカイブページかどうか?
{% if topic %} タグアーカイブページである {% endif %}
著者アーカイブページかどうか?
公式ドキュメント:If blog_author statement
{% if blog_author %} 著者アーカイブページである {% endif %}
「全ての記事」ページかどうか?
公式ドキュメント:If not simple_list_page statement
{% if simple_list_page %} 「全ての記事」ページである {% else %} 通常の一覧ページである {% endif %}
前後のページが存在するか?(ページャー)
こちらはほぼ公式ドキュメント:Listing pagination からの引用です。
{% if last_page_num %} <a href="{{ blog_page_link(last_page_num) }}">前へ</a> {% endif %} <a href="{{ group.absolute_url }}/all">全ての記事を見る</a> {% if next_page_num %} <a href="{{ blog_page_link(next_page_num) }}">次へ</a> {% endif %}