Closure Library で HTML から UI を生成する
非常に久しぶりの記事投稿となってしまいましたが orz 本日は Closure Library ネタを書いてみようと思います。最近は、 Closure Library もだいぶ知られるようになってきたみたいで、 Closure Library についての日本語のつぶやきもだいぶ増えてきて嬉しい限りです。
Closure Library で UI を構築する場合、もっとも簡単な方法は render メソッドを利用して JavaScript でコンポーネントを配置していく方法です。この方法はプログラマにとってはわかりやすいのですが、デザイナーさんの自由度が下がってしまいます。また、コードで直接 UI を配置するというのは、表示とロジックの分離という面でも問題になるかもしれません。
こうしたことに対処するため、 Closure Library には decorate という別の方法も用意されています。これは HTML 内のタグを適切なコンポーネントに差し替えるというもので、とくにツールバーやメニュー等の表示によく利用されます。しかし、それに限らずアプリケーション全体の UI を decorate で構築することも可能なので、本日は decorate を行う独自コンポーネントの構築なども含めてご紹介します。
話題の Google+ も、デザイナーさんが作成したテンプレートを元に UI コンポーネントを配置しているとのことなので、おそらく decorate を最大限に活用しているはずです(実際にコードを解析したわけではないので、憶測ですが)。多数の UI 要素を持つアプリケーションを開発する際にはとくに便利なので、ぜひ参考にしてください。
decorate メソッドの基礎
decorate メソッドの説明をする前に、まずは通常の(JavaScript のみで行う) UI コンポーネントの生成方法を説明しておきます。以下は簡単なツールバーを表示する例です。
goog.require('goog.dom'); goog.require('goog.ui.Toolbar'); goog.require('goog.ui.ToolbarButton'); goog.require('goog.ui.ToolbarToggleButton'); var toolbar = new goog.ui.Toolbar(); var btn1 = new goog.ui.ToolbarButton('ボタン'); var btn2 = new goog.ui.ToolbarToggleButton('トグルボタン'); toolbar.addChild(btn1); toolbar.addChild(btn2); toolbar.render(goog.dom.getElement('toolbar'));
最初に必要なライブラリを goog.require で読み込み、ツールバー本体を作成します。そして、必要なボタンを同様に作成し、子コンポーネントとしてツールバーに追加します。最後に render() メソッドを呼ぶことで必要な DOM 要素がドキュメントに挿入され、 UI が表示されます。
これが decorate を使う方法だと、以下のようになります。
goog.require('goog.dom'); goog.require('goog.ui.Toolbar'); goog.require('goog.ui.ToolbarButton'); goog.require('goog.ui.ToolbarToggleButton'); var toolbar = new goog.ui.Toolbar(); toolbar.decorate(goog.dom.getElement('toolbar'));
単にツールバー本体を作成し、 decorate メソッドを呼ぶだけです。ツールバー内部に表示するボタンは、 HTML に記述します。以下、その記述例です。(ソースはこちら)
ツールバーの要素内にある div の CSS クラス名を見て、適切なボタンに差し替えてくれます。編集可能にしてありますので、ラベルを変更したり、新しいボタンを加えたりしてみてください。「ツールバーを作成する」ボタンをクリックすれば、ツールバーに反映されるはずです。各ボタンの div 要素内にはタグも書けるので、 img タグで画像ボタンにもできます。
どのクラス名でどの UI コンポーネントが表示されるかは Closure Library 本に一覧表を掲載していますので、参考にしていただければ。もっとも、だいたい予想がつくでしょうが :)
ただし、 goog.require で読み込んでいないクラスは、いくら CSS クラス名を指定しても表示されません。上記であれば、 ToolbarButton と ToolbarToggleButton しか読み込んでいないので、その他のコンポーネントは表示できません。また、当然ながら適切な CSS も読み込んでおかないと正しく表示されないので、注意してください。
ボタンクリックの処理
ボタンが配置できたので、それがクリックされた時の処理を記述してみましょう。これにはいろいろな方法がありますが、個人的にオススメなのは、 Toolbar などの親コンポーネントにイベントハンドラを設定し、 getId メソッドで target の ID を取得して処理を振り分ける方法です。
var toolbar = new goog.ui.Toolbar(); toolbar.decorate(goog.dom.getElement('toolbar')); goog.events.listen(toolbar, goog.ui.Component.EventType.ACTION, onAction); function onAction(e) { var id = e.target.getId(); switch(id) { case 'btn-save': // id1 のボタンの処理 break; case 'btn-load': // id2 のボタンの処理 break; // ... } }
各ボタンの DIV 要素に id 属性を付けておけば、その値がそのままコンポーネントの ID になります。したがって、 HTML には以下のように記述しておけば良いでしょう。
<div id="toolbar"> <div id="btn-save" class="goog-toolbar-button">保存</div> <div id="btn-load" class="goog-toolbar-button">読込</div> </div>
特筆すべき点は、イベントがコンポーネントの親子関係を辿って最上位のコンポーネントまで通知されることです。階層の深いところにあるボタンなどを、最上位のコンポーネントで一括処理できます。こうすることで処理の見通しが良くなり、ボタンの場所を移動してもコード修正が不要になります。デザイナーさんが自由に試行錯誤してもらえますね :)
さきほどのサンプルでも、クリックされたボタンの ID を alert() で表示するようになっていますので、 id 属性を追加して試してみてください。
独自のコンポーネントで decorate を利用する
goog.ui.Toolbar や goog.ui.Container といったクラスを利用すれば、上記のように decorate で子コンポーネントを配置できます。しかし、ときには独自に作成したコンポーネントで decorate を利用したい場合もあるでしょう。とくに、 Toolbar や Container 内に配置できるのは goog.ui.Control のサブクラスだけなので、それ以外のコンポーネントを配置したい場合は、コンテナとなるコンポーネントを独自に定義しなければなりません。
以下は、 decorate で子コンポーネントを作成する、最も単純なコンポーネントです。
goog.provide('myapp.MyComponent'); goog.require('goog.dom.classes'); goog.require('goog.ui.registry'); goog.require('goog.ui.MyComponent'); goog.require('goog.ui.CustomButton'); // 使用するコンポーネントを require する /** @constructor */ myapp.MyComponent = function(opt_domHelper) { goog.base(this, opt_domHelper); }; goog.inherits(myapp.MyComponent, goog.ui.Component); myapp.MyComponent.CSS_CLASS = goog.getCssName('myapp', 'mycomponent'); myapp.MyComponent.prototype.decorateInternal = function(element) { goog.base(this, 'decorateInternal', element); goog.dom.classes.add(element, myapp.MyComponent.CSS_CLASS); var contentEl = this.getContentElement(); var childEl = dom.getFirstElementChild(contentEl); while(childEl) { var nextEl = dom.getNextElementSibling(childEl); var decorator = goog.ui.registry.getDecorator(childEl); if(decorator) { decorator.setElementInternal(childEl); // addChild() の前に要素を設定しておく this.addChild(decorator); decorator.decorate(childEl); } childEl = nextEl; } };
ポイントは、 decorateInternal で呼び出している goog.ui.registry.getDecorator です。この関数に DOM 要素を渡すと、その CSS クラス名を見て、適切なコンポーネントを作成して返してくれます。あとは addChild で子コンポーネントに登録し、子コンポーネントの decorate を呼び出せば、子コンポーネントが表示されます。
ただし、 goog.require していないクラスは表示できないというのは、前述したとおりです。
独自コンポーネントを decorate で配置できるようにする
せっかくなので、 MyComponent も decorate で他のコンポーネントの中に配置できるようにしてみましょう。これは非常に簡単で、以下のコードを上のソースに加えるだけです。
goog.ui.registry.setDecoratorByClassName( myapp.MyComponent.CSS_CLASS, function() { return new myapp.MyComponent(); });
第一引数に CSS クラス名、第二引数にファクトリ関数を指定して goog.ui.registry.setDecoratorByClassName を呼び出します。こうしておくと、 getDecorator に指定した CSS クラス名を持つ DOM 要素が渡されたときに、ファクトリ関数が呼ばれて適切なコンポーネントが作成される、という仕組みです。
以上、本日は HTML をもとに UI コンポーネントを作成・配置する decorate の使い方をご紹介しました。デザイナーさんとの共同作業では非常に便利ですし、また個人で開発している場合もメンテナンスが楽になるでしょう。 render と decorate をうまく使い分けて、効率よく開発したいものですね。
詳しくはこちらの記事をどうぞ!
この記事にコメントする