H2Databaseを追っかけていたりしたブログ

H2 database のリリースノートを読んだりとか。

goog-inline-blockとgoog.style.setInlineBlock(element)の違いについて

何の違いがあるんだろう...と思っていたんだけど、goog.style.setInlineBlock(element)はgoog.style.removeInlineBlock(element)がないのに対して、goog.dom.classes.add(element,'goog-inline-block')はgoog.dom.classes.remove(element,'goog-inline-block')することが出来る、とのこと。

途中でinline-blockを切り替えたい、っていうのもあまり思いつかないので、どちらでも良いような気はしますが、使い分けがわかったので、それはとても良かった。

Closure: The Definitive Guide

Closure: The Definitive Guide

ほんとおススメ。というか、これないと厳しそうな感じがするよ。

今現在のClosureに対しての感想。

  • 思ったよりは作りやすい(plovrのおかげでビルドとか意識しない)
  • Containerがネストできなくてびっくりした
  • でもJavaの人なら、GWTのがよいのではないか

closure-library始める4 uiコンポーネント作ってみた

いやね、Containerのサンプル見たら、HorizontalのコンテナにControlをaddChildする前に、addClassName("goog-inline-block")してて、そこコメントアウトしたら横に並ばず...。

goog.provide('hoge.HBox');
goog.require('goog.ui.Container');
goog.require('goog.style');

hoge.HBox = function(opt_renderer,opt_domHelper){
		goog.base(this,goog.ui.Container.Orientation.HORIZONTAL,opt_renderer,opt_domHelper);
};
goog.inherits(hoge.HBox,goog.ui.Container);

hoge.HBox.prototype.addChild = function(child, opt_render){
		goog.base(this,'addChild',child, opt_render);
		if(child.getElement()){
			goog.style.setInlineBlock(child.getElement());
		}
};
hoge.HBox.prototype.addChildAt = function(control, index, opt_render){
		goog.base(this,'addChildAt',control, index, opt_render);
		if(control.getElement()){
			goog.style.setInlineBlock(control.getElement());
		}
};

closure-library始める3 uiコンポーネントについての続き

enterDocumentはDOMが作成されて、document上に配置された時に呼ばれる。だから、イベントリスナーを張るのに適した場所だ。

その前に、プロパティに、goog.events.EventHandlerを持っておく。

goog.require('goog.events.EventHandler');
//略
hoge.Box = function(opt_label,opt_domHelper){this.eh_ = new goog.events.EventHandler(this);
…
}

これ、なんでEventHandlerをわざわざプロパティに持っておくのかな...と思ったら、new goog.events.EventHandler(this)としておくことで、このオブジェクトを使ってlistenさせるとその時のthisがEventHandlerのコンストラクタで渡したオブジェクトになるのね。
というか、このオブジェクトがEventHandlerって名前は微妙におかしくないか...?

hoge.Box.prototype.enterDocument = function(){
  goog.base(this,"enterDocument");
  this.eh_.listen(this.getElement(),goog.events.EventType.CLICK, function(){alert('clicked ' + this.label_)});
};

goog.base(this,"enterDocument")で、スーパークラスのenterDocumentを呼んでいる。で、その後、自分のルート要素のイベントをlistenする。最後の関数でthis.label_を呼び出せているのがポイント。

...この方が簡単だと思って、無名関数使ったが、unlistenもあるので、元ソースと同じように変更する。

で、enterDocumentと対になっているのがexitDocumentで、ここでイベントリスナーを外しておかないと、オブジェクトがリークして後々大変なことになったりするのでしょう。まとめると

hoge.Box.prototype.enterDocument = function(){
  goog.base(this,"enterDocument");
  this.eh_.listen(this.getElement(),goog.events.EventType.CLICK, this.onDivClicked_);
};
hoge.Box.prototype.exitDocument = function(){
  goog.base(this,"exitDocument");
  this.eh_.unlisten(this.getElement(),goog.events.EventType.CLICK, this.onDivClicked_);
};
hoge.Box.prototype.onDivClicked_ = function(event){
  alert(this.label_);
};

で、DOM関連の後始末はexitDocumentで行って、そのほかのオブジェクトの破棄は、disposeInternalで行う。

hoge.Box.prototype.disposeInternal = function(){
  goog.base(this,"disposeInternal");
  this.eh_.dispose();
};

これで非常に簡単なコンポーネントができた。こうやってみると、意外とわかりやすい?。

script/apps.js

goog.provide('hoge.App');
goog.require('hoge.Box');

hoge.App = function(){
  this.initialize_();
}
hoge.App.prototype.initialize_ = function(){
  var he = new hoge.Box();
  he.render(document.body);
};
new hoge.App();

script/box.js

goog.provide('hoge.Box');
goog.require('goog.ui.Component');
goog.require('goog.events.EventHandler');

hoge.Box = function(opt_label,opt_domHelper){
  goog.ui.Component.call(this,opt_domHelper);
  this.label_ = opt_label || 'Click Me';
  this.eh_ = new goog.events.EventHandler(this);
  this.initialize_();
}
goog.inherits(hoge.Box,goog.ui.Component);

hoge.Box.prototype.initialize_ = function(){
};

hoge.Box.prototype.createDom = function(){
  this.decorateInternal(this.dom_.createElement('div'));
};

hoge.Box.prototype.decorateInternal = function(element){
  this.setElementInternal(element);
  this.dom_.setProperties(element, {"style":"border: 1px solid black; width: 150px; background-color: gray; color: white; text-align: center; font-weight: bold;"});
  if(!this.getLabelText()){
    this.setLabelText(this.label_);
  }
};
hoge.Box.prototype.getLabelText = function(){
  if(!this.getElement()){
    return '';
  }
  return this.dom_.getTextContent(this.getElement());
};
hoge.Box.prototype.setLabelText = function(text){
  if(this.getElement()){
    this.dom_.setTextContent(this.getElement(), text);
  }
};
hoge.Box.prototype.enterDocument = function(){
  goog.base(this,"enterDocument");
  this.eh_.listen(this.getElement(),goog.events.EventType.CLICK, this.onDivClicked_);
};
hoge.Box.prototype.exitDocument = function(){
  goog.base(this,"exitDocument");
  this.eh_.unlisten(this.getElement(),goog.events.EventType.CLICK, this.onDivClicked_);
};
hoge.Box.prototype.disposeInternal = function(){
  goog.base(this,"disposeInternal");
  this.eh_.dispose();
};
hoge.Box.prototype.onDivClicked_ = function(event){
  alert(this.label_);
};

closure-library始める2 uiコンポーネントについて

まず、コンポーネントを作ってみる。

参考。http://code.google.com/p/closure-library/wiki/IntroToComponents

参考のページとそのサンプルコードを見ればそれでいいような気もするが、ちょっと簡略化してちょっとずつ確認してみる。

goog.provide('hoge.Box');
goog.require('goog.ui.Component');

hoge.Box = function(opt_label,opt_domHelper){
  goog.ui.Component.call(this,opt_domHelper);
  this.label_ = opt_label || 'Click Me';
  this.initialize_();
}
goog.inherits(hoge.Box,goog.ui.Component);

hoge.Box.prototype.initialize_ = function(){
};

goog.provideで自分が何のクラスかを宣言して、goog.requireで依存を表現している。uiを作るので、goog.ui.Componentに依存している。

コンストラクタの最初でgoog.ui.Componentのコンストラクタを呼んでいる。で、goog.inheritsでhoge.Boxはgoog.ui.Componentを継承することを宣言している。次はDOMを構築する。

hoge.Box.prototype.createDom = function(){
  this.decorateInternal(this.dom_.createElement('div'));
};

createDomは実装必須。decorateInternalはオプション。createDomでは、このクラスのDOM表現のルートとなる要素をつくる。で、decorateInternalでは、外部から要素が与えられたときに、その要素を自分のルート要素として、必要なお膳立てを行う、という感じのようだ。ここでは、ルート要素を空のdivで作って、それ以外の装飾の部分はdecorateInternalに任せるという形になっている。いきなりthis.dom_とか出てきてなによ、と思うがAPIリファレンスを読むと、goog.domのショートカットだ。

hoge.Box.prototype.decorateInternal = function(element){
  this.setElementInternal(element);
  this.dom_.setProperties(element, {"style":"border: 1px solid black; width: 150px; background-color: gray; color: white; text-align: center; font-weight: bold;"});
  if(!this.getLabelText()){
    this.setLabelText(this.label_);
  }
};
hoge.Box.prototype.getLabelText = function(){
  if(!this.getElement()){
    return '';
  }
  return this.dom_.getTextContent(this.getElement());
};
hoge.Box.prototype.setLabelText = function(text){
  if(this.getElement()){
    this.dom_.setTextContent(this.getElement(), text);
  }
};

this.setElementInternal(element)が重要。closureのuiコンポーネントでは、DOM表現のルート要素はsetElementInternal(element)でセットして保持しておく。以降はgetElement()で取り出せる。getLabelText/setLabelTextはおまけ。

これで、実行してみる。app.jsに以下のように記述する。

goog.provide('hoge.App');
goog.require('hoge.Box');

hoge.App = function(){
  this.initialize_();
}
hoge.App.prototype.initialize_ = function(){
  var he = new hoge.Box();
  he.render(document.body);
};
new hoge.App();

なんかボタンみたいなものが表示できた。

closure-library始める

closure-libraryはgoogleがつくった、gmail等で利用されているライブラリ。かっちり作りこみたい向きのためのライブラリなので、小さい規模の開発には面倒が多い、という感じを受ける。
関連プロダクトであるclosure compilerを使って、最適化して使うことが前提になっており、そのためにはpythonインストールする必要があるなど、さらに敷居が高い。

しかし、最近はjava製のplovrというツールがあり、これをつかうとオンデマンドでコンパイルしてくれるようになり、若干手軽に開発できる。

環境を準備する。DocumentRoot以下に適当なディレクトリを作る。

closure libraryはsvnでチェックアウトして持ってくる。

svn checkout http://closure-library.googlecode.com/svn/trunk/ closure-library

plovrは以下からjarがダウンロードできる。最新の持ってくればいいんだと思う。
http://code.google.com/p/plovr/downloads/list

plovrはjavaなので、javaが実行できるようにしておく必要がある。

ドキュメントルート下の適当なディレクトリ下の構成は下記の通り。cssの下にスタイルシートを、scriptsの下にjavascriptを置く想定。closure-libraryはチェックアウトしてきたclosure library。

closure-library/
css/
scripts/app.js
config.json
index.html
plovr.jar

config.jsonはplovrのための設定ファイル。

{"id":"base","paths":"scripts/","inputs":"scripts/app.js"}

scripts/app.jsに最小限のスクリプト。

goog.provide('hoge.App');
goog.require('goog.dom');

hoge.App = function(){
  this.initialize_();
}
hoge.App.prototype.initialize_ = function(){
  var hc = goog.dom.createDom('span',null,'Hello Closure');
  goog.dom.appendChild(document.body, hc);
};
new hoge.App();

index.html

<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>closure test</title>
    <script type="text/javascript" src="http://hostname:9810/compile?id=hoge"></script>
  </head>
  <body>
    <div id="hoge"></div>
    <script type="text/javascript">
      goog.require('hoge.App');
    </script>
  </body>
</html>

これで、用意はできたので、まずplovrを起動してみる。デフォルトでは9810ポートで上がる。

java -jar plovr.jar serve config.json > /dev/null 2>&1

ブラウザで http://ホスト名:9810/ でアクセスしてみるとそれっぽい画面が起動していれば、まぁ、いいんじゃないか。

http://ホスト名:9810/compile?id=hoge でアクセスしてみると、minifyされたscriptが返ってくる。よく見ると、自分がapp.jsで書いたコードが含まれているのがわかるはず。以降app.jsを書き換えて、表示する都度コンパイルしてくれる。この状態になら、あまり意識せず開発できそうだ。

で、ブラウザで、http://ホスト名/適切な/パス名/index.html にアクセスすると間違いがなければ、ちゃんとHello Closureと表示されているはずだ。はじめの一歩。