Mixer2 で作る 
カスタムテンプレートエンジン 
第八回#渋谷java 
Jun Futagawa (@jfut) 
Mixer2 で作るカスタムテンプレートエンジン, September 20, 2014 1
自己紹介 
 Jun Futagawa 
 有限会社インテグシステム 
 Twitter: @jfut 
 好きなこと: Linux サーバー・ネットワーク構築 
 今好きなフレームワーク: Cubby, S2JDBC, Mixer2 
Mixer2 で作るカスタムテンプレートエンジン, September 20, 2014 2
今日のお話 
 Mixer2 で作るカスタムテンプレートエンジン 
 素材: Mixer2 テンプレートエンジン 
 http://mixer2.org/ 
 作者: @nabedge さん 
 テンプレートファイルはHTML ファイル 
 HTML の構造をマッピングしたJava のクラスインスタンスに変換 
 HTML タグに対応する個別のJava クラス型を持つ 
 Java の世界でタグや値の合成操作 
 テンプレート記述言語を覚えなくて良い 
new Html(); 
new Body(); 
new Div(); new P() 
Html 
Head Body 
Div P 
Mixer2 で作るカスタムテンプレートエンジン, September 20, 2014 3
Mixer2: 利用例 
 HTML ファイルをMixer2 でインスタンス化、値を書き換えて出力 
<!DOCTYPE html> 
<html xmlns="http://www.w3.org/1999/xhtml"> 
... <head /> 省略... 
<body> 
<article> 
<section> 
<h1>headline</h1> 
<p id="HERE.msg">here comes hello message</p> 
<span class="DEBUG">dummy span</span> 
<p class="DEBUG">dummy p</p> 
</section> 
</article> 
</body> 
<html> 
Mixer2Engine m2e = new Mixer2Engine(); 
Html html = m2e.loadHtmlTemplate(new File("hello.html")); 
H1 h1 = html.getDescendants(H1.class).get(0); 
h1.unsetContent(); 
h1.getContent().add("New Headline"); 
P p = html.getById("HERE.msg"); 
p.setId("msg"); 
p.unsetContent(); 
p.getContent().add("Hello World!"); 
// or html.removeDescendants("DEBUG"); 
List<AbstractJaxb> debugTagList = html.getDescendants("DEBUG"); 
for (AbstractJaxb abstractJaxb : debugTagList) { 
html.remove(abstractJaxb); 
} 
// タグで取得 
// コンテンツ削除 
// コンテンツ追加 
// id 属性で取得 
// id 属性の値を設定 
// コンテンツ削除 
// コンテンツ追加 
// ↓class 属性で取得 
// タグごと削除 
hello.html 
特別な記法のないHTML Java コードの例 
... 省略... 
<article> 
出力: m2e.saveToString(html); 
<section> 
<h1>New Headline</h1> 
<p id="msg">Hello World!</p> 
</section> 
</article> 
... 省略... 
DOM 操作ではないため 
判りやすい 
(ちょっと冗長ではある) 
* AbstractJaxb はMixer2 が持つHTML タグ用クラスのスーパークラス 
Mixer2 で作るカスタムテンプレートエンジン, September 20, 2014 4
カスタムテンプレートエンジン 
 なぜ? 
 Mixer2 はビューのHTML を全部Java で操作できる 
 でも、汎用部分を毎回Java で操作するのはさすがに手間 
 レイアウト・共通部品埋め込み 
 単なるリクエスト・セッションの値の出力 
 そこで 
 共通処理の記述はテンプレートファイルへ 
 <div id="機能名1">dummy div</div> 
 <p class="機能名2">dummy p</p> 
 Mixer2 でインスタンス化したhtml から機能名で該当タグを検索 
 AbstractJaxb tag = html.getById("機能名1"); 
 List<AbstractJaxb> tagList = html.getDescendants("機能名2"); 
 機能名に応じた処理を実行するヘルパークラスでタグや値を操作 
 ヘルパークラス群= カスタムテンプレートエンジン 
Mixer2 で作るカスタムテンプレートエンジン, September 20, 2014 5
Class ヘルパークラスの実装イメージ 
 テンプレートファイル 
<article> 
<section> 
<h1>headline</h1> 
<p id=" HERE.msg">here comes hello message</p> 
<span class="M_FUNC">dummy span</span> 
<p class=" M_FUNC ">dummy p</p> 
</section> 
</article> 
 ヘルパークラス実装 
String name = "M_FUNC"; 
... 
public <T extends AbstractJaxb> T replace(String path, String templatePath, T parent, HttpServletRequest request) { 
List<AbstractJaxb> abstractJaxbList = parent.getDescendants(name); 
for (AbstractJaxb abstractJaxb : abstractJaxbList) { 
... 機能に応じたインタスタンス操作... 
} 
return parent; 
} 
Mixer2 で作るカスタムテンプレートエンジン, September 20, 2014 6
ヘルパークラスのオプション値 
 機能によってはオプション値が必要 
 例: 値や条件式の指定 
 オプション値はHTML5 のdata-* 属性を利用すると便利 
 独自タグなし(独自data-* 属性は使う) 
 Class ヘルパーM_OUT 用のHTML テンプレート記述例: 
 .html: <span class="M_OUT" data-value="${ sessionScope.name }">dummy.name</span> 
 値の出力記述方法の比較 
 JSTL 例: 
 .jsp: <c:out value="${sessionScope.username }" /> 
 Thymeleaf の例 
 .html: <span th:text="${ session.username }">dummy.name</span> 
 Mayaa 例: 
 .html: <span id="name">dummy.name</span> 
 .mayaa: <m:writem:id="name" value="${ session.username }" /> 
Mixer2 で作るカスタムテンプレートエンジン, September 20, 2014 7
テンプレートエンジン実行例 
 Mixer2 を使用したView の処理時にまとめて実行 
 例: SpringWeb MVC: View クラス内 
 コントローラから対応するView 内で実行 
1. View に対応するテンプレートファイルからHtml インスタンス化(Mixer2) 
2. 事前処理ヘルパークラス群(カスタム) 
3. ページ固有のView 処理(Mixer2) 
4. 事後処理ヘルパークラス群(カスタム) 
処理イメージ 
Html html = m2e.loadHtmlTemplate(new File(templatePath)); 
/** 事前処理用共通ヘルパー群を適用します。*/ 
html = FunctionsHelper.getDefaultPreInstance().replaceAll(path, templatePath, html); 
...ページ固有のレンダリング処理をMixer2 のインスタンス操作で適用します。... 
/** 事後処理用共通ヘルパー群を適用します。*/ 
html = FunctionsHelper.getDefaultPostInstance().replaceAll(path, templatePath, html); 
ヘルパークラス群は事前に 
Map へ登録しておく 
Mixer2 で作るカスタムテンプレートエンジン, September 20, 2014 8
デモ実装 
 エンジン実装例: Spring Web MVC 依存 
 https://github.com/jfut/integ-web-spring-webmvc-mixer2 
 機能 
 M_ACTIVE_PATH 表示パスと同じ時にclass 属性へactive を追加 
 M_APP_NAME pom.xml の<name /> の値を出力 
 M_DEBUG デバッグモード時にデバッグ欄を作成して表示 
 M_DELETE レンダリング時に削除 
 M_EXPORT レイアウト機能 
 M_IF EL 式によるタグの表示・削除 
 M_IF_DEBUG バッグモード時のみ表示 
 M_INCLUDE 別のテンプレートファイルを埋め込む 
 M_LINK リンクアドレスの相対パスを解決 
 M_MESSAGE リソースファイルの値を出力 
 M_OUT リクエスト情報の値を出力、EL 式サポート 
 M_REQUEST_DEBUG リクエスト情報の値をすべて出力 
 M_VERSION pom.xml の<version /> の値を出力 
 利用例: Mixer2 のSpring Boot デモを修正 
 https://github.com/jfut/mixer2-sample/tree/custom-template 
Mixer2 で作るカスタムテンプレートエンジン, September 20, 2014 9
デモ実装利用例 
<span class="badge M_IF M_OUT" 
data-test="${ 0 != sessionScope.cart.getReadOnlyItemList().size() }" 
data-value="${ sessionScope.cart.totalAmount }"> 
dummy.10</span></a></li> 
<li class="M_ACTIVE_PATH" data-path="/"> 
<a class="M_LINK" data-href="../../" href="../../index.html"> 
ホーム</a></li> 
コンテンツファイル: /index.html (ここが起点) 
<div id="M_EXPORT" data-src="/component/layout/layout.html"> 
レイアウトファイル: /component/layout/layout.html 
<div id="M_EXPORT"></div> 
レイアウトファイル: /component/layout/layout.html 
<div class="M_INCLUDE" data-src="/component/layout/footer.html"> 
レイアウトファイル: /component/layout/layout.html 
<div class="M_INCLUDE" 
data-src="/component/layout/header.html"> 
z 
<a class="M_LINK" data-href="../../cart/view" href="../../cart.html">view cart</a> 
レイアウトファイル: /component/layout/layout.html 
<div class="M_INCLUDE" 
data-src="/component/layout/sidebar.html"> 
<span class="M_APP_NAME">dummy.appName</span> 
Version <span class="M_VERSION">x.y.z</span> 
<div class="M_REQUEST_DEBUG">dummy.M_REQUEST_DEBUG</div> 
Mixer2 で作るカスタムテンプレートエンジン, September 20, 2014 10
まとめ 
 Mixer2 
 HTML タグをすべてJava で操作可能 
 テンプレート記述言語を覚えなくて良い 
 レンダリング済みHTML のテストもJava で書ける= テスト簡単 
 参考: @nabedge さんのスライドたくさん: http://www.slideshare.net/nabedge 
 - さらばJSP - JAXBとテンプレートエンジンMixer2 など 
 Mixer2 の上にカスタムテンプレートエンジンが簡単に作れる 
 汎用部分の記述をテンプレートファイル側へ 
 独自のテンプレート記述言語(機能名、data-*属性)を覚えるデメリットはある 
 Mixer2 のインスタンス操作だけ、DOM 操作ツライがない 
Mixer2 で作るカスタムテンプレートエンジン, September 20, 2014 11
Thank You for Your Attention! 
Mixer2 で作るカスタムテンプレートエンジン, September 20, 2014 12
Mixer2 + テスト 
 Spring MVC 連携時のテスト 
@Test 
public void showItem_exists() throws Exception { 
// Mock とRequestMappingHandlerAdapter を使用してリクエストを再現し、ModelAndView を取得 
... 省略... 
request.setMethod("GET"); 
request.setRequestURI("/item/10001"); 
Object handler = handlerMapping.getHandler(request).getHandler(); 
ModelAndView modelAndView = handlerAdapter.handle(request, response, handler); 
// ViewName をテスト 
String viewName = modelAndView.getViewName(); 
assertThat(viewName, is("item")); 
// ビューをレンダリング 
// レンダリング時にビュークラスでテンプレートHTML ファイルをMixer2 でインスタンス化し、タグの書き換え、レスポンスに書き出す 
AbstractMixer2XhtmlView view = 
(AbstractMixer2XhtmlView)mixer2XhtmlViewResolver.resolveViewName(viewName, Locale.getDefault()); 
view.render(modelAndView.getModelMap(), request, response); 
// レンダリングに使用したhtml インスタンスをダイレクトにテスト 
Html renderedHtml = view.getRenderedHtml(); // or view.getHtml(); 
H1 itemNameH1 = renderedHtml.getById("itemName"); // テンプレート: <h1 id="itenName">dummy.header</h1> 
assertThat(itemNameH1.getContent().get(0).toString(), is("Apple")); // レンダリング後: <h1 id="itemName">Apple</h1> 
Mixer2 で作るカスタムテンプレートエンジン, September 20, 2014 13 
} 
* https://github.com/jfut/mixer2-sample/blob/custom-template/mixer2-fruitshop-springboot

Mixer2 で作るカスタムテンプレートエンジン #渋谷java

  • 1.
    Mixer2 で作る カスタムテンプレートエンジン 第八回#渋谷java Jun Futagawa (@jfut) Mixer2 で作るカスタムテンプレートエンジン, September 20, 2014 1
  • 2.
    自己紹介  JunFutagawa  有限会社インテグシステム  Twitter: @jfut  好きなこと: Linux サーバー・ネットワーク構築  今好きなフレームワーク: Cubby, S2JDBC, Mixer2 Mixer2 で作るカスタムテンプレートエンジン, September 20, 2014 2
  • 3.
    今日のお話  Mixer2で作るカスタムテンプレートエンジン  素材: Mixer2 テンプレートエンジン  http://mixer2.org/  作者: @nabedge さん  テンプレートファイルはHTML ファイル  HTML の構造をマッピングしたJava のクラスインスタンスに変換  HTML タグに対応する個別のJava クラス型を持つ  Java の世界でタグや値の合成操作  テンプレート記述言語を覚えなくて良い new Html(); new Body(); new Div(); new P() Html Head Body Div P Mixer2 で作るカスタムテンプレートエンジン, September 20, 2014 3
  • 4.
    Mixer2: 利用例 HTML ファイルをMixer2 でインスタンス化、値を書き換えて出力 <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> ... <head /> 省略... <body> <article> <section> <h1>headline</h1> <p id="HERE.msg">here comes hello message</p> <span class="DEBUG">dummy span</span> <p class="DEBUG">dummy p</p> </section> </article> </body> <html> Mixer2Engine m2e = new Mixer2Engine(); Html html = m2e.loadHtmlTemplate(new File("hello.html")); H1 h1 = html.getDescendants(H1.class).get(0); h1.unsetContent(); h1.getContent().add("New Headline"); P p = html.getById("HERE.msg"); p.setId("msg"); p.unsetContent(); p.getContent().add("Hello World!"); // or html.removeDescendants("DEBUG"); List<AbstractJaxb> debugTagList = html.getDescendants("DEBUG"); for (AbstractJaxb abstractJaxb : debugTagList) { html.remove(abstractJaxb); } // タグで取得 // コンテンツ削除 // コンテンツ追加 // id 属性で取得 // id 属性の値を設定 // コンテンツ削除 // コンテンツ追加 // ↓class 属性で取得 // タグごと削除 hello.html 特別な記法のないHTML Java コードの例 ... 省略... <article> 出力: m2e.saveToString(html); <section> <h1>New Headline</h1> <p id="msg">Hello World!</p> </section> </article> ... 省略... DOM 操作ではないため 判りやすい (ちょっと冗長ではある) * AbstractJaxb はMixer2 が持つHTML タグ用クラスのスーパークラス Mixer2 で作るカスタムテンプレートエンジン, September 20, 2014 4
  • 5.
    カスタムテンプレートエンジン  なぜ?  Mixer2 はビューのHTML を全部Java で操作できる  でも、汎用部分を毎回Java で操作するのはさすがに手間  レイアウト・共通部品埋め込み  単なるリクエスト・セッションの値の出力  そこで  共通処理の記述はテンプレートファイルへ  <div id="機能名1">dummy div</div>  <p class="機能名2">dummy p</p>  Mixer2 でインスタンス化したhtml から機能名で該当タグを検索  AbstractJaxb tag = html.getById("機能名1");  List<AbstractJaxb> tagList = html.getDescendants("機能名2");  機能名に応じた処理を実行するヘルパークラスでタグや値を操作  ヘルパークラス群= カスタムテンプレートエンジン Mixer2 で作るカスタムテンプレートエンジン, September 20, 2014 5
  • 6.
    Class ヘルパークラスの実装イメージ テンプレートファイル <article> <section> <h1>headline</h1> <p id=" HERE.msg">here comes hello message</p> <span class="M_FUNC">dummy span</span> <p class=" M_FUNC ">dummy p</p> </section> </article>  ヘルパークラス実装 String name = "M_FUNC"; ... public <T extends AbstractJaxb> T replace(String path, String templatePath, T parent, HttpServletRequest request) { List<AbstractJaxb> abstractJaxbList = parent.getDescendants(name); for (AbstractJaxb abstractJaxb : abstractJaxbList) { ... 機能に応じたインタスタンス操作... } return parent; } Mixer2 で作るカスタムテンプレートエンジン, September 20, 2014 6
  • 7.
    ヘルパークラスのオプション値  機能によってはオプション値が必要  例: 値や条件式の指定  オプション値はHTML5 のdata-* 属性を利用すると便利  独自タグなし(独自data-* 属性は使う)  Class ヘルパーM_OUT 用のHTML テンプレート記述例:  .html: <span class="M_OUT" data-value="${ sessionScope.name }">dummy.name</span>  値の出力記述方法の比較  JSTL 例:  .jsp: <c:out value="${sessionScope.username }" />  Thymeleaf の例  .html: <span th:text="${ session.username }">dummy.name</span>  Mayaa 例:  .html: <span id="name">dummy.name</span>  .mayaa: <m:writem:id="name" value="${ session.username }" /> Mixer2 で作るカスタムテンプレートエンジン, September 20, 2014 7
  • 8.
    テンプレートエンジン実行例  Mixer2を使用したView の処理時にまとめて実行  例: SpringWeb MVC: View クラス内  コントローラから対応するView 内で実行 1. View に対応するテンプレートファイルからHtml インスタンス化(Mixer2) 2. 事前処理ヘルパークラス群(カスタム) 3. ページ固有のView 処理(Mixer2) 4. 事後処理ヘルパークラス群(カスタム) 処理イメージ Html html = m2e.loadHtmlTemplate(new File(templatePath)); /** 事前処理用共通ヘルパー群を適用します。*/ html = FunctionsHelper.getDefaultPreInstance().replaceAll(path, templatePath, html); ...ページ固有のレンダリング処理をMixer2 のインスタンス操作で適用します。... /** 事後処理用共通ヘルパー群を適用します。*/ html = FunctionsHelper.getDefaultPostInstance().replaceAll(path, templatePath, html); ヘルパークラス群は事前に Map へ登録しておく Mixer2 で作るカスタムテンプレートエンジン, September 20, 2014 8
  • 9.
    デモ実装  エンジン実装例:Spring Web MVC 依存  https://github.com/jfut/integ-web-spring-webmvc-mixer2  機能  M_ACTIVE_PATH 表示パスと同じ時にclass 属性へactive を追加  M_APP_NAME pom.xml の<name /> の値を出力  M_DEBUG デバッグモード時にデバッグ欄を作成して表示  M_DELETE レンダリング時に削除  M_EXPORT レイアウト機能  M_IF EL 式によるタグの表示・削除  M_IF_DEBUG バッグモード時のみ表示  M_INCLUDE 別のテンプレートファイルを埋め込む  M_LINK リンクアドレスの相対パスを解決  M_MESSAGE リソースファイルの値を出力  M_OUT リクエスト情報の値を出力、EL 式サポート  M_REQUEST_DEBUG リクエスト情報の値をすべて出力  M_VERSION pom.xml の<version /> の値を出力  利用例: Mixer2 のSpring Boot デモを修正  https://github.com/jfut/mixer2-sample/tree/custom-template Mixer2 で作るカスタムテンプレートエンジン, September 20, 2014 9
  • 10.
    デモ実装利用例 <span class="badgeM_IF M_OUT" data-test="${ 0 != sessionScope.cart.getReadOnlyItemList().size() }" data-value="${ sessionScope.cart.totalAmount }"> dummy.10</span></a></li> <li class="M_ACTIVE_PATH" data-path="/"> <a class="M_LINK" data-href="../../" href="../../index.html"> ホーム</a></li> コンテンツファイル: /index.html (ここが起点) <div id="M_EXPORT" data-src="/component/layout/layout.html"> レイアウトファイル: /component/layout/layout.html <div id="M_EXPORT"></div> レイアウトファイル: /component/layout/layout.html <div class="M_INCLUDE" data-src="/component/layout/footer.html"> レイアウトファイル: /component/layout/layout.html <div class="M_INCLUDE" data-src="/component/layout/header.html"> z <a class="M_LINK" data-href="../../cart/view" href="../../cart.html">view cart</a> レイアウトファイル: /component/layout/layout.html <div class="M_INCLUDE" data-src="/component/layout/sidebar.html"> <span class="M_APP_NAME">dummy.appName</span> Version <span class="M_VERSION">x.y.z</span> <div class="M_REQUEST_DEBUG">dummy.M_REQUEST_DEBUG</div> Mixer2 で作るカスタムテンプレートエンジン, September 20, 2014 10
  • 11.
    まとめ  Mixer2  HTML タグをすべてJava で操作可能  テンプレート記述言語を覚えなくて良い  レンダリング済みHTML のテストもJava で書ける= テスト簡単  参考: @nabedge さんのスライドたくさん: http://www.slideshare.net/nabedge  - さらばJSP - JAXBとテンプレートエンジンMixer2 など  Mixer2 の上にカスタムテンプレートエンジンが簡単に作れる  汎用部分の記述をテンプレートファイル側へ  独自のテンプレート記述言語(機能名、data-*属性)を覚えるデメリットはある  Mixer2 のインスタンス操作だけ、DOM 操作ツライがない Mixer2 で作るカスタムテンプレートエンジン, September 20, 2014 11
  • 12.
    Thank You forYour Attention! Mixer2 で作るカスタムテンプレートエンジン, September 20, 2014 12
  • 13.
    Mixer2 + テスト  Spring MVC 連携時のテスト @Test public void showItem_exists() throws Exception { // Mock とRequestMappingHandlerAdapter を使用してリクエストを再現し、ModelAndView を取得 ... 省略... request.setMethod("GET"); request.setRequestURI("/item/10001"); Object handler = handlerMapping.getHandler(request).getHandler(); ModelAndView modelAndView = handlerAdapter.handle(request, response, handler); // ViewName をテスト String viewName = modelAndView.getViewName(); assertThat(viewName, is("item")); // ビューをレンダリング // レンダリング時にビュークラスでテンプレートHTML ファイルをMixer2 でインスタンス化し、タグの書き換え、レスポンスに書き出す AbstractMixer2XhtmlView view = (AbstractMixer2XhtmlView)mixer2XhtmlViewResolver.resolveViewName(viewName, Locale.getDefault()); view.render(modelAndView.getModelMap(), request, response); // レンダリングに使用したhtml インスタンスをダイレクトにテスト Html renderedHtml = view.getRenderedHtml(); // or view.getHtml(); H1 itemNameH1 = renderedHtml.getById("itemName"); // テンプレート: <h1 id="itenName">dummy.header</h1> assertThat(itemNameH1.getContent().get(0).toString(), is("Apple")); // レンダリング後: <h1 id="itemName">Apple</h1> Mixer2 で作るカスタムテンプレートエンジン, September 20, 2014 13 } * https://github.com/jfut/mixer2-sample/blob/custom-template/mixer2-fruitshop-springboot