Oracle ADF Faces Rich ClientへのAuto Suggest機能の実装

Auto Suggestコンポーネントは、AjaxやWeb 2.0の概念をもっともよく象徴しているコンポーネントです。Auto Suggest(オートコンプリートと呼ばれることもある)は、ユーザーがテキスト・フィールドに入力したテキストによってフィルタ処理された値を、ド ロップダウン・リストに一覧表示する機能です。Auto Suggestコンポーネント自体は新しいWeb技術ではありませんが、部分ページ更新の動作を示すもっともわかりやすい実例の1つです。この記事では、 Oracle JDeveloper 11gのADF Faces Rich ClientにAuto Suggest機能を実装する手法について説明します。

執筆: Frank Nimphius(オラクル)、Maroun Imad(オラクル)
2009年4月9日

はじめに

ADF Faces Rich Client用のネイティブのAuto Suggestコンポーネントは、Oracle JDeveloperの将来のリリースに向けて計画されている新機能のうちの1つです。ただし、すでに説明したように、注目されている機能および現在の ADF Faces Rich Client上で利用可能な手法によって構築できる機能と比べれば、Auto Suggest機能は単純なものです。この記事では、最初のプロトタイプのアーキテクチャおよびこのプロトタイプにおけるJDeveloper 11gの作業領域について説明します。このプロトタイプは、あとで宣言的コンポーネントに拡張することが計画されています。この記事にあるプロトタイプは リリースが決定しているので、ADF Facesのユーザーは、手動実装の指針に従って、このAuto Suggest機能を利用することができます。

アーキテクチャ

Auto Suggest機能では、非同期通信チャネルを使用して互いに通信するクライアント側の実装とサーバー側の実装が必要です。サーバー側で使用するロジック では、値のリストが提供され、ユーザーの各キーストロークに合わせてそのリストに対してフィルタ処理がおこなわれます。さらに、値のリストにある文字列を ラベルまたは値のどちらとして表示するかが選択されます。一方、クライアント側で使用するJavaScriptでは、ユーザーのキーボード入力のリスニン グ、Auto Suggestリストの表示、およびユーザーが入力した文字のサーバーへの送信が実行されます。入力した文字は、ペイロードとしてサーバーに送信され、 データのフィルタ処理に使用されます。

Poor Manのプロセス・チャート

このバージョンのAuto Suggest機能では、データ入力コンポーネントとしてaf:inputTextFieldが使用され ます。ユーザーがフィールドへの入力を開始するとただちに、JavaScriptイベントがaf:clientListenerコンポーネ ントによって取得されます。このコンポーネントは、リクエストを処理するために、そのイベントをJavaScript関数に渡します。 JavaScript関数は受け取ったキー・コードを調べ、それが有効な文字であればテキスト・フィールドの下にポップアップ・ダイアログを表示します (表示されていない場合)。リスト・ボックスは、マネージドBeanに対してリスト・データの問合せをおこないます。このマネージドBeanは、リスト・ データをADFやPOJOなどから読み取れるようにするために、抽象化レイヤーとしてビューとモデルの間に追加しました。また、実際のところ、Auto Suggest機能の汎用性を維持するのにもこの抽象化レイヤーが必要です。その理由は、フェッチされるデータに対するキャッシング戦略を、開発者が決め られるようにしたいためです。サンプルでは、Oracle ADF Business Componentsを使用してデータの問合せをおこないます。さらに、後続の問合せを、データベースではなくメモリに対して実行するように指示するオプ ションもあります。リスト・データがリフレッシュされる場合には、リフレッシュする部分ターゲットとしてリスト・コンポーネントがリクエストの最後に追加 されます。入力フォーカスが入力テキスト・フィールドに戻され、ユーザーは次の文字を入力できる状態になります。

Oracle ADF Business Components

Auto Suggestリストは、ADF Business Componentsアプリケーション・モジュールで公開されているクライアント・メソッドから取得されます。このメソッドは、文字列を入力引数として受 け取り、ジョブ・ビュー・オブジェクトへの問合せをおこない、クライアントに戻される文字列のリストを返します。

Managed Bean

このプロトタイプでは、Managed BeanとしてSuggestListとDemoAdfBcSuggestModelの2つが使用されます。DemoAdfBcSuggestModel Beanは、ADF Business Componentsによる問合せを抽象化して、結果をクライアントに提供するSuggestList Beanに文字列のリストを返します。ニーズに合わせてこのサンプルをカスタマイズすることを計画している場合は、DemoAdfBcSuggestModel Beanの部分を、独自の実装に置き換える必要があります。SuggestList Beanでは、このあと説明する2つの重要なメソッドが提供されます。

public ArrayList<SelectItem> getJobList()

このメソッドは、Auto Suggestリストにデータを移入します。メソッドは、f:selectItems要素から参照されま す。これは、ユーザーに値を提示するaf:popupコンポーネントの中に常駐するaf:selectOneListbox要 素の子要素です。

  
                          
public ArrayList<SelectItem> getJobList() {
                            
//first time query is against database
if (jobList == null){
jobList = new ArrayList<SelectItem>();
List<String> filteredList = null;
filteredList = suggestProvider.filteredValues(srchString, false);
jobList = populateList(filteredList);
}
return jobList;
}


private ArrayList<SelectItem> populateList(List<String> filteredList) { ArrayList<SelectItem> _list = new ArrayList<SelectItem>(); for(String suggestString : filteredList){ SelectItem si = new SelectItem(); si.setValue(suggestString); si.setLabel(suggestString); _list.add(si); } return _list; }
public void doFilterList
Auto Suggest機能が有効になっている入力フィールドでユーザーがキーストロークを実行するたびに、ブラウザ・クライアントはaf:serverListenerコ ンポーネントを通してdoFilterListメソッドをコールします。
  
                          
public void doFilterList(ClientEvent clientEvent) {
                            
// set the content for the suggest list
srchString = (String)clientEvent.getParameters().get("filterString");
List<String> filteredList = suggestProvider.filteredValues(srchString, true);
jobList = populateList(filteredList);
RequestContext.getCurrentInstance().addPartialTarget(suggestList);
}

doFilterListメソッドに対する各コールの最後でaf:selectOneListbox要素はリフレッ シュされ、新しいリスト値が移入されます。

JSFページのマークアップ

Auto Suggestフィールドには、keyUpイベントとkeyDownイベントをリスニングするaf:clientListenerコ ンポーネントが、2つ割り当てられています。Auto Suggestフィールド上のすべてのキーボード・イベントは、JavaScript関数handleKeyUpOnSuggestFieldへ 送信されます。この関数は、イベント・オブジェクトを入力引数として受け取ります。イベント・オブジェクトは、イベント・ソース、キーストロークを受信す るコンポーネント、および押されたキーのkeyCodeに関する情報を提供します。ADF Faces Rich ClientのJavaScript APIを使用すると、ブラウザの違いに対処する必要がなくなるので、大幅に手間を省くことができます。

<!-- START Suggest Field -->
                          
<af:inputText id="suggestField"
clientComponent="true"
value="#{bindings.JobId.inputValue}"
label="#{bindings.JobId.hints.label}"
required="#{bindings.JobId.hints.mandatory}"
columns="#{bindings.JobId.hints.displayWidth}"
maximumLength="#{bindings.JobId.hints.precision}"
shortDesc="#{bindings.JobId.hints.tooltip}">
<f:validator binding="#{bindings.JobId.validator}"/>
<af:clientListener method="handleKeyUpOnSuggestField"
type="keyUp"/>
<af:clientListener method="handleKeyDownOnSuggestField"
type="keyDown"/>
</af:inputText> <!-- END Suggest Field -->
The suggest list is defined as follows
<!-- START Suggest Popup -->
                          
<af:popup id="suggestPopup" contentDelivery="immediate" animate="false" clientComponent="true">
<af:selectOneListbox id="joblist" contentStyle="width: 250px;"
size="15" valuePassThru="true"
binding="#{SuggestListBean.suggestList}">
<f:selectItems value="#{SuggestListBean.jobList}"/>
<af:clientListener method="handleListSelection" type="keyUp"/>
<af:clientListener method="handleListSelection" type="click"/>
</af:selectOneListbox> <af:serverListener type="suggestServerListener"
method="#{SuggestListBean.doFilterList}"/>
</af:popup> <!-- END Suggest Popup -->

af:selectOneListbox要素に対しては、2つのaf:clientListenerコンポーネントが定義されていま す。これらのコンポーネントは、ユーザーが値を選択(キー入力、クリック)または操作を取り消し(Esc)たときに発生するkeyUpイベントとクリッ ク・イベントをリスニングします。af:serverListenerコンポーネントは、Auto Suggest機能が有効になっている入力フィールドにユーザーが新しい文字を入力した場合に、JavaScriptからコールされます。af:serverListenerコ ンポーネントは、SuggestList BeanのdoFilterListメソッドをコールします。

JavaScript

JavaScriptはAjaxによって広く普及し、ADF Faces Rich Clientでも使用できるオプションの1つとなっています。ADF Facesの以前のバージョンとは異なり、ADF Faces Rich Clientにはクライアント側フレームワークが実在しています。このフレームワークはほとんど内部的に使用されますが、DOMツリーのハッキングから ユーザーを保護する目的で、パブリック関数としても公開されています。

 

免責事項

JavaScriptのプログラミングを開始する前に 確認しておく必要があるのは、ADF Faces Rich Clientでの JavaScriptの使用が 次善のソリューションであるという ことです。変わらない最善のソリューションは、JavaServer FacesのプログラミングAPIを使用する方法です。その理由は、それがあるリリースから別のリリースに移行するよいチャンスであり、また現時点でいつ 登場するかわからない新バージョンのブラウザに対応することもできるためです。ただし、Auto Suggest機能のような一部のユースケースでは、必要な場所にロジックを実装し、さらにネットワーク・ラウンドトリップをできる限り削減するために、 JavaScriptが必要になります。

 

 

Oracle JDeveloper 11gのこのバージョンでは、JavaScriptはaf:document要素のmetacontainerファ セットに追加されます。JDeveloper 11g Release 1が本格的に使用できる状態になった場合は、ただちにコードのこの部分が新しいaf:resource要 素に置き換えられる可能性があります。

                           
<f:facet name="metaContainer">
                            
<af:group>
<![CDATA[
<script>
//******************************************************************
//PROTOTYPE: AUTO SUGGEST
//STATUS: 1.0
//authors: Frank Nimphius, Maroun Imad
//Functionality
//====================================
// Implemented in v 1.0
//1 - Suggest window opens JOB_ID field
//2 - Initial query is to database, follow up queries are in memory
//3 - Enter list with down arrow
//4 - Enter key and mouse click to select
//5 - ESC to close
//******************************************************************

function handleKeyUpOnSuggestField(evt){ // start the popup aligned to the component that launched it suggestPopup = evt.getSource().findComponent("suggestPopup"); inputField = evt.getSource(); //don't open when user "tabs" into field if (suggestPopup.isShowing() == false && evt.getKeyCode()!= AdfKeyStroke.TAB_KEY){ //We reference a DOM area by using getClientId()+"::content" to position the suggest list. Shame
//on use that we are doing this, but there is no other option we found. Keep in mind that using
//direct DOM references may break the code in the future if the rendering of the component changes
//for whatever reason. For now, we want to feel safe and continue using it hints = {align:AdfRichPopup.ALIGN_AFTER_START, alignId:evt.getSource().getClientId()+"::content"}; suggestPopup.show(hints); //disable popup hide to avoid popup to flicker on key press. Note that we override the default framework
//behavior of the popup component for this instance only. When hiding the popup, we re-implement the default
//functionality. By all means, never change the framework functionality on the prototype level as this could
//have an unpredictable impact on all nstances of this type suggestPopup.hide = function(){} } //suppress server access for the following keys
//for better performance if (evt.getKeyCode() == AdfKeyStroke.ARROWLEFT_KEY || evt.getKeyCode() == AdfKeyStroke.ARROWRIGHT_KEY || evt.getKeyCode() == AdfKeyStroke.ARROWDOWN_KEY || evt.getKeyCode() == AdfKeyStroke.SHIFT_MASK || evt.getKeyCode() == AdfKeyStroke.END_KEY || evt.getKeyCode() == AdfKeyStroke.ALT_KEY || evt.getKeyCode() == AdfKeyStroke.HOME_KEY) { return false; } //close the popup when teh ESC key is pressed if (evt.getKeyCode() == AdfKeyStroke.ESC_KEY){ hidePopup(evt); return false; } // get the user typed values valueStr = inputField.getSubmittedValue(); // query suggest list on the server. The custom event references the af:serverListener component
// which calls a server side managed bean method. The payload we send is the current string entered
// in the suggest field AdfCustomEvent.queue(suggestPopup,"suggestServerListener",
// Send single parameter
{filterString:valueStr},true);
// put focus back to the input text field. We set the timout to 400 ms, which is not the final answer but a
// value that worked for us. In a next version, we may improve this setTimeout("inputField.focus();",400); } //TAB and ARROW DOWN keys navigate to the suggest popup we need to handle this in the key down event as otherwise
//the TAB doesn't work. Note that a TAB key cannot be used to navigate in the suggest list. Use the arrow keys for
//this. The TAB key can be done to navigate the list, but this requires some more JavaScript that we didn't complete
//in time function handleKeyDownOnSuggestField(evt){ if (evt.getKeyCode() == AdfKeyStroke.ARROWDOWN_KEY) { selectList = evt.getSource().findComponent("joblist"); selectList.focus(); return false; } else{ return false; } } //method called when pressing a key or a mouse button on the list. The ENTER key or a mosue click select
//the list value and write it back to the input text field (the suggest field) function handleListSelection(evt){ if(evt.getKeyCode() == AdfKeyStroke.ENTER_KEY || evt.getType() == AdfUIInputEvent.CLICK_EVENT_TYPE){ var list = evt.getSource(); evt.cancel(); var listValue = list.getProperty("value"); hidePopup(evt); inputField = evt.getSource().findComponent("suggestField"); inputField.setValue(listValue); } //cancel dialog else if (evt.getKeyCode() == AdfKeyStroke.ESC_KEY){ hidePopup(evt); } } //function that re-implements the node functionality for the popup to then call it
//Please do as if you've never seen this code ;-) function hidePopup(evt){ var suggestPopup = evt.getSource().findComponent("suggestPopup"); //re-implement close functionality suggestPopup.hide = AdfRichPopup.prototype.hide; suggestPopup.hide(); } </script> ]]> </af:group> </f:facet>

次のステップ

このプロトタイプは、ADF Faces Rich ClientでAuto Suggest機能を実現できることを証明する第一の証拠です。オラクルでは、このプロトタイプを引き続き改良していくことを計画しています。簡単なロー ド・マップが、サンプルのページ・ソース・コードの中に記載されています。もっとも重要な改良は、このサンプルから宣言的なコンポーネントを作成すること です。宣言的なコンポーネントはパラメータ化が可能であるため、開発者は1つのページ上でJavaまたはJavaScriptとの競合を起こさずに、 Auto Suggest機能の複数のインスタンスを使用できます。現在オラクルでは、新機能およびバグ・レポートに関するフィードバックを広く受け付けています。 さらに、改良またはバグ修正を加えたコード・サンプルの送付についても歓迎しています。

サンプルのダウンロード

Oracle JDeveloper 11gのサンプルは、データベースのHRスキーマに作用します。サンプルのファイルは、 こちら からダウンロードできます。