目次


Dojo および IBM WebSphere Portalによるクライアント・サイドのポートレット間通信の実装

Comments

はじめに

多くのポータル開発者がWebSphere Portalを使用し、イベントとデータを共有してユーザー・エクスペリエンスを強化する連携ポートレットを作成してきました。たとえば、1つのポートレットで選択された内容が送信されると、関連する複数のポートレットで更新された情報が表示され、ポートレット間の表示の同期がとられます。この機能はサーバーに実装されており、ポートレットからサーバーに送られるアクション要求に基き、ページが更新される前にデータ転送を実施できます。

しかし、Ajax機能をポートレットに追加すると、アクション要求の送信またはページ更新を行わずにポートレットに表示されるデータを動的に更新できるため、この手法は使用されなくなり始めました。この記事では、Dojo JavaScriptツールキット を使用して、イベントとデータをブラウザー内のポートレット間で共有し、ポートレットを動的に更新して連携をサポートする方法を紹介します。

Dojoとは?

Dojo は動的なWebアプリケーションの開発に使用できるオープン・ソースのJavaScriptツールキットです。Dojoには、Ajaxのサポート、DOM操作ツール、イベント処理システム、およびカスタマイズ可能なウィジェット・セットなど多数のライブラリーが含まれています。Dojoは柔軟なパッケージング・システムでこれらのライブラリーを結合しているため、開発者は必要なライブラリーだけをインポートできます。ライブラリー間のすべての依存関係は、Dojoによって自動的に解決されます。
The Dojo toolkit(US)

Dojoの使用

動的なパッケージング・システムにより、 Dojoライブラリーの使用方法は、従来のJavaScriptのインクルード方法と異なります。Dojoライブラリーのインクルードは、以下に示す2つのステップの手順となります。

  1. Dojoブートストラップをインクルードします。
    <script type="text/javascript" src="/path/to/dojo/dojo.js"> 
    </script>
  2. アプリケーションで必要なDojoライブラリーを特定します。dojo.requireステートメントを使用して、Dojoウィジェット管理関数(dojo.widget.*)およびButtonウィジェット(dojo.widget.Button)をインクルードします。
     <script type="text/javascript">
     dojo.require("dojo.widget.*");
     dojo.require("dojo.widget.Button");
     </script>

dojo.requireステートメントは、アプリケーションで使用するDojoリソースを指定します。Dojoブートストラップはこれらのdojo.requireステートメント用にページをスキャンし、対応するライブラリーを動的にインクルードし、これらのリソースを利用可能にします。また、動的なロード・プロセスはDojoライブラリー間のすべての依存関係を処理するため、開発者は直接使用するリソース用のrequireステートメントだけを追加します。

WebSphere PortalでのDojoの使用

Dojo独自のロード手法により、ポートレット開発者に1つの課題がもたらされます。Dojoブートストラップdojo.jsは、HTMLページで1度だけインクルードする必要があります。ブラウザーによっては、dojo.jsを複数回インクルードすると、エンド・ユーザーにJavaScriptエラーがレポートされることがあります。WebSphere Portalでこの問題を避ける最も簡単な方法は、テーマのJSPでdojo.jsをインクルードし、必要に応じてdojo.requireステートメントをテーマおよびポートレットJSPに配置することです。

しかし、サンプル・アプリケーションでは、テーマを変更せずにポートレットをデプロイできるようにするつもりでした。このため、ポートレット・プロジェクトでDojoライブラリーをインクルードし、条件分岐ローダーをJavaScriptで書きました。

条件分岐ローダーは、dojoLoader.jspファイルに含まれています。

<script type="text/javascript">
	var path = "<%=request.getContextPath() %>/dojo/dojo.js";
	if(typeof dojo=="undefined") {
		document.write('<S');
		document.write('CRIPT type=\"text/javascript\" src=\"');
		document.write(path);
		document.write('\" ><\/S');
		document.write('CRIPT>');
	} 
</script>

このコードはdojo JavaScriptオブジェクトの存在をチェックします。オブジェクトが定義されていない場合は、dojo.jsをインクルードするスクリプトを現在のHTML文書に挿入します。これを実行するJavaScriptコードは少し読みにくいですが、文字列中の<script>または</script>のすべてのインスタンスを分離する必要があります。そうしないと、一部のブラウザーは、スクリプトの実際の開始および終了とみなして解析を試みてしまいます。テーマのJSPを変更する方法は、これよりもさらにクリーンなソリューションです。この条件分岐ローダーの手法は、テーマのJSPを変更できない場合にのみ使用してください。

Dojoイベントの使用

Dojoは、開発者にアプリケーションのコンポーネント間の接続を可能にするイベント通信システムを持ちます。この機能は、イベントを任意のプロパティー、オブジェクト、または要素にマッピングする従来のDOMイベントよりも優れており、アスペクト指向プログラミング(AOP)またはイベントのパブリッシュ/サブスクライブ(publish/subscribe)などの高度な機能を備えています。この記事では、イベントのパブリッシュ/サブスクライブ・システムに焦点を当て(これをトピックと呼びます)、これを使用してポートレット間の通信を行う方法を説明します。

Dojoのイベント・パブリッシュ/サブスクライブ・システムは、共有トピックまたはキュー名に基づいてコンポーネント間で匿名通信を行う方法を提供します。JavaScriptコンポーネントはDojoイベント・ライブラリーを使用して、トピックをパブリッシュまたはサブスクライブすることができます。パブリッシュ側のコンポーネントは、イベントに関連するデータを使用してオブジェクトを作成し、このオブジェクトをトピックにパブリッシュします。このトピックの任意のサブスクライバーは、パブリッシュされたオブジェクトをパラメーターとして渡されて呼び出され、新しいデータに応答することができます。たとえば、サブスクライバーは非同期呼び出しを実行し、その表示を更新できます。

この設計は、クライアント・サイドのポートレット間通信に非常によく対応しています。ポートレットは<portlet:namespace>タグを使用してそのDOMおよびJavaScript要素を識別するため、1つのポートレットが他のポートレットのHTMLからのDOMイベントをlistenすることは非常に困難です。Dojoイベント・トピックを使用すると、複数のポートレットが独立性を維持しながら、1つのポートレットからのDOMイベントに応答できます。たとえば、動的コンポーネントが非同期呼び出しを実行して情報を取得し、イベント・トピックを使用して、この更新済み情報を他のポートレットにも利用可能にすることができます。

dojo.event.topic APIのまとめ

DojoトピックAPIの概要、およびさまざまなメソッドを使用したJavaScriptコンポーネント間通信の方法を以下に示します。

dojo.event.topic.registerPublisher (String topic, Object obj, String funcName)
関数をトピックのパブリッシャーとして登録します。以降、この関数の呼び出しによってイベントがトピックにパブリッシュされ、トピックのすべての登録済みリスナーに関数の引数が渡されます。

dojo.event.topic.publish(String topic, Object message )
渡されたオブジェクトを特定のトピックに手動でパブリッシュします。このオブジェクトはトピックのすべての登録済みリスナーに渡されます。

dojo.event.topic.subscribe(String topic, Object obj, String funcName)
関数を特定のトピックにサブスクライブします。以降、このトピックにパブリッシュされたイベントにより、サブスクライブされた関数が呼び出されます。たとえパブリッシャーによってトピックが参照される前であっても、リスナーはいつでもトピックをサブスクライブすることができます。サブスクライバーまたはパブリッシャーによって初めてトピックが参照されるときに、Dojoはトピックを作成します。

dojo.event.topic.unsubscribe(String topic, Object obj, String funcName )
特定のトピックから関数をアンサブスクライブします。

dojo.event.topic.destroy(String topic)
トピックを破棄し、トピックのすべてのリスナーの登録を解除します。

パブリッシャーまたはサブスクライバーを参照するときは、トピックAPIがオブジェクトおよび関数名を指定します。この手法はDojoウィジェット・システムで機能するよう設計されているため、特定のウィジェット・インスタンスに対し登録を行うことができます。ウィジェットを使用しない場合は、トピック・サブスクライバーをJavaScriptオブジェクトとしてインプリメントするか、単純な関数としてインプリメントするかを選択できます。

サンプル・アプリケーションでは、トピック・サブスクライバーはJavaScriptオブジェクトとしてインプリメントされています。代わりに単純なJavaScript関数を使用したい場合は、そのオブジェクトをwindowとして指定することにより、それを行うことができます。

以下に例を示します。

dojo.event.topic.subscribe( "/myApp/myTopic", window, "myHandler");

このコードは、トピック"/myApp/myTopic"へのサブスクリプションを作成します。このトピックにイベントをパブリッシュすると、パブリッシュされたイベントがパラメーターとして渡されて、JavaScript関数myHandlerが呼び出されます。

動作可能なサンプル

1つのイベント・パブリッシャー・ポートレット、2つのリスナー・ポートレット、および1つのトピックで構成される簡単なサンプルを調べてみましょう。パブリッシュされたイベント・オブジェクトには、リスナー・ポートレットによって消費される2つのデータ・フィールドがあります。ポートレット間の通信におけるイベントの順番は以下のようになります。

図1 サンプル・ポートレットのイベントの流れ
図1 サンプル・ポートレットのイベントの流れ
図1 サンプル・ポートレットのイベントの流れ
  1. ユーザーがパブリッシャー・ポートレットで「Publish Event」ボタンをクリックします。これにより、JavaScriptハンドラー関数をトリガーするDOMイベントが作成されます。
  2. このハンドラーはDojoライブラリーを使用して独立したイベント・オブジェクトを作成し、これをトピックにパブリッシュします。この新規イベントは、すべてのトピック・サブスクライバーによって、オリジナルの「Publish Event」ボタンに接続することなく、受信されます。
  3. リスナー・ポートレットには、Dojoによって呼び出され、イベント・オブジェクトを受信するトピック・サブスクリプション関数があります。これらの関数は、イベントに含まれる情報を使用してポートレットの表示を更新します。

この例では、パブリッシャー・ポートレットは「Publish Event」ボタンがクリックされた回数を示すカウントを保持しています。パブリッシュされたイベントには、このカウントとテキスト・メッセージが含まれています。各リスナー・ポートレットは、イベント・メッセージのいずれかの要素を用いてポートレットの表示を更新します。リスナー・ポートレットがイベントを処理するたびに、更新されたエリアが点滅して変更を強調します。この点滅効果は、イベントのパブリッシュ/サブスクライブ・サイクルの一部ではありません。デモの効果を高めるために追加されたものです。

イベント・パブリッシャー・ポートレット

イベント・パブリッシャー・ポートレットには、ユーザーがイベントをパブリッシュするためにクリックするボタンと、ボタンがクリックされた回数をトラッキングするカウンターが必要です。リスト1に、HTML/JavaScriptソース・コードを示します。このコードは、ボタンを作成し、ボタンのクリック・イベントを処理するハンドラー関数を作成します。そして、カウンターを1つ増加させ、最後にイベントをパブリッシュします。

リスト1. イベント・パブリッシャー・ポートレットのソース
<P>
The button below will publish a Dojo event to all registered subscribers.<br />
<button dojoType="Button" 
  widgetId="<portlet:namespace />_publishButton">Publish Event
</button>
</P>
<script type="text/javascript">
	// Load Dojo's code relating to widget managing functions
	dojo.require("dojo.widget.*");	
	// Load Dojo's code relating to the Button widget
	dojo.require("dojo.widget.Button");
	// Load Dojo's event handling functionality
	dojo.require("dojo.event.*");
	dojo.require("dojo.event.topic.*");

	// Register our init function
	dojo.addOnLoad( <portlet:namespace />_init ); 
	
	// Click counter
	var <portlet:namespace />_counter = 0;

	// Init function for this portlet's Dojo components
	function <portlet:namespace />_init() {
		var pubButton = dojo.widget.byId("<portlet:namespace />_publishButton");
		dojo.event.connect(pubButton,'onClick','<portlet:namespace />_onButton');
	}

	// Button onClick handler
	function <portlet:namespace />_onButton() {
		<portlet:namespace />_counter++;
		var evt = {
			count: <portlet:namespace />_counter,
			message: "Click message #" + <portlet:namespace />_counter
		};
		dojo.event.topic.publish("/myApp/myTopic", evt);
	}
</script>

このコードを読むと、dojoTypeタグが「Publish Event」ボタンをDojo Buttonウィジェットとして定義していることがわかります。JavaScriptセクションでは、dojo.requireステートメントが、このコンポーネントによって使用されるDojoライブラリーを定義しています。dojo.addOnLoad()を呼び出すと、ページDOMのロードの完了後、Dojoによって呼び出される関数のリストにinit関数が追加されます。文書のロード後に実行しなければならない複数のinit関数をDojoが処理する点を除き、この手法はwindow.onLoadハンドラーでの関数の設定に似ています。この手法は、window.onLoadハンドラーが上書きされることを気にせずに、各ポートレットがJavaScript init関数を指定することを可能にするため、ポータル・アプリケーションではたいへん役に立ちます。

このポートレットのJavaScript関数および変数は、標準ポートレット・タグ・ライブラリーの<portlet:namespace>タグを使用してネームスペースが定義されています。複数のポートレットで1つのページを組み立てるときにネームスペースの競合を防ぐために、このタグはポートレット・インスタンス固有の英数文字列に置き換えられます。init関数はwidgetIdによって「Publish Event」ボタンを特定し、そのウィジェットのonclickイベントをイベント・ハンドラーに接続します。イベント・ハンドラーはカウンターを増加させ、イベント・オブジェクトを作成します。さらに、dojo.event.topic.publish APIを使用して、myApp/myTopicと呼ばれるトピックにこのイベント・オブジェクトをパブリッシュします。イベント・オブジェクトには、以下の2つの要素があります。

  • count、カウンターの値です。
  • message、カウンターを含むテキスト・メッセージです。

Dojoはイベント・オブジェクトをそれぞれのイベント・サブスクライバーに渡します。イベント・サブスクライバーは、その内容を適切な方法で処理します。

イベント・サブスクライバー・ポートレット

イベント・サブスクライバー・ポートレットには、パブリッシュされたイベントからの情報で(その到着時に)更新される表示フィールドが必要です。また、サブスクライバー・ポートレットは、そのイベント処理関数をトピック用に登録しなければなりません。イベント・パブリッシャーで使用する方法と同様に、init関数によってこの登録を行うと便利です。イベント・サブスクライバー・ポートレットのHTML/JavaScriptソースをリスト2に示します。

リスト2. イベント・サブスクライバー・ポートレットのソース
<P>Button Click Count<br />
<span id="<portlet:namespace />_count" style="font-size : x-large;">0</span>
</P>
<script type="text/javascript">
  // Load Dojo's event handling functionality
  dojo.require("dojo.event.*");
  dojo.require("dojo.event.topic.*");
  dojo.require("dojo.lfx.html");
  dojo.require("dojo.gfx.color");

  dojo.addOnLoad( <portlet:namespace />_init ); 

  // Init function for this portlet's Dojo components
  function <portlet:namespace />_init() {
	<portlet:namespace />_myListener = new <portlet:namespace />_listener();
	<portlet:namespace />_myListener.load();
  }

  // event listener object
  function <portlet:namespace />_listener() {
    // Event handler - override for each portlet
    this.handleEvent = function(args) {
	  var countElement = document.getElementById("<portlet:namespace />_count");
	  countElement.innerHTML = args.count;
	  dojo.lfx.html.highlight("<portlet:namespace />_count",
	    dojo.gfx.color.named.blue,500).play();
     }
	    
    this.load = function() {
        dojo.event.topic.subscribe("/myApp/myTopic", this, this.handleEvent);
     }
  }
</script>

イベント・パブリッシャー・ポートレットと同様に、dojo.requireステートメントはこのコンポーネントに必要な Dojoライブラリーを指定し、dojo.addOnLoadはポートレットのinit関数を登録します。init関数は新しいイベント・リスナー・オブジェクトを作成し、そのloadメソッドを呼び出すことにより、/myApp/myTopicと呼ばれるトピックにこのイベント・リスナー・オブジェクトをサブスクライブします。このトピックにイベントがパブリッシュされると、リスナー・オブジェクトのhandleEventメソッドが呼び出されます。イベント・ハンドラーはメッセージのカウントが表示されるDOM要素を見つけ、イベント・オブジェクトによって渡された新しい値でこの要素のHTMLを更新します。

その後、ハンドラーはdojo.lfx.html.highlight()を呼び出してビジュアルな強調効果を生成し、更新されたエリアにユーザーの注意を引きつけます。

サンプル・アプリケーションには、同じ基本機能を持つ2つ目のイベント・サブスクライバー・ポートレットがあります。このポートレットは、カウントの代わりにイベント・メッセージを表示します。どちらのリスナーもイベントのパブリッシュから同じオブジェクトを受け取りますが、各リスナーはオブジェクトの異なるメンバーを使用して動作します。

サンプルの実行

サンプル・アプリケーションを実行し、ポートレットがどのように連携して動作するのか確認しましょう。

  1. この記事で説明したポートレットが含まれる、サンプルのRational Application Developer 7プロジェクト交換ファイルをまだダウンロードしていない場合は、ダウンロードします。
  2. このファイルをApplication Developerにインポートします。
  3. ポートレットをデプロイするには、WARファイルをエクスポートしてWebSphere Portalにデプロイするか、統合テスト環境でポートレットを実行します。
  4. 新しいテスト・ページを作成し、3つのポートレットをすべてこのページに追加します。
  5. アプリケーションをテストするには、手順4で作成したテスト・ページに移動し、「Publish Event」ボタンをクリックします。

ボタンをクリックするたびに、点滅する2つのイベント・リスナー・ポートレットがページに表示され、パブリッシャー・ポートレットによってパブリッシュされたカウントおよびメッセージによって更新されます。

図2. サンプル・ポートレット
図2. サンプル・ポートレット
図2. サンプル・ポートレット

まとめ

Ajax機能をWebアプリケーションに追加すると、ユーザー・エクスペリエンスを大幅に改善できます。WebSphere Portal環境では、ポートレットでAjaxを使用すると、連携ポートレットなどの高度な機能を使用できなくなるトレードオフが生じることがあります。しかし、Dojoツールキットを使用すれば、クライアント・サイドのポートレット間通信を追加できます。ポートレット開発者はこの手法を使用することにより、ページの更新を最小限に抑え、応答性を改善しながら、同期された状態を保つ動的な連携ポートレットを作成できます。


ダウンロード可能なリソース


関連トピック


コメント

コメントを登録するにはサインインあるいは登録してください。

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Lotus, WebSphere
ArticleID=342046
ArticleTitle=Dojo および IBM WebSphere Portalによるクライアント・サイドのポートレット間通信の実装
publish-date=03142007