Category Archives: Mayaa

Mayaaに慣れた僕がJSF2.x Faceletsを試してみる

これは Mayaa Advent Calendar 2015 の19日目です。昨日は「Mayaaに慣れた僕がThymeleafを試してみる」でした。

さて、一昨日、昨日と、MayaaのライバルとみなされるJava HTMLテンプレート、Mixer2とThymeleafを試してみました。思えば2年前、JJUGのLT大会で、テンプレートエンジンネタでぶつけてきたときを思い出します。

1 yusuke テンプレートエンジンを制する者が世界を制す
2 nabedge 進撃のMixer2 – 立体機動テンプレートエンジン
3 eiryu Thymeleafでハマったこと
4 susumuis from JSP to Design-friendly Template Endine / JSPからMayaaに移行した本当の話
5 smilelx_xl(+susumuis) テンプレートエンジンを利用したプログラマーとデザイナーの共同開発で大切なこと(仮)
:
:

実際にためしてみると、あながち競合する技術ではないのかなと印象を持ちました。

そういえば、忘れてました!Java EE本家、JSFのFacelets書式はXHTML形式なので、これも、検討対象となりそうです。今日はこれで、デザインーとの協業が可能か、いしがみメソッドは適用可能かを、見てみたいと思います。

Java EEといったら、NetBeans

僕は普段、Eclipseを使い、Androidの時はAndroid Studioを使いますが、Java EEの時はNetBeansを使います。

詳しくは、Introduction to JavaServer Faces 2.x というドキュメントが分かりやすいです。今回はもっとシンプルに作ってみます。

Hello World

NetBeansで[新規プロジェクト] - [Java Web]を選び、フレームワークに「JavaSever Faces」を選んで、ライブラリにJSF2.2を選べば雛形が作られます。

WEB-INF/index.xhtmlを次のように作成します。

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://xmlns.jcp.org/jsf/html">
    <h:head>
        <title>Facelet Title</title>
    </h:head>
    <h:body>
        <h:outputText value="#{helloBean.hello}"/>
    </h:body>
</html>

タグ名にh:が付いている時点で、Mayaa、Mixer2、Thymeleafとは毛色が違います。

デザインエディットするときはサーバーを立ち上げるか、開発環境のエディタを使う必要があります。デザイナーが作成したHTMLを元にプログラマーが埋め込む方式であれば、いいかもしれません。

ループはどうするか?

ループするときはこんな感じになります。

例えばこんな感じのItemクラスを作って

public class Item {    
    private String name;
    public Item(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
}

管理ビーンのプロパティにリストとして登録してみます。

@Named(value = "helloBean")
@Dependent
public class HelloBean {
    private String hello = "Hello World";
    private List<Item> list = new ArrayList<>();

    public HelloBean() {
        list.add(new Item("りんご"));
        list.add(new Item("ばなな"));
        list.add(new Item("みかん"));
    }

    public String getHello() {
        return hello;
    }    
    public List<Item> getList() {
        return list;
    }
}

テンプレートは

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://xmlns.jcp.org/jsf/html"
      xmlns:ui="http://xmlns.jcp.org/jsf/facelets">
    <h:head>
        <title>Facelet Title</title>
    </h:head>
    <h:body>
        <h1>sample</h1>
        <h:outputText value="#{helloBean.hello}"/>
        <ui:repeat var="row" value="#{helloBean.list}">
            <div><h:outputText value="#{row.name}"/></div>
        </ui:repeat>
    </h:body>
</html>

結果

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"><head id="j_idt2">
        <title>Facelet Title</title></head><body>
        <h1>sample</h1>Hello World
            <div>りんご</div>
            <div>ばなな</div>
            <div>みかん</div></body>
</html>

ふむふむ。大体分かってきました。
JSFは基本的にはPrimeFacesを使ったコンポーネント指向の開発しかできないイメージでしたが、テンプレート主体の開発もできそうです。

いしがみメソッド検証

JSFをテンプレート主義で使った想定で検証してみます。

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

Mayaaで言うところのm:id、Thymeleafに言うところのth:ifなどの独自属性は、JSFでは独自タグになります。

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

-出力: h:outputText
-分岐: c:if
-繰り返し: ui-repeat
-属性: pt:属性名

4種類の実装方法はルールに従う

このルールに対応するとしたら、「使うタグはある程度限定する」といったところになると思います。

h:inputTextのようなコンポーネントは便利ですが、どこまで使うかはプロジェクトごとのポリシーとして、決めてから使うのが良いと思います。

LOOP系のm:idはindexには長い名前を使い、maxを十分に

ui:repeatに変数名を取れるので、ここではiとかjとかは使わず適度な長さの名前をつければ良いと思います。

Mayaaで長い名前をつけるべきとしたのは、名前がMayaaファイル内に隠蔽されているためなので、Thymeleaf同様、表に出てくる場合はそれほど神経質になる必要はありません。

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

エスケープしないテキストは escape="false" という属性をつけるだけです。

tableタグだけは特別に扱う

uiなどの独自タグが使えるので、Mayaa、Mixer2のようなHTMLにこだわったテンプレートエンジンほどここは問題にならないと思います。

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

Thymeleafと同様、式を直接書けてしまうので、m:idを減らすためのパラメータという要求は発生しないと思います。

PathAdjusterを効果的に使う

"/"で始まっていないパスは相対パスと想定されて動作するようです。その時の調整ロジックをフックできるかについては、すみません、よくわかりませんでした。

ヘッダー・フッター・共通部品はiframeタグをうまく使う

直接テンプレートを開くことはあまり得意ではなさそうなので、これは諦めるしかなさそうです。

テンプレート上のコメントは、ソース表示時に見えないようにする

<ui:remove> </ui:remove>
を使うのが良いそうです。

単語はなるべくテンプレート側に書く

これはできそうです。

nekoHTMLパーサーをいじる

もちろん、そんな必要はありません。

まとめ

JSFというと、結構複雑なコンポーネント指向の仕組みだと思いましたが、テンプレート指向でもけっこう行けることがわかりました。

プロジェクト内で、使うタグのルールを定めることで、デザイナーとの連携も可能かもしれません。

Mayaaに慣れた僕がThymeleafを試してみる

これは Mayaa Advent Calendar 2015 の18日目です。昨日は「Mayaaに慣れた僕がMixer2を試してみる」でした。

毎日ブログを書き続けて18日目です。

始めの頃は辛かったですが、今では一つの記事を1,2時間で書けるようになりました。書き溜めすれば良いのではないかと思われるかもしれませんが、案外、書き溜めはできないもので、毎日ネタを考えて書いています。

Mayaaアドベントカレンダーはまだ参加者を募集しています!

さて、テンプレートエンジン特集第2回はThymeleafです

前回のMixer2は、Mayaaと似たようなエンジンだろうと思って触ってみると、大分正確が異なる印象でした。

Mayaaよりも原始的な「テンプレートエンジン」として特化していて、Mixer2と比較するとMayaaは「ビューフレームワーク」と読んだほうが良いかもしれないと思いました。

さて、今日は海外でも人気があるThymeleafを試してみます。

Thymeleafとは

Thymeleaf is a Java library. It is a template engine capable of processing and generating HTML, XML, JavaScript, CSS and text, and can work both in web and non-web environments. It is better suited for serving the view layer of web applications, but it can process files in many formats, even in offline environments.
(引用:公式サイト)

Thymeleafは、
* Javaライブラリ
* テンプレートエンジン
* HTML, XML, JavaScript, CSS, Textを生成・処理できる
* Webだけでなく、Web以外でも動く
* Webアプリケーションのビューレイヤーとして提供するのに適している
* 様々なフォーマットのファイルを扱うことができ、オフライン環境のファイルすらも扱うことができる

という特徴をもったライブラリです。

日本語訳されたチュートリアルもあります。

テンプレートをXHTMLで書くという点がMayaaと共通しています。Mayaaと異なる点は、mayaaファイルというような独自の定義ファイルは存在せず、また、Mixer2のようにJava側でXMLツリーを操作するわけでもありません。

代わりに

<p th:text="#{home.welcome}">Welcome to our grocery store!</p>

のように書くようです。mayaaファイルの内容をそのままテンプレートに書いてしまったような感じです。

さっそく、HelloWorldしてみましょう。

HelloWorldしてみる

Thymeleafはリリース版の2.x系と、Beta版の3.x系があります。ここでは2を使いましょう。

ダウンロードはここからできます。Mavenと単体配布の両方があるようです。

また、SpringBootを使うならSTSのウィザードから最初に選べたりします!

今回は単体でダウンロードしました。

まず、チュートリアルに従って、アプリケーションクラスを作ってみます。Mixer2と同じでエンジンのシングルトン化をします。

public class HelloApplication {
    private static TemplateEngine templateEngine;
    static {
        initializeTemplateEngine();
    }
    public static TemplateEngine getTemplateEngine() {
        return templateEngine;
    }
    private static void initializeTemplateEngine() {

        ServletContextTemplateResolver templateResolver = 
            new ServletContextTemplateResolver();
        // XHTML is the default mode, but we set it anyway for better understanding of code
        templateResolver.setTemplateMode("XHTML");
        // This will convert "home" to "/WEB-INF/templates/home.html"
        templateResolver.setPrefix("/WEB-INF/templates/");
        templateResolver.setSuffix(".html");
        // Template cache TTL=1h. If not set, entries would be cached until expelled by LRU
        templateResolver.setCacheTTLMs(3600000L);

        templateEngine = new TemplateEngine();
        templateEngine.setTemplateResolver(templateResolver);

    }   
}

テンプレートを /WEB-INF/templates/hello.html として作成します

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

Servletを書いてみます

@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        TemplateEngine templateEngine = HelloApplication.getTemplateEngine();
        WebContext ctx = new WebContext(request, response, getServletContext(), request.getLocale());
        ctx.setVariable("title", "タイトルThymeleaf");
        ctx.setVariable("message", "こんにちはタイムリーフ!");
        response.setCharacterEncoding("UTF-8");
        response.setContentType("text/html");
        templateEngine.process("hello", ctx, response.getWriter());
    }
}

結果

こんにちはタイムリーフ!

結果ソース

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>タイトルThymeleaf</title>
</head>
<body>
<span>こんにちはタイムリーフ!</span>
</body>
</html>

引き続き、いしがみメソッドの確認を行いましょう。

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

m:idのような識別子ではなく、直接テンプレートに変数を埋め込んでいきます。
例えば、上記例でのmessageなどの変数を大文字にすることはできるかもしれません。

ただ、そうすると、オブジェクトのプロパティを全部大文字にしなければならなくなるので適切な方法ではないでしょう。

もっとも、使う属性が"th:"から始まる独自のものなので、デザイナーと名前空間がぶつかることはありません。

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

Thymeleafが提供している制御方法は次のものがあります。

  • th:text
  • th:each
  • th:if, th:unless
  • th:attr
  • etc, ...

概ね必要なものはそろっています。デザイナーが属性名でどんな動きをするのか理解する必要がありますが、うまく伝達できれば問題なさそうです。

ただ、これら、自由な式が書けてしまうので、m:idのように一覧化できず、デザイナーは何を書いたら良いのか迷うと思われます。

事前に何を出すにはどうしたらよいかの手引書を共有するか、デザイナーに、ある程度の制御書式とデータ構造を教育する必要があります。

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

あまり問題にならないでしょう。

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

エスケープしないテキストは th:utext で出力します。

tableタグだけは特別に扱う

m:idのような識別子を必要としないため、都度適切な属性を書けば良さそうなので、ここはThymeleafでは問題にならなそうです。

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

式を直接書けてしまうので、もはや、パラメータという概念がなさそうです。ただ、メソッドをコールするといった複雑なことはできなそうなので、案外むずかしいかもしれません。

PathAdjusterを効果的に使う

Thymeleafではth:href属性によって、パスを制御します。相対パスはIWebContextに格納したオブジェクトから取得してくるようですが、MayaaやMixer2ほど柔軟で自動的な制御はしてくれないようです。

ヘッダー・フッター・共通部品はiframeタグをうまく使う

「テンプレートフラグメント」という仕組みを使います。
th:fragment, th:includeなどを使います。

テンプレート側は、Mayaaのときと同じようにiframeで制御しても良いかもしれませんが、これも、自動的にうまいことやってもらうというより、自分でローカル用と、サーバー用の出力を両方書くようなスタイルになるのでしょう。

テンプレート上のコメントは、ソース表示時に見えないようにする

Thymeleafでは

<!--/*
*/-->

という書式を使うようです。

単語はなるべくテンプレート側に書く

これはできそうです。

また、多言語化やメッセージの書き換えの仕組みとして、th:text="#{キー}"という構文でメッセージを多言語・テーブル化することができます。

Thymeleafではある程度テンプレートにロジックが入ってしまいますので、メッセージのテーブルを活用してテンプレートはあまりバリエーションを作らないのが良いスタイルかもしれません。

nekoHTMLパーサーをいじる

HTML5対応しているので、そんな必要はありません。

まとめ

Thymeleafは、テンプレートとmayaaファイルのように、表示と制御を完全に分離することなく、テンプレートにある程度の制御情報を残すことを許すことで、非常にシンプルで実用的な設計になっている印象を受けました。

デザイナーが直接テンプレートを触ることはないが、かといって、テンプレートにロジックをゴリゴリ書きたくないといったとき、妥当な選択肢だと思います。

デザイナーがある程度システムのわかる人間の場合は、ブラックボックス部分が少ないことがかえって円滑に連携できるかもしれません。

とはいえ、テンプレートの編集にかなりのシステムの知識が要求されてくるので、「デザイナーが自由にテンプレートをいじる」という世界には向かないようです。

Mayaaに慣れた僕がMixer2を試してみる

これは Mayaa Advent Calendar 2015 の17日目です。昨日は「Mayaaで独自Processorを作ろう!」でした。

寒くなってきました。いよいよクリスマスまでカウントダウンですね!

毎日書けるのか心配だったこのブログも残すところあと1桁となり、終わってしまうことがちょっとさみしく思います。とはいえ、一日たりとも中断せぬよう、最後まで引き締めて行きます。

なお、Mayaaアドベントカレンダーはまだ参加者を募集しています!

Mayaaに慣れた僕がシリーズ開始

さて、今日から、Mayaa以外のテンプレートエンジンを試すシリーズを始めたいと思います。

Mayaa以外にもデザイナーとプログラマーの協業をうたったテンプレートエンジンは多数あります。それらを実際に試してみて、3日目から紹介した「いしがみメソッド」を適用できるか検証していきます。

第一回はMayaaの影響を受けて開発されたと言われる、Mixer2を試してみたいと思います。

Mixer2とは

Mixer2

Mixer2はJavaアプリケーション用テンプレートエンジンです。テンプレートはXHTMLで書きます。 100% pureな、XHTMLとCSSです。(html5もXML構文で書けば使えます)
と掲げられています。

テンプレートをXHTMLで書くという点がMayaaと共通しています。Mayaaと異なる点は、mayaaファイルというような独自の定義ファイルは存在せず、すべてJavaで制御することです。すべてJavaにすることでフロントのJUnitテストを可能であることをうたっています。

HelloWorldしてみる

Mixer2はMavenを使うことを推奨しているようです。jarファイル単体の配布はここでしているようですが、古いバージョンで止まっています。

POMに以下を追加しましょう。

<dependencies>
  <dependency>
    <groupId>org.mixer2</groupId>
    <artifactId>mixer2</artifactId>
    <version>1.3.2</version>
  </dependency>
</dependencies>

Mixer2 - TIPSによると、Mixer2EngineはSingletonとして扱うべきのようですので、はじめからSingletonにしておきます。

public class Mixer2EngineSingleton {
    private static Mixer2Engine m2e = new Mixer2Engine();
    public static Mixer2Engine get() {
        return m2e;
    }
}

テンプレートファイルを/WEB-INF/view/hello.xhtmlにおいてみます。

<!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>
<span id="HELLO_HERE">ハローワールド</span>
</body>
</html>

mixer2はm:idのような独自の名前空間ではなく、id属性を使うことを推奨しています。id属性だと複数使い回すことができないので、m:idのような使い方をするなら、class属性を使うことになるかもしれません。

また、XHTMLの文法上、titleタグにid属性を付けることは正しくないので、この部分も悩ましいところではあります。

そして、以下のようなServletを書けば、画面に「こんにちはMixer2」と表示されます。

@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        try {
            Mixer2Engine m2e = Mixer2EngineSingleton.get();
            Html html = m2e.loadHtmlTemplate(getServletContext().getResourceAsStream("/WEB-INF/view/hello.xhtml"));
            html.getById("HELLO_HERE", Span.class).replaceInner("こんにちはMixer2");
            response.setCharacterEncoding("UTF-8");
            response.setContentType("text/html");
            response.getWriter().print(m2e.saveToString(html));
        } catch (TagTypeUnmatchException e) {
            e.printStackTrace(response.getWriter());
        }
    }
}

Mayaaと違って、Servletと疎結合になっています。そのため、文字コードやContentTypeなどは自分で指定してあげる必要があります。

Mixer2でいしがみメソッドを適用する

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

これは、プログラム制御用のid, class属性を常に大文字にすることで対応できます。

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

同上です。ただし、id属性は、テンプレート内で一度しか使えないので、class属性を使うことが多くなると思います。

class属性を使った場合、getDescendantsで検索をするのですが、常に全DOMを走破するとオーバーヘッドが大きいかもしれません。

(Mayaaの場合、DOMを使わずSAXのように先頭からトラバースしていく方式なので、この問題がない一方xpath指定の効率が非常に悪くなっています。)

4種類の実装方法はルールに従う

これらは、毎回タグ操作をゴリゴリ書くのではなく、ある程度ユーティリティ化することによって、定型化することができると思います。

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

Mixer2の場合はプログラミングをJava内のスコープで行うのでこのような問題はありません。

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

replaceInnerメソッドは常にエスケープしてしまうみたいなので、エスケープして欲しくない場合は別の方法が必要になりそうです。

tableタグだけは特別に扱う

これは、テンプレートエンジンというより、HTMLの仕様上の問題点なので、Mixer2でも同じ課題があります。

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

タグの属性は getOtherAttributes() で取得できます。こんな感じで取得することができます。

span.getOtherAttributes().get(new QName("number"))

PathAdjusterを効果的に使う

Mixer2にもPathAdjusterがあります。仕様はMayaaのそれに似ているようです。

ヘッダー・フッター・共通部品はiframeタグをうまく使う

この部分は部分マーシャルを使ってうまく行くのではないかと思います。

テンプレート上のコメントは、ソース表示時に見えないようにする

Mixer2ではそもそもコメントが全部消えます!

単語はなるべくテンプレート側に書く

これは問題ないように思います。

nekoHTMLパーサーをいじる

初めからHTML5対応です。

まとめ

触ってみた感想として、Mayaaよりも、原始的な単機能のテンプレートエンジンという感じがしました。そのまま使っても、デザイナーと協業まで持っていくのは厳しいと思います。

また、タグの構造に対して厳密なコードを書く必要があるので、出来上がったテンプレートのタグの構造を後からプログラムの変更なしに変更するということには制約が多いように思います。

Mixer2を更にベースにして、独自フレームワークを構築することが必要になってくると思われます。

Mixer2の良い点は逆に、Servletと結合がないために単体で動いてしまうことです。サーバーサイド側でHTMLを整形するならJavaとの相性が非常によく、型安全でテスタブルにコーディングできるのは嬉しいです。

全体のテンプレートエンジンとして使うより、システムにmixer2を読み込んでおいて、HTMLを部分的に整形したり、Mayaaと組み合わせて部品をMixer2で作るといったこともというような合わせ技も検討の余地があるかもしれません。

思ったよりもMayaaと競合しない技術ではないかと思いました。