Monthly Archives: 12月 2015

MayaaでWebサイトのビューを実装するときに押さえておくべき基本テクニック4 – ヘッダー・フッター共通部分の扱い

これは Mayaa Advent Calendar 2015 の6日目です。昨日は「MayaaでWebサイトのビューを実装するときに押さえておくべき基本テクニック3 – m:idを減らすテクニック」でした。まだまだ頑張りますよ!

さて、Mayaaを手なずけるテクニックを色々紹介してきました。今回までで基本的なテクニックは大分紹介しました。あと1,2回で終わると思います。

なお、ここまで読んでくださった方は気づいていると思いますが、僕のブログはMayaaの基本を押さえている方が実際にサイトを作ろうとした時の指針となるように書いています。初めてMayaaを使う方は事前に公式ドキュメントを一通り見ていただくことをおすすめします。

いしがみメソッド4. ヘッダー・フッター・共通部品はiframeタグをうまく使う

テンプレートエンジンの必須機能として、ヘッダーやフッターなどの共通部品を実現する、includeとかimportとかの機能があげられるでしょう。Mayaaではこれをコンポーネントと呼び、insertプロセッサーによって実現します。

使い方は公式サイトの通りですので割愛します。

ここではこれを実際に使うときの問題点とテクニックを紹介します。

問題点1: 親のテンプレートを直接開いた時、コンポーネントの部分が見えない

例えば、common.HEADER_HERE, common.FOOTER_HEREという名前でヘッダ・フッタを出力するm:idを発行したとします。(m:id名の分類はHEREになります。デザイナーから見たら「何かが」出力されることはwriteでもinsertでも同じだからです)

使う側はこの世になるでしょう

<div m:id="common.HEADER_HERE">
ここにヘッダが表示されます。
</div>
メインコンテンツ
<div m:id="common.FOOTER_HERE">
ここにヘッダが表示されます。
</div>

このテンプレートをサーバーで実行すると、ヘッダー・フッターの部分に共通部品が表示されます。しかし、テンプレートファイルを直接開くと

ここにヘッダが表示されます。
メインコンテンツ
ここにヘッダが表示されます。

となってしまってなんだか簡素です。サーバーに上げなくてもデザインの確認ができるMayaaの良さが半減しています。

解決案1: divタグの中身を都度書く

一番単純な方法は、ヘッダ・フッタテンプレートのソースコードをコピペしてdivタグの中身に書いてしまうことです。

<div m:id="common.HEADER_HERE">
<h1>○○のサイト</h1>
</div>
メインコンテンツ
<div m:id="common.FOOTER_HERE">
<small>copyright (c) 2015 susumuis.info</small>
</div>

この方法は確かにデザインを確認することができましが、共通部品のデザインを変更すると、都度すべてのテンプレートを書き換える必要があって、作業量が爆発します。この方針はダメですね!

解決案2: iframeタグをうまく使う

それではどうするかというと、おすすめなのがiframeタグです。
iframeタグはまさしく、HTMLの仕組みだけで、ページのコンポーネント化を実現する機能です。が、実際にはあまり積極的に使われません。

そんなiframeタグにinsertプロセッサーを適用すると、
* ローカルプレビュー時はiframeで見た目の確認ができる
* サーバーで実行した時は直接内容が書かれている
ということが実現できます。

呼び出し元のテンプレート側にはこのように書きます。

<iframe m:id='common.IMPORT_HERE></iframe>

ただ、これだけでは、ページを開いた時に、スクロールバーが出てしまいます。iframeは縦横サイズが固定だからです。

そこで、JavaScriptで魔法を書けます。jQueryを使用している前提で書くとこうなります。

$(function() {
    $('iframe[m\\:id="common.HEADER_HERE"]').load(
        function() {
            $(this).attr('frameborder', "0")
                .attr('scrolling', "no")
                .attr('width', "100%")
                .css('height', this.contentWindow.document.documentElement.scrollHeight + "px");
    });
});

これでロードされた際に、縦横サイズが画面にピッタリフィットするようになります。これで大体の場合OKです。

ただし、chromeのみ、この方法では上手く行きません。fileスキーマでアクセスした時、iframe内のDOMにアクセスすることが制限されてしまうためです。起動オプションを都度付けることが出来るなら、次の起動オプションを付けて起動することで制限を回避することが出来ます。

–allow-file-access-from-files

あるいは、簡易的なWebサーバーを立ててしまって、httpプロトコルでアクセスする方法もあります。

iframeタグのsrc属性をそのままinsertプロセッサーのpath属性にしてしまう

この方法で、common.HEADER_HERE, common.SIDEBAR_HERE, common.FOOTER_HEREなどの部品を都度、default.mayaaの中に登録しても良いのですが、更に前回紹介したテクニックを使用して、iframeタグのsrc属性のパスをそのままinsertプロセッサーのpath属性に突っ込むm:idを作ってしまいましょう。これさえあればどんなページのm:idの追加することなくコンポーネント化できます。

事前に以下のような関数を作っておきましょう。

function getDirectory(path) {
    return path.subatring(0, path.lastIndexOf("/"));
}
<e:insert m:id='common.IMPORT_HERE'
        path='${getDirectory(Packages.org.seasar.mayaa.impl.engine.EngineUtil.sourcePath) + $p("src")}' />

便利だよ\(^o^)/

ただし、このテクニックは、TemplateSuffixに"/"があるケースでは、上手く動かない時があります。はじめから、TemplateSuffixに"/"を使うテクニック
は使わないようにしましょう。

参考

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

これは Mayaa Advent Calendar 2015 の5日目です。昨日はMayaaでWebサイトのビューを実装するときに押さえておくべき基本テクニック2 – m:idの実装における注意点」でした。

ふう、今日で5日目ですか。25日書き続けるのが不安になってきました。でも、おかげさまで、いつもよりも多くの方に読んでいただいているようです。出来る限り途切れさせないように頑張ります。

今日は何を書きましょうかね。困ったときは過去記事の焼き直し...全く同じでも仕方がないですが、ここに書いていることでまだ書いていないことを今日は書きます。

いしがみメソッド3. m:idを減らすテクニック

mayaaファイルはxmlなので、サイズが大きくなると、XMLエディタが重くなったりします。なので、なるべくなら、m:idは減らしたいと思ってきます。

1.m:idをパラメータ対応にする

m:idを減らすテクニックとして、同じようなメソッドをパラメータ化すればまとめられることが良く有ります。

そんな時は、こんな関数を作りましょう。

function $p(name, defaultValue) {
    var attr = originalNode.getAttribute(
        Packages.org.seasar.mayaa.impl.engine.specification.SpecificationUtil.createQName(name)
    );
    if (attr != null) return attr.value;
    attr = originalNode.getAttribute(
        Packages.org.seasar.mayaa.impl.engine.specification.SpecificationUtil.createQName(originalNode.getDefaultNamespaceURI(), name)
    );
    return defaultValue;
}

使い方はこんな感じです。例えば
一昨日書いたforプロセッサーの件は

<m:for m:id="LOOP_ITEM" init="${var loopItemIndex = 0;}" test="${loopItemIndex &gt; items.size() &amp;&amp; loopItemIndex &gt; toInt($p('max'), 999999)}" max="999999">
  <m:echo>
    <m:doBody />
  </m:echo>
</m:for>

事前に次の関数をload関数で読み込む外部jsファイルなどで読み込んでおいたとします。

function toInt(s, default) {
  try {
    return java.lang.Integer.parseInt(s);
  } catch (e) {
    return default;
  }
}

使うときはこんな感じに出来ます。

<div m:id="LOOP_ITEM" m:max="10">
  ...
</div>

このようにすることで、最初の10件のみを表示することができます。

この他にも、m:ifプロセッサーに対して常に条件を逆にするパラメータを作って、逆条件のためのm:id発行を減らすこともできます。(僕はそのためにプロセッサーを作ったりします。プロセッサーの独自作成については、後日説明します。)

PathAdjusterを効果的に使う

PathAdjusterは、画像などの相対パスを自動調整して、サーバー実行時に絶対パスに変換する機能です。本来はjpgなどの画像やjsファイルなどに使うものですが、独自のロジックを追加して、リンク先のURLをいい具合に変換すると、単なるリンクではわざわざm:idを発行しなくても良くなることが有ります。

公式マニュアル - 5-9. パス自動調整の設定 を参照してパス自動調整機能をカスタマイズしましょう。

<?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>
    <pathAdjuster class="jp.example.MyPathAdjuster">
        <parameter name="enabled" value="true"/>
        <parameter name="force" value="false"/><!-- since 1.1.13 -->
    </pathAdjuster>
</provider>

そして、jp.example.MyPathAdjuster を作成しておきます。

package jp.example;
import org.seasar.mayaa.impl.builder.PathAdjusterImpl;
public class MyPathAdjuster extends PathAdjusterImpl {
  @Override
  public String adjustRelativePath(String base, String path) {
    if (path.endsWith(".html")) {
      return MyConst.SITE_ROOT_URL + "/" + path;
    }
    return super(base, path);
  }
}

実際のシステムでは色々なパス階層があると思うので、色々工夫してかけると思います。

参考PathAdjuster のカスタマイズ - marcie001のメモ

以前はPathAdjusterは、この他に、画像のタイムスタンプを出力してキャッシュに使うこともできます。。。などと以前は言っていましたが、ファイルのタイムスタンプをリアルタイムに検索するのも少なからず負荷がかかりますし、最近はcloudfrontなどのCDNを使うことも多かったりします。画像自体別サーバーに置くかもしれません。その辺り、サービスの規模や正確に合わせてください。そんな時も、PathAdjusterがプログラマブルであることが役に立つと思います。

まとめ

今回は、m:idを減らすテクニックを紹介しました。ただし、減らし過ぎにも注意してください。あくまで、デザイナーへの使いやすさを前提にしてください。

あまりやり過ぎると、結局混乱を招いてしまいます。こういう暗黙なことをやるときは、事前に説明しておきましょう。

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
のようなものは使わず、必要最小限の機能で賄う戦略を取ることが結局効率が良いです。

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