Wonderful! WordPress

Gutenberg Block Editor 自作その3 - form編

Page No.1

なんだかんだと言いつつなぜかひたすらにブロックを作ってしまっているではないか!これはいったい?
などと言っていてもしょうがないのである。
と、いうことでまた自作ブロックでじたばたとしてる。

Gutenberg Block Editor 自作その1・・・>「Gutenberg Block Editor だとさ
Gutenberg Block Editor 自作その2・・・>「Gutenberg Block Editor 自作その2 – id指定

で、今回は何かというと、forminput 関連。
まぁね、たぶんあまり使うことも無いのだということはよくわかってはいるのだけれど。
ただ、企業のサイトとかいうことになると、少しは使い道も有るのかもしれない、というかちょっと必要な場面に出くわしてしまったわけですね。
デフォルトブロックの中にはどうやらなさそう。ボタンというのがあるけれど、これは anchor タグにて実装しているもの。

と、いうことでまずは input タグ関連。input タグは type が色々あるので、そのあたりをカバーできるものというのが目標。少し勝手の違う、textareaselect ボックスは別に作るつもり。それと、input タグをまとめて入れる innerblock としての form ブロックと。

Input Block – non-JSX

相変わらず non-JSX にこだわってる。
input タグに必要な type 属性とか namemaxmin などなどはインスペクターコントロールで指定できるようにして、送信フォームではなく、単体の機能として使うこともありそうなので、formdivspan と親タグも選択できるようにした。インスペクターコントロールの部分は、いままでもたくさん使ってきているので、それの使い回し。あと、個々にインラインスタイルも設定できるようにしてあるのは、この前のページにてやってみたことのそのまんま使いまわし。

これにおいてちょっと苦労したのは、value 属性とか checked 属性で React さんから 警告が出ることかな。この件については解決の手がかりとなる、ネット上に詳しいページがいくつもあったが、その Warning にもちゃんと解決法を示してくれていた。
「そのまま value を使うなら、onChange を指定しないと mutable ( 可変 )にはならないぞ!でなければ、defaultValue をつかってはどうだい!」と、だいたいそんな感じのことだったと思う。
だけれども、defaultValue においては、やってみたけれど設定しても何の意味があるのかさえ、わからなかった。実際のフォームにおいて無意味なんじゃないのだろうか?よくわからない。
実際に、その value の値が一度設定したのちにユーザーによって変更される可能性のある type=”text” などは、onChange の指定をどうすればよいのかわからなかったので、type で指定の有無を選択するようにして回避した。checkedmaxminplaceholder なども同様に。

そして checkboxradiohidden においては value 属性が必須なわけであるけれど、その設定をユーザーによって変更されることは無いわけであり、onChange 属性は付けてあるだけで、その値は null にしてある。それで良いのかどうかわからないのだけれど、React さんから赤字で怒られることもなく、一応、期待通りに動いてくれてはいる。
あと、label タグにおいて、その対象を指定する for 属性は React では htmlFor を使う。実はこれも間違って for を使うと 「label において for を使ったね、もしかして htmlFor じゃないのかい?」てな感じで React さんが教えてくれたんじゃないかと記憶する。

※2023年1月10日追記

2024年1月10日時点における WordPress のバージョンは 6.4.2。php はといえば 8.2.14。が、php においてはすでに最新のものは 8.3.1 がリリースされている。 ブロックエディタも着々と進化しているようで、API バージョンも 3 となった。

バージョンアップに伴いちゃんと動いているかと確認してみると、案の定、何やらエラーが出ていた。改めてコードを見ていると、あれっ!と思う部分があり。これで果たしてちゃんと機能していたのだろうか?と疑問に思いつつも、ちゃんと動いていたように思うし。

とりあえず、その疑問に感じた部分を訂正してみるとエラーも出なくなった。直したのは inputtype を更新する部分。
あと、useBlockProps を使うならば、api version は2以上を指定する必要があるがゆえの指示がコンソールに出ていたので、その指定も追加した。api version の指定はその文を追加するだけのことで、このブロックにおいてはコードをイジる必要はなかった。

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

    // 文字列としてのスタイル設定をばらして連想配列にする関数
	// 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;
    }

    blocks.registerBlockType( 'stx-input/wrappedinput', {
        apiVersion: 2,
        title: 'STX input',
        icon: 'smiley',
        category: 'design',
        description: 'input label 等、control を追加するブロック',

        attributes: {
            itype: { type: 'string', default: 'text' },
            iname: { type: 'string', default: '' },
            wrapper: { type: 'string', default: 'div' },
            ilabel: { type: 'string', default: '' },
            ichecked: { type: 'string', default: '0' },
            pholder: { type: 'string', default: '' },
            ival: { type:'string', default: '' },
            istyle: { type: 'string', default: '' },
            imax: { type: 'string', default: '' },
            imin: { type: 'string', default: '' },
            anchor: { type: 'string' },
        },

        supports: {
            anchor: true,
            // className: false, //(default:true)ブロック要素を作成した際に付く .wp-block-[ブロック名]で自動生成されるクラス名の設定。
        },
    
        edit: function( props ) {
            let blockProps = useBlockProps(),
                { itype, iname, wrapper, ilabel, ichecked, pholder, ival, istyle, imax, imin } = props.attributes,
                elem = {// creatElement の第二引数、props のプロパティを指定するためのオブジェクト
                    type: itype,
                    name: iname,
                    id:iname,
                    style: treat_style( istyle )
                };

            function update_elem( itype ) {

                let elem2 = {};
        
                if ( 'checkbox' === itype || 'radio' === itype ) {
                    const checked = Number( ichecked ) ? true : false;

                    elem2 = {
                        value: ival,
                        onChange: null,
                        defaultChecked: checked,
                    };
                } else if ( 'hidden' ===itype || 'submit' === itype || 'button' === itype ) {

                    elem2 = {
                        value: ival,
                        onChange: null,
                    };
                } else if ( 'number' === itype || 'range' === itype ) {
                    elem2 = {
                        max: imax,
                        min: imin,
                    };
                } else {
                    if ( pholder ) {
                        elem2 = { placeholder: pholder };
                    }
                }
                Object.assign( elem, elem2 );
            }

            update_elem( itype );

            return(
                el(
                    Fragment,
                    null,
                    el(
                        InspectorControls,
                        null,
                        el( 'div', { id: 'stx_control_sdbr', style: { fontSize: '1.3em' } },
                            el(
                                RadioControl,
                                {
                                    label: 'type of parent box',
                                    selected: wrapper,
                                    options: [
                                       { label: 'form', value: 'form' },
                                       { label: 'div', value: 'div' },
                                       { label: 'span', value: 'span' },
                                       { label: 'none', value:'none' }
                                    ],
                                    onChange: function( newValue ){ props.setAttributes( { wrapper: newValue } ); }
                                }
                            ),
                            el(
                                TextControl,
                                {
                                    label: 'label strings',
                                    help: 'label に表示する文字列',
                                    value: ilabel,
                                    onChange: function( newValue ){ props.setAttributes( { ilabel: newValue } ); }
                                }
                            ),
                            el(
                                RadioControl,
                                {
                                    label: 'type of input box',
                                    selected: itype,
                                    options: [
                                        { label: 'text', value: 'text' },
                                        { label: 'checkbox', value: 'checkbox' },
                                        { label: 'radio', value: 'radio' },
                                        { label: 'hidden', value: 'hidden' },
                                        { label: 'number', value: 'number' },
                                        { label: 'range', value: 'range' },
                                        { label: 'color', value: 'color' },
                                        { label: 'mail', value: 'mail' },
                                        { label: 'password', value: 'password' },
                                        { label: 'date', value: 'date' },
                                        { label: 'datetime-local', value: 'datetime-local' },
                                        { label: 'email', value: 'email' },
                                        { label: 'month', value: 'month' },
                                        { label: 'reset', value: 'reset' },
                                        { label: 'search', value: 'search' },
                                        { label: 'button', value: 'button' },
                                        { label: 'tel', value: 'tel' },
                                        { label: 'time', value: 'time' },
                                        { label: 'url', value: 'url' },
                                        { label: 'week', value: 'week' },
                                        { label: 'submit', value: 'submit' }
                                    ],
                                    onChange: function( newValue ){ props.setAttributes( { itype: newValue } );update_elem( newValue ); }
                                }
                            ),
                            el(
                                TextControl,
                                {
                                    label: 'name of input',
                                    help: 'input 要素の name 指定、name属性、id属性、に表示する文字列で共通になる',
                                    value: iname,
                                    onChange: function( newValue ){ props.setAttributes( { iname: newValue } ); }
                                }
                            ),
                            el(
                                TextControl,
                                {
                                    label: 'input の value',
                                    value: ival,
                                    onChange: function( newValue ){ props.setAttributes( { ival: newValue } ); }
                                }
                            ),
                            el(
                                RadioControl,
                                {
                                    label: 'attribute of checked for input',
                                    help: 'input 要素の checked 指定',
                                    selected: ichecked,
                                    options: [ { label: 'checked', value: '1' },{ label: 'no-check', value: '0' } ],
                                    onChange: function( newValue ){ props.setAttributes( { ichecked: newValue } ); }
                                }
                            ),
                            el(
                                TextControl,
                                {
                                    label: 'styles for input',
                                    help: 'input 要素の style 指定',
                                    value: istyle,
                                    onChange: function( newValue ){ props.setAttributes( { istyle: newValue } ); }
                                }
                            ),
                            el(
                                TextControl,
                                {
                                    label: 'Placeholder string',
                                    help: 'placeholder 文字列指定',
                                    value: pholder,
                                    onChange: function( newValue ){ props.setAttributes( { pholder: newValue } ); }
                                }
                            ),
                            el(
                                TextControl,
                                {
                                    label: 'type が number、range の場合の min の値',
                                    value: imin,
                                    onChange: function( newValue ){ props.setAttributes( { imin: newValue } ); }
                                }
                            ),
                            el(
                                TextControl,
                                {
                                    label: 'type が number、range の場合の max の値',
                                    value: imax,
                                    onChange: function( newValue ){ props.setAttributes( { imax: newValue } ); }
                                }
                            ),
                        ),
                    ),
                    el(
                        'none' === wrapper ? Fragment : wrapper,
                        'none' === wrapper ? null : Object.assign( blockProps, { className: props.className } ),
                        ilabel ? el( 'label', { htmlFor: iname}, ilabel + ' ' ) : null,
                        el( 'input', elem )
                    )
                )
            );
        },
        save: function( props ) {
            let blockProps = useBlockProps.save(),
                { itype, iname, wrapper, ilabel, ichecked, pholder, ival, istyle, imax, imin } = props.attributes,
                elem = {
                    type: itype,
                    name: iname,
                    id:iname,
                    style: treat_style( istyle )
                },
                elem2 = {};
    
            if ( props.attributes.anchor ) {
                blockProps.id = props.attributes.anchor;
            }
 
            if ( 'checkbox' === itype || 'radio' === itype ) {
                const checked = Number( ichecked ) ? true : false;

                elem2 = {
                    value: ival,
                    onChange: null,
                    defaultChecked: checked,
                };
            } else if ( 'hidden' ===itype || 'submit' === itype || 'button' === itype ) {
    
                elem2 = {
                    value: ival,
                    onChange: null,
                };
            } else if ( 'number' === itype || 'range' === itype ) {
                elem2 = {
                    max: imax,
                    min: imin,
                };
            } else {
                if ( pholder ) {
                    elem2 = { placeholder: pholder };
                }
            }
            Object.assign( elem, elem2 );

            } else if ( 'number' === itype || 'range' === itype ) {
                elem2 = {
                    max: imax,
                    min: imin,
                };
            } else {
                if ( pholder ) {
                    elem2 = { placeholder: pholder };
                }
            }
            Object.assign( elem, elem2 );

            return (
                el(
                    'none' === wrapper ? Fragment : wrapper,
                    'none' === wrapper ? null : blockProps,
                    ilabel ? el( 'label', { htmlFor: iname }, ilabel + ' ' ) : null,
                    el( 'input', elem )
                )
            );
        },
    } );
}(
	window.wp.blocks,
	window.wp.element,
	window.wp.blockEditor,
    window.wp.components
));
JavaScript
CopyExpand

Textarea Block – non-JSX

input type の違いだけでもたくさん要素があるので、少々趣の異なる selecttextarea は別々に作ることにした。
と、いっても、textarea の方はそんなに違わない。根本的に異なるのは、子要素である option タグを複数内包する select の方。
とりあえず、textarea の方から。
基本的に input と同じなので一緒にしてしまってもよかったのだけれど、textarea には colsrowsmaxlengthform と独自の属性を持つので、それも別にした理由となる。で、これもインラインスタイルを指定できるようにしてあり、専用の label が付き、親要素として span タグに内包される。必要なものは、input の方と全く同じなので、下のは独立しているけれど、自的には input と同じ即時関数の中に入れている。なお、ブロックエディタ画面の右サイドバーにおける「高度な設定」で指定できる idclass は親の span タグに対しての設定にしてある。

※2024年1月10日追記

この textarea ブロックも少し訂正してある。api version 2 の指定と、function treat_style() において。

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

    // 文字列としてのスタイル設定をばらして連想配列にする関数
	// 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;
    }

    blocks.registerBlockType( 'stx-textarea/wrappedtextarea', {
        apiVersion: 2,
        title: 'STX textarea',
        icon: 'smiley',
        category: 'design',
        description: 'textarea を追加するブロック',

        attributes: {
            iname: { type: 'string', default: '' },
            ilabel: { type: 'string', default: '' },
            pholder: { type: 'string', default: '' },
            icols: { type:'string', default: '' },
            irows: { type:'string', default: '' },
            iform: { type: 'string', default: '' },
            imaxlen: { type: 'string', default: '' },
            istyle: { type: 'string', default: '' },
            anchor: { type: 'string' },
        },

        supports: {
            anchor: true,
            // className: false, //(default:true)ブロック要素を作成した際に付く .wp-block-[ブロック名]で自動生成されるクラス名の設定。
        },
    
        edit: function( props ) {
            let blockProps = useBlockProps(),
                { iname, ilabel, pholder, icols, irows, iform, imaxlen, istyle } = props.attributes;

            return(
                el(
                    Fragment,
                    null,
                    el(
                        InspectorControls,
                        null,
                        el( 'div', { id: 'stx_control_sdbr', style: { fontSize: '1.3em' } },
                            el(
                                TextControl,
                                {
                                    label: 'label strings',
                                    help: 'label に表示する文字列',
                                    value: ilabel,
                                    onChange: function( newValue ){ props.setAttributes( { ilabel: newValue } ); }
                                }
                            ),
                            el(
                                TextControl,
                                {
                                    label: 'name of textarea',
                                    help: 'textarea の name 指定、name属性、id属性で共通になる',
                                    value: iname,
                                    onChange: function( newValue ){ props.setAttributes( { iname: newValue } ); }
                                }
                            ),
                            el(
                                TextControl,
                                {
                                    label: 'cols属性',
                                    help: '一行当たりの最大文字数の目安を指定する',
                                    value: icols,
                                    onChange: function( newValue ){ props.setAttributes( { icols: newValue } ); }
                                }
                            ),
                            el(
                                TextControl,
                                {
                                    label: 'rows属性',
                                    help: '表示行数を指定する',
                                    value: irows,
                                    onChange: function( newValue ){ props.setAttributes( { irows: newValue } ); }
                                }
                            ),
                            el(
                                TextControl,
                                {
                                    label: 'Placeholder string',
                                    help: 'placeholder 文字列指定',
                                    value: pholder,
                                    onChange: function( newValue ){ props.setAttributes( { pholder: newValue } ); }
                                }
                            ),
                            el(
                                TextControl,
                                {
                                    label: 'form属性',
                                    help: 'どのフォームと関連付けるかを<form>のid名で指定',
                                    value: iform,
                                    onChange: function( newValue ){ props.setAttributes( { iform: newValue } ); }
                                }
                            ),
                            el(
                                TextControl,
                                {
                                    label: 'maxlength属性',
                                    helgt: '入力可能な最大文字数を指定',
                                    value: imaxlen,
                                    onChange: function( newValue ){ props.setAttributes( { imaxlen: newValue } ); }
                                }
                            ),
                            el(
                                TextControl,
                                {
                                    label: 'styles for input',
                                    help: 'input 要素の style 指定',
                                    value: istyle,
                                    onChange: function( newValue ){ props.setAttributes( { istyle: newValue } ); }
                                }
                            ),
                        ),
                    ),
                    el(
                        'span',
                        Object.assign( blockProps, { className: props.className } ),
                        ilabel ? el( 'label', { htmlFor: iname}, ilabel + ' ' ) : null,
                        el( 'textarea', {
                            placeholder: pholder ? pholder : null,
                            name: iname,
                            id:iname,
                            cols: icols ? icols : null,
                            rows: irows ? irows : null,
                            style: treat_style( istyle ),
                            form: iform ? iform : null,
                            maxLength: imaxlen ? imaxlen : null,
                        } )
                    )
                )
            );
        },
        save: function( props ) {
            let blockProps = useBlockProps.save(),
                { iname, ilabel, pholder, icols, irows, iform, imaxlen, istyle } = props.attributes;

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

            return (
                el(
                    'span',
                    blockProps,
                    ilabel ? el( 'label', { htmlFor: iname }, ilabel + ' ' ) : null,
                    el( 'textarea', {
                        placeholder: pholder ? pholder : null,
                        name: iname,
                        id:iname,
                        cols: icols ? icols : null,
                        rows: irows ? irows : null,
                        style: treat_style( istyle ),
                        form: iform ? iform : null,
                        maxLength: imaxlen ? imaxlen : null,
                    } )
                )
            );
        },
    } );
}(
	window.wp.blocks,
	window.wp.element,
	window.wp.blockEditor,
    window.wp.components
));
JavaScript
CopyExpand

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=170144

Sanbanse Funabashi

Top

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