この記事はブロックのサイドバーに任意にユーザーを選択するセレクトボックスを表示させる方法の覚書です。WordPressのブロックでREST APIを利用したい方には参考になると思います。
この記事で作るユーザー情報ブロックの仕様
事の発端はクライアントから「任意のユーザー情報を表示するブロックを作って欲しい」という依頼を受けたことです。完成品はこんな感じです。
ブロックの具体的な仕様は次のようになります。
- ユーザーIDを設定してその名前やアバター画像、プロフィール情報を表示させる
- ユーザー情報が編集されたら自動的に更新
データベースの内容に応じて中身が変わるので、表示部分はサーバーサイドレンダリングを利用することにしました。ブロックの設定項目はデザイン面で色々な装飾をする必要もないのでユーザーIDのみです。
簡単に作るならユーザーIDをテキストボックスに入力してもらえばOKですが、WordPressはユーザーIDがすぐに分かる仕様になっていないので使い勝手がよくありません。そもそも「どうやってユーザーIDを調べるの?」となる人もいると思います。
そう考えると理想は「ユーザー一覧を表示してそこから選べるようにする」ということになります。
では、どうやってブロックエディタ内にユーザー情報を表示させるかですが、次の2通りの方法があります。
- wp_localize_scriptsでHTLMのヘッダーに変数として書き出す
- REST APIでデータベースから直接取得する
1番の方法はPHPのスキルがあれば簡単に実装できますがイマイチスマートではありません。そこで、今回はREST APIを使うことにしました。
ユーザー情報を取得するエンドポイント
WordPressのREST APIでユーザー情報を取得するためのエンドポイントは次のようになります。
/wp/v2/users/
このままだとユーザー情報がいろいろ取得できますが、グローバルパラメータのfields
を使うことで情報を絞り込むことができます。例えば次のように書くとユーザー名、ユーザースラッグ、ユーザーIDのみが返されます。
/wp/v2/users/?_fields=name,slug,id
ブロックエディタでのREST APIの実装
ブロックエディタ内でREST APIを利用したい場合、apiFetch()
を使います。次のソースでは取得したユーザーのデータからユーザー名とIDを取り出してSelectControl
で使うための配列を作っています。
const authors = [];
authors.push({ label: 'ユーザを選択してください', value: 0 });
wp.apiFetch({ path: '/wp/v2/users/?_fields=name,slug,id' }).then(
users => {
users.forEach(user => {
authors.push({ label: user['name'], value: user['id'] });
})
}
);
このソースの内容を簡単に説明すると、users
の中に全ユーザーの情報が入っているので、それをforEach
でユーザー毎にとりだしてauthors
に追加しています。
これを利用したブロック全体のソースコードは次のようになります。
import {
InspectorControls,
} from '@wordpress/block-editor';
import {
PanelBody,
SelectControl,
} from '@wordpress/components';
const { registerBlockType } = wp.blocks;
const { serverSideRender: ServerSideRender } = wp;
const { Fragment } = wp.element;
//ユーザー情報を取得
const authors = [];
authors.push({ label: 'ユーザを選択してください', value: 0 });
wp.apiFetch({ path: '/wp/v2/users/?_fields=name,slug,id' }).then(
users => {
users.forEach(user => {
authors.push({ label: user['name'], value: user['id'] });
})
}
);
registerBlockType('my-blocks/author-info', {
title: 'ユーザー情報ブロック',
discription: 'ユーザー情報を表示',
icon: 'id-alt',
category: 'my-blocks',
attributes: {
id: {
type: 'string',
default: 0
},
},
edit: (props) => {
const {
attributes: {
id,
},
setAttributes,
className,
} = props;
return (
<Fragment>
<InspectorControls>
<PanelBody title={'ユーザー情報設定'}>
<SelectControl
label={'ユーザー選択'}
value={id}
options={authors}
onChange={(value) => setAttributes({ id: value })}
/>
</PanelBody>
</InspectorControls>
<div className={className}>
<ServerSideRender
block="my-blocks/author-info"
attributes={props.attributes}
/>
</div>
</Fragment >
);
},
このコードによってサイドバーには次のように表示されます。ユーザー選択のセレクトボクスをクリックするとユーザーの一覧が表示されます。
ポイントはユーザー情報をREST APIで取得する部分をregisterBlockType
より前に持ってきているところです。
最初はregisterBlockType
のedit:props
内に書いていたのですが、REST APIがデータを取得する前にSelectBox
のレンダリングが終わってしまい、セレクトボックスの内容が正しく表示されませんでした。
しばらく悩んだのですが、このコードのようにregisterBlockType
の外で定義しておけばAPIからデータを取得してからのレンダリングとなり意図通りにセレクトボックスが表示されます。下記の記事で同じ事に悩んでいる方がいて参考になりました。
Indeed! If you load it before the edit: part, it gets loaded with the page before the elements are rendered. The constants are the FIRST thing in your block js file. Before you even use registerBlockType you define the constants and give them value. – Chad Holden Nov 29 ’18 at 14:41
categories – How would I get a taxonomy/category list inside a Gutenberg block? – WordPress Development Stack Exchange
サーバーサイドレダリングの実装
サーバーサイドレダリングの部分は次のようなソースになります。今回は表示部分にショートコードを使っています。
register_block_type(
'my-blocks/author-info',
[
'editor_style' => 'my-blocks-editor-css',
'editor_script' => 'my-blocks-js',
'attributes' => [
'id' => [
'type' => 'string',
],
],
'render_callback' => function( $attr, $content = '' ) {
$attr = wp_parse_args(
$attr,
[
'id' => 0,
]
);
return do_shortcode( '[author id="' . $attr['id'] . '"]' );
},
]
);
/**
* ユーザ情報ボックスを表示するショートコード
*/
add_shortcode( 'author', 's_code_author_box' );
function s_code_author_box( $atts, $content = null ) {
$atts = shortcode_atts(
array(
'id' => '',
),
$atts
);
$author_data = get_userdata( intval( $atts['id'] ) );
if ( ! $author_data ) {
return '<p class="no_author">投稿者を指定してください。</p>';
}
$author_info = $author_data->description;
$author_name = $author_data->display_name;
$link_text = $author_name . 'が書いた記事一覧';
ob_start();
?>
<div class="author_profile_box">
<div class="author_profile_box-header">
<div class="author_profile_box-face_image">
<?php echo get_avatar( $author_data->ID, AUTHOR_AVATAR, '', $author_name ); ?>
</div>
<div class="author_profile_box-name"><?php echo esc_html( $author_name ); ?></div>
</div>
<div class="author_profile_box-description">
<?php echo esc_html( $author_info ); ?>
</div>
<div class="author_profile_box-more_link">
<a href="<?php echo esc_ulr( get_author_posts_url( $author_data->ID ) ); ?>"><?php echo esc_html( $link_text ); ?></a>
</div>
</div>
<?php
$src = ob_get_clean();
return $src;
}
このソースのショートコードは分かりやすくするために必要最低限のことしかしていません。実際はエラーチェックや空白時に余計なソースを吐き出さないなど色々な処理を追加する必要があります。
また、この記事の趣旨から離れるのでCSSは掲載していません。
ショートコードとして作っておくとクラシックエディタ環境でも同じようにコンテンツを表示させることができるようになります。これは意外と便利で、ウィジェット内やクラシックエディタにしか対応していないカスタム投稿タイプでも使えます。それもあって私はサーバーサイドレンダリングを使う時は全てショートコードにしています。
あと、ショートコードにしておくとHTMLを大きく変えるような変更があってもブロックが壊れないというメリットもあります。
まとめ
WordPressのREST APIをつかってブロックエディタにデータを渡す方法、いかがだったでしょうか?簡単な記述で柔軟にデータを渡すことができるのでアイデア次第で色々なブロックが作れると思います。
是非参考にしてみてください。
コメント
この記事へのコメントはありません。