Xoetrope
View

Advanced Tutorial - Extending XUI Dialogs - Modify the ExceptionHandler
SUMMARY  Basic dialogs are built into the core XUI libraries and can be extended to provide a more polished or branded application or to add more functionality to the basic dialogs. This step will explore how to extend the dialogs to create a more interactive end user application in the way that the validations are handled.

FURTHER READING  This step follows on from the advanced validations step at the beginning of this tutorial. This step only touches on some of the ways in which the dialogs can be used. The API documentation as well as the Carousel user guide should be read for more information.

The ExceptionHandler created in the first step of this tutorial will now be modified to record the exact types of errors which are being generated by the application as shown in listing 1.

Listing 1 - ExceptionHandler.java
public class ExceptionHandler implements XExceptionHandler
{
  boolean pageValidation = false;
  //String validationText = "";
  private XPage currentPage;
  String title = "";
  Vector errors, warnings;
  private XProject project;

  public ExceptionHandler( XPage page )
  {
    project = XProjectManager.getCurrentProject();
    ResourceBundle rb = project.getResourceBundle( 
	project.getStartupParam( "Language" ) );
    title = rb.getString( "ERROR_TITLE" );
    currentPage = page;
  }

  public boolean handleException( Object comp, Exception ex, Object xvalidator )
  {
    XValidator validator = ( XValidator ) xvalidator;
    if ( (validator.getLevel() == validator.LEVEL_ERROR) && (! pageValidation) ){
      currentPage.showMessage( "Input error", ex.getMessage() );
      return true;
    }
    String msg = validator.getMessage();

    //if ( validationText.length()>0 )
    //  validationText += "\n";
    //validationText += currentPage.translate( msg );
    if ( validator.getLevel() == validator.LEVEL_ERROR )
      errors.add( currentPage.translate( msg ) );
    else
      warnings.add( currentPage.translate( msg ) );

    return true;
  }

  public int accumulateMessages( boolean start, int level )
  {
    pageValidation = start;
    if ( pageValidation ){
      //validationText = "";
      errors = new Vector();
      warnings = new Vector();
    } else {
      //if ( validationText.length()>0 ) {
      //  currentPage.showMessage( title, validationText );
      //}
      if ( errors.size() > 0 || warnings.size() > 0 )
        return getDialogResult( level );
    }
    return level;
  }

  private int getDialogResult( int level )
  {
    ErrorMessage dlg = ( ErrorMessage )project.getPageManager()
	.loadPage( "ErrorDlg", false );
    dlg.setErrorMessages( errors, warnings );
    int result = dlg.showDialog( currentPage );
    if ( level == XValidator.LEVEL_WARNING )
      return result == XValidator.LEVEL_IGNORE ? XValidator.LEVEL_IGNORE : level;
    else
      return XValidator.LEVEL_ERROR;
  }
  
}

The messages are no longer just added to the validation text String but are instead added to two vectors, errors and warnings in the handleException function. The accumulateMessages function creates the two vectors if the validation process is beginning. In the same method, if the validation process is ending and there are either errors or warnings, the getDialogResult function is called.

The getDialogResult function loads a new XDialog class ErrorMessage which we will look at next. The two vectors are passed to the new dialog and the dialog is shown using the showDialog method. The dialog is modal meaning that code execution will halt at this point until the dialog is dismissed. Once dismissed, the dialog returns the result of the dialog. If the level parameter is LEVEL_ERROR then that will be returned meaning that the page cannot proceed. If the level parameter is LEVEL_WARNING then we want the dialog to give the user the option of staying on the current page or to proceed ignoring the warnings. If the dialog returns the value LEVEL_IGNORE, that is what is returned from the function meaning that the page can proceed otherwise the level parameter is returned.

Now we can take a look at the new XDialog page as referred to in the getDialogResult in listing 1. Start by creating the ErrorDlg page as shown in listing 2.

Listing 2 - ErrorDlg.xml
<XPage class="net.xoetrope.mortgage.ErrorMessage" style="Title" 
	title="Errors & Warnings">
    <Components>
        <Panel name="errorPanel" x="0" y="0" w="400" h="100" style="Title">
            <Label name="continueLabel" x="0" y="0" w="400" h="20" style="prompt" 
			content="Do you wish to continue?" alignment="center" 
			visible="false"/>
        </Panel>
        <Panel name="buttonPanel" x="0" y="100" w="400" h="30" style="Title">
            <Button name="OKButton" x="80" y="0" w="100" h="25" content="OK"/>
            <Button name="cancelButton" x="220" y="0" w="100" h="25" 
			content="Cancel"/>
        </Panel>
    </Components>
    <Events>
        <Event method="okClicked" target="OKButton" type="ActionHandler"/>
        <Event method="cancelClicked" target="cancelButton" type="ActionHandler"/>
    </Events>
</XPage>

As can be see from the XML declaration the dialog defines its own OK and cancel buttons and has attached events to them. The class attribute specifies the use of the ErrorMessage class as shown in listing 3.

Listing 3 - ErrorMessage.java
package net.xoetrope.mortgage;

import java.awt.Container;
import java.util.*;
import java.awt.*;

import net.xoetrope.swing.*;
import net.xoetrope.xui.XTextRenderer;
import net.xoetrope.xui.style.XStyleFactory;
import net.xoetrope.xui.validation.XBaseValidator;

public class ErrorMessage extends XDialog 
{
  XLabel msgLabel;
  XButton okBtn, cancelBtn;
  Vector errors, warnings;
  XPanel errorPanel, buttonPanel;
  XLabel continueLabel;
  int ret = XBaseValidator.LEVEL_WARNING;
  int currentY = 10;
  XStyleFactory compFact;
  
  public void pageCreated()
  {
    super.pageCreated();
    errorPanel = ( XPanel )findComponent( "errorPanel" );
    buttonPanel = ( XPanel )findComponent( "buttonPanel" );
    okBtn = ( XButton )findComponent( "OKButton" );
    cancelBtn = ( XButton )findComponent( "cancelButton" );
    continueLabel = ( XLabel )findComponent( "continueLabel" );
    compFact = ( XStyleFactory )getComponentFactory();
    compFact.setParentComponent( errorPanel );
    setSaveOnClose( false );
  }

  public void setErrorMessages( Vector err, Vector warn )
  {
    errors = err;
    warnings = warn;
    addErrors();

    if ( ( errors != null ) && errors.size() == 0 ) {
      errorPanel.setSize( 400, currentY + 40 );
      continueLabel.setVisible( true );
      continueLabel.setLocation( continueLabel.getX(), currentY + 20 );
      buttonPanel.setLocation( buttonPanel.getX(), errorPanel.getHeight() + 40 );
      okBtn.setLocation( okBtn.getX(), okBtn.getY() );
      cancelBtn.setLocation( cancelBtn.getX(), cancelBtn.getY() );
      setSize( 400, currentY + 100 );
    } else {
      errorPanel.setSize( 400, currentY + 50 );
      buttonPanel.setLocation( 0, errorPanel.getHeight() );
      okBtn.setLocation( 150, okBtn.getY() );
      okBtn.setText( "Close" );
      cancelBtn.setVisible( false );
      setSize( 400, currentY + 60 + buttonPanel.getHeight() );
    }
    pack();
  }

  public int showDialog( Container owner )
  {
    super.showDialog( owner );
    return ret;
  }

  public void cancelClicked()
  {
    ret = XBaseValidator.LEVEL_WARNING;
    super.closeDlg();
  }
  
  public void okClicked()
  {
    ret = XBaseValidator.LEVEL_IGNORE;
    super.closeDlg();
  }

  private void addErrors()
  {
    addMessages( errors, "error.gif", "Error" );
    addMessages( warnings, "warning.gif", "Error/warning" );
  }
  
  private void addMessages( Vector messages, String imageName, String style )
  {
    if ( messages != null ) {
      int height = 0;
      int labelWidth = 400;
      Font font = new Font("Arial", Font.PLAIN, 11);
      FontMetrics fm = errorPanel.getFontMetrics(font);
      int numErrors = messages.size();
      for ( int i = 0; i < numErrors; i++ ) {
        String err = ( String )messages.elementAt( i );
        int width = fm.stringWidth( "      " + err );
        height = ( ( int )( fm.stringWidth( err ) * 1.11 / 
		( labelWidth ) ) + 1 ) * 20;
        compFact.addComponent( IMAGE, 10, currentY + 3, 16, 16, imageName );
        compFact.addComponent( LABEL, 10, currentY, labelWidth, height, 
		"      " + err, style );
        currentY = currentY + height;
      }
    }
  }
}

The pageCreated function obtains references to all of the dialog components which will need to be manipulated. A reference is also created to the page's XStyleFactory which will be used to construct components dynamically.

The setErrorMessges function takes the two vectors as parameters and begins to manipulate the page components as needed. If there are errors then the dialog is displayed with a single button which closes the dialog. If there are warnings without errors then two buttons, OK and Cancel are shown along with a message asking the user "Do you wish to continue?". The okClicked and cancelClicked functions then set the relevant return value and close the dialog returning control back to the calling class which in this case is the ExceptionHandler.

The okClicked function specifies LEVEL_IGNORE as the return value meaning that it is ok to proceed with the current page.

The addMessages function shows how the XStyleFacotry is used to construct the message images and labels. It is necessary only to keep track of the currentY position for addition of the next components and the factory takes care of setting the other component properies including their content and styles.

Now a new FunctionValidation can be added to the Finance class which will generate a warning if the mortgage amount is more than the property value. So begin by adding a new validation to the validations file as shown in listing 4.

Listing 4 - Validations.xml
    ...
  <validation name="mortratio" type="function"
	msg="Mortgage amount is greater than property value!"/>
    ...

Now create a new validation in the finance.xml file as shown in listing 5.

Listing 5 - finance.xml
    ...
    <Validations>
      <Validation rule="propvalue" target="propValueText"/>
      <Validation rule="mortamt" target="mortAmtText"/>
      <Validation rule="mortratio" target="mortAmtText" method="checkRatio"/>
    </Validations>
    ...

The checkRatio function needs to be written in the Finance class as shown in listing 6.

Listing 6 - Finance.java
    ...
  public Integer checkRatio()
  {
    String propValue = propValueText.getText();
    String mortAmt = mortAmtText.getText();
    if ( ( mortAmt.length() > 0 ) && ( propValue.length() > 0 ) ) {
      if ( Double.parseDouble( mortAmt ) > Double.parseDouble( propValue ) )
        return new Integer( XBaseValidator.LEVEL_WARNING );
    }
    return new Integer( XBaseValidator.LEVEL_IGNORE );
  }
    ...

This function compares the mortgage amount and the property value and returns the relevant constant value. To finish this step change the styles file in order to include the error styles are referred to in the ErrorMessage class.

Listing 7 - styles.xml
    ...
    <style name="Error">
        <color_back value="ffffff"/>
        <color_fore value="550000"/>
        <font_face value="arial"/>
        <font_size value="12"/>
        <font_weight value="1"/>
        <style name="warning">
            <color_fore value="005500"/>
        </style>
    </style>
    ...

Run the application and it will appear as in the screenshot.

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