Category Archives: Mayaa

MayaaでWebサイトのビューを実装するときに押さえておくべき基本テクニック2 – m:idの実装における注意点

これは Mayaa Advent Calendar 2015 の4日目です。昨日は「MayaaでWebサイトのビューを実装するときに押さえておくべき基本テクニック1 – タグの命名規則と実装方法」でした。

昨日は、命名規則についての注意点を説明しました。今日はそれを軸に、各m:idの実装の仕方の注意点を説明します。

いしがみメソッド2. m:id実装時の注意すること

1. LOOP系のm:idはindexには長い名前を使い、maxを十分に大きな値を設定する

m:forを使ってfor文ライクなループを作るとき、インデックスの変数はiやjではなく、長い名前をつけることをおすすめします。これは、内側に含めるm:idが使う変数にどのインデックスが利くのかわからなくなってしまうためです。

また、for系のプロセッサーはmax属性で指定した回数以上ループすると、エラーが発生してしまいます。maxのデフォルト値は256ですが、256回制限は簡単に超えてしまいます。256を超えたら出力が止まるのではなく、エラーになるので、max属性を常に10000くらいに指定するべきです。

<m:for m:id="LOOP_ITEM" init="${var loopItemIndex = 0;}" test="${loopItemIndex &lt; items.length;}" after="${loopItemIndex++}" max="10000"

別途、ユーザーに指定した回数でループを打ち切る機能を作るのがベストです。それにはテンプレート記述者にパラメータを指定される方法が必要ですので、このやり方は今後紹介します。

2. writeプロセッサーでエスケープを解除するときは全部する

m:writeプロセッサーは、標準では常に改行・XML特殊文字・ホワイトスペース文字をエスケープします。XSSの心配がない箇所で動的にタグを出力したい場合など、エスケープを解除する場合は

<m:write m:id="INFO_HTML_HERE" escapeXml="false" />

のように書けますが、m:idごとにエスケープの設定が変わると混乱を招きます。
ので、3種類のescapeは全て同時にfalseにしましょう。

<m:write m:id="INFO_HTML_HERE" escapeXml="false" escapeWhitespace="false" escapeEol="false" />

3. 混乱を招く機能を無効にする

Mayaaの標準設定では、m:id属性ではなく、id属性でもプロセッサーと紐付けられるようになっていますが、デザイナーと分業をする際に混乱を招くため無効にしましょう。

また、XPath機能は使うべきではありません。
理由としては、デザイナーから見て、暗黙の動作のように見えてしまうことがあります。これは後々必ず混乱を招きます。
パフォーマンスにも問題があるので、これも無効にしてしまいましょう。

さらに、m:injectを使ってのプロセッサーの解決も、分業する上では不要な機能なので、無効にするべきです。

よって、次のようになります。

参考: 5-6. id属性を無視する

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE provider
    PUBLIC "-//The Seasar Foundation//DTD Mayaa Provider 1.0//EN"
    "http://mayaa.seasar.org/dtd/mayaa-provider_1_0.dtd">
<provider>
    <templateBuilder
            class="org.seasar.mayaa.impl.builder.TemplateBuilderImpl">
        <resolver class="org.seasar.mayaa.impl.
                builder.injection.MetaValuesSetter"/>
        <resolver class="org.seasar.mayaa.impl.
                builder.injection.ReplaceSetter"/>
        <resolver class="org.seasar.mayaa.impl.
                builder.injection.RenderedSetter"/>
        <resolver class="org.seasar.mayaa.impl.
                builder.injection.InsertSetter"/>
<!--
        <resolver class="org.seasar.mayaa.impl.
                builder.injection.InjectAttributeInjectionResolver"/>
-->
        <resolver class="org.seasar.mayaa.impl.
                builder.injection.EqualsIDInjectionResolver">
            <parameter name="reportUnresolvedID" value="true"/>
            <parameter name="reportDuplicatedID" value="true"/>
            <!-- HTMLのidとXHTMLのidを追加しないようコメントアウト
            <parameter name="addAttribute"
                    value="{http://www.w3.org/TR/html4}id"/>
            <parameter name="addAttribute"
                    value="{http://www.w3.org/1999/xhtml}id"/>
            -->
        </resolver>
<!--
        <resolver class="org.seasar.mayaa.impl.builder.
                injection.XPathMatchesInjectionResolver"/>
-->
        <parameter name="outputTemplateWhitespace" value="true"/>
        <parameter name="outputMayaaWhitespace" value="false"/>
        <parameter name="optimize" value="true"/>
    </templateBuilder>
</provider>

余談ですが、将来ここに、独自のInjectionResolverを追加していくようになればMayaa上級者です!

4. 名前は英語にこだわらない

m:idの名前はHTMLタグの属性名として指定します。プログラマーはついメソッドを定義するときのように、英語的な名前付けをしてしまいますが、HTMLファイルが横に長くなってしまって読みづらくなってしまうので、積極的に省略しましょう。

IF_IS_MEMBER
ではなく
IF_MEMBER
とします。

5. tableタグだけは特別に扱う

tableタグはMayaaと相性が悪いタグの一つです。
divタグのように、無限に包むことが出来ず、

table > tbody > tr > td
のように階層構造が決まっているため、例えば、trタグにm:idを既に適用してしまうと、そのtrをある条件にもとづいて出し分けなどが出来ません。

ダメな例:


<table>
  <tbody>
<div m:id="IF_MEMBER">
    <tr m:id="ROW1_TAG">
      <td>aaa</td>
      <td>bbb</td>
      <td>ccc</td>
    </tr>
</div>
    <tr m:id="ROW2_TAG">
      <td>aaa</td>
      <td>bbb</td>
      <td>ccc</td>
    </tr>
  </tbody>
</table>

この書き方は出来ません。divタグをtbodyとtrの間に書くことが出来ないためです。IF_ROW3をm:echoなしのifプロセッサーにして、タグが消えるようにすれば、サーバー側では大丈夫になりますが、テンプレート側で見た時に表示が崩れてしまいます。

そこで、せっかくIF_MEMBERのように流用できるm:idがあったとしても、ROW1_TAGの方に、ifプロセッサーを追加して次のようにしてください。

<m:if m:id="ROW1_TAG" test="${context.isMember()}">
  <m:echo>
    <m:attribute name="xxx" value="yyy" />
    <m:doBody />
  <m:echo>
</m:if>

まとめ

地味なルールですが、これらを守ることであとあと混乱を防ぐということが効いてきます。

Mayaaはとても汎用的に作られていて、初期状態では、大変ゆるい状態でリリースされている印象を持ちます。

しかし、チームで作業をする場合は例えば、
InjectAttributeInjectionResolver
のようなものは使わず、必要最小限の機能で賄う戦略を取ることが結局効率が良いです。

さらに、次回はもっと面白いテクニックを紹介しようと思います。

MayaaでWebサイトのビューを実装するときに押さえておくべき基本テクニック1 – m:idの命名規則と実装方法

これは Mayaa Advent Calendar 2015 の3日目です。昨日は「JSPで書かれたシステムをMayaaに移行する」でした

さて前回の記事で、JSPでフロントが書かれたプロジェクトをMayaaに移行する例を書きました。しかし、実際にWebシステムのビューをすべて実装しようとすると大変です。

そこで、今日から数日に分けて私の経験に基づく、MayaaでWebサイトを実装するテクニックを紹介します。

自分で言うのも恥ずかしいですが、「いしがみメソッド」ってなやつです (^^;

いしがみメソッド1「命名規則は徹底せよ」

デザイナーはMayaaファイルを読めません。したがって、m:id一つ一つ何を意味するのか、意思疎通をしておくことが必須です。

1.m:id含め、プログラム都合の識別子は常に大文字を使う

Mayaaを使う最大の目的は、デザイナーとプログラマーが平行して、最終的に出力される一つのHTMLを作ることです。

したがって、同時にひとつのHTMLを作ることになります。そうすると、二人して同時にclassを作ってしまったりします。

そこで、プログラマーが書くものは常に大文字、デザイナーが書くものは常に小文字というように、命名規則を分けることを推奨します。

プログラマーが小文字、デザイナーが大文字でも良いですが、制御を扱うm:idが大文字の方が埋もれないメリットが有り、次のようなスタイルが良いです。

<div m:id="IF_MEMBER" class="member_box">
 ...
</div>

2.m:idのする仕事を4種類に限定する

僕は発行するm:idを次の4種類に限定しています。

  • *_HERE

    • その場所に文字列を出力する
  • *_TAG

    • そのタグの属性を変化させたり要素を足したりする
  • IF_*

    • そのタグ及びその子要素が特定の条件にもとづいて消える
  • LOOP_*

    • そのタグ及びその子要素を特定の条件に基づいて繰り返す

これまでの経験から、この4種類だけでWebサイトは作れ作れます。

3.それぞれの実装方法

それぞれ、実装の方法も固定されます。_HEREは、その場に出力されれば良いので、m:writeプロセッサーをそのまま使うのが良いでしょう。

<!-- タイトルを出力します -->
<m:write m:id="TITLE_HERE" value="${page.getTitle()}" />

m:writeプロセッサーではなく、m:insertプロセッサーの場合もあります。これらは、デザイン視点から見たら違いがないので、m:idの名前は、常にデザイナー視点で考えてください。

_TAGは主にタグの属性を変更するために使用します。場合によっては、hidden要素の追加にも使います。hiddenはデザインとは関係ない要素なので、テンプレートに記述することはせず、常にm:idで出すべきです。

また、属性はデザイナーが編集する場合もあり、m:idで制御する対象とデザイナーが書いた属性がぶつかると、デザイナー側の記述が無効になってしまいます。そこで、事前にどのm:idがどの属性を制御するのかわかるようにしておきます。classやid属性が変化するやつは特に注意が必要です1

<!-- 商品画像を表示します。src属性を制御します -->
<m:echo m:id="ITEM_IMAGE_TAG">
  <m:attribute name="src" value="${image.getSrc()}" />
</m:echo>

IFのm:idは、そのままm:ifプロセッサーを使ってはいけません。m:ifプロセッサーは、条件にマッチしても、自分のタグを消してしまいます。例えば

ダメな例:

<m:if m:id="IF_MEMBER" test=${context.isMember()} />
<div m:id="IF_MEMBER">
会員限定
</div>

会員限定

このようになってしまうと、テンプレート上ではdivで囲まれて前後が改行されているのに、divが消えて横並びになってしまいます。これではいけませんので。正解は次のように書くことです。

良い例:

<m:if m:id="IF_MEMBER" test=${context.isMember()}>
  <m:echo><m:doBody /></m:echo>
</m:if>

LOOPも同じことが言えます。

良い例:

<m:forEach m:id="LOOP_ITEM" items=${items}>
  <m:echo><m:doBody /></m:echo>
</m:if>

4._TAGの運用な慎重に

これら、4つのルールの中で、*_TAGは唯一汎用的に使うことができます。それは、IFのようにも、_HEREのようにもできます。

このことは、コンポーネント的なm:idを作れる反面、きちんとドキュメントを書かないとデザイナーとの間で混乱を招きます。

例えば、ある時「条件に応じて、display:noneで表示非表示を切り替えられる領域」という意味で「**_AREA_TAG」というものを作りました。
「エリアタグとはなんぞや」
というクレームが上がりました。どうしても、特殊な概念のTAGを作りたければ、事前にネゴっておきましょう。

実際は_INPUT_TAGや、_RADIO_TAG、*_IMAGE_TAGのように指定するべきタグ名と名前を合わせるのが無難な選択です。

ただ、必ずしもタグ名がデザインとシンクロするとも限らないので、この辺りは事前に話し合って決めましょう。

default.mayaaに書いたm:idには接頭辞"common."を付けよう! (2015/12/07 追記)

default.mayaaに書いたm:idはすべてのテンプレートファイルで使えます。これらと各ページ特有のm:idは名前で見分けられるほうが良いです。

ということで、僕は"common."という接頭辞を付けています。

<span m:id="common.SITE_NAME_HERE">サイト名</span>

まとめ

ざっと命名規則について、説明させていただきました。

まとめてみると、常に、同じリソースを、プログラマーとデザイナーが同時に書いているということを意識することが大切です。

Mayaaという技術の採用はプログラマー側が引っ張って導入しているものなので、プログラマーは是非自分の範囲以上に、チーム全体のことを考えてm:idの設計を行ってください。

Mayaaの導入で最も重要なのはチームワークです。明日以降もチームワークを支える様々なテクニックを紹介します。

JSPで書かれたシステムをMayaaに移行する

これは Mayaa Advent Calendar 2015 の2日目です。昨日は「Mayaaとの出会い」でした。

JSPで書かれたシステムのMayaaへの移行

さて、前回、MayaaはJSPと同じレイヤーで動いているから、JSPでデザインが描かれているシステムは簡単に移行できると書きました。

今回は実例を元に、その様子を解説しようと思います。

何でも良いので、JSPをビューに使っているMVCフレームワークがあるとします。

例えばこれは、SAStrutsのコード例です。

import org.seasar.struts.annotation.Execute;
public class IndexAction {
    @Execute(validator = false)
    public String index() {
        return "index.jsp";
    }
}

今から新規にStrutsのコードを書くことはないと思いますが、StrutsライクなMVCフレームワークなら何でも同じようにできると思います。自分の使っているフレームワークに脳内変換してください。
(後日、最近のメジャーなフレームワークとMayaaを連携する例を書きたいと思います。)

MayaaServletをweb.xmlに登録

もし使っているのが、レガシーServletでWeb.xmlを書いているなら、次の設定を書き加えましょう。

    
    <servlet>
        <servlet-name>MayaaServlet</servlet-name>
        <servlet-class>org.seasar.mayaa.impl.MayaaServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>MayaaServlet</servlet-name>
        <url-pattern>*.xhtml</url-pattern>
    </servlet-mapping>

<url-pattern>*.xhtml</url-pattern>という部分は、標準のMayaaの流儀ではないかもしれません。しかし、多くのWebシステムは既にhtmlをマッピングしているのではないでしょうか?私の現場もそうでした。そこで私はxhtmlファイルをMayaaのテンプレートとして設定しました。その結果、テンプレートファイルは正しいxhtml書式で書く文化がチーム内に浸透したため、良い判断だったと思います。

あとは、Java側でフォワードしている部分のJSPの部分を

import org.seasar.struts.annotation.Execute;
public class IndexAction {
    @Execute(validator = false)
    public String index() {
        return "index.xhtml";
    }
}

のように書き換えましょう。後はindex.jspの代わりにindex.xhtmlを置くだけです。

この方法の良い所は、JSPとMayaaを同居できることです。移行作業中は、古いJSPのテンプレートも残すことが出来ます。ただし、JSPにMayaa、MayaaにJSPをインクルードすることは出来ませんので、そのあたりはご注意ください。

テンプレートを書く

次にテンプレートを書きましょう。例えばJSPがあったとします。ちなみに、僕はJSPのtaglibが嫌いで、ほとんど使いません。なので、MayaaにもJSPのtaglibをサポートする機能を持っていますが、全然使ったことがありませんので、公式のマニュアルを参照してください(笑)

ちなみに、EL式も使いません。何か文句ありますか?

<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ page import="info.susumuis.sample.ViewHelper" %>
<%@ page import="info.susumuis.sample.SampleBean" %>
<% SampleBean bean = (SampleBean) request.getAttribute("bean"); %>
<html>
<head>
  <title ><%= ViewHelper.escape(bean.getTitle()) %> </title>
</head>
<body>
<%= ViewHelper.escape(bean.getMessage()) %>
</body>
</html>

ViewHelper.escapeは、HTMLエスケープをする共通メソッドだと思ってください。

これをMayaaに移植する最も手っ取り早い方法は、JSPを実行した結果のHTMLをいきなり貼り付けてしまうことです。

 



<html>
<head>
<title >たいとる</title>
</head>
<body>
ハローワールド
</body>
</html>

このままでは文字化けしてしまいますので、metaタグで文字コードを指定します。Mayaaはmetaタグでコンテントタイプを設定しておくと、勝手にヘッダを書き換えてくれますので活用しましょう。

このとき、注意として、HTML5の、<meta charset="">を使わないことです。使っても良いのですが、text/html;の部分の情報が抜けてしまうので、以下のように古い書き方を使うことをおすすめします。

<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />

また、JSP特有の空行は消していきましょう。また、拡張子をxhtmlファイルにしてしまったので、ブラウザでプレビューできるようにxmlns指定を書きます。本当は先頭行に宣言が必要ですがこれはサボっても表示できるようです。(この辺りは、MayaaがContent-Typeを見て最終的に適切な出力に調整してくれます)

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title >たいとる</title>
</head>
<body>
ハローワールド
</body>
</html>

このテンプレートはWebブラウザでそのまま開くことが出来るはずです。

これで、サーバーで実行するのと同じ出力を得ることができますね。

え、動的な部分が固定されてしまっている?その通りです。しかし、Mayaaのページ作りの流儀としてはこれでよいのです。この後の説明を読めば分かります。

動的部分を実装する

元のコードはSampleBeanから、タイトルとメッセージを取得していましたね。Mayaaでは動的な出力を実現するために、m:idというものを使います。

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:m="http://mayaa.seasar.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title m:id="TITLE_TAG">たいとる</title>
</head>
<body>
<span m:id="MESSAGE_HERE">ハローワールド</span>
</body>
</html>

htmlタグに名前空間xmlns:mを追加しています。
m以外を指定しても動きますが、普通はm:に割り当てます。"m:id"は「エム・アイ・ディー」と読みます。Mayaaの世界では非常に多用する単語なので、覚えましょう。

動的にしたいタグにm:idを加えていきます。もともとタグがなかったところはspanタグで補います。

ところで、Mayaaは非常に柔軟ですが、自由に使い過ぎると、デザイナーさんとの連携をスムーズにできません。お互いにある程度ルールを定めるべきです。そこで、僕はm:idは次の4種類に限定しています。

  • _HERE

    • その場所に文字列を出力する
  • _TAG

    • そのタグの属性を変化させたり要素を足したりする
  • IF_

    • そのタグ及びその子要素が特定の条件にもとづいて消える
  • LOOP_

    • そのタグ及びその子要素を特定の条件に基づいて繰り返す

m:idを発行するときは、出来る限り、HTMLとしてvalidになるように心がけましょう。
例えば、tableタグの中に、divタグを入れたり、titleタグの中にspanタグを入れるのは無しです。そのようなことをすると、Mayaaの良さを喪失してしまうので気をつけてください。

このままページを出力してみましょう。
ローカルでそのままダブルクリックしてブラウザに表示させても、サーバーで実行させても相変わらず表示は同じです。

mayaaファイルを書く


<m:mayaa xmlns:m="http://mayaa.seasar.org">
    <m:beforeRender>
        var bean = request.bean;
    </m:beforeRender>

    <!-- タイトルタグです -->
    <m:echo m:id="TITLE_TAG">
        <m:write value="${bean.getTitle()}" />
    </m:echo>

    <!-- メッセージを出力します -->
    <m:write m:id="MESSAGE_HERE" value="${bean.getMessage()}" />
</m:mayaa>

このようにm:idの動作を記述していきます。実は命名規則を定めたので、これらの書き方はある程度定型化できます。Mayaaはm:idだけ定義しておいて、mayaaファイルに実装していないとログにエラーメッセージを吐くので、エラーメッセージから雛形を出力するマクロを作るのも良いでしょう。僕も過去に作りました。

Mayaaファイルを命名規則から一括作成するEmEditorマクロ

本当は、標準のm:write, m:ifなどは使い勝手が悪いので、自分でプロセッサーを作ることをおすすめします。これについては、後日ご紹介します。

もう一つ、JSPのコードからViewHelper.escapeの記述が消えました。Mayaaでは、出力をエスケープするのがデフォルトの動作です。なので、「必要に応じてエスケープを解除する」というスタイルになります。昨今脆弱性が対策は必須となっていますので、この流儀は大変便利です。

このようにして1ページの移行が完了しました。あとは、全てのページを同じように移行していくことです。

しかし、実際のページはもっと複雑で、難しいことも多いと思います。次回から、個別にハマリポイントとその回避方法を紹介して行こうと思います。