Developer's Daily Visual Cafe Education
  front page | java | perl | unix | DevDirectory
   
Front Page
Java
Education
Visual Cafe
Articles
   

Completing the FinancialTextField component
for commerce applets

Introduction

In Part 1 of our FinancialTextField Component article last month, we discussed the need for a FinancialTextField component for use in Internet commerce applets.  Our review of Internet applets showed that most applets made no effort to control the user input into financial data-entry fields.

For instance, in the world of electronic banking, where you can transfer money between various bank accounts, we found that you can type something like "One million" into the withdrawal field of an applet, where you should only be able to enter financial values - float values with two positions after the decimal, like "1250.00" or "9123.95".

Of course good applets always validate user input before trying to use the data, so the bad input never propagates further into the applet.  However, a downside of this approach is that you must prompt the user at some point with an "input error" message.

Another approach is to minimize data-entry errors by letting the user enter only valid financial values into the data-entry field to begin with.  Not only does this approach eliminate errors, it can also make your applet look more polished.  That's the approach we'll present in this article, as we complete our development of a FinancialTextField data-entry component.
 

A quick review

At the conclusion of last month's article, the end-user was limited to entering only numeric digits and decimal characters into the FinancialTextField component.  This is a good start, but we noted that it suffers from at least two primary limitations:

    1. Users can still enter any number of decimal characters into the field.
    2. Users can enter any number of characters to the right of the decimal character, when only two numeric digits should be allowed.
At the end of the last article we created the keyDown() method shown in Listing 1. As discussed at that time, the keyDown() method is invoked by the handleEvent() method any time a keyboard event occurs.
 
 
public boolean keyDown(Event evt, int key) { 

        if ( evt.id == Event.KEY_PRESS ) { 

            switch (key) { 
                case  8:    // backspace key 
                case  9:    // tab key 
                case 10:   // return key 
                case 46:   // decimal key 
                case 48:   // 0 
                case 49:   // 1 
                case 50:   // 2 
                case 51:   // 3 
                case 52:   // 4 
                case 53:   // 5 
                case 54:   // 6 
                case 55:   // 7 
                case 56:   // 8 
                case 57:   // 9 
                case 127:  // del key 
                    return super.keyDown(evt, key);  
                default: 
                    return true; 
            } 
        } 
}

 
Listing 1:  Last month's keyDown() method limits the characters a user can type into the FinancialTextField component. 
 
In this keyDown() method, the user is limited to entering only those valid keys identified in the switch/case statement.  Valid keys are the numeric digits 0-9, the [Backspace], [Del], [Enter], and [Tab] keys, and decimal-character (.).  If the user keystroke is one of these valid values, it's passed on to the keyDown() method of our superclass for further processing.

If the user keystroke is not one of these valid values, keyDown()returns a value of true to the handleEvent() method.  This tells handleEvent() that the user keystroke has already been handled, and no further event handling is required.  By doing this, we ignore keystrokes that we consider "bad".  This simple method was okay for our first-cut approach, but still leaves us with the two errors identified above.

In this article we'll improve our approach by creating a new method, named validateKeystroke(), that tests the user keystroke according to the rules we define, and lets the user enter only valid numeric values into the FinancialTextField component.
 

Modifying the keyDown() method

Before creating the validateKeystroke() method, let's look at how it should be called from the keyDown() method.  By doing this, we'll understand what behavior the validateKeystroke() method needs to provide.

Looking at the two problems identified earlier, we know that we'll need to validate the user input any time the user tries to enter a numeric or decimal character.  Because these characters are acceptable only under certain conditions, we'll call the validateKeystroke() method any time one of these characters is entered.

If the validateKeystroke() method returns a value of true - meaning the keystroke passes our tests - we'll hand the keystroke over to the super.keyDown() method for further processing.  However, if validateKeystroke() returns a value of false - meaning that it has violated one of our rules - we'll just ignore the keystroke attempt.

We know that we need to call validateKeystroke() when we receive numeric and decimal characters, but what about the cases when another valid keystroke occurs, such as the [Backspace], [Del], [Enter] or [Tab] keys?  Because these keystrokes don't result in the user creating an invalid financial value in a text field, you don't have to validate them.  Instead, you can just pass them along to the super.keyDown() method.

Understanding these possibilities, the modified keyDown() method is shown in Listing 2.  In this listing, validateKeystroke() is called any time a user keystroke is a numeric digit or a decimal character.  Otherwise, if the user enters a keystroke such as  the [Backspace], [Del], [Enter] or [Tab] keys, the validateKeystroke() method is skipped, and the super.keyDown() method is called.  Any other keystrokes the user attempts are ignored.
 
 
public boolean keyDown(Event evt, int key) { 

   if ( evt.id == Event.KEY_PRESS ) { 

      switch (key) { 

         //validate these keystrokes 
         case 46:   // decimal key 
         case 48:   // 0 
         case 49:   // 1 
         case 50:   // 2 
         case 51:   // 3 
         case 52:   // 4 
         case 53:   // 5 
         case 54:   // 6 
         case 55:   // 7 
         case 56:   // 8 
         case 57:   // 9 
            boolean keystrokeIsOkay = validateKeystroke(key);  
            if (keystrokeIsOkay) 
               return super.keyDown(evt, key); 
            else 
               return true;  

         // pass these keystrokes along 
         case  8:   // backspace key 
         case  9:   // tab key 
         case 10:   // return key 
         case 127:  // del key 
            return super.keyDown(evt, key); 

         // ignore other KEY_PRESS keystrokes 
         default: 
            return true; 
      } 
   }  

   //  all other keystrokes 
   return super.keyDown(evt, key);  
}

 
Listing 2:  The new keyDown() method calls validateKeystroke() to test the input of numeric and decimal characters. 
 

Now that we know what the new keyDown() method will look like, we'll create the validateKeystroke() method to fit into this structure.
 

Creating the validateKeystroke() method

The validateKeystroke() method will apply our keystroke tests to the numeric and decimal characters the user enters into the FinancialTextField.  If the keystroke is considered valid, we'll return a true value.  If the keystroke fails any of our tests, we'll return a false value instead, indicating that the attempted keystroke is not okay - it would result in an improperly formed financial value.

In the validateKeystroke() method, four tests are applied to the user keystroke to determine whether it is "acceptable".  These tests cover four data-entry conditions that can result in an invalid financial data-entry field.

The first two tests have been discussed already:

    1. Allow only one decimal character in the text field.

    2. Allow only two numeric digits to be placed to the right of a decimal.

The second two tests are less-obvious, and their need may not be understood until you begin testing the component: Test 1:  Allow only one decimal character in the input

The first test condition restricts the user to entering only one decimal character in the FinancialTextField component.  Because the event handler captures the keystroke before it enters the text field, this condition is easy to enforce.

When the validateKeystroke() method is called, we'll get the text string currently in the FinancialTextField with the getText() method.  If there is already a decimal character in the string, we'll return a value of false to the calling routine, indicating that the keystroke is not okay - a second decimal character is not allowed.

The code to accomplish this task in the validateKeystroke() method is shown below:

After assigning the currentText variable, we create a variable named decimalPosition.  This variable is created by using the String method indexOf() on the variable currentText.  If currentText contains a decimal character, indexOf() returns the position of the decimal character in currentText.  However, if currentText does not contain a decimal character, indexOf() returns a value of -1.

Because Test 1 applies only when the decimal key is entered by the user, the outer if/then test runs the inner test only when the decimal key is hit:

Then, knowing how indexOf() behaves, we test the value of decimalPosition.  If decimalPosition is less than zero, no decimal was found; otherwise, if decimalPosition is greater-than or equal-to zero, currentText already contains a decimal character, and a value of false is returned - this character is not permitted.
 

Test 2:  Allowing only two characters to the right of the decimal

The second test we'll apply in validateKeystroke() restricts the user to entering only two numeric digits to the right of a decimal.  The best way to create this code is to first look at how it would be written in pseudocode:
 

The next step is to turn this pseudocode into Java code:

The most difficult part of developing this test is determining the position in the text field where the user is trying to enter the numeric digit.  This cursor position is obtained with the getSelectionStart() method.

This method is implemented in the TextComponent class, the superclass of the TextField class.  It's normally used to determine the starting location of a highlighted section of text. I've also found it to be an indicator of what I call the cursor position - the position of the on-screen cursor as the user is typing.  (You'll also see this referred to as the caret position.)
 

Test 3:  Inserting a decimal into an existing string

The third test covers the case in which the user attempts to enter a decimal character into an existing string of numeric digits.  Let's say for instance that the user entered the string "12345", and then tried to enter a decimal between the 2 and the 3. Because this would result in an invalid financial value, it can't be allowed.  By our interpretation, a decimal can only be entered into one of the last three positions in the string, because it's only legal to have two numeric digits to the right of the decimal.

Writing this test as pseudocode results in something like the following:

Converting this to Java results in this test condition:

 

Test 4:  Insertion of a decimal when a range is highlighted

The fourth and final test in the validateKeystroke() method is applied when the user highlights a range of characters in the FinancialTextField component, and then attempts to enter a decimal character onto the selected text.

An example of this is shown in Figure 1, where the characters "34.56" are highlighted.  If the user hits the decimal key at this time, the keystroke should be allowed.  However, in another example, if the characters "34" are highlighted and the decimal key is hit, the keystroke should not be allowed, because this would result in two decimals in the field.
 
 
Figure 1:   An example of a 
highlighted 
section of text in a 
FinancialTextField. 
 
An example of a highlighted section of text in a FinancialTextField.
 

A pseudocode description of Test 4 looks like this:

How do you determine if the user has highlighted a section of text in a TextField component?  The code snippet below shows how to determine whether a user has highlighted a range of text:

First, you determine where the selection starts (selStart) and where it ends (selEnd).  If the user has highlighted any characters in the text field, selStart and selEnd will contain different values, so rangeSelected is set to true.  Otherwise, if a user has not highlighted a region of text, selStart and selEnd will contain the same value.

Placing that snippet of code at the top of our method, here's the Java code that implements Test 4:

 

Fixing the previous test conditions

In the three previous tests, we didn't check to see if the user highlighted a range of characters as part of our test conditions.  Is this an error?  You bet it is.

Looking at the first three tests, you'll see that each condition assumes that a range of characters is not selected.  In fact, our discussion did not consider the possibility of a range of characters being highlighted until we discussed Test 4.

Fortunately, fixing the first three tests is a simple matter.  Because each of these tests are designed to be used only when a range of characters is not selected, we'll fix the tests by adding this simple test condition around each test:

 

The final code

Combining the four tests together results in validateKeystroke() method shown in Listing 3.

 
private boolean validateKeystroke(int key) { 

   int     selStart = getSelectionStart(); 
   int     selEnd   = getSelectionEnd(); 
   boolean bs       = key == BACKSPACE; 
   boolean del      = key == DEL; 

   int cursorPosition = selStart; 
   boolean rangeSelected = selStart != selEnd; 

   String s  = getText(); 
   int    strLen = s.length(); 

   //  Test 1: 
   int decimalPosition = s.indexOf('.');  
   if (!rangeSelected) { 
      if (key == DECIMAL) { 
         if (decimalPosition >= 0) 
            return false; 
      } 
   } 
  
   // Test 2: 
   if (!rangeSelected) { 
      if (key > 47  &&  key < 58) { 
         if (decimalPosition >= 0) { 
            if (cursorPosition > decimalPosition) {     
               if ((strLen-decimalPosition) > 2) 
                  return false; 
            } 
         } 
      }  
   } 
  
   //  Test 3: 
   if (!rangeSelected) { 
      if (key == DECIMAL) { 
         if (decimalPosition < 0) { 
            if ((strLen-cursorPosition) > 2) 
               return false; 
         } 
      } 
   } 
  
   //  Test 4: 
   if (rangeSelected) { 
      if (key == DECIMAL) { 
         if (decimalPosition >= 0) { 
            if (decimalPosition < selStart ||  
                decimalPosition >= selEnd) 
               return false; 
         } 
      } 
   } 

   return true; 
}

 
Listing 3:  The validateKeystroke() method applies four tests to the current user keystroke. 
 

Conclusion

The FinancialTextField class can be used to create data-entry prompts in commerce applets.  This class offers the advantage that only numeric and decimal characters can be entered into your financial data-entry fields, in the proper format, thereby reducing the opportunity for input error.

In developing the FinancialTextField class we examined several low-level user interface concepts.  First, we examined the process of creating a keyDown() method that is invoked by the event handler.  Next, we showed how to control which keystrokes the end user is allowed to type by modifying keyDown().  After that, we showed how to validate those keystrokes according to our customized rules before they're entered into a TextField component.  In our keystroke-validation algorithm we demonstrated the process of determining the position of the cursor in a TextField component, and showed how to determine when a range of characters has been selected in a text field.

Hopefully you can use the FinancialTextField component in your commerce applets, and also use these techniques in developing other customized components.

[Note - This article first appeared in ZD Journals Visual Cafe Developer's Journal. The article is reprinted here with their permission. The author now works for Developer's Daily.]



Do the BLOG

Copyright © 1998 DevDaily Interactive, Inc.
All Rights Reserved.