End-to-end-Verarbeitung mit UI-Programm und Datengrid

In diesem Thema wird ein Beispiel für die End-to-End-Verarbeitung erläutert. Als Back-End fungiert ein UI-Programm. Als Front-End fungiert eine Rich-UI-Anwendung, die ein Datengrid anzeigt. Hintergrundinformationen zu den beiden Technologien finden Sie in 'UI-Programm und Gateway-Service' und 'Rich-UI-Datengrid und Datengrid-QuickInfo'.

Anfängliche Darstellung der Rich-UI-Anwendung:

Rich-UI-Anwendung

Weitere Details finden Sie in den folgenden Abschnitten:

Ereignisse zur +++Laufzeit

Kurzbeschreibung: Das Datenladeprogramm fordert eine erste Seite an. Das UI-Programm ruft die für alle Seiten erforderlichen Datensätze ab und gibt die Gesamtzahl zusammen mit den Datensätzen für die erste Seite zurück. Das Datenladeprogramm antwortet auf Benutzerklicks mit dem Aufruf des UI-Programms, um je nach Bedarf zusätzliche Seiten bereitzustellen. Wenn der Benutzer die letzte Seite anfordert, die zuvor noch nicht angezeigt wurde, fordert das Datenladeprogramm die Seitennummer -1 an, wodurch das UI-Programm beendet wird.

Ausführliche Beschreibung:
  1. Das Datenladeprogramm geht wie folgt vor:
    1. Es ruft den Gateway-Service für den Zugriff auf das UI-Programm auf und gibt die Größe der Seite im Datengrid sowie die Anzahl der angeforderten Seiten (in diesem Fall 1) an.
    2. Das erste Element in einer globalen Feldgruppe (im Folgenden als Feldgruppe für angeforderte Seiten bezeichnet) wird auf 'true' gesetzt, da es das Protokoll der Seitenanforderungen enthält.
  2. Das UI-Programm führt die folgenden Schritte aus:
    1. Es greift auf eine relationale Datenbank zu, um die Anzahl der Datensätze zu bestimmen, die abgerufen werden sollen.
    2. Es testet, ob die Anzahl klein genug ist. Wenn der Test erfolgreich ist, greift das Programm erneut auf die Datenbank zu, um die Daten zu lesen, die für alle Seiten im Grid erforderlich sind. Wenn der Test fehlschlägt, löst das Programm eine angepasste Ausnahmebedingung aus und die Verarbeitung wird beendet.
    3. Die Daten werden in einer Feldgruppe mit SQL-Datensätzen gespeichert.
    4. Die Verbindung zur Datenbank wird getrennt.
    5. Die Anzahl der verfügbaren Seiten und die Anzahl der Datensätze auf der letzten Seite werden berechnet.
    6. Das Programm führt eine while-Schleife aus, die an diesem Punkt zwei Tasks ausführen muss: Zuerst muss eine Feldgruppe definiert werden, die nur die Anzahl der zu sendenden Datensätze enthält. Dann muss eine Anweisung converse zum Senden der Datensätze ausgegeben werden.
  3. Eine Callback-Funktion+++ in der Rich-UI-Anwendung geht wie folgt vor:
    1. Sie empfängt die Datensätze für die erste Seite zusammen mit der Anzahl Datensätze, die vom UI-Programm abgerufen wurden.
    2. Eine globale boolesche Variable wird versuchsweise auf 'true' gesetzt. Anhand dieser Variablen ermittelt das Datenladeprogramm, ob alle Seiten empfangen wurden.
    3. Sie berechnet die Anzahl der Seiten, die zur Verarbeitung sämtlicher Datensätze erforderlich sind.
    4. Gegebenenfalls wird die Feldgruppe für angeforderte Seiten erweitert und alle auf das erste folgenden Elemente werden auf 'false' gesetzt.
    5. Die Funktion testet, ob alle Seiten angefordert wurden. Ist dies der Fall, wird das UI-Programm aufgerufen (die Seitennummer ist auf -1 gesetzt). Andernfalls wird die globale boolesche Variable auf 'false' gesetzt.
    6. Die Datensätze für die erste Seite werden zu einer Feldgruppe (im Folgenden als Datenfeldgruppe bezeichnet) hinzugefügt.
    7. Die Datenfeldgruppe wird dem Datengrid zugewiesen.
    8. Die gridspezifische render-Funktion wird aufgerufen, um die Verarbeitung für die aktuelle Seite auszuführen.
  4. Der Benutzer klickt auf die Schaltflächenleiste im Datengrid, um eine andere Seite aufzurufen.
  5. Die Rich-UI-Anwendung ruft erneut das Datenladeprogramm auf, das die folgenden Schritte ausführt:
    1. Anhand eines Parameterwerts wird die Anzahl der angeforderten Seiten berechnet.
    2. Das entsprechende Element in der Feldgruppe für angeforderte Seiten wird auf 'true' gesetzt.
    3. Für den Zugriff auf das UI-Programm wird der Gateway-Service unter Angabe der Seitennummer aufgerufen.
  6. Das UI-Programm setzt die while-Schleife fort:
    1. Aus der Feldgruppe, die zuvor zum Senden von Daten an die Rich-UI-Anwendung verwendet wurde, werden alle Elemente entfernt.
    2. Dieser Feldgruppe wird die Anzahl der zu sendenden Datensätze hinzugefügt.
    3. Zum Senden der Datensätze wird eine Anweisung converse ausgegeben.

Entwicklungsaufwand

Überblick über die gesamte Task:
  1. Stellen Sie sicher, dass ein Anwendungsserver verfügbar ist.
  2. Erstellen Sie eine SQL-Datenbankverbindung (siehe 'SQL-Datenbankverbindung erstellen').
  3. Erstellen Sie das Zielprojekt: ein EGL-Webprojekt oder ein dynamisches Nicht-EGL-Webprojekt. Dieses Projekt wird im Folgenden als Zielprojekt bezeichnet.
  4. Gehen Sie bei der Entwicklung des Codes wie folgt vor:
    • Entwickeln Sie die Rich-UI-Anwendung in einem Rich-UI-Projekt und greifen Sie über die Dokumentvorschau des Rich-UI-Editors auf das UI-Programm zu. Stellen Sie sicher, dass der Implementierungsdeskriptor über ein REST-Service-Binding für den Gateway-Service verfügt:
      • Name des REST-Service-Bindings: UIGatewayService
      • Die Basis-URL sieht ungefähr wie folgt aus: http://localhost:8080/TargetProject/restservices/uiGatewayService
      Fügen Sie den folgenden Eintrag in den Ordner für Webinhalte ('WebContent') des Projekts, in den CSS-Unterordner und in die CSS-Datei ein, um alle Header zu zentrieren:
      .EglRuiDataGridHeaderCell {
         text-align: center;
      }

      Die Rich-UI-Anwendung muss erst dann im Zielprojekt implementiert werden, wenn alle Entwicklungsarbeiten abgeschlossen sind.

    • Entwickeln Sie das UI-Programm in einem allgemeinen Projekt. Führen Sie regelmäßig die folgenden Schritte aus:
      1. Generieren Sie das allgemeine Projekt im Zielprojekt. Durch Generierung des allgemeinen Projekts und nicht nur des Programms werden sowohl das Programm als auch der EGL-Implementierungsdeskriptor generiert.
      2. Implementieren Sie das Zielprojekt extern auf dem Anwendungsserver. Anschließend kann die Rich-UI-Anwendung über den Rich-UI-Editor (Registerkarte Vorschau) auf das UI-Programm zugreifen. Der Zugriff auf das UI-Programm bedeutet stets Zugriff auf den implementierten Code.

      Für eine erfolgreiche Generierung müssen Sie angeben, dass die Builddeskriptoroption genProject das Zielprojekt referenziert. Stellen Sie ferner sicher, dass der Implementierungsdeskriptor über einen Eintrag zur Implementierung des Gateway-REST-Service verfügt und das Kontrollkästchen Mit Zustandsüberwachung (STATEFUL) auf der Registerkarte Web-Service-Implementierung aktiviert ist.

      Führen Sie die folgenden Schritte aus, um sicherzustellen, dass die erforderlichen Details für den Datenbankzugriff zur Generierungs- und Laufzeit zur Verfügung stehen:
      1. Wählen Sie im Builddeskriptoreditor für das allgemeine Projekt die SQL-Datenbankverbindung aus, indem Sie die Datenbankverbindung im Listenfeld für Datenbankoptionen mit Hilfe von Verbindung laden angeben.
      2. Aktualisieren Sie den Builddeskriptoreditor, um sicherzustellen, dass die Details für Java™ EE generiert werden. Definieren Sie insbesondere die zusätzlichen Builddeskriptoroptionen j2ee (YES) und sqlJNDIName (jdbc/verbindung, wobei verbindung der Name der referenzierten Verbindung in Kleinbuchstaben ist).
      3. Stellen Sie sicher, dass das Zielprojekt auf die Datenbank zugreifen kann. Die folgenden Schritte müssten ausreichend sein: Klicken Sie mit der rechten Maustaste auf das Projekt, klicken Sie auf Eigenschaften und auf EGL-Laufzeitdatenquelle, und laden Sie Werte aus der Verbindung. Klicken Sie auf 'OK', wenn Sie gefragt werden, ob die Projektdateien aktualisiert werden sollen. Sollte später ein Fehler beim Zugriff auf die Datenbank zur Laufzeit auftreten, lesen Sie die Informationen in 'SQL-Datenbankverbindung zur Ausführungszeit verwenden'.
  5. Generieren Sie die Rich-UI-Anwendung und implementieren Sie diese im Zielprojekt, in dem das UI-Programm bereits enthalten ist.
  6. Greifen Sie auf die Rich-UI-Anwendung über einen Browser zu, der sich außerhalb der Workbench befindet. Die Webadresse könnte beispielsweise wie folgt lauten:
    http://localhost:8080/MyTargetProject/MyRichUIHandler-en_US.html	

    Dies ist die richtige Adresse, wenn Sie das Projekt auf Ihrer Workstation implementieren, der Anwendungsserver für Port 8080 empfangsbereit ist, der Name des Zielprojekts MyTargetProject und der Name des Rich-UI-Handlers MyRichUIHandler lauten, und die HTML-Datei für die Ländereinstellung 'US English' konfiguriert ist.

  7. Klicken Sie bis zur letzten Anzeige und dann eine zurück. An diesem Punkt sind alle Datensätze geladen und die Sortierung ist aktiviert.
    Anmerkung: In dieser Konfiguration funktioniert die Sortierung in einem Datengrid erst dann ordnungsgemäß, wenn alle Daten geladen sind. Aus diesem Grund setzt die Anwendung die gridspezifische Eigenschaft sortingEnabled zuerst auf 'false' und später auf 'true'.

Datenbankstruktur und Beispieldaten

Das UI-Programm verwendet einen SQL-Datensatz, um auf eine Datenbank namens Cars zuzugreifen und drei Tabellen zu verknüpfen:
  • In der Tabelle CAR_MASTER wird die VIN (Vehicle Identification Number) sowie zusätzliche Details zum Fahrzeug selbst gespeichert.
  • In der Tabelle CAR_PURCHASES sind Kaufdatum und -preis aufgeführt.
  • Die Tabellen CAR_SALES enthalten nachrangige Verkaufsdaten und -kosten.

Das Datenbankschema ist nur für ein Beispiel geeignet. Es liegen beispielsweise keine Integritätsbedingungen vor.

Im Folgenden sind die SQL-CREATE-Anweisungen aufgeführt:
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
);
Im Folgenden sind die SQL-INSERT-Anweisungen aufgeführt:
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-Programm

Im Folgenden ist ein Beispiel für ein UI-Programm aufgeführt:

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);
   end  
end  

Rich-UI-Anwendung

Im Folgenden ist ein Beispiel für eine Rich-UI-Anwendung aufgeführt:

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
   end
end

Feedback