Gutenberg(ブロックエディタ)メモ: サイドバーを作ってラジオボタンを使う

WordPressのGutenberg
WordPressのGutenberg

WordPressのGutenbergハンドブックに載っている記述そのままでは作る方法が分からず、別の方法での実装を試した内容です。

注意点

  • Gutenbergがコアに入る前の段階の記事です
  • 書き方だけではなく、語句や名称にも問題がある可能性があります
  • バッドノウハウな状態だと思いますので、読む方は参考程度に考えてください
  • 私自身の書き方がまだ固まっていないため、当ブログ内のサンプルコードにはconstやfunction、アロー関数の使用の有無など、表記揺れがあります

実現したいこと

  • Gutenbergでブロック編集時にサイドバーを付ける
  • サイドバー内にラジオボタンを付けて動作させる

ハンドブックでは以下のページに説明があります。

サンプル1 withState不使用

[2018.9.27 追記]

この記事を書いた当初より理解が進み、withStateを使わない方法で作っても良さそうだと思えたので、withStateを無視して作成したサンプルです。

役に立つとは思いませんが、以前のwithState使って無理矢理作ったサンプルも一応サンプル2として後述していますので見られる方は下にスクロールしてください。


//初期設定
add_action( 'enqueue_block_editor_assets', function() {
  wp_enqueue_script(
    'myplugin-gutenberge',
    plugins_url( 'block.js', __FILE__ ),
    [ 'wp-blocks', 'wp-element', 'wp-components', 'wp-editor' ]
  );
} );

const { registerBlockType } = wp.blocks;
const { Fragment } = wp.element;
const {
    RichText,
    BlockControls,
    AlignmentToolbar,
    InspectorControls,
} = wp.editor;
const {
    PanelBody,
    RadioControl,
} = wp.components;

registerBlockType( 'my-plugin/test-inspector-radio-nostate', {
  title: 'test-inspector-radio-nostate',
  icon: 'universal-access-alt',
  category: 'layout',

  attributes: {
    content: {
      type: 'array',
      source: 'children',
      selector: 'p',
    },
    alignment: {
      type: 'string',
    },
    option: {
      type: 'string',
      default: 'a',
    }
  },
  edit( { attributes, setAttributes } ) {
    const { content, option, alignment } = attributes;

    function onChangeContent( newContent ) {
     setAttributes( { content: newContent } );
    }
    function onChangeAlignment( newAlignment ) {
     setAttributes( { alignment: newAlignment } );
    }
    function onChangeOption( newOption ) {
     setAttributes( { option: newOption } );
    }

    return(
      <Fragment>
        <BlockControls>
          <AlignmentToolbar
            value={ alignment }
            onChange={ onChangeAlignment }
          />
        </BlockControls>
        <InspectorControls>
          <PanelBody title="著者表記">
            <RadioControl
            label="User type"
            help="The type of the current user"
            selected={ option }
            options={ [
                { label: 'Author', value: 'a' },
                { label: 'Editor', value: 'e' },
            ] }
            onChange={ onChangeOption }
          />
          </PanelBody>
        </InspectorControls>
        <RichText
          key="editable"
          tagName="p"
          style={ { textAlign: alignment } }
          onChange={ onChangeContent }
          value={ content }
        />
      </Fragment>
    );
  },
  save( { attributes, className } ) {
    const { content, option, alignment } = attributes;
    let value = attributes.option;

    const addName = ( value ) => {
      switch( value ) {
        case 'a':
         return "Author";
         break;
        case 'e':
         return "Editor";
         break;
        default:
         return "John Does";
        break;
      }
    };

    return (
      <div>
        <RichText.Content
          style={ { textAlign: alignment } }
          value={ content }
          tagName="p"
        />
        <p class="author">By {addName(value)} </p>
      </div>
    );
  },
} );

サンプル2 withState使用

RadioControl完成例
RadioControl

以下は「ラジオボタンで著者名表示を変える」という動作を目的としたサンプルですが、ハンドブックにあるwithStateをなんとか使おうとして半ば無理矢理作ったものです。


//初期設定
add_action( 'enqueue_block_editor_assets', function() {
  wp_enqueue_script(
    'myplugin-gutenberge',
    plugins_url( 'block.js', __FILE__ ),
    [ 'wp-blocks', 'wp-element', 'wp-components', 'wp-editor', 'wp-compose' ]
  );
} );

const { registerBlockType } = wp.blocks;
const { Fragment } = wp.element;
const {
    RichText,
    BlockControls,
    AlignmentToolbar,
    InspectorControls,
} = wp.editor;
const {
    PanelBody,
    RadioControl,
} = wp.components;
const { withState } = wp.compose;

const edit = ( {attributes, className, setAttributes, option, setState} ) => {
  const { content, alignment, newOption } = attributes;

  option = newOption;

  function onChangeContent( newContent ) {
   setAttributes( { content: newContent } );
  }
  function onChangeAlignment( newAlignment ) {
   setAttributes( { alignment: newAlignment } );
  }
  function onChangeOption( newOption ) {
   setAttributes( { newOption: newOption } );
  }

  return (
    <Fragment>

      <BlockControls>
        <AlignmentToolbar
          value={ alignment }
          onChange={ onChangeAlignment }
        />
      </BlockControls>
      <InspectorControls>
        <PanelBody title="著者表記">
          <RadioControl
          label="User type"
          help="The type of the current user"
          selected={ option }
          options={ [
              { label: 'Author', value: 'a' },
              { label: 'Editor', value: 'e' },
          ] }
          onChange={ ( option ) => { setState( { option }, onChangeOption(option)) } }
        />
        </PanelBody>
      </InspectorControls>
      <RichText
        key="editable"
        tagName="p"
        style={ { textAlign: alignment } }
        onChange={ onChangeContent }
        value={ content }
      />
    </Fragment>
  );
}

const save = ( { attributes, className} ) => {
  const { content, alignment, newOption } = attributes;
  let value = attributes.newOption;

  function addName( value ) {
    switch( value ) {
      case "a":
       return "Author";
       break;
      case "e":
       return "Editor";
       break;
      default:
       return "John Does";
      break;
    }
  }

  return (
    <div >
      <RichText.Content
        style={ { textAlign: alignment } }
        value={ content }
        tagName="p"
      />
      <p class="author2">By { addName(value) } </p>
    </div>
  );
}

registerBlockType( 'my-plugin/test-inspector-radio', {
  title: 'test-inspector-radio',
  icon: 'universal-access-alt',
  category: 'layout',

  attributes: {
    content: {
      type: 'array',
      source: 'children',
      selector: 'p',
    },
    alignment: {
      type: 'string',
    },
    newOption: {
      type: 'string',
      default: 'a',
    }
  },

  edit: withState() (edit),
  save: save,
} );

ハンドブックではRadioControlの使用に際しwithStateを使っていますが、この形で値を取り出す公式的な方法がわからず、主に下記ページを参考にしながらかなり無理矢理に作った状態です。

withStateで設定できる初期値を使わず、attributesで初期値の設定を行っています。

説明入りサンプル

以下は上記と同じコードにコメントで説明を入れたものです。


//ブロックが作れるようにregisterBlockTypeを読み込む
const { registerBlockType } = wp.blocks;
//editやsave内で<Fragment>が使えるように、 wp.elementから読み込む
const { Fragment } = wp.element;
//editやsave内で<RichText>などが使えるように、 wp.editorから読み込む
//サイドバー内への配置をしている<InspectorControls>もここで設定
const {
    RichText,
    BlockControls,
    AlignmentToolbar,
    InspectorControls,
} = wp.editor;
//editやsave内で<RadioControl>などが使えるように、 wp.componentsから読み込む
const {
    PanelBody,
    RadioControl,
} = wp.components;
//withStateが使えるように、 wp.composeから読み込む
const { withState } = wp.compose;

//編集画面用の設定を作成
//withStateを使う際に分かりやすくするために、registerBlockTypeから分離
//optionとsetStateはサンプル通りで、他は必要に応じて追加
const edit = ( {attributes, className, setAttributes, option, setState} ) => {
  //attributesでまとめていた各属性を分けて使えるようにする
  //こうしておけば{alignment}と書けばalignment: centerのように出力される
  const { content, alignment, newOption } = attributes;

  //stateとは別にattributes内にnewOptionを作っており(この点後述)、そのnewOptionの値をstate内のoptionの値に入れて、保存後のラジオボタンの選択を反映させている
  option = newOption;

  //RichTextへの入力内容を取得して更新する
  function onChangeContent( newContent ) {
   setAttributes( { content: newContent } );
  }
  //AlignmentToolbarの値を取得して更新する
  function onChangeAlignment( newAlignment ) {
   setAttributes( { alignment: newAlignment } );
  }
  //選択されたラジオボタンのvalueをattributes内のnewOptionに設定
  function onChangeOption( newOption ) {
   setAttributes( { newOption: newOption } );
  }

  return (
    <Fragment>
      {//ブロックの上の場所を指定
      }
      <BlockControls>
        {//並び方向を設定するUIをこれだけで出力できる
        }
        <AlignmentToolbar
          value={ alignment }
          onChange={ onChangeAlignment }
        />
      </BlockControls>
      {//サイドバーの場所を指定
      }
      <InspectorControls>
        {/*
        個別に分けるために設定
        titleを設定するとトグルで開閉式にできる。必須ではない。
        */}
        <PanelBody title="著者表記">
          {//ラジオボタンの設定
          }
          <RadioControl
          label="User type"
          help="The type of the current user"
          selected={ option }
          options={ [
              { label: 'Author', value: 'a' },
              { label: 'Editor', value: 'e' },
          ] }
          {/*
           ラジオボタンの設定
           setStateのコールバックでonChangeOptionを動作させてnewOptionsにも同時に同じ値を送る
          */}
          onChange={ ( option ) => { setState( { option }, onChangeOption(option)) } }
        />
        </PanelBody>
      </InspectorControls>
      {//リッチテキストエディタの設定
      }
      <RichText
        key="editable"
        tagName="p"
        style={ { textAlign: alignment } }
        onChange={ onChangeContent }
        value={ content }
      />
    </Fragment>
  );
}

//editと形をあわせる意味でregisterBlockTypeから分離
const save = ( { attributes, className} ) => {
  //最初に設定し、edit内で操作したattributesの値をattributesと書くことでまとめて引数にしている
  const { content, alignment, newOption } = attributes;
  //分岐に使うためにnewOptionの値を取り出す
  let value = attributes.newOption;

  //分岐に使うためにnewOptionの値を頼りに、出力内容を変更する
  //defaultはまず要らないはずだが一応設定
  function addName( value ) {
    switch( value ) {
      case "a":
       return "Author";
       break;
      case "e":
       return "Editor";
       break;
      default:
       return "John Does";
      break;
    }
  }

  return (
    <div >
      <RichText.Content
        style={ { textAlign: alignment } }
        value={ content }
        tagName="p"
      />
      {//上で設定した関数を使って値を出力
      }
      <p class="author2">By { addName(value) } </p>
    </div>
  );
}

//編集画面用の設定を作成
registerBlockType( 'my-plugin/test-inspector-radio', {
  //iconやcategoryなどの設定(今回は適当に決定)
  title: 'test-inspector-radio',
  icon: 'universal-access-alt',
  category: 'layout',

  //属性とその値を利用できるように設定。初期設定のようなもの。
  //atributesに入れてしまえば、edit内の値をsave内に簡単に持ち込める
  attributes: {
    content: {
      type: 'array',
      source: 'children',
      selector: 'p',
    },
    alignment: {
      type: 'string',
    },
    newOption: {
      type: 'string',
      //ラジオボタンの初期値を設定
      default: 'a',
    }
  },

  //editは投稿画面内の内容を設定
  //edit全体を囲うようにwithStateを記述
  //ハンドブックにある初期値は使わない
  edit: withState() (edit),

  //saveは保存時の内容を設定。基本的にはそのまま出力されるので、記事表示時の内容とほぼ同義。
  //今回は分離しているのでこれだけ
  save: save,
} );

witStateの使い方を把握できなかったのが根本的な原因といえますが、withState内で変更された値(今回はラジオボタンのvalue)の保存と保存後の取り出し方法に関して無理矢理な方法をとっています。

具体的にはsetStateのコールバック関数を使って、stateと同じ値をattrigutesに設定した属性に設定し、それを保存時や保存後の取り出し時に利用しています。

結果としてなんとか動くところまでは来ましたが、明らかに公式が想定していない作りになりました。

そのため、今後目にする記事や情報次第で追記などではなく当記事を削除する可能性もありますので、その点ご容赦ください。

関連記事

Gutenberg(ブロックエディタ)に関連する記事一覧。

1人がこの記事を評価

役に立ったよという方は上の「記事を評価する」ボタンをクリックしてもらえると嬉しいです。

連投防止のためにCookie使用。SNSへの投稿など他サービスとの連動は一切ありません。

コメント欄