トップへ戻る
BLOGS

WordPress スライダーブロックの制作 (投稿スライドレンダリング編)

WordPress スライダーブロックの制作 (投稿スライドレンダリング編)

今回も前回の続きでWordPressのブロック制作です

今回は投稿記事を並べてスライダーを挿入するプラグインを作成していきます

前回までの記事で環境構築から管理画面の設定と編集画面のUI、
画像のデータをレンダリングする画像ブロックを作成していたので
今回はその続きで投稿データをレンダリングするブロックの作成です

※本記事はMacを使用したWordPressのローカル環境であり、Node.jsのインストール環境での開発を想定した記事です

さてそれでは続きを実装していきます

今回の目標

前回の記事で投稿データを管理画面で取得して選択できるように実装しました

今回はPHP側で選択した投稿データをWPQueryを使用してレンダリングしていきます

また、メンテナンスや管理の観点から先に作成していた画像ブロックの記述と分けてリファクタリングも軽くしていこうと思います

※追記
投稿データを複数選択して取得する投稿スライダーもなんとか実装できたので、後半で紹介します
お楽しみに(^o^)

レンダリングファイルの分岐

早速ですが、最初の記事でブロック登録を行ったPHPファイルでは下記のようなレンダリングコールバックでダイナミックブロックとしていました

'render_callback' => 'oja_slider_render_func',

では前回せっかく投稿スライダーと画像スライダーのフラグを立てたので、
うまく利用して画像スライダーのファイルと投稿スライダーのファイルを分けたいと思います

投稿スライダーは $attributes[‘isPostSlider’]) これが true である場合であるためこれで条件分岐をしてそれぞれのファイルを require しようと思います

custom-slider.php

//ダイナミックブロックによるレンダリング
function oja_slider_render_func($attributes, $content) {
  if( $attributes['isPostSlider']) {
    //画像ブロックレンダリング
    require_once dirname(__FILE__) . '/views/media_slider_render.php';
    return media_slider_render_func($attributes, $content);
  } else {
    require_once dirname(__FILE__) . '/views/post_slider_render.php';
    return post_slider_render_func($attributes, $content);
  }
}

これで別々で読み込むことができるので新たに下記ディレクトリを作成します

  • /views/media_slider_render.php
  • /views/post_slider_render.php

画像をレンダリングする記述は/views/media_slider_render.phpへコピペしましょう
次は投稿の取得とレンダリングを作成した/views/post_slider_render.phpへ記述しましょう

投稿のレンダリング

いきなり全文です

/views/post_slider_render.php

<?php
function post_slider_render_func($attributes, $content) {
  // 投稿が選択されていれば(値が初期値の 0 より大きければ)
  if ($attributes['selectedPostId']) {
    $args = array(
      'order'   => 'ASC',
      'orderby' => 'title',
      'tax_query' => array(
        array(
          'taxonomy' => 'oja_cat',
          'field'    => 'term_id',
          'terms'    => $attributes['selectedPostId']
        )
      )
    );

    //スライダーのオプション(attributes の値により data 属性を追加)
    $slider_options = '';

    if($attributes['slideAutoPlay'] !== 0) {
      $slider_options .= 'data-autoplay="'.$attributes['slideAutoPlay'].'"';
    }

    $slider_options .= ' data-speed="'.$attributes['slideSpeed'].'"';

    if($attributes['slideLoopEnable']) {
      $slider_options .= ' data-loop="true"';
    }

    if($attributes['slideEffect'] !== 'slide') {
      $slider_options .= ' data-effect="' .$attributes['slideEffect']. '"';
    }
    // $slider_options .= ' data-slides-per-view="' .$attributes['slidesPerView'] . '"';

    if($attributes['slideCentered']) {
      $slider_options .= ' data-centeredSlides="true"';
    }

    $the_query = new WP_Query( $args );
    if ( $the_query->have_posts() ) {
      //該当する投稿があればスライダーのマークアップを組み立てる
      $output = '<div class="wp-block-oja-custom-slider">';
      $output .= '<div class="swiper oja-post-slider"'. $slider_options .'>';
      $output .= '<div class="swiper-wrapper">';
      while ( $the_query->have_posts() ) {
        $the_query->the_post();
        $output .=
        '<div class="swiper-slide">
          <div class="item_detail">
            <a href="'. get_the_permalink() . '" class="item_page">
              <div class="post_thumbnail">';
                if (has_post_thumbnail()):
                  $output .= get_the_post_thumbnail();
                else:
                  $output .=
                  '<img src="' . get_stylesheet_directory_uri() . '/img/no-image.png" alt="アイキャッチ画像がない時の代替画像です">';
                endif;
              $output .=
              '</div>
              <div class="post_text">
                <h1>' . get_the_title() . '</h1>
              </div>
            </a>
          </div>
        </div>';
      }
    wp_reset_postdata(); //グローバル変数 $post を復元
    $output .= '</div>';

      //ページネーションを表示する場合
      if($attributes['showPagination']) {
        $output .= '<div class="swiper-pagination"></div>';
      }
      //ナビゲーションボタンを表示する場合
      if($attributes['showNavigationButton']) {
        $output .= '<div class="swiper-button-prev"></div><div class="swiper-button-next"></div>';
      }
      //スクロールバーを表示する場合
      if($attributes['showScrollbar']) {
        $output .= '<div class="swiper-scrollbar"></div>';
      }
      $output .= '</div></div>';
      return $output;
    } else {
      echo '<p>';
      echo '投稿が見つかりませんでした';
      echo '</p>';
    }
  } // if ($attributes['selectedPostId'])
}

別段難しいことはしていません

Swiperのマークアップの組み立て方などは画像スライドと同じです

投稿スライダーは後で画面幅に応じて表示の仕方を変えるため Swiper のdate属性を付与する記述でスライド枚数を変更する data-slides-per-view はコメントアウトしてあります

また投稿の取得は普通に WP_Query のパラメータを組み立てる際に
今回の例はタクソノミーごとの記事なので tax_query のパラメータの部分に属性値を使用しています

'terms' => $attributes['selectedPostId']

あとは普通にループの記述ですが注意点としては一旦変数に格納した後returnすることになるためWP関数は get_the_~~~ となることくらいです

init-swiper.jsを変更

上で少し触れましたがスライダーのタイプによって一度に表示する枚数を適宜変更するようにします

画像などはビューの印象だけになりトグルでキャプションを添えるデザインになりますが、投稿スライダーはタイトルや抜粋などを表示することもできるため、管理画面で表示枚数を極端に増やされたら違和感のある見た目になってしまいます

投稿スライダーの場合はPC画面で3枚表示、タブレットサイズで2枚表示、スマホは1枚というようにレスポンシブにしてあげます

breakpointsの設定

スライダーの初期化の定義はdate属性よりも優先されるためこちらの、breakpointsパラメータを投稿スライダーのときに変更するようにします

以下はbreakpoints: の例です

breakPoint = {
  slidesPerView: 1,
  spaceBetween: 10,
  768: {
    slidesPerView: 2,
    spaceBetween: 22,
  },
  1080: {
    slidesPerView: 3,
    spaceBetween: 17,
  },
};

上記のようにするとモバイルファースト(min-width)でスライドを出し分けできます

今回はスライドの種類によって出し分けを行うので変数にして分岐します

init-swiper.js

let breakPoint;
if (elementEffect && elementEffect !== "slide") {
  breakPoint = "";
} else if (element.classList.contains("oja-media-slider")) {
  breakPoint = "";
} else {
  breakPoint = {
    slidesPerView: 1,
    spaceBetween: 10,
    //breakpoints
    768: {
      slidesPerView: 2,
      spaceBetween: 22,
    },
    1080: {
      slidesPerView: 3,
      spaceBetween: 17,
    },
  };
}

この変数を初期化するプロパティに設定します

init-swiper.js

breakpoints: breakPoint,

これで画像スライダーの時は、インスペクターの設定に従い、投稿スライダーの時はレスポンシブにスライド枚数が変更になる仕様が実装できました

投稿スライダー完成編

ここからは投稿のデータをすべて取得して、その中から複数選択した投稿データをスライダーとして表示するブロックの作成を行います
基本的には今まで書いてきたこのブロックを適所変更して書き換えるだけで実装可能です

まずは投稿データを受け取るための属性値の変更から、

属性値の型を配列に

custom-slider.php

// selectedPostId を属性として追加
  'selectedPostId' => [
    'type' => 'array',
    'default' => []
  ],

type を array にして値を配列で受け取るようにします

次に、作成したコンポーネントを書き換えていきます

withSelect を変更

posts-select.js

export default withSelect((select, props) => {
  //現在の投稿の post ID を取得
	const currentPostId  = select("core/editor").getCurrentPostId(),
  //現在の投稿タイプ名を取得
        curentPostType = select('core/editor').getCurrentPostType();
	//クエリパラメータ
	const query = {
		per_page: -1,
    order: 'desc',
    status: 'publish',
		exclude: currentPostId, //現在の投稿は除外
	};
	return {
		posts: select("core").getEntityRecords('postType', curentPostType, query),
	};
})(PostsSelect);

変更点としては .getEntityRecords での問い合わせとして postType を指定し現在の投稿タイプと同じ記事を取得するようにしています
これはカスタム投稿タイプを使用している人でも問題なく投稿データが取得できるようになります

今度はこの投稿データを使用して表示してみます

<SelectControl>

今回は投稿データが膨大なケースもあると思うのでセレクトボックスで表示できる <SelectControl> を使用します

公式のハンドブックはこちら

edit.js

import { withSelect } from '@wordpress/data';
import { SelectControl } from "@wordpress/components";

const PostsSelect = (props) => {
	const { posts, selectedPostId, selectPost } = props;

	//SelectControl の options プロパティに指定する投稿のタイトルとIDから成るオブジェクトの配列
	let select_options = [];
	if (posts) {
		select_options.push({ value: 0, label: "投稿を選択", disabled: true });
		posts.forEach((post) => {
			select_options.push({ value: post.id, label: post.title.rendered });
		});
	} else {
		select_options.push({ value: 0, label: "読み込み中", disabled: true });
	}

	return (
		<SelectControl
			id="oja_posts_select"
			multiple
			options={select_options} //投稿データの配列
      value={ selectedPostId } //更新された配列
			onChange={selectPost}    //値の更新メソッド
		/>
  );
};;

まず文頭でコンポーネントをインポートします

import { SelectControl } from "@wordpress/components";

セレクトボックスのオプションとしての配列を作成します select_options がその変数です
今回は forEachで投稿データを配列に渡していきます

posts.forEach((post) => {
 select_options.push({ value: post.id, label: post.title.rendered });
});

今回は配列でデータを扱うということで <SelectControl> のプロパティの一つ
multiple と記述することで配列として扱うことができます

その他のメソッドはそのままで問題ありません

しかし配列として投稿データを扱うということで <SelectControl> のプロパティの onChange のメソッド selectPost の内容を変更します

selectPostメソッド(onChange)を変更

edit.js

import React, { useRef } from 'react';

//省略、、

//投稿を選択する関数
const postArray = useRef([]);
const selectPost = (postId) => {
  if (postArray.current.includes(parseInt(postId[0]))) {
    let index = postArray.current.indexOf(parseInt(postId[0]));
    postArray.current.splice(index, 1);
    setAttributes({ selectedPostId: postArray.current});
  } else {
    postArray.current.push(parseInt(postId[0]));
    setAttributes({ selectedPostId: postArray.current});
  }
};

文頭でインポートしているのは React のフックという概念の変数です

useRefフック

useRefは、.currentプロパティが渡された引数(初期値はinitialValue)をrefObjectへ返します。
この引数の値が書き換え可能な値であり、 ブロックが再レンダリングされても.currentプロパティ内に値を保持することができます

つまり、要素への参照を行うことができます。返されるオブジェクトはコンポーネントの存在期間全体にわたって存在し続けます

この例では postArray.current に現在の値が保存されます

仮に属性値を直接扱うと、onChange の実行などでブロックが再レンダリングされます。
そのたびに属性値の値がリセット(変数宣言などで)されてしまう事になっていました
こうして変数の値を保持しなくては、属性値を保持して更新することが出来ませんでした

投稿IDで条件分岐

selectPostメソッドは引数に投稿のIDを取ります
したがってこの投稿IDで条件分岐します

.includes(検索値)

.includesは配列に対して引数の要素が含まれているかどうか真偽値で返します

これで if文で投稿IDが配列に含まれているか、という分岐で組み立てます

.indexOf(検索値)

indexOfは配列に対して引数の検索値のindex番号を返します
つまり検索値の位置がわかるわけで、これを一旦変数にしておきます

.splice(index, 1)

.spliceは配列から値を削除、または挿入できる破壊的なメソッドです
第一引数には削除開始位置となるので先程変数化しておいた index を指定し第二引数に削除する個数を指定します

これらのメソッドを駆使して投稿IDがダブった際の削除を行っています

最後に属性値を書き換えておきます

setAttributes({ selectedPostId: postArray.current});

次は投稿IDの新規追加です

投稿IDを追加する

投稿IDを取得したらpostArray.current に追加して保存します

こちらでは .pushメソッドで追加します

postArray.current.push(parseInt(postId[0]));

これで onChangeプロパティの変更は完了です

editor.scss

これで管理画面上の機能は一通り完成したので、CSSで軽くスタイリングをしておきます

editor.scss

@charset 'utf-8';

.wp-block-oja-custom-slider {
  max-width: 720px;
}
.wp-block-oja-custom-slider .slider-block-container {
  display: flex;
  flex-wrap: wrap;
  margin: 20px;
}
 
.wp-block-oja-custom-slider .slider-block-container img {
  width: 100%;
  max-width: 160px;
  margin: 10px;
}
 
.image {
  cursor: pointer;
}
 
.wp-block-oja-custom-slider figcaption.block-image-caption {
  text-align: center;
  margin-top: 0;
}

#oja_posts_select {
  overflow: hidden scroll;
  border: 1px solid #C0BCBC;
  background: #fcfcfc;
  font-size: 14px;
  min-width: 500px;
  min-height: 150px !important;
  height: auto;
  padding: 2%;
  max-width: inherit;
  option {
    padding-bottom: 3px;
    padding-top: 3px;
  }
}

PHPのレンダリングメソッドを変更

これで問題なく投稿のデータを選択した上で取得できたはずなので、最後はレンダリングの関数を変更します
といっても書き換えるのは Wp_Queryのパラメータをほんの少し変えるだけです。

post_slider_render.php

$post_type = get_post_type();
    $args = array(
      'order'   => 'ASC',
      'orderby' => 'title',
      'post_type' => $post_type,
      'post__in' => $attributes['selectedPostId'],
    );

投稿タイプは現在の投稿から選べるようにするということで文頭で get_post_type()を使用し投稿タイプのパラメータに使用しています

変更した部分は tax_query を削除し、post__in パラメータを追加しています

'post__in' => $attributes['selectedPostId'],

このパラメータは投稿IDの配列でクエリーを指定するということでこれでレンダリングも完成です

まとめです(完結編)

今回4記事に渡って一つのブロックを作成してきました

最初は画像のスライダーを作成するだけのシンプルなブロックでしたが、コーディングしてUIを触っていると機能を追加したくなり、
カテゴリースライダーへの切り替え機能の実装
投稿選択スライダーへの変更など、
自分の「やってみたい」が詰まったブロックへと完成しました

たくさんブロック作成するのは楽しいですが、リファクタリングなども兼ねて、そろそろこれまで作成してきたブロックのClass化やアップデートなどを行ってみたいと思います

またReact も基礎から勉強しなきゃですね

コメントをお待ちしております

お気軽にコメントをどうぞ。

CAPTCHA