カテゴリー: 全般

  • インナーブロックの属性を動的に変更するのは簡単じゃなかった!

    インナーブロックの属性を動的に変更するのは簡単じゃなかった!

    Gutenbergのブロック開発をしたことがある方はご経験があると思いますが、ちょっと実用的なブロックを開発しようと思うとインナーブロックを利用することは必須の技術になると思います。そのインナーブロックを制御するのにかなり深いドツボにハマったという経験をご披露したいと思います。

    作成しようとしたブロックの概要

    先日次のようなブロックを作成しました。

    ①、②はインナーブロックで①はAというインナーブロックを、②はB,Cというインナーブロックを持っています。Aは内部にHTMLのinput要素を持っていてユーザーが入力するようになっています。
    Bは静的なラベルです。
    CはHTMLのtable要素で、Gutenbergのcore/tableでレンダリングしています。
    このブロックはユーザーがAに入力した内容をCのテーブルに反映させ、入力内容を確認できるようにするというもので、お問合せフォームなどでよく利用されるものです。上の画像は説明のため①、②を縦に並べましたが、実用段階においては①と②は同時に表示されるのではなく、①のボタンを押すと②が表示され、②のボタンを押すと①に戻るというものです。
    一見単純な操作に見えるのですが、Aの入力にあわせてCを再レンダリングするというのが、実はかなり難しいのです。

    まず、インナーブロックの設置方法から

    まず、インナーブロックの設置方法を説明します。
    これは比較的簡単でuseInnerBlocksPropsというフックを使います。具体的には

    import { useBlockProps, useInnerBlocksProps } from '@wordpress/block-editor';
    
    const TEMPLATE = [
        ['core/paragraph', {}],
        ['core/table', {}],
    ];//①
    
    export default function Edit() {
        const blockProps = useBlockProps();
        const innerBlocksProps = useInnerBlocksProps(
            { blockProps },
            {
                template: TEMPLATE,
                templateLock: false
            }
        );//②
    
        return (
            <div {...innerBlocksProps} />//③
        );
    }
    

    これは@wordpress/create-blockで作ったブロックのひな型の中のedit.jsファイルです。

    ①でインナーブロックのテンプレートを作ります。そして②でuseInnerBlocksPropsフックを実行して、③でレンダリングという仕組みです。
    すると、次のようなブロックがブロックエディタ上に現れます。

    テンプレートによる初期化

    先ほどのコードはTEMPLATEにブロックの名称しか入れなかったので、ブロックエディタ上ブロックは初期化されない状態でレンダリングされましたが、TEMPLATEの配列内の要素に初期化するための属性値をオブジェクトとして与えてやれば、初期化することができます。
    コアブロックを初期化するためには、初期化したいブロックに応じたオブジェクトを用意する必要があります。単純なものから複雑なものまで様々です。
    こちらの公式ページに初期化できる属性名が掲載されているので、まずはこのページで調べるのですが、属性名がわかってもそれはオブジェクトのキーの部分がわかるだけで、オブジェクトの値をどのようにするのかがわからないという問題につきあたります。この話をしだすと大きく本題からそれるのでしませんが、今回はcore/paragraphcore/tableの初期化方法だけ説明します。
    core/paragraphは単純でcontent: '初期化しました'というオブジェクトを用意すれば初期化できます。
    core/tableは複雑です。headbodyfootという属性を初期化すればtable要素がレンダリングされるのですが、属性値は単純な文字列ではありません。cellsというキーを持つオブジェクトである必要があります。そこで次のような関数を作りました。

    // セル要素を生成する関数
    const cellObjects = (inputInnerBlocks) => {
        return inputInnerBlocks.map((input_elm) => ({
            cells: [
                {
                    content: input_elm.attributes.labelContent,
                    tag: 'th'
                },
                {
                    content: input_elm.attributes.inputValue,
                    tag: 'td'
                }
            ]
        }));
    }
    

    これを使って次のようにオブジェクトを用意します。

    const tableHead = [];
    const tableBody = cellObjects(inputFigureBlocks);
    const tablefoot = [];
    const tableAttributes = { head: tableHead, body: tableBody, foot: tablefoot };
    

    今回はtableHeadtablefootは空配列にしましたが、ここにも関数で値を入れればHTMLのthead要素とtfoot要素がレンダリングされます。

    ここまでできたら、TEMPLATEにあてはめます。

    const TEMPLATE = [
        ['core/paragraph', { content: '初期化しました' }],
        ['core/table', {...tableAttributes}],
    ];
    

    これでこんなふうに初期化されます。

    ここまでの全コードは次のようになります。

    
    import { useBlockProps, useInnerBlocksProps } from '@wordpress/block-editor';
    
    // セル要素を生成する関数
    const cellObjects = (inputInnerBlocks) => {
        return inputInnerBlocks.map((input_elm) => ({
            cells: [
                {
                    content: input_elm.attributes.labelContent,
                    tag: 'th'
                },
                {
                    content: input_elm.attributes.inputValue,
                    tag: 'td'
                }
            ]
        }));
    }
    
    export default function Edit() {
        //とりあえずinputBlocksを静的にハードコードします
        const inputBlocks = [
            { attributes: { labelContent: 'ラベル1', inputValue: 'インプット1' } },
            { attributes: { labelContent: 'ラベル2', inputValue: 'インプット2' } },
        ]
        //テーブルボディを初期化
        const tableBody = cellObjects(inputBlocks);
        const tableAttributes = { body: tableBody };
    
        const TEMPLATE = [
            ['core/paragraph', { content: '初期化しました' }],
            ['core/table', { ...tableAttributes }],
    
        ];
        const blockProps = useBlockProps();
        const innerBlocksProps = useInnerBlocksProps({ blockProps }, {
            template: TEMPLATE,
            templateLock: false
        });
    
        return (
            <div {...innerBlocksProps} />
        );
    }
    

    inputBlocksを外部のブロックから取得する

    上記のコードは、とりあえずinputBlocksを静的にハードコードしましたが、最初の課題に立ち返ると、inputBlocksは外部のブロックのインナーブロックから情報を取得して配列として生成する必要があります。
    そこで、次のようにブロックエディタで別のブロックを用意しました。

    core/groupの中にitmar/design-text-ctrlという自作のカスタムブロックをインナーブロックとして2つ入れました。
    ここで一つ重要な注意点があります。本題からそれてしまいますが、ここでもかなりハマったので書き留めておきます。

    const TEMPLATE1 = [
        ['itmar/design-text-ctrl', {}],
        ['itmar/design-text-ctrl', {}],
    
    ];
    const TEMPLATE2 = [
        ['core/paragraph', { content: '初期化しました' }],
        ['core/table', { ...tableAttributes }],
    
    ];
    
    const innerBlocksProps1 = useInnerBlocksProps({ blockProps }, {
        template: TEMPLATE1,
        templateLock: false
    });
    const innerBlocksProps2 = useInnerBlocksProps({ blockProps }, {
        template: TEMPLATE2,
        templateLock: false
    });
    
    
        return (
            <>
                <div {...innerBlocksProps1} />
                <div {...innerBlocksProps2} />
            </>
        );
    

    こんなふうに書きたくなりませんか?
    このコードでエラーはでません。インナーブロックも2つレンダリングされます。しかし、2種類のテンプレートがレンダリングされることはないのです。こんなふうになります。

    最初にセットした<div {...innerBlocksProps1} />のテンプレートの内容しかレンダリングされません。これを回避する方法はなさそうです。
    したがって、同じブロックの中に複数のインナーブロックコンポーネントを入れることはできないと覚えておくことが重要です。
    ですから、インナーブロックのエリアを複数作りたいのであれば、まったく別のブロックにインナーブロックをいれるという必要があります。

    では、本題に戻ります。
    今回は自分以外のブロックから情報を引き出す必要あります。そこで必要なのがuseSelectフックです。このフックはブロックエディタ内のすべてのブロックの状態を監視してくれます。
    具体的なコードを示します。

    const inputBlocks = useSelect((select) => {
        const {getBlocks} = select('core/block-editor');
        //全ブロック
        const allInnerBlocks = getBlocks();
        //親ブロックを取得
        const parentBlock = allBlocks.find(block => block.name === 'core/group');
        //その中のインナーブロック
        const targetBlocks = parentBlock ? parentBlock.innerBlocks : [];
        return targetBlocks; 
    }, []);
    

    初めてuseSelectを使う方にはわかりにくいと思うので、もう一度この画像で説明します。

    上記のコードでやりたいのは、2つの「A」の状態を監視することです。つまり、まず「A」を抽出しないといけないのです。
    「親ブロック」はブロックエディタ全体、つまり’core/block-editor’です。その中のブロック全部であるallInnerBlocksは①と②です。今回は「A」があるブロックは①であり、その名前はcore/groupであるということがわかっているので、const parentBlock = allBlocks.find(block => block.name === 'core/group');で①をparentBlockとすることができました。あとはその中のインナーブロック全部ということで、const targetBlocks = parentBlock ? parentBlock.innerBlocks : [];でtargetBlocksが2つの「A」ということになります。
    これで「A」の状態が監視できるようになりました。

    ここでまた重要な注意点があります。useSelectは「A」の状態を監視してinputBlocksを書き換えてくれます。しかし、この処理は非同期処理なのです。
    つまり、いつ書き換わるかわからないということです。書き換わった時にインナーブロックのテンプレートが確実に書き換わるようにするためにどうしたらいいかという課題が出てきます。これを解決するのがuseEffectです。
    こんなふうにしてテンプレートを書き換えます。

    const [TEMPLATE, setTemplate] = useState([]);
    
    useEffect(() => {
        //テーブルボディを初期化
        const tableBody = cellObjects(inputBlocks);
        const tableAttributes = { body: tableBody };
        setTemplate(
            [
                ['core/paragraph', { content: '初期化しました' }],
                ['core/table', { ...tableAttributes }],
            ]
        )
    }, [inputBlocks]);
    

    このuseEffectinputBlocksを依存配列に持っているのでuseSelectinputBlocksを書き換えると発火してくれます。それでテンプレートを書き換えるのですが、useEffect内でconst宣言した定数は外にスコープが効かないので、useInnerBlocksPropsから見えません。
    そこで、TEMPLATEを状態変数にしてuseStateで書き換えるのです。こうすればuseInnerBlocksPropsTEMPLATEの状態変化を検知して、インナーブロックを再レンダリングしてくれるはずです。

    テンプレートは動的に書き換わらない!?

    ここまでかなり苦労してインナーブロックの再レンダリングの仕組みを作ってきました。
    しかし、本当の苦難はこれからでした。
    結論から言ってしまいますが、TEMPLATEが書き換わってもインナーブロックの属性は書き換わりません。初期化したときはTEMPLATEの内容にしたがってレンダリングしてくれますが、その後TEMPLATEの内容が書き換わってもその変化には対応してくれません。これはブロックというのはユーザーが手動で書き換えることを前提としたコンポーネントだからでしょう。自動的に書き換えることは、ある意味で御法度なのかもしれません。
    一度初期化したブロックは動的に書き換えることはできないということです。
    ではどうしたらいいのか?
    一旦削除するという方法があるのです。一旦削除すれば、次のレンダリングは初期化から始まるので、新しいテンプレートでレンダリングされます。
    では、どうやって削除するのか。

    削除の方法はuseDispatchというフックでremoveBlocksという関数を取得し、その関数に引数としてブロックのclientIDを渡せば削除されます。
    ただし、単純に削除してテンプレートを書き換えればいいというものではありません。removeBlocksも非同期の関数なのです。つまり、removeBlocksを実行してすぐにテンプレートを書き換えても、ブロックはまだ削除されていないので効果はありません。もう一つuseEffectを用意してremoveBlocksが完了した後に処理が始まるようにしなければなりません。

    整理すると次のようになります。

    1. inputBlocks(画像のAのブロック)の変化
    2. 画像のB,Cブロックの削除
    3. 削除されたことの確認
    4. テンプレートの再生成
    5. インナーブロックの再レンダリング

    具体的には次のようなコードになります。

    import { useSelect, useDispatch } from '@wordpress/data';
    
    export default function Edit() {
    
        //removeBlocks関数の取得
        const { removeBlocks } = useDispatch('core/block-editor');
        //インナーブロックの監視
        const innerBlockIds = useSelect((select) =>
            select('core/block-editor').getBlocks(clientId).map((block) => block.clientId)
        );
        //inputBlocksに変化があればブロックを一旦削除
        useEffect(() => {
            removeBlocks(innerBlockIds);
    
        }, [inputBlocks]);
    
        //ブロックの削除を確認して再度レンダリング
        useEffect(() => {
            if (innerBlockIds.length === 0) {
                //テーブルボディを初期化
                const tableBody = cellObjects(inputBlocks);
                const tableAttributes = { body: tableBody };
                setTemplate(
                    [
                        ['core/paragraph', { content: '初期化しました' }],
                        ['core/table', { ...tableAttributes }],
                    ]
                )
            }
        }, [innerBlockIds.length]);
        
        ・・・
    

    これで、何とかなるんですが、これだけの工程を踏まないといけないというのは、ちょっと大変すぎますよね。

    replaceInnerBlocksという関数が用意されていた

    この存在を最近知りました。この関数を使えば一旦レンダリングされたインナーブロックの差し替えができるのです。removeBlocksで削除して、その完了を待って、再レンダリングというのに比べたらはるかに効率的です。
    その実行部分のコードを示します。

    useEffect(() => {
        //テーブルボディを初期化
        const tableBody = cellObjects(inputBlocks);
        const tableAttributes = { body: tableBody };
        const newInnerBlocks = [
            createBlock('core/paragraph', {}),
            createBlock('core/table', { ...tableAttributes }),
        ];
        replaceInnerBlocks(clientId, newInnerBlocks, false);
    }, [inputBlocks]);
    

    useEffectはこの一本だけです。
    replaceInnerBlocks関数はテンプレートではなく、createBlock関数でブロックをつくって、既存のインナーブロックと差し替えるというものです。したがって、useInnerBlocksPropsで再レンダリングということも行いません。ですから、useInnerBlocksPropsは、次のように最初に初期化していないインナーブロックの枠だけ作るという役割を果たしてくれればいいのです。

    const TEMPLATE = [];
    
    const innerBlocksProps = useInnerBlocksProps({ blockProps }, {
        template: TEMPLATE,
        templateLock: false
    });
    

    TEMPLATEは変化しませんから、状態変数にする必要はなく、そのためのuseStateも必要なくなりました。

    最後に

    長々説明しましたが、最終的な全コードを示します。

    import { useBlockProps, useInnerBlocksProps } from '@wordpress/block-editor';
    import { useSelect, useDispatch } from '@wordpress/data';
    import { useEffect } from '@wordpress/element';
    import { createBlock } from '@wordpress/blocks';
    
    // セル要素を生成する関数
    const cellObjects = (inputInnerBlocks) => {
        return inputInnerBlocks.map((input_elm) => ({
            cells: [
                {
                    content: input_elm.attributes.labelContent,
                    tag: 'th'
                },
                {
                    content: input_elm.attributes.inputValue,
                    tag: 'td'
                }
            ]
        }));
    }
    
    export default function Edit({ clientId }) {
    
        //replaceInnerBlocks関数の取得
        const { replaceInnerBlocks } = useDispatch('core/block-editor');
    
        //ブロックエディタ全体('core/block-editor')から'itmar/design-text-ctrl'を抽出
        const inputBlocks = useSelect((select) => {
            const { getBlocks } = select('core/block-editor');
            // 全ブロックを取得
            const allBlocks = getBlocks();
            //親ブロックを取得
            const parentBlock = allBlocks.find(block => block.name === 'core/group');
            //その中のインナーブロック
            const targetBlocks = parentBlock ? parentBlock.innerBlocks : [];
            return targetBlocks;
        }, []); //
    
        //インナーブロックの置き換え
        useEffect(() => {
            //テーブルボディを初期化
            const tableBody = cellObjects(inputBlocks);
            const tableAttributes = { body: tableBody };
            const newInnerBlocks = [
                createBlock('core/paragraph', {}),
                createBlock('core/table', { ...tableAttributes }),
            ];
            replaceInnerBlocks(clientId, newInnerBlocks, false);
        }, [inputBlocks]);
    
    
        const blockProps = useBlockProps();
    
        const TEMPLATE = [];
    
        const innerBlocksProps = useInnerBlocksProps({ blockProps }, {
            template: TEMPLATE,
            templateLock: false
        });
    
        return (
            <>
                <div {...innerBlocksProps} />
            </>
        );
    }
    

    ちょっと実用的なブロックを作成しようとするとインナーブロックの活用は不可欠です。今回はフロントエンドでのレンダリングについては触れませんでしたが、

    <InnerBlocks.Content />
    

    というコンポーネントでレンダリングできるのは、コーディングの作業量を大幅に削減してくれます。
    ですから、インナーブロックは積極的に使っていくべきだと思っています。

    今回の制作作業で是非覚えておきたいことを箇条書きにします

    • インナーブロックはテンプレートで初期化することができる。
    • インナーブロックはテンプレートで複数のブロックを入れることはできるが、テンプレートを分割して複数のエリアに配置することはできない。
    • インナーブロックはテンプレートを差し替えても一度レンダリングした属性は動的に変更されない。
    • 一度レンダリングしたインナーブロックの属性を変更するにはcreateBlock関数でブロックを作成し、それを配列にしてreplaceInnerBlocks関数で差し替える。

    こんな感じでまとめることができると思います。

    いかがだったでしょうか。
    このブログが、これからブロック制作をしようと思っている方の参考になれば幸いです。

  • 文字はなくてもfont-sizeプロパティの設定が役立つことがありますよ

    文字はなくてもfont-sizeプロパティの設定が役立つことがありますよ

    文字がない要素にfont-sizeプロパティを設定するって意味がわからん。
    そう考えがちですが、そんなことはないのです。ふとした思い付きがあったのでご披露させていただきます。

    課題は次のようなUIをGutenbergのブロックで制作するにあたってです。

    フォームでユーザーに入力作業をしてもらっているとき、その進捗がどうなっているのかを表示するプログレスバーです。Bの数字とCのバーはAの文字要素に付加された擬似要素です。Bがbefore要素でCがafter要素です。B要素内には文字があるのでcssでfont-sizeを設定しました。コード全体としては次のようになります。

    &::before {
      content: counter(step);
      counter-increment: step;
      width: 1.5em;
      height: 1.5em;
      line-height: 1.3em;
      text-align: center;
      display: block;
      font-size: ${font_style_num.fontSize};
      font-family: ${font_style_num.fontFamily};
      font-weight: ${font_style_num.fontWeight};
      font-style: ${fontStyle_num};
      color: ${textColor_num};
      background: white;
      border: ${textColor_num} solid 1px;
      border-radius: 50%;
      margin: 0 auto;
    }
    

    ${font_style_num.fontSize}のようにテンプレートリテラルで動的にプロパティ値を設定できるのはCSS in JSの一つである「styled-components」を使っているからです(styled-componentsについては別のブログで解説します。)。
    このようにするとB要素の大きさは動的に変化します。
    ここで問題が生じます。B要素の大きさが変化すると、C要素の縦位置を動かす必要が出てきます(C要素はB要素の中心の高さにないと不細工です。)。
    C要素は次のようなコードで設定されています。

    &::after {
      content: '';
      width: 100%;
      height: 2px;
      position: absolute;
      left: -50%;
      top: 0.75em;
      background-image: linear-gradient(to right, ${textColor_num} 50%, ${bgColor_num} 50%);
      background-position: 0 0;
      background-size: 200% auto;
      transition: all 1s;
      z-index: -1;
      /*put it behind the numbers*/
    }
    

    進捗の変化に応じてアニメーションさせるためにtransitionなどの設定がありますが、それは本題からそれるので無視してください。問題はtop: 0.75em;のところです。emは親要素のフォントサイズを1としたときの相対値で設定する単位です。したがって、親要素であるA要素のフォントサイズが20pxだとすれば、0.75emは15pxになるということですね。
    C要素の縦位置はこれで設定されています。しかし、A要素のフォントサイズはC要素の縦位置とは関係ないんです。関係あるのはB要素の大きさです。この変化をC要素に反映させるのはちょっと複雑です。

    どうしたものかと悩んでいたのですが、ふと目が行ったのはA要素のCSSにある

    width: 1.5em;
    height: 1.5em;
    line-height: 1.3em;
    

    の部分です。これはB要素を〇で囲むためにフォントに外枠を設定している部分ですが、この数値はB要素のフォントサイズの変化に応じて変化します。なぜかというと、この単位emを決定する親要素はA要素のフォントサイズではなく、B要素のフォントサイズだからです。これはB要素のCSS設定にfont-size: ${font_style_num.fontSize};があることの効果です。
    だったら、C要素にも同じように設定してやればいいんじゃないのか。要素に文字がなくてもfont-sizeプロパティは設定できますよね。
    ということで、こんなコードを書きました。

    &::after {
      content: '';
      width: 100%;
      height: 2px;
      position: absolute;
      left: -50%;
      font-size: ${font_style_num.fontSize};//これが入った
      top: 0.75em;
      background-image: linear-gradient(to right, ${textColor_num} 50%, ${bgColor_num} 50%);
      background-position: 0 0;
      background-size: 200% auto;
      transition: all 1s;
      z-index: -1;
      /*put it behind the numbers*/
    }
    

    ばっちりB要素の真ん中にバーが設定されるようになりました。
    A要素からフォントサイズを引っ張ってきて、その単位がpxか emか remかで条件分岐させて計算するしかないかなと思ってうんざりしていたのですが、こうすれば何の苦労もなく設定できました。
    たまにはこんな閃きが浮かぶこともあるんですよ!!

  • オブジェクトの数の数えるにはkeysメソッドを使うって知ってた?

    オブジェクトの数の数えるにはkeysメソッドを使うって知ってた?

    そんなの常識でしょ。と言われちゃうかもしれません。でも、私はハマりました。

    オブジェクトって配列に入っているのと、オブジェクトを含んだオブジェクトと2パターンがありますよね。

    colors=[
        {
          color: '#72aee6',
          name: 'Blue 20'
        },
        {
          color: '#3582c4',
          name: 'Blue 40'
        },
        {
          color: '#e65054',
          name: 'Red 40'
        },
        {
          color: '#8a2424',
          name: 'Red 70'
        },
        {
          color: '#f2d675',
          name: 'Yellow 10'
        },
        {
          color: '#bd8600',
          name: 'Yellow 40'
        }
    ]
    

    これはcolorsという名前の配列の中に、オブジェクトが入っています。いくつオブジェクトを含んでいますか?

    そうこれはcolors.lengthで6が返ってきます。

    これとオブジェクト内のオブジェクトの数を返す方法を同じと考えてはいけません。

    border = { 
        topLeft: "0px", 
        topRight: "0px", 
        bottomRight: "0px", 
        bottomLeft: "0px", 
        value: "0px" 
    }
    

    このborderオブジェクトにはいくつのオブジェクトが含まれていますか。

    ついborder.lengthとやってしまいそうですが、これってundefinedが返ります。

    正しくは

    Object.keys(radius_heading).length
    

    としないといけないのです。

    念のため説明を加えると、Object.keys(radius_heading) というのは、オブジェクトが持っている全てのプロパティ名を配列にするコマンドです。これでプロパティ名が配列になったので、その.length プロパティを使って長さを取得します。それがオブジェクトの数というわけです。

    オブジェクト自体はlengthプロパティは持っていません。案外知らなかったということはありませんか?

  • ラジオボタンのチェックを値で設定するときは配列で設定する!!

    ラジオボタンのチェックを値で設定するときは配列で設定する!!

    ラジオボタンの設定をjqueryのスクリプトで行うときにハマるという話です。

    問題の前提

    ラジオボタンは通常複数の同一name属性をもつ要素が集まってできています。

    <input type="radio" name="post_radio" value="val1" checked>要素1
    <input type="radio" name="post_radio" value="val2">要素2
    <input type="radio" name="post_radio" value="val3">要素3
    

    こんな感じです。
    ここで、要素3にチェックを入れるという設定をjqueryでやりたいという要求があるとします
    まず、

    let elms=$('input:radio[name="post_radio"]')
    

    とやると変数elmsは配列となり、3つの要素が入ります。
    そして、要素3にチェックを入れたいわけです。

    eqメソッドによる設定

    一つの方法としては
    3番目の要素をeqメソッドで特定して、propメソッドでチェックを入れる方法。次のような感じです。

    elms.eq(2).prop('checked',true);
    

    しかし、この方法は「要素3」が3番目にあることがわかっていなければ成立しません。HTMLが書き換わり、要素の数が増えたり、順番が変わるとjqueryも書き換える必要があります。

    値(value属性)で設定する

    そこで、HTMLが変わってもjquery側に影響を及ぼさない方法としてもう一つの方法があります。
    それはvalue属性で要素を特定するという方法です。もちろんこの方法でもvalue属性が書き換わればjqueryも書き換える必要が生じますが、要素の数が変わったり、順番が変わることの頻度に比べると少ないと思われます。また、スクリプトの可読性も高いと思います(何番目という指定より、〇〇というvalueをもつ要素というほうが分かりやすいでしょ。)。
    そこでこんな書き方をします。

      $('input:radio[name="post_radio"]').val(['val3'])
    

    これで要素3にチェックが入るのですが、ここで罠が潜んでいるのです。
    よく見ればわかるのですが、valメソッドの引数が配列になっています。これがこの記事の結論です。

    2つの失敗例

      $('input:radio[name=post_radio]').val('val3')
    

    これだとチェックは入りません。中途半端に覚えているとこのように文字列を入れてしまうと思いませんか?
    このコードはラジオボタンのvalue属性を変更しようとするものです。これを実行すると、チェックの状態は変わりませんが、全てのラジオボタンのvalue属性は’val3’に変わってしまします。

      $('input:radio[name=post_radio]').val(['val3']).prop('checked', true);
    

    これもダメです。これは最後のラジオボタンが選択状態になります。 今回の例はたまたま最後のラジオボタンにチェックを入れようとしているので結果オーライですが2番目に入れようと思って’val2’を指定しても要素3が選択状態になります。これは最初に value=”val2″ のラジオボタンが選択されるのですが、その後で全てのラジオボタンがチェックされようとします。しかし、ラジオボタンのグループに対して、一度に複数の要素をチェックすることはできません。結果的に最後のラジオボタンだけがチェックされた状態になります。

    これ相当ハマりすよ。
    ご注意ください。

  • ブロックのラベルの幅を合わせたい<WordPressのブロック制作>

    ブロックのラベルの幅を合わせたい<WordPressのブロック制作>

    インナーブロックを含むブロックの作り方

    ブロック制作でマスターしておきたい技術の一つにインナーブロックがあります。ブロックをパーツのように子ブロックとし、親となるブロックに集約するのです。次の画像のようなものです。

    この画像にあるラベルとインプットボックスは一つのブロックです。つまり、この画像は3つのブロックを集めたブロックなのです。3つのブロックの一つ一つをインナーブロックと呼びます。
    この仕組みを作るのは簡単です。
    edit.jsに次のようなコードを書けば、

    import {useBlockProps,useInnerBlocksProps} from '@wordpress/block-editor';
    ・・・
    return (
    <div {...useBlockProps()}>
        <div {...useInnerBlocksProps()}></div>
    </div>
    )
    

    そのための課題は次の3つです。

    1. インナーブロックの情報収集
    2. 親ブロックにおける要素幅の取得
    3. 親ブロックからインナーブロックへの情報伝達

    インナーブロックの情報収集

    これも覚えてしまえばそれほど難しいわけではありません。useSelect というフックを使えばインナーブロックの情報は簡単に取得できます。具体的には次のコードのとおりです。

    import { useSelect } from '@wordpress/data';
    
    export default function Edit({ attributes, setAttributes, clientId }) {
        const innerBlocks = useSelect((select) => select('core/block-editor').getBlocks(clientId), [clientId]);
        ・・・
    
    }
    

    これでinnerBlocksにはインナーブロックの情報をオブジェクトにしたものが配列として格納されます。
    あとは、innerBlocksの変化に応じて収集した情報を更新する仕組みを作ります。

    useEffect(() => {
        //情報収集のコード
    }, [innerBlocks]);
    

    このuseEffectはinnerBlocksが変化すれば発火します。インナーブロックの数が変わればもちろん編集画面で属性値を変更しただけでも発火してくれます。今回インナーブロックとして挿入したブロックはラベルの文字列やフォントをブロックの属性値として持たせてあるので、それが変化したとき、すなわち、ラベルの文字列の長さが変化するような事象が起きれば発火してくれます。

    親ブロックにおける要素幅の取得

    失敗事例(その1)

    この課題が今回は一番難しかったと思います。useSelect でインナーブロックの情報を収集できたのだから、その中からDOM要素として取り出してoffsetWidthで長さを計ればいいんじゃないのと思います。
    たしかに、innerBlocksattributesの中にはoriginalContentというオブジェクトがあり、ブロックのHTMLが丸ごと文字列として格納されています。ですから次のようなコードで長さが図れるんではないかと思いました。

    const parser = new DOMParser();
    const doc = parser.parseFromString(block.originalContent, 'text/html');
    const elements = doc.getElementsByTagName('label');
    const width = elements[0].offsetWidth
    

    しかし、これではダメです。offsetWidthはレンダリングされた結果を返すのであり、単に文字列の情報をパースしても結果は返りません。上記のコードでwidthは0にしかなりませんでした。

    失敗事例(その2)

    それなら、インナーブロック側でレンダリングされた結果を属性値として記録し、それを親ブロックで収集してインナーブロックにフィードバックしてはしてはどうかと考えました。
    しかし、これはどうも順序が違うようでうまくいきません。save.jsで記録されたブロック情報が途中で書き換えられたということでエラーで止まってしまします。

    したがって、要素幅は親ブロックで独自に計算するということは必須条件だとわかりました。

    ようやく成功

    要するに親ブロックが収集したインナーブロックの情報をもとに、独自にラベル要素の幅を計算しないといけないということなのです。これはかなり困難かなと思いましたが、かつて、こんなコードで要素幅を計算したことがありました。

    const measureTextWidth = (text, fontSize, fontFamily) => {
        const canvas = document.createElement('canvas');
        const context = canvas.getContext('2d');
        context.font = `${fontSize} ${fontFamily}`;
        const metrics = context.measureText(text);
        return metrics.width;
    }
    

    canvasオブジェクトを用意して、そこにレンダリングして幅を計るというものです。このコードは結構役に立ちます。テキストの内容とフォントサイズ、フォントファミリーで要素幅が取得できるのです。他の場面でも使えると思うので、よかったら是非ご利用ください。
    ともあれ、これでインナーブロックのレンダリングに頼らずラベルの要素幅を取得することができました。
    これでuseEffectの中味も完成します。
    これまでの結果をまとめると、以下のようなコードになります。

    import { useEffect } from '@wordpress/element';
    import { useSelect } from '@wordpress/data';
    
    //要素幅を計測する関数
    const measureTextWidth = (text, fontSize, fontFamily) => {
        const canvas = document.createElement('canvas');
        const context = canvas.getContext('2d');
        context.font = `${fontSize} ${fontFamily}`;
        const metrics = context.measureText(text);
        return metrics.width;
    }
    
    export default function Edit({ attributes, setAttributes, clientId }) {
    
    
        const innerBlocks = useSelect((select) => select('core/block-editor').getBlocks(clientId), [clientId]);
    
        useEffect(() => {
            const maxNum = innerBlocks.reduce((max, block) => {
                return Math.max(max, measureTextWidth(block.attributes.labelContent, block.attributes.font_style_label.fontSize, block.attributes.font_style_label.fontFamily));
            }, Number.MIN_SAFE_INTEGER);
            setAttributes({ label_width: `${Math.round(maxNum)}px` })
        }, [innerBlocks]);
        
        return(
            <div {...useBlockProps()}>
            </div>
        );
    }
    

    useEffect内ではinnerBlocks配列からblockを取り出し、そこからblock.attributes.labelContent(ラベルの文字列)、block.attributes.font_style_label.fontSize(ラベルのフォントサイズ)、block.attributes.font_style_label.fontFamily(ラベルのフォントファミリー)の情報を抽出してmeasureTextWidth関数で幅を計測しています。そして計測結果の最大値をmaxNumに格納して、親ブロックの属性値であるlabel_widthに格納するという仕組みです。

    親ブロックからインナーブロックへの情報伝達

    ここまでで親ブロックがラベルの幅を計測して決定することができました。しかし、これだけではインナーブロック(子ブロック)には伝わりません。
    これを実現するため、Gutenbergはとても便利な仕組みを用意してくれています。
    それがprovidesContextusesContextです。
    コードで見るのが一番わかりやすいでしょう。
    親ブロックのblock.jsonです。

    "$schema": "https://schemas.wp.org/trunk/block.json",
        "apiVersion": 2,
        ・・・
    "attributes": {
        "label_width": {
            "type": "string",
            "default": "100px"
        }
    },
    "providesContext": {
        "itmar/label_width": "label_width"
    },
        ・・・
    }
    

    インナーブロックのblock.jsonです。

    "$schema": "https://schemas.wp.org/trunk/block.json",
        "apiVersion": 2,
        ・・・
    "usesContext": [
        "itmar/label_width"
    ],
        ・・・
    }
    

    このようにするとインナーブロックのedit.jsで

    const label_width = props.context['itmar/label_width'] || 'auto';
    useEffect(() => {
    setAttributes({ labelWidth: label_width });
        }, [label_width]);
    

    とすると、親ブロックでprovidesContextとして指定されたlabel_widthの属性値がlabel_width に入ります。上記の例では指定されていなければautoが入ります。そして、setAttributesでインナーブロックの属性値と記録しておきます。
    あとは、この labelWidthをCSSのwidthプロパティにセットすれば、親ブロックでlabel_widthが変化すれば、インナーブロックで再レンダリングが起きて更新されるというわけです。

    最後に注意事項

    ブロック制作においては一般的な注意事項ですが、edit.jsで使える仕組みがsave.jsで使えるとは限りません。というよりも使えないことの方が多いでしょう。
    今回利用したprovidesContextusesContextもその一つです。インナーブロックのsave.jsで

    const label_width = props.context['itmar/label_width'] || 'auto';
    

    として参照しようと思ってもエラーが返ります。save.jsで参照できるのはattributesだけと考えておきましょう。そのため、edit.jsで

    useEffect(() => {
        setAttributes({ labelWidth: label_width });
    }, [label_width]);
    

    としたのです。これでsave.jsでも親ブロックから伝達されたlabel_widthが参照できて、フロントエンドにもレンダリングできるようになります。

  • dangerouslySetInnerHTMLっていかにも危険?!

    dangerouslySetInnerHTMLっていかにも危険?!

    dangerouslySetInnerHTMLが必要なった場面

    Reactコンポーネントでこんなコードを書きました。

    const mediaContent = getMediaContent();
    return(
        <div className="post_text">
            { mediaContent }
        </div>
    )
    

    getMediaContent()は画像等を表示するためのHTMLを生成するためのオリジナルの関数です。mediaContentにはHTMLの文字列が格納されます。
    これで画像が表示されるかというと、さにあらず。文字列がブラウジングされずに表示されました。
    なんでなのと思って、ChatGPTに聞きました。

    「JavaScript (JSX) では、HTML 文字列を直接レンダリングすると、文字列として表示されてしまいます。文字列を HTML としてレンダリングするには、dangerouslySetInnerHTML プロパティを使用する必要があります。」
    コードは次のようになります。

    const mediaContent = getMediaContent();
    return(
         <div
            className="post_text"
              dangerouslySetInnerHTML={{ __html: mediaContent }}
        />
    )
    

    これで、きちんとブラウジングされました。しかし、なんだかいかがわしい名前の属性と思ってChatGPTの回答を読んでいると、

    dangerouslySetInnerHTML は名前の通り、セキュリティリスクがあるため注意して使用してください。mainText に不正なスクリプトが含まれている場合、クロスサイトスクリプティング (XSS) 攻撃の対象となる可能性があります。

    まあ、getMediaContent()の出元がはっきりしていればいいようです。

    不要なタグが付いてしまうという問題

    実は話はこれで終わりません。ここからが備忘録として記録しておくべきところ。
    このdangerouslySetInnerHTMLは属性なので、何らかのタグとともにでないと使えません。つまりmediaContentで出来上がった文字列が必要なタグを全部備えていると、不要なタグが一つつくことになるのです。
    この不要なタグは実は問題です。mediaContentで出来上がるDOM要素が場面によって変化してしまうのです。これではCSSだって当たらなくなる可能性もあるのです。これは一大事です。

    ちなみにWordpressが用意しているwp.element.Fragmentをインポートして

    <Fragment 
        dangerouslySetInnerHTML={{ __html: mediaContent }} 
    />
    

    としてもダメです。全く表示されなくなりました。

    html-react-parserライブラリの活用

    でも、回避する方法はありました。
    html-react-parser というライブラリがあるのです。
    まず、次のコマンドでインストールします。

    npm install html-react-parser
    

    それでReactコンポーネントには次のように書くとOKです。とってもすっきりしてるじゃないですか。

    import parse from 'html-react-parser';
    
    // ...
    
    return (
      <>
        {parse(mediaItem)}
      </>
    );
    

    ということでhtml-react-parserを使うのがベストチョイスと思いました。だたし、この方法でも

    信頼できない HTML 文字列が含まれる場合のセキュリティリスクに注意してください。必要に応じて、サニタイズ処理を実施してください。

    ChatGPTからの忠告

    JSXで要素を直接作成する

    でも、条件によってはもっと簡単な方法がありました。
    それはJSXで要素を直接作成するという方法です。HTMLを生成している関数が複雑だとそういうわけにはいかないのですが、たとえば、flgという状態変数がtrueなら文字列にspanタグをつけて表示したいというような場合です。
    まずは、こんなコードを試してみます。

    const dispLabel = flg ? `${labelContent}<span>(${required.display})<span>` : labelContent;
    return(
        <label>
            {dispLabel}
        </label>
    )
    

    テンプレートリテラルで文字列を生成してレンダリングしようとしています。これだと

    となります。そこで

    const dispLabel = flg ? `${labelContent}<span>(${required.display})<span>` : labelContent;
    return(
        <label
            dangerouslySetInnerHTML={{ __html: dispLabel }}
        />
    )
    

    とすればうまくいくのですが、そもそもdispLabel を文字列で生成するのがよくないわけです。
    そこで、

    const dispLabel = required.flg ? <>{labelContent}<span>({required.display})</span></> : labelContent;
    return(
        <label>
            {dispLabel}
        </label>
    )
    

    これでうまくいくのです。
    少し説明を加えます。
    JSX(JavaScript XML)は、Reactの一部として提供される拡張構文で、JavaScriptの中にHTMLタグを書けるようになるという特徴があります。

    const element = <h1>Hello, world!</h1>;
    

    と書くと、、elementはReactが理解できる特別なオブジェクトになります。これはJavaScriptのオブジェクト表現となり、Reactはこれを利用してDOMを更新します。これを「Reactエレメント」と呼びます。
    つまり、文字列ではなく「Reactエレメント」をつくればいいということです。
    これでdangerouslySetInnerHTMLを使わずに済む範囲が大きくなるのではないでしょうか。

  • 同一プラグインで複数ブロックを仕込む方法<WordPressのブロック制作>

    同一プラグインで複数ブロックを仕込む方法<WordPressのブロック制作>

    ブロック開発していくにあたって、プラグインを使うのは一般的です。そのひな型を作成するツールが「wordpress/create-block」で、プラグインフォルダに移動して、ターミナルから
    npx @wordpress/create-block
    と入力します。
    これで、簡単にプラグインのひな型ができるのはいいのですが、プラグイン一つにつき、出来上がるブロックは一つです。
    これではプラグインをインストールする手間がかかって、インストールしてもらえないかもしれません。
    「新しいブロックを作りました。」といって公開しても、それじゃ寂しいですよね。
    だから、最低でも3つか4つはプラグインの中に仕込みたいわけですが、この方法が結構難しいんです。
    ポイントが理解できていないと結構苦労します。
    そこで、今回できるだけ具体的にその方法を説明したいと思います。

    やっぱり最初は@wordpress/create-blockからスタート

    このコマンドが基本となることは間違いありません。

    npx @wordpress/create-block
    

    出来上がったフォルダ構成を変更

    最初は次のようになっています。

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

    これを次のようにします。

    my-block
    ├── build
    ├── node_modules 
    ├── my-block.php  
    ├── package-lock.json
    ├── package.json 
    ├── readme.txt
    └── src
       └── blocks //新規作成
           └── new-block //新規作成
               ├── block.json //移動  
               ├── edit.js //移動
               ├── editor.scss //移動
               ├── index.js //移動
               ├── save.js //移動
               └── style.scss //移動
    

    お判りでしょうか。srcフォルダの下にblocksフォルダを作り、その下に複数のフォルダを増やしていこうというわけです。この例ではまずnew-blockというフォルダを作りました。

    PHPファイル内のregister_block_typeを書き換え

    PHPファイルには次のようにブロックを登録するPHPの関数が仕込んであり、initというWordPressのアクションフックでこの関数が実行されます。

    function itmar_single_block_init() {
        register_block_type( __DIR__ . '/build' );
    }
    add_action( 'init', 'itmar_single_block_init' );
    

    ここで __DIR__ . '/build'という部分は自分(PHPファイル)がいるディレクトリの中のbuildフォルダを指しています。そして、その中にblock.jsonというファイルがあれば、その内容に基づいてブロックを登録するという機能を果たします。
    これを複数のフォルダに対してその数だけ実行すれば複数のブロックが一度に登録されるという仕組みをつくります。
    どんなふうにコード書くかというと

    function itmar_multi_block_init() {
        foreach (glob(plugin_dir_path(__FILE__) . 'build/blocks/*') as $block) {
            // Static block
            register_block_type($block);
        }
    }
    add_action( 'init', 'itmar_multi_block_init' );
    

    glob(plugin_dir_path(__FILE__) . 'build/blocks/*')でbuild/blocks内のフォルダ名が配列で渡るので、それをループで回してブロック登録を行うというわけです。
    なので、build/blocks内にフォルダを増やしていけば、登録されるブロックは増えていきます。
    ここまで出来たらとりあえずmy-blockフォルダ直下に移動してターミナルから

    npm start
    

    を実行してブロックが登録されていることを確認してください。プラグインを有効にするのを忘れないでくださいね

    @wordpress/create-block –no-pluginでブロックを増やしていく

    ブロックを増やすのは、src/blocksフォルダに移動してターミナルを開き、次のようにコマンドを実行しますbuild/blocksではないですよ。間違わないようにしてください。

    npx @wordpress/create-block --no-plugin
    

    これで会話モードになるのでブロック名などを指定できます。フォルダ名をオプションにしてnpx @wordpress/create-block new-block2 --no-pluginなどとしない方がいいと思います。これだと会話モードにならずname spaceがデフォルトの「create-block」になります。普通は独自のものを使うと思います。特に複数のブロックになると他のブロックと合わせる必要があるのが通常なので、あとで手作業で変更する羽目になります。
    仮にそうなったらblock.jsonだけでなく、editor.scssとstyle.scssのクラス名を変更するのを忘れてはいけません。これを忘れるとスタイルがあたらずハマってしまうことになります。
    これでnpm startが効いていれば複数のブロックが登録されているのを確認できると思います。

    ちなみにこの段階でフォルダ構成は次のようになっています。

    my-block
    ├── build
    ├── node_modules 
    ├── my-block.php  
    ├── package-lock.json
    ├── package.json 
    ├── readme.txt
    └── src
        └── blocks 
            └── new-block 
                ├── block.json  
                ├── edit.js 
                ├── editor.scss 
                ├── index.js
                ├── save.js
                └── style.scss
            └── new-block2 //npx @wordpress/create-block --no-pluginで新たに作成されたフォルダとファイル
                ├── block.json  
                ├── edit.js 
                ├── editor.scss 
                ├── index.js
                ├── save.js
                └── style.scss
    

    私は最初
    このページ
    で勉強させてもらいました。大変助かりました。非常にわかり安いんですが、それでもブロックの登録が確認できずハマったことが何度もありました。でも何回かやっているうちにハマらなくなったんで、初めての方は何度かやるつもりで長い目で見てやりましょう。

  • プログラマ泣かせのブラウザキャシュ

    プログラマ泣かせのブラウザキャシュ

    直したはずのCSSが当たっていない

    CSSの修正って結構面倒ですよね。もちろん最近はCSSを直接編集せずSASSの技術を使うことが多いと思います。それでも、細かい数字を調整するのは苦労します。
    それでやっとできたと思って、確認すると何も変わっていない。これほどガックリすることはありません。
    SASSのコンパイルがうまくいってないのか?CSSファイルを直接エディタで確認しても、ちゃんとコンパイルされています。
    実際、これで1時間ぐらい悩むのことは何度かありました。

    そんな時は強制リロード

    まず、試してもらいたのがこの強制リロードです。それってなにかというと、修正したCSSファイルやJSファイルを強制的に再読み込みすることです。

    このブログをご覧になっている方々にとっては当然のこととは思いますが、あえて説明しておくとブラウザは常に画面に表示すべき内容(専門的な表現だとレンダリングすべきDOM要素)を常に全てサーバーから読み込んでいるわけではありません。DOM要素以外のCSSファイルやJSファイル、イメージファイルなども同様で、一度読み込んだデータはキャッシュと呼ばれるメモリに保存され、変化がないと判断するとそれを使いまわすのです。これは重要な機能でパフォーマンスの維持には欠かせません。この機能があるから画面がサクサク切り替わるのです。

    しかし、これがCSSやJSを修正したのに直っていないという原因になるのです。ですから、Web制作に携わる方々はそのことを常に念頭に置いて、クライアント様にはそのことを伝えないといけません。

    「そんなのリロードすれば勝手にやってくれてんじゃないの?」「再起動すればさすがに再読み込みするんでしょ。」なんて思われがちでしょうが、そうはいきません。下記の操作以外では、強制リロードと同じ効果は得られません。

    でも、実際には強制リロードって難しい

    キーボード操作で簡単に実行できる強制リロードですが、一般ユーザーはその方法をほぼ知らないでしょう。そのため、これを素人のクライアント様に伝えるのはかなり困難です。

    また、パソコンは上記の方法で簡単に操作できますが、スマホの場合はそんなキー操作ができません。かなり面倒な操作が必要で、私などなんどやっても覚えられません。この方法を説明しだすとそれだけで一記事になるので、ここではしません。参考となるURLを張っておきますのでそちらをご覧ください。

    スーパーリロードのやり方は?スマホやiPhoneとブラウザ別で紹介

    ということで結論です

    ご存じのとおり、ブラウザは次のようなコードでcssファイルやjsファイルを読み込んでいます。

    link rel="stylesheet" href="./css/example.css
    

    先ほどブラウザは、「前に読み込んだファイルに変化がないと判断するとそれを使いまわす」と説明しましたが、このようにファイル名が固定されているとファイル名が変わらない限り、過去に読み込んだファイルと判断してしまうのです。
    ですが、逆にいうとファイル名が変われば再度読み込みを行います。つまり、再読み込みの際にファイル名が変わっていればよいということです。その方法は以下のとおりです。

    link rel="stylesheet" href="./css/example.css?<?php echo date('YmdHis')
    

    つまり、ファイル名のあとに?’.date(‘YmdHis’)というスクリプトをつけてファイル名をかえるというわけです。date(‘YmdHis’)は時間を返すPHPの関数です。したがって、このスクリプトはブラウザに読み込んだ時間付のファイル名を渡すことになり、違うファイル名になるため、過去に読み込んだことがあるという判断はできないことになります。これで常に新しいCSSが読み込まれるという仕組みが出来上がります。
    しかし、この方法はPHPのスクリプトが機能することが前提なので、静的なHTMLファイルでは実現できません。また、どんなファイルにも、この設定をしてしまうとキャッシュ機能が失われるのでmp4の動画ファイルのような大容量のファイルに、この手法を適用するのはまずいと思います。そんなファイルについては、やはり強制リロードによるしかないと考えます。
    CSSやJSはいくら長いといっても1MBに達することはないでしょう。また、これらのファイルはバージョンアップの度に変わるもので、不特定多数のユーザーが利用するプラグインなどでは強制リロードの説明は困難ですから、利用価値は非常に高いと思います。
    ということで最後にWordpressサイトでよく使われるスクリプトの書き方を紹介します。テーマならfunction.phpに、プラグインならエントリポイントとなるPHPファイルに埋め込んで使用してみてください。

    function my_script_init()
    {
      wp_enqueue_style('my', get_template_directory_uri() . '/css/style.css?'.date('YmdHis'), array(), '1.0.0', 'all');
    }
    add_action('wp_enqueue_scripts', 'my_script_init');
    
  • Full site editingの時代がやってきた!!

    Full site editingの時代がやってきた!!

    今、WordpressをインストールするとTwenty twentythreeというテーマが付属しています。
    私は既存のテーマを使ってサイト構築するというようなことはしないので、あまり気にしていなかったのですが、ある時、このテーマの中味を見る機会がありました。
    すると、テーマのルートディレクトリにはfunctions.phpはおろか、front-page.php,home.php,index.phpもないのです。フォルダをのぞいても慣れ親しんだPHPファイルはほとんどなく、テンプレートとして用意されたのだろうと思われるHTMLが数個あるだけ。しかも、そのHTMLファイルの中味は半分以上コメント要素。
    これでどうやって動くのか。ChatGPTに聞いてもよくわからないし、関連するようなブログやサイトもあまりない。
    それでようやくFull site editingという言葉にたどり着きました。
    次のサイトは英文のサイトですが、読み解いていくと衝撃的でした。
    Full site editing for theme developers – Full Site Editing
    PHPのテンプレートファイルは既にクラシックスタイルと表現されています。
    かなりの衝撃でした。
    未だにWordPressでWeb制作を教えているスクールは、PHPでテンプレートファイルを書くように教えていますよ・・・
    しかし、そんなことは言ってられません。
    早く勉強して新しい技術を身につけなければいけないんですよ。
    ということで、このブログもFull site editingでのテーマ作成を取り上げていこうと思っています。