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

런타임 시의 이벤트
간략히 설명하면, 데이터 로더가 첫 페이지를 요청합니다. UI 프로그램은 모든 페이지에 필요한 레코드를 검색하고 첫 페이지를 위한 레코드와 총 레코드 개수를 리턴합니다. 그 후 데이터 로더는 UI 프로그램을 호출하여 필요에 따라 추가 페이지를 제공함으로써 사용자 클릭에 응답합니다. 사용자가 이전에 본 적이 없는 마지막 페이지를 요청하면 데이터 로더는 UI 프로그램을 종료하는 페이지 숫자인 -1을 요청합니다.
- 데이터 로더는 다음과 같은 작업을 수행합니다.
- 데이터 그리드에 있는 페이지의 크기 및 요청된 페이지의 수(이 경우에는 1)를 지정하여 UI 프로그램에 액세스하는 데 필요한 게이트웨이 서비스를 호출합니다.
- 이하 페이지 요청 배열로 지칭되는 글로벌 배열의 첫 번째 요소를 true로 설정합니다. 이는 여기에 페이지 요청의 히스토리가 저장되어 있기 때문입니다.
- UI 프로그램은 다음 단계를 수행합니다.
- 관계형 데이터베이스에 액세스하여 검색할 레코드의 수를 판별합니다.
- 이 수가 충분히 작은지 테스트합니다. 이 테스트가 성공하면 데이터베이스에 다시 액세스하여 그리드에 있는 모든 페이지에서 필요로 하는 데이터를 읽습니다. 테스트가 실패하면 사용자 정의된 예외를 처리하고 처리를 종료합니다.
- 데이터를 SQL 레코드의 배열에 저장합니다.
- 데이터베이스와의 연결을 끊습니다.
- 사용 가능한 페이지의 수와 마지막 페이지에 있는 레코드의 수를 계산합니다.
- 이 시점에서 두 가지 태스크를 수행하는 while 루프를 실행합니다. 첫 번째는 전송될 레코드의 수만을 저장하는 배열을 설정하는 것입니다. 두 번째는 레코드를 전송하는 converse 문을 실행하는 것입니다.
- Rich UI 애플리케이션의 콜백 함수는 다음 작업을 수행합니다.
- 첫 번째 페이지의 레코드와 UI 프로그램이 검색한 레코드의 개수를 수신합니다.
- 데이터 로더가 모든 페이지가 수신되었는지 판별하는 데 사용하는 글로벌 부울 변수를 임시로 true로 설정합니다.
- 모든 레코드를 처리하는 데 필요한 페이지의 수를 계산합니다.
- 필요한 경우에는 첫 번째 요소 이후의 모든 요소를 false로 설정하여 페이지 요청 배열을 확장합니다.
- 모든 페이지가 요청되었는지 테스트합니다. 요청된 경우에는 페이지 수를 -1로 설정하여 UI 프로그램을 호출합니다. 그렇지 않은 경우에는 글로벌 부울 변수를 false로 설정합니다.
- 이하 데이터 배열로 지칭되는 배열에 첫 번째 페이지의 레코드를 추가합니다.
- 데이터 배열을 데이터 그리드에 지정합니다.
- 현재 페이지에 대한 처리를 완료하기 위해 그리드별 render 함수를 호출합니다.
- 사용자가 데이터 그리드에 있는 단추 막대를 클릭하여 다른 페이지로 이동합니다.
- Rich UI 애플리케이션이 데이터 로더를 다시 호출하며, 수행되는 단계는 다음과 같습니다.
- 매개변수값을 사용하여 요청되는 페이지의 수를 계산합니다.
- 페이지 요청 배열의 해당 요소를 true로 설정합니다.
- UI 프로그램에 액세스하기 위해 페이지 번호를 지정하여 게이트웨이 서비스를 호출합니다.
- UI 프로그램은 while 루프를 계속 진행합니다.
- 이전에 데이터를 Rich UI 애플리케이션으로 전송하는 데 사용된 배열에서 모든 요소를 제거합니다.
- 전송될 레코드의 수를 해당 배열에 추가합니다.
- converse 문을 실행하여 레코드를 전송합니다.
개발 노력
- 애플리케이션 서버가 사용 가능한지 확인합니다.
- "SQL 데이터베이스 연결 작성"에 설명되어 있는 바와 같이 SQL 데이터베이스 연결을 작성합니다.
- 대상 프로젝트(EGL 웹 프로젝트 또는 비EGL 동적 웹 프로젝트)를 작성합니다. 이 프로젝트의 이름은 이하 TargetProject로 가정합니다.
- 다음과 같이 코드를 개발합니다.
- 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 프로그램을 일반 프로젝트에서 개발합니다. 주기적으로 다음 작업을 수행하십시오.
- 대상 프로젝트에 일반 프로젝트를 생성하십시오. 프로그램이 아니라 일반 프로젝트를 생성하면 프로그램과 EGL 배치 디스크립터를 둘 다 생성할 수 있습니다.
- 대상 프로젝트를 외부적으로 애플리케이션 서버에 배치하십시오. 이렇게 하면 Rich UI 애플리케이션이 Rich UI 편집기 미리보기 탭에서 UI 프로그램에 액세스할 수 있습니다. 이 UI 프로그램에 대한 액세스는 항상 배치된 코드에 대해 이뤄집니다.
생성이 작동하도록 하려면 genProject 빌드 디스크립터 옵션이 대상 프로젝트를 참조하도록 지정하십시오. 또한 배치 디스크립터에 게이트웨이 REST 서비스를 배치하는 데 필요한 항목이 있으며 웹 서비스 배치 탭의 Stateful 선택란을 선택했는지 확인하십시오.
데이터베이스 액세스에 필요한 세부사항이 생성 시 및 런타임 시에 사용 가능한지 확인하려면 다음 단계를 따르십시오.- 일반 프로젝트의 빌드 디스크립터 편집기에서, 연결을 사용하여 DB 옵션 로드의 목록 상자에 있는 데이터베이스 연결을 지정하여 SQL 데이터베이스 연결을 선택하십시오.
- 동일한 빌드 디스크립터를 업데이트하여 Java™ EE에 대한 세부사항이 생성되었는지 확인하십시오. 구체적으로는, 추가 빌드 디스크립터 옵션 j2ee(YES) 및 sqlJNDIName(jdbc/connection, 여기서connection은 참조한 연결의 소문자 이름)을 설정하십시오.
- 대상 프로젝트가 데이터베이스에 액세스할 수 있는지 확인하십시오. 프로젝트를 마우스 오른쪽 단추로 클릭하고, 특성을 클릭하고, EGL 런타임 데이터 소스를 클릭하고, 연결로부터 값을 로드하고, 프로젝트 파일을 업데이트할 것인지 물음이 표시되면 확인을 클릭하여 이를 수행할 수 있습니다. 나중에 런타임 시에 데이터베이스에 액세스하는 데 문제가 발생하는 경우에는 "런타임 시 SQL 데이터베이스 연결 사용"에 설명되어 있는 프로시저를 검토하십시오.
- Rich UI 편집기의 미리보기 분할창으로부터 UI 프로그램에 액세스하여 Rich UI 프로젝트에 Rich UI 애플리케이션 프로젝트를 작성하십시오.
배치 디스크립터에 게이트웨이 서비스에 대한 REST 서비스 바인딩이 있는지 확인하십시오.
- Rich UI 애플리케이션을 생성하고 이미 UI 프로그램을 포함하고 있는 대상 프로젝트에 배치하십시오.
- 워크벤치의 외부에 있는 브라우저에서 Rich UI 애플리케이션에 액세스하십시오. 예를 들면, 사용자의 웹 주소는 다음과 같을 수 있습니다.
http://localhost:8080/MyTargetProject/MyRichUIHandler-en_US.html이 주소는 워크스테이션에 프로젝트를 배치하며, 애플리케이션 서버가 포트 8080을 청취하고 있고, 대상 프로젝트의 이름이 MyTargetProject이며, Rich UI 핸들러의 이름이 MyRichUIHandler이고 HTML 파일이 미국 영어 로케일에 대해 구성되어 있는 경우에 적합합니다.
- 클릭하여 마지막 화면까지 이동한 후 이전을 한 번 클릭하십시오. 이렇게 하면 모든 레코드가 로드되며 정렬이 사용으로 설정됩니다.
참고: 이 작성에서 정렬은 모든 데이터가 로드된 후에만 데이터 그리드에서 올바르게 작동합니다. 이 이유로 인해 애플리케이션은 그리드 고유 sortingEnabled 특성을 처음에는 false로 설정하고 나중에 true로 설정합니다.
데이터베이스 구조 및 샘플 데이터
- CAR_MASTER 테이블에는 차량 식별 번호(VIN) 및 차량 자체에 대한 추가 세부사항이 저장되어 있습니다.
- CAR_PURCHASES 테이블은 구매 날짜 및 비용을 제공합니다.
- CAR_SALES 테이블은 후속 판매 날짜 및 비용을 제공합니다.
이 데이터베이스 스키마는 샘플에만 적합합니다. 예를 들면, 이 스키마에는 키 제한조건이 없습니다.
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
);
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