動的要素を持つ Web ページを自動的に更新する

皆さんは、標準的な JSF (JavaServer Faces) コンポーネントで JavaScript と CSS (Cascading Style Sheets) を使って、オプションで表示される JSF コンポーネントの表示/非表示を切り替える方法をご存じかと思います。これを行うためには、JSF ページで使われるすべての JSF コンポーネントを最初に特定しておいてから、それらのコンポーネントを JSF ページに書き込む必要があります。しかしこの方法は、実行時にならないとわからない動的要素を含む Web ページを作成する場合には不可能です。この記事では、Web ページ上の古い UI コンポーネントをクリアーする一方で動的要素を自動的に更新する方法と、Java™ コードを使って Web ページに新しい要素を追加し、適切な場所に配置する方法について学びます。また、Web ページ上のそれぞれの動的要素にさまざまなイベント・ハンドラーをバインドする方法、サーバー・サイドのデータの変更をリッスンしてページを最新の情報に更新するためのリスナーを登録する方法、そして Ajax (Asynchronous JavaScript and XML) の手法を使って Web ページの動的部分のみを最新の情報に更新する方法についても学びます。

Li Li Lin, Software Engineer, IBM

Li Li LinLi Li Lin は IBM Director の開発に携わっているソフトウェア・エンジニアであり、特に OSGI に関連する部分の開発に従事しています。彼女は熟練したプラグイン開発者であり、また Web ベースのプロジェクトに関する経験も豊富です。



2009年 10月 13日

はじめに

皆さんは、動的要素を使って Web ページを自動的に更新したい場合があると思います。例えば、新しい投票がデータベースに追加されると即座に結果が更新される投票用の Web サイトが必要な場合や、証券取引データをリアルタイムで表示する株価用の Web サイトが必要な場合などです。投票結果やリアルタイムの取引データは、Web サイトへの追加や更新が行われるまでは未知の動的要素であり、サーバー・サイドからシグナルが送信されるとサイトへの追加や更新が行われます。では、これを JSF アプリケーションではどのように行うのでしょうか。

以前に公開された developerWorks の記事「JSF と CSS、JavaScript を使用して作成する Ajax アプリケーション: 第 2 回 動的な JSF フォーム」では、Web ページを最新の情報に更新することなくオプションの JSF コンポーネントの表示/非表示を切り替える方法を説明しています。しかし、Web ページに動的要素が含まれる場合には、その方法を使うことはできません。その方法では、JSF ページで使われるすべての JSF コンポーネントを特定しておいてから、それらのコンポーネントを JSF ページに書き込む必要があるからです。では、実行時まで動的要素を特定できない場合には、どうすればよいのでしょう。

この場合には、JSF は適切なソリューションではありません。皆さんは、データが変更されると即座に GUI が更新される clock などの Java Swing アプリケーションをよく理解しているかもしれません。あるいはそうしたアプリケーションの実装の詳細について、どこかの基本的な Swing 開発ガイドの中で読んだことがあるかもしれません。しかし、そうした手法は今回の状況には有効ではありません。Swing には、内部データの状態のみに基づいて自動的に GUI を更新するための成熟した手段が既に用意されていますが、JSF にはサーバー・サイドからのリクエストに基づいて GUI を更新するための適切な手段が用意されていません。JSF の標準的なライフサイクルを調べてみると、GUI の更新を呼び出すためには、通常はユーザーが Web ページ上でイベントを生成する (例えばボタンをクリックする) 必要があることがわかります。これはつまり、実行時に動的要素が作成されて Web ページに追加されたとしても、その Web ページはユーザーが操作しない限り自動的には更新されないということです。

では、動的要素を持つ Web ページを自動更新するためにはどうすればよいのでしょう。この記事ではそのための方法として、以下をはじめとするいくつかのステップからなる方法を説明します。

  • Web ページ上の古い UI コンポーネントをクリアーし、新しい UI コンポーネントを適切な場所に追加する。
  • Web ページ上のそれぞれの動的要素にさまざまなイベント・ハンドラーをバインドする。
  • サーバー・サイドの変更をリッスンするリスナーを登録する。
  • Web ページ全体ではなく Web ページ上の動的部分のみを Ajax の手法を使って更新する。

サーバー・サイドのデータ変更をモニタリングする

動的要素を持つ Web ページを自動更新する方法についてわかりやすく説明するために、この記事をとおして 1 つの簡単な例を用います。例として用いるのは、オンライン書店用の Web サイトを構成するアプリケーションです。この Web サイトのホーム・ページには、本のカテゴリーとカテゴリーごとの本の冊数といった在庫情報が表示されます (図 1)。

図 1. オンライン書店用のホーム・ページ
オンライン書店の在庫を表示するウィンドウ。カテゴリーごとに、本の在庫数に続いてカテゴリー名が表示されています。

情報を正確に反映させるためには、このページの在庫情報とサーバー・サイドのデータとをリアルタイムで同期させる必要があります。在庫への本の追加や削除といったアクションによってサーバー・サイドのデータが変更されるため、これらのアクションをモニタリングする必要があります。変更をモニタリングする方法として、サーバー・サイドのデータの変更をリッスンするためのリスナーを追加し、何らかの変更が発生した場合にはサーバー・サイドからリスナーに通知が行われるようにします。リスト 1 は、クラスに対しリスナーを登録、および登録解除する方法を示しています。

リスト 1. 在庫に対するリスナーを追加、および削除する
public class Inventory{
……
   private Map<String, InventoryListner> listeners = 
                    new HashMap<String,InventoryListner>();
……
   public void register(String id, InventoryListner listener){
	  listeners.put(id, listener);
   }	    
   public void deregister(String id){
	  listeners.remove(id);			               
   }
……
}

リスト 1 では、2 つの Java メソッドを使って Inventory クラスに対する在庫リスナーの追加、および削除を行っています。在庫に何らかの変更がある場合、その変更はすべて本の追加や削除といったアクションの結果だと仮定すると、そうしたアクションが発生するたびに、Inventory クラスに登録されたすべてのリスナーに対して変更が通知されます。リスト 2 は、これらの変更をリスナーに通知する方法を示しています。

リスト 2. リスナーに変更を通知する
public class Inventory{
……
     public void addBookItem(String bookName,String auther,String price,
                                                                   String category){
	     //codes for adding books
	    categoryChanged();
     }
	   
      public void removeBookItem(String bookName,String auther,
                                          String price,String category){
	     //codes for deleting books
	     categoryChanged(); 	
      }

      private synchronized void categoryChanged(){
	     for (InventoryListner listener : listeners.values()) {
		  listener.categoryChanged();
	    } 			               
      }
}

次に、InventoryBean という Managed Bean に InventoryListener を実装し、この InventoryBean を在庫データに対するリスナーとして登録し、在庫データが変更されると InventoryBean が通知を受け取れるようにします。リスト 3 は、この Managed Bean を Inventory クラスのインスタンスに対して登録する方法を示しています。

リスト 3. Managed Bean を Inventory クラスのインスタンスに対して登録する
public interface InventoryListner {
	 public abstract void categoryChanged();
   }

   public class InventoryBean implements InventoryListner{
   ……
          private String m_clientId ;
          private InventoryNotifier m_notifier;
          public InventoryBean(){
	        m_notifier = InventoryNotifier.getInstance();
	        if(m_clientId == null) {
          		m_clientId = "bookstore";
        		m_notifier.register(m_clientId, this);
	        }		
         }
	public void categoryChanged() {
	refresh();
	//code for refresh dynamic part via ajax
	}	
……
}

リスト 1 からリスト 3 で概要を説明した方法を使うと、サーバー・サイドのデータの変更を Managed Bean によってモニタリングする仕組みを確立できたことになります。ワークフローとしては、サーバー・サイドのデータが変更されたという通知 (Notify changes) を Managed Bean が受け取ると、InventoryBeancategoryChanged() メソッドが呼び出され、データ・モデルが更新されます。図 2 は、この仕組みによってデータベース (Database) レイヤーと Bean レイヤーが関係付けられていることを示しています。サーバー・サイドのデータの変更をモニタリングするアプリケーションや、サーバー・サイドからイベントを受信するアプリケーションは、この仕組みをテンプレートとして使うことができます。

図 2. ビジネス・プロセスのモデル
Web ページから Bean を経由してデータベースに至るまでのプロセスを示すワークフロー図

データ・モデルを更新し、動的な GUI 要素を作成する

以上で、サーバー・サイドのデータの変更をモニタリングする仕組みを構築できました。今度は、何らかの変更が Bean に通知されたらデータ・モデルを更新し、動的な GUI 要素を作成するための方法を考える必要があります。このプロセスは Managed Bean の内部で実行され (図 2 の Bean レイヤーを参照)、データ・モデルの更新と GUI 要素の作成という 2 つのサブプロセスに分割されます。

データ・モデルを更新する

このサブプロセスは先ほどリスト 3 で示した refresh() メソッドによって呼び出されます。リスト 4 はデータ・モデルを更新するための方法を示しています。refresh() メソッドを使って在庫を再構成し、本が必ず適切なカテゴリーに割り当てられるようにします。つまりデータ・モデルを更新した後は、本がないカテゴリーは削除されていること、また新しいカテゴリーの本が追加された場合は新しいカテゴリーが追加されていることが保証されています。

refresh() メソッドをよく理解できるように、ここで使用している自己定義のデータ構造を簡単に説明します。ここでは Category クラスを使って在庫情報を保持しています。この Category クラスには、カテゴリーの名前と本のメタデータが ArrayList<BookItem>.BookItem クラスという形で含まれています (このクラスには本の題名、著者、価格、カテゴリーが含まれています)。リスト 4 はデータ・モデルを更新する方法を示しています。

リスト 4. データ・モデルを更新する
public class InventoryBean implements InventoryListner{
...
       private Inventory m_notifier;
       private Category[] m_category;
       public InventoryBean(){
	      m_notifier = Inventory.getInstance();	
       }
       private void refresh(){
            //reorganize the data model
	      ArrayList<Category> categoryList = m_notifier.reorgnizeCategory();
            // code for converting data to the type used in this bean,
           // ArrayList<Category> to Category[]	
       }
...
}

動的な GUI 要素を作成する

次に、もう 1 つのサブプロセスである、動的な GUI 要素の作成について説明します。この場合の動的な GUI 要素は、カテゴリーのリンクです (図 1 を参照)。ユーザーがホーム・ページ上の特定のカテゴリーをクリックすると、そのカテゴリーのすべての本の詳細な情報を含む新しいページにリダイレクトされます。図 3 は、Detective (推理小説) カテゴリーの中にあるすべての本の例を示しています。

図 3. Detective カテゴリーの詳細
Detective (推理小説) カテゴリーの中にある本の詳細情報を示すウィンドウ。このウィンドウには本の題名と本の価格が含まれています。

クリックして大きなイメージを見る

図 3. Detective カテゴリーの詳細

Detective (推理小説) カテゴリーの中にある本の詳細情報を示すウィンドウ。このウィンドウには本の題名と本の価格が含まれています。

カテゴリーのリンクを機能させるためには、Web ページ上の古いリンクを削除し、新しいリンクを適切な場所に挿入し、カテゴリーごとのリンクに各カテゴリーの詳細情報をバインドする必要があります。

リンクの挿入と削除

リンクを挿入または削除するには、2 通りの方法があります。1 番目の方法は、JSF コンポーネント・ツリーの中の動的要素の親コンポーネントを探し、要素を削除または挿入する方法です。動的要素の親コンポーネントが変わる場合には、この方法を採用する必要があります。2 番目の方法は、動的要素を Web ページに直接バインドする方法です。この方法は JSF コンポーネント・ツリーの中で親ノードを見つける必要がないため、1 番目の方法よりも簡単です。ただし簡単な代わりに制約もあり、この方法では削除または挿入対象の要素の親が固定されている必要があり、しかも実行前にその親がわかっている必要があります。私はこの 2 番目の方法を選びました (リスト 5)。この例ではカテゴリー・リンクの親は固定されている上に、あらかじめ定義されているからです。

リスト 5. GUI コンポーネントを作成/更新し、それぞれのコンポーネントを対応するアクション・ハンドラーにバインドする
category.jsp
……
<f:view> 
    <h:form id="helloForm"> 
     ……
      <h:panelGrid id="title">	   
	 <h:outputText id = "hello_title" value="Inventory"/>
	    <a4j:outputPanel  id = "book" 
                           binding = "#{InventoryBean.categorygrid}"/>
     ……
      </h:panelGrid>
    </h:form>
</f:view>

public class InventoryBean implements InventoryListner {
……
   private Category[] m_category;
   public HtmlAjaxOutputPanel getCategorygrid() {
	updateGUI();
	return categorygrid;
   }

   public void setCategorygrid(HtmlAjaxOutputPanel categorygrid) {
	this.categorygrid = categorygrid;
}

   private void updateGUI(){
	categorygrid.getChildren().clear();
	if (m_category != null) {
	    int num = m_category.length;
	    for (int index = 0; index < num; index++) {
		HtmlPanelGrid categorySubgrid = 
                      JSFUtil.getLinkgrid("Bookstore_sublink" + index,
		     "#{InventoryBean.category[" +index+ "].categoryLabel}",
		     "#{InventoryBean.category[" +index+ "].onClickAction}");
		categorygrid.getChildren().add(categorySubgrid);
	    }
	}
   }
……
}

これを見るとわかるように、category.jsp ファイルの updateGUI() 行は Managed Bean の動的要素にバインドするためのものです。この updateGUI() では、それまでに作成されたすべての動的要素がクリアーされると、新しいデータ・モデルに基づいて新しい動的要素が作成され、その新たに作成された動的要素があらかじめ定義された親に追加されます。

それぞれのリンクにさまざまな動作をバインドする

今度は、それぞれのカテゴリー・リンクに、そのカテゴリーの詳細情報をバインドする方法を説明しましょう。ここでは、配列に繰り返し処理を行って配列の各要素を GUI コンポーネントに転送し、この GUI コンポーネントを JSF コンポーネント・ツリーに挿入する必要があります。私のやり方では、すべてのカテゴリーを配列に入れ、各カテゴリーを配列の要素とします。すべての要素には、そのカテゴリーのラベルを返すためのメソッドと、クリック・アクションをバインドするためのメソッドがあります。各要素に独自のカテゴリー情報を保持させ、他の要素とは区別できるようにすることで、各要素は「onclick」アクションにバインドされた固有の動作をすることになります。

updateGUI() の中にある "Bookstore_sublink" + index はカテゴリー・リンクの ID です。"#{InventoryBean.category[" + index+ "].categoryLabel}" はカテゴリー・リンクのラベルで、"#{InventoryBean.category[" + index+ "].onClickAction}" はカテゴリー・リンクにバインドされたアクションです。また下記の getCategoryLabel() メソッドはリンクのラベルを返すために使われ、onClickAction() によってクリック・アクションがバインドされます (リスト 6)。

リスト 6. 値とアクションをバインドするメソッド
public class Category {
……
  private String category;
  private ArrayList<BookItem> bookitems;

  public String getCategoryLabel(){
	if(bookitems.size() <2){
		return bookitems.size() + " " + category;
	}else{
		return bookitems.size() + " " + category+"(s)";
	}
  }
	
public String onClickAction(){		
	HttpSession session =
               (HttpSession)JSFUtil.getFacesContext().
                         getExternalContext().getSession(true);
	        session.setAttribute("CATEGORY", this);
	        return "success";    
	}
……
}

Web ページをリダイレクトする

このセクションでは、クリックされたリンクに基づいてユーザーを新しいページにリダイレクトする方法を説明します。ここでは JSF のナビゲーション・ルールを使ってページをリダイレクトしています。OnClickAction() メソッドは “success” を返してアクションを開始します。新しいページのコンテンツは、Httpsessionに送信されるデータとして提供されます。このデータを Httpsession から取得するのは、新しいページの、DetailBean という Managed Bean です。DetailBean は、そのデータに従って GUI コンポーネントを作成します。

リスト 7 は実装の詳細を示しています。detail.jsp はユーザーのリダイレクト先の新しいページです。detail.jsp の DetailBean の一部である getDetailgrid() は、このページの動的要素を作成するメソッドにバインドされています。このメソッドの中で、まず表示対象のカテゴリー・データを取得し、次にそのデータに対応する GUI コンテンツを populate() メソッドを使って作成します。populate() を見ると、動的な GUI 要素と、さらにはページ・レイアウトまで、リアルタイムで作成する方法を理解できるはずです。すべてのページ情報は、Httpsession に送信されるカテゴリー・データとして渡されます。つまり理論的には、新しいページの外観は Httpsession に入力される情報によって決定されます。

リスト 7. ユーザーを詳細情報のページにリダイレクトする
detail.jsp
……
<f:view> 
    <h:form id="detailForm">
        <h:panelGrid id="list">
	   <h:outputText id = "book_list" value="#{DetailBean.title}"/>
		<h:panelGrid id = "detail" binding = "#{DetailBean.detailgrid}"/>
        </h:panelGrid>
        <h:commandButton id="back" value="Back" action="success"/> 
    </h:form>
</f:view>

public class DetailBean {
……
    private HtmlPanelGrid detailgrid = null;
    private Category cat;
    public HtmlPanelGrid getDetailgrid() {
	if(detailgrid == null){
		detailgrid = new HtmlPanelGrid();
	}
	detailgrid.getChildren().clear();
	HttpSession session =
        (HttpSession)JSFUtil.getFacesContext().getExternalContext().getSession(true);
	cat = (Category)session.getAttribute("CATEGORY");
	session.removeAttribute("CATEGORY");
	populate(detailgrid);	
	return detailgrid;
    }
    public void setDetailgrid(HtmlPanelGrid detailgrid) {
	this.detailgrid = detailgrid;
    }

    private void populate(HtmlPanelGrid parent) {
	if (cat != null) {
	    String category = cat.getCategory();
	    ArrayList<BookItem> items = cat.getBookitems();
	    if (category.equals("News paper")) {
                   //create GUI for News paper category.
             }else if (category.equals("Magazine")) {
                   //create GUI for Magazine category.
             }else{
                   //create GUI for other categories.
             }
    
}

ここまでの時点で、データ・モデルを更新する方法と動的 GUI 要素を作成する方法を学びました。そして、その方法における 3 つの側面、つまり Web ページ上の適切な場所に要素を挿入する方法とそこから要素を削除する方法、それぞれの要素にさまざまな動作をバインドする方法、Web ページをリダイレクトする方法、について詳しく説明しました。これらの関係をよく理解し、皆さん自身の開発シナリオに必要な部分を選択してください。


Web ページの動的要素を Ajax を使って更新する

このセクションでは、Web ページの動的要素を更新するために、図 2 の「Bean」レイヤーと「GUI」レイヤーとを関係付ける方法を説明します。ここでは RichFaces の Ajax4jsf を使って更新を行います。RichFaces は JavaScript を使わずに既存の JSF アプリケーションに Ajax 機能を追加するための、オープンソースのフレームワークです。Ajax4jsf を使うことで、サーバー・サイドからページの更新が行えないという現状の JSF の制約を克服することができるとともに、また必要なコンテンツのみを更新するという要件を満たすことができます。

RichFaces を登録する

RichFaces をインストールしたら、web.xml ファイルを変更してリスト 8 の行を追加し、RichFaces を登録する必要があります。

リスト 8. RichFaces を登録する
<!-- Plugging the "Blue Sky" skin into the project -->
<context-param>
   <param-name>org.richfaces.SKIN</param-name>
   <param-value>blueSky</param-value>
</context-param>
<!-- Making the RichFaces skin spread to standard HTML controls -->
<context-param>
      <param-name>org.richfaces.CONTROL_SKINNING</param-name>
      <param-value>enable</param-value>
</context-param>
<!-- Defining and mapping the RichFaces filter -->
<filter>
   <display-name>RichFaces Filter</display-name>
   <filter-name>richfaces</filter-name> 
   <filter-class>org.ajax4jsf.Filter</filter-class> 
</filter>
<filter-mapping> 
   <filter-name>richfaces</filter-name>
   <servlet-name>Faces Servlet</servlet-name>
   <dispatcher>REQUEST</dispatcher>
   <dispatcher>FORWARD</dispatcher>
   <dispatcher>INCLUDE</dispatcher>
</filter-mapping>

Web ページに加えられた変更

RichFaces を登録したら、category.jsp ファイルにリスト 9 のタグ群を追加し、Reverse Ajax を実現する必要があります。Reverse Ajax というのは、サーバー・サイドからクライアント・サイドにデータをプッシュし、Ajax 技術を使ってページを更新する方法です。

リスト 9. Web ページにデータをプッシュする
...
<f:view> 
    <h:form id="helloForm"> 
        <a4j:region>
       	   <a4j:push reRender="book" eventProducer="#{InventoryBean.addListener}"/>
     	</a4j:region>
    	<h:panelGrid id="title">
	   <h:outputText id = "hello_title" value="Inventory"/>
	   <a4j:outputPanel  id = "book" binding ="#{InventoryBean.categorygrid}"/>
	   <h:outputText id = "summary" 
                    value="#{InventoryBean.categoryNumber}"></h:outputText>
	</h:panelGrid>	
    </h:form>
</f:view>

a4j:push タグを見てください。eventProducer="#{InventoryBean.addListener}" によって、Web ページには Managed Bean に対するリスナーが登録され、Managed Bean は必要に応じて Web ページを更新することができるようになります。reRender = "book" の意味は、サーバー・サイドのデータを Web ページにプッシュした後、”book” という ID を持つコンポーネントのみを更新する必要がある、ということです。a4j:outputPanel は、Ajax レスポンスに応じて更新されるページ領域を指定します。

Managed Bean に加えられた変更

何らかのプッシュ・イベントが発生した場合にサーバー・サイドのデータがクライアント・サイドにプッシュされるように、Managed Bean では PushEventListener を登録する必要があります。このメソッドは eventProducer 属性によって Web ページにバインドされます。プッシュ・イベントは categoryChanged() メソッドの this.listener.onEvent(new EventObject(this)); によって生成されます。categoryChanged() メソッドはサーバー・サイドのデータが変更されるたびに呼び出されますが、このメソッドについては先ほど説明しました。リスト 10 はこの実装の詳細を示しています。

リスト 10. eventProducer を登録し、データをプッシュする
public class InventoryBean implements InventoryListner{
……
     public void addListener(EventListener listener) {
	synchronized (listener) {
	   if (this.listener != listener) {
		this.listener = (PushEventListener) listener;
	   }
	}
     }

     public void categoryChanged() {
	refresh();
	//code for refresh dynamic part via ajax
	this.listener.onEvent(new EventObject(this));
     }
}

これで、サーバー・サイドから Ajax による更新をプッシュすることができるようになりました。この手法を先ほど説明した手法と組み合わせると、図 2 の「データベース (Database)」、「Bean」、「GUI」の各レイヤーを互いに関係付けることができます。これまで説明したすべての方法と同様、この方法も使用するのに適した状況であれば、単独で使用することができます。


まとめ

JSF は、HTML ページの生成やユーザー入力の受信、そしてナビゲーション・フロー管理のための便利な Web フレームワークです。JSF でページを更新するためには、通常はユーザーが Web ページ上で何らかのアクションを実行する必要があり、それによって HTTP リクエストが生成されます。その HTTP リクエストに対して HTTP レスポンスが返され、その HTTP レスポンスによってページが更新されます。しかし JSF では、サーバー・サイドからのトリガーによって Web ページを変更することは容易ではありません。この記事で説明した方法では、サーバー・サイドからのリクエストに基づいて自動的に Web ページを更新できるだけではなく、サーバー・サイドのデータと、実行時に作成されて常に変化する Web ページ上の動的要素とを同期させることもできます。

参考文献

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Web development
ArticleID=446935
ArticleTitle=動的要素を持つ Web ページを自動的に更新する
publish-date=10132009