|
|
|||
|
|
![]()
|
|
|
|
Chapter 7Creating Smarter Formsby Mark Wutka
Smarter FormsIn the beginning, Web pages were not very lively. You could read information, click certain words and pictures, and view other unlively pages. Then, the forms interface came along and added some degree of interaction with a page. You were able to enter data and then click a button and send your information to a server, which would analyze what you sent and return the results. Unfortunately, these forms were also lacking a certain "lively" quality. All the error checking was left up to the server, as was any other form of interaction such as context-sensitive help. Java enables you to spice up your old Web forms. You can perform error checking before you ever send data to the server, drastically improving response time to the user and cutting down on server usage. You can also add context-sensitive help. You can even create dynamic forms that change depending on the other information added. Creating Forms with the AWTJava's AWT toolkit contains a set of GUI components that are very useful for constructing forms suitable for a Web page. All the form components provided in HTML have equivalent AWT components, so you can simulate any existing HTML form.
Listing 7.1 shows just such a form, a query entry form for the
Lycos Web searching engine (http://www.lycos.com). It enables
you to enter the keywords to search for, and some other search
parameters, and then it sends the query to the Lycos server just
as if you had used, the Lycos page.
Listing 7.1 Source Code for LycosForm.java
The AWT layout managers provide a reasonable way to place components
on the screen without putting them in fixed positions. This allows
your applet to adapt to different screen sizes. Unfortunately,
it is often difficult to arrange the components the way you want
them. The GridBagLayout class
provides the most flexible way to arrange components, but it is
often rather cumbersome to use. As an alternative to the GridBagLayout
class, or even in conjunction with it, you can use different panels
to group your components, nesting some panels within others. The
LycosQuery class uses this
technique. It creates a main panel that uses a grid layout with
three rows. The first row is another panel that uses a flow layout,
while the last two rows use two-column grid layouts.
Listing 7.1 Source Code for LycosForm.java (continued)
The URLQuery class used in this next part of the LycosQuery class was introduced in the section, "Performing a Query with GET," in Chapter 6, "Communicating with a Web Server." It allows you to create an HTTP query from an URL and a properties table containing the query parameters. It would be nice if you could examine the data coming back from the query and still let the browser display the actual HTML codes returned, but on most browsers you can't. You can either examine the data coming back and display it yourself from the Java program, or use showDocument to display the data directly. Listing 7.1 Source Code for LycosForm.java (continued)
Figure 7.1 shows the original version of the Lycos query form. Figure 7.1 : The Lycos search engine is a popular Web search tool. Figure 7.2 shows a mimic of an HTML form. Figure 7.2 : You can mimic any HTML form in Java. You may be wondering why you should go through the trouble of creating a Java applet that presents a form when it is easier to define one in HTML. If you are simply presenting a form, with no help facility and no error checking, go ahead and do it in HTML. The real advantage of Java comes when you need to do things beyond the basic form facilities in HTML. Checking for Errors on the Client SideYou can increase the response time of your forms and lessen the load on your server if you put error checking into your Java forms. When you do forms in HTML, there is no way to check to make sure the data is correct before you send it off to the server. You put all the responsibility for error checking on the server's shoulders. This also means that the user has to wait for the request to go out to the server, and a response to come back, before he knows that there was something wrong. If you use Java to do error checking, you will make the user happier because he will know instantly that he has entered incorrect data. You will also decrease the load on your server because it is no longer handling any incorrect data (hopefully). As your forms become more and more complex, the need for error checking grows dramatically. In the example of the Lycos query form, the only place in which you can make an error entering data is in the query string itself. If you fail to enter any keywords, the search engine has nothing to look for. While Lycos normally just presents you with another form, this Java version of the query form pops up a dialog box reminding you that you need to enter keywords in the query entry area. The only place you need to change the old LycosForm class is in the action method. You make it call a checkRequest method instead of sendRequest. The checkRequest method verifies the form, and if everything is correct, it calls sendRequest. Here are the updated action and sendRequest methods:
This is actually a pretty minor form of error checking. On more advanced forms, you may need to check to see that information entered in one section is consistent with information entered in another area. For example, you might have a "sex" field on your form and a "maiden name" field somewhere else. If sex was "male," the maiden name doesn't apply. Your error checking routine would check to make sure that if you entered something under "maiden name," you had better be female. You can avoid some situations such as this one by creating dynamic forms, which are discussed later in this chapter. Adding Context-Sensitive HelpContext-sensitive help is an incredibly useful feature, especially on the day when a software product comes with a 500-page manual. While you should strive towards making your program completely intuitive, requiring no special training or documentation, that isn't always possible. Context-sensitive help can lessen the need for other documentation and is much more timely and relevant than an online user's manual. In case you are unclear about what "context-sensitive help" is, it is simply help on what you are currently doing. For example, if you are entering text in a field and you suddenly press the help key, you should expect to get help for the text field you are entering. Many software products require that you pop up the online manual and skip to the page discussing the field you want information on. If you know what the user is currently doing, you know what to tell them when they ask for help. The way you present context-sensitive help is up to you. One very useful method, which is also quite passive, is to display a one-line message at the bottom of the screen giving a quick description of the area where the mouse is. For example, when you are looking at a Web page and pass the mouse over a link, your browser may display the destination URL for that link at the bottom of the page. This is a form of context-sensitive help. You can also define a particular key to be the "help" key. Tell the user to press that key any time he needs help. The F1 key in many software packages is the help key; you should seriously consider making it the help key in your applets, too. Remember that you want to give all your applets and applications a similar feel in the same way that most cars have a similar feel. It doesn't take long to figure out how to drive a car you've never seen before. It should be that way with software. Listing 7.2 shows a HelpDialog class that is useful for popping up screens containing help text. The help dialog is a simple OK dialog box-it displays a text message and a button labeled "OK" which, when clicked, makes the dialog box disappear. One thing to keep in mind when you want to create dialog boxes is that you must have a parent frame for the dialog box. When you are running an applet, you can't normally access the applet's parent frame. The HelpDialog class addresses this problem by creating its own frame. It saves the frame in a static variable so it doesn't have to create a new frame the next time it needs to create a dialog window. You can actually access the parent frame for an applet. Sometimes it will work exactly like you want. It usually works for dialogs, but it fails miserably on some platforms when you try to create a menu for the parent frame. You can use the getParent method from the component class to trace back up through the component hierarchy to find the applet's parent frame. The following code fragment finds an applet's parent frame:
At this point, myFrame would either contain the parent frame of the applet, or null if it couldn't find the parent frame. Listing 7.2 Source Code for HelpDialog.java
In addition to the HelpDialog class, you need a way to assign help information directly to your AWT components. It would have been nice if Sun had built that right into the AWT, and maybe they will in the future, but for now you have to do it yourself. You could subclass all the AWT components to support help if you really had nothing better to do for a month or two, but there are easier ways. One simple way is just to store the components and their corresponding help text in a hash table. Whenever someone requests help from within an AWT component, look in the table and see if you have defined any help for that component. Listing 7.3 shows the HelpSystem class that enables you to assign help text to AWT components. It also contains a method to display the help for a component, but it makes no assumptions on how you actually request the help in the first place. Listing 7.3 Source Code for HelpSystem.java
Now that you have a way to display help and a way to map help strings to components, you need to add some sort of help key to your applet. Going back to the Lycos search form applet, you can modify it to use F1 as the help key. The AWT components are polite enough to ignore keyDown events for keys they do not recognize, and they all leave the F1 key alone. You can trap the F1 key in your applet and display the appropriate help text. To add context-sensitive help to the LycosForm class, you need to create an instance of the help system. Since there are several methods that actually use the help system, you declare it as an instance variable: protected HelpSystem helpSystem = new HelpSystem(); Next, for each component that will have a help screen, you add the component to the help system. For example, once you create the queryString text field, you can add a help string for it with the following code fragment: helpSystem.addHelp(queryString, "QUERY HELP\nEnter the words you want to search\n"+ "for separated by spaces. Avoid common words like\n "+ "\"the\" or \"and\"."); The trickiest part of implementing the help system is grabbing the F1 key and figuring out which component the user wants help on. When you receive keyboard events, you are given an x and y coordinate where the keystroke occurred. Unfortunately, this does not really indicate where the mouse was when you pressed the key. The x and y coordinates are bounded by the component that currently has the keyboard focus. For context-sensitive help, you don't want the user to have to move the keyboard focus to another component before requesting help. If this were the case, they would have to click a button before they could get help for that button. What you must do, instead, is track the movement of the mouse all the time. You can do this very simply by creating two instance variables in your class, mouseX and mouseY: protected int mouseX; // the current X coord of the mouse protected int mouseY; // the current Y coord of the mouse Next, you override the mouseMove method. This method is called whenever the mouse moves. You simply copy the x and y coordinates of the mouse and return:
Notice that you return false from the mouseMove method. This indicates that you haven't actually handled the mouse movement event, allowing the event to be passed to another component. If you do not want another component to see the mouse movement event, you should return true instead. The hardest part of implementing this context-sensitive help system is determining which component the user wants help on. The problem here is that you have to take the x and y coordinates of the mouse and locate the component at those coordinates. The locate method does this, sort of. The locate method takes an x and y coordinate and returns the component at those coordinates. It only looks one level deep in the component hierarchy, however. If you are using nested panels, as the LycosForm applet does, the locate method will only return the panel enclosing the component you really want. The solution for this problem is simple. If the locate method returns a container, you use the locate method in that container. You keep repeating the process until locate returns a component that is not a container. There is one additional little sticking point here. The locate method expects the x and y coordinates to be relative to the container you are searching. The first time you call locate, everything is fine, since the mouse x and y coordinates are relative to your applet. After that, you have to adjust them to be relative to the container returned by locate. For example, suppose you had mouse coordinates of 100, 50 and the locate method returned an instance of the Panel class for those coordinates. Suppose that the panel's upper-left corner was at 65, 45. You would subtract the panel's coordinates from the original mouse coordinates, giving a new location of 35,5. Now you call the locate method in the panel with the new coordinates. You can use the location method to get the coordinates of the upper-left corner of any component. Listing 7.4 shows a keyDown method for the LycosForm applet that uses this technique to identify the component where the F1 key was pressed. Listing 7.4 Source Code for the keyDown Method in LycosForm3.java
Figure 7.3 shows the LycosForm3 applet in action with a Help dialog box displayed. Figure 7.3 : Context-sensitive help screens make your applets easier to use. Creating Dynamic FormsIt's funny that with all the advanced technology running on the desktop today, the methods of recording information haven't really changed. Most online forms are just electronic versions of printed forms. This is really a shame because we now have the ability to create forms that adapt to the information you are entering. For example, suppose you are creating a personal information form containing all the typical pieces of information associated with a person. If you look at a typical form of this type, you'll see many sections with instructions such as "Fill in this section only if married." No computerized form should have instructions such as that-not when there are so many ways to avoid it. If you really must have your form look exactly the same, no matter what information is being entered, consider selectively enabling and disabling components if they apply. Listing 7.5 shows a very brief example of this technique. Listing 7.5 Source Code for DynamicDisable.java
This technique doesn't provide much of an improvement over paper forms, however. You could still be looking at a huge document full of components, some of which are enabled and some which are disabled. It would be a lot kinder to the user to show him only the items he actually needs to fill in. In other words, rather than just disabling a component, hide it-make it invisible. Hiding has its drawbacks, however. When you hide a component, the layout manager will change the layout of the components. If you aren't using a layout manager, this won't be a problem. If you are using a layout manager, pay special attention to how the form changes when you show and hide various components. You may want to perform a mixture of disabling and hiding. Listing 7.6 shows a very brief example of how to hide and show components dynamically, using the same components as the example in Listing 7.5. Notice that you must call the validate method after hiding or showing a component. This causes the layout manager to recompute the component positions. Listing 7.6 Source Code for DynamicHide.java
The CardLayout layout manager is another good tool for dynamic form construction. It lets you create a stack of different containers (usually panels), only one of which is displayed at any time. By using a card layout, you can create all your panels ahead of time and add them to the card layout. Then, whenever you want to display a specific panel, you tell the card which panel to display. For example, you may have panels that display information on Moe, Larry, and Curly. Listing 7.7 shows a simple example program that uses a card layout and some buttons to select the specific card. Listing 7.7 Source Code for CardExample.java
Figure 7.4 shows the CardExample applet in action. The buttons along the bottom select the different card, which simply contain a single label. Figure 7.4 : A card layout enables you to display one of several panels. When you are creating dynamic forms, you can group sections of your forms onto different cards. You can create different methods for going from one card to the next, like having a master index of the different cards, or putting Next and Prev buttons on each card. If you want to disable a section of the form, don't make that section's card available. For example, suppose you have a part of the form for entering marriage information-date, place, witnesses, and so on. If a person is single, you don't want to present that part. You can remove it from the set of cards in your card layout. Loading Another URL from an AppletYou may have noticed that several applets in the last two chapters actually open up URLs on the Web and display their contents in the browser. They all use the showDocument method in the AppletContext class. You can use the showDocument class to give your Java applets the same connectivity to the rest of the Web that any Web page has. This also enables you to create new and unique ways to access Web pages.
The showDocument method can
take a second parameter, which is the target frame for the URL.
Your Java applet can open up an URL in its own frame, its parent
frame, the top-most frame, a brand new frame, or a particular
named frame. You can use this ability to create interesting Web
page layouts. For instance, you can create an index applet in
Java that provides some neat new way of listing URLs. You could
run this applet in a narrow frame on the left side of the page,
leaving the rest of the page for the frame where the selected
URL will be loaded.
Creating Image Maps with Hot SpotsImage maps were a neat addition to the Web a few years ago. Rather than a list of textual links, you could open up an URL by clicking a particular part of an image. These image maps still have limitations, however, because they are not very interactive. You can create a Java image map, however, that contains hotspots-areas that light up when the mouse passes over them. You can also add context-sensitive help, which is always a nice thing to have. To create an image map with hot spot, you need a way to define what a "hot spot" is. You could simply create a class that represented an area of the image. This class would also be responsible for displaying whatever should appear when the mouse passes over the hot spot. Listing 7.8 shows an abstract class that defines the methods necessary to implement such a class. Listing 7.8 Source Code for ImageRegion.java
Because the ImageRegion class is an abstract class, you need something concrete to actually implement a region. You will almost certainly need to define a rectangular region at some point. Actually, it is trivial to extend a rectangular region to be a polygon region. Listing 7.9 shows an implementation of ImageRegion that supports polygon regions. Listing 7.9 Source Code for ImageRegionPoly.java
You may also have a need for a circular region. The ImageRegionCircle class in Listing 7.10 implements a circular region. Listing 7.10 Source Code for ImageRegionCircle.java
Now that you have a method for defining a region in an image, you need a way to display an image, add these regions to it, and track the mouse to see when it hits a region. The ImageMap class in Listing 7.11 does just that. It also shows you how to define a canvas that displays an image. Listing 7.11 Source Code for ImageMap.java
The next section of the ImageMap
class demonstrates a very important concept in object-oriented
design. The ImageMap class
implements a framework that allows you to plug in different ImageRegion
objects. You can add many new types of ImageRegion
objects without changing the ImageMap
class itself. It is very important to correctly assign class responsibilities
in your design. In this case, the ImageMap
class is responsible for displaying the master image, or background
image. It is also responsible for tracking mouse movements and
passing them on to affected regions. The ImageRegion
class is responsible for displaying itself on the map if necessary,
and for responding to a mouse click.
Listing 7.11 Source Code for ImageMap.java (continued)
You may have noticed that the implementations of the image regions were incredibly small and didn't really seem to do anything. You are correct on both counts. To get any benefit out of the regions, you have to create subclasses that actually do something. Suppose you want to create a map that has a set of circular hotspots that light up with the name of the city in that section of the map. You need to keep track of the name of the city and also implement a paint method that displays the city name. Because an image map isn't very useful if you can't select items, your city hotspot should also do something when you click it. Listing 7.12 shows a circular region that represents a city. When you click the region, it pops up an OK dialog box telling you which city you clicked. Listing 7.12 Source Code for CityRegion.java
Now that all the pieces of the puzzle are in, you can create an the image map for displaying these cities. Listing 7.13 shows the CityApplet class. Listing 7.13 Source Code for CityApplet.java
Figure 7.5 shows the output from this applet. Figure 7.5 : Image maps in Java can implement hot spots. |
|
|
|