Ajax トランザクション Dynamic Faces コンポーネントを使用した Ajax チャットルームの構築
寄稿者: Matthew Bohm、Chris Kutler、および James Branam
このチュートリアルでは、Ajax 対応でないコンポーネントである POJC (Plain Old JavaServer Faces Components) を使用して Ajax チャットルーム Web アプリケーションを構築します。JavaServer Faces テクノロジの拡張である Dynamic Faces テクノロジを使用することで、Ajax 機能の実装を簡単に実現できます。特に、Dynamic Faces コンポーネントライブラリ (0.2) に含まれる Ajax トランザクションコンポーネントを使用します。このコンポーネントを使用することで、デザイン時に Ajax 機能を視覚的に構成できます。チャットルームアプリケーションは、次に示すように、1 ページに描画されるページから構成され、このページ全体が Ajax ベースです。従来のページ送信は行われません。具体的には、Ajax 要求は、ユーザーがテキストフィールドにテキストを入力して「Send」ボタンをクリックし、Enter キーを押して要求を送信することで送信されます。このページではまた、コメントのトランスクリプトを更新する目的で、サーバーを継続的にポーリングするために Ajax 要求を使用します。
|
目次
このチュートリアルに従うには、次のソフトウェアとリソースが必要です。
| NetBeans IDE |
Web および Java EE の version 6.1 または 6.0 |
| Java Development Kit (JDK) |
version 6 または
version 5 |
JavaServer Faces コンポーネント /
Java EE プラットフォーム |
1.1 と J2EE 1.4
|
| GlassFish アプリケーションサーバー |
V2 |
| Travel データベース |
任意 |
チュートリアルの要件
開始する前に、コンピュータに次のソフトウェアをインストールしておく必要があります。
- Web および Java EE 機能 (「Web および Java EE」のダウンロードおよび「すべて」のダウンロードに含まれる) を持つ NetBeans IDE 6.0 または 6.1。(ダウンロード)
- Visual Web サンプルプラグイン。このプラグインには、Dynamic Faces コンポーネントライブラリ (0.2) が含まれます。『Visual Web サンプルプラグインのインストール』のセクション「プラグインのインストール」の指示に従います。Visual Web JSF Post Release Samples と Visual Web JSF Backwards Compatibility Kit の両方のエントリを選択するようにしてください。
プロジェクトの作成
- メインメニューから「ファイル」>「新規プロジェクト」を選択します。
- 「新規プロジェクト」ウィザードで、「カテゴリ」リストから「Web」を選択し、「プロジェクト」リストから「Web アプリケーション」を選択して、「次へ」をクリックします。
- プロジェクトに AjaxChatRoom という名前を付け、プロジェクトの場所を選択します。
「サーバー」に Sun Java System Application Server を選択し、「Java EE バージョン」に J2EE 1.4 を選択して、「ソースレベルを 1.4 に設定」と「主プロジェクトとして設定」の各チェックボックスが選択されていることを確認します。「次へ」をクリックします。
- 「Visual Web JavaServer Faces」を選択し、「完了」をクリックします。
配備記述子の設定
Dynamic Faces テクノロジでは、Web アプリケーションの配備記述子に初期化パラメータが必要です。
- 「プロジェクト」ウィンドウで、「Web ページ」>「WEB-INF」ノードを展開します。
- web.xml をダブルクリックすると、エディタが開いて内容が表示されます。
「エディタ」ツールバーの「サーブレット」をクリックし、次の図に示す「初期化パラメータ」の下に表示される「追加」ボタンをクリックします。
「初期化パラメータの追加」ダイアログで「パラメータ名」テキストボックスに「javax.faces.LIFECYCLE_ID」と入力し、「パラメータ値」テキストボックスに「com.sun.faces.lifecycle.PARTIAL」と入力して、「了解」をクリックします。
新しいパラメータがエディタに表示されます。
- 編集用ツールバーの「XML」をクリックし、生 XML の変更を確認するために
lifecycle を検索します。
- Ctrl-S キーを押して変更内容を web.xml ファイルに保存し、ファイルタブを閉じてファイルを閉じます。
プロジェクトへの Dynamic Faces コンポーネントライブラリの追加
「プロジェクト」ウィンドウで「コンポーネントライブラリ」ノードを右クリックし、次に示すように「コンポーネントライブラリを追加」を選択します。
「コンポーネントライブラリを追加」ダイアログで「Dynamic Faces コンポーネント (0.2)」を選択し、「コンポーネントライブラリを追加」をクリックしてダイアログを閉じます。
「パレット」ウィンドウに新しい「Dynamic Faces」セクションが表示されます。
アプリケーション Bean へのコードの追加
- 「プロジェクト」ウィンドウでプロジェクトの「ソースパッケージ」>「ajaxchatroom」ノードを展開し、「ApplicationBean1.java」をダブルクリックしてソースエディタでファイルを開きます。
ApplicationBean1.java の最下部にスクロールし、次のコードを終了中括弧の前に追加します。
| コード例 1: コメントを保存して匿名のユーザー名を生成するアプリケーション Bean コード |
private int nextAnonIndex;
private final int MAX_ENTRIES = 100;
//各配列の長さ 2 の文字列配列の一覧
private List entryList = new LinkedList();
//entryList と同じ内容を格納する配列
private String[][] entries;
public String[][] getEntries() {
synchronized(this.entryList) {
return this.entries;
}
}
public void addEntry(String username, String comment) {
if (comment == null || comment.length() < 1) {
return;
}
if (username == null) {
username = "anonymous";
}
synchronized(this.entryList) {
if (this.entryList.size() == MAX_ENTRIES) {
this.entryList.remove(0);
}
String[] entry = new String[]{username, comment};
this.entryList.add(entry);
this.entries =
(String[][])this.entryList.toArray(
new String[this.entryList.size()][entry.length]);
}
}
public synchronized String getNextAnonUsername() {
nextAnonIndex++;
return "anonymous" + nextAnonIndex;
} |
このコードでは、各コメントとそれに関連付けられたユーザー名をトランスクリプトエントリとして保存します。これは、長さ 2 の String[] オブジェクトです。エントリを entryList 変数と entries 変数の両方に保存します。コメントを addEntry メソッドを介して追加するか、またはトランスクリプトエントリを getEntries メソッドを介して取得する場合、クライアントコードは this.entryList 上でモニターを取得する (つまり同期する) 必要があり、必然的にパフォーマンスに影響しますが、データの整合性を維持します。トランスクリプト内のエントリの最大数は、MAX_ENTRIES によって維持されます。
ユーザーは http://サーバーの IP アドレス:8080/AjaxChatRoom?username=someuser の形式の URL を使用して、このアプリケーションにアクセスできるようになります。指定されたユーザー名は、ユーザーが送信した任意のコメントと一緒に表示されます。ユーザーが、ユーザー名を指定しない URL を使用してアプリケーションにアクセスする場合、ユーザーのコメントには匿名のユーザー名が表示されます。この場合、getNextAnonUsername メソッドの呼び出しによって、アプリケーション内で一意の匿名ユーザー名が生成されます。
ソース内で右クリックし、「インポートを修正」を選択します。
List クラスに複数の選択肢があるため、「インポートを修正」ダイアログが表示されます。
- 「了解」をクリックし、java.util.List を List の完全修飾名として受け入れます。
- ファイルを閉じて保存します。
ユーザー名を保存するためのコードの追加
- 「プロジェクト」ウィンドウで「ソースパッケージ」>「ajaxchatroom」>「SessionBean1.java」をダブルクリックし、ファイルをソースエディタで開きます。
SessionBean1.java の最下部にスクロールし、次のコードを終了中括弧の前に追加します。
| コード例 2: ユーザー名を保存するためのセッション Bean コード |
private String username;
public synchronized String getUsername() {
return username;
}
public synchronized void setUsername(String username) {
this.username = username;
} |
コード例 5 に示すように、ユーザーがコメントを送信すると、action メソッドによってセッションスコープからユーザー名が取得され、コメントと一緒に記録されます。同じセッションと関連付けられた複数のスレッドが取得メソッドと設定メソッドにアクセスする可能性があるため、これらのメソッドは同期されます。
- ファイルを閉じて保存します。
- 「プロジェクト」ウィンドウで「ソースパッケージ」>「ajaxchatroom」>「Page1.java」をダブルクリックし、そのページの Java ソースコードを開きます。
「ナビゲータ」ウィンドウで「prerender()」をダブルクリックし、ソースエディタ内のそのメソッドに移動します。
prerender メソッドに、次のボールドで示されるコードを追加します。
| コード例 3: ユーザー名を保存するための Page1 コード |
public void prerender() {
String username = (String)getExternalContext().getRequestParameterMap().get("username");
if (username != null) {
getSessionBean1().setUsername(username);
}
else if (getSessionBean1().getUsername() == null) {
getSessionBean1().setUsername(getApplicationBean1().getNextAnonUsername());
}
} |
現在の要求の間、そのページが最初に描画されており、ユーザーが URL にユーザー名を指定した場合、要求パラメータマップにそのユーザー名が含まれます。この場合、ユーザー名をセッション Bean に保存します。要求パラメータマップにユーザー名が含まれない場合、そのページが描画されるのがはじめてであり、ユーザーがユーザー名を URL に指定しなかったか、または現在の要求が Ajax 要求であるかの 2 つの可能性があります。前者の場合で、以前にユーザー名をセッション Bean に保存していなかった場合は、匿名のユーザー名を生成して保存します。Ajax 要求の場合、ページを最初に描画するとき、ユーザー名はすでにセッション Bean に保存されています。
「transcript」プロパティーの Page1.java への追加
Page1.java の最下部にスクロールし、次のコードを終了中括弧の前に追加します。
コード例 4:「transcript」プロパティーのコード |
public String getTranscript() {
String[][] entries =
getApplicationBean1().getEntries();
if (entries == null) {
return null;
}
StringBuffer sb = new StringBuffer();
for (int i = 0; i < entries.length; i++) {
String entryUsername = entries[i][0];
String comment = entries[i][1];
String color = "purple";
String username = getSessionBean1().getUsername();
if (username.equals(entryUsername)) {
color = "blue";
}
sb.append(
"<div><span style=\"font-weight:bold;color:");
sb.append(color);
sb.append("\">[");
sb.append(entryUsername);
sb.append("]</span> ");
sb.append(comment);
sb.append("</div>");
}
return sb.toString();
} |
このメソッドでは、トランスクリプトコンテンツを String オブジェクトとして提供します。エントリのユーザー名が SessionBean1 に保存されたユーザー名と同じ場合は青、それ以外の場合は紫で書式設定されます。これにより、ユーザー自身のエントリは青で表示され、ほかのチャットルーム参加者のエントリは紫で表示されます。
- (任意) 右クリックして「書式コード」を選択し、「すべて保存」ボタンを標準ツールバーからクリックして変更内容を保存します。
ユーザーインタフェースの作成
- Page1 のビジュアルデザイナに戻ります。
- パレットの「レイアウト」セクションからレイアウトパネルをドラッグします。
ビジュアルデザイナで、レイアウトパネルコンポーネントを選択し、右下のサイズ変更ボックスを右下にドラッグして、コンポーネントの幅が約 14、高さが約 10 になるようにします。
- 「プロパティー」ウィンドウで、レイアウトパネルコンポーネントの「
id」プロパティーを「transcriptPanel」に変更します。
「style」プロパティーの省略符号 (...) ボタンをクリックし、次の規則を「CSS スタイル」に追加して「了解」をクリックします。
; overflow: auto; border: 2px solid black;
「overflow」スタイルプロパティーを auto に設定すると、チャットルームのトランスクリプトが必要に応じてスクロールバーを表示します。transcriptPanel レイアウトパネルの「panelLayout」プロパティーが flow に設定されていることを確認します。これはデフォルトの設定です。次の手順 6 から 10 で、静的テキストコンポーネントを transcriptPanel レイアウトパネルの子として追加し、静的テキストの「text」プロパティーを Page1 の「transcript」プロパティーにバインドします。
- 静的テキストコンポーネントをパレットの「基本」セクションから transcriptPanel レイアウトパネルにドラッグします。マウスボタンを離す前に、transcriptPanel のアウトラインが青色で強調表示されていることを確認します。そうなっている場合、transcriptPanel コンポーネントに静的テキストコンポーネントが含まれています。
「プロパティー」ウィンドウで、静的テキストコンポーネントの「id」プロパティーを「transcriptText」に変更し、「escape」のチェックボックスを選択解除してプロパティーを「false」に設定します。
「escape」プロパティーを「false」に設定すると、このテキストに埋め込まれた任意のマークアップタグがブラウザで評価されます。これにより、トランスクリプトエントリが、青や紫などの目的の書式設定で表示されます。
- ビジュアルデザイナで transcriptText 静的テキストコンポーネントを右クリックし、「データにバインド」を選択します。
- 「データにバインド」ダイアログで、「オブジェクトにバインド」タブをクリックします。
「Page1」>「transcript」を選択し、「了解」をクリックします。
- テキストフィールドコンポーネントをパレットの「基本」セクションからドラッグし、transcriptPanel コンポーネントの下にドロップします。
- 「プロパティー」ウィンドウで、テキストフィールドコンポーネントの「
id」プロパティーを「comment」に変更します。
- comment テキストフィールドの「
columns」プロパティーを「60」に変更します。
- ボタンコンポーネントをパレットの「基本」セクションからドラッグして comment テキストフィールドの右側にドロップし、「
Send」と入力して Enter キーを押します。
「style」プロパティーの省略符号 (...) ボタンをクリックし、次の規則を「CSS スタイル」に追加して「了解」をクリックします。
; width: 70px
「プロパティー」ウィンドウで、ボタンコンポーネントの「id」プロパティーを「send」に変更します。
Page1 の外観は次の図のようになります。
ビジュアルデザイナで、「Send」ボタンコンポーネントをダブルクリックします。
IDE によって送信ボタンの action メソッドが作成され、このメソッドがソースコードエディタに表示されます。
このメソッドに、次にボールドで示されているコードを追加します。
| コード例 5: ボタンアクションハンドラ |
public String send_action() {
String comment = (String)getComment().getText();
String username = getSessionBean1().getUsername();
getApplicationBean1().addEntry(username, comment);
return null;
} |
このコードは、ユーザーがコメントテキストフィールドに入力した任意のテキストを取得し、それを addEntry メソッドに提供します。これによって、ユーザーのコメントがチャットルームのトランスクリプトに追加されます。
コメントの送信とポーリングのための Ajax トランザクションの構成
Dynamic Faces コンポーネントライブラリ (0.2) に付属している Ajax トランザクションコンポーネントを使用すると、デザイン時に、Ajax 機能を視覚的に構成できます。このとき、ビジュアルデザイナには、さまざまなコンポーネントのボーダーが色分けされて表示されます。最低でも、Ajax トランザクションの開始時に入力をサーバーに送信するコンポーネントと、クライアントが Ajax 応答を受信するときに再描画するコンポーネントを指定します。ビジュアルデザイナには、入力をサーバーに送信するコンポーネントは実線付きで表示され、再描画するコンポーネントは破線付きで表示されます。さらに、Ajax トランザクションを開始する JavaScript のコード行をコーディングする必要があります。
この節では、コメントの送信用とサーバーのポーリング用の 2 つの Ajax トランザクションを構成します。commentTx Ajax トランザクションは、ユーザーが送信ボタンをクリックするか、Enter キーを押すと、それに応答して開始されます。commentTx が開始すると、コメントテキストフィールドと送信ボタンによって、Ajax 要求を介して入力がサーバーに送信され、クライアントが Ajax 応答を受信するときに transcriptText StaticText での再描画が行われます。ページが読み込まれると、pollTx Ajax トランザクションが最初に開始されます。次に、pollTx Ajax トランザクションと関連付けられた Ajax 応答をクライアントが受信すると、短い遅延のあとにふたたび pollTx が開始されます。この方法で、サーバーを継続的にポーリングします。pollTx の開始時、Ajax 要求を介して入力を送信するコンポーネントはなく (トランスクリプトデータを取得するのみで変更しないため)、クライアントでの Ajax 応答の受信時に transcriptText StaticText での再描画が行われます。その結果、コメントのトランスクリプトがブラウザ内で継続的に更新されます。
- Page1 のビジュアルデザイナに戻ります。
ビジュアルデザイナのツールバーにある「仮想フォームを表示」ボタンをクリックします ( )。
従来の送信用の仮想フォームで提供されていた機能に似た Ajax 機能が、Ajax トランザクションコンポーネントによって提供されます。「仮想フォームを表示」ボタンをクリックすると、このページに対して構成された仮想フォームと Ajax トランザクションの両方が表示されます。
パレットの「Dynamic Faces」セクションを展開し、Ajax トランザクションコンポーネントを「Dynamic Faces」セクションからビジュアルデザイナにドラッグします。
ビジュアルデザイナの最下部に、Ajax トランザクションの凡例が表示され、青字に関連付けられた ajaxTransaction1 Ajax トランザクションが表示されます。
ビジュアルデザイナで、コメントテキストフィールドと送信ボタンの両方を選択します。右クリックして「Configure Ajax Transactions」を選択します。
「Configure Ajax Transactions」ダイアログが表示されます。このダイアログの最上部に、「send」および「comment」が表示され、送信ボタンとコメントテキストフィールドの Ajax トランザクションを構成中であることが示されます。
- 「Configure Ajax Transactions」ダイアログの「Name」フィールド内をダブルクリックし、Ajax トランザクションの名前を変更するために「
commentTx」と入力し、Enter キーを押します。
「Send Input」フィールドを「Yes」に設定し、「了解」をクリックしてダイアログを閉じます。
コメントテキストフィールドと送信ボタンは、ビジュアルデザイナ上で青い実線で囲んで表示されます。これは、commentTx Ajax トランザクションの開始時に、これらのコンポーネントが Ajax 要求を介して入力をサーバーに送信することを示します。送信ボタンは、ボタンがクリックされたことをサーバーに通知するために、入力をサーバーに送信する必要があります。次の「JavaScript の追加」節では、ユーザーが「Send」ボタンをクリックするか、または Enter キーを押す操作に応じて commentTx Ajax トランザクションを開始する JavaScript コードの行を追加します。
デザイナで transcriptText 静的テキストを選択します。右クリックして「Configure Ajax Transactions」を選択します。
「Configure Ajax Transactions」ダイアログが表示されます。このダイアログの最上部に「transcriptText」が表示され、transcriptText 静的テキストの Ajax トランザクションを構成中であることが示されます。
- 「Configure Ajax Transactions」ダイアログ内で、「New」をクリックして新しい Ajax トランザクションを構成します。
- 新しい Ajax トランザクションに関連付ける色として赤を選択します。
- 新しい Ajax トランザクションの「Name」フィールド内をダブルクリックし、「
pollTx」と入力して Enter キーを押します。
「commentTx」と「pollTx」の両方の Ajax トランザクションの「Re-Render」フィールドを「Yes」に設定し、「了解」をクリックしてダイアログを閉じます。
transcriptText 静的テキストは、青い破線と赤い破線の両方で囲まれて表示され、クライアントが commentTx または pollTx Ajax トランザクションと関連付けられた Ajax 応答を受信すると、常にコンポーネントでの再描画が行われることを示します。次の「JavaScript の追加」節では、pollTx Ajax トランザクションを開始することによってサーバーをポーリングする JavaScript ロジックを追加します。
- 「ナビゲータ」ウィンドウで、「Page1」>「page1」>「html1」>「head1」ノードの順に展開し、「commentTx」Ajax トランザクションを選択します。
「プロパティー」ウィンドウで、commentTx Ajax トランザクションコンポーネントの「postReplace」プロパティーを「customPostReplaceForCommentTx」に設定します。
ここで、commentTx Ajax トランザクションと関連付けられた Ajax 応答を受信して適切なコンポーネント (つまり transcriptText 静的テキスト) を再描画したあと、クライアントが customPostReplaceForCommentTx JavaScript 関数を起動するように指定します。この JavaScript 関数は、次の「JavaScript の追加」節に記載されています。
- 「ナビゲータ」ウィンドウの「Page1」>「page1」>「html1」>「head1」ノードの下で、「pollTx」Ajax トランザクションを選択します。
「プロパティー」ウィンドウで、「pollTx」Ajax トランザクションコンポーネントの「inputs」プロパティーおよび「execute」プロパティーの両方を「none」に設定します。
この手順では、Ajax トランザクションが開始されたときに、Ajax 要求で入力を送信するコンポーネントまたはサーバー上の処理を受けるコンポーネントがないように、「pollTx」Ajax トランザクションを明示的に構成します。
「pollTx」Ajax トランザクションの「postReplace」プロパティーを「customPostReplaceForPollTx」に設定し、「replaceElement」プロパティーを「customReplaceForPollTx」に設定します。
ここで、クライアントが「pollTx」Ajax トランザクションに関連付けられた Ajax 応答のクライアントを受信する場合、customReplaceForPollTx JavaScript 関数がカスタムの再描画ロジックを実装するように指定します。また、適切なコンポーネント (つまり transcriptText 静的テキスト) の再描画後、クライアントで customPostReplaceForPollTx JavaScript 関数を実行することを指定します。これらの JavaScript 関数は、次の「JavaScript の追加」節に記載されています。
本体コンポーネントおよびフォームコンポーネントの JavaScript プロパティーの設定
- 「ナビゲータ」ウィンドウの「Page1」>「page1」>「html1」ノードの下で、「body1」本体コンポーネントを選択します。
「プロパティー」ウィンドウで、body1 本体コンポーネントの「onLoad」プロパティーを「handleOnLoad()」に設定し、「onUnload」プロパティーを「handleOnUnload()」に設定します。
これらの JavaScript 関数は、次の「JavaScript の追加」節に記載されています。
- 「ナビゲータ」ウィンドウの「Page1」>「page1」>「html1」>「body1」ノードの下で、「form1」フォームコンポーネントを選択します。
「プロパティー」ウィンドウで、form1 フォームコンポーネントの「onSubmit」プロパティーを「return interceptFormSubmit()」に設定します。
この interceptFormSubmit JavaScript 関数は、次の「JavaScript の追加」節に記載されています。ユーザーが送信ボタンをクリックするか、または Enter キーを押すと、この JavaScript 関数によって従来の形式での送信が防止され、代わりに commentTx Ajax トランザクションが開始されます。
JavaScript の追加
次に、ajaxchatroom.js JavaScript ファイルを作成します。
「プロジェクト」ウィンドウで、「Web ページ」ノードを展開し、リソースフォルダを右クリックし、「新規」>「その他」を選択します。
- 「新規ファイル」ウィザードで、「カテゴリ」リストから「その他」を選択し、「プロジェクト」リストから「空のファイル」を選択して、「次へ」をクリックします。
- ファイルを
ajaxchatroom.js という名前にして、「完了」をクリックします。
次の JavaScript コードをファイルに追加します。
コード例 6: ajaxchatroom.js |
var pollDelay = 1000;
var continuePolling = false;
var mouseDownOnTranscript = false; //ユーザーがトランスクリプト上 (任意のスクロールバーを含む) でマウスポインタボタンを押し
//まだボタンを離していない状態かどうか
function customPostReplaceForCommentTx(element, markup) {
//トランスクリプトの最下部にスクロール
var transcriptPanel = document.getElementById('form1:transcriptPanel');
transcriptPanel.scrollTop = transcriptPanel.scrollHeight;
//テキストフィールドを消去
var commentTextField = document.getElementById('form1:comment');
commentTextField.value = '';
//フォーカスをテキストフィールドに置く
commentTextField.focus();
}
function handleOnLoad() {
//トランスクリプトでマウスポインタボタンを押す動作の処理
var transcriptPanel = document.getElementById('form1:transcriptPanel');
transcriptPanel.onmousedown = handleMouseDown;
//ページ上の任意の場所でマウスポインタボタンを離す動作の処理
document.onmouseup = handleMouseUp;
//テキストフィールドのオートコンプリート機能をオフにする
document.getElementById('form1:comment').setAttribute('autocomplete','off');
//ポーリングの開始
continuePolling = true;
poll();
}
function handleOnUnload() {
//ポーリングの停止
continuePolling = false;
}
function poll() {
//pollTx Ajax トランザクションの開始
DynaFaces.Tx.fire('pollTx');
}
function customReplaceForPollTx(element, markup) {
//ユーザーがトランスクリプトテキストの選択やトランスクリプトのスクロールなどの操作を実行していない場合、
//このポーリング要求の置換 (再描画) を実行し、
//置換後必要に応じてトランスクリプトをスクロールする
if (!mouseDownOnTranscript) {
var transcriptPanel = document.getElementById('form1:transcriptPanel');
//scrollTop: トランスクリプトの最上部と現在表示されている部分との間の長さ
//scrollHeight: スクロールのために表示されていないすべての部分を含む、トランスクリプト全体の高さ
//clientHeight: トランスクリプトの表示されている部分の高さ
//置換の前に、スクロールバーがあるかどうかを取得する
//scrollHeight が clientHeight を超えているときにスクロールバーがある場合
var scrollbarExistsBeforeReplacement = transcriptPanel.scrollHeight > transcriptPanel.clientHeight;
//置換の前にトランスクリプトが最下部にスクロールされているかどうかを取得する
var scrolledToBottomBeforeReplacement = false;
if (scrollbarExistsBeforeReplacement) {
//scrollTop と clientHeight との合計が scrollHeight と等しい場合、トランスクリプトは最下部にスクロールされている
if (transcriptPanel.scrollTop + transcriptPanel.clientHeight == transcriptPanel.scrollHeight) {
scrolledToBottomBeforeReplacement = true;
}
}
//置換の前に scrollTop を取得する
var scrollTopBeforeReplacement = transcriptPanel.scrollTop;
//トランスクリプトの内容を実際に置換するため、デフォルトの置換関数を実行する
DynaFaces.replace(element, markup);
//置換のあとにスクロールバーがあるかどうかを取得する
var scrollbarExistsAfterReplacement = transcriptPanel.scrollHeight > transcriptPanel.clientHeight;
//置換の前にトランスクリプトの最下部にスクロールされた状態だった場合、
//または、置換の前にスクロールバーがなく、置換後の現在はある場合、トランスクリプトの最下部にスクロールする
//それ以外の場合、トランスクリプトの置換前と同じ位置にスクロールする
if (scrolledToBottomBeforeReplacement || (!scrollbarExistsBeforeReplacement && scrollbarExistsAfterReplacement)) {
transcriptPanel.scrollTop = transcriptPanel.scrollHeight; //トランスクリプトの最下部にスクロールする
}
else {
transcriptPanel.scrollTop = scrollTopBeforeReplacement; //トランスクリプトの置換の前と同じ位置にスクロールする
}
}
}
function handleMouseDown() {
//トランスクリプトのスクロールバーでマウスポインタボタンが押された場合、
//ボタンが離れたときに、マウスポインタを離す操作と対応するイベントハンドラが IE では実行されない
//この場合、mouseDownOnTranscript の設定を false に戻す処理が行われないため、true に設定しない
//その代わりに、ユーザーがトランスクリプトをスクロール中であっても、customReplaceForPollTx の更新のみを実行する
//こうしても、IE では何の問題も発生しないため
if (document.all) {
var transcriptPanel = document.getElementById('form1:transcriptPanel');
var scrollBarExists = transcriptPanel.scrollHeight > transcriptPanel.clientHeight;
if (scrollBarExists) {
if (window.event.offsetX > transcriptPanel.clientWidth) {
//スクロールバー上でマウスポインタボタンが押された場合
return;
}
}
}
mouseDownOnTranscript = true;
}
function handleMouseUp() {
mouseDownOnTranscript = false;
}
function customPostReplaceForPollTx(element, markup) {
//次のポーリング要求を送信
if (continuePolling) {
setTimeout(poll, pollDelay);
}
}
function interceptFormSubmit() {
//テキストフィールドが空でない場合、commentTx を開始
if (document.getElementById('form1:comment').value != '') {
DynaFaces.Tx.fire('commentTx');
}
//従来のフォーム送信の防止
return false;
} |
「commentTx」Ajax トランザクションの「postReplace」プロパティーを customPostReplaceForCommentTx に設定したため、commentTx に関連付けられた Ajax 応答の受信および対応するコンポーネント (つまり transcriptText 静的テキスト) の再描画後、customPostReplaceForCommentTx 関数がクライアントで呼び出されます。Dynamic Faces では「置換」と「再描画」という用語を使用しますが、これらはおおよそ同じ意味です。送信ボタンをクリックするか、または Enter キーを押してユーザーがコメントを送信すると、customPostReplaceForCommentTx 関数によってトランスクリプトの最下部へスクロールされ、コメントテキストフィールドが消去され、コメントテキストフィールド内にフォーカスが移動します。
page1 ページコンポーネントの「onLoad」プロパティーを handleOnLoad に設定したため、クライアントで、このページを最初に読み込んだ直後に handleOnLoad 関数が実行されます。この関数では、transcriptPanel.onmousedown イベントハンドラへの handleMouseDown 関数リファレンスと、document.onmouseup イベントハンドラへの handleMouseUp 関数リファレンスが割り当てられます。handleMouseDown 関数は transcriptPanel 要素上でマウスポインタボタンを押すイベントに対応し、handleMouseUp 関数はページ上の任意の場所でマウスポインタボタンを離すイベントに対応します。これら両方の関数と customReplaceForPollTx 関数とが一緒に動作することで、ユーザーがトランスクリプト上でマウスをクリックしてまだ離していない場合、ポーリング要求に応じた再描画が行われないことが保証されます。このようなとき、ユーザーがトランスクリプトテキストの一部を選択する操作 (たとえば、コピー操作の準備) を実行中の場合、トランスクリプトを再描画すると、選択が妨げられることになります。同様に、ユーザーが Firefox 上のトランスクリプトをスクロール中である場合、トランスクリプトを再描画すると、スクロールバーがフリーズすることになります。handleMouseUp 関数は、transcriptPanel 要素上だけではなく、document オブジェクト上でマウスポインタボタンを離すイベントも処理されます。これは、ユーザーがトランスクリプトのスクロールバー上でマウスを押し、トランスクリプト領域の外にマウスをドラッグして、そのあとマウスポインタボタンを離す場合があるためです。このような場合、このロジックには、トランスクリプト自体の上でなくても、マウスポインタボタンを離すイベントを処理する必要があります。これらの関数の割り当てのほかに、handleOnLoad 関数も、コメントテキストフィールドへのブラウザのオートコンプリート機能をオフにして、サーバーへのポーリングを開始します。
poll 関数によって、Ajax 要求をサーバーに対して送信する pollTx Ajax トランザクションが実行されます。対応する Ajax 応答によって transcriptText 静的テキストの再描画がトリガーされます。
pollTx Ajax トランザクションの「replaceElement」プロパティーを customReplaceForPollTx に設定したため、クライアントでは、pollTx に関連付けられた Ajax 応答の受信時には常に、デフォルトの置換関数 (つまり DynaFaces.replace) ではなく customReplaceForPollTx 関数が実行されます。customReplaceForPollTx カスタム置換関数の提供により、ユーザーが特定の操作 (具体的には、トランスクリプトテキストの選択やトランスクリプトのスクロール操作) をトランスクリプト上で実行しているかどうかの条件に基づいて、デフォルトの置換関数を実行できます。同様に、置換前にスクロールの位置を取得し、置換後に必要に応じてプログラムでスクロールできます。置換の前に、transcriptPanel 要素の「scrollTop」、「scrollHeight」、および「clientHeight」の各プロパティーを使用してスクロールバーがあるかどうかを判定し、ある場合、トランスクリプトが最下部までスクロールされているかどうかを判定します。次に、トランスクリプトの内容を実際に置換する処理を、デフォルトの置換関数 (DynaFaces.replace) に委譲します。置換に続いて、特定の条件下の場合を除いて、トランスクリプトの置換前と同じ位置までスクロールします。特に、トランスクリプトが置換前に最下部にスクロールされていた場合、またはスクロールバーが更新前にはないが現在ある場合、トランスクリプトの最下部までスクロールします。前者の場合、トランスクリプトをプログラムで最下部にスクロールしないと、追加したトランスクリプトのエントリが表示範囲に入りません。後者の場合、トランスクリプトをプログラムで最下部にスクロールしないと、トランスクリプトは最上部にスクロールされたままになるため、ユーザーが手動で最下部までスクロールしなければならなくなります。
handleMouseDown 関数では、Internet Explorer の場合、追加のロジックがいくつか必要です。ユーザーがトランスクリプトのスクロールバー上でマウスポインタボタンを押した場合、Internet Explorer では、ボタンを離すまで document.onmouseup に割り当てられたイベントハンドラが呼び出されません。その結果、ユーザーがボタンを離す操作で handleMouseUp が呼び出されず、そのため mouseDownOnTranscript が true のままになるので、mouseDownOnTranscript を true に設定できません。そのような場合、次にマウスが操作されて handleMouseup 関数がトリガーされるまで、customReplaceForPollTx 関数でのトランスクリプトの再描画は行われません。mouseDownOnTranscript を true に設定せず、単純に handleMouseDown 関数から戻るのが、より良いやり方です。その結果、ユーザーがトランスクリプトをスクロール中の場合でも、customReplaceForPollTx 関数は Internet Explorer 上のトランスクリプトを再描画します。ただし、ユーザーのスクロール中にトランスクリプトを再描画するとスクロールバーがフリーズするのは Firefox のみで、Internet Explorer では起こらないため、これを行なっても問題は発生しません。
pollTx Ajax トランザクションの「postReplace」プロパティーを customPostReplaceForPollTx に設定したため、クライアントでは、pollTx と関連付けられた Ajax 応答の受信後と、customReplaceForPollTx 関数の呼び出し後に、customPostReplaceForPollTx 関数が呼び出されます。continuePolling 変数が true の場合 (ページが読み込み解除中でない場合など)、pollDelay 変数で指定された遅延後に、customPostReplaceForPollTx 関数で次のポーリング要求が送信されます。
form1 フォームコンポーネントの「onSubmit」プロパティーを return interceptFormSubmit に設定したため、クライアントでは、ユーザーが送信ボタンをクリックするか、または Enter キーを押したときに interceptFormSubmit 関数が呼び出されます。コメントテキストフィールドが空白でない場合、interceptFormSubmit 関数によって commentTx Ajax トランザクション (Ajax 要求を送信するトランザクション) が開始され、従来のフォームの送信を防止するために false が返されます。
- パレットの「拡張」セクションを展開し、「スクリプト」コンポーネントを「拡張」セクションからビジュアルデザイナにドラッグします。
- 「ナビゲータ」ウィンドウの「Page1」>「page1」>「html1」>「head1」ノードの下で、「script1」スクリプトコンポーネントを選択します。
「プロパティー」ウィンドウで、script1 スクリプトコンポーネントの「url」プロパティーの省略符号 (...) ボタンをクリックします。ダイアログで「resources」>「ajaxchatroom.js」を選択し、URL フィールドに /resources/ajaxchatroom.js が表示されたら、「了解」をクリックします。
前の手順で作成した ajaxchatroom.js JavaScript ファイルの <script> タグを発行するように、script1 スクリプトコンポーネントを構成しました。
プロジェクトの配備
この Web アプリケーションは、Firefox と Internet Explorer 7 でテスト済みです。
「プロジェクト」ウィンドウで、プロジェクトノードを右クリックし、Web アプリケーションを構築、配備、および起動するために「実行」を選択します。
ブラウザが http://localhost:8080/AjaxChatRoom/ に移動します。
コメントをコメントテキストフィールドに入力し、Enter キーを押すか、または送信ボタンをクリックします。
エントリがトランスクリプトに青字で表示され、ユーザー名が anonymous1 になります。
ブラウザのロケーションバーで、URL を http://localhost:8080/AjaxChatRoom/?username=jack に変更します。
これで、ユーザー名が anonymous1 でなく jack と認識されるようになったため、以前のエントリは紫で表示されます。
- 別のコメントをコメントテキストフィールドに入力し、Enter キーを押すか、または送信ボタンをクリックします。新しいエントリがトランスクリプトに青字で表示され、ユーザー名は
jack になります。
- ターミナル (またはコマンド) ウィンドウを開いて次のいずれかを実行し、IP アドレスを特定します。
- Solaris/Linux/MAC の場合。「
ifconfig -a」と入力し、Enter キーを押します。
- Windows の場合。「
ipconfig」と入力し、Enter キーを押します。
2 つ目のブラウザウィンドウまたはタブを開き、「http://your-ip-address:8080/AjaxChatRoom?username=jill」と入力します。
anonymous1 および jack によって送信されたエントリが紫で表示されます。
別のコメントをコメントテキストフィールドに入力し、Enter キーを押すか、または送信ボタンをクリックします。
新しいエントリがトランスクリプトに青字で表示され、ユーザー名は jill になります。
元のブラウザウィンドウまたはタブに切り替えます。
jill によって送信されたエントリが紫で表示されます。
- トランスクリプトにスクロールバーが表示されるまで、
jack と jill の会話を続けます。
トランスクリプトの内容の最初にスクロールし、会話の最初を再読するように、マウスボタンを押したままにします。数秒間待ってからマウスボタンを離します。
ブラウザによって動作は若干異なります。Firefox では、マウスボタンを押している間はポーリング要求に応じたトランスクリプトの再描画は行われません。Internet Explorer では、マウスボタンを押している間中トランスクリプトは継続的に再描画されます。これは、Internet Explorer で handleMouseDown 関数の mouseDownOnTranscript 変数が true に設定されないためです。このわずかな動作の違いは、この節で説明したとおり、アプリケーションを単独でテストする場合には検知されません。動作の違いを観察するには、別の人にチャットの相手として加わってもらい、この手順の実行中にコメントを送信してもらう必要があります。
トランスクリプトの一部のテキストを選択し、マウスボタンを押したままにします。キーボードショートカットを使用し、テキストをクリップボードにコピーします。次に、マウスボタンを離し、この操作のあとでテキストの選択が解除されていることを確認します。
マウスボタンを押している間中、トランスクリプトではポーリング要求に応じた再描画が行われません。しかし、マウスボタンを離すと、そのあとのポーリング要求によってトランスクリプトが再描画されます。トランスクリプトの再描画によって、テキストの選択は解除されます。このアプリケーションは、置換前に選択内容に関する情報を取得し、更新後に選択内容を再確立するロジックを customReplaceForPollTx 関数内に追加することによって拡張できます。この場合、ユーザーが一部のテキストを選択すると、次にドキュメント上の任意の場所でマウスボタンを押す操作に応じて選択を解除できます。これは、トランスクリプト内のテキストをすべて選択解除する関数を追加し、その関数への参照を document.onmousedown イベントハンドラに割り当てると実現できます。
関連項目
|