This month I'm going back to programming for a while. I need a rest from the weirdness on the Talkback discussion in last month's column. I do intend to write more about theory issues in the future, however, but not for the next couple months.
I do need to make one clarification from last month's column. Many people interpreted my comments about user interfaces as advocating heavyweight objects with billions of rendering methods in them. That's not what I had in mind. There are numerous viable ways to create user interfaces (UIs) without exposing implementation details. The Gang of Four Builder and Visitor patterns immediately come to mind. A simple
drawYourself() method obviously can't work on anything but the most simple objects, and having 50
drawYourselfInThatFormat() methods is a nonsensical recipe for unmanageable code. Many people think I'm advocating that approach, however, so I apologize if I gave that impression.
Since I've seen a lot of misunderstanding regarding the UI issue, I'll plan on showing you a few implementations of object-oriented (OO) UI-building approaches in future columns. I presented one such solution in JavaWorld a few years ago (see Resources), but I've built better systems in the intervening years. This current column presents a piece of one of these UI systems: an infrastructure class I've used in building client-side UIs in an OO way. It is not in and of itself a solution to the UI problem, but it's a useful building block.
Since the code samples are rather large, I'll break up the presentation into two chunks. This month is documentation and application code; next month is the source code.
Read the whole "Create Client-Side User Interfaces in HTML" series:
Using HTML on the client side
HTML is a wonderful thing. It lets you lay out complicated user interfaces with a minimum of fuss; it does a great job of separating UI structure and layout from business logic; it's easy to write; and it's easy to maintain. Abstract Window Toolkit (AWT)/Swing layout is, in contrast, annoyingly difficult to use. You must modify (and recompile) code to change the look of your screens, and the code for a trivial layout is itself nontrivial, extending to many pages. Wouldn't it be nice if you could specify your entire client-side user interface in HTML?
(I know that some of you will answer the preceding question with a rousing "No, it wouldn't be nice!" Many argue that HTML and a good user experience are mutually exclusive concepts, since HTML forces your user interface into "infinite cascading dialog box" mode. On the other hand, a lot of applications can leverage HTML effectively in at least some part of the user interface—for tabular reports if nothing else. You can't throw out the baby with the bath water.)
JEditorPane class, at first, seems to be an answer to the HTML-layout problem. It does understand HTML input after a fashion. For example, the following code pops up a frame that displays some simple HTML text:
JFrame main_frame = new JFrame(); JEditorPane pane = new JEditorPane(); pane.setContentType ( "text/html" ); pane.setEditable ( false ); pane.setText ( "<html>" + "<head>" + "</head>" + "<body>" + "<center><b>Hello</b> <i>World</i></center>" + "</body>" + "</html>" ); main_frame.setContentPane(pane); main_frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); main_frame.pack(); main_frame.show();
I say "after a fashion" because
JEditorPane is not particularly good at handling complex HTML. It doesn't do a great job with nested tables, and it doesn't do Cascading Style Sheets (CSS) very well. (I've been told that many of these problems will be fixed in the Java 1.5 release next year, but for the time being, we all have to put up with them.) Finally,
JEditorPane doesn't do a particularly good job of laying out things like radio buttons. They aren't aligned properly with the text baseline, and they always display a gray background, so they don't work well if you change the page's background color (with a style on the
<body...> tag, for example).
All these flaws are annoying, but the show-stopper problem with
JEditorPane is that it works as a text control, not as a layout facility. You can specify an HTML
<form...> in the input, for example, but the form is submitted to a Web server when you hit the Submit button. To be useful for specifying a client-side UI, you want the form data to go back to the program that displayed the form, not to some remote server. You also need a convenient way to add custom tags for nonstandard input or display purposes or to provide place holders for standard Swing
JComponents you want to use on the form. (
JEditorPane lets you do this, but the mechanism is far from convenient.) Finally, you need to be handle things like a Cancel button, which doesn't exist in HTML.
Fortunately, the most egregious of the foregoing problems can be solved using customization facilities built into
onclick attribute, but that's an awful lot of work. Similarly, providing true custom-tag support (where you can process everything that comes between a start and end tag) is very difficult to do with the existing parser. You could replace the
JEditorPane's parser with a better one, but that's a lot of work too. I opted for simpler solutions that did the job. I put enough functionality in my class that I could use it to build a user interface for the program I was writing, but didn't provide a "perfect" solution. The problem I was solving was: provide a way to specify a user interface in HTML. I was not solving the problem: provide a way to display all possible HTML in a client-side application. The
HTMLPane class I present in this article solves the specify-a-UI-in-HTML problem nicely.
Using the HTMLPane
My client-side-only HTML-input class,
HTMLPane, is a
JEditorPane derivative that fixes the problems discussed earlier. Listing 1 shows how to use an
HTMLPane. I created a simple
JDialog derivative called
HtmlDialog in which you can specify dialog-box layout as HTML. The
HtmlDialog is a trivial example of the Façade design pattern. It just does the rote work necessary to put an
HTMLPane into a dialog box and display it.
HtmlDialog.Test class (Listing 1, line 134) provides a simple example of how to use the
HtmlDialog. It creates a mostly empty main frame (
owner). Using code like the snippet reproduced below,
main() creates an
HtmlDialog object whose contents are specified in the CLASSPATH-relative file
com/holub/ui/HTML/test/okay.html (Listing 2). The string
"Test HtmlDialog" appears in the title bar. Finally,
main() pops up the dialog by calling
d.popup(), which won't return until the user shuts down the dialog:
// Display the okay.html file in a dialog box that has // the title "Test HtmlDialog". // HtmlDialog dialog = new HtmlDialog( owning_frame, "com/holub/ui/HTML/test/okay.html", "Test HtmlDialog"); // Pop up the dialog and wait for the user to dismiss it. // dialog.popup(); // Print the "form" data that the user typed. // System.out.println ( "hidden=" + dialog.data().getProperty("hidden") + "user-input" + dialog.data().getProperty("user-input") );
Form data (the text the user typed into an
<input ... name="x" ...> element or equivalent), is available via the
data() method, which returns a
java.util.Properties object that holds key/value pairs representing the form data. The above call to
dialog.data().getProperty("hidden") returns the string
"hidden-field data". The
dialog.data().getProperty("user-input") call returns whatever the user typed into the input field.
Most of the work involved in instantiating the encapsulated
HTMLPane happens in the
HtmlDialog constructor (Listing 1, line 46). The constructor first sets up an
ActionListener that handles the Submit button on the form. This Observer shuts down the current dialog box and copies any form data from the
HTMLPane to the
data instance variable. The constructor then gets the input file from the CLASSPATH, and then loads the HTML into the
setText(). (There's also a
setPage(URL) method, but you would need a URL for the absolute path to the file if you used it. I wanted the HTML filename to be CLASSPATH relative.)
Cancel processing is handled in
popup() (line 121), which assumes that a Cancel button was pressed if a Cancel key exists in the submitted form data. (More on how that data gets into the
Properties object in a moment.)