Xoetrope
View

Advanced Tutorial - Advanced Navigation using Library Functions - Create a navigation library
SUMMARY  Library functions are a very useful way of interacting with Java classes which are not derived from the XPage class. In this case a Singleton object is created in order to carry out some navigation but similar calls can be made for any type of functionality

FURTHER READING  There is an article on this subject published on XUI Zone.

Up to now we have only been able to process a Sole or a Joint application. In many cases mortgage applications can have up to four applicants and we are going to introduce that capability. Also each page needed it's own next and prev attributes in order to specify the order of navigation. This can be a little cumbersome to maintain so we are going to look at making that easier to manage. We are going to achieve this through the use of library functions.

We will start by modifying the navpanel.xml page declaration.

Listing 1 - navpanel.xml
<XPage style="Heading">
    <Components>
        <Image name="nextButton" x="564" y="51" w="19" h="18" content="right.gif"/>
        <Image name="prevButton" x="60" y="54" w="19" h="18" content="left.gif"/>
    </Components>
    <Events>
        <Event method="net.xoetrope.mortgage.navigate.Navigator.next" 
            target="nextButton" type="MouseHandler"/>
        <Event method="net.xoetrope.mortgage.navigate.Navigator.previous" 
            target="prevButton" type="MouseHandler"/>
    </Events>
</XPage>

The class attribute has been removed and there is no longer any need for the NavPanel.java class so it can be removed from the project. Both of the method attributes for the Event nodes have been changed to use the new Navigator class. The next and previous functions are static functions within the Navigator class. So the basic Navigator class is shown in listing 2.

Listing 2 - Navigator.java
public class Navigator {
  
  private static Navigator navigator;

  private Navigator() {
  }
  
  public static Navigator getInstance()
  {
    if ( navigator == null )
      navigator = new Navigator();
    
    return navigator;
  }
  
  public void moveNext()
  {
  }
  
  public void movePrevious()
  {
  }
  
  public static void next()
  {
    getInstance().moveNext();
  }
  
  public static void previous()
  {
    getInstance().movePrevious();
  }
  
}

The class uses the Singleton design pattern and the next and previous static functions call the moveNext and movePrevious functions of the Singleton instance respectively. This class is saved in a folder called navigate under the src directory.

We are now going to introduce a new datasource file called navigation.xml which will define the pages and the order in which they will be displayed.

Listing 3 - navigation.xml
<Datasets>
  <dataset id="navigation">
    <page id="welcome" value="welcome" /> 
    <page id="personal" value="personal" title="PER_HEADING" startrepeat="true" 
		endrepeat="true"/> 
    <page id="finance" value="finance" title="FINANCE_TITLE"/> 
    <page id="finish" value="finish" /> 
  </dataset>
</Datasets>

As you can see the id attributes specify the names of the pages we have been using to date in the application. The page titles will now be specified using the title attribute. The startrepeat and endrepeat attributes specify where the pages start and end repeating for multiple customers. The datasource.xml file now needs to reference the new file

Listing 4 - datasource.xml
<DataSources>
  <DataSource name="ListValues" filename="statics.xml" /> 
  <DataSource name="Navigation" filename="navigation.xml" />
</DataSources>

We are now going to modify the welcome page by adding a dropdown list for the number of applicants to be processed.

Listing 5 - welcome.xml
...
<Panel name="createPnl" x="0" y="150" w="650" h="50">
  <Panel x="0" y="0" w="650" h="50" style="banner/prompt">
	<Label x="180" y="15" w="140" h="20" style="prompt" 
		content="Number of applicants:" alignment="Left" opaque="false"/>
	<ComboBox name="numAppsList" x="340" y="15" w="100" h="20"/>
  </Panel>
</Panel>
...
<Data>
	...
<Bind target="numAppsList" source="/defaults/numApps" output="/mortapp/numApps"/>
...
</Data>
...

The numApps list can now be defined in the statics.xml file.

Listing 6 - statics.xml
	...
    </list>
    <list id="numApps">
      <item value="1" id="1" />
      <item value="2" id="2" />
      <item value="3" id="3" />
      <item value="4" id="4" />
    </list>
    <list id="languages">
      <item value="English" id="en" /> 
      <item value="Français" id="fr" /> 
    </list>
	...

Now it will be much easier to adjust the number of applicants which can be processed for an application.

A slight change needs to be made to the pageDeactivated function in the Welcome class as in listing 7.

Listing 7 - Welcome.java
    ...
  public void pageDeactivated()
  {
    currentCust.set( "1" );
    String create = ( String )createMdl.get();
    if ( createRadio.getText().compareTo( create ) == 0 ) {
      String numApplicants = ( String )( ( XModel )rootModel.get( "mortapp/numApps" ) ).get();
      ( ( XBaseModel ) rootModel.get( "mortapp" ) ).clear();
      ( ( XModel )rootModel.get( "mortapp/numApps" ) ).set( numApplicants );
      //updateBindings();
      //saveBoundComponentValues();
    } else {
      openFile();
    }
  }
    ...

The numApps node needs to be restored once the mortapp is initialised as the saveBoundComponentValues will be called from the Navigator class.

Now we can complete the Navigator class.

Listing 8 - Navigator.java
public class Navigator {
 
  private static Navigator navigator; 
  private XPage navPage;
  private XBaseModel navMdl, rootModel;
  private int currentMdl = 0;
  private XPageManager pageMgr;
  private int customer = 0;
  private int numCustomers = 0;
  private int startRepeatIndex = -1, endRepeatIndex = -1;
  ResourceBundle rb;

 private Navigator() {
    XProject project = XProjectManager.getCurrentProject();
    rb = project.getResourceBundle( project.getStartupParam( "Language" ) );
    pageMgr = project.getPageManager();
    navPage = ( XPage )( ( XTarget ) 
		pageMgr.getTarget( "navPanel" ) ).getComponent( 0 );
    rootModel = ( XBaseModel ) project.getModel();
    navMdl = ( XBaseModel ) rootModel.get( "navigation" );	
    int numChildren = navMdl.getNumChildren();    
    for (int i = 0; i < numChildren; i++){
         XBaseModel nextMdl = ( XBaseModel )navMdl.get( i);
         setRepeats( nextMdl, i );
    }
  }
  
  public static Navigator getInstance()
  {
    if ( navigator == null )
      navigator = new Navigator();
    
    return navigator;
  }
  
  public void moveNext()
  {
    if ( navPage.wasMouseClicked() ) {
      XPage currentPage = ( XPage )pageMgr.getCurrentPage( "content" );
      if ( currentPage.checkValidations() == XValidator.LEVEL_IGNORE ) {
        currentPage.saveBoundComponentValues();
        setNumCustomers();
  
        if ( endRepeatIndex == currentMdl ) {
          if ( customer == numCustomers ) {
            currentMdl++;
            customer++;
          } else {
            currentMdl = startRepeatIndex;
          }
        } else {
          currentMdl++;
        }
        
        XBaseModel nextMdl = ( XBaseModel )navMdl.get( currentMdl );
        if ( startRepeatIndex == currentMdl )
          customer++;
        setPage( nextMdl.getId() );
      }
    }
  }
  
   public void movePrevious()
  {
    if ( navPage.wasMouseClicked() && currentMdl !=0) {
        XPage currentPage = ( XPage )pageMgr.getCurrentPage( "content" );
        currentPage.saveBoundComponentValues();

        if ( currentMdl == startRepeatIndex ) { 
            customer--;    
            if ( customer != 0 )
                currentMdl = endRepeatIndex;             
            else           
                currentMdl--;
        } else {
            if(currentMdl == (endRepeatIndex+1))
                customer--;
            currentMdl--;
        }

        XBaseModel nextMdl = ( XBaseModel )navMdl.get( currentMdl );
        setPage( nextMdl.getId() );
    }
  }
    
  private void setPage( String pageName )
  {
    XPage newPage = ( XPage )pageMgr.showPage( pageName, "content" );
    newPage.setExceptionHandler( new ExceptionHandler( newPage ) );
    setCurrentTitle( newPage );
  }
  
  private void setRepeats( XBaseModel mdl, int i )
  {
    boolean startRepeat = XModelHelper.getBooleanValue( mdl, "startrepeat" );
    boolean endRepeat = XModelHelper.getBooleanValue( mdl, "endrepeat" );
    if ( startRepeat )
      startRepeatIndex = i;
    if ( endRepeat )
      endRepeatIndex = i ;
  }
  
  private void setNumCustomers()
  {
    XBaseModel numApplicants = 
		( XBaseModel )rootModel.get( "mortapp/numApps" );
    numCustomers = XModelHelper.getIntValue( numApplicants );
  }
  
  public void setCurrentTitle( XPage page )
  {
    if ( ( customer > 0 ) && ( customer <= numCustomers ) ) {
      XLabel lbl = ( XLabel ) page.findComponent( "Title" );
      XBaseModel currMdl = ( XBaseModel )navMdl.get( currentMdl );
      lbl.setText( rb.getString( XModelHelper.getAttrib( currMdl, "title" ) ) 
		+ " (Applicant " + customer + ")" );
    }
  }
  
  public static void next()
  {
    getInstance().moveNext();
  }
  
  public static void previous()
  {
    getInstance().movePrevious();
  }  

  public String getCurrentCust()
  {
    return String.valueOf( customer );
  }
 
}

The constructor now gets references to the current language ResourceBundle, the XPageManager, the XPage in the navPanel portion of the frameset and the navigation XModel. The moveNext and movePrevious functions keep track of the startRepeatIndex and the endRepeatIndex as defined in the navigation.xml file. They also take care of processing the amount of applicants selected on the welcome page.

The setCurrentTitle function retrieves the XLabel called Title from the current page and sets its text to the translated resource and appends (Applicant x) to the end.

So in order to get the application back to the way it was behaving before we stared this step, we will modify the personal page. Start by editing the personal.xml file.

Listing 9 - personal.xml
<XPage class="net.xoetrope.mortgage.CustomerPage" resource="">
  <Components>
    <Label name="Title" x="30" y="0" w="420" h="30" 
		style="Title" alignment="Left" opaque="true"/>
    <Label x="123" y="51" w="100" h="20" style="prompt" 
		content="PER_TITLE" alignment="Left" opaque="true"/>
    ...

The next attribute has been removed and the class which is used has been changed to a generic customerPage class. The content attribute for the title label has also been removed. Now the new customerPage class needs to be created as in listing 10.

Listing 10 - CustomerPage.java
package net.xoetrope.mortgage;

import net.xoetrope.mortgage.navigate.Navigator;
import net.xoetrope.xui.XPage;

public class CustomerPage extends XPage
{
  public String getCustomerID()
  {
    return "customer" + Navigator.getInstance().getCurrentCust();
  }

}

So just to prove that it works, we will add a new page to the application which will capture bank details for each of the customers.

Listing 11 - bank.xml
<XPage class="net.xoetrope.mortgage.CustomerPage" resource="">
  <Components>
    <Label name="Title" x="30" y="0" w="420" h="30" style="Title" alignment="Left" 
		opaque="true"/>
    <Label x="123" y="51" w="100" h="20" style="prompt" content="Bank Name:" 
		alignment="Left" opaque="true"/>
    <Edit name="bankNameText" x="238" y="51" w="178" h="20" alignment="Leading"/>
    <Label x="123" y="71" w="100" h="20" style="prompt" content="Account No.:" 
		alignment="Left" opaque="true"/>
    <Edit name="bankAccountNo" x="238" y="71" w="178" h="20" alignment="Leading"/>
    <Label x="123" y="91" w="100" h="20" style="prompt" content="Sort code:" 
		alignment="Left" opaque="true"/>
    <Edit name="bankSortCode" x="238" y="91" w="178" h="20" alignment="Leading"/>
  </Components>
  <Data>
    <Bind target="bankNameText" source="/mortapp/${getCustomerID()}/bankdetails/bankname" /> 
    <Bind target="bankAccountNo" source="/mortapp/${getCustomerID()}/bankdetails/accountno" /> 
    <Bind target="bankSortCode" source="/mortapp/${getCustomerID()}/bankdetails/nsc" /> 
  </Data>
</XPage>

This page can use the existing CustomerPage class and a reference to it simply needs to be put into the navigation file.

Now the navigation.xml file needs to be amended to include a reference to it.

Listing 12 - navigation.xml
  <dataset id="navigation">
    <page id="welcome" value="welcome" /> 
    <page id="personal" value="personal" startrepeat="true" title="PER_HEADING"/> 
    <page id="bank" value="bank" endrepeat="true" title="BANK_HEADING"/>
    <page id="finance" value="finance" title="FINANCE_TITLE"/> 
    <page id="finish" value="finish" /> 
  </dataset>

The endRepeat attribute has been moved to the declaration of the new bank page so that the personal and bank pages will be repeated for each applicant.

Finally update en.properties and fr.properties with the values for PER_HEADING and BANK_HEADING.

Listing 13 - en.properties
PER_HEADING=Personal Details
BANK_HEADING=Bank Details 

Listing 14 - fr.properties
PER_HEADING=Détails Personnels
BANK_HEADING=Petits groupes De Banque

Now when an application file is saved it will look something like...

Listing 15 - outputted model
<Datasets><data id="mortapp">
  <data value="3" id="numApps"/>
    <data id="customer1">
      <data value="Joe" id="firstname"/>
      <data value="Bloggs" id="surname"/>
      <data value="10/10/1970" id="dob"/>
      <data value="Mr" id="title"/>
      <data id="bankdetails">
        <data value="Great bank of Ireland" id="bankname"/>
        <data value="12345678" id="accountno"/>
        <data value="941104" id="nsc"/>
      </data>
    </data>
    <data id="customer2">
      <data value="Jemima" id="firstname"/>
      <data value="Bloggs" id="surname"/>
      <data value="11/11/1971" id="dob"/>
      <data value="Mrs" id="title"/>
      <data id="bankdetails">
        <data value="United Irish Bank" id="bankname"/>
        <data value="55554444" id="accountno"/>
        <data value="956606" id="nsc"/>
      </data>
    </data>
    <data id="customer3">
      <data value="James" id="firstname"/>
      <data value="Bloggs" id="surname"/>
      <data value="27/05/1967" id="dob"/>
      <data value="Dr" id="title"/>
      <data id="bankdetails">
        <data value="European Bank" id="bankname"/>
        <data value="65465454" id="accountno"/>
        <data value="982205" id="nsc"/>
      </data>
    </data>
    <data id="finance">
      <data value="250000" id="propvalue"/>
      <data value="200000" id="mortamt"/>
    </data>
  </data>
</Datasets>

The data files can be saved and opened in exactly the same way as previously and you can add in as many screens as necessary in order to capture application data.

Log in or register to download the source code for this step.