• 活用ブログTOP
  • エンジニアが解説!新テーマ「Fusion Corporate」制作のポイント(テーマ作成者向け)② かんたんデザイン編集編
jun jun

エンジニアが解説!新テーマ「Fusion Corporate」制作のポイント(テーマ作成者向け)② かんたんデザイン編集編

はじめに

こんにちは!エンジニアのjunです。
去る2024年10月1日、MovableType.net 用の新テーマ「Fusion Corporate」を公開しました🎉

この「Fusion Corporate」は、デザイン性と更新性のfusion(融合)を目指して制作されたテーマです。
更新性(更新しやすさ)の肝となるのが、6月に公開した「かんたんデザイン編集機能」です!

この機能のおかげで、トップページなどのデザイン要素を、運用担当者が自らコードを触らず編集できるのです。
いわばノーコード的な機能をアドオンできる便利機能がかんたんデザイン編集です。この機能の使い方の詳細は、次の2つのリンク先をご覧下さい。

さて、「Fusion Corporate」ではかんたんデザイン編集機能を、どの場所でどのように組み込んでいるのでしょうか?
この記事は Fusion Corporate のような汎用的なテーマを作りたい人向けに、テーマ実装方法を解説する3回連載記事の第2回目です。かんたんデザイン編集機能の実装について解説します。

既存のテーマをかんたんデザイン編集に対応させたい!」「テーマをイチから作ってみたい!」
そんな時の参考になれば嬉しいです。

  1. CSS編
  2. かんたんデザイン編集編 ← 今回はココ!
  3. MTテンプレート編

さあ、かんたんデザイン編集編、スタートです!!

1行のテキストを設定する

まずは最も基本的な、ページ内の特定の1行を編集するための実装方法を見ていきましょう。
「Fusion Corporate」では、フッターのコピーライトなどで、1行のテキストフィールドを利用しています。

コピーライトでは、以下のような記述になっています。

// footer.mtml
<p class="text-md-end small mb-0">
  <mt:Var
    name="theme_copyright"
    editor:label="コピーライト"
    editor:type="contenteditable"
    editor:mode="text"
    default="©2003 EXAMPLE INC."
    editor:button:mode="before"
    editor:update:required="1"
    editor:button:position="-10,-15"
  >
</p>
// footer_settings.mtml
<mt:Var
  name="theme_copyright"
  editor:label="コピーライト"
  editor:type="text"
  editor:mode="editor"
  editor:update:required="1"
  default="©2003 EXAMPLE INC."
  editor:register="0"
>

footer.mtmlは、最終的なHTMLを出力するテンプレートになるため、クラスのついた<p>タグで囲っています。
footer_settings.mtmlは、管理画面の入力欄の定義です。装飾のためのHTMLタグなどはありません。

一番重要なのは、両者のname="theme_copyright"属性を揃える点です。
実際にテーマを作成する際は、
まずは settings にて変数定義を行い、
次に出力させたい箇所で呼び出す、といった流れをイメージしていただくと良いかもしれません。

default="©2003 EXAMPLE INC."も、それぞれへ記述する必要があります。
なお、ソフトウェア版 Movable Type の グローバルモディファイア
_defaultとは異なり、アンダースコアはないためご注意ください。

共通点もある一方、editor:typeの内容には差があります。
footer.mtmlではcontenteditableを指定しているため、要素を直接編集可能になります。

「かんたんデザイン編集」機能で、1行テキストフィールドを更新するデモンストレーション

モディファイアは1行に1つ

余談ですが、MTタグのモディファイアは1行に1つずつ、複数の行にわたって記述しています。 これは、人気のJavaScriptフレームワーク Vue.js を参考にしています。 公式スタイルガイドには、以下のように記されています。

JavaScript では、複数のプロパティをもつ要素を複数の行に分けて書くことはよい慣習だと広く考えられています。なぜなら、その方がより読みやすいからです。Vue のテンプレートや JSX も同じように考えることがふさわしいです。

MTテンプレートでも同じように行を分けて書くほうが読みやすいと考え、Vue.jsのスタイルを踏襲しています。

複数行のテキストを設定する

閑話休題。

続けて、複数行テキストフィールドについて。

先ほどと同様、フッターの「連絡先」を例にしたいと思います。
「連絡先」以外には、メインページのキャッチコピーなどで利用しています。

// footer.mtml
<address class="mb-0 theme-prose theme-block-margin-0">
  <mt:Var
    name="theme_address"
    editor:label="連絡先"
    editor:type="contenteditable"
    editor:editor="richtext"
    editor:mode="text"
    default="<p>〒123-4567<br>○○県△△市□□町1-23-45<br>◇◇ビル 6階</p>"
    editor:button:mode="before"
    editor:update:required="1"
    editor:button:position="-10,-15"
  >
</address>
// footer_settings.mtml
<mt:Var
  name="theme_address"
  editor:label="連絡先"
  editor:type="textarea"
  editor:editor="richtext"
  editor:mode="editor"
  default="<p>〒123-4567<br>○○県△△市□□町1-23-45<br>◇◇ビル 6階</p>"
  editor:update:required="1"
  editor:register="0"
>

1行テキストフィールドと変わらない使い勝手ではないでしょうか。

defaultの値に<p>タグまでも含めている点は異なります。
editor:editor="richtext"を指定した場合、ユーザーが入力値を変更すると<p>タグも含めた値が保存されます。
それと揃えるために
defaultにもタグを含めています。

リンクを設定する

リンクを設定したい場合は、1行テキストフィールドを組み合わせて対応します。
「Fusion Corporate」では、グローバルナビゲーションのお問い合わせボタンなどで利用しています。

// global_nav.mtml
<a
  class="btn btn-primary btn-sm fw-bold mb-1 px-3 py-2 w-100 theme-CTA"
  href="<$mt:Var name="theme_CTA_button_url" editor:label="URL" editor:mode="attribute" editor:type="text" default="/contact/"$>"
>
  <mt:Var 
    name="theme_CTA_button_text"
    editor:label="テキスト"
    editor:mode="text"
    editor:type="contenteditable"
    editor:button:mode="before"
    default="お問い合わせ"
    editor:button:position="-5,-20"
  >
</a>
// global_nav_settings.mtml
<mt:Var 
  name="theme_CTA_button_text"
  editor:label="テキスト"
  editor:type="text"
  default="お問い合わせ"
  editor:register="0"
>
<mt:Var 
  name="theme_CTA_button_url"
  editor:label="URL"
  editor:type="text"
  default="/contact/"
  editor:register="0"
>

用意したフィールドは2つ。ボタンの文言と、リンク先URLです。

ボタンの文言は、記事冒頭の1行テキストフィールドの例と同様です。

目新しい点は、editor:mode="attribute"の登場です。
その名前の通り、HTML属性として出力する際の設定です。[href]として出力するglobal_nav.mtmlのみ設定し、global_nav_settings.mtmlでは設定していません。

クラス名のCTAとは

「クラス名に略語は用いない」という方針でいますが、CTAは例外のため、補足です。

CTAとは、Call to Action の頭文字です。日本語訳すると「行動喚起」。
例えば「お問い合わせ」や「ログイン」、「無料トライアル」など、サイト訪問者に次に起こして欲しい行動を喚起させるための導線作りと言えます。fusion corporate のようにボタンリンクの場合は、「CTAボタン」などと呼ばれることも多いですね。

画像を設定する

画像フィールドは、ロゴ画像の入力欄などで登場します。
ここらから少し見慣れない記述が増えてくるかもしれませんが、出来るだけ丁寧に解説できればと思います。

まずは実際のコード。
ここでも、フッターを例にします。

// footer.mtml
<p class="fs-4 m-0 theme-footer-logo MTSE-footer-logo">
  <mt:Var
    name="theme_footer_logo"
    editor:label="ロゴ画像"
    editor:type="image"
    editor:update:attribute="src"
    editor:update:selector=".MTSE-footer-logo img"
    editor:if:value="<img>"
    editor:if:empty="$theme_name"
    editor:button:position="-10,-15"
  >
  <!-- mt-site-editor-theme_footer_logo -->
  <mt:If name="theme_footer_logo">
    <img
      src="<mt:Asset id="$theme_footer_logo"><$mt:AssetURL$></mt:Asset>"
      alt="<$mt:Var name="theme_name"$>"
      width="<mt:Asset id="$theme_footer_logo"><$mt:AssetProperty property="image_width"$></mt:Asset>"
      height="<mt:Asset id="$theme_footer_logo"><$mt:AssetProperty property="image_height"$></mt:Asset>"
      decoding="async"
      fetchPriority="high"
    >
  <mt:Else>
    <!-- mt-site-editor-theme_footer_logo-if-empty -->
      <$mt:Var name="theme_name"$>
    <!-- /mt-site-editor-theme_footer_logo-if-empty -->
  </mt:If>
  <!-- /mt-site-editor-theme_footer_logo -->
</p>
// footer_settings.mtml
<$mt:BlogName encode_html="1" setvar="theme_name"$>

<mt:Var
  name="theme_footer_logo"
  editor:label="ロゴ画像"
  editor:type="image"
  editor:update:attribute="src"
  editor:update:selector=".MTSE-footer-logo img"
  editor:if:value="<img>"
  editor:if:empty="$theme_name"
  editor:register="0"
>

テキストフィールドでは登場しなかったeditor:update:attributeeditor:update:selectorにまずは注目です。
この2つはセットになります。

テキストフィールドとは異なり、<img>タグの[src]属性を変更したいため、editor:update:attributeを設定します。
加えて、
editor:update:selectorにて、変更すべき要素を指定します。属性名に selector とあるように、CSSや JavaScript のセレクターが有効です。

なお、.MTSEの接頭語がついたクラスは、かんたんデザイン編集のプレビュー画面専用クラスです。
役割をより明確にすること、スタイルの柔軟性を高めることが目的です。

変数に保存される値は、画像のID

name属性で指定したMT変数に保存される値は、画像のIDになります。
そのため、取り出す際には
<mt:Asset>タグを用います。

mt:Var の位置 = 編集ボタンの位置

ここで1つ注意したいのが、出力テンプレートにおける<mt:Var>の扱いです。

かんたんデザイン編集プレビュー画面では、<mt:Var>は、鉛筆アイコンの編集ボタンへ置き換わります。
これは、テキストフィールドではあまり意識する必要がなかった点ではないでしょうか。
「プレビュー画面だけスタイルが崩れた」とならないように、画像には親要素を設け、その中で
<mt:Var>を呼び出すと良いと思います。もちろん、最終的な公開画面では編集ボタンは表示されません。

画像が未設定の場合の処理

「Fusion Corporate」では、画像未設定の場合に、ブログタイトルを通常のテキストとして表示させています。画像タグは出力されません。この、未設定の処理が一番わかりにくい箇所かと思います。

画像フィールドに限りませんが、プレビュー画面でのリアルタイム反映の裏では、JavaScript が大きな役割を担っています。
そのため、 JavaScript の経験があると動作をイメージしやすいかもしれません。

とはいえ、未設定処理のために JavaScript を書く必要はありません。
具体的に追加すべきコードは以下の2つです。

<!-- mt-site-editor-theme_footer_logo -->
<!-- /mt-site-editor-theme_footer_logo -->
<!-- mt-site-editor-theme_footer_logo-if-empty -->
<!-- /mt-site-editor-theme_footer_logo-if-empty -->

いずれも、開始タグと終了タグのセットです。
theme_footer_logoの箇所は、nameで設定した文字列が適用されます。
開始と終了のタグで挟まれた内容が、JavaScriptで処理され、画像未設定の場合の条件分岐が行われます。

MTタグのif文と、JavaScriptのif文の2つを併記するわけです。
前者は、ページのロード時に処理されます。公開画面にも影響するため、メインはこちらです。
後者は、ページをリロードせずともリアルタイムに処理されます。公開画面には影響せず、あくまでも管理画面のリアルタイム反映のための処理です。

なお、今回は、画像未設定の場合に画像タグを出力しない仕様も、複雑になった理由の1つに思います。
もし、「常に画像タグは出力する。画像未設定の場合は、プレースホルダー画像を表示させる」などの仕様になる場合は、以下などで十分かもしれません。

<mt:If name="theme_footer_logo">
  <mt:Asset id="$theme_footer_logo"><$mt:AssetURL setvar="theme_footer_logo_src"$></mt:Asset>
<mt:Else>
  <$mt:Var name="theme_footer_logo_src" value="/path/to/placeholder.svg"$>
</mt:If>

<img src="<mt:Var name="theme_footer_logo_src">">

記事やウェブページのリストを設定する 基本編

editor:type="list"を利用することで、ブログ内の記事やウェブページへのリンクを簡単に作成することができます。 やはりフッターを例に紹介したいと思います。

なお、メインページや記事一覧ページへのリンクがありますが、これらかんたんデザイン編集の対象外です。テンプレートを書き換えないかぎり、必ず出力されます。ただし、リンクの文言だけはかんたんデザイン編集の「全般設定」から変更可能です。

// footer.mtml
<ul class="d-flex flex-column flex-sm-row flex-wrap column-gap-5 row-gap-2 list-unstyled m-0 small MTSE-sub-menu">
  <li><a href="<$mt:BlogRelativeURL$>"><$mt:Var name="theme_toppage_title" default="トップページ"$></a></li>
  <li><a href="<$mt:BlogRelativeURL$>archive/"><$mt:Var name="theme_archive_title" default="お知らせ"$></a></li>

  <mt:Var
    name="theme_sub_menu_items"
    editor:label="フッターナビゲーションに表示するウェブページ"
    editor:type="list"
    editor:mode="editor"
    editor:update:selector=".MTSE-sub-menu-item"
    editor:update:html="$theme_sub_menu_item_template"
    editor:update:parent:selector=".MTSE-sub-menu"
    editor:button:position="-10,-15"
  >
  <mt:If name="theme_sub_menu_items">
    <mt:Pages ids="$theme_sub_menu_items">
      <li class="MTSE-sub-menu-item">
        <a class="MTSE-sub-menu-item__title MTSE-sub-menu-item__url" href="<$mt:PagePermalink$>"><$mt:PageTitle encode_html="1"$></a>
      </li>
    </mt:Pages>
  </mt:If>
</ul>
// footer_settings.mtml
<mt:SetVarBlock name="theme_sub_menu_item_template">
  <li class="MTSE-sub-menu-item">
    <a class="text-reset MTSE-sub-menu-item__title MTSE-sub-menu-item__url"></a>
  </li>
</mt:SetVarBlock>
<mt:Var
  name="theme_sub_menu_items"
  editor:label="フッターナビゲーションに表示するウェブページ"
  editor:type="list"
  editor:mode="editor"
  editor:update:selector=".MTSE-sub-menu-item"
  editor:update:html="$theme_sub_menu_item_template"
  editor:update:parent:selector=".MTSE-sub-menu"
  editor:register="0"
>

nameeditor:labelなどの属性は、引き続き同様です。
画像で利用した
editor:update:selectorを利用していますが、今回はeditor:update:htmleditor:update:parent:selectorの、3つがセットになります。

editor:update:selectorは、挿入したいリンクアイテムのセレクターです。

editor:update:htmlは、今回のように階層を持ったHTML構造を挿入したい場合に利用します。ここでは、一度MT変数に代入しています。
ここで注意が必要なのが、
<a>タグのクラスです。暗黙のルールとして、editor:update:selectorの値に__title__urlを連結したクラス名が必須になります。これらがないと、ページタイトルやリンク先が表示されず空になってしまいます。
クラスの命名規則は変更できません。また、
editor:update:selectorの子要素である必要があります。以下のように、すべて同じ要素にまとめて指定することはできません。

<a class="item item__title item__url">動作しない</a>

変数に保存される値は、ページのID

name属性で指定したMT変数に保存される値は、記事やウェブページのIDになります。
そのため、取り出す際には
<mt:Entries>タグや<mt:Pages>を用います。

記事やウェブページのリストを設定する 応用編

先ほどのeditor:type="list"には、より高度な利用方法があります。
editor:update:fetch="1"属性の利用です。
「Fusion Corporate」では、メインページの「ピックアップ記事」や「ピックアップウェブページ」で利用します。

通常のeditor:type="list"では、プレビュー画面のその場でHTMLの断片を作成し画面を更新します。
一方、
editor:update:fetch="1"を追加すると、プレビュー画面とは別の場所でページ全体をまるっと作成します。その後に、フェッチでページを取得し、必要な箇所を差し替えて画面を更新しているのです。

差し替えすべき必要な箇所の指定はeditor:update:selectorで行います。

使い分けの基準としては、まずはHTML構造の複雑さです。
画像を用いた、いわゆるカード型リンクなどの場合には、画像やカスタムフィールドの情報を取得することもあると思います。
カスタムフィールドの処理が必要な場合は、フェッチで対応する必要があります。

もうひとつの判断基準は、リスト未設定の場合の処理です。
画像と同様、未設定の場合には
<!-- mt-site-editor--if-empty --><!-- /mt-site-editor--if-empty -->のタグを利用しますが、これもコードの見通しを悪くする原因になります。
未設定の場合は表示を切り替えたい場合は、フェッチの利用が良いと思います。

フェッチにはデメリットもあります。
それは、反映までのタイムラグです。
別の場所でページ全体をまるっと作成しているため、どうしても時間がかかります。
無闇矢鱈とフェッチするととても重くなってしまう可能性もあるため注意が必要です。

「Fusion Corporate」での実際のコードは以下です。

// index.mtml
<mt:SetVarTemplate name="theme_webpage_item_template">
  <$mt:Include module="ウェブページアイテム" title_tag="h3"$>
</mt:SetVarTemplate>

<section class="MTSE-selected-webpages tw-my-24">
  <mt:Var
    name="theme_toppage_selected_webpages"
    editor:label="ピックアップウェブページ"
    editor:type="list"
    editor:resource:type="page"
    editor:update:fetch="1"
    editor:update:selector=".MTSE-selected-webpages"
    editor:button:position="-10,-15"
  >
  <mt:If name="theme_toppage_selected_webpages">
    <h2 class="visually-hidden">ピックアップページ</h2>

    <div class="theme-webpages theme-block-columns">
      <mt:Pages ids="$theme_toppage_selected_webpages">
        <$mt:Var name="theme_webpage_item_template"$>
      </mt:Pages>
    </div>
  <mt:Else>
    <mt:If name="is_site_editor">
      <h2>ピックアップページ</h2>
      <p>かんたんデザイン編集で、ピックアップしたいウェブページを設定することができます。</p>
    </mt:If>
  </mt:If>
</section>
// index.mtml
<mt:Var
  name="theme_toppage_selected_webpages"
  editor:label="ピックアップウェブページ"
  editor:type="list"
  editor:resource:type="page"
  editor:update:fetch="1"
  editor:update:selector=".MTSE-selected-webpages"
  editor:register="0"
>

なお、カード型リンクは複数ページで利用するため、テンプレートモジュール「ウェブページアイテム」として切り出して登録していますが、ここでは割愛します。
上記例では、
<$mt:Var name="theme_webpage_item_template"$>と呼び出すのみです。

MTのモディファイアを設定する

いよいよ、最後です。
最後は、かんたんデザイン編集の変更を、MTのモディファイアとして利用する例です。

「Fusion Corporate」では、メインページに新着記事を表示させることができます。
この、新着記事の件数を変更できるようにする例です。

かんたんデザイン編集未対応の場合は、以下のようにすれば新着3件を取得できます。

<mt:Entries limit="3">
  ...
</mt:Entries>

limit="3"となっている箇所を、かんたんデザイン編集で任意の値へ変更できるようにします。
「Fusion Corporate」での実際の例は以下です。

// index.mtml
<section class="MTSE-latest-entries tw-my-24">
  <mt:Var
    name="theme_toppage_latest_entries_limit"
    editor:label="最新記事の件数"
    editor:type="number"
    default="3"
    editor:update:fetch="1"
    editor:update:selector=".MTSE-latest-entries"
    editor:button:position="-10,-15"
  >
  <mt:Unless name="theme_toppage_latest_entries_limit">
    <$mt:Var name="theme_toppage_latest_entries_limit" value="3"$>
  </mt:Unless>

  <mt:If name="theme_toppage_latest_entries_limit" ge="1">
    <h2 class="m-0"><$mt:Var name="theme_archive_title" default="お知らせ"$></h2>

    <div class="theme-entries column-gap-5 my-5 border-top border-bottom">
      <mt:Entries limit="$theme_toppage_latest_entries_limit">
        <$mt:Var name="theme_entry_item_template"$>
      </mt:Entries>
    </div>
  
    <div class="text-center">
      <a href="<$mt:BlogRelativeURL$>archive/" class="btn btn-outline-primary">一覧をみる</a>
    </div>
  <mt:Else>
    <mt:If name="is_site_editor">
      <h2>最新記事</h2>
      <p>かんたんデザイン編集で、最新記事の件数を設定することができます。</p>
    </mt:If>
  </mt:If>
</section>
// index_settings.mtml
<mt:Var
  name="theme_toppage_latest_entries_limit"
  editor:label="最新記事の件数"
  editor:type="number"
  default="3"
  editor:update:fetch="1"
  editor:update:selector=".MTSE-latest-entries"
  editor:update:html="$theme_entry_item_template"
  editor:register="0"
>

じつは、一番のポイントは、既出のeditor:update:fetch="1"
モディファイアの値を動的に反映させたい場合も、フェッチが必要になります。

もうひとつのポイントは、<mt:Unless>タグを登場している点です。
これは、テーマのクリーンインストール時など、変数が本当に空っぽの場合への対応です。

MTの実装上、<mt:Var name="theme_toppage_latest_entries_limit" default="3">だけではtheme_toppage_latest_entries_limit変数へ値が保存/格納されないのです。
このままでは
<mt:If name="theme_toppage_latest_entries_limit" ge="1">trueになりません。
そのため、
<mt:Unless>を用いて、改めて初期値を設定しています。

まとめ

前回に引き続き、すっかり長文になってしまいました。。。
しかし、今回も、お伝えしたいことはしっかり書き切りました。
とくに、最後の 
<mt:Unless> を用いた初期値の扱いなどは、私自身がばっちりハマった点でもあります。それを共有することは、とても有意義なことだと信じています。

ぜひみなさんもテーマ作成へ挑戦してみてください!