マクロの利用例

http://f.hatena.ne.jp/images/fotolife/a/amrita2/20070711/20070711144132.png

これは、Amrita2+Gettextによって日本語化したLoginEngineのユーザ情報修正画面です。この上半分の部分テンプレートでは、マクロを活用してAmrita2化してみました。

元のrhtmlは次のようになっています。

<div class="user_edit">
  <table>
    <%= form_input changeable(user, "firstname"), "First Name", "firstname" %>
    <%= form_input changeable(user, "lastname"), "Last Name","lastname" %>
    <%= form_input changeable(user, "login"), "Login ID", "login", :size => 30 %>
    <%= form_input changeable(user, "email"), "Email", "email" %>
    <% if submit %>
      <%= form_input :submit_button, (user.new_record? ? 'Signup' : 'Change Settings'), :class => 'two_columns' %>
    <% end %>
  </table>
</div>

form_inputは、LoginEngine独自のhelperメソッドで、<tr><td>... を生成するものです。これと似た機能ををAmrita2のマクロとして作成してみました。

changeableも同じくLoginEngine独自のメソッドです。いくつかの条件によって text_field とread_only_fieldを切り替える処理ですが、この部分はこの例では省略しています。(常にtext_fieldになる)

class TwoColumns < Amrita2::Macro::Base
  TemplateText = <<-END
      <<table<
        <<tr class="two_columns":rows<
          <<td class="prompt"<
              <<label:title>>
          <<td class="value" :contents>>
  END

  def macro_data(element)
    rows = element.search("tr").collect do |c|
      title, contents = *c.search("td")
      {
        :title => title.contents,
        :contents => Amrita2::SanitizedString[contents.children.to_s],
      }
    end
    {
      :rows => rows,
    }
  end
end

このマクロは2カラムのテーブルを作成し、1つ目のカラムには<label>...</label>を埋めこむものです。

これを利用すると、上記のテンプレートは次のようになります。

<%(BeforeCompile)
  use_macro(TwoColumns)
%>

<<div class="user_edit"<
  <<two_columns<
    <<<-------------------------------------------------------------
      ||| First Name: | <%= text_field "user", "firstname" %>      |
    <<<-------------------------------------------------------------
      ||| Last Name:  | <%= text_field "user", "lastname" %>       |
    <<<-------------------------------------------------------------
      ||| Login ID:   | <%= text_field "user", "login" %>          |
    <<<-------------------------------------------------------------
      ||| Email:      | <%= text_field "user", "email" %>          |
  << ?[submit] <
    <%= submit_button 'user', (user.new_record? ? _('Signup') : _('Change Settings')), :class => 'two_columns' %>

区切られた表の部分は、<tr><td>を生成する略記法です。これによって生成されたxmlが一回分解されて、上記のマクロで再度テーブルに組み込まれます。

これを日本語化して表示すると次のようになります。

<div class = "user_edit">
  <table>
    <tr class = "two_columns">
      <td class = "prompt"><label></label></td>
      <td class = "value"> <input id="user_firstname" name="user[firstname]" size="30" type="text" value="中島" /> </td>
    </tr>
    <tr class = "two_columns">
      <td class = "prompt"><label>名前</label></td>
      <td class = "value"> <input id="user_lastname" name="user[lastname]" size="30" type="text" value="拓" /> </td>
    </tr>
    ....

「姓」や「名前」の部分が<label>...</label>に挟まれていることに注目してください。

マクロの作成は、はっきり言って難易度が高いですが、これを一回作ってしまうと、表の略記法と組み合わせることで、以下のようなメリットがあります。

  1. 個々のテンプレートを最終表示結果に近い直感的な形で記述できる
  2. マクロの展開方法もAmXMLのテンプレートになっているので、CSSとマクロ内テンプレートを調整することで、最終的なHTMLの調整を行なうことができる
  3. <table>によるレイアウトから<div>等によるレイアウトへの変更もマクロ変更のみで可能
  4. アプリ側(ビュー)の記述範囲とマクロの記述範囲を入れ子にできる(上記の<label>の付加はマクロの担当、<label>の中(セルの中身)はアプリ側の記述)

元ネタでは、helperメソッドを駆使してややトリッキーな方法によって似たようなことをやっていますが、この方式の方が意図が明確で、保守性も高いと思います。