レイアウト側の属性を制御する

Mayaaのレイアウト共有機能では、タグ単位での置き換えは可能だけど、レイアウト側のタグの属性のみを置き換えることは普通の方法では出来ないようだ。


コンポーネントのinsertの場合とは違い、extends時にbindingで変数を渡すことができない。その代わりにpageスコープを使うことで、個別のページ側で指定した値をレイアウト側で使用する方法を考えた。


どうしてもこれが正解とは思えないんだけど、一応うまく行ったので公表。あとできるできないとか断言口調だけど、調査が足りないだけのところもあるかも……

背景

mayaaのレイアウト共有機能を使えば、(tilesのように)ヘッダやフッタなどを共有部品をひとまとめにして共有する事が出来る。形としては、レイアウト用のページを一枚用意してその中でページ毎に切り替わる部分を指定し、個々のページではその切り替わる部分を定義する形で使用する。(Mayaa公式サイトに分かり易い説明がある。)


ページによってtitleタグの中身を変えたい場合等も、挿入箇所および挿入内容をそれぞれ複数個指定することで対応できる。


m:doRenderとm:insertを使ったこの方法は、タグ単位での置き換え(というか挿入)には使用出来るけど、外側のタグ(など)の属性のみを変更するのには使えないようだ。(をinsert対象にすると、bodyタグ全体が置き換わってしまう。)


ページ内部に他のページをinsertする場合には、insertされる側のページ(コンポーネント)に変数を渡す事が出来る。渡された変数はコンポーネント側でbindingスコープとして参照する事が出来る(コンポーネントに対して独立したスコープで変数を渡せるのはMayaaの素晴らしいところの一つだ。) しかし、extendsを使う際には、この方法を取る事が出来ない。

方針

いろいろ調べた結果、以下の事が分かった。

  • requestスコープやpageスコープはレイアウト側と個別ページ(レイアウトを使う側のページ)で共有されている
  • レイアウト側よりも個別ページの方が先にレンダリングされる(2008/6/15訂正: コメント欄のsugaさんの指摘の通り、「レイアウト側よりも個別ページの方が先にbeforeRender が実行される」です。詳しくは公式サイトのドキュメントに図解されています。)

そこで、個別ページ側のm:beforeRenderでpageスコープに変数をセットし、その変数をレイアウトのレンダリング時に参照するようにする。

公式ページのサンプルをベースにして、bodyタグのclass属性を個別ページ側から変更するサンプルを作ってみた。(ページによって背景色を変えたい事って結構あるよね)


レイアウト側html(layout.html)

<html xmlns:m="http://mayaa.seasar.org">
<head>
<style type="text/css">
  body.yellow-page {background:yellow;}
  body.red-page {background:red;}
</style>
</head>
<body m:id="layoutBody" class="yellow-page">
    <h1>Hello</h1>

    <div m:id="contentPosition">Dummy content</div>
</body>
</html>

デフォルトでは背景が黄色になる。これを置き換えられるようにする。


レイアウト側mayaa(layout.mayaa)

<?xml version="1.0" encoding="UTF-8"?>
<m:mayaa xmlns:m="http://mayaa.seasar.org">

    <m:echo m:id="layoutBody">
        <m:if test="${bodyClass != null}">
            <m:attribute name="class" value="${bodyClass}"/>
        </m:if>
        <m:doBody/>
    </m:echo>

    <m:insert m:id="contentPosition" name="contentBody" />

</m:mayaa>

(pageスコープ等で)変数bodyClassが定義されている場合だけ、bodyタグのclass属性がbodyClass変数の内容で置き換えられる。


個別ページ側html(hello.html)

<html xmlns:m="http://mayaa.seasar.org">
<body>
    <h1>DummyTitleHello</h1>

    <div m:id="content">Hello Mayaa!</div>
</body>
</html>

個別ページ側mayaa(hello.mayaa)

<?xml version="1.0" encoding="UTF-8"?>
<m:mayaa xmlns:m="http://mayaa.seasar.org"
    extends="/layout.html">

    <m:beforeRender>
    	page.bodyClass='red-page';
    </m:beforeRender>

    <m:doRender m:id="content" name="contentBody" />
    
</m:mayaa>

m:beforeRenderでpageスコープの変数bodyClassに'red-page'をセットする。
(「page.bodyClass='red-page'」でなく「var bodyClass='red-page'」でもOK)


以下が、hello.htmlにアクセスしてレンダリングした結果

<html>
<head>
<style type="text/css">
  body.yellow-page {background:yellow;}
  body.red-page {background:red;}
</style>
</head>
<body class="red-page">
    <h1>Hello</h1>

    Hello Mayaa!
</body>
</html>

ちゃんと背景が赤に置き換わってます。


Faceletsの場合

Faceletsの場合、を使ってレイアウト共有をおこなう。内でを記述することで個別ページからレイアウト側にパラメータを渡せる。


レイアウト側(layout.xhtml)

<?xml version="1.0" encoding="Shift_JIS" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
          "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns:ui="http://java.sun.com/jsf/facelets">
<head>
<style type="text/css">
  body.yellow-page {background:yellow;}
  body.red-page {background:red;}
</style>
</head>
<body class="${bodyClass!=null ? bodyClass : 'yellow-page'}">
    <h1>Hello</h1>

    <ui:insert name="contentPosition" />
</body>
</html>


個別ページ側(hello.xhtml)

<?xml version="1.0" encoding="Shift_JIS" ?>
<!DOCTYPE facelet-taglib PUBLIC
  "-//Sun Microsystems, Inc.//DTD Facelet Taglib 1.0//EN"
  "facelet-taglib_1_0.dtd">
<html xmlns:ui="http://java.sun.com/jsf/facelets">
<body>
    <h1>DummyTitleHello</h1>

    <ui:composition template="layout.xhtml">
        <ui:param name="bodyClass" value="red-page" />
        <ui:define name="contentPosition">Hello Facelets!</ui:define>
    </ui:composition>
</body>
</html>

ui:paramを使って変数bodyClassを定義し、レイアウト側に渡している。


実行結果

<?xml version="1.0" encoding="Shift_JIS" ?>
<html>
<head>
<style type="text/css"><!--

  body.yellow-page {background:yellow;}
  body.red-page {background:red;}

--></style>
</head>
<body class="red-page">
    <h1>Hello</h1>Hello Facelets!
</body>
</html>

感想など

  • 今回の方法は、m:beforeRenderの呼び出し順序に依存するというところが少しダーティーな感じ
    • やっぱりレイアウト側のスコープが外側というのは少し使いにくいかも。(この辺で書いたのは今回のような状況の事でした。)
  • Faceletsのui:composition/ui:insertはWOComponentContentっぽく使えてとても幸せ。
    • この辺りにWebObjectsTapestry→Faceletsという系譜を感じる。
    • これはもっと細かい粒度でテンプレートを分割した時に効いてくる。入力部品ごとに部品コンポーネントを作って、かつその部品コンポーネントに共通レイアウトを使いたいときとか。
    • Mayaaコンポーネント側にextends書いたらどうなるのかな。何もおこらないみたいだけど……