Wonderful! WordPress

Block Editor Innerblock & css grid layout on edit-page

Page No.1

Gutenberg Block Editor 自作block その4- grid wrapper

ブロックエディタでテーブルなんぞを作っている時に、そういえば css grid は標準で使えないのだろうか?などとふと思った。ちょっとやってみたところ、グループボックス等で flex はできたけれど grid は見当たらなかった。ネットでうろうろしてみたが、出くわすのはプラグインを使う情報ばかり。

え~!さくっと簡単に使えないの?って、しっかり調べたわけではないので、もしかしたら手軽な方法などあるのかもしれない。まぁ、でも、インナーブロックでインラインスタイルを設定できる自作ブロックがあるので、それを使えば別にどうってことなく、css グリッドレイアウトなるものは作れるだろうと。

まぁ、実際にやってみると、公開されているページのほうは、なんの問題もなく想定通りのグリッドレイアウトで表示される。しかるに、問題は管理画面における編集ページのほう、いわゆる Block Editor の画面においてである。早い話、こちらではまったくグリッドレイアウトでの表示にはならない。

原因はといえば、実に明解でシンプルである。インナーブロックを使ったブロックの場合、親要素にラッピングされた子要素としての innerblock があるのだけれど、エディタ画面においてはその innerblock 自体も二重の div ボックスによってラッピングされているのである。インラインスタイルが設定されるのは親要素であり、その直下にあるのは1つの div ボックスなので、その1つだけの div が対象となる grid レイアウトなどは、無意味にただ幅が狭くなってしまうだけのものとなってしまうのである。
こんな具合に。2、3行めが自動的に innerblock によって付加される div となる。

<div class="block-editor-block-list__block wp-block has-child-selected wp-block-stx-grid-wrapper-id" id="stx-grid-wapper-id" tabindex="0" role="document" aria-label="ブロック: STX grid-wrapper-id" data-block="1c05c7a1-3980-4082-880a-779309901e62" data-type="stx/grid-wrapper-id" data-title="STX grid-wrapper-id">
    <div class="block-editor-inner-blocks">
        <div class="block-editor-block-list__layout" data-is-drop-zone="true">
            <span role="document" aria-multiline="true" aria-label="ブロック: wrapped span tag" id="block-00b1fabc-001f-4719-a75a-c970016f9f50" tabindex="0" data-block="00b1fabc-001f-4719-a75a-c970016f9f50" data-type="str-span/wrapedspan" data-title="wrapped span tag" class="block-editor-rich-text__editable block-editor-block-list__block wp-block wp-block-str-span-wrapedspan rich-text" style="display: block; margin: 5px; white-space: pre-wrap; min-width: 1px;" contenteditable="true">abc</span>
            <span role="document" aria-multiline="true" aria-label="ブロック: wrapped span tag" id="block-cb1bc907-9545-4bc0-bcad-2a0372c89158" tabindex="0" data-block="cb1bc907-9545-4bc0-bcad-2a0372c89158" data-type="str-span/wrapedspan" data-title="wrapped span tag" class="block-editor-rich-text__editable block-editor-block-list__block wp-block wp-block-str-span-wrapedspan rich-text" style="display: block; margin: 5px; white-space: pre-wrap; min-width: 1px;" contenteditable="true">def</span>
            <span role="document" aria-multiline="true" aria-label="ブロック: wrapped span tag" id="block-d4d4f988-b9df-4b94-a940-a1a3ba879346" tabindex="0" data-block="d4d4f988-b9df-4b94-a940-a1a3ba879346" data-type="str-span/wrapedspan" data-title="wrapped span tag" class="block-editor-rich-text__editable block-editor-block-list__block wp-block wp-block-str-span-wrapedspan rich-text" style="display: block; margin: 5px; white-space: pre-wrap; min-width: 1px;" contenteditable="true">ghi</span>
            <span role="document" aria-multiline="true" aria-label="ブロック: wrapped span tag" id="block-ad3f1c0c-0a5a-4332-9eb6-8a6b18c084cc" tabindex="0" data-block="ad3f1c0c-0a5a-4332-9eb6-8a6b18c084cc" data-type="str-span/wrapedspan" data-title="wrapped span tag" class="block-editor-rich-text__editable block-editor-block-list__block wp-block is-selected wp-block-str-span-wrapedspan rich-text" style="display: block; margin: 5px; white-space: pre-wrap; min-width: 1px;" contenteditable="true">jkl</span>
        </div>
    </div>
</div>
HTML
CopyExpand

インナーブロックにおいて、自動的に付加される二重のラッパー div を取り除く方法はあるのだろうか?あれこれと探し回ってみたけれど、やはりというか、これはなかなか困難なことのようだ。ちなみに、標準装備のグループボックスで使える flex レイアウト。これ、flex を使うときには、エディタ画面においても、子要素をまとめるインナーブロックのラッパーは無くなる。このことからも出来なくはなさそうなのだけれど、なかなかややこしい部分なのではなかろうかと。

それがだめならと、エディタ画面においては、親ボックスに設定されるはずのインラインスタイル設定を、インナーブロックの内側のラッパーに設定できればグリッドレイアウトになるはずである。そのほうがよほど手っ取り早いしわかりやすい。ただし、アップデートの場合など、WordPress 側の都合でこれらの class 名を変更されてしまうと無意味になってしまう。レイアウトが反映されなくなったときは、まずそれを疑ってみればいいと。

自作 grid layout Innerblock with ID & inline-style

上記 HTML を見ると、インナーブロックの外側のラッパーの class は「 block-editor-inner-blocks 」であり、内側の class は「 block-editor-block-list__layout 」であり、他の同じブロックを見ても共通しているので、これがセレクターとして使えそう。内側だけあれば良さそうであるけれど、例えば、子要素にまた innerblock を使ったブロックがあったりした場合、それらの要素にはスタイルが反映されないようにするには、親要素の直下の要素だけ、という指定をする必要があり、それには外側の class も必要になるとおもう。

親であるブロック本体の方は、同じブロックを複数使っている可能性を考慮すると、それぞれに違うスタイル設定をしている場合などは間違いなくあるはずで、そうなると class は使えないので、必然的に id を使わざるをえない。ただこれは、useBlockProps においてはデフォルトで id の文字列を持っているので、指定されていない場合はそれを使えばいいと。

React のベースは Javascript なので普通に Javascript で使えるものは動く。
スタイルの設定においては、ページ読み込み時には .sheet.insertRule を使うことにして、スタイル設定に影響のある要素の入力による更新があった場合は、普通に、document.element.style.cssText によってスタイルの更新をさせた。

.sheet.insertRule を使う必要性というのは、ページ読み込み時に既存のブロックにおいて、innerblock で付加される内側のラッパー div にたいしてスタイルを設定する方法が他に思いつかなかったから。エディタにおいてのみ読み込むスタイルシートを指定することは出来るけれど、それだと固定されたスタイル設定になってしまい、ブロックによって異なるスタイル設定を動的に指定することはできないと思う。

( function ( blocks, element, blockEditor, components ) {
	const el = element.createElement,
        Fragment = element.Fragment,
        InspectorControls = blockEditor.InspectorControls,
        TextControl = components.TextControl,
        useBlockProps = blockEditor.useBlockProps,
        InnerBlocks = blockEditor.InnerBlocks;

    // 文字列としてのスタイル設定をばらして連想配列にする関数
	// useBlockProps の style は連想配列の形式なため
    function treat_style( val ){
        let tmpstyle = {};
        if ( val ) {
            let vary = val.split( ';' );

            for ( let i = 0, vcnt = vary.length; i < vcnt; ++i ) {
                let tmp = [];
                
                if ( vary[ i ] ) {
                    tmp[0] = vary[ i ].substring( 0, vary[ i ].indexOf( ':' ) );
                    tmp[1] = vary[ i ].substring( vary[ i ].indexOf( ':' ) + 1 );

                    tmp[2] = tmp[0].split( '-' );

                    if ( tmp[2][1] ) {
                        let tmpstr = '';
                        for ( let j = 1, tar_len = tmp[2].length; j < tar_len; ++j  ) {
                            tmpstr += tmp[2][ j ].slice( 0, 1 ).toUpperCase() + tmp[2][j].slice( 1 );
                        }
                        tmp[2][0] += tmpstr;
                    }

                    if ( tmp[1] ) {
                        tmpstyle[ tmp[2][0] ] = tmp[1];
                    }
                }
            }
        }
        return tmpstyle;
    }
    
    let dynamicStyles = null;

    // エディタページ読み込み時に既存するブロックのスタイルを設定するための関数
    function addStyle( cssstr ) {

        if ( ! dynamicStyles ) {
            const doc = document;

            if ( null === doc.getElementById( 'stx-grid-id-styles' ) ) {

                dynamicStyles = doc.createElement( 'style' );
                dynamicStyles.id = 'stx-grid-id-styles';
                document.head.appendChild( dynamicStyles );

            }
        }
        dynamicStyles.sheet.insertRule( cssstr, dynamicStyles.length );
    }
    
    blocks.registerBlockType( 'stx/grid-wrapper-id', {
        apiVersion: 2,
        title: 'STX grid-wrapper-id',
        icon: 'grid-view',
        category: 'design',
        description: 'id 及びインラインスタイルを指定でき、css grid で layout できるラッパー。デフォルトで付加されるclass, wp-block-stx-grid-wrapper によってエディタでは css grid layout となる。',

        attributes: {
            anchor: { type: 'string' },
            styles: { type: 'string', default: '' },
        },

        supports: {
           anchor: true,
        },

        edit: function( props ) {
            // apiVersion: 2 の場合はこうしないと class がeditor で適用されない
            const blockProps = useBlockProps( { className: props.attributes.className } );

            let styles = props.attributes.styles,
                anchor = props.attributes.anchor,
                elem = {
                    // apiVersion: 1 の場合はこれでも class が editor で適用される
                    // className: props.className,
                    // エディタ画面では親boxにスタイルを設定してもほぼ無意味
                    // style: treat_style( props.attributes.styles ),
                    id: anchor ? anchor : blockProps.id,
                },
                // ページ読み込み時に設定されたスタイルを適用させるための文字列をつくる
                editorgridchildstyle = '#' + ( anchor ? anchor : blockProps.id ) + ' > .block-editor-inner-blocks > .block-editor-block-list__layout{' + styles + '}';

            // 上で作成したスタイル設定用の文字列を実際に適用させる
            addStyle( editorgridchildstyle );

            let changeId = null;

            // スタイル設定を変更された時に、そのスタイルを適用させる関数
            // そのままだと一文字入力があるごとに動いてしまうので、setTimeout でまとまった文字列にする
            function styleChange( value ) {
                clearTimeout( changeId );

                changeId = setTimeout( () => {
                    let styleval = 'display:block;';

                    if ( value ) {
                        styleval = value;
                    }
                    // スタイル設定の区切りである";"が最後なら
                    if ( ';' === styleval[ ( styleval.length - 1 ) ] ) {

                        if ( ! anchor ) {
                            anchor = blockProps.id;
                        }

                        const doc = document;

                        if ( anchor ) {
                            if ( null !== doc.getElementById( anchor ) ) {
                                const tarel = doc.getElementById( anchor ),
                                    tarchil = tarel.getElementsByClassName( 'block-editor-block-list__layout' );

                                if ( tarchil[0] ) {
                                    tarchil[0].style.cssText = styleval;
                                }
                            }
                        }
                    }
                }, 1500 );
            }

            return(
                el(
                    Fragment,
                    null,
                    el(
                        InspectorControls,
                        null,
                        el( 'div', { id: 'stx_grdiv_sdbr', style: { fontSize: '1.3em' } },
                            el(
                                TextControl,
                                {
                                    label: 'styles',
                                    help: 'div に設定するインラインスタイル。通常のcssに記述するスタイルで。最後に必ず ";" を付加する。eq. margin-left:10px;color:red;font-size:1.1em;',
                                    value: styles,
                                    onChange: function( newValue ){ props.setAttributes( { styles: newValue } ); styleChange( newValue ); }
                                }
                            ),
                        ),
                    ),
                    el(
                        'div',
                        Object.assign( blockProps, elem ),
                        el( InnerBlocks )
                    )
                )
            );
        },

        save: function( props ) {

            const blockProps = useBlockProps.save();

            if ( props.attributes.anchor ) {
                blockProps.id = props.attributes.anchor;
            }
            if ( props.attributes.styles ) {
                blockProps.style = treat_style ( props.attributes.styles );
            }

            return el(
                'div',
                blockProps,
                el( InnerBlocks.Content )
            );
        },
    });
}(
	window.wp.blocks,
	window.wp.element,
	window.wp.blockEditor,
    window.wp.components,
) );
JavaScript
CopyExpand

実を言うと始め、このブロックの id属性においては、ブロックが標準で備えている anchor の機能は使わず InspectorControls を使用して自前で装備した。と、いうのは、anchor を使用した場合の id を指定する文字列が更新されたときの、イベントの取得がなかなかにややこしいもので、よくわからなかったから。id が変更されたということは、スタイル設定の対象が変更されたということで、その都度、対応させたい訳である。その点、InspectorControls を使って自装すれば、その変更は onChange により実に簡単に対応できる。

その辺りの更新に際してのイベント処理も自装しなければいけないものだと思いこんでいたわけで。そして始めに作ったブロックが下のもの。これで id 変更時のスタイル設定の対象の変更もうまく動いていた。だがしかし、ちょっと待てよと!あれっ! id 変更に伴う処理は入れただろうかと、確認してみると、やはりというかまだその処理を加えてはいなかった。

下を見るとわかると思うけれど、edit InspectorControls の部分、idstr の変更における onChange に登録してあるのは attributes の更新処理だけ。この状態で id の変更において、ちゃんと意図した結果が得られていたから不思議に思った。わざわざそのための処理を入れなくとも、attributes の変更により、それに紐付けられている .sheet.insertRule のスタイル設定も更新されるということか。それなら、と style の方も、その更新処理を入れなくてもいいのかも?と、試してみたところ、style の更新に関しては全く知らぬ顔だった。

同じくattributes の値であり、同じように変数で紐づけて .sheet.insertRule に設定しているのに id はよくて style はだめというのは、いったいどう違うというのだろうか。よくわからない。
と、まぁ、なんにしても id属性のほうは変更に際しての更新処理をわざわざ入れなくても良さそうということで、それなら gutenberg が標準で備えてくれている anchor でも同じなのではと、ためしてみたら思ったとおりそれでもいけた。と、いうことで、わざわざその部分をあえて InspectorControls で自装する必要もなく anchor を使うことにした、ということである。ちなみに、もちろんのこと、下のブロックもちゃんと機能する。

( function ( blocks, element, blockEditor, components ) {
	const el = element.createElement,
        Fragment = element.Fragment,
        InspectorControls = blockEditor.InspectorControls,
        TextControl = components.TextControl,
        SelectControl = components.SelectControl,
        useBlockProps = blockEditor.useBlockProps,
        InnerBlocks = blockEditor.InnerBlocks;

    // 文字列としてのスタイル設定をばらして連想配列にする関数
	// useBlockProps の style は連想配列の形式なため
    function treat_style( val ){
        let tmpstyle = {};
        if ( val ) {
            let vary = val.split( ';' );

            for ( let i = 0, vcnt = vary.length; i < vcnt; ++i ) {
                let tmp = [];
                
                if ( vary[ i ] ) {
                    tmp[0] = vary[ i ].substring( 0, vary[ i ].indexOf( ':' ) );
                    tmp[1] = vary[ i ].substring( vary[ i ].indexOf( ':' ) + 1 );

                    tmp[2] = tmp[0].split( '-' );

                    if ( tmp[2][1] ) {
                        let tmpstr = '';
                        for ( let j = 1, tar_len = tmp[2].length; j < tar_len; ++j  ) {
                            tmpstr += tmp[2][ j ].slice( 0, 1 ).toUpperCase() + tmp[2][j].slice( 1 );
                        }
                        tmp[2][0] += tmpstr;
                    }

                    if ( tmp[1] ) {
                        tmpstyle[ tmp[2][0] ] = tmp[1];
                    }
                }
            }
        }
        return tmpstyle;
    }

    let dynamicStyles = null;

    // エディタページ読み込み時に既存するブロックのスタイルを設定するための関数
    function addStyle( cssstr ) {

        if ( ! dynamicStyles ) {
            const doc = document;

            if ( null === doc.getElementById( 'stx-grid-styles' ) ) {

                dynamicStyles = doc.createElement( 'style' );
                dynamicStyles.id = 'stx-grid-styles';
                document.head.appendChild( dynamicStyles );

            }
        }
        dynamicStyles.sheet.insertRule( cssstr, dynamicStyles.length );
    }

    blocks.registerBlockType( 'stx/grid-wrapper-id', {
        apiVersion: 2,
        title: 'STX grid-wrapper-id',
        icon: 'grid-view',
        category: 'design',
        description: 'id 及びインラインスタイルを指定でき、css grid で layout できるラッパー。デフォルトで付加されるclass, wp-block-stx-grid-wrapper によってエディタでは css grid layout となる。',

        attributes: {
            idstr: { type: 'string ', default: '' },
            styles: { type: 'string', default: '' },
        },

        edit: function( props ) {
            // apiVersion: 2 の場合はこうしないと class がeditor で適用されない
            const blockProps = useBlockProps( { className: props.attributes.className } );

            let styles = props.attributes.styles,
                idstr = props.attributes.idstr,
                elem = {
                    // apiVersion: 1 の場合はこれでも class が editor で適用される
                    // className: props.className,
                    // エディタ画面では親boxにスタイルを設定してもほぼ無意味
                    // style: treat_style( props.attributes.styles ),
                    id: idstr ? idstr : blockProps.id,
                },
                // ページ読み込み時に設定されたスタイルを適用させるための文字列をつくる
                editorgridchildstyle = '#' + ( idstr ? idstr : blockProps.id ) + ' .block-editor-block-list__layout{' + styles + '}';

            // 上で作成したスタイル設定用の文字列を実際に適用させる
            addStyle( editorgridchildstyle );

            let changeId = null;

            // スタイル設定を変更された時に、そのスタイルを適用させる関数
            // そのままだと一文字入力があるごとに動いてしまうので、setTimeout でまとまった文字列にする
            function styleChange( value ) {
                clearTimeout( changeId );

                changeId = setTimeout( () => {
                    let styleval = 'display:block;';

                    if ( value ) {
                        styleval = value;
                    }
                    // スタイル設定の区切りである";"が最後なら
                    if ( ';' === styleval[ ( styleval.length - 1 ) ] ) {

                        if ( ! idstr ) {
                            idstr = blockProps.id;
                        }

                        const doc = document;

                        if ( idstr ) {
                            if ( null !== doc.getElementById( idstr ) ) {
                                const tarel = doc.getElementById( idstr ),
                                    tarchil = tarel.getElementsByClassName( 'block-editor-block-list__layout' );

                                if ( tarchil[0] ) {
                                    tarchil[0].style.cssText = styleval;
                                }
                            }
                        }
                    }
                }, 1500 );
            }

            return(
                el(
                    Fragment,
                    null,
                    el(
                        InspectorControls,
                        null,
                        el( 'div', { id: 'stx_grdiv_sdbr', style: { fontSize: '1.3em' } },
                            el(
                                TextControl,
                                {
                                    label: 'id',
                                    help: 'div に設定するid属性',
                                    value: idstr,
                                    onChange: function( newValue ){ props.setAttributes( { idstr: newValue } ); }
                                }
                            ),
                            el(
                                TextControl,
                                {
                                    label: 'styles',
                                    help: 'div に設定するインラインスタイル。通常のcssに記述するスタイルで。eq. margin-left:10px;color:red;font-size:1.1em;',
                                    value: styles,
                                    onChange: function( newValue ){ props.setAttributes( { styles: newValue } ); styleChange( newValue ); }
                                }
                            ),
                        ),
                    ),
                    el(
                        'div',
                        Object.assign( blockProps, elem ),
                        el( InnerBlocks )
                    )
                )
            );
        },

        save: function( props ) {

            var blockProps = useBlockProps.save();

            if ( props.attributes.idstr ) {
                blockProps.id = props.attributes.idstr;
            }
            if ( props.attributes.styles ) {
                blockProps.style = treat_style ( props.attributes.styles );
            }

            return el(
                'div',
                blockProps,
                el( InnerBlocks.Content )
            );
        },
    });

}(
	window.wp.blocks,
	window.wp.element,
	window.wp.blockEditor,
    window.wp.components,
));
JavaScript
CopyExpand

これで、エディタ画面において css grid レイアウトを表示できるブロック、はできたわけではある。ところで、このブロック、apiVersion: 2 となってはいるけれど、その登録の仕方は現在の block.json を使用する方法ではなく、始めに学んだときのままである。やはり今現在において推奨されている方法というのも知っておかなくては、という思いがある。
で、その場合、non-JSX でもできるのだろうか?ということもあって。

結論から言ってしまえば、もちろんそれも可能だったし、block.json での登録にすれば、そのブロック用のエディタ画面だけでロードさせるスタイルシートの設定などもできるようになる。
そのあたりのことを次ページへと。

Leave a Reply!

JavaScript is necessary to send a comment.
You can edit and delete your comment if you input a edit key.
Edit key is necessary for attesting you when you edit and delete it.
The tag of HTML cannot be used in comment.
When you comment for the first time, it is displayed after the approval of the administrator.
Because I cannot speak English so much, it takes time to answer.
Required fields are marked *.

※Please enter more than 5 characters only alphabets.
※Edit or delete are possible for 2000 days after approval.

*

♠Simplistic Comment User Editable v4.0

♠When visitors leave comments on the site this site collect the data shown in the comments form, and also the visitor’s IP address and browser user agent string to help spam detection.
♠This site does not use cookie when visitors leave comments and commenter edit comment.
♠This site uses Akismet to reduce spam. Learn how your comment data is processed.

Comments feed

Trackback URL : https://strix.main.jp/wp-trackback.php?p=171751

Sanbanse Funabashi
2011.01.01 sunrise

Top

スクロールさせるか画像をクリックすると元に戻ります。