UI プログラムおよびデータ・グリッドを使用したエンドツーエンド処理

このトピックでは、エンドツーエンド処理の例を示します。UI プログラムはバック・エンドにあります。フロントエンドには、データ・グリッドを表示する Rich UI アプリケーションがあります。この 2 つのテクノロジーの背景情報については、『UI プログラムおよびゲートウェイ・サービス』および『Rich UI の DataGrid および DataGridTooltip』を参照してください。

Rich UI アプリケーションの初期外観を次に示します。

Rich UI アプリケーション

さらに詳しくは 、次のセクションを参照してください。

実行時のイベント

要約すると、データ・ローダーが初期ページを要求し、UI プログラムがすべてのページに必要なレコードを取得し、最初のページのレコードと合計カウントを返します。データ・ローダーがユーザーのクリック操作に応答し、必要に応じて追加ページを提供するために UI プログラムを呼び出します。ユーザーがこれまでに表示されていない最終ページを要求すると、データ・ローダーはページ番号 -1 を要求します。これにより、UI プログラムが終了します。

以下に詳細を示します。
  1. データ・ローダーの振る舞いは以下のとおりです。
    1. UI プログラムにアクセスするため、データ・グリッドのページのサイズと、要求するページの番号 (この場合 1) を指定してゲートウェイ・サービスを呼び出します。
    2. グローバル配列の 1 番目の要素を TRUE に設定します。グローバル配列は、ページ要求の履歴を保持することから、今後要求ページ配列 と呼びます。
  2. UI プログラムは以下のステップを実行します。
    1. リレーショナル・データベースにアクセスし、取得するレコードの数を判別します。
    2. この数が十分に小さいかどうかをテストします。テストが正常に完了したら、データベースに再度アクセスし、グリッド内のすべてのページに必要なデータを読み取ります。テストが失敗した場合は、カスタマイズされた例外をスローし、処理を終了します。
    3. SQL レコードの配列にデータを保存します。
    4. データベースから切断します。
    5. 使用可能なページの数と、最終ページのレコード数を計算します。
    6. while ループを実行します。このループには、この時点で 2 つのタスク (送信レコードの数のみを保持する配列のセットアップと、レコードを送信する converse ステートメントの発行) があります。
  3. Rich UI アプリケーションのコールバック関数が、次の操作を実行します。
    1. 1 番目のページのレコードと、UI プログラムが取得したレコードの数を受け取ります。
    2. データ・ローダーがすべてのページを受け取ったかどうかを判別するために使用するブール値のグローバル変数に、一時的に TRUE を設定します。
    3. すべてのレコードを処理するために必要なページの数を計算します。
    4. 必要に応じて要求ページ配列を展開し、2 番目以降のすべての要素を FALSE に設定します。
    5. すべてのページが要求されたかどうかをテストします。要求されている場合は、ページ番号に -1 を設定して UI プログラムを呼び出します。要求されていない場合は、ブール値のグローバル変数を FALSE に設定します。
    6. 配列に最初のページのレコードを追加します (この配列は、今後データ配列 と呼びます)。
    7. データ配列をデータ・グリッドに割り当てます。
    8. グリッド固有の render 関数を呼び出し、現行ページの処理を実行します。
  4. ユーザーが別のページへ移動するためにデータ・グリッドのボタン・バーをクリックします。
  5. Rich UI アプリケーションによりデータ・ローダーが再度呼び出されます。データ・ローダーは以下のステップを実行します。
    1. パラメーター値を使用して、要求されるページの番号を計算します。
    2. 要求ページ配列の適切な要素を TRUE に設定します。
    3. UI プログラムにアクセスするため、ページ番号を指定してゲートウェイ・サービスを呼び出します。
  6. UI プログラムは while ループを続行します。
    1. Rich UI アプリケーションにデータを送信するために以前使用された配列からすべての要素を削除します。
    2. 送信するレコードの数を配列に追加します。
    3. converse ステートメントを発行して、レコードを送信します。

開発作業

作業の概要を以下に示します。
  1. アプリケーション・サーバーが使用可能な状態にあることを確認します。
  2. 『SQL データベース接続の作成』の説明に従って SQL データベース接続を作成します。
  3. ターゲット・プロジェクト (EGL Web プロジェクトまたは非 EGL 動的 Web プロジェクト) を作成します。今後は、このプロジェクトの名前を TargetProject として説明します。
  4. 以下のようにコードを開発します。
    • Rich UI プロジェクトで Rich UI アプリケーションを開発し、Rich UI エディターの「プレビュー」ペインから UI プログラムにアクセスします。デプロイメント記述子に、ゲートウェイ・サービスの REST サービス・バインディングが含まれていることを確認します。
      • REST サービス・バインディング名: UIGatewayService
      • ベース URL は http://localhost:8080/TargetProject/restservices/uiGatewayService のようになります。
      すべてのヘッダーを中央に揃えるには、次のエントリーをプロジェクトの WebContent フォルダー、css サブフォルダー、CSS ファイルに組み込みます。
      .EglRuiDataGridHeaderCell {
         text-align: center;
      }

      すべての開発作業が完了するまでは、Rich UI アプリケーションをターゲット・プロジェクトにデプロイする必要はありません。

    • 一般プロジェクトで UI プログラムを開発します。以下を定期的に行ってください。
      1. ターゲット・プロジェクトに一般プロジェクトを生成します。プログラムのみではなく一般プロジェクトを生成することで、プログラムと EGL デプロイメント記述子の両方が生成されます。
      2. ターゲット・プロジェクトをアプリケーション・サーバー外部でデプロイします。 これ以降、Rich UI アプリケーションは Rich UI エディターの「プレビュー」タブから UI プログラムにアクセスできます。UI プログラムにアクセスするときには、常にデプロイ済みコードにアクセスします。

      プロジェクトを適切に生成するには、genProject ビルド記述子オプションがターゲット・プロジェクトを参照することを指定します。また、デプロイメント記述子に、ゲートウェイ REST サービスをデプロイするための項目が含まれており、「Web サービス・デプロイメント」タブの「ステートフル」チェック・ボックスにチェック・マークを付けていることを確認します。

      データベース・アクセスに必要な詳細情報が、生成時および実行時に使用可能であるようにするには、以下のステップを実行します。
      1. 一般プロジェクトのビルド記述子エディターで SQL データベース接続を選択するため、「接続を使用した DB オプションのロード」のリスト・ボックスに SQL データベース接続を指定します。
      2. 同じビルド記述子を更新して、Java™ EE の詳細が生成されるようにします。特に、追加のビルド記述子オプション j2ee (YES) および sqlJNDIName (jdbc/connection (connection は参照する接続名の小文字表記)) を設定してください。
      3. ターゲット・プロジェクトからデータベースにアクセスできることを確認します。 このための手順として、プロジェクトを右クリックし、「プロパティー」をクリックし、「EGL ランタイム・データ・ソース」をクリックし、接続から値をロードし、プロジェクト・ファイルを更新するかどうか尋ねられたら「OK」をクリックします。後で実行時にデータベースへのアクセスで問題が発生した場合は、『実行時の SQL データベース接続の使用』で説明する手順を確認してください。
  5. Rich UI アプリケーションを生成してターゲット・プロジェクトにデプロイします。このプロジェクトには既に UI プログラムが含まれています。
  6. ワークベンチ外部のブラウザーで Rich UI アプリケーションにアクセスします。例えば、Web アドレスが以下のアドレスであるとします。
    http://localhost:8080/MyTargetProject/MyRichUIHandler-en_US.html	

    このアドレスが適切であるのは、プロジェクトをワークステーションにデプロイしており、アプリケーション・サーバーがポート 8080 で listen しており、ターゲット・プロジェクト名が MyTargetProject、Rich UI ハンドラー名が MyRichUIHandler であり、HTML ファイルの構成でロケールが米国英語に設定されている場合です。

  7. クリックして最終画面に移動してから、1 つ前の画面に戻ります。この時点ですべてのレコードがロードされており、ソートが有効になっています。
    注: 本資料の編集段階では、すべてのデータのロード後にのみデータ・グリッドでソートが正しく機能します。このため、アプリケーションはグリッド固有の sortingEnabled プロパティーを最初は FALSE に設定し、後で TRUE に設定します。

データベース構造とサンプル・データ

UI プログラムが、SQL レコードを使用してデータベース Cars にアクセスし、次の 3 つの表を結合します。
  • CAR_MASTER 表。この表には、車の車両識別番号 (VIN) およびその他の詳細情報が格納されています。
  • CAR_PURCHASES 表。この表には、購入日と購入費が格納されています。
  • CAR_SALES 表。この表には、後続の販売データおよびコストが格納されています。

データベース・スキーマはサンプルでのみ有効です。例えば、キー制約は存在しません。

以下に、SQL CREATE ステートメントを示します。
CREATE TABLE APP.CAR_MASTER
(
   VIN     CHAR(17)    NOT NULL,
   Make    VARCHAR(15) NOT NULL,
   Model   VARCHAR(15) NOT NULL,
   TheYear CHAR(4)     NOT NULL     
);

CREATE TABLE APP.CAR_PURCHASES
(
   VIN            CHAR(17)      NOT NULL,
   PURCHASE_DATE  DATE          NOT NULL,
   COST           DECIMAL(8,2)  NOT NULL
);
  
CREATE TABLE APP.CAR_SALES
(
   VIN            CHAR(17)      NOT NULL,
   SALE_DATE      DATE          NOT NULL,
   PRICE          DECIMAL(8,2)  NOT NULL
);
以下に、データを追加する SQL INSERT ステートメントを示します。
INSERT INTO APP.CAR_MASTER VALUES
  ( '11111111111111111', 'Honda', 'Accord', '1998'),
  ( '22222222222222222', 'Ford', 'Mustang', '2006'),
  ( '33333333333333333', 'Chevrolet', 'Camaro', '2010'),
  ( '44444444444444444', 'Toyota', 'RAV4', '2008'),
  ( '55555555555555555', 'Triumph', 'Spitfire', '1980'),
  ( '66666666666666666', 'BMW', '328XI', '2007'),
  ( '77777777777777777', 'Cadillac', 'Escalade', '2004'),
  ( '88888888888888888', 'Chrysler', 'Sebring', '2006'),
  ( '99999999999999999', 'Lexus', 'ES 300', '2009'),
  ( 'AAAAAAAAAAAAAAAAA', 'Honda', 'Civic', '2008'),
  ( 'BBBBBBBBBBBBBBBBB', 'Toyota', 'Celica', '2005');
  
INSERT INTO APP.CAR_PURCHASES VALUES
  ( '11111111111111111', '2004-06-15', 4000.00),
  ( '22222222222222222', '2008-07-26', 7200.00),
  ( '33333333333333333', '2010-11-11', 21000.00),
  ( '44444444444444444', '2009-02-02', 18000.00),
  ( '55555555555555555', '2007-01-04', 20500.00),
  ( '66666666666666666', '2010-08-08', 26900.00),
  ( '77777777777777777', '2010-04-04', 17200.00),
  ( '88888888888888888', '2009-11-06', 11400.00),
  ( '99999999999999999', '2009-12-07', 37000.00),
  ( 'AAAAAAAAAAAAAAAAA', '2010-11-12', 13000.00),
  ( 'BBBBBBBBBBBBBBBBB', '2008-03-03', 8300.00);
  
INSERT INTO APP.CAR_SALES VALUES
  ( '11111111111111111', '2004-11-18', 4800.00),
  ( '22222222222222222', '2009-01-12', 7000.00),
  ( '33333333333333333', '2010-11-14', 22000.00),
  ( '44444444444444444', '2009-02-03', 19200.00),
  ( '55555555555555555', '2007-02-18', 23500.00),
  ( '66666666666666666', '2010-09-20', 28000.00),
  ( '77777777777777777', '2010-05-06', 18500.00),
  ( '88888888888888888', '2009-11-06', 11400.00),
  ( '99999999999999999', '2010-02-07', 37200.00),
  ( 'AAAAAAAAAAAAAAAAA', '2010-11-16', 13800.00),
  ( 'BBBBBBBBBBBBBBBBB', '2008-04-05', 7400.00);

UI プログラム

次に UI プログラム例を示します。

package myPkg;

record CarStatus type SQLRecord {
                 tableNames =[["APP.CAR_MASTER", "A"],
                 ["APP.CAR_PURCHASES", "B"],["APP.CAR_SALES", "C"]], 
                 defaultSelectCondition = 
                 #sqlCondition{ A.VIN = B.VIN AND A.VIN = C.VIN ORDER BY VIN}, 
                 keyItems =[VIN]}
   VIN string{column = "A.VIN"};
   MAKE string;
   MODEL string;
   YEAR string{column = "A.THEYEAR"};
   PURCHASE_DATE date;
   COST decimal(8, 2);
   SALE_DATE date;
   PRICE decimal(8, 2);
end

// リクエスターからの初期ページ・サイズ
Record PageSizeContainer
   pageSize INT;   
end

Record MyException type Exception end

Record SendListContainer  
   // 使用可能なレコードの数
   numberOfRecords INT;    
   pageNumber INT = 1;         
   sendList CarStatus[]{};
end


program MyUIProgram type UIProgram
                    { inputUIRecord = myPageSizeContainer, segmented = true }
    
   const MAXIMUM_DATABASE_ROWS INT = 100;
      
   myPageSizeContainer PageSizeContainer;
         
   function main()
    	
      tentativeCarListSize INT;
      numberToSend INT;
      numberOnLastPage INT;
      sendCounter, startRowOnPage, endRowOnPage INT;       
      numberOfAvailablePages INT;
      mySendListContainer SendListContainer;
      myCarList CarStatus[]{};    
      i int;
                
      if (myPageSizeContainer.pageSize <= 0)
         throw new MyException{ message = 
            "Your page size is not valid.  You requested " + numberToSend + "."};
      end        
        
      tentativeCarListSize = getNumberofCars();
        
      if (tentativeCarListSize  > MAXIMUM_DATABASE_ROWS || tentativeCarListSize == 0)
         throw new MyException{message = 
            "The number of available rows is not valid.  Cannot return " + 
            tentativeCarListSize + " rows."};
      end
                       
      // 取得したデータベース行を使用して配列をセットアップする。
      try
         get myCarList;
         SQLLib.disconnect();       
         onException(except AnyException)
            throw new MyException{message = "Problem in data access.  "  
                                  + except.message};
      end

      // 現時点では、行が多すぎる場合には例外はスローされない
      mySendListContainer.numberOfRecords = myCarList.getSize(); 
                    
      numberOfAvailablePages = 
      MathLib.Ceiling(mySendListContainer.numberOfRecords / 
                      myPageSizeContainer.pageSize);        
      numberOnLastPage = mySendListContainer.numberOfRecords % 
                         myPageSizeContainer.pageSize; 
        
      if (numberOnLastPage == 0)
         numberOnLastPage = myPageSizeContainer.pageSize;	        	
      end
                
      while (mySendListContainer.pageNumber != -1)
        	
         // 送信する要素の数のための配列をセットアップする        	
         if (mySendListContainer.pageNumber < numberOfAvailablePages) 
            numberToSend = myPageSizeContainer.pageSize;           
         else
            numberToSend = numberOnLastPage; // 最終ページ
         end

         for (sendCounter from 1 to numberToSend)
            mySendListContainer.sendList.appendElement(new CarStatus{});
         end

         // 送信するデータベース・レコードを指定する
         if (mySendListContainer.pageNumber == 1)           
            startRowOnPage = 1;
         else              
            startRowOnPage = (mySendListContainer.pageNumber - 1) * 
                             myPageSizeContainer.pageSize + 1;
         end   
        
         if (mySendListContainer.pageNumber == numberOfAvailablePages )           	
            endRowOnPage = startRowOnPage + numberOnLastPage - 1;	
         else	
            endRowOnPage = startRowOnPage + myPageSizeContainer.pageSize - 1; 	
         end        
        
         // 送信するデータベース・レコードをコピーする
         i = startRowOnPage;

         for (n int from 1 to numberToSend)
            mySendListContainer.sendList[n] = myCarList[i];
            i = i + 1;
         end                      
    
         converse mySendListContainer;
              
         mySendListContainer.sendList.removeAll();
      end
   end // main() を終了する
    
   function getNumberOfCars() returns(int)
      numberOfRows INT;          
      countQuery STRING = "Select count(*) from APP.CAR_MASTER";

      try
         prepare myTest from countQuery;
         get with myTest into numberOfRows;
         onException(except AnyException)
            throw new MyException{message = 
                  "Problem in accessing the row count.  " + except.message};                end

      return(numberOfRows);
   end  
end  

Rich UI アプリケーション

次に Rich UI アプリケーションの例を示します。

package myRichUIPkg;

import com.ibm.egl.rui.widgets.DataGrid;
import com.ibm.egl.rui.widgets.DataGridColumn;
import com.ibm.egl.rui.widgets.DataGridLib;
import egl.ui.gateway.UIGatewayRecord;

record CarStatusBasic type SQLRecord
   VIN string;
   MAKE string;
   MODEL string;
   YEAR string;
   PURCHASE_DATE date;
   COST decimal(8, 2);
   SALE_DATE date;
   PRICE decimal(8, 2);
end

record PageSizeContainer
   pageSize int;
end

// フィールド名は JSON 文字列のエントリーと一致している必要がある
record CarStatusBasicContainer
   numberOfRecords int;
   pageRequested int{JSONName = "pageNumber"} = 1;
   sendList CarStatusBasic[]{};   
end

handler MyRichUIHandler type RUIhandler
   {initialUI =[grid], onConstructionFunction = start, 
    cssFile = "css/RichUIProject.css", title = "View Car Sales"}

   grid DataGrid{pageSize = PAGE_SIZE, showButtonBar = true, style = "overflow:auto",
      columns =[
      new DataGridColumn{name = "VIN", displayName = "VIN", width = 135, 
         alignment = DataGridLib.ALIGN_CENTER},
      new DataGridColumn{name = "MAKE", displayName = "Make", 
         alignment = DataGridLib.ALIGN_CENTER},
      new DataGridColumn{name = "Model", displayName = "Model", 
         alignment = DataGridLib.ALIGN_CENTER},
      new DataGridColumn{name = "Year", displayName = "Year", 
         alignment = DataGridLib.ALIGN_CENTER},
      new DataGridColumn{name = "Purchase_Date", displayName = "Purchase Date", 
         alignment = DataGridLib.ALIGN_CENTER},
      new DataGridColumn{name = "Cost", displayName = "Purchase Price", 
         alignment = DataGridLib.ALIGN_RIGHT, width=90},
      new DataGridColumn{name = "Sale_Date", displayName = "Sale Date", 
         alignment = DataGridLib.ALIGN_CENTER},
      new DataGridColumn{name = "Price", displayName = "Sale Price", 
         alignment = DataGridLib.ALIGN_RIGHT, width =80},
      new DataGridColumn{name = "Profit", displayName = "Profit (Loss)", 
         formatters =[formatProfit], alignment = DataGridLib.ALIGN_RIGHT, width = 80}]};

   const PAGE_SIZE int = 5;
   myPageSizeContainer PageSizeContainer;
   listContainer CarStatusBasicContainer{};
   firstInvocation boolean;
   gridDataList CarStatusBasic[1]{};
   requestedPage int = 1;
   pagesRequested boolean[]{};
   handledEarlier boolean;
   numberOfAvailablePages int;
   gateRec UIGatewayRecord{};
   allLoaded boolean;
      gatewayServiceVar UIGatewayService{@BindService{bindingKey = "UIGatewayService"}};

   function start()
      gateRec.uiProgramName = "myPkg.MyUIProgram";
      myPageSizeContainer.pageSize = PAGE_SIZE;
      StrLib.defaultDateFormat = "yyyy-MM-dd";
      pagesRequested.appendElement(false);
      firstInvocation = true;
      allLoaded = false;

      // 本資料の編集段階では、すべてのデータがロードされているる場合にのみソートが使用可能である
      grid.enableSorting = false;  
      
      grid.dataLoader = myDataLoader;
      grid.data = gridDataList as any[];
   end

   function formatProfit(class string inOut, value string inOut, rowData any in)
      calculation decimal(8, 2) = 
         rowData.Price as decimal(8, 2) - rowData.Cost as decimal(8, 2);

      if(calculation < 0)
         calculation = -calculation;
         value = "(" + calculation + ")";
      else
         value = calculation as string;
      end
   end

   function myDataLoader(startRow int in, endRow int in, sortFieldName string in, 
                         sortDirection int in) returns(boolean)

      if(allLoaded)
         return(true);
      end

      if(firstInvocation)

         if(pagesRequested[1] == true)
            handledEarlier = true;
         else
            handledEarlier = false;
            pagesRequested[1] = true;
            gateRec.data = ServiceLib.convertToJSON(myPageSizeContainer);

            call gatewayServiceVar.invokeProgram(gateRec)
                 returning to callbackFunc onException handleException;
         end

      else
         requestedPage = mathLib.ceiling(startRow /
                         myPageSizeContainer.pageSize);

         if(pagesRequested[requestedPage] == true)
            handledEarlier = true;
         else
            handledEarlier = false;
            listContainer.pageRequested = requestedPage;
            pagesRequested[requestedPage] = true;
            gateRec.data = ServiceLib.convertToJSON(listContainer);

            call gatewayServiceVar.invokeProgram(gateRec)
               returning to callbackFunc onException handleException;
         end
      end

      return(handledEarlier);
   end

   function callbackFunc(gatewayRecord uigatewayrecord in)
      i, n, startUpdate, endUpdate int;
      ServiceLib.convertFromJSON(gatewayRecord.data, listContainer);
      numberOfReturns int = listContainer.sendList.getSize();
      allLoaded = true;

      // pagesRequested 配列をセットアップできるのは、
      // UI プログラムからの使用可能なレコードの数を確認した後のみである
      if(firstInvocation)
         numberOfAvailablePages = 
            MathLib.Ceiling(listContainer.numberOfRecords /
                            myPageSizeContainer.pageSize);

         if(numberOfAvailablePages > 1)
         
            for(i from 2 to numberOfAvailablePages)
               pagesRequested.appendElement(false);
            end
         end

         // グリッドのデータ配列をセットアップする
         for(i from 2 to listContainer.numberOfRecords)
            GridDataList.appendElement(new CarStatusBasic);
         end

         firstInvocation = false;
      end

      // UI プログラムの使用を終了するかどうか
      for(i from 1 to numberOfAvailablePages)
         if(pagesRequested[i] == false)
            allLoaded = false;
         end
      end

      if(allLoaded)
         grid.enableSorting = true;
         listContainer.pageRequested = -1;
         gatewayRecord.data = ServiceLib.convertToJSON(listContainer);
         call gatewayServiceVar.invokeProgram(gatewayRecord)
              returning to handleServiceEnd onException handleException;
      end

      // データのロード
      if(requestedPage == 1)
         for(i from 1 to numberOfReturns)
            gridDataList[i] = listContainer.sendList[i];
         end
      else
         startUpdate = (requestedPage - 1) * myPageSizeContainer.pageSize + 1;
         endUpdate = startUpdate + numberOfReturns - 1;
         n = 1;

         for(i from startUpdate to endUpdate)
            gridDataList[i] = listContainer.sendList[n];
            n = n + 1;
         end
      end

      grid.data = gridDataList as any[];
      grid.render();
   end

   function handleException(exp AnyException in)
      SysLib.writeStdOut(exp.message);
      grid.cancelDataLoader();
   end

   function handleServiceEnd(gateRec uiGatewayRecord in)
      if(!gateRec.terminated)
         SysLib.writeStdOut("Error:  The program is still running.");
         grid.render();
      end
   end
end

フィードバック