2015年1月26日月曜日

【GAE】初級実装編2 主キー検索 その1

株式会社ジェニシス 技術開発事業部の遠藤 太志郎(Tacy)です。

今週からはクラウド基盤「Google App Engine(以下、GAE)」の検索機能についてご紹介したいと思います。
今回は基本中の基本、主キー検索です。

主キーなんてあったっけ?


さて、いきなりですが、ここでGAEの個性が出て来ます。


「いつ主キーなんて定義したんだ?」



これですね。
普通のDBでの開発の場合、先にDB設計で主キーを定義して、Javaではそれを主キーであると認識の元で使うというやり方です。
つまり、「Javaから見たら主キーかどうかなんて分かりゃしないが、プログラマーが主キーとして使うように頑張る」、これが基本です。

(まあ、HibernateとかiBatisとかDBマッピングライブラリを使いますと、Javaの実装上で主キーであることが明示的になるよう制約を入れてくれたりするわけで、普通はそういうライブラリを使う物ですけどね。)

ともかく、私が言いたいのは、「DB設計なんか無いGAEで主キーの定義はいつやるんだ?」ということです。

それは、これ。
Javaのモデル中にベタ書きするんです。

/** キー */
 @Attribute(primaryKey = true)
 private Key key;

「@Attribute(primaryKey = true)」というアノテーションがありますね。
これが主キーであることを指し示します。

そしてその下、「Key」クラス。これが主キー型です。


StringやIntegerを主キーとして使うのでは無く、Keyという主キー型が存在する。


これがGAEの特徴です。


どうやってパラメータをセットするの?


次の疑問です。

「主キーには商品IDをセットしたいんだけど、Key型って何よ?」

そうです。
主キーが「商品ID」なら、普通なら主キーは「String shohinId」でしょう?
でも、GAEの場合は「Key key」ですので、そう単純ではありません。

GAEの場合、主キーは2種類のやり方があるようです。


  • 自動インクリメント
  • 明示的にセット

この2つです。
そして「自動インクリメント」の方がGAEの標準です。
今回は初級編ですし、後者「明示的にセット」の方は後回しとし、自動インクリメントの方から調査していきます。

自動インクリメント

システム開発をやっている過程で、「主キーは数字。1から順にカウントアップする」という設計を見たことはありませんか?
そう、自動インクリメント主キーですね。

その場合、主キーとなるその数字は「ID」というカラム名が振られていることが多いです。


  • ID:自動シーケンス番号。1から順にカウントアップする。
  • shohinId:主キーではなく一意キー。


こういう作りになっているケースです。

はい?

「shohinIdで一意なんだから主キーとして使えよ」って?

ごもっともです。
DB設計としてはそれが一番合理的。
何でわざわざ商品ID以外に別の主キーを用意せねばならんのやら……。

これには実装側の都合があるのですよ。

DB専門のDBスペシャリストがDB設計すると、必ず「主キーはshohinId」になりますよ、絶対。
でも、実装まで考慮するJavaプログラマーがDB設計をやった場合、必ずしもそうはなるとは限りません。


何故なら、主キーってのは「複数個のカラムで一個の主キー」というケースもありますから。

その場合、Javaのソースはこうなります。

Entity entity = dao.get(主キー1,主キー2,主キー3,主キー4,主キー5);

主キー多過ぎでソースがグダグダ。

でも、自動インクリメントのIDを主キーにしたら?

Entity entity = dao.get(id);

スッキリしましたね!!
そう、IDで一発なんですよ。
「テーブルの主キーは全部ID」「主キー検索=ID検索」という制約の元でDB設計することにより、実装効率が高まるのです。

なので、私はこのID使用のDB設計を好みます。
今回の連載とは関係ありませんが、あの有名な「Ruby On Rails」もこのID使用のDB設計を前提とするものです。
プログラマーとしては一考の価値のある思想です。

ともかく、GAEにもID思想は存在しまして、それがGAEの基本です。

自動インクリメントですかあ、特に明示的にセットする必要はありません。
過去にご紹介した「DB登録編」でも、特に主キーについてはアピールが無かったのもその為。

DBに保存した時に、勝手に主キーは入っている

登録時は主キーを気にする必要は無いのです。

ログを見てみましょう。



toStringすることにより「key=Shohin(57)」と、バッチリ主キーのシーケンス番号が入っていることが分かりますね。

この数字のパラメータ名はID、型はlong型です。
以下のやり方で取得出来ます。

long id = shohin.getKey().getId();

検索の時は、このIDで検索してあげれば良いんです。
分かれば簡単ですね。


終わりに

今回で「主キーとは何ぞや」ということが分かりました。

次回は実際にこれを使って検索します

2015年1月15日木曜日

【GAE】初級実装編2 第二ラウンド開始

株式会社ジェニシス 技術開発事業部の遠藤 太志郎(Tacy)です。

さて、先週まで画面レンダリングツール「JsRender」について連載していましたが、
今週からは再びクラウド基盤「Google App Engine(以下、GAE)」の連載に復活しようと思います。

今までのおさらい

GAE連載の最後は以下でした。


GAEのデータベースである「BigTable」にデータを叩き混む所までは出来ているんですね。
しかし、データは入れただけでは何の意味も無く、出力出来なければなりません。

出力は以下の二段構えになります。

  • 検索する。
  • 画面に出す。

このうち、「画面に出す」はGAEの機能ではなく、上述の「JsRender」によりフルAjaxにより実現します。
「JsRender」の連載は「検索→表示」の記事を書く為の前フリだったわけです。

JsRendeの記事は以下にまとめていますので、未読の方はご一読下さい。


第二ラウンド開始

そんなわけでいよいよ本命のGAEの記事に戻るわけですが、かなり期間が空いてしまったので一旦仕切り直し。
ここからは第二ラウンド「初級実装編2」として連載を進めて行こうと思います。

この第二ラウンドで、無事にレコードの「登録⇒検索⇒表示」の一連の流れは完成して、初級実装編は卒業ということになります。

しかしながら、私が思うに、検索の方は登録よりも難しいです……。
以下にサラッと触りを記載します。

登録は簡単

以前の記事の転載になりますが、GAEというのは、「登録・更新」は簡単だと思いますよ。

ShohinDao dao = new ShohinDao();
dao.put(shohin);

これだけですから。

Shohin shohin = new Shohin();
Map map = new HashMap();
map.put("ID_0001",shohin);

これとソックリ。
Mapにデータを叩き込むだけ。

まあ、一意制約チェックとかを実現するには少々ステップを伴うこともありますが、
基本的にはこれだけです。

普通のDBであれば、「大量データを一括登録する為にバッチインサート文を作って……」とか「SELECT INSERTで一括して登録を……」みたいな感じに、
大量データ登録を高速化する為に色々と頭を捻ったりすることも無く、
ただボコボコとJavaのクラスそのまま叩き込んでいくだけのパワー勝負です。

しかし、検索は違います。
検索は非常にテクニカルでエンジニアの力量が試されます。

検索の制約

GAEの検索は制約が非常に多いです。

リレーショナルデータベースではない。

「データベースとはリレーショナルデータベースのことだ!!」くらいの勢いで考えているエンジニアは非常に多いと思いますが、
OracleやPostgreSqlみたいなリレーショナルデータベースではありませんので、その常識の根底が根こそぎ崩れます。

結合出来ない。

「SELECT * FROM table1,table2 WHERE TABLE1.id = TABLE2.id」みたいなselect文での結合はSQLの常識ですが、
リレーショナルデータベースではないGAEはsqlもありませんので、こういう結合は出来ません。

常に「table1ならtabe1だけ」という単表検索です。

早い話がJavaのMapみたいなものなんです。
Mapにmap同士を結合する機能なんかありませんよね?

結合させたい場合は、キーによる一意検索を乱射してロジックで結合させるのです。

インデックス検索

ここが重要。

普通のリレーショナルDBでは「インデックスを利用した検索」というテクニックがあります。
これを理解しているエンジニアとしていないエンジニアでは、SQLの検索速度が段違い!!
軽く数千倍の差が出ることも珍しくありません。

GAEの場合はこれに特化していまして、インデックス検索しか無いのです。

前方一致検索は出来ても後方一致検索は出来ない

「where table1.name lile '%tacy%'」みたいに前後方一致検索は普通のSQLでもありますが、
この場合、インデックスが効いていません。
よって検索は遅いです。

「where table1.name lile 'tacy%'」と、前方一致検索ならばインデックスが効きますので高速です。

これと同じことがGAEでも発生しまして、後者の前方一致検索であればGAEならではの高速検索が作動します。
前者の前後方一致検索は実行不能ですので、全件取得してJavaでフィルタリングする、という荒業を行うハメになります。

大量件数では実現出来ませんね。
このように、インデックスで実現出来ない検索は出来ないというのがGAEの基本です。

count(*)が無い

これが驚きなのですが、count(*)は無いのです!!
単純に検索条件で全部取得して、Javaで「++count」みたいに一件一件数えるしか無いです。

GAEの制約の中でもこれは結構ダメージが大きいかと。
「画面上に総件数を出す」みたいな要件への対応は難しいかと。

ORも無い

OR条件が出来ません。2回検索してJavaでマージする必要があります。




とまあ、ザッと目ぼしい所を挙げるだけでもGAEの検索にはこんなに沢山の制約があります。

これを乗り越える為には、

  • そもそも出来ないことを要件に入れないようにする。
  • テーブル設計を冗長化することにより結合を発生させない。
  • 検索キー専用のカラムを容易する。例えば後方一致検索を行う為に文字列を前後反転させて保存しておくなど

などなど、技術と言うよりサーカスと言った方が良いんじゃないかというような技を駆使することになります。

これを聞いただけでゲンナリする人もいらっしゃるかもしれませんが、いえいえ、諦めてはいけません。
Googleの誇るGoogle検索や、Google+や、このブログ、GMailなど、これらは全部そういう課題を乗り越えて作っているのですから。
頑張ればちゃんと出来るはずなのです。

気合入れて行きましょう!!

終わりに

というわけで、初級実装編2は、実質的には検索編となるでしょう。

来週から実際に色々と要件を決めてサンプルを実装していこうと思います。

2015年1月8日木曜日

【JsRender】GAEとの連携

あけましておめでとう。
株式会社ジェニシス 技術開発事業部の遠藤 太志郎(Tacy)です。

本年もよろしくお願いします。

さて、ここしばらくはJavaScriptによる画面レンダリングツール「JsRender」について連載していましたが、
それも今回で最終回を迎えます。

最終回はGAEとの連携です。

思い出す

過去の記事を掘り返してみたら、GAEの記事を最後に書いたのは2014年9月でした。
もう4ヶ月も経ってしまったのですね……。

そして、そのGAEでの連載で私が何度も主張していたこと。それは。

GAEはjspを使わず、フルAjaxで書いてこそ真価を発揮する。

これでした。
そして、そのGAEの連載を中断して始めたのが、本連載である画面レンダリングツール「JsRender」のご紹介……。

もうお分かりですね。

この一連の連載は、JsRenderをGAEで使用する為の前振りだったのです。

ようやく本懐を遂げること出来ました。
では、さっそくGAEに適用してみましょう。

JSON発行

GAEからJSONを発行する方法は過去の記事で紹介しておりましたね。



昔の記事なので改めて書きますが、
GAEでは「GSON」というライブラリを使ってJavaのオブジェクトをJSON文字列化するやり方をオススメします。

Gson gson = new Gson();
String json = gson.toJson(jet);

その結果、以下のようなJSON文字列が出力されます。

{"shohinList":[{"key":{"kind":"Shohin","id":57},"seqNo":1,"shohinId":"57","shohinName":"%E5%95%86%E5%93%813","stock":345,"deliveryDate":"2015/01/10","createdAt":"2015/01/08 21:45:28","updatedAt":"2015/01/08 21:45:28"},{"key":{"kind":"Shohin","id":56},"seqNo":2,"shohinId":"56","shohinName":"%E5%95%86%E5%93%81%EF%BC%92","stock":234,"deliveryDate":"2015/01/09","createdAt":"2015/01/08 21:45:15","updatedAt":"2015/01/08 21:45:15"},{"key":{"kind":"Shohin","id":55},"seqNo":3,"shohinId":"55","shohinName":"%E5%95%86%E5%93%81%EF%BC%91","stock":123,"deliveryDate":"2015/01/08","createdAt":"2015/01/08 21:44:48","updatedAt":"2015/01/08 21:44:48"}],"responseCode":0}

ゴジャゴジャしていて分かりませんが……。
実は「日本語はURLエンコードしないと文字化けして送受信出来ない」とか色々問題があるのですが、その辺については後日の連載で。

ともかく、この文字列を以下のやり方でレスポンスに乗せてあげれば、Ajaxでレスポンスを取得することが出来ます。
response.getWriter().write(json);

画面出力

文字列さえ出力出来てしまえばこっちのものです。

標準的なJQueryの非同期通信で文字列を取得し、そのままJsRenderに流し込んでしまいます。

function doSearch() {

 $.ajax({
  url : $("#form").attr('action'),
  type : $("#form").attr('method'),
  data : $("#form").serialize(),
  dataType : 'json',
  success : function(json) {

   var items = json.shohinList;

   var length = items.length;
   for (i = 0; i < length; i++) {
    items[i].shohinName = decodeURI(items[i].shohinName);
   }

   $("#shohinList").html($("#shohinListTemplate").render(json));
  }
 });
}
JsRenderのテンプレートはこちら。
<script id="shohinListTemplate" type="text/x-jsrender">

<table class="shohinList">
 <tr>
  <th class="no">NO</th>
  <th class="shohinId">商品ID</th>
  <th class="shohinName">商品名</th>
  <th class="action"></th>
 </tr>
 {{for shohinList}}
 <tr>
  <td class="no">{{>seqNo}}</td>
  <td class="shohinId">{{>shohinId}}</td>
  <td class="shohinName">{{>shohinName}}</td>
  <td class="action">詳細 編集</td>
 </tr>
 {{/for}}
</table>

</script>

>/script<

結果は、ほら。
普通のjspと比較しても遜色無い機能的な検索結果一覧が出力出来ました。


これでGAEの画面出力も安心ですね。

終わりに

ようやくGAEに戻って参りました。

画面出力機能の骨子が出来上がったことですし、
次回からは再びGAEに復帰。

GAEのDBであるBigTableの『検索』についてご紹介して行きたいと思います。

2014年12月23日火曜日

【JsRender】#parent

株式会社ジェニシス 技術開発事業部の遠藤 太志郎(Tacy)です。

現在はJavaScriptによる画面レンダリングツール「JsRender」について連載しています。

今回も公式サイトに乗っていない秘密機能「parent」についてのご紹介です。

必要になるシチュエーション

#parentはfor文の真っ最中などで、親クラスを遡ってパラメータを取得する機能です。

例えば、私は「技術開発事業部」の所属ですが、同じ部にはメンバーが何人もいます。

  • 技術開発事業部:Aさん
  • 技術開発事業部:Bさん
  • 技術開発事業部:Cさん

こういう場合、データの持ち方によっては

  • 親クラス:部署
  • 子クラス:メンバー


というように、親子関係が生ずることになります。

このデータの持ち方で所属とメンバーを表現したい場合は、以下のような形式にするのが合理的です。


<div id="result"></div>

<script id="jsRenderTmpl" type="text/x-jsrender">
★{{:department}}
{{for members}}
<div>
・{{:name}}
</div>
{{/for}}
</script>

<script>
var data = {
  "department": "技術開発事業部",
  "members": [
      {"name": "Aさん"},
      {"name": "Bさん"},
      {"name": "Cさん"}
 ]
};

var template = $.templates("#jsRenderTmpl");

var htmlOutput = template.render(data);

$("#result").html(htmlOutput);
</script>

しかしですね、場合によっては以下みたいな表示にしたいケースもあるわけですよ。


これは痛いんですよね。
何故なら、階層が違うから。

JSONの定義では「members」をfor文のスコープとして回していますので、membersより配下のデータを表示する場合は何も難しくありません。
普通に順を追って定義していけば表示出来ます。

しかし、membersより上のデータはスコープの中に入っていませんので、単純に取得することが出来ません。
今回のテーマである#parentはこういう場合に使用します。

#parentを活用

#parentは、まあ、タグ名から分かりますようにfor文のスコープの外にある親データを取ってくる機能です。
その実装方法はこちら。

<script id="jsRenderTmpl" type="text/x-jsrender">
{{for members}}
<div>
・{{:#parent.parent.data.department}}:{{:name}}
</div>
{{/for}}
</script>

<script>
var data = {
  "department": "技術開発事業部",
  "members": [
      {"name": "Aさん"},
      {"name": "Bさん"},
      {"name": "Cさん"}
 ]
};

var template = $.templates("#jsRenderTmpl");

var htmlOutput = template.render(data);

$("#result").html(htmlOutput);
</script>

「#parent.parent.data.department」、これで「技術開発事業部」という値が取れています。

詳細解説

「あれ? parent2連打になってるのは何?」と思われたかと思います。

jsrenderでは「parent」を一個各毎に階層を一個遡るという意味になります。

今回の場合、スタート地点となるのはfor文の中のメンバーですから、


  • 単にそのレコードを出したい場合⇒「#data.name」(dataは通常省略)
  • その一個上のmembersを取りたい場合⇒「#parent.data」
  • その一個上のdataを取りたい場合⇒「#parent.parent.data」
  • 目的のdepartmentはdataの子供なので⇒「#parent.parent.data.department」

こういうわけです。
parentを連結することで一回一番上まで遡って、それからdepartmentを狙い撃ちするという段取りになるわけです。

parentを連発する辺り最初は戸惑いますが、分かってしまえば簡単なことですね。

終わりに

今回ご紹介した#parentは結構便利な機能です。
これを知らないと画面を表示する為にクラスを非正規化しなければならなくなり、手間も転送データ量も増えて大変ですからね。
上手く活用すれば開発効率もUPするでしょう。

ぜひマスターすることをオススメします。

2014年12月8日月曜日

【JsRender】#index

株式会社ジェニシス 技術開発事業部の遠藤 太志郎(Tacy)です。

現在はJavaScriptによる画面レンダリングツール「JsRender」について連載しています。

さて、私はこの連載を行うに辺り、基本的に以下の公式サイトを読んで執筆しています。




しかし、どうも公式サイトのドキュメントに載っていない機能があるようです。
酷い話ですが、今回はその機能の一つ「#index」についてご紹介します。

for文のループ回数「#index」

普通、for文だと「for(int i=0;i;10;i++)」みたいに現在がfor文の何番目なのかを
iなどの変数で認識することが出来ます。

しかし、JsRenderのfor文はfor文と言ってもJavaで言う所の「for(String str : list)」の形式と言いますか、
つまり『配列全出力』の体を取っています。

この為、実装する上では通常のプログラミングである「i」に相当する変数が登場しません。
この為、「偶数行と奇数行で色を変えたい」みたいな、現在が何ループ目であるかを認識するには、挿入の元になっているJSONにパラメータを持たせる必要があるように見えます。

しかし、実は隠しパラメータとしてループの行数を持っていまして、それが「#index」です。

<script id="jsRenderTmpl" type="text/x-jsrender">
★{{:title}}
{{for members}}
<div>
・{{>#index}}:{{:name}}
</div>
{{/for}}
</script>

<script>
var data = {
  "title": "for文サンプル一覧",
  "members": [
      {"name": "碇シンジ",sex:"male"},
      {"name": "綾波レイ",sex:"female"},
      {"name": "ゼルエル",sex:"unknown"}
 ]
};

var template = $.templates("#jsRenderTmpl");

var htmlOutput = template.render(data);

$("#result").html(htmlOutput);
</script>



ちゃんと行数が出ているでますね?

加工

では、この「#index」がちゃんと加工したりif文で使えたりするかも検証してみましょう。

まずは偶数/奇数の判定から。

<script id="jsRenderTmpl" type="text/x-jsrender">
★{{:title}}
{{for members}}
<div>
・{{>#index}}:{{:name}}:
 {{if #index %2 == 0}}
  偶数行
 {{else}}
  奇数行
 {{/if}}
</div>
{{/for}}
</script>

<script>
var data = {
  "title": "for文サンプル一覧",
  "members": [
      {"name": "碇シンジ",sex:"male"},
      {"name": "綾波レイ",sex:"female"},
      {"name": "ゼルエル",sex:"unknown"}
 ]
};

var template = $.templates("#jsRenderTmpl");

var htmlOutput = template.render(data);

$("#result").html(htmlOutput);
</script>


バッチリですね。

では、「0からスタートではなく、1からスタートしたい」という場合は、
単純にindexに+1すればOKです。

<script id="jsRenderTmpl" type="text/x-jsrender">
★{{:title}}
{{for members}}
<div>
・{{>#index + 1}}:{{:name}}
</div>
{{/for}}
</script>

<script>
var data = {
  "title": "for文サンプル一覧",
  "members": [
      {"name": "碇シンジ",sex:"male"},
      {"name": "綾波レイ",sex:"female"},
      {"name": "ゼルエル",sex:"unknown"}
 ]
};

var template = $.templates("#jsRenderTmpl");

var htmlOutput = template.render(data);

$("#result").html(htmlOutput);
</script>



簡単ですね!

終わりに

この「#index」は、前述の通り無くてもJSON定義の方で回避可能です。
しかし、知っていると知らないとでは便利さが格段に違います。

何で公式サイトに載っていないのか謎ですが、ぜひ活用していきたいものです。

引き続き隠し機能を検証します。

2014年11月5日水曜日

【JsRender】prop

株式会社ジェニシス 技術開発事業部の遠藤 太志郎(Tacy)です。

現在はJavaScriptによる画面レンダリングツール「JsRender」について連載しています。

今回は「props」、つまり「プロパティ」についてご紹介します。

要するにMap

このpropsとは、要するにJavaで言えばMap総当たりのことです。
propsは「Mapに格納されている全てのkeyに対して値を取得する」というものです。

例を挙げてみましょう。

まず以下にまたしてもメンバー情報一覧を表示するソースを記載します。

<script id="jsRenderTmpl" type="text/x-jsrender">
★{{:title}}
<table>
 <tr>
  <td>名前</td>
  <td>年齢</td>
  <td>性別</td>
  <td>血液型</td>
 </tr>
{{for members}}

<tr>
 <td>{{:name}}</td>
 <td>{{:age}}</td>
 <td>{{:sex}}</td>
 <td>{{:blood}}</td>
</tr>

{{/for}}

</table>
</script>

<script>
var data = {
  "title": "名前一覧",
  "members": [
      {"name": "Tacy1号","age": "28","sex": "男","blood": "AB"},
      {"name": "Tacy2号","age": "28","sex": "男","blood": "AB"},
      {"name": "Tacy17号","age": "28","sex": "男","blood": "AB"}
 ]
};

var template = $.templates("#jsRenderTmpl");

var htmlOutput = template.render(data);

$("#result").html(htmlOutput);
</script>



今回問題になるのは「横の長さ」です。
サンプルでは「名前」「年齢」「性別」「血液型」の4列ですけれども、案件によってはズラッと長々と出てくることもありますので、そういう場合に一つ一つペタペタ書いていくのは結構大変というもの。

そこで、「JSONタグの中にあるものを全て出力する」としたい場合に出番となるのがpropsです。

そのサンプルがこちら。

<script id="jsRenderTmpl" type="text/x-jsrender">
★{{:title}}
<table>
{{props members}}
<tr>
 {{props prop}}
 <td>{{>key}}:{{>prop}}</td>
 {{/props}}
</tr>
{{/props}}

</table>
</script>

<script>
var data = {
  "title": "名前一覧",
  "members": [
      {"seq":0,"name": "Tacy1号","age": "28","sex": "男","blood": "AB"},
      {"seq":1,"name": "Tacy2号","age": "28","sex": "男","blood": "AB"},
      {"seq":2,"name": "Tacy3号","age": "28","sex": "男","blood": "AB"}
 ]
};

var template = $.templates("#jsRenderTmpl");

var htmlOutput = template.render(data);

$("#result").html(htmlOutput);
</script>



「{{props members}}」とやることで、menbersの中の情報を全部取得します。
ここから、


  • 「{{>key}}」でそのパラメータのキー値を。(配列の場合はシーケンス番号)
  • 「{{>prop}}」と指定することそのパラメータを。


これらをループで取得することが出来るわけです。

propsは入れ子で使うことも可能で、上記の例も入れ子型です。
「{{props prop}}」となっている部分が入れ子に書いている部分ですね。

デザインが……

「あれ? 上と下で画面レイアウトが違うじゃねえか!!」とお気づきの方、その通りです。

propsはパラメータ総出力なので、細かいレイアウト調整が効かないのです。
なので「ヘッダーを日本語で追加したい」とか「JSONタグのうち、必要なカラムだけ出す」とか、
そういう細かい要望にはif文でも使わない限り対応出来ません。

そんなロジック入れるのであれば、最初から従来のやり方通りにkey値をペタペタ決め打ちして、for文で回して書いていった方が良いと思いますね。

需要としては「JSONタグの中身をシンプルに全部出す」というケースくらいしか無いでしょう。

JSONタグを合わせる

「加工無しの全出力なんて、デバッグでしか使わないよ!!」と思ってしまうかもしれませんが、
しかしですね、この問題は発想の転換で乗り切りましょう。
ズバリ、JsRender側ではなく、JSONタグの方を合わせる方法で解決出来ます。

「サーバ側処理で必要なデータ加工を全て完了させて、HTML側ではそれを出すだけ」という切り分けを徹底すれば、
非常に便利な機能になります。

こんな感じに。


<script id="jsRenderTmpl" type="text/x-jsrender">
★{{:title}}
<table>
<tr>
 {{props header}}
 <td>{{>prop}}</td>
 {{/props}}
</tr>
{{props members}}
<tr>
 {{props prop}}
 <td>{{>prop}}</td>
 {{/props}}
</tr>
{{/props}}

</table>
</script>

<script>
var data = {
  "title": "名前一覧",
  "header": {"seq":"No","name": "名前","age": "年齢","sex": "性別","blood": "血液型"},
  "members": [
      {"seq":0,"name": "Tacy1号","age": "28","sex": "男","blood": "AB"},
      {"seq":1,"name": "Tacy2号","age": "28","sex": "男","blood": "AB"},
      {"seq":2,"name": "Tacy3号","age": "28","sex": "男","blood": "AB"}
 ]
};

var template = $.templates("#jsRenderTmpl");

var htmlOutput = template.render(data);

$("#result").html(htmlOutput);
</script>


ほら?
ヘッダーの属性とかも全部ひっ包めてJSONに入れてしまえばキッチリ綺麗になるでしょ?

これはコーディング思想として一つの基準になると思いますね。
HTMLやJavaScriptにボコボコ書き過ぎてしまうと可読性や生産性が大幅に下がります。

JsRenderは確かに便利なライブラリですが、しかしJsRenderは重い処理をする為にあるのではないのです。
「必要な情報はJSONに全部入れる」という実装思想を持つことが良いモジュールを作るキーとなるでしょう。

終わりに

引き続きJsRenderについて連載を継続します。

2014年10月31日金曜日

【JsRender】include文

株式会社ジェニシス 技術開発事業部の遠藤 太志郎(Tacy)です。

現在はJavaScriptによる画面レンダリングツール「JsRender」について連載しています。

前回までで「if文」と「for文」のご紹介は終わりました。
それだけ分かってればJsRenderはもう十分やっていけるのですが、JsRenderには他にもチョコチョコ機能がありまして、そういうのを使うと開発効率が向上しますので知っておいて損はありません。

今回は外部定義の取り込み機能「include」についてご紹介します。

includeを使わない場合

JsRenderではテンプレート部分を「scriptタグ」で定義するわけですが、一方のscriptタグの中に、別のscriptタグを取り込む機能が「incude」です。

要するに、

  • 一個のscriptタグに全部書くと複雑だったりサイズが大きすぎる。
  • 同一テンプレートを再利用したい。


こういう需要の為に使う機能です。

例を挙げてみましょう。

「メンバーの名前一覧を表出力する」という機能を想定します。
テーブル出力ですので「ヘッダー」と「ボディ」があります。
ただし、特殊な事情として「スクロール対策として10行置きにヘッダーを挿入したい」という
仕様があります。

画面としてはこんな感じ。



それを実現するソースが以下です。

<script id="jsRenderTmpl" type="text/x-jsrender">
★{{:title}}
<table>
{{for members}}

{{if seq % 10 == 0}}
 <tr>
  <td>名前</td>
 </tr>
{{/if}}

<tr>
 <td>・{{:name}}</td>
</tr>

{{/for}}

</table>
</script>

<script>
var data = {
  "title": "名前一覧",
  "members": [
      {"seq":0,"name": "Tacy1号"},
      {"seq":1,"name": "Tacy2号"},
      {"seq":2,"name": "Tacy3号"},
      {"seq":3,"name": "Tacy4号"},
      {"seq":4,"name": "Tacy5号"},
      {"seq":5,"name": "Tacy6号"},
      {"seq":6,"name": "Tacy7号"},
      {"seq":7,"name": "Tacy8号"},
      {"seq":8,"name": "Tacy9号"},
      {"seq":9,"name": "Tacy10号"},
      {"seq":10,"name": "Tacy11号"},
      {"seq":11,"name": "Tacy12号"},
      {"seq":11,"name": "Tacy13号"},
      {"seq":11,"name": "Tacy14号"},
      {"seq":11,"name": "Tacy15号"},
      {"seq":11,"name": "Tacy16号"},
      {"seq":12,"name": "Tacy17号"}
 ]
};

var template = $.templates("#jsRenderTmpl");

var htmlOutput = template.render(data);

$("#result").html(htmlOutput);
</script>

if文とfor文とテンプレHTMLが入り乱れてちゃってますよね?

まあ、このサンプルはカラムが「名前」しか無いので何とでもなりますが、
もっとカラムが増えると、段々と可読性が下がってきます。

そこで、includeの出番です。

includeを使う場合

上記のサンプルの可読性を向上させる為にincludeタグを使ってみましょう。
構造としては以下の3分割にしようと思います。


  • ベース部分を担当する親
  • ヘッダー部のテンプレート
  • ボディ部のテンプレート


これでそれぞれの性質、機能毎にscriptタグが切れますので、
「ちょっとヘッダーだけ修正したい」みたいな要望にも対応しやすいです。

その対応バージョンがこちら。

<script id="jsRenderTmpl" type="text/x-jsrender">
★{{:title}}
<table>
{{for members}}
{{if seq % 10 == 0}}
 {{include tmpl="#jsRenderTmpl_head"/}}
{{/if}}
{{include tmpl="#jsRenderTmpl_body"/}}
{{/for}}
</table>
</script>

<script id="jsRenderTmpl_head" type="text/x-jsrender">
 <tr>
  <td>名前</td>
 </tr>
</script>

<script id="jsRenderTmpl_body" type="text/x-jsrender">
 <tr>
  <td>・{{:name}}</td>
 </tr>
</script>

どうです?
ソースの総量は増えてしまいますけど、キチッと分割されているので見易くなったでしょう?

まあ、ちょっとしたことにイチイチincludeしていると逆に面倒なだけですので、
includeを使うかどうかは、実装者の判断ですね。

上手くやれば巨大な画面でも綺麗にコーディング出来るでしょう。

終わりに

次回はテンプレートに送り込むJSONタグが複雑な場合に活躍する機能「prop」についてご紹介します。