レベル: 中級 米持 幸寿, テクノロジー・エバンジェリスト, IBM
2009年 02月 13日 この記事は、IBM developerWorks主催の渋谷テクニカルナイトで2008年10月10日に実施したセミナーの内容を記事に起こしたものです。講演資料、MP3音声ファイルおよびサンプルデータがサイトからダウンロードできます。
はじめに
DojoツールキットではJavaScriptのオブジェクト指向開発を助けるために、JavaScriptの言語仕様を拡張しています。本記事では、その基本となるDojoツールキットでのクラス宣言や継承の方法を解説します。
この記事では、Dojoツールキットの利用方法を既に知っていることを前提にしています。
JavaScriptのオブジェクト指向とは?
JavaScriptと言えば「スクリプト言語」「関数」「HTMLの付属品」というようなイメージが強いと思います。しかし、JavaScriptでもオブジェクト指向開発が可能なことを皆さんはご存じでしょうか。
オブジェクト指向という言葉を聞いて、オブジェクト指向を知っている人なら何を思い浮かべるでしょうか。多くの場合「クラス」「継承」「インスタンス」「クラス変数」「クラスメソッド」「抽象クラス」というようなことを思い浮かべるに違いありません。しかし、JavaScriptのオブジェクト指向はこれとは違う概念で動作するようになっています。
JavaScriptでは、ほとんどのものをオブジェクトとして扱っています。リテラル、変数、メソッドさえもオブジェクトです。オブジェクトには参照名と値があり、複合物や配列を作ることもできます。複合物は { と } で囲んだ、名前:値を並べたものとなります。間をカンマ「,」で分けます。配列は [ と ] で囲み、間をカンマ「,」で分けます。項目に名前は付けられません。
JavaScript のオブジェクトの例
people1 = {
name : "米持幸寿",
getName : function() {
return this.name;
}
}
|
JavaScript の配列の例(関数が並んでいる配列)
methods = [
function() { /* なんらかの処理 */ },
function() { /* なんらかの処理 */ },
function() { /* なんらかの処理 */ }
]
|
クラスのようなものを作りインスタンス化することもできます。その場合、コンストラクターとなる関数を定義し、その中にクラス変数やクラスメソッドを定義していきます。また、「プロトタイプ」という機能があり、この機能を利用すると継承が可能ですし、メソッドを差し替えることができますのでメソッドのオーバーライドが可能、ということになります。
しかし、これらはどちらかと言うと「手作り」感たっぷりのオブジェクト指向です。一般的にオブジェクト指向言語として知られているC++、Java、C#のような宣言型のオブジェクト指向言語とは違う感覚でプログラミングをしますし、そもそもそういうことを考慮しなくても使える点が、JavaScriptがオブジェクト指向言語としてあまり認知されない理由かもしれません。
この記事はprototypeを基本としたJavaScriptのオブジェクト指向を学んでもらおうというものではないので、これくらいでやめておきます。ポイントは「JavaScriptのオブジェクト指向は、よく知られているものとは何か違う」ということです。
クラス宣言「dojo.declare」
Dojoツールキットでは、コアライブラリにおいてJavaScriptの言語仕様を拡張し、一般的なオブジェクト指向言語に似た形でオブジェクト指向開発(クラス宣言やインスタンス化)を実現できるようにしています。
Dojoツールキット(コア)がロードされていると、JavaScriptにおいて「dojo.declare」関数でクラス宣言をすることができます。
図1 dojo.declareによるクラス宣言
これで、いくらか一般的なオブジェクト指向に近い形になります。すこし蛇足になりますが、Javaで書くとしたらこうなるでしょう。
リスト1 Javaで表現(※JavaScriptには引数などの型宣言がないため、???で表現しています)
package myModule;
public class myClass extends myBaseClass {
public myProp= "";
public myClass(??? myProp){
this.myProp = myProp;
…
}
public ??? myFunc(){
…
}
}
|
dojo.declareを使う場合、名前に接頭語(プリフィクス、名前空間、みたいなもの)を付けることができるようになります。例えば、yone.MyClassのようにです。
さて、では実際に動くコードで動作を確認していきましょう。sample01.htmlを見てください。dojo.require の直後にdojo.declareがあります。ここでは、yone.MyClassが宣言されます。何も継承していないので二番目の引数にnullが指定されています。コンストラクターもクラス変数もクラスメソッドもありません。
リスト2 sample01.html
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>クラスの定義:1ファイルバージョン</title>
<script type="text/javascript" src="../dojo-release-1.1.1/dojo/dojo.js"
djConfig="parseOnLoad: true"></script>
<script type="text/javascript">
dojo.require("dojo.parser");
dojo.require("dijit.form.Button");
dojo.declare(
"yone.MyClass",
null,
{
}
);
dojo.addOnLoad(function(){
dojo.connect(dojo.byId("button1"), "onclick", function(){
var myClass = new yone.MyClass();
alert(myClass);
});
});
</script>
</head>
<body>
<div dojoType="dijit.form.Button" id="button1">クラス生成</div>
</body>
</html>
|
このコードでは、ロードされたときにdojo.declare()が呼ばれ、yone.MyClassが実行されます。ボタンをクリックするとnewされ、そのオブジェクトが生成されalertで表示されます。実行すると次のようになります。
図2 sample1の実行結果
継承
次に継承を試します。sample02.htmlでは、dijit.form.Buttonクラスを継承しています。
リスト3 sample02.html
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>クラスの定義:1ファイルバージョン</title>
<script type="text/javascript" src="../dojo-release-1.1.1/dojo/dojo.js"
djConfig="parseOnLoad: true"></script>
<script type="text/javascript">
dojo.require("dojo.parser");
dojo.require("dijit.form.Button");
dojo.require("dijit.form.Button");
dojo.declare(
"yone.MyButton",
dijit.form.Button,
{
}
);
</script>
</head>
<body>
<div dojoType="yone.MyButton" id="button1">米持のボタン</div>
</body>
</html>
|
このように宣言すると「MyButton」クラスは、dijit.form.Buttonと同じ機能を持ちますので、dojoTypeに指定することができます。これでボタンが表示されるようになります。
クラスを別ファイルに分ける
クラスは一旦作成したらさまざまなアプリケーションで共有して使いたいものです。となると、同じファイルに定義されているだけでなく、別のjsファイルから読み込むことができなければいけません。
Dojoツールキットでは、クラスが定義されているjsファイルをdojo.requireで読み込みますが、このとき読み込むファイルの検索方法に一定のルールがあります。
例えば、先ほどのyone.MyButtonクラスを読み込む場合、名前をピリオドで分解し、Dojoツールキットのルート(dojoディレクトリの一つ上のディレクトリ)からフォルダ名として探しにいきます。yone.MyButtonなら、dojoフォルダと横並びのyone\MyButton.js というファイルを探します。
図3 MyButton.jsを配置する
もう一つのルールとして、クラスが定義されているファイルを読み込むとき、「このファイルでyone.MyButtonが宣言されるよ」ということを名言しなければいけません。これは、クラス宣言のされたファイルを一度読み込んだら二度と読み込まないように、クラス名を登録するためです。そのための関数がdojo.provideです。
リスト4 yone\MyButton.js
dojo.provide("yone.MyButton");
dojo.require("dijit.form.Button");
dojo.declare(
"yone.MyButton",
dijit.form.Button,
{
}
);
|
このファイルを、dojoフォルダと同じ並びの「yone」フォルダを作ってその下に「MyButton.js」というファイル名で保存します。そして、このクラスを使いたい時は「dojo.require("yone.MyButton");」としてあげると、見つけ出して読み込んでくれます。
リスト5 sample03.html
:
<script type="text/javascript" src="../dojo-release-1.1.1/dojo/dojo.js"
djConfig="parseOnLoad: true"></script>
<script type="text/javascript">
dojo.require("dojo.parser");
dojo.require("yone.MyButton");
</script>
</head>
<body>
<div dojoType="yone.MyButton" id="button1">米持のボタン</div>
</body>
:
|
自分のクラスをdojo.requireするのは、なんかうれしい瞬間です。
別の場所に配置する
dojoフォルダと並ぶ箇所に自分のクラスを配置する、ということになると、アプリケーションとDojoツールキットをまぜてしまうことになるため、あまり望ましくありません。例えば、Dojoツールキットを取り替えたりするときにフォルダごとごっそり、ってのができなくなりますからね。
では、Dojoツールキットのルートフォルダより外へ自分のクラスを置く方法をお教えしましょう。
Dojoのルートにクラスを置く場合、置く側が工夫するのではなく使う側が工夫する、という考えかたに基づいています。これは、一度作ったクラスファイルはそのままでよく、使うときに「どこに置いてあるよ」と宣言して使う、という考え方と思えばよいです。
「どこに置いてあるよ」と宣言する方法は2つあります。一つ目はdojo.registerModulePath関数を使う方法です、引数としてクラスの接頭辞とdojo.jsからの相対パスを指定します。
例えば、MyButton.js をDojoツールキットのルート(dojoフォルダの上のフォルダ)と横並びにある「yone2」フォルダに置いてあり、yone2.MyButtonを提供しているとすると、使う時にsample04.htmlのようにファイルの場所を設定します。
リスト6 sample04.html
:
<script type="text/javascript" src="../dojo-release-1.1.1/dojo/dojo.js"
djConfig="parseOnLoad: true"></script>
<script type="text/javascript">
dojo.require("dojo.parser");
dojo.registerModulePath("yone2", "../../yone2");
dojo.require("yone2.MyButton");
</script>
</head>
<body>
<div dojoType="yone2.MyButton" id="button1">米持のボタン</div>
:
|
この指定は、dojo.js(Dojoのブートローダー)の読み込み引数であるdjConfigに指定することもできます。文法は次のとおりです。
リスト7 djConfigの例
djConfig="parseOnLoad: true, modulePaths:{yone2: '../../yone2'}"
|
ウィジェットレイアウトの再利用
先ほどの例でdijit.form.Buttonクラスを継承していたように、Dojoツールキットが提供するウィジェットを継承して新しい挙動をするクラスを作ることは可能です。しかし、より大きな部品に複数のウィジェットが組み込まれているような「複合部品」を作るには便利な方法ではありません。
複数の部品が組み込まれている部品を作るには「テンプレート・ウィジェット」という機能を使うと便利です。ここでのテンプレートとは、ウィジェットをHTML構造=DOMから生成していく方法、と考えてください。
テンプレートの指定
テンプレート・ウィジェットを作るにはdijit._Widget(Dijitのベースクラス)とdijit._Templated、2つのクラスを多重継承します。dojo.declareの継承元クラスを2つ以上指定するには、配列にします。
また、テンプレートとなるHTMLは、次の2つの方法で定義することができます(DOMで設定する方法もありますが、ここでは割愛します)。
内包
クラスを定義するときにtemplateStringというクラス変数を定義します。
外部提供
templatePathに指定したURLからファイルを読み込みます。
文字を直接書く場合、リスト8のようになります(※templateStringの後ろは、実際には改行せずに一行で書くか、+でつなぎます)。
リスト8 HelloWorld.js
dojo.provide("yone.HelloWorld");
dojo.require("dijit._Widget");
dojo.require("dijit._Templated");
dojo.declare(
"yone.HelloWorld",
[dijit._Widget,dijit._Templated],
{
templateString:
"<table border='1'><tr><td>Hello</td><td>World</td></tr></table>"
}
);
|
外部にファイルを置く場合、リスト9のようになります。
リスト9 HelloWorld2.js
dojo.provide("yone.HelloWorld2");
dojo.require("dijit._Widget");
dojo.require("dijit._Templated");
dojo.declare(
"yone.HelloWorld2",
[dijit._Widget,dijit._Templated],
{
templatePath: dojo.moduleUrl("yone", "templates/HelloWorld.html")
}
);
|
変数の埋め込み
テンプレートに、クラスの変数を埋め込むには${変数名}を使います。
例えば、クラスにtextという変数があるとき、そのtextの初期値をテンプレートに埋め込むには、次のような記述をします。
リスト10 変数の埋め込み(クラス変数とテンプレート)
text : "",
templateString "${text}さん、こんにちは。"
|
リスト11 埋め込んだ変数の利用(属性として指定することで変数の初期値を書き換えられます)
<div dojoType="yone.MyDijit" text="米持"></div>
|
子要素を内包する
子要素に指定された内容を包含するにはdojoAttachPointを使います。自分が定義しているテンプレートの中で、子要素をはめ込みたい場所のノードにdojoAttachPoint='containerNode'を指定します。
リスト12 子要素の埋め込み(テンプレート)
<table border='1'>
<tr><td>
<span dojoAttachPoint='containerNode' ></span>さんこんにちは
</td></tr>
</table>
|
リスト13 子要素の埋め込み(利用時)
<div dojoType="yone.MyDijit">米持</div>
|
まとめ
もう少し詳しく説明すれば、細かいところでJavaやC++と挙動が違うところはありますが、以上の知識があれば、とりあえずはオブジェクト設計をしてクラスと継承関係を作ってJavaScriptでプログラミングができるようになります。また、Dojoツールキットが提供する既存コンポーネントなどを継承し、改造して部品を作って行ったり、再利用したりすることができるので、開発効率がグンとアップするはずです。
参考文献
著者について  | |  | 米持 幸寿 (ヨネモチ ユキヒサ) 日本アイ・ビー・エム公認ソフトウェア・エバンジェリスト。alphaWorks、developerWorks、インキュベーション系製品、アセットなどのテクノロジーの推進をしつつ、テクノロジー戦略、エバンジェリストチームをリードしている。講演や執筆も多数。主な著書に「かんたんサーバーサイドJava」(翔泳社)、「Webサービス完全解説」(翔泳社)がある。developerWorks Japan ブログ 「米持幸寿のブログ」 |
記事の評価
|