Webエンジニア susumuis の技術ブログ

このブログの内容は個人の見解であり、所属する組織の公式見解ではありません

2年間Mayaaを使ってわかったこと その2

前回のエントリは、ここ最近の自分の取り組みの集大成(大げさw)のつもりで気合を入れて書きました。結果、このブログとしては多めアクセス数、ブックマークをいただいているようで、誠にありがとうございます。*1しかし読み返してみると書き忘れたことが幾つかありました。すでに前エントリは十分長いので、ここに続きを書こうと思います。

ちなみに、仕事のことを公に書いて大丈夫かと時々言われますが、弊社代表が「オープンにして失うものはない」ということをおっしゃっているので、それに従っています。もちろんこのブログは上司も社長も読んでいるので、万が一なにか差し支えがあって怒られたら、その時は全力で修正するでしょう。

それでは書きます。

Mayaaを効果的に使うためのノウハウ

iframeを使ってサーバサイドインクルードを偽装する

Mayaaを使ったテンプレートで妥協はよくありません。例えばWebサイトでは当たり前のように使われる「ヘッダー」「フッター」「サイドバー」などの部品は、テンプレートエンジン関連で重大な悩みだと思います。
「Webページにヘッダーなどの共通部分を埋め込みたい!」…‥古くはFrameタグやSSIからあるニーズですね。
しかし、このことをうまく解決している現場はなかなかないと思います。例えば、FrameやIFrameを使った方法では、JavaScriptでの制御が難しく、ユーザに対してもどことなく違和感を感じさせてしまいます。一方、SSIのようなサーバサイドのインクルードは(Mayaaではinsertプロセッサーで実現できますが)、「サーバ上で実行しなければ結果がわからない」というデメリットがあります。

私たちは、Mayaaのinsertプロセッサと、IFrameを組み合わせてこの問題を解決しました。

まず、こんな感じのm:idを定義します。

<!-- 常に非表示にします。 -->
<m:null m:id='DUMMY' />
<!-- ヘッダーを出力します。 -->
<m:insert m:id='common.HEADER_HERE' path='${"common/header.xhtml"}' />

次に、次のようなクライアントサイドJavaScript(js/iframe.js)を作成します。

function resize_iframe(element) {
	if (element.contentWindow.document.documentElement) {
		element.style.height = element.contentWindow.document.documentElement.scrollHeight + "px" ;
	} else {
		element.style.height = element.contentWindow.document.body.scrollHeight + "px";
	}	
}

テンプレートには次のように出力します。

<script type="text/javascript" charset="utf-8" src="js/iframe.js" m:id='DUMMY'></script>
<iframe m:id='common.HEADER_HERE' src="common/header.xhtml" frameborder="0" scrolling="no" width="100%" onload="resize_iframe(this)"></iframe>

このようにすると、ローカルでhtmlファイルを直接開いたときは、iframeとしてヘッダ部分が表示され、サーバ上で実行したときは、insertプロセッサの働きでHTML内にインクルードされます。

ただ、残念なことにchrome4(いつのことやら)あたりで、セキュリティが厳しくなってしまって、上記コードをローカルの実行するとJavaScriptエラーになってしまいました。この場合、chrome起動オプション

–allow-file-access-from-files

を付与すると、エラーが出なくなり正しく動作するようになります。

IFやLOOPのタグの定型文

いろいろ試したのですが、ifプロセッサやforプロセッサ使用時のテンプレートやmayaaファイルの定義は次の書き方に統一するのが一番良いように思います。

<m:if m:id="IF_MEMBER" test="${条件}">
  <m:echo><m:doBody /></m:echo> <!-- この一行が大事! -->
</m:if>
....
<div m:id="IF_MEMBER">
  こんにちは....さん。
</div>
...

このようにすると、testの条件を満たすときは、divタグがまるごと表示され、条件を満たさないときはdivタグごと消えてくれます。これを、

<m:if m:id="IF_MEMBER" test="${条件}" />

のように書いてしまうと、test条件を満たすときのhtml出力が

....
  こんにちは....さん。
...

のようになってしまい、divタグが消えてしまいます。そうすると、デザイナーさんがもし、divタグにstyle属性を付けている場合、それらが適用されなくなってしまい、デザインと本番との整合性が取れなくなってしまいます。

しかし、どうせいつも同じように書くなら、

<my:if m:id="IF_MEMBER" test="${条件}" />

のように書けたほうが楽なので、独自のプロセッサーを定義することで実現できないか、今度試みようと思います。

エスケープを解除するときは全部解除する

writeプロセッサーなどで出力した文字は自動でエスケープされます、デフォルトでXSS対策上望ましい挙動をしてくれるのは安心です。もし、HTMLタグを出力したい場合など個別にエスケープを解除したい場合は、

escapeXml="false"

という属性をつければOKです。
http://mayaa.seasar.org/documentation/processor_reference.html#write

ドキュメントにもあるとおり、エスケープ系の属性は、他にもescapeEol、escapeWhiteSpaceがあります。それぞれ、改行をBRに、空白を&nbspに変換するものです。これについて、僕は初めのうち、統一性なくごちゃ混ぜ(ある程度は利便性を意識したが)に使ってしまいました。その結果、ユーザに混乱を招いてしまいました。しまいにはDBのデータから改行コードを損失するという事件が発生してしその時はお客様にはご迷惑をかけ、先輩にはログから復旧する作業を徹夜でさせてしまいました。今でも大変申し訳ないことをしたと反省しています。
それ以来、エスケープを解除するときは、

escapeXml="false" escapeEol="false" escapeWhiteSpace="false"

というふうに全部falseにすることに統一しました。また属性の書き方も順番も統一し、何かあったときにgrepしやすいようにしました。ならば、これについても、

<my:write value="データ" escape="false" />

のようにまとめて書けると尚更シンプルだと思いますので、これもそのうち試してみようと思います。

テーブルレイアウトは良くない。divレイアウトが良いわけ

Mayaaを使ったテンプレートで画面を実装するとき、出来ればいわゆるtableレイアウトは望ましくありません。その具体例を紹介します。例えば、「table2行を1かたまりとして繰り返したい」というニーズがあった場合、

<table>
<span m:id="LOOP_DATA">
  <tr>...1行目...</tr>
  <tr>...2行目...</tr>
</span>
</table>

と書くのが良いでしょう。でもこれはhtmlの文法違反です。確か、IEFirefoxChromeで違う出力をしてしまったと思います。htmlには文法上、子要素にできるタグの種類に制限がありますが、tableタグ関連ではその制限が厳しめです。本番実行時はspanタグごと消してしまうようにmayaaファイルを書けば良いですが、そうすると、本番とローカルとでデザインのズレが出てしまいます。
一方divタグならばいくら入れ子にしても正常に動きます。ですので、可能ならばtableレイアウトは避けてdivレイアウトを採用しましょう。といっても、いまどき常識ですよね。

<div>
  <div m:id="LOOP_DATA">
    <div>...1行目...</div>
    <div>...2行目...</div>
  </div>
</div>
iPhoneなどのスマートフォン対応に必要なこと

ここにまとめてました。
http://d.hatena.ne.jp/s-ishigami/20110331/p2

多言語対応

ひとつのサイトを複数の言語で運用する場合、言語ごとにテンプレートを分けるのが無難でしょう。公式サイトでは以下の手法が紹介されています。
http://mayaa.seasar.org/documentation/template_suffix.html
私たちは別の所でtemplateSuffixを使ってしまいましたので、SourceDescriptorによる振り分けで対応しましたが、本質的には同じことだと思います。

ここでも、前回書いた「プログラムは文言を吐き出さない」「テンプレートに文言を委譲する」方針が生きてきます。プログラムで文言を言語切り替えする場合、一般的にはリソースバンドルを使うことになるでしょう。しかし、経験者はわかると思いますが、リソースバンドル+プロパティファイルでの多言語化は、結構しんどいです。1ファイルで何千行もの文言を管理しなければなりません。。。(私の所属する会社ではコードジェネレータでそのめんどくささを回避していますが、それは別の話ですね)。なので、なるべくテンプレート側に文言を持ちましょう。

その他

書き忘れたことがあったら、さらに追って書いていきます。

情報源

日本発のオープンソースらしく、日本語の情報、コミュニティが充実しています。まずは、

  • 本家サイトの説明を熟読する
  • MLを購読する。過去ログを読みあさる

これだけでかなり深い情報が得られます。

また、

  • SeasarConferenceなどで発表があったら聴く

というのもおすすめします。

2010年のSeasar Conferernce×JCMT2010では、ヌーラボさんの「Cacoo」発表の中にMayaaの話題がありました。
http://www.slideshare.net/nulab/seasarwebcacoo
実運用事例なので大変参考になりました。

2006年のSC2006Autumnでは、現プロジェクトリーダーの須賀さんの発表があったようです。
http://mayaa.seasar.org/documentation/slide/SC2006Autumn_B1_Mayaa.pdf
こちらも、参考にさせていただいております。

日本には、Mayaaユーザが結構いるようですね。派手な存在ではないですが、話をすると使ったことある方がポロポロ出てくる印象です。

*1:と、同時にプレッシャを感じます(笑)