メインコンテンツに移動

Drupalのテーマ作成で詰まった5箇所|出力の仕組みを知らずにCSSを書いた結果

Drupalで自作テーマを作ったとき、5箇所で詰まりました。

振り返ってみると、原因はそれぞれ違うようで、共通していたことがあります。

Drupalが裏でどのようなHTMLやデータを組み立てているのかを確認する前に、表面だけ見てコードを書いていたことです。

CSSを書いたのに効かない。
パンくずを変更したのに表示されない。
Viewsで関連記事を作ったのに何も出てこない。

どれも、最初から出力の仕組みを確認していれば、もっと早く解決できた問題でした。

この記事では、自作テーマを作る過程で実際に詰まった5つのポイントを記録します。

同じところで止まっている方の参考になれば幸いです。

Drupal 11の構築編はこちらにまとめています。

 

Drupalのフィールドは、そのままのHTMLでは出てこない

最初に詰まったのが、記事のタグを横並びにするところでした。

親要素に display: flex を指定したのに、思ったように横並びになりません。

CSSだけを見ると間違っていないように見えます。

そこで、実際にブラウザへ出力されたHTMLを確認しました。

<div class="field field--name-field-tags">
  <div class="field__label">タグ</div>
  <div class="field__items">
    <div class="field__item"><a>Drupal</a></div>
    <div class="field__item"><a>PHP</a></div>
  </div>
</div>

原因は単純でした。

flexが効くのは、指定した要素の直下にある子要素です。

Drupalのフィールドは、ラベル、項目全体、個々の値というように複数の要素で包まれています。

つまり「タグを横並びにしたい」という目的に対して、CSSを指定する場所が合っていませんでした。

まず表示設定を確認する

さらに確認すると、ラベルについてはCSSで消す必要すらありませんでした。

コンテンツタイプの「表示管理」から、フィールドごとにラベルの表示・非表示を設定できます。

HTMLをCSSで隠す前に、Drupal側の表示設定を確認する。

これは、その後のテーマ作成でも意識するようになりました。

CSSは実際の出力を見てから書く

今回は、特定のクラスだけを狙い撃ちするのではなく、記事内のタグ領域について内側の要素をインライン表示にする方法を取りました。

.blog-article__tags div,
.blog-article__tags span {
  display: inline;
}

もちろん、これはどのDrupalサイトでも使える万能な書き方ではありません。

大切なのは、想像したHTMLではなく、実際にDrupalが出力したHTMLを見てCSSを書くことです。

 

Viewsの一覧では :first-child が思った通りに効かない

次に詰まったのが、記事一覧の余白です。

「最初の記事だけ上の余白を消したい」と考えて、次のCSSを書きました。

.blog-teaser { padding: 32px 0; }
.blog-teaser:first-child { padding-top: 0; }

ところが、思った結果になりません。

ここでもHTMLを見ると理由が分かりました。

Viewsでは、記事そのものの周囲に行用のラッパーが出力されていました。

<div class="views-row">
  <article>...</article>
</div>
<div class="views-row">
  <article>...</article>
</div>

そのため、それぞれの .views-row の中では、記事が「最初の子」になります。

記事が1件だけのときは問題が見えませんでした。

一覧が増えて初めて、CSSの前提が間違っていたことに気づいたわけです。

結局、最初の記事だけ特別扱いする必要をなくし、上下均等の余白に変更しました。

CSSで特殊な例外を作る前に、HTMLの構造を見る。

これだけで解決できる問題はかなりあります。

 

ブログトップのパンくずが表示されない

ブログトップに「HOME > ブログ」というパンくずを表示しようとしたときにも詰まりました。

hook_preprocess_breadcrumb() を使ってパンくずの内容を変更しようとしたのですが、何も表示されません。

ここでも、最初はテンプレートやCSSを疑いました。

しかし問題は、その前の段階にありました。

Drupal側でパンくずのブロック自体が描画されていなかったのです。

パンくずとして扱う情報が存在しない場合、ブロック自体が出力されないことがあります。

つまり、既に存在するパンくずを変更する処理と、存在しないパンくずを新しく作る処理では、考え方が違います。

記事ページではパンくずが存在するため処理できます。

一方、ブログトップでは自分で表示する仕組みを用意する必要がありました。

空のものを加工するのではなく、自分で組み立てる

最終的には、ブロックの出力を待つのではなく、ページ側でパンくずを組み立ててテンプレートに渡しました。

$variables['blog_top_breadcrumb'] = [
  '#theme' => 'breadcrumb',
  '#breadcrumb' => [
    ['text' => 'HOME', 'url' => '/'],
    ['text' => 'ブログ'],
  ],
];

#theme を指定しているため、表示自体は既存のパンくず用テンプレートを利用できます。

見た目だけ別に作るのではなく、既存の仕組みに乗せることで、HTMLや構造化データも共通化できます。

 

Viewsの「デフォルト値」に頼りすぎない

今回もっとも時間を使ったのが、関連記事です。

同じタグを持つ記事をViewsで取得しようとしました。

Viewsのコンテキストフィルタには、URLに値がない場合に現在の記事などから値を取得するための設定があります。

一見すると、関連記事を作るには便利な機能です。

ところが、今回は思ったように動きませんでした。

設定項目が複数あり、画面だけを見ていると、どこで処理が止まっているのか分かりません。

そこで、まず値を手動で渡して検証しました。

$view = \Drupal\views\Views::getView('related_articles');
$view->setDisplay('block_1');
$view->setArguments(['3+5+6', '2']);
$view->execute();
print count($view->result);

結果は3件。

クエリ自体は正常で、問題はViewsに渡している値でした。

ここで、原因を「Viewsの検索がおかしい」と考えるのではなく、「入力値が正しく渡っているか」というところまで分解できました。

自動取得から明示的な値渡しへ

最終的には、現在の記事が持っているタグを自分で取得し、そのIDをViewsへ渡す方法に変更しました。

foreach ($node->get('field_tags')->referencedEntities() as $term) {
  $tids[] = $term->id();
}
$view->setArguments([implode('+', $tids), (string) $node->id()]);

検証時に動作した値を、そのまま確定した形で渡します。

Drupalに推測させるのではなく、自分で値を確認して明示的に渡す。

これだけで、原因の切り分けもかなり楽になりました。

なお、Viewsのコンテキストフィルタで複数の値を扱う場合、設定によって値の区切り方と条件の意味が変わります。

今回は + を使い、「いずれかのタグに一致」という条件で利用しました。

このあたりはViewsの設定画面だけを見るより、実際に渡している値とクエリ結果を確認した方が早いです。

 

タグとカテゴリのデータ構造を確認すると、実装が減った

最後は「失敗」というより、調べたことで実装を増やさずに済んだ話です。

記事が少ないうちは、タグが一致する関連記事が見つからないことがあります。

そこで、

タグで関連記事が見つからなければ、同じカテゴリの記事を表示する。

という補完を考えました。

最初はカテゴリ用に新しいViewsを作ろうと思いました。

ところが、Drupalのデータ構造を確認すると、別の方法が取れました。

タクソノミーの関連情報は、語彙が違っていても共通の索引構造で扱われています。

例えば、

nid  tid
2    3    ← 3 は「技術ノート」
2    5    ← 5 は「Drupal」

のように、記事とタームの関連を同じ仕組みで扱えます。

そのため、既存のViewsにカテゴリのIDを渡すだけで、関連記事を取得する仕組みを流用できました。

新しいViewsを作れば動くかもしれません。

しかし、既存の仕組みを使えるなら、管理する設定もコードも増やさずに済みます。

実装する前に、データがどう保存されているかを見る。

これも今回のテーマ作成で得た大きな教訓でした。

 

5つの問題に共通していたこと

ここまでの5つを並べると、共通点が見えてきます。

症状実際に起きていたこと
flexが効かないフィールドが複数の要素に入れ子になっていた
:first-child が想定と違うViewsが行ラッパーを生成していた
パンくずが出ない変更する前に、出力そのものが存在していなかった
関連記事が出ないViewsに渡される値が想定と違っていた
Viewsを増やしかけた既存のデータ構造を確認すると流用できた

つまり、どれも最初にDrupalの内部構造を確認していれば、もっと早く解決できた問題でした。

Drupalは、入力したものをそのままHTMLとして出すCMSではありません。

フィールド、Views、パンくず、タクソノミーなど、それぞれの仕組みを通して最終的なHTMLやデータが組み立てられます。

そのため、テーマ側でCSSやTwigを書くときは、

「自分はこう出力されるはずだ」

ではなく、

「実際にはどう出力されているのか」

を見ることが重要になります。

 

Drupalでテーマを作るとき、最初に確認する3つ

今回の経験から、次にDrupalのテーマを作るときは、最初に次の3つを確認します。

1. 実際のHTMLを見る

CSSを書く前に、ブラウザの開発者ツールでHTMLを確認します。

フィールドが何に包まれているのか。
Viewsがどの要素を生成しているのか。
クラス名や階層はどうなっているのか。

想像でCSSを書かない。

2. 自動処理は、入力値まで確認する

「自動で現在の記事を取得する」「自動で値を渡す」という機能が動かないときは、その機能そのものを疑う前に、入力値を確認します。

Viewsなら、実際にどんな引数が渡っているのかを確認する。

Drushやコードから直接実行してみれば、クエリの問題なのか、値の問題なのかを切り分けられます。

3. データがどこにあるかを見る

実装を増やす前に、Drupalがすでに持っているデータ構造を確認します。

新しいViewsを作ろうとしていたものが、実は既存のViewsで処理できる。

新しいテーブルが必要だと思っていたものが、既存の仕組みで取得できる。

こうしたことは珍しくありません。

コードを書く前に構造を見る。

Drupalでは、この順番がかなり重要だと感じました。

 

まとめ

Drupalで自作テーマを作る中で、5箇所で詰まりました。

しかし、振り返ってみると、5つの問題を個別に解決したというより、

Drupalの出力とデータ構造を理解していくことで、順番に解決していった

という方が正確です。

CSSが効かないなら、まずHTMLを見る。

Viewsが動かないなら、まず渡している値を見る。

パンくずが出ないなら、そもそも描画対象が存在するのかを見る。

新しい実装を考える前に、既存のデータ構造を見る。

Drupalは、仕組みを理解すると「なぜこうなるのか」が見えてきます。

逆に、出力結果だけを見てCSSやTwigを書き始めると、今回のように何度も遠回りすることになります。

Drupalのテーマ作成では、コードを書く前に構造を見る。

今回の5つの失敗から得た、一番大きな教訓はこれでした。

記事の更新はメルマガでもお届けしています。