ブロックエディタを用いたコンテンツの編集(ComponentBlocksプラグイン)

ComponentBlocksプラグインは、設計済のHTML/CSSコンポーネントに対応した入力フィールドを用意するためのブロックエディタです。Vue.jsを用いて開発されています。

ComponentBlocksプラグインを導入してブロックエディタを表示した画面

ブロックの設定

はじめに記事モデル等で本文を記述する際に利用する「ブロック」をブロックモデルに登録します。

カラム名 入力内容
ラベル ブロックの名称です。ブロックエディタでブロックを選択するドロップダウンに表示されます。
フィールド 入力欄を定義します。チェックボックス・ラジオボタンの選択肢やリレーションで紐付ける対象のモデルは「詳細設定」ボタンを押して設定してください。
キー
入力欄を識別するもので、ブロックのデータを保存する時、保存したブロックのデータを取り出す時に利用します。input要素のname属性と同じように考えてください。(id, type, fields, containers, blocks, invalidは設定できません。)
ラベル
入力欄のラベルです。ブロックに複数の入力欄を設ける場合はラベルを付けると分かりやすいです。
編集表示
どのような入力欄にするかを選択します。
初期値
テキストフィールドに予め設定しておく値やチェックボックスで予めチェックしたい値を入力します。
ヒント
入力欄の下に表示される説明文を入力します。
選択肢
編集表示がチェックボックス・ラジオボタンの場合に選択肢をカンマ区切りで指定します。A,B,Cのようにラベルのカンマ区切り、もしくは2=見出し2,3=見出し3,4=見出し4のように値=ラベルのカンマ区切りが指定できます。
単一選択
編集表示がアセット・リレーションの時、オブジェクトを単一選択にしたい場合にチェックします。単一選択の場合でも値は配列に保存されます。
繰り返しフィールド dl要素におけるdt+ddのセット、table要素におけるth+tdのセットのように同一の入力フィールドを繰り返し利用する入力欄を定義します。設定内容はフィールドと同一です。
マルチブロックを使用 繰り返しフィールドに似ていますが、コンテナ(1つの入力セット)毎に任意のブロックを追加していくことができます。入力内容が定まっていないカラムレイアウト等に使用します。これにチェックを入れた場合、フィールド・繰り返しフィールドの定義は必要ありません。代わりにマルチブロック内で利用できるブロックを指定してください。
テンプレート 入力欄のカスタマイズができます。詳細はテンプレートをご覧ください。
methods 独自の処理を追加できます。詳細はmethodsの追加(上級者向け)をご覧ください。
利用できるブロック マルチブロック内で指定できるブロックを選択します。
モデル 今定義しているブロックが利用可能なモデルを指定します。(モデルの指定を忘れるとブロックエディタのブロック選択ドロップダウンに表示されません。)
コンポーネント名 ブロックを識別するための名称を英数字で指定します。名称はCMS内で一意である必要があります。パスカルケース(アッパーキャメルケース)で入力してください。例えば見出しブロックの場合はHeadingなどと命名します。(Editor, ImageSelector, ObjectSelector, MultiBlockEdit, などは既にシステムで使用しているため設定できません。)

モデルの設定

ブロックエディタを使用したいモデルの編集画面を開き、block_editカラム(カラム名は任意)を追加して編集表示を「コンポーネントブロック」に設定します。記事(Entry)モデルの本文(text)カラムをリッチテキストエディタからコンポーネントブロックに変更することも可能です。プロジェクト毎に検討の上設定を行ってください。
モデルの編集画面で編集表示にコンポーネントブロックを指定している画面

コンテンツを出力するためのテンプレート

ブロックエディタで入力したコンテンツはJSON形式で保存されています。入力フィールドで「保存されるデータ」を開くと確認できます。1ブロック1オブジェクトになっています。よって、保存されている値を取り出してfrom_jsonモディファイアでJSONオブジェクトに変換した後にループするとブロックのデータを取り出すことができます。

記事のblock_editカラムにおいて見出し・テキスト・テーブルブロックを使用した場合のテンプレートを示します。

ブロック 入力内容 キー
見出しブロック 見出しテキスト text
見出しレベル level
テキストブロック テキスト text
テーブルブロック 列見出し th
データ td
<mt:entryblockedit from_json="blocks" />
<mt:loop name="blocks"><mt:ignore>blocksをループで回すとブロックのデータを1つずつ取り出すことができる</mt:ignore>
  <mt:if name="type" eq="Heading"><mt:ignore>typeにコンポーネント名が入っている</mt:ignore>
    <mt:ignore>見出しブロック:見出しレベルに応じてHTMLを出力</mt:ignore>
    <mt:ignore>level, textはテンプレート・データ定義で入力した「キー」です</mt:ignore>
    <mt:if name="level" eq="2">
      <h2><mt:var name="text" escape /></h2>
    <mt:elseif name="level" eq="3">
      <h3><mt:var name="text" escape /></h3>
    <mt:elseif name="level" eq="4">
      <h4><mt:var name="text" escape /></h4>
    </mt:if>

  <mt:elseif name="type" eq="Text">
    <mt:ignore>テキストブロック:エディタの入力内容をそのまま出力</mt:ignore>
    <mt:var name="text" />

  <mt:elseif name="type" eq="Table">
    <mt:ignore>テーブルブロック:繰り返しフィールドの入力内容がfieldsに入っているのでループして出力</mt:ignore>
    <mt:loop name="fields">
      <mt:if name="__first__"><table></mt:if>
        <tr>
          <th><mt:var name="th" escape /></th>
          <td><mt:var name="td" remove_html /></td>
        </tr>
      <mt:if name="__last__"></table></mt:if>
    </mt:loop>
  </mt:if>
</mt:loop>

カスタマイズ

ブロックのスタイル

user.cssを用いて任意のスタイルを適用することが可能です。Bootstrap v4のコンポーネントやユーティリティが利用できます。

ブロックの選択肢

例えば特定のブロックをマルチブロック内でのみ選択可能としたい場合や特定のカラムでのみ使用したい場合、JavaScriptを用いて選択肢から除外することができます。</body>の直前で実行してください。

delete window.componentBlocks[0][0].enableBlocks.Heading;
// delete window.componentBlocks[何個目のエディタか - 1][0].enableBlocks.コンポーネント名;

またuser.cssを利用して、CSSで選択肢から隠す方法もあります。

テンプレート

ブロックの表示をCSSのみで希望の見た目にできない場合など、テンプレートを編集することでHTMLを変更することができます。

div要素を追加したり独自の説明を追加したりする場合

テンプレート入力欄の下にある「標準テンプレート」ボタンをクリックして標準テンプレートを取得した後で希望のHTMLに変更してください。MTBlockPartsタグ・MTBlockPartsFieldsタグは入力フィールドに置き変えられます。(Vue.js・Bootstrapの作法を隠し編集しやすくするために独自のタグを利用しています。)

ゼロから独自のテンプレートを記述する場合(上級者向け)

姓・名と何らかの選択肢を表示する場合の例を元に説明します。

  • name属性の代わりにv-model属性を指定します
  • v-model属性値の内容はフィールド・繰り返しフィールドで定義したキーを指定します。
    • このキーはHTMLの出力時にも変数名として利用します
    • キーの先頭にはelement.を付けてください(プラグインの仕様によるものです)
  • ラジオボタンや数値のフィールドにおいて値を数値型で保存する場合はv-model.numberとしてください
    • ラジオボタンのvalue属性は:valueにしてください
    • ブロックエディタの編集結果をMTMLで出力するだけの場合(JavaScriptで利用しない場合)は型にこだわる必要はない可能性が高いです
<div>
    <label>姓 <input type="text" v-model="element.family"></label>
</div>
<div>
    <label>名 <input type="text" v-model="element.given"></label>
</div>
<div>
    <label><input type="radio" v-model.number="element.select" :value="1">選択肢1</label>
    <label><input type="radio" v-model.number="element.select" :value="2">選択肢2</label>
</div>
単一画像の選択ボタン

下記のように<ImageSelector>コンポーネントを配置します。field-keyには入力欄を識別するキー(フィールド・繰り返しフィールドで定義したキー)を指定します。

<ImageSelector :element="element" field-key="キーを指定" />
アセットの選択ボタン・リレーションの選択ボタン

下記のように<ObjectSelector>コンポーネントを配置します。modelには選択したいオブジェクトが存在するモデルを指定します。field-keyには入力欄を識別するキー(フィールド・繰り返しフィールドで定義したキー)を指定します。

<ObjectSelector :element="element" class="image" field-key="キーを指定" />
  • class属性を指定することで選択可能なファイルの種類を限定できます。imagefilepdfのいずれかを指定します。
インラインエディタ(TinyMCE)

下記のように<Editor>コンポーネントを配置します。強調やリンクの設定を目的としたミニマムなエディタです。:initの内容を変更することでエディタの設定を変更できます。(詳細は「Vue integration | Docs | TinyMCE」を参照してください。)

<Editor :init="store.initTextEditor" v-model="element.text" class="form-control inline-mce text" :id="element.id" />
マルチブロック

下記のように<MultiBlockEdit>コンポーネントを配置します。

<MultiBlockEdit :element="element" :enableblocks="enableBlocks" />

methodsの追加(上級者向け)

Vue.jsのコンポーネントに記述するmethodsを追加可能です。methodsを追加する場合はテンプレートもゼロから記述する必要があります。Vue.jsで子コンポーネントから親コンポーネントのプロパティを変更することはベストプラクティスではない(「プロパティ | Vue.js」を参照)とされますが、本プロジェクトではCMSに組み込んで利用する際の簡便さを優先してプロパティを変更しています。

this.elementでアクセスできるブロックのデータは編集画面の「保存されるデータ」で確認できますが、下記のようなオブジェクトです。

{ "id":"zqtagryg", "type":"Heading", "text":"見出しテキストが入ります", "level":2 }
onBlurHandler() {
    // NOTE: this.elementでブロックのデータにアクセスできる
    if (this.element.fields.some(field => !field.key)) {
        this.element.invalid = true;
    } else {
        this.element.invalid = false;
    }
},

任意のスクリプトの追加(上級者向け)

ブロックで利用したい任意のスクリプトを追加することができます。新たにPowerCMS Xのプラグインを作成し、template_sourceコールバックで下記の変数に必要なコードを追加してください。

変数名 内容
component_blocks_add_script script要素でJavaScriptをロードします。 <script src="/path/to/foo.js"></script>
component_blocks_custom_script Vue.createApp()の直後(mount()より前)にスクリプトを追加できます。ここでVue.defineComponent()を利用してコンポーネントを完全に自作する場合も、管理画面でブロックの名称、フィールド・繰り返しフィールドの定義、利用可能なモデルの指定、コンポーネント名の指定は行ってください。
if (typeof CustomizeDev !== 'undefined') {
    CustomizeDev = Vue.defineComponent({
        props: ['element', 'index'],
        template: `<div>外部ファイルでカスタマイズ可能なコンポーネント</div>`,
    });
}

補足

  • ブロック選択セレクトボックスの内容はブロックモデルの表示順カラムの昇順で並びます
  • ブロック一覧画面からブロック定義のエクスポート、インポート画面からブロック定義のインポートが可能です(ただしコンポーネント名の重複チェックが行われないため、インポート後はブロック一覧画面を確認してください)
  • ダイナミック生成ページでも利用可能です
  • APIでもブロックエディタのコンテンツが取得可能です
  • 環境変数componentblocks_develop_modeをtrueに設定することでVue.jsのプロダクションモードを無効化することができます(Vue.js devtoolsが利用可能になります)

トラブルシューティング

ブロックが正しく表示されない(コンソールに「Uncaught ReferenceError: Vue is not defined」と表示される)
PowerCMS Xのキャッシュ管理画面を開き「テンプレート・コンパイルキャッシュ ( ファイル )」をリセットしてください。(もしくはVueの編集でエラーが出た時刻に近いcompile_cacheのファイルを削除します。)
ブロックが正しく表示されない(コンソールに「Uncaught SyntaxError: Identifier 'コンポーネント名' has already been declared」と表示される)
ブロックをCSVインポートした際にコンポーネント名の重複が発生しています。ブロック一覧画面を確認し、コンポーネント名の重複を修正してください。
ブロックの選択肢に作成したブロックが表示されない
ブロックの編集画面でモデルを指定しているか確認してください。
マルチブロックのブロック選択肢に空白の選択肢が表示される
例えばマルチブロックを記事モデルで利用するように設定している状況で、記事では利用できないブロックを「利用できるブロック」に指定すると発生します。(現状では仕様です。)

ブロックデータの解説

[
  // 通常のブロック
  {
    "id":"o3uof3k0",
    "type":"Heading",
    "text":"サンプル見出し①", // 自身で決めたキーに入力値が保存される
    "level":2 // 自身で決めたキーに入力値が保存される
  },

  // 増減するフィールドを持つブロック
  {
    "id":"1rxxugt0",
    "type":"Dl",
    "invalid":false,
    // fieldsに入力欄のセット(例:dt+ddの入力フィールド)が入る
    "fields":[
      {
        "id":"mhg0mhbs",
        "key":"資料はありますか?", // 自身で決めたキーに入力値が保存される
        "value":"はい、ございます。" // 自身で決めたキーに入力値が保存される
      },
      {
        "id":"gpnqwswr",
        "key":" 製品デモが見たい",
        "value":"担当者がお伺いしてご説明いたします。"
      },
      {
        "id":"mqk5o0fs",
        "key":"どのような環境が必要ですか?",
        "value":"詳細については「インストール」ページを参照ください。"
      }
    ]
  },

  // マルチブロック
  {
    "id":"g8fu970o",
    "type":"MultiBlockExample",
    "useMultiBlock":true,
    // containersにコンテナのデータが入る
    "containers":[
      {
        "id":"l6rmym1x",
        // コンテナ内に追加したブロックはblocksに入る
        "blocks":[
          {
            "id":"ixomjt7b",
            "type":"Heading",
            "text":"1カラム目見出し",
            "level":2
          },
          {
            "id":"vuqjzlao",
            "type":"Text",
            "text":"<p>テキストが入ります。</p>"
          }
        ]
      },
      {
        "id":"hh43n1vp",
        "blocks":[
          {
            "id":"rv3ct3x4",
            "type":"Heading",
            "text":"2カラム目見出し",
            "level":2
          },
          {
            "id":"6ua2hypf",
            "type":"Text",
            "text":"<p>テキストが入ります。</p>"
          }
        ]
      }
    ]
  }
]