【Gutenberg】カスタムフィールド職人からカスタムブロック職人へ

Gutenbergのカスタムブロックからpost metaを更新する方法や小ネタをまとめました。

PHPの部分は前記事にも記載してあるので参考にしてください。記事の途中にもコード例へのリンクを貼っています。
[MVCモデル風WordPressカスタマイズ設計](https://qiita.com/ichigo_daifuku3/items/1f204d5fc1b8d89d1f06)

# カスタムブロック 開発環境

「Create Guten Block Toolkit」でサクッと開発環境を構築。

Create Guten Block Toolkit
https://ahmadawais.com/create-guten-block-toolkit/

下記の記事が導入の参考になりました。

Create Guten BlockでGutenbergのカスタムブロック開発環境を構築する
https://www.webopixel.net/wordpress/1420.html

src内は、1ブロック=1ディレクトリで作成する。
src内にブロック（ディレクトリ）を追加したら、src/blocks.jsに追加したjsファイルのimportを記述する。
エディタ内のブロックのスタイルは各ブロック（ディレクトリ）内のeditor.scssに記述する。
フロント表示時のスタイル用のblocks.style.build.cssを使わない場合は、src/init.phpの'enqueue_block_assets'のアクションフックを削除する（お好みで）。

# ブロックカテゴリ・metaフィールドの準備
'block_categories'フィルターフックを使ってブロックカテゴリを追加。
投稿タイプによって、使用できるカスタムブロック（ブロックタイプ）を制限する場合は、'allowed_block_types'フィルターフックを使う。
register_meta()で事前に、metaフィールドを登録しておく。

コード例
[init.php](https://qiita.com/ichigo_daifuku3/items/1f204d5fc1b8d89d1f06#initphp)
[setting.php](https://qiita.com/ichigo_daifuku3/items/1f204d5fc1b8d89d1f06#settingphp)
[hooks/blocks.php](https://qiita.com/ichigo_daifuku3/items/1f204d5fc1b8d89d1f06#hooksblocksphp)

# カスタムブロック の作成
各ブロック（ディレクトリ）内のblock.jsに記述する。

下記は、ブロックカテゴリ'block-post_type_1'にブロック'myplugin/image-section'を追加するコード例です。
色々試せるようにコンポーネントを多めに入れてみました。
[setting.php](https://qiita.com/ichigo_daifuku3/items/1f204d5fc1b8d89d1f06#settingphp)のブロックタイプ・metaフィールドとは部分的にしか対応してないので、コードを試す際には適宜補完してください。


```react:block-image-section/block.js
import './editor.scss';

const {
  registerBlockType
} = wp.blocks;
const {
  RichText,
  MediaUpload
} = wp.editor;
const {
  PanelBody,
  BaseControl,
  TextControl,
  SelectControl,
  RadioControl,
  Button
} = wp.components;
const yearStart = 2019;
const yearEnd = 2030;
const linkType = [
  { label: '外部リンク', value: '' },
  { label: '内部リンク', value: '内部リンク' },
];

registerBlockType( 'myplugin/image-section', {
  title: '画像セクション登録',
  icon: 'edit',
  category: 'block-post_type_1',

  attributes: {
    title: {
      type: 'string',
      source: 'meta',
      meta: 'meta_title' // タイトル
    },
    content: {
      type: 'array',
      source: 'meta',
      meta: 'meta_content' // 説明文
    },
    year: {
      type: 'string',
      source: 'meta',
      meta: 'meta_year' // 撮影年
    },
    place: {
      type: 'string',
      source: 'meta',
      meta: 'meta_place' // 場所
    },
    url: {
      type: 'string',
      source: 'meta',
      meta: 'meta_link_url' // リンク先URL
    },
    type: {
      type: 'string',
      source: 'meta',
      meta: 'meta_link_type' // リンクタイプ
    },
    imageID: {
      type: 'number',
      source: 'meta',
      meta: 'meta_image_id' // 画像ID
    },
    imageURL: {
      type: 'string',
      source: 'meta',
      meta: 'meta_image_url' // 画像URL
    }
  },

  edit({attributes, setAttributes, className}) {
    function onSelectImage( media ) {
        setAttributes( {
            imageID: media.id,
            imageURL: media.url,
        } );
    }
    function onRemoveImage() {
        setAttributes( {
            imageID: null,
            imageURL: '',
        } );
    }
    function onChangeYear( event ) {
        setAttributes( { year: event.target.value } );
    }

    const optYear = [];
    for (let i = yearStart; i <= yearEnd; i++) {
      optYear.push(
        <option value={i} selected={i === Number(attributes.year)}>{i}</option>
      )
    }

    return (
      <PanelBody
        className={ className }
        title="画像セクション登録"
        icon="edit"
        initialOpen={ true }
      >
        <TextControl
          label="タイトル"
          help="画像タイトルを入力してください。"
          value={ attributes.title }
          onChange={ ( text ) => setAttributes( { title: text } ) }
        />
        <BaseControl
          label="説明文"
        >
          <RichText
            tagName="div"
            value={ attributes.content }
            onChange={ (content) => setAttributes( { content: content } ) }
          />
        </BaseControl>
        <SelectControl
            label="場所"
            help="撮影した場所を選択してください。"
            value={ attributes.place }
            options={ [
                { label: '選択なし', value: '' },
                { label: '屋内', value: '屋内' },
                { label: '屋外', value: '屋外' },
            ] }
            onChange={ ( option ) => { setAttributes( { place: option } ) } }
        />
        <BaseControl
          label="撮影年"
        >
          <select onChange={ onChangeYear } className="select-year">
            <option value='' selected={'' === attributes.year}>なし</option>
            {optYear}
          </select> 年
          <p class="components-base-control__help">撮影した年を入力してください。</p>
        </BaseControl>
        <TextControl
          label="リンク先URL"
          value={ attributes.url }
          onChange={ ( text ) => setAttributes( { url: text } ) }
        />
        <RadioControl
          label="タイプを選択"
          selected={ attributes.type ? attributes.type : linkType[0].value }
          options={ linkType }
          onChange={ ( option ) => { setAttributes( { type: option } ) } }
        />
        <BaseControl
          label="画像登録"
        >
          <MediaUpload
              onSelect={ onSelectImage }
              type="image"
              value={ attributes.imageID }
              render={ ( { open } ) => (
                  <Button className={ attributes.imageID ? 'image-button' : 'button button-large' } onClick={ open }>
                      { ! attributes.imageID ? '画像アップロード': <img src={ attributes.imageURL } /> }
                  </Button>
              ) }
          />
          { ! attributes.imageID ? '': <Button className={ 'delete-image button button-large' } onClick={ onRemoveImage }>画像削除</Button> }
        </BaseControl>
      </PanelBody>
    );
  },

  save() {
    return null;
  },

} );
```

```scss:block-image-section/editor.scss
.wp-block-myplugin-image-section {
	.components-base-control .components-base-control__field {
		margin-bottom: 15px;

		.components-select-control__input {
			width: 200px;
		}
		.select-year {
			margin-right: 5px;
			margin-bottom: 15px;
		}
		.delete-image.button {
			margin-left: 1em;
			vertical-align: bottom;
		}
	}
	.components-base-control .components-base-control__label {
		margin: 5px 0;
		padding: .5em 10px .5em 10px;
		font-weight: bold;
		background-color: #f3f3f3;
	}
	.components-base-control .components-base-control__help {
		font-size: 12px;
	}
	.editor-rich-text {
		font-family: "Noto Serif", serif;
		font-size: 16px;
	}
}
```

# 投稿タイプにエディタのテンプレートを設定する

register_post_type()に'template'を設定することで、エディタのデフォルト表示にブロックが追加される。
後々のカスタムブロックの追加等の改修を想定して、'template_lock'は設定しない。
（'template_lock'が設定してあると'template'を編集した際、既存の投稿が壊れる。）

コード例
[hooks/post-type.php](https://qiita.com/ichigo_daifuku3/items/1f204d5fc1b8d89d1f06#hookspost-typephp)

# フロントに表示する

get_registered_metadata()で投稿されたページのmetaデータを取得する。

コード例
[controllers/single.php](https://qiita.com/ichigo_daifuku3/items/1f204d5fc1b8d89d1f06#controllerssinglephp)
[single-post_type_1.php](https://qiita.com/ichigo_daifuku3/items/1f204d5fc1b8d89d1f06#single-post_type_1php)