Pagination - Actions
Introduction
Pagination is based around client implementations of Composite Cache and is complex, so read that first. Pagination Actions are also client implementations of Template classes. Examples here use Convention plug-in but that's not required.
| Action type | Template classes | Description |
|---|---|---|
| View List | AbstractViewListActionSupport | Reads and displays entire list, a page, or a single record with details, which are usually lazy loaded. May read URL parameter to set the page number or select record by index. |
| Find List | AbstractFindBaseRecordListActionSupport, AbstractFindIdListActionSupport, AbstractFindIndexListActionSupport, AbstractFindListActionSupport, AbstractFindListMultiModeActionSupport |
Creates list finder Commands, usually from a form, then redirects to a View List Action. |
| Save List | AbstractSaveListActionSupport | Updates the selected record, usually from a form, then redirects to a View List Action. May read URL parameter to set the selected item index. |
View List
Introduction
View List Actions are client implementations of AbstractViewListActionSupport and various generic types must be defined and abstract functions implemented.
Generic types
| Generic type | Description |
|---|---|
| M | Entry type of master list, or NA if Action displays master list. |
| K | Type of list's primary key. |
| T | Entry type of list. |
| FT | Type of view helper that's a formatted version of T. This can be a class with fields with the same names as in T but all strings but additional fields for presentation calculations are common. This can be the same as T but this depends on the ValueStack for formatting and not recommended. |
For example:
@InterceptorRefs({ @InterceptorRef(value="ViewStack") })@Results({ @Result(name="success", location="/views/account/AccountList.jsp") })public class ViewAccountListAction extends AflViewListActionSupport<NA,GenericAccountId,GenericAccountDTO,AccountSocnetDisplay> { ... }
Required functions
execute only calls the superclass function but must still be implemented to have annotation.
@Action("/account/ViewAccountList")@Overridepublic String execute() throws Exception { return super.execute(); }
formatRecord is called for each record that will be displayed to return a formatted version.
@Override
protected AccountSocnetDisplay2 formatRecord(NA selectedMaster, GenericAccountDTO record) {
return new AccountSocnetDisplay(record);
}
getListCache should return the list cache to display.
@Override
protected ListCache<NA, GenericAccountId, GenericAccountDTO> getListCache() {
return AccountCompositeCache.getInstance(getAflBrowserTabSession()).getMasterListCache();
}
getMasterListCache should return the master list cache, or null if this page displays the master list cache.
@Override
protected ListCache<?, ?, NA> getMasterListCache() {
return null;
}
ViewListConfig configures how much is displayed, amongst other parameters, though ViewListConfig.makeNonPagedListConfig() and ViewListConfig.makePagedListConfig(pageSize) suffices for lists and ViewListConfig.makeSingleRecordConfig() for detailed single records.
@Override
protected ViewListConfig getViewListConfig() {
return ViewListConfig.makePagedListConfig(20);
}
Useful optional functions
startRecordFormatting and endRecordFormatting is called before and after the loop for formatRecord and is useful for loading data for record formatting.
getViewResponse can load and format other data for the JSP page.
getItemSorter can read URL parameters ('sort' and 'ascending' by default) to create a Command that sorts the list or leave as is. As sorting requires loading the entire list, this is only recommended for full list pagination mode.
View List JSP
Introduction
The core property is formattedList, which is type FormattedListDisplay. A few uses are shown below.
List
The formattedList.list property is the page of formatted records for display. As lists typically link to a detailed single record page, the example below includes the link to set the selected item.
<TABLE class="list" >
<s:iterator var="account" status="loopStatus" value="formattedList.list">
<s:url var="detailpage" value="/account/ViewAccount.do2">
<s:param name="select" value="#loopStatus.index + formattedList.pageStartIndex" />
</s:url>
<TR STYLE="cursor: pointer;" onclick="location = '<s:property value="#detailpage" />';">
<TD style="width: 80px;"><s:property value="#account.service"/></TD>
<TD style="width: 210px;"><s:property value="#account.email"/></TD>
<TD style="width: 110px"><s:property value="#account.nickname" /></TD>
<TD style="width: 100px"><s:property value="#account.CLI" /></TD>
<TD style="width: 110px"><s:property value="#account.firstName" /></TD>
<TD style="width: 110px;"><s:property value="#account.lastName" /></TD>
<TD style="width: 50px"><s:property value="#account.status" /></TD>
<TD style="width: 60px"><s:property value="#account.ageVerifiedFlag" /></TD>
</TR>
</s:iterator>
</TABLE>
Page navigation
The formattedList property has sub-properties, such as page number, which can be used for navigation controls, such as below.
<DIV CLASS="footing_box">
<DIV CLASS="invisibleinlinepanel" STYLE="width: 165px;">
<s:if test="formattedList.page > 1" >
<s:url var="backwardUrl" value="/tools/ViewContentTypeList.do2" >
<s:param name="page" value="formattedList.page - 1" />
</s:url>
<s:url var="firstUrl" value="/tools/ViewContentTypeList.do2" >
<s:param name="page" value="1" />
</s:url>
<BUTTON CLASS="action_button" TYPE="BUTTON" onclick="location = '<s:property value='#firstUrl' />'">First</BUTTON>
<BUTTON CLASS="action_button" TYPE="BUTTON" onclick="location = '<s:property value='#backwardUrl' />'">Previous</BUTTON>
</s:if>
</DIV>
<DIV CLASS="invisibleinlinepanel" STYLE="width: 630px; text-align: center;">
<P CLASS="navigationcounterlabel">Page <s:property value="formattedList.page"/> of <s:property value="formattedList.totalPages"/></P>
</DIV>
<DIV CLASS="invisibleinlinepanel" STYLE="width: 145px;">
<s:if test="formattedList.page < formattedList.totalPages">
<s:url var="forwardUrl" value="/tools/ViewContentTypeList.do2" >
<s:param name="page" value="formattedList.page + 1" />
</s:url>
<s:url var="lastUrl" value="/tools/ViewContentTypeList.do2" >
<s:param name="page" value="formattedList.totalPages" />
</s:url>
<BUTTON CLASS="action_button" TYPE="BUTTON" onclick="location = '<s:property value='#forwardUrl' />'">Next</BUTTON>
<BUTTON CLASS="action_button" TYPE="BUTTON" onclick="location = '<s:property value='#lastUrl' />'">Last</BUTTON>
</s:if>
</DIV>
</DIV>
Detailed single record
The formattedSelectedItem property is the formatted version of the selected item.
<INPUT STYLE="width: 60px;" CLASS="displayfield" value="<s:property value="formattedSelectedItem.accountId"/>" name="accountId"/>
Single record navigation
Similarly, formattedList sub-properties, can be used for navigation controls, such as below.
<DIV CLASS="footing_box">
<DIV>
<s:if test="formattedList.selectedIndex > 0">
<s:url var="firstUrl" value="/tools/ViewContentType.do2">
<s:param name="select" value="0" />
</s:url>
<s:url var="backwardUrl" value="/tools/ViewContentType.do2">
<s:param name="select" value="formattedList.selectedIndex - 1" />
</s:url>
<BUTTON CLASS="action_button" TYPE="BUTTON" onclick="location = '<s:property value='#firstUrl' />'">First |<</BUTTON>
<BUTTON CLASS="action_button" TYPE="BUTTON" onclick="location = '<s:property value='#backwardUrl' />'">Previous <<</BUTTON>
</s:if>
<BUTTON CLASS="action_button" TYPE="BUTTON" onclick="location = '<s:url value="/tools/ViewContentTypeList.do2" />'">Back to List</BUTTON>
<s:if test="formattedList.selectedIndex > -1">
<P CLASS="navigationcounterlabel" >Record <s:property value="formattedList.selectedIndex + 1" /> of <s:property value="formattedList.totalItems" /></P>
</s:if>
<s:if test="formattedList.selectedIndex > -1 && formattedList.selectedIndex < formattedList.totalItems - 1">
<s:url var="forwardUrl" value="/tools/ViewContentType.do2">
<s:param name="select" value="formattedList.selectedIndex + 1" />
</s:url>
<s:url var="lastUrl" value="/tools/ViewContentType.do2">
<s:param name="select" value="formattedList.totalItems - 1" />
</s:url>
<BUTTON CLASS="action_button" TYPE="BUTTON" onclick="location = '<s:property value='#forwardUrl' />'" >Next >></BUTTON>
<BUTTON CLASS="action_button" TYPE="BUTTON" onclick="location = '<s:property value='#lastUrl' />'" >Last >|</BUTTON>
</s:if>
</DIV>
</DIV>
Add mode
The existingSelection property checks if formattedList.selectedIndex is zero or more. Setting the selected index to -1 is useful for single record pages to detect an 'add' mode, such as below.
<s:if test="existingSelection"> <s:url var="formURL" value="/tools/UpdateContentType.do2" /> </s:if> <s:else> <s:url var="formURL" value="/tools/AddContentType.do2" /> </s:else> <FORM NAME="form" ACTION="<s:property value="#formURL" />"> ... </FORM>
Find List Actions
Introduction
Find List Actions are client implementations of various Template classes, one for each pagination mode, and various generic types must be defined and abstract functions implemented. There's also a special Template that chooses the pagination mode at run-time.
| Pagination mode | Template class |
|---|---|
| Base record list | AbstractFindBaseRecordListActionSupport |
| Full list | AbstractFindListActionSupport |
| Multiple | AbstractFindListMultiModeActionSupport |
| Page by id | AbstractFindIdListActionSupport |
| Page by index | AbstractFindIndexListActionSupport |
Generic types
| Generic type for | |||||
|---|---|---|---|---|---|
| Base record list | Page by id | Page by index | Full list | Multi mode | Description |
| M | M | M | M | M | Entry type of master list, or NA if Action finds master list. |
| K | K | Type of list's primary key. | |||
| T | T | T | T | Entry type of list. | |
| F | F | F | F | F | Type of form read this Action, or NullForm for no form. |
For example:
@InterceptorRefs({ @InterceptorRef(value="formDrivenStack") })@Results({ @Result(name="error", type="redirectAction", params = {"namespace", "/account", "actionName", "ViewAccountList"}), @Result(name="inout", type="redirectAction", params = {"namespace", "/account", "actionName", "ViewAccountList"}), @Result(name="success", type="redirectAction", params = {"namespace", "/account", "actionName", "ViewAccountList"}) })public class FindAccountsMultiAction2 extends AbstractFindListMultiModeActionSupport<NA,GenericAccountId,GenericAccountVO,FindAccountsMultiForm> { ... }
Required functions
execute functions only calls the superclass function but must still be implemented to have annotation.
@Action("/account/FindAccountsMulti")@Overridepublic String execute() throws Exception { return super.execute(); }
Each Template class has its own version of the function for creating finder Commands. Each returns an instance of
its own response class, usually created by a static function named makeSuccessResponse. Use
makeFailureResponse if an error happens. Use the function named like
makeSuccessWithListResponse if the query needs to be run immediately, such as treating no results as
an error.
| Pagination mode | Create finder function | Finder interfaces | Response class |
|---|---|---|---|
| Base record list | getBaseRecordListFinder | ListFinder | FindBaseRecordListResponse |
| Full list | getFindCommand | ListFinder | FindListResponse |
| Multiple | getFindCommand | ListFinder, IdListFinder, and ListSizeFinder | FindListMultiModeResponse |
| Page by id | getFindCommand | IdListFinder | FindIdListResponse |
| Page by index | getFindCommand | ListSizeFinder | FindIndexListResponse |
The example below creates various finder Commands with some calling a remote service that supports pagination.
@Override
protected FindListMultiModeResponse<Object,GenericAccountId,GenericAccountVO> getFindCommand(NA selectedMaster) throws Exception {
FindAccountsMultiForm form;
Integer serviceId;
form = getForm();
serviceId = form.getServiceId();
switch (form.getSearchCriteria()) {
case ACCOUNT_ID: return FindListMultiModeResponse.makeSuccessFullListResponse(new AccountIdListFinder(form.getAccountId()));
case EMAIL: return FindListMultiModeResponse.makeSuccessFullListResponse(new EmailAccountListFinder(form.getEmail()));
case JOINED_DATE: return FindListMultiModeResponse.makeSuccessFullListResponse(new JoinedDateRangeAccountListFinder(form.getJoinedDateRangeStart(), form.getJoinedDateRangeEnd()));
case SERVICE_PARTIAL_NICKNAME: return FindListMultiModeResponse.makeSuccessIdListResponse(new ServicePartialNicknameAccountIdsFinder(serviceId, form.getNickname()));
case SERVICE_PARTIAL_FORENAME: return FindListMultiModeResponse.makeSuccessIdListResponse(new ServicePartialForenameAccountIdsFinder(serviceId, form.getForename()));
case SERVICE_PARTIAL_SURNAME: return FindListMultiModeResponse.makeSuccessIdListResponse(new ServicePartialSurnameAccountIdsFinder(serviceId, form.getSurname()));
}
return null;
}
getListCache should return the list cache to display.
@Override
protected ListCache<NA, GenericAccountId, GenericAccountDTO> getListCache() {
return AccountCompositeCache.getInstance(getAflBrowserTabSession()).getMasterListCache();
}
getMasterListCache should return the master list cache, or null if this Action finds for the master list cache.
@Override
protected ListCache<?, ?, NA> getMasterListCache() {
return null;
}
Each Template class has its own version of the function for configuring behaviour, which is whether to return an error message if no results are found and the error message to display.
| Pagination mode | Create finder function | Response class |
|---|---|---|
| Base record list | getFindBaseRecordListConfig | .FindBaseRecordListConfig |
| Full list | getFindListConfig | FindListConfig |
| Multiple | getFindListMultiModeConfig | FindListMultiModeConfig |
| Page by id | getFindIdListConfig | FindIdListConfig |
| Page by index | getFindIndexListConfig | FindIndexListConfig |
@Override
protected FindListMultiModeConfig getFindListMultiModeConfig() {
FindListMultiModeConfig listConfig;
listConfig = new FindListMultiModeConfig();
listConfig.setRejectNoMatch(true);
listConfig.setRejectMessage("No records found");
return listConfig;
}
makeForm is required by all form processing Actions to create an initial form.
@Override
protected FindAccountsMultiForm makeForm() {
return new FindAccountsMultiForm();
}
Useful optional functions
All versions have a function named translateFinderException, which is called if the finder Command
throws an exception and returns the error message to display.
Save List Actions
Introduction
Save List Actions are client implementations of AbstractSaveListActionSupport that updates a database (or other business function) based on the selected record. Various generic types must be defined and abstract functions implemented.
Generic types
| Generic type | Description |
|---|---|
| M | Entry type of master list, or NA is Action displays master list. |
| K | Type of list's primary key. |
| T | Entry type of list. |
| F | Type of form used this Action, or NullForm no form used. |
For example:
@InterceptorRefs({ @InterceptorRef(value="formDrivenStack") })@Results({ @Result(name="error", type="redirectAction", params = {"namespace", "/account", "actionName", "ViewAccount"}), @Result(name="input", type="redirectAction", params = {"namespace", "/account", "actionName", "ViewAccount"}), @Result(name="success", type="redirectAction", params = {"namespace", "/account", "actionName", "ViewAccount"}) })public class SaveAccountProfileAction extends AbstractSaveListActionSupport<NA,GenericAccountId,GenericAccountVO,AccountProfileForm> { ... }
Required functions
doSave is the purpose of the Action and returns an instance of SaveResponse, which defines what to do next. It's easiest to call one of the static functions of SaveResponse.
| Static function | Parameters | Use |
|---|---|---|
| makeCreatedAndReloadItemSuccessResponse | Created item, info message | Includes DTO of just created entry, which is added to list and item is reloaded using its primary key. This is useful where list expects related data but the back-end create function just returns the base DTO. |
| makeCreatedItemSuccessResponse | Created item, info message | Includes DTO of just created entry, which is added to list. |
| makeDeselectResponse | Set list's selected index to -1, which is an easy way to indicate pages should be in 'add' mode. | |
| makeFailureAndReloadItemResponse | Error message | Sets error message and reloads item. This is useful where a concurrent update of the record by another process caused the failure and the updated record must be displayed to the user to show why. |
| makeFailureAndReloadListResponse | Error message | Sets error message then reloads entire list. This is useful where a concurrent update of the record by another process caused the failure and the updated list must be displayed to the user to show why. |
| makeFailureAndReloadMasterResponse | Error message | Sets error message as update failed, then reloads master record. This is useful where a concurrent update of the record by another process caused the failure and the updated master record must be displayed to the user to show why. |
| makeFailureResponse | Error message | Includes error message to display as update failed. This is the most common response to failure. |
| makeFailureResponseExistingMessages | An unusual use case of aborting an Action if at least one error message has already been set. | |
| makeInvisibleSuccessResponse | Info message | For unusual use cases, such as cache invalidation, where there's no apparent change in the record . |
| makeRemovedItemSuccessResponse | Info message | Sets info message and removes selected record from list. |
| makeSuccessAndReloadItemResponse | Info message | Sets info message and reloads item as there are back-end changes, such as audit fields, that need to be displayed. |
| makeSuccessAndReloadListResponse | Info message | Sets info message and reloads entire list as there are back-end changes that need to be displayed. |
| makeSuccessAndReloadMasterResponse | Info message | Sets info message and reloads item as there are back-end changes that need to be displayed. |
| makeUpdatedItemSuccessResponse | Updated item, info message | Replaces selected item with updated item and sets info message. |
| makeUpdatedItemSuccessSilentResponse | Updated item | Replaces selected item with updated item and without setting any message. |
For example.
@Override
protected SaveResponse<GenericAccountVO> doSave(NA selectedMaster, GenericAccountVO selectedItem) throws Exception {
try {
accountAdminSocnetModel.setProfile(accountId.getServiceId(), selectedItem);
return SaveResponse.makeSuccessAndReloadItemResponse("Customer profile for " + selectedItem.getNickname() + " edited.");
}
catch (RemoteServiceCommunicationFailureRuleException e) {
return SaveResponse.makeFailureResponse("Server not responding. Service may be shutdown.");
}
catch (EmailAlreadyVerifiedException e) {
return SaveResponse.makeFailureResponse("The email address is being used by another account.");
}
}
execute only calls the superclass function but must still be implemented to have annotation.
@Action("/account/SaveAccountProfile")@Overridepublic String execute() throws Exception { return super.execute(); }
getListCache should return the list cache to update.
@Override
protected ListCache<NA, GenericAccountId, GenericAccountDTO> getListCache() {
return AccountCompositeCache.getInstance(getAflBrowserTabSession()).getMasterListCache();
}
getMasterListCache should return the master list cache, or null if this page updates the master list cache.
@Override
protected ListCache<?, ?, NA> getMasterListCache() {
return null;
}
getSaveListConfig can usually be the following unless URL parameter names need to changed.
@Override
protected SaveListConfig getSaveListConfig() {
return new SaveListConfig();
}
makeForm is required by all form processing Actions to create an initial form.
@Override
protected SaveAccountProfileForm makeForm() {
return new SaveAccountProfileForm();
}