UI 프로그램 및 데이터 그리드를 사용한 엔드-투-엔드 처리

이 주제에서는 엔드-투-엔드 처리에 대한 예제를 제공합니다. 백엔드는 UI 프로그램입니다. 프론트 엔드는 데이터 그리드를 표시하는 Rich UI 애플리케이션입니다. 두 기술에 대한 배경 정보는 "UI 프로그램 및 게이트웨이 서비스"와 "Rich UI DataGrid 및 DataGridToolTip"을 참조하십시오.

Rich UI 애플리케이션의 초기 모양은 다음과 같습니다.

Rich UI 애플리케이션

런타임 시의 이벤트

간략히 설명하면, 데이터 로더가 첫 페이지를 요청합니다. UI 프로그램은 모든 페이지에 필요한 레코드를 검색하고 첫 페이지를 위한 레코드와 총 레코드 개수를 리턴합니다. 그 후 데이터 로더는 UI 프로그램을 호출하여 필요에 따라 추가 페이지를 제공함으로써 사용자 클릭에 응답합니다. 사용자가 이전에 본 적이 없는 마지막 페이지를 요청하면 데이터 로더는 UI 프로그램을 종료하는 페이지 숫자인 -1을 요청합니다.

세부사항은 다음과 같습니다.
  1. 데이터 로더는 다음과 같은 작업을 수행합니다.
    1. 데이터 그리드에 있는 페이지의 크기 및 요청된 페이지의 수(이 경우에는 1)를 지정하여 UI 프로그램에 액세스하는 데 필요한 게이트웨이 서비스를 호출합니다.
    2. 이하 페이지 요청 배열로 지칭되는 글로벌 배열의 첫 번째 요소를 true로 설정합니다. 이는 여기에 페이지 요청의 히스토리가 저장되어 있기 때문입니다.
  2. UI 프로그램은 다음 단계를 수행합니다.
    1. 관계형 데이터베이스에 액세스하여 검색할 레코드의 수를 판별합니다.
    2. 이 수가 충분히 작은지 테스트합니다. 이 테스트가 성공하면 데이터베이스에 다시 액세스하여 그리드에 있는 모든 페이지에서 필요로 하는 데이터를 읽습니다. 테스트가 실패하면 사용자 정의된 예외를 처리하고 처리를 종료합니다.
    3. 데이터를 SQL 레코드의 배열에 저장합니다.
    4. 데이터베이스와의 연결을 끊습니다.
    5. 사용 가능한 페이지의 수와 마지막 페이지에 있는 레코드의 수를 계산합니다.
    6. 이 시점에서 두 가지 태스크를 수행하는 while 루프를 실행합니다. 첫 번째는 전송될 레코드의 수만을 저장하는 배열을 설정하는 것입니다. 두 번째는 레코드를 전송하는 converse 문을 실행하는 것입니다.
  3. Rich UI 애플리케이션의 콜백 함수는 다음 작업을 수행합니다.
    1. 첫 번째 페이지의 레코드와 UI 프로그램이 검색한 레코드의 개수를 수신합니다.
    2. 데이터 로더가 모든 페이지가 수신되었는지 판별하는 데 사용하는 글로벌 부울 변수를 임시로 true로 설정합니다.
    3. 모든 레코드를 처리하는 데 필요한 페이지의 수를 계산합니다.
    4. 필요한 경우에는 첫 번째 요소 이후의 모든 요소를 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 웹 프로젝트 또는 비EGL 동적 웹 프로젝트)를 작성합니다. 이 프로젝트의 이름은 이하 TargetProject로 가정합니다.
  4. 다음과 같이 코드를 개발합니다.
    • Rich UI 편집기의 미리보기 분할창으로부터 UI 프로그램에 액세스하여 Rich UI 프로젝트에 Rich 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 서비스를 배치하는 데 필요한 항목이 있으며 웹 서비스 배치 탭의 Stateful 선택란을 선택했는지 확인하십시오.

      데이터베이스 액세스에 필요한 세부사항이 생성 시 및 런타임 시에 사용 가능한지 확인하려면 다음 단계를 따르십시오.
      1. 일반 프로젝트의 빌드 디스크립터 편집기에서, 연결을 사용하여 DB 옵션 로드의 목록 상자에 있는 데이터베이스 연결을 지정하여 SQL 데이터베이스 연결을 선택하십시오.
      2. 동일한 빌드 디스크립터를 업데이트하여 Java™ EE에 대한 세부사항이 생성되었는지 확인하십시오. 구체적으로는, 추가 빌드 디스크립터 옵션 j2ee(YES) 및 sqlJNDIName(jdbc/connection, 여기서connection은 참조한 연결의 소문자 이름)을 설정하십시오.
      3. 대상 프로젝트가 데이터베이스에 액세스할 수 있는지 확인하십시오. 프로젝트를 마우스 오른쪽 단추로 클릭하고, 특성을 클릭하고, EGL 런타임 데이터 소스를 클릭하고, 연결로부터 값을 로드하고, 프로젝트 파일을 업데이트할 것인지 물음이 표시되면 확인을 클릭하여 이를 수행할 수 있습니다. 나중에 런타임 시에 데이터베이스에 액세스하는 데 문제가 발생하는 경우에는 "런타임 시 SQL 데이터베이스 연결 사용"에 설명되어 있는 프로시저를 검토하십시오.
  5. Rich UI 애플리케이션을 생성하고 이미 UI 프로그램을 포함하고 있는 대상 프로젝트에 배치하십시오.
  6. 워크벤치의 외부에 있는 브라우저에서 Rich UI 애플리케이션에 액세스하십시오. 예를 들면, 사용자의 웹 주소는 다음과 같을 수 있습니다.
    http://localhost:8080/MyTargetProject/MyRichUIHandler-en_US.html	

    이 주소는 워크스테이션에 프로젝트를 배치하며, 애플리케이션 서버가 포트 8080을 청취하고 있고, 대상 프로젝트의 이름이 MyTargetProject이며, Rich UI 핸들러의 이름이 MyRichUIHandler이고 HTML 파일이 미국 영어 로케일에 대해 구성되어 있는 경우에 적합합니다.

  7. 클릭하여 마지막 화면까지 이동한 후 이전을 한 번 클릭하십시오. 이렇게 하면 모든 레코드가 로드되며 정렬이 사용으로 설정됩니다.
    참고: 이 작성에서 정렬은 모든 데이터가 로드된 후에만 데이터 그리드에서 올바르게 작동합니다. 이 이유로 인해 애플리케이션은 그리드 고유 sortingEnabled 특성을 처음에는 false로 설정하고 나중에 true로 설정합니다.

데이터베이스 구조 및 샘플 데이터

UI 프로그램은 SQL 레코드를 사용하여 이름이 Cars인 데이터베이스에 액세스하고 세 개의 테이블을 결합합니다.
  • 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
// initial page size from requester
Record PageSizeContainer
   pageSize INT;   
end
Record MyException type Exception end

Record SendListContainer  
   // number of available records
   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                       
      // set up array with retrieved database rows.
      try
         get myCarList;
         SQLLib.disconnect();       
         onException(except AnyException)
            throw new MyException{message = "Problem in data access.  "  
                                  + except.message};
      end
      // no exception thrown if too many rows now
      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)
        	
         // set up array for the number of elements to send        	
         if (mySendListContainer.pageNumber < numberOfAvailablePages) 
            numberToSend = myPageSizeContainer.pageSize;           
         else
            numberToSend = numberOnLastPage; // last page
         end
         for (sendCounter from 1 to numberToSend)
            mySendListContainer.sendList.appendElement(new CarStatus{});
         end
         // specify which database records to send
         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        
         // copy the database records to send           
         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 // 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);
   endend  

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
// field names must match entries in the JSON string
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;

      // at this writing, sorting is available only when all the data is loaded
      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;

      // can set up the pagesRequested array only after learning 
      // the number of records available from the UI program        
      if(firstInvocation)
         numberOfAvailablePages = 
            MathLib.Ceiling(listContainer.numberOfRecords /
                            myPageSizeContainer.pageSize);

         if(numberOfAvailablePages > 1)
         
            for(i from 2 to numberOfAvailablePages)
               pagesRequested.appendElement(false);
            end         end
         // set up the data array for the grid 
         for(i from 2 to listContainer.numberOfRecords)
            GridDataList.appendElement(new CarStatusBasic);
         end
         firstInvocation = false;
      end
      // end use of the UI program?         
      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
      // load data
      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   endend