WordPress(Gutenberg) の標準ブロックの親要素に任意の class
を挿入するために、以前、元のブロックを class
を引き継いで div
でラップする少々トリッキーな方法をご紹介しました。
しかし、WordPress 5.9 以降では、この方法でも「Block validation: Block validation failed for…」のエラーが返されるようになりました。
そこで、今回は editor.BlockListBlock
フィルター(エディター側)と blocks.getSaveContent.extraProps
フィルター(保存時)を利用した正攻法で実装してみます。
カスタム属性を追加する
前段階として、WordPress の任意のブロックをカスタマイズするための独自のカスタム属性を blocks.registerBlockType フィルターを利用して追加します。
ここでは、標準ブロックである「テーブル」に全セルのテキストを一括で水平、垂直別に中央寄せするためのオプション(追加 class)と、テーブルの下に指定したサイズのスペース(インライン style)を空けるためのオプションを例として追加してみます。
import { addFilter } from '@wordpress/hooks'; // 対象とするブロック const allowedBlocks = [ 'core/table' ]; const addExtraAttributes = ( settings ) => { // テーブルブロックだったら if ( allowedBlocks.includes( settings.name ) ) { // horizontalAlignCenter, verticalAlignCenter, bottomSpace という属性を追加 settings.attributes = Object.assign( settings.attributes, { horizontalAlignCenter: { type: 'boolean', default: false, }, verticalAlignCenter: { type: 'boolean', default: false, }, bottomSpace: { type: 'string', default:'', } } ); } return settings; } addFilter( 'blocks.registerBlockType', 'my-name-space/add-extra-attributes', addExtraAttributes, );
エディターでカスタム属性を操作するためのコントロールを追加
追加したカスタム属性の値を、ユーザーがエディターから操作するためのコントロール(InspectorControls
)を editor.BlockEdit フィルターを利用してテーブルブロックに組み込みます。
import { addFilter } from '@wordpress/hooks'; import { createHigherOrderComponent } from '@wordpress/compose'; import { Fragment } from '@wordpress/element'; import { InspectorControls } from '@wordpress/block-editor'; import { PanelBody, ToggleControl, PanelRow } from '@wordpress/components'; import { __experimentalUnitControl as UnitControl } from '@wordpress/components'; const allowedBlocks = [ 'core/table' ]; const addCustomControls = createHigherOrderComponent( ( BlockEdit ) => { return ( props ) => { if ( allowedBlocks.includes( props.name ) ) { const { setAttributes, isSelected, attributes } = props; const { className, horizontalAlignCenter, verticalAlignCenter, bottomSpace } = attributes; const unitControlProps = ( label, labelPosition, value ) => { return { label: label, labelPosition: labelPosition, value: value, units: [ { value: 'px', label: 'px', default: 0 }, { value: '%', label: '%', default: 0 }, { value: 'vw', label: 'vw', default: 0 }, { value: 'vh', label: 'vh', default: 0 }, ], __unstableInputWidth: '80px', } } return ( <Fragment> <BlockEdit { ...props } /> { isSelected && <InspectorControls> <PanelBody title= { __( 'Advanced Settings' ) } initialOpen={ false }> <ToggleControl label={ __( 'Center all cells horizontally' ) } checked={ horizontalAlignCenter } onChange={ ( horizontalAlignCenter ) => setAttributes( { horizontalAlignCenter } ) } /> <ToggleControl label={ __( 'Center all cells vertically' ) } checked={ verticalAlignCenter } onChange={ ( verticalAlignCenter ) => setAttributes( { verticalAlignCenter } ) } /> <PanelRow> <UnitControl { ...unitControlProps( __( 'Block bottom space' ), 'top', bottomSpace ) } onChange={ ( bottomSpace ) => setAttributes( { bottomSpace } ) } /> </PanelRow> </PanelBody> </InspectorControls> } </Fragment> ); } return <BlockEdit { ...props } />; } }, 'addCustomControls' ); addFilter( 'editor.BlockEdit', 'my-name-space/add-custom-contorols', addCustomControls, );
これでカスタム属性の追加とそれをエディター側で操作するための準備が整いました。
対象とするブロックは、allowedBlocks
定数にまとめておきます。
ここでは 'core/table'
のみがありますが、複数ブロックで共通のカスタム属性としたい場合は、この allowedBlocks
の配列に対象ブロック名を追加します。
ブロック下部のスペース(margin-bottom
)は、UnitControl コンポーネントを利用して単位を px, %, vw, vh から選べるようにしています。
続いて、これらのカスタム属性の値を元にして、テーブルブロックのラッパー要素の class
と style
に独自の定義を追加する処理を用意します。
[エディター]ブロックの親要素に class と style を追加
editor.BlockListBlock フィルターを利用して、カスタム属性が変更された際にエディターの対象ブロックにそれを反映させるために、親要素の class
と style
属性に追加します。
import { addFilter } from '@wordpress/hooks'; import { createHigherOrderComponent } from '@wordpress/compose'; import classnames from 'classnames'; const applyExtraAttributesInEditor = createHigherOrderComponent( ( BlockListBlock ) => { return ( props ) => { const { attributes, className, name, isValid, wrapperProps } = props; if ( isValid && allowedBlocks.includes( name ) ){ const { horizontalAlignCenter, verticalAlignCenter, bottomSpace } = attributes; // 値があれば class を追加 const extraClass = [ { 'center-horizontally': horizontalAlignCenter, 'center-vertically': verticalAlignCenter, } ] // 値があれば margin-bottom の値を追加 const extraStyle = { marginBottom: bottomSpace ? bottomSpace : undefined } let blockWrapperProps = wrapperProps; blockWrapperProps = { ...blockWrapperProps, style: { ...( blockWrapperProps && { ...blockWrapperProps.style } ), ...extraStyle }, }; return ( <BlockListBlock { ...props } className={ classnames( extraClass, className ) } wrapperProps={ blockWrapperProps } /> ); } return ( <BlockListBlock { ...props } /> ); }; }, 'applyExtraAttributesInEditor' ); addFilter( 'editor.BlockListBlock', 'my-name-space/apply-extra-attributes-in-editor', applyExtraAttributesInEditor, );
horizontalAlignCenter
属性が有効の場合は .center-horizontally
という class が追加され、verticalAlignCenter
属性が有効の場合は .center-vertically
という class が追加されます。
ブロック下部のスペースは、bottomSpace
属性の値を margin-bottom
に渡しています。
中央寄せの CSS
このデモでは、テーブルブロックの全てのセル内のテキストの配置を一括で水平または垂直方向にセンタリングするカスタム属性を追加するため、水平方向の中央寄せのトリガーとなる.center-horizontally
というセレクタと、垂直方向の中央寄せのトリガーとなる .center-vertically
というセレクタに必要な CSS を定義しておきます。
.wp-block-table{ &.center-horizontally{ th, td{ text-align: center; } } &.center-vertically{ th, td{ vertical-align: middle; } } }
[フロントエンド]ブロックの親要素に class と style を追加
同様に、フロントエンド側(保存時)にも反映させるため、blocks.getSaveContent.extraProps フィルターを利用して対象ブロックの親要素に class
と style
属性を追加します。
import { addFilter } from '@wordpress/hooks'; import classnames from 'classnames'; const applyExtraAttributesInFrontEnd = ( props, blockType, attributes ) => { if ( allowedBlocks.includes( blockType.name ) ) { const { setAttributes, isSelected, attributes } = props; const { className, horizontalAlignCenter, verticalAlignCenter, bottomSpace } = attributes; const extraClass = [ { 'center-horizontally': horizontalAlignCenter, 'center-vertically': verticalAlignCenter, } ] const extraStyle = { marginBottom: bottomSpace ? bottomSpace : undefined } props.className = classnames( props.className, extraClass ); return Object.assign( props, { style: { ...props.style, ...extraStyle } } ); } return props; } addFilter( 'blocks.getSaveContent.extraProps', 'my-name-space/apply-extra-attributes-in-front-end', applyExtraAttributesInFrontEnd, );
以上で、対象のブロックへエディターとフロントエンドの両方にカスタム属性を元にした任意の class
と style
が追加されるようになります。
まとめる
ここまでの処理をまとめると、以下のようになります。
import { addFilter } from '@wordpress/hooks'; import { createHigherOrderComponent } from '@wordpress/compose'; import { Fragment } from '@wordpress/element'; import { InspectorControls } from '@wordpress/block-editor'; import { PanelBody, ToggleControl, PanelRow } from '@wordpress/components'; import { __experimentalUnitControl as UnitControl } from '@wordpress/components'; import classnames from 'classnames'; const allowedBlocks = [ 'core/table' ]; const addExtraAttributes = ( settings ) => { if ( allowedBlocks.includes( settings.name ) ) { settings.attributes = Object.assign( settings.attributes, { horizontalAlignCenter: { type: 'boolean', default: false, }, verticalAlignCenter: { type: 'boolean', default: false, }, bottomSpace: { type: 'string', default:'', } } ); } return settings; } const addCustomControls = createHigherOrderComponent( ( BlockEdit ) => { return ( props ) => { if ( allowedBlocks.includes( props.name ) ) { const { setAttributes, isSelected, attributes } = props; const { className, horizontalAlignCenter, verticalAlignCenter, bottomSpace } = attributes; const unitControlProps = ( label, labelPosition, value ) => { return { label: label, labelPosition: labelPosition, value: value, units: [ { value: 'px', label: 'px', default: 0 }, { value: '%', label: '%', default: 0 }, { value: 'vw', label: 'vw', default: 0 }, { value: 'vh', label: 'vh', default: 0 }, ], __unstableInputWidth: '80px', } } return ( <Fragment> <BlockEdit { ...props } /> { isSelected && <InspectorControls> <PanelBody title= { __( 'Advanced Settings' ) } initialOpen={ false }> <ToggleControl label={ __( 'Center all cells horizontally' ) } checked={ horizontalAlignCenter } onChange={ ( horizontalAlignCenter ) => setAttributes( { horizontalAlignCenter } ) } /> <ToggleControl label={ __( 'Center all cells vertically' ) } checked={ verticalAlignCenter } onChange={ ( verticalAlignCenter ) => setAttributes( { verticalAlignCenter } ) } /> <PanelRow> <UnitControl { ...unitControlProps( __( 'Block bottom space' ), 'top', bottomSpace ) } onChange={ ( bottomSpace ) => setAttributes( { bottomSpace } ) } /> </PanelRow> </PanelBody> </InspectorControls> } </Fragment> ); } return <BlockEdit { ...props } />; } }, 'addCustomControls' ); const applyExtraAttributesInEditor = createHigherOrderComponent( ( BlockListBlock ) => { return ( props ) => { const { attributes, className, name, isValid, wrapperProps } = props; if ( isValid && allowedBlocks.includes( name ) ){ const { horizontalAlignCenter, verticalAlignCenter, bottomSpace } = attributes; const extraClass = [ { 'center-horizontally': horizontalAlignCenter, 'center-vertically': verticalAlignCenter, } ] const extraStyle = { marginBottom: bottomSpace ? bottomSpace : undefined } let blockWrapperProps = wrapperProps; blockWrapperProps = { ...blockWrapperProps, style: { ...( blockWrapperProps && { ...blockWrapperProps.style } ), ...extraStyle }, }; return ( <BlockListBlock { ...props } className={ classnames( className, extraClass ) } wrapperProps={ blockWrapperProps } /> ); } return ( <BlockListBlock { ...props } /> ); }; }, 'applyExtraAttributesInEditor' ); const applyExtraAttributesInFrontEnd = ( props, blockType, attributes ) => { if ( allowedBlocks.includes( blockType.name ) ) { const { setAttributes, isSelected, attributes } = props; const { className, horizontalAlignCenter, verticalAlignCenter, bottomSpace } = attributes; const extraClass = [ { 'center-horizontally': horizontalAlignCenter, 'center-vertically': verticalAlignCenter, } ] const extraStyle = { marginBottom: bottomSpace ? bottomSpace : undefined } props.className = classnames( props.className, extraClass ); return Object.assign( props, { style: { ...props.style, ...extraStyle } } ); } return props; } addFilter( 'blocks.registerBlockType', 'my-name-space/add-extra-attributes', addExtraAttributes, ); addFilter( 'editor.BlockEdit', 'my-name-space/add-custom-contorols', addCustomControls, ); addFilter( 'editor.BlockListBlock', 'my-name-space/apply-extra-attributes-in-editor', applyExtraAttributesInEditor, ); addFilter( 'blocks.getSaveContent.extraProps', 'my-name-space/apply-extra-attributes-in-front-end', applyExtraAttributesInFrontEnd, );
各種ブロックフィルターの詳細については公式ドキュメントを参考にしてください。