WordPress Logo WordPress カスタムブロック MediaUpload で複数の画像を挿入

WordPress の Gutenberg で MediaUpload コンポーネントを使って画像を挿入するブロックを作成する際に複数の画像を挿入する方法や MediaUpload の基本的な使い方についての覚書です。

基本的には MediaUpload コンポーネントの multiple プロパティを true にし、onSelect プロパティのコールバック関数で画像オブジェクトの配列を受け取って処理します。また、gallery プロパティを true にするとギャラリー機能が使えるので便利です。

以下はすでに Node.js がインストールされていて、WordPress のローカル環境があることを前提にしています。

更新日:2020年10月18日

作成日:2020年10月10日

ブロックの基本的な作成方法や環境の構築方法については以下を御覧ください。

関連ページ

[2020年10月18日] 属性の設定など内容を一部を変更しました。

以下では最初に1枚の画像を挿入するブロックを作成し、そのブロックをもとに複数の画像を挿入できるように変更しています。

環境とファイルを準備

ブロックの作成では JSX を使用するので JSX をコンパイルする環境が必要ですが、create-block を使用すると簡単に環境の構築と初期ファイルが作成できます。

関連ページ:Gutenberg ブロック開発の環境構築

この例ではプラグインとしてブロックを作成するので、ターミナルでプラグインディレクトリ wp-content/plugins に移動します。パスは環境に合わせて指定します。

$ cd /path/to/wp-content/plugins return //プラグインディレクトリへ移動

続いて、create-block のコマンドを実行して環境の構築と初期(雛形の)ファイルを作成します。

以下は、--namespace オプションで名前空間(namespace)を wdl に指定し、slug(作成するプラグインのフォルダ名)に my-images を指定して create-block のコマンドを実行しています。

この例では slug に my-images を指定してコマンドを実行したので、my-images というフォルダがプラグインディレクトリ(wp-content/plugins )に作成され、その中に以下のようなファイルが出力されます。

my-images //作成されたプラグインのディレクトリ
├── block.json  
├── build //ビルドで出力されるファイル(本番環境で使用するファイル)のディレクトリ
│   ├── index.asset.php 
│   ├── index.css 
│   ├── index.js
│   └── style-index.css 
├── node_modules 
├── my-images.php  //PHP 側でブロックを登録するプラグインファイル
├── package-lock.json
├── package.json 
├── readme.txt
└── src  //開発用ディレクトリ(この中のファイルを編集)
    ├── edit.js  //edit 関数を記述するファイル(この例では後で削除)
    ├── editor.scss  //エディター用スタイル
    ├── index.js  //ブロック用スクリプト(エントリーポイント)
    ├── save.js  //save 関数を記述するファイル(この例では後で削除)
    └── style.scss  //フロントエンド及びエディターに適用するスタイル

この例では単純なブロックを作成するので、src フォルダの開発用のファイル edit.js と save.js は index.js に統合して1つのファイルとします。

src/index.js を以下のように変更して edit.js と save.js は削除します。

registerBlockType() の title や description、category などのプロパティは自動的に生成されたファイルのものを使用しています。必要に応じて変更できます。

edit と save プロパティは以降で変更していきますが、取り敢えずは単に p 要素のレンダリングだけを記述しています。

src/index.js
import { registerBlockType } from '@wordpress/blocks';
import './editor.scss';
import './style.scss';

registerBlockType( 'wdl/my-images', {
  title: 'My Images',
  description: 'Example block written with ESNext',
  category: 'widgets',
  icon: 'smiley',
  edit: ( props ) => {
    const { className } = props;
    return (
      <p className={ className }>My Images – hello from the editor!</p>
    );
  },
  save: () => {
    return (
      <p>My Images – hello from the saved content!</p>
    );
  },
} );

PHP 側でブロックを登録するファイル my-images.php はプラグインファイルでもあるので、先頭にプラグインヘッダの記述があります。

create-block コマンドでは対話モードでオプションを指定しなかったので、プラグインヘッダの部分にはデフォルトの Description や Author が設定されています。

必要なファイルの登録と register_block_type() を使ったブロックの登録が記述されています。

31 行目の wp_set_script_translations は翻訳関連の関数で、この例では使用しないので削除しても問題ありません。この例の場合、特に変更する必要はないのでこのまま使用します。

変更したファイルをンパイルするため、ターミナルで作成したプラグインのディレクトリに移動し、 npm run build と入力して return キーを押しビルドを実行します(以降の作業は開発モードで行います)。

確認のため、プラグインページでプラグインを有効化します。

投稿にブロックを挿入して保存すると、エディター側では以下のように表示されます。

背景色や文字色は create-block コマンドで自動的に生成された src/editor.scss と src/style.scss にデフォルトで記述されているスタイルが適用されています。

フロントエンド側では以下のように表示されます。

開発モード

ターミナルで作成されたプラグインのディレクトリ(この例では my-images)に移動して npm start を実行して開発モードで作業をしていきます。

開発モードの場合、ファイルの変更が自動的に検知され、ビルドが実行されるので、毎回ビルドコマンドを実行する必要がありません。何らかの理由でビルドが失敗するとターミナル及びコンソールにエラーが表示されます。

開発モードを終了するには control + c を押します。

$ cd my-images  return  //作成されたプラグインのディレクトリに移動
  
$ npm start  return  //開発モード

開発が完了して procuction ビルドするには npm run build を実行します。

本番環境(サーバー)で必要になるのは build ディレクトリとその中の全てのファイルと PHP 側でブロックを登録するプラグインファイル my-images.php になります。

MediaUpload コンポーネント

カスタムブロックで画像やメディアを挿入できるようにするには MediaUpload コンポーネントを使用します。MediaUpload はメディアをアップロードするモーダルウィンドウを開くボタンを描画するコンポーネントで、block-editor パッケージにあります。

以下は MediaUpload コンポーネントを使った画像を1つ選択して表示するブロックの例です。

以下は上記コードの詳細です。

import

必要なコンポーネントをインポートします。MediaUpload、MediaUploadCheck、Button のコンポーネントを追加でインポートします。MediaUploadCheck はユーザがメディアライブラリを使う権限があることをチェックするためのコンポーネントです。

src/index.js
import { registerBlockType } from '@wordpress/blocks';
import { MediaUpload, MediaUploadCheck } from '@wordpress/block-editor';
import { Button } from '@wordpress/components';

attributes

必要な情報を保持するための属性を設定します。registerBlockType() の第2パラメータの attributes プロパティに以下の属性を追加します。

  • mediaID:MediaUpload の value の値を保持する属性。
  • imageUrl:MediaUpload で表示する画像の src 属性の値を保持する属性。
  • imageAlt:MediaUpload で表示するの画像の alt 属性の値を保持する属性。

これらの値は MediaUpload コンポーネントの onSelect プロパティに指定するコールバック関数で選択された画像オブジェクトのプロパティから取得することができます。

この例では mediaID に default プロパティで初期値 0 を設定しています。imageUrl と imageAlt には default プロパティを指定しないので、初期状態ではその属性の値は null になります。

registerBlockType( 'wdl/my-images', {
  ・・・中略・・・
  //属性を設定
  attributes: {
    //MediaUpload の value の値
    mediaID: {
      type: 'number',
      default: 0
    },
    //img の src に指定する URL
    imageUrl: {
      type: 'string',
      source: 'attribute',
      attribute: 'src',
      selector: '.card_image'
    },
    //img の alt 属性の値 
    imageAlt: {
      type: 'string',
      source: 'attribute',
      attribute: 'alt',
      selector: '.card_image'
    },
  },

source プロパティ

source プロパティを設定すると、ブロックのデータを save 関数で保存された投稿のコンテンツから取得することになります。また、selector プロパティに指定しているのは MediaUpload で表示する画像に指定するクラス属性の値です。上記の場合、elector プロパティに指定した .card_image クラスの要素の attribute プロパティで指定した src や alt 属性から値を文字列として取得するというような意味になります。

edit 関数

edit プロパティ にはエディターがどのようにブロックをレンダリングするかを定義して返す edit 関数を記述します。

return ステートメント

以下は edit 関数の return ステートメントの部分です。MediaUpload を使ってボタンと挿入された画像をレンダリングします。

MediaUpload コンポーネントは、ユーザがメディアライブラリを使う権限があることをチェックするためのコンポーネント MediaUploadCheck でラップします。

onSelect プロパティはメディアが選択されたとき呼び出されるコールバック関数で別途定義します。allowedTypes プロパティには選択できるメディアのタイプを配列で指定し、value プロパティには選択したメディアの Media ID を指定します。

render プロパティはメディアライブラリを開くボタンをレンダリングするために呼び出されるコールバック関数で別途定義します。

11〜10行目は表示された画像を削除するボタンを表示する記述です。この例の場合、画像だけのブロックなのでブロックを削除すれば画像も削除されますが、ブロックの一部として画像を挿入している場合は、削除ボタンもあると便利かも知れません。

属性 mediaID の値は初期値またはリセットされた場合は 0 になるので、それ以外の場合(画像が表示されている場合)にボタンを表示します。このボタンの表示も MediaUploadCheck でラップしています。

onClick プロパティには画像を削除する関数 removeMedia を指定しています。

また、このボタンには Button コンポーネントのプロパティ isDestructive と isLink を設定して赤い文字色のリンクのように見せています。

return (
  <div className={ className }>
    <MediaUploadCheck>
      <MediaUpload
        onSelect={ onSelectImage }
        allowedTypes={ ['image'] }
        value={ attributes.mediaID }
        render={ ({ open }) => getImageButton( open ) }
      />
    </MediaUploadCheck>
    { attributes.mediaID != 0  && 
      <MediaUploadCheck>
        <Button 
          onClick={removeMedia} 
          isLink 
          isDestructive 
          className="removeImage">
          画像を削除
        </Button>
      </MediaUploadCheck>
    }
  </div>
);
onSelect プロパティ

以下は onSelect プロパティに指定するメディアが選択されたとき呼び出されるコールバック関数です。

このコールバック関数は選択したメディア(オブジェクト)が引数として渡されるので、そのオブジェクトから alt と url 及び id プロパティを取得することができます。そして setAttributes() メソッドを使って属性の値を取得した値で更新します。

//選択された画像の情報(alt 属性、URL、Media ID)を更新する関数
const onSelectImage = ( media ) => {
  setAttributes( {
    imageAlt: media.alt, 
    imageUrl: media.url, 
    mediaID: media.id 
  } );
  // console.log( media );
};
画像オブジェクトのプロパティ

上記コールバック関数の8行目のコメントを外して画像を選択すると、onSelectImage が実行される際に選択された画像のオブジェクトの情報がコンソールに出力されます。alt や url 以外にも多数のプロパティがあります。例えばキャプションが画像に設定されていれば、media.caption で取得できます。

console.log( media ) でのコンソールへの出力例
alt: "Caribbean Sea"    //alt 属性
author: "1"
authorName: "xxxxxx"
caption: "sea"  //キャプション
compat: {item: "", meta: ""}
context: ""
date: Mon Oct 05 2020 16:35:14 GMT+0900 (日本標準時) {}
dateFormatted: "2020年10月5日"
description: ""   //説明
editLink: "http://localhost/blocks/wp-admin/post.php?post=1528&action=edit"
filename: "sample_03-1.jpg"
filesizeHumanReadable: "192 KB"
filesizeInBytes: 196902
height: 797    //高さ
icon: "http://localhost/blocks/wp-includes/images/media/default.png"
id: 1528   //ID
link: "http://localhost/blocks/2020/10/04/content-slider/sample_03-2/"
menuOrder: 0
meta: false
mime: "image/jpeg"
modified: Fri Oct 09 2020 13:50:19 GMT+0900 (日本標準時) {}
name: "sample_03-2"
nonces: {update: "f59a207883", delete: "5abc2149d7", edit: "30af633ba0"}
orientation: "landscape"
sizes: {thumbnail: {…}, medium: {…}, large: {…}, full: {…}}   //サイズ
status: "inherit"
subtype: "jpeg"
title: "sample_03"   //タイトル
type: "image"
uploadedTo: 1514   //この画像をアップロードした投稿の ID
uploadedToLink: "http://localhost/blocks/wp-admin/post.php?post=1514&action=edit"
uploadedToTitle: "Content Slider"
url: "http://localhost/blocks/wp-content/uploads/2020/10/sample_03-1.jpg"
width: 1200   //幅
__proto__: Object
render プロパティ

以下は render プロパティに指定するコールバック関数です。

この関数はメディアライブラリを開くボタンをレンダリングするために呼び出されるコールバック関数で、引数には呼び出されるとメディアモーダルを開く open という関数が渡されます。open はコンポーネント側で定義されている関数です。

この例では属性 imageUrl に default プロパティに初期値を指定していないので、画像が選択される際に onSelect のコールバック関数で値が設定されていなければ、imageUrl の値は null です。

imageUrl の値が true の場合、画像が選択されているので img をレンダリングしています。その際に属性 imageUrl に設定されている URL を src 属性に指定し、onClick にはメディアモーダルを開く open という関数を指定することで、表示された画像をクリックするとメディアモーダルが開き、画像を選択し直すことができます。

属性 imageUrl の値が設定されていなければまだ画像は選択されていないので、画像をアップロードするためのボタンを Button コンポーネントでレンダリングし、onClick にメディアモーダルを開く open という関数を指定して、ボタンをクリックするとメディアモーダルが開き、画像を選択することができます。

//メディアライブラリを開くボタンをレンダリングする関数
const getImageButton = ( open ) => {
  if(attributes.imageUrl) {
    return (
      <img 
        src={ attributes.imageUrl }
        onClick={ open }
        className="image"
        alt=""
      />
    );
  }
  else {
    return (
      <div className="button-container">
        <Button 
          onClick={ open }
          className="button button-large"
        >
          画像をアップロード
        </Button>
      </div>
    );
  }
};

上記の場合、画像(img 要素)の onClick プロパティに open という関数を指定して、画像をクリックできるようにしていますが、img 要素にマウスオーバーしてもカーソルは通常のままです。

そのため、この画像にはクラスのセレクターを使って以下のような CSS を指定します。

.image {
  cursor: pointer;
}

ボタンと画像の全体を Button でマークアップする例

上記の render プロパティに指定するコールバック関数は、以下のように全体を Button でマークアップすることもできます。

この例では、画像が選択されているかどうかの判定は属性 mediaID を使っています(画像が選択されていない場合、 mediaID の値は初期値の 0、つまり false になります)。

画像が選択されている場合は、image-button というクラスを指定し、img 要素をレンダリングし、画像が選択されていなければ img 要素はレンダリングしません。

//メディアライブラリを開くボタンをレンダリングする関数(全体をボタンでマークアップする例)
const getImageButton = ( open ) => {
  return (
    <Button 
      className={ attributes.mediaID ? 'image-button' : 'button button-large' } 
      onClick={ open }>
      { ! attributes.mediaID ? "画像をアップロードx" :
        <img src={ attributes.imageUrl } className="image" alt="" /> 
      }
    </Button>
  );
}

上記の場合、Button の中に img をレンダリングするため、image-button クラスのボタンの高さを auto にして画像が適切に表示されるようにする必要があります。但し、ボタンで囲んであるので画像にマウスオーバーするとカーソルはポインターになるので cursor: pointer の設定は不要です。

.image-button {
  height: auto;
}
画像を削除する関数

以下は画像を削除するボタンの onClick プロパティに指定しているコールバック関数で、setAttributes を使って属性の値をリセットします。

mediaID を 0 にリセットすると、return ステートメント内の記述により「画像を削除するボタン」は非表示になります。

imageUrl の値が空になると挿入された img 要素の src 属性が空になり、また、render プロパティのコールバック関数により画像(img 要素)の代わりに「画像をアップロードするボタン」が表示されます。

//画像を削除する(メディアをリセットする)関数
const removeMedia = () => {
  setAttributes({
    mediaID: 0,
    imageUrl: '',
    imageAlt: ''
  });
}

save 関数

save 関数はフロントエンド側でブロックがどのようにレンダリングされるかを定義して返す関数です。

引数には props から分割代入で attributes(属性)を受け取っています。そして画像をレンダリングする関数 getImagesSave を定義し、属性 imageUrl と imageAlt の値を元に画像をレンダリングしています。

save: ( { attributes } ) => {
  //画像をレンダリングする関数
  const getImagesSave = (src, alt) => {
    if(!src) return null;
    if(alt) {
      return (
        <img 
          className="card_image" 
          src={ src }
          alt={ alt }
        /> 
      );
    }
    return (
      <img 
        className="card_image" 
        src={ src }
        alt=""
        aria-hidden="true"
      /> 
    );
  };

  return (
    <div className="card">
      { getImagesSave(attributes.imageUrl, attributes.imageAlt) }
    </div>
  );
},

画像をレンダリングする関数 getImagesSave は引数に attributes プロパティに保持されている画像の src と alt 属性の値を取り、それらの値により出力を切り替えます。

src(attributes.imageUrl)が設定されていなければ、画像はまだ選択されていないので、null を返します。何もレンダリングしない場合、JSX では null を返します。

alt(attributes.imageAlt)が設定されていれば、選択された画像には alt 属性が設定されているので、その値をレンダリングする画像の alt 属性に設定した img 要素を返します。

alt が設定されていない場合は、alt 属性を空で設定し、aria-hidden="true" を設定し、スクリーンリーダーではこの要素が非表示であることをブラウザに伝えます。

また、attributes プロパティで属性 imageUrl と imageAlt の selector プロパティに指定したクラス(card_image)を設定しています。

スタイル

create-block を実行した場合、エディター用のスタイルは src ディレクトリの editor.scss に、フロントエンド及びエディターに適用されるスタイルは style.scss に記述します。

エディター用のスタイルでは、render プロパティのコールバック関数で挿入された image クラスを指定した img 要素に cursor: pointer を設定します。また、以下ではブロックに自動的に付与されるクラス名を使って枠線を設定しています。

src/editor.scss
.wp-block-wdl-my-images {
  border: 1px solid #999;
}

.image {
  cursor: pointer;
}

style.scss には背景色と文字色及びパディングを設定しています。

src/style.scss
.wp-block-wdl-my-images {
  background-color: #fefefe;
  color: #666;
  padding: 10px;
}

妥当性検証プロセス

src/index.js を変更後、投稿のページを再読み込みすると以下のように「このブロックには、想定されていないか無効なコンテンツが含まれています」と表示され、コンソールにはエラーが表示されます。

これは、エディターが現在定義しているものとは異なる save 関数の出力を検出するために発生します。

※ 静的なブロックでは save 関数の return ステートメント内に変更が発生するとブロックの検証(妥当性検証プロセス)により、ブロックは無効(invalid)としてマークされ上記のように表示されます。

この場合、表示されるボタンをクリックして「ブロックを削除」を選択し、現在のブロックを一度削除してから再度ブロックを挿入します。

再度ブロックを挿入すると以下のように表示されます。

画像を挿入して「更新」ボタンをクリックして保存すると以下のような表示になります。「画像を削除」をクリックすると画像が削除され、「画像をアップロード」するボタンが表示されます。

以下はフロントエンド側の表示です。

MediaUpload のプロパティ

MediaUpload コンポーネントには以下のようなプロパティがあります。

MediaUpload コンポーネントのプロパティ
プロパティ 説明
allowedTypes メディアライブラリからアップロード・選択できるメディアのタイプを配列で指定。それぞれのメディアタイプは 'image', 'audio', 'text' のような mime type または 'audio/mpeg', 'image/gif' のような完全な mime type を指定。
multiple 複数選択を許可するかどうかの真偽値。デフォルトは false
value 選択したメディアの Media ID (数値)。multiple が true の場合は media ID の配列
onClose メディアモーダルが閉じられたときに呼び出されるコールバック関数。メディアが選択されたときと、ユーザーが選択せずにモーダルを閉じたときに呼び出されます。
onSelect メディアが選択されメディアモーダルが閉じられたとき呼び出されるコールバック関数(onClose の後に呼び出されます)。選択したメディア(オブジェクト)が引数として渡されます。
title メディアモーダルに表示するタイトル。デフォルトは「Select or Upload Media」
modalClass メディアモーダルのフレームに追加される CSS クラス
addToGallery true の場合、ギャラリーメディアモーダルはユーザーが画像を追加できるメディアライブラリで直接開きます。false の場合、ギャラリーメディアモーダルは編集モードで開き、ユーザーは既存の画像を並べ替えたり、削除したり、属性を変更したりして、既存の画像を編集できます。 gallery === trueの場合にのみ適用されます。デフォルトは false
gallery true の場合、コンポーネントはギャラリーとして開始します。 デフォルトでは、メディアモーダルはギャラリー編集モードで開きますが、addToGallery を使用して変更できます。デフォルトは false
render メディアライブラリを開くボタンをレンダリングするために呼び出されるコールバック関数。コールバック関数の引数には、呼び出されるとメディアモーダルを開く open という関数が渡されます。

multiple プロパティ(複数画像)

MediaUpload コンポーネントはデフォルトでは1つの画像(メディア)しか選択できないようになっていますが、multiple プロパティ を true に設定することで画像を複数選択できるようになります。

<MediaUploadCheck>
  <MediaUpload
    multiple={ true }  // プロパティを追加設定
    onSelect={ onSelectImage }
    allowedTypes={ ['image'] }
    value={ attributes.mediaID }
    render={ ({ open }) => getImageButton( open ) }
  />
</MediaUploadCheck>

メディアモーダルを開いて複数の画像を選択できるようになります。

但し、multiple プロパティ を true に設定しただけでは選択した画像はブロックに挿入されません。

これは onSelect プロパティのコールバック関数に渡されるオブジェクトが配列で渡されるようになるためです。

複数の画像を挿入できるようにするにはブロックのコードを変更する必要があります。

attributes の変更

画像を1つだけ選択する場合に設定していた属性は以下になります。この場合、mediaID はブロックのコメントデリミタに JSON 形式で保存されます。imageUrl と imageAlt は card_image クラスの要素の属性 src 及び alt から取得します。

attributes: {
  //MediaUpload の value の値
  mediaID: {
    type: 'number',
    default: 0
  },
  //img の src に指定する URL
  imageUrl: {
    type: 'string',
    source: 'attribute',
    attribute: 'src',
    selector: '.card_image'
  },
  //img の alt 属性の値 
  imageAlt: {
    type: 'string',
    source: 'attribute',
    attribute: 'alt',
    selector: '.card_image'
  },
},

複数の画像の ID、URL、alt の値を保存するため、type プロパティを配列に、default プロパティを空の配列に変更します。

imageUrl と imageAlt は要素の属性に保存していましたが、mediaID 同様、ブロックのコメントデリミタに JSON 形式で保存するように変更します。

attributes: {
  //MediaUpload の value の値
  mediaID: {
    type: 'array', //array に変更
    default: [] //空の配列に変更
  },
  //img の src に指定する URL
  imageUrl: {
    type: 'array', //array に変更
    default: [] //初期値を設定
  },
  //img の alt 属性の値 
  imageAlt: {
    type: 'array', //array に変更
    default: [] //初期値を設定
  },
},

onSelect の変更

onSelect プロパティに指定するコールバック関数を変更します。

メディアが選択されたとき呼び出されるコールバック関数の引数(media)にはメディアオブジェクトの配列が渡されるので、メディアオブジェクトの配列(media)から map でそれぞれのプロパティの配列を生成して、setAttributes で属性の値を設定(更新)します。

//選択された画像の情報を更新する関数
const onSelectImage = ( media ) => {
  // media から map で id プロパティの配列を生成
  const media_ID = media.map((image) => image.id);
  // media から map で url プロパティの配列を生成
  const imageUrl = media.map((image) => image.url);
  // media から map で alt プロパティの配列を生成
  const imageAlt = media.map((image) => image.alt);

  setAttributes( {
    mediaID: media_ID,  //メディア ID の配列
    imageUrl: imageUrl,  // URL の配列
    imageAlt: imageAlt,  // alt 属性の配列
  } );  
};

render の変更

render プロパティに指定するコールバック関数を変更します。

メディアライブラリを開くボタンをレンダリングするコールバック関数では、複数の画像が選択されると複数の画像をレンダリングする必要があるので、選択されたメディアオブジェクトの URL の配列(属性 imageUrl)からレンダリングする画像を生成する関数 getImages を定義します。

そしてその関数を使って render プロパティ に指定するコールバック関数(メディアライブラリを開くボタンをレンダリングする関数)を定義します。

その際、画像が選択されているかどうかは、URL の数(attributes.imageUrl.length)で判定します。また、画像は div 要素でラップし、その div 要素に onClick プロパティを設定し、モーダルを開く関数 open を設定します。

画像が選択されていない場合のボタンのレンダリングはそのままで同じです。

//URL の配列から画像を生成
const getImages = ( urls ) => {
  let imagesArray = urls.map(( url ) => {
    return (
      <img 
        src={ url }
        className="image"
        alt="アップロード画像"
      />
    );
  });
  return imagesArray;
}

//メディアライブラリを開くボタンをレンダリングする関数(上記関数を使って画像をレンダリング)
const getImageButton = (open) => {
  if(attributes.imageUrl.length > 0 ) {
    return (
      <div onClick={ open } className="block-container">
       { getImages( attributes.imageUrl ) }
      </div>
    )
  }
  else {
    return (
      <div className="button-container">
        <Button 
          onClick={ open }
          className="button button-large"
        >
          画像をアップロード
        </Button>
      </div>
    );
  }
};

画像を削除する関数の変更

画像を削除する関数では、setAttributes() で属性の値を初期値の空の配列 [ ] で設定してメディアをリセットします。

//画像を削除する(メディアをリセットする)関数
const removeMedia = () => {
  setAttributes({
    mediaID: [],
    imageUrl: [],
    imageAlt: [],
  });
}  

return ステートメントの変更

return ステートメントでは MediaUpload に multiple プロパティを追加します。

削除ボタンを表示するかどうかは、属性の imageUrl(URL の配列の長さ)で判定します。

return (
      <div className={ className }>
        <MediaUploadCheck>
          <MediaUpload
            multiple={ true }
            onSelect={ onSelectImage }
            allowedTypes={ ['image'] }
            value={ attributes.mediaID }
            render={ ({ open }) => getImageButton( open ) }
          />
        </MediaUploadCheck>
        { attributes.imageUrl.length != 0  &&   // imageUrl(配列の長さ)で判定
          <MediaUploadCheck>
            <Button 
              onClick={removeMedia} 
              isLink 
              isDestructive 
              className="removeImage">画像を削除
            </Button>
          </MediaUploadCheck>
        }
      </div>
    );

save 関数の変更

save 関数では複数の画像をレンダリングするように変更します。

画像をレンダリングする関数 getImagesSave では、引数に選択された画像の URL と alt 属性の値の配列を受け取るように変更し、for 文を使って選択された画像のレンダリングを生成します。

save: ( { attributes } ) => {

  //画像をレンダリングする関数
  const getImagesSave = ( url, alt ) => {
    let image_elem;
    let imagesArray = [];

    for(let i = 0 ; i < url.length; i ++) {
      if( url.length === 0 ) {
        image_elem = null;
      }else{
        if( alt[i] ) {
          image_elem =  (
            <img 
              className="card_image" 
              src={ url[i] }
              alt={ alt[i] }
            /> 
          );
        }else{
          image_elem = (
            <img 
              className="card_image" 
              src={ url[i] }
              alt=""
              aria-hidden="true"
            /> 
          );
        }
      }
      imagesArray.push( image_elem ) ;
    }
    return imagesArray;
  }

  return (
    <div className="block-container">
     { getImagesSave( attributes.imageUrl, attributes.imageAlt ) }
    </div>
  );
},

スタイルの追加

この例では挿入する複数の画像を Flexbox を使って配置するように style.scss に以下を追加します。

.block-container {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
}

.block-container img {
  width: 100%;
  max-width: 160px;
  margin: 10px;
}

例えば、3つの画像を選択して挿入した場合、エディター側では以下のような表示になります。

src/index.js
import { registerBlockType } from '@wordpress/blocks';
import { MediaUpload, MediaUploadCheck } from '@wordpress/block-editor';
import { Button } from '@wordpress/components';
import './editor.scss';
import './style.scss';
 
registerBlockType( 'wdl/my-images', {
  title: 'My Images',
  description: 'Example block written with ESNext',
  category: 'widgets',
  icon: 'smiley',
  //属性を設定
  attributes: {
    mediaID: {
      type: 'array', //array に変更
      default: [] //空の配列に変更
    },
    //img の src に指定する URL
    imageUrl: {
      type: 'array',
      default: []
    },
    //img の alt 属性の値 
    imageAlt: {
      type: 'array',
      default: []
    },
  },
  
  edit: ( props ) => {
    //分割代入を使って props 経由でプロパティを変数に代入
    const { className, attributes, setAttributes} = props;
    
    //選択された画像の情報を更新する関数
    const onSelectImage = ( media ) => {
      // media から map で id プロパティの配列を生成
      const media_ID = media.map((image) => image.id);
      // media から map で url プロパティの配列を生成
      const imageUrl = media.map((image) => image.url);
      // media から map で alt プロパティの配列を生成
      const imageAlt = media.map((image) => image.alt);
      
      setAttributes( {
        mediaID: media_ID,  //メディア ID の配列
        imageUrl: imageUrl,  // URL の配列
        imageAlt: imageAlt,  // alt 属性の配列
      } );  
    };
    
    //URL の配列から画像を生成
    const getImages = ( urls ) => {
      let imagesArray = urls.map(( url ) => {
        return (
          <img 
            src={ url }
            className="image"
            alt="アップロード画像"
          />
        );
      });
      return imagesArray;
    }

    //メディアライブラリを開くボタンをレンダリングする関数(上記関数を使って画像をレンダリング)
    const getImageButton = (open) => {
      if(attributes.imageUrl.length > 0 ) {
        return (
          <div onClick={ open } className="block-container">
           { getImages( attributes.imageUrl ) }
          </div>
        )
      }
      else {
        return (
          <div className="button-container">
            <Button 
              onClick={ open }
              className="button button-large"
            >
              画像をアップロード
            </Button>
          </div>
        );
      }
    }
    
    //画像を削除する(メディアをリセットする)関数
    const removeMedia = () => {
      setAttributes({
        mediaID: [],
        imageUrl: [],
        imageAlt: [],
      });
    }  

    return (
      <div className={ className }>
        <MediaUploadCheck>
          <MediaUpload
            multiple={ true }
            onSelect={ onSelectImage }
            allowedTypes={ ['image'] }
            value={ attributes.mediaID }
            render={ ({ open }) => getImageButton( open ) }
          />
        </MediaUploadCheck>
        { attributes.imageUrl.length != 0  &&   // imageUrl(配列の長さ)で判定
          <MediaUploadCheck>
            <Button 
              onClick={removeMedia} 
              isLink 
              isDestructive 
              className="removeImage">画像を削除
            </Button>
          </MediaUploadCheck>
        }
      </div>
    );
  },
  save: ( { attributes } ) => {

    //画像をレンダリングする関数
    const getImagesSave = ( url, alt ) => {
      let image_elem;
      let imagesArray = [];
      
      for( let i = 0 ; i < url.length; i ++ ) {
        if( url.length === 0 ) {
          image_elem = null;
        }else{
          if( alt[i] ) {
            image_elem =  (
              <img 
                className="card_image" 
                src={ url[i] }
                alt={ alt[i] }
              /> 
            );
          }else{ 
            image_elem = (
              <img 
                className="card_image" 
                src={ url[i] }
                alt=""
                aria-hidden="true"
              /> 
            );
          }
        }
        imagesArray.push( image_elem ) ;
      }
      return imagesArray;
    }

    return (
      <div className="block-container">
       { getImagesSave( attributes.imageUrl, attributes.imageAlt ) }
      </div>
    );
  },
});

メディアオブジェクトの配列を属性に保存

2020年10月18日 追記

attributes の変更では、属性に URL、alt 属性、キャプションの値の配列を設定しましたが、media(メディアオブジェクトの配列)を使うこともできます。

但し、その場合、保存される属性のデータが大きくなってしまいます。

属性 に source プロパティを指定しない場合、属性はブロックのコメントデリミタに JSON 形式で保存され、ロード時に読み出されます。

URL や alt 属性、キャプションの配列を保存した場合、エディター画面でコードエディターに切り替えて確認すると以下のように属性がコメントデリミタに保存されます。

見やすいように改行しています
<!-- wp:wdl/my-images {

"mediaID":[1530,1528,1520],"

imageUrl":["http://localhost/blocks/wp-content/uploads/2020/10/sample_01-2.jpg","http://localhost/blocks/wp-content/uploads/2020/10/sample_03-1.jpg","http://localhost/blocks/wp-content/uploads/2020/10/sample_02-1.jpg"],

"imageAlt":["","Caribbean Sea","beach chairs lined up"],

"imageCaption":["beach","sea",""]} -->

<div class="wp-block-wdl-my-images block-container"><figure><img class="card_image" src="http://localhost/blocks/wp-content/uploads/2020/10/sample_01-2.jpg" alt=""/><figcaption class="block-image-caption">beach</figcaption></figure><figure><img class="card_image" src="http://localhost/blocks/wp-content/uploads/2020/10/sample_03-1.jpg" alt="Caribbean Sea"/><figcaption class="block-image-caption">sea</figcaption></figure><figure><img class="card_image" src="http://localhost/blocks/wp-content/uploads/2020/10/sample_02-1.jpg" alt="beach chairs lined up"/><figcaption class="block-image-caption"></figcaption></figure></div>
<!-- /wp:wdl/my-images -->

上記は3つの画像をブロックに保存した場合の例ですが、同様に3つの画像オブジェクトの配列を属性に保存すると以下のようにデータが大きくなります(以下の画像では全てが収まらないので途中で切れています)。

見やすいように改行しています
<!-- wp:wdl/my-images {

"mediaID":[1530,1528,1520],

//メディアオブジェクトの配列
"media":[{"sizes":{"thumbnail":{"height":150,"width":150,"url":"http://localhost/blocks/wp-content/uploads/2020/10/sample_01-2-150x150.jpg","orientation":"landscape"},"medium":{"height":199,"width":300,"url":"http://localhost/blocks/wp-content/uploads/2020/10/sample_01-2-300x199.jpg","orientation":"landscape"},"full":{"url":"http://localhost/blocks/wp-content/uploads/2020/10/sample_01-2.jpg","height":398,"width":600,"orientation":"landscape"}},"mime":"image/jpeg","type":"image","subtype":"jpeg","id":1530,"url":"http://localhost/blocks/wp-content/uploads/2020/10/sample_01-2.jpg","alt":"","link":"http://localhost/blocks/2020/10/04/content-slider/sample_01-2/","caption":"beach"},{"sizes":{"thumbnail":{"height":150,"width":150,"url":"http://localhost/blocks/wp-content/uploads/2020/10/sample_03-1-150x150.jpg","orientation":"landscape"},"medium":{"height":199,"width":300,"url":"http://localhost/blocks/wp-content/uploads/2020/10/sample_03-1-300x199.jpg","orientation":"landscape"},"large":{"height":385,"width":580,"url":"http://localhost/blocks/wp-content/uploads/2020/10/sample_03-1-1024x680.jpg","orientation":"landscape"},"full":{"url":"http://localhost/blocks/wp-content/uploads/2020/10/sample_03-1.jpg","height":797,"width":1200,"orientation":"landscape"}},"mime":"image/jpeg","type":"image","subtype":"jpeg","id":1528,"url":"http://localhost/blocks/wp-content/uploads/2020/10/sample_03-1.jpg","alt":"Caribbean Sea","link":"http://localhost/blocks/2020/10/04/content-slider/sample_03-2/","caption":"sea"},{"sizes":{"thumbnail":{"height":150,"width":150,"url":"http://localhost/blocks/wp-content/uploads/2020/10/sample_02-1-150x150.jpg","orientation":"landscape"},"medium":{"height":199,"width":300,"url":"http://localhost/blocks/wp-content/uploads/2020/10/sample_02-1-300x199.jpg","orientation":"landscape"},"full":{"url":"http://localhost/blocks/wp-content/uploads/2020/10/sample_02-1.jpg","height":531,"width":800,"orientation":"landscape"}},"mime":"image/jpeg","type":"image","subtype":"jpeg","id":1520,"url":"http://localhost/blocks/wp-content/uploads/2020/10/sample_02-1.jpg","alt":"beach chairs lined up","link":"http://localhost/blocks/2020/10/04/content-slider/sample_02-1/","caption":""}],

<div class="wp-block-wdl-my-images block-container"><figure><img class="card_image" src="http://localhost/blocks/wp-content/uploads/2020/10/sample_01-2.jpg" alt="" aria-hidden="true"/><figcaption class="block-image-caption">beach</figcaption></figure><figure><img class="card_image" src="http://localhost/blocks/wp-content/uploads/2020/10/sample_03-1.jpg" alt="Caribbean Sea"/><figcaption class="block-image-caption">sea</figcaption></figure><figure><img class="card_image" src="http://localhost/blocks/wp-content/uploads/2020/10/sample_02-1.jpg" alt="beach chairs lined up"/><figcaption class="block-image-caption"></figcaption></figure></div>
<!-- /wp:wdl/my-images -->

例えば、以下のように属性にメディアオブジェクト(画像オブジェクト)の配列を指定した場合、

//属性を設定
attributes: {
  mediaID: {
    type: 'array', //array に変更
    default: [] //空の配列に変更
  },
  //属性 media(メディアオブジェクトの配列)を追加
  media: {
    type: 'array',
    default: []
  }
}

画像を生成する関数などでは、URL や alt 属性、キャプションなどはメディアオブジェクトのプロパティとして取得できるので記述は多少簡潔になり、また、追加で他のプロパティを使用する場合では、属性を新たに追加しないで済みます。

そのため、当初はメディアオブジェクトの配列をループす方法で以下のように記述していましたが、挿入する画像が増えるほど保存するデータは大きくなってしまうため属性に URL、alt 属性、キャプションの値の配列を設定するように書き換えました。

import { registerBlockType } from '@wordpress/blocks';
import { MediaUpload, MediaUploadCheck } from '@wordpress/block-editor';
import { Button } from '@wordpress/components';
import './editor.scss';
import './style.scss';
 
registerBlockType( 'wdl/my-images', {
  title: 'My Images',
  description: 'Example block written with ESNext',
  category: 'widgets',
  icon: 'smiley',
  //属性を設定
  attributes: {
    mediaID: {
      type: 'array', 
      default: [] 
    },
    //属性 media(メディアオブジェクトの配列)
    media: {
      type: 'array',
      default: []
    }
  },
  
  edit: ( props ) => {
    //分割代入を使って props 経由でプロパティを変数に代入
    const { className, attributes, setAttributes} = props;
    
    //選択された画像の情報(ID とメディアオブジェクト)を更新する関数
    const onSelectImage = ( media ) => {
      // media から map で id プロパティの配列を生成
      const media_ID = media.map((image) => image.id);
      setAttributes( {
        mediaID: media_ID,  //メディア ID の配列
        media: media  //メディアオブジェクトの配列
      } );  
    };
    
    //media の配列から画像を生成(figure と figcaption を使ってレンダリング)
    const getImages = ( media ) => {
      //メディアオブジェクトの配列をループ処理
      let imagesArray = media.map(( image ) => {
        return (
          <figure>
            <img 
              src={ image.url }
              className="image"
              alt="アップロード画像"
            />
            <figcaption className="block-image-caption">
              { image.caption ? image.caption : '' }
            </figcaption>
          </figure>
        );
      });
      return imagesArray;
    }
    
    //メディアライブラリを開くボタンをレンダリングする関数(上記関数を使って画像をレンダリング)
    const getImageButton = (open) => {
      if(attributes.media.length > 0 ) {
        return (
          <div onClick={ open } className="block-container">
           { getImages( attributes.media ) }
          </div>
        )
      }
      else {
        return (
          <div className="button-container">
            <Button 
              onClick={ open }
              className="button button-large"
            >
              画像をアップロード
            </Button>
          </div>
        );
      }
    };
      
    //画像を削除する(メディアをリセットする)関数
    const removeMedia = () => {
      setAttributes({
        mediaID: [],
        media: [],
      });
    }  
 
    return (
      <div className={ className }>
        <MediaUploadCheck>
          <MediaUpload
            multiple={ true }
            gallery={ true }  //追加
            onSelect={ onSelectImage }
            allowedTypes={ ['image'] }
            value={ attributes.mediaID }
            render={ ({ open }) => getImageButton( open ) }
          />
        </MediaUploadCheck>
        { attributes.media.length != 0  && //メディアオブジェクト(配列の長さ)で判定
          <MediaUploadCheck>
            <Button 
              onClick={removeMedia} 
              isLink 
              isDestructive 
              className="removeImage">画像を削除
            </Button>
          </MediaUploadCheck>
        }
      </div>
    );
  },
    
  save: ( { attributes } ) => {
    //画像をレンダリングする関数
    const getImagesSave = ( obj ) => {
      let image_elem;
      const imagesArray = obj.map(( image ) => {
        if( !image.url ) {
          image_elem = null;
        }else{
          if( image.alt ) {
            image_elem =  (
              <figure>
                <img 
                  className="card_image" 
                  src={ image.url }
                  alt={ image.alt }
                /> 
              <figcaption className="block-image-caption">
                { image.caption ? image.caption : '' }
              </figcaption>
              </figure>
            );
          }else{
            image_elem = (
              <figure>
                <img 
                  className="card_image" 
                  src={ image.url }
                  alt=""
                  aria-hidden="true"
                /> 
              <figcaption className="block-image-caption">
                { image.caption ? image.caption : '' }
              </figcaption>
              </figure>
            );
          }
        }
        return image_elem;
      });
      return imagesArray;
    }
    
    return (
      <div className="block-container">
       { getImagesSave(attributes.media) }
      </div>
    );
  
  },
} );

関連ページ