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

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

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_);
};