Opentaps Ajax Pagination Framework

From Opentaps Wiki
Jump to navigationJump to search

In this guide, we will show you how to replace a static form-widget list form with an Ajax paginated form using the opentaps Form Macros pagination framework.

The screen is the Financials > Configure >> Chart of Accounts screen and displays all the general ledger accounts configured for a company. Originally, the list of accounts was created with the ofbiz form widget, but because a company typically has several hundred accounts associated with it, such a static form was not very user-friendly. It always displayed 100 GL accounts per page, and paging through was slow.

Configuring the Screen Widget

The first step is to edit screen widget XML definition and remove the references to the form widget. Edit the file hot-deploy/financials/widget/financials/screens/ConfigurationScreens.xml and look for the screen "listGlAccounts". Since the ajax pagination is done within freemarker (FTL) templates, you can remove the following lines which referenced the old form widget :

<container style="screenlet-body">
    <include-form name="listGlAccounts" location="component://financials/widget/financials/forms/configuration/ConfigurationForms.xml"/>
</container>

You can also remove these lines:

<set field="viewIndex" from-field="parameters.VIEW_INDEX" type="Integer" default-value="0"/>
<set field="viewSize" from-field="parameters.VIEW_SIZE" type="Integer" default-value="100"/>

These are no longer needed because they were used to control the pagination of the list of GL accounts from the server side, but the opentaps Ajax pagination form macro allows the user to set pagination choices.


Configuring the Pagination Query

The second step is to configure the data for the pagination. If you are doing a query or building a List of Maps or GenericValues, you do not need to do anything special: you can just pass your List directly to the paginator.

You can also define a query for the paginator in a beanshell BSH script, which is typically used to retrieve data for display. This would cause the paginator to do the query for you, rather than holding the entire of List of values in memory. In most opentaps applications, the BSH script is used to do a lookup of data and then put the resulting list or list iterator (cursor) into the context for an FTL page or form widget XML to display, like this:

accounts = EntityUtil.filterByDate(delegator.findByAndCache("GlAccountOrganizationAndClass", 
               UtilMisc.toMap("organizationPartyId", session.getAttribute("organizationPartyId")), UtilMisc.toList("accountCode")));
context.put("accounts", accounts);

For Ajax pagination, however, the FTL page needs to interact with the data directly, so we need to pass the data lookup query information to the FTL. This done by following the closure pattern, where a function is passed to the pagination object in FTL. This function is created in the BSH to represent what data lookup should be performed:

glAccountListBuilder(organizationPartyId) {

   entityName = "GlAccountOrganizationAndClass";
   where = UtilMisc.toList(
       new EntityExpr("organizationPartyId", EntityOperator.EQUALS, organizationPartyId),
       EntityUtil.getFilterByDateExpr()
   );
   orderBy = UtilMisc.toList("accountCode");

   return this;
}

This function essentially defines what entity (GlAccountOrganizationAndClass) will be queried, what the conditions are in the where statement, and how the query results will be ordered. The fields entityName, where, orderBy, having, fieldsToSelect, and options' can be used to configure the query for pagination. Note that orderBy here specifies the initial order by sequence. In the pagination macro the user could define how he wants the fields ordered, and those will be saved the next time he visits the form. Then, you must make sure your function does this:

   return this;

The next step is just to pass this function to the FTL, like this:

context.put("glAccountListBuilder", glAccountListBuilder(session.getAttribute("organizationPartyId")));

Paginating in the FTL

The pagination of the GL accounts will be handled in an FTL file like glAccounts.ftl, which previously only displayed a header. The first thing we will need is to import the opentaps form macros:

  <@import location="component://opentaps-common/webapp/common/includes/lib/opentapsFormMacros.ftl"/>

Then, we simply call the <@paginate> form macro with the list we created in the BSH script:

   <@paginate name="glAccountOrganization" list=myList>

Alternatively, we can pass in the function we built in BSH:

  <@paginate name="glAccountOrganization" list=glAccountListBuilder>

We will need to turn off freemarker parsing inside the paginate macro:

 <#noparse>

Next, we bring in the pagination buttons, which let you scroll back and forth and to the beginning and end of the list of results with the <@paginationNavContext/>:

  <div class="subSectionHeader">
    <div class="subMenuBar">
      <@paginationNavContext />
    </div>
  </div>

That's most of the magic required. You would just build a table of your form in HTML with headers, like any other table, but use the <@headerCell> macro to define the heading cell as something that the user could order the list results by:

 <table class="listTable">
     <tr class="listTableHeader">
        <@headerCell title=uiLabelMap.FinancialsGLAccountCode orderBy="accountCode"/>
        <@headerCell title=uiLabelMap.FinancialsGLAccountName orderBy="accountName"/>
        <@headerCell title=uiLabelMap.FinancialsPostedBalance orderBy="postedBalance"/>
        <td> </td>
     </tr>

For columns which shouldn't be sorted, just use the HTML TD tag.

Next, you would use the FTL <#list> directive to display the individual rows. pageRows is returned from the pagination macro:

     <#list pageRows as row>  
       <tr class="${tableRowClass(row_index)}">
         <@displayCell text=row.accountCode/>
         <@displayCell text=row.accountName/>
         <td class="textright" style="padding-right: 40px"><@displayCurrency amount=row.postedBalance/></td>
         <td>
           <@displayLink href="reconcileAccounts?glAccountId=${row.glAccountId}" text=uiLabelMap.FinancialsReconcile/>
           <@displayLink href="updateGlAccountScreen?glAccountId=${row.glAccountId}" text=uiLabelMap.CommonEdit/>
           <@displayLink href="addSubAccountScreen?glAccountId=${row.glAccountId}" text=uiLabelMap.FinancialsAddSubAccount/>
           <@displayLink href="removeGlAccountFromOrganization?glAccountId=${row.glAccountId}&organizationPartyId=${row.organizationPartyId}" text=uiLabelMap.FinancialsDeactivate/>  
         </td>
       </tr>
     </#list>

tableRowClass is created for you by the pagination macro to define different CSS classes for different rows. You can use either FTL and HTML to display the results or use one of the other form macros, such as <@displayLink> or <@displayCell>.

Finally, you would wrap up like this:

  </table>
</#noparse>
</@paginate>

And that's it!

Debugging

There are a few things you should know about debugging the paginator:

  1. The ofbiz framework caches the freemarker files, so after changing your .ftl file, make sure you clear the cache in Webtools > Cache. Otherwise, the changes may not appear.
  2. The paginator's content is retrieved via AJAX after the main page has loaded. Thus, if you did a "View Page Source" on your browser, it would not show the content inside paginate. If you are using Firefox, you can highlight the paginated area, right click on your mouse, and click on "View Selection Source" to view the HTML code of your paginator.

Notes

If you use an EntityListBuilder, then add additional fields, you will not be able to sort by the fields which are not part of the database table.

The paginator can accept additional parameters into it. They can be passed in as part of the @paginate directive, like this:

<@paginate name="pendingInboundEmails" list=inboundEmails teamMembers=teamMembers>

Then, inside of the paginator, you can access them using the parameters Map, like this:

<#if parameters.teamMembers?has_content>
...
<#list parameters.teamMembers as option>