Delphi Clinic | C++Builder Gate | Training & Consultancy | Delphi Notes Weblog | Dr.Bob's Webshop |
|
One of the corner stones of the popularity of Java is the fact Java is an Web programming language. This means that Java makes it possible for developers to write programs that can be run inside a Web browser. These programs are called applets. A normally static and relatively dull HTML page can now be more dynamic and add interactivity to our page without reloading the page from a web server.
In this article we will build an applet that will show a hierarchy of pages on a web server. By clicking on a branch of the hierarchy tree the corresponding HTML page will be shown in our web browser. This way we can give the visitor to our web site a navigation aid to move around.
Applet Wizard
JBuilder gets us right on the way by providing us with a very nice New Applet Wizard.
But before we can begin we have to make a new empty project in JBuilder.
Next we add an applet to our project.
Click File | New and select Applet from the first tab.
This will start the Applet Wizard:
Figure 1. First screen of the Applet Wizard
The Applet Wizard consists of three pages that can be filled in.
By clicking on the Finish button JBuilder will generate applet source code and a HTML page with the right <APPLET> tag code inserted.
The previous image shows the first page of the wizard.
Here we can fill in the name of the package the applet will belong to.
JBuilder already has filled in the name of our project.
Most of the times this will do.
But if we want to re-use our classes it is advisable to think of a naming strategy for our packages.
This way we can build a library of classes that can be easily found and used in our other JBuilder projects.
For example I use the following naming strategy:
name of the IP domain.name programming environment.name of programmer.package name
So I entered the following package name into the Applet Wizard:
com.drbob42.jbuilder.hubert.sitemap
Next we enter the name of our applet at the Class entry: SiteMapApplet.
JBuilder builds a file path by combining the package and class field and shows it in the File field.
The second section of this dialog is labeled Applet style. Here we can check and uncheck different options. The first option let us decide if we want to generate source that will use the new Swing classes from the Java Foundation classes. If we leave this option unchecked JBuilder will generate source code that will not use these classes. Because the Swing classes are a very big step forward in Java UI design we will check this option for our applet. Generate header comments inserts the project information to the top of our source code. It saves us a little typing so let's check this option. Can run standalone generates the right methods and properties to make our applet capable of running outside the browser as a standalone program. We don't need this in our example, so we let it unchecked. The last option Generate standard methods puts standard applet methods destroy(), stop(), start() in our source code, so we can override these methods. We don't need these in our example and leave this option unchecked.
By clicking on next we get to the second page of the Applet Wizard.
Figure 2. Screen 2 of the Applet Wizard
Here we can add applet parameters to our applet.
Applet parameters are parameters that can be passed to our applet by using <PARAM> tags inside the <APPLET> tag in the HTML document.
This way we can make our applet very flexible to use by others.
The wizard makes sure the <PARAM> tags are inserted into the HTML page and inserts code to handle the parameters in our applet source code.
For every parameter the name has to be filled in.
In the Type column we can select from different types the parameter can be.
The Desc let's us give a brief description of the parameter.
The Variable let's us define a name for the parameter that is to be used in our applet.
We can reference the parameter in our code by this name.
The last column, Default, gives us a possibility to enter a default value in case the user hasn't provided the applet with the parameter.
For our applet we add the following parameter info:
Name | Type | Desc | Variable | Default |
---|---|---|---|---|
treestyle | String | Style of tree view | pmTreeStyle | arrows |
Later on we will see how we use this parameter in our applet. So we don't have to worry if we don't know the meaning of this parameter yet.
By clicking on the Next button we get to the last page of the Applet Wizard.
Figure 3. Last screen of the Applet Wizard
Here we can add extra information to the <APPLET> tag before the HTML page is generated. We don't need to change anything here, and we can always changes this information later by hand in the generated HTML page.
So now we can click on the Finish button and let JBuilder do its work. As we can see JBuilder created two extra files into our project: SiteMapApplet.java and SiteMappApplet.html, containing the Java source code and the HTML page with the applet tag inserted. Take a look at the source code JBuilder has generated. We notice that our applet extends JApplet and not Applet. This is because we checked the option Use only core JDK & Swing classes in the wizard and JApplet is part of the Swing classes. We can see the init method, containing two try/catch statements. The first to read in and assign the parameter value, the second makes a call to jbInit(). The jbInit method is responsible for initializing all the UI components in our applet. Just before the jbInit method there is a whole block of code commented out. This code is responsible for changing the look-and-feel of our applet. We can choose between three different themes. The standard theme is Metal if none other theme is initialized. If we remove the comment slashes (//) this block will be executed by the applet and the Windows look-and-feel will be implemented.
At the end of the code we find two information functions. The getParameterInfo method returns information about our parameters. JBuilder has used the information we provided in the Applet Wizard to generate this function. The getAppletInfo method returns information about our applet to for example a Web browser or the appletviewer. We must overwrite the text after return with our own information, like name and version. For example:
public String getAppletInfo() { return "SiteMapApplet - Version 1.1 - Author: Hubert A. Klein Ikkink\r\n" + "Build with Borland JBuilder 2.0\r\n" + "Shows a hierarchy of pages on a website."; }
We can build and run our project (F9) and if everthing went well, we should see an empty applet. And that's because we haven't added any components or other user interface elements to it. We can click on Applet | Info... to see our customized information about our applet.
Adding the TreeControl Component
It is time to give the applet an UI.
Select the SiteMapApplet.java file in the Navigation Pane of the AppBrowser.
(The Navigation Pane is in the upper-left corner of the AppBrowser).
Click on the Design Tab in the Content Pane (The Content Pane is the most right pane, and to be complete: the lower-left is called the Structure Pane).
This opens the design mode of our java source file, in this case our applet.
In the Structure Pane we see in hierarchical view of the components in the applet.
The UI folder contains all the UI components.
Now there is just the this component.
This means the applet itself.
We are now ready to add a tree control. Select the TreeControl on the Controls tab of the component palette. Click on this in the Structure Pane. The TreeControl is now added to our applet. We can see it at the upper-left corner of the applet in the Content Pane and a TreeControl with the name treeControl1 is added to this. To make sure the control takes up all the space the applet has to offer, we hava to change a couple of properties.
Reading data from the network
Next we must read in the data to fill our tree with.
This data is placed on the web server in a data file, called toc.txt.
We must add our own methods to the applet source code to achieve this functionality.
So click on the source tab.
Make sure that our cursor isn't anywhere within a method, go for example with the cursor to the end of the init method and after the last bracket of the init method enter the following code (don't type in the line numbers):
1: private void loadTree() { 2: // Load file from netwerk 3: // 4: String toc = null; 5: try { 6: URL tocURL = new URL(this.getDocumentBase(), "toc.txt"); 7: toc = readTOCFile(tocURL); 8: } catch (MalformedURLException mfuex) { 9: System.out.println("MalformedURLException in TOC file: " + mfuex); 10: } 11: 12: // Parse resulting string 13: // 14: if (toc != null) { 15: links = new Vector(5); 16: StringTokenizer stok = new StringTokenizer(toc, "\r\n"); 17: while ( stok.hasMoreElements() ) { 18: String tmp = (String) stok.nextElement(); 19: StringTokenizer node = new StringTokenizer(tmp, "|"); 20: String depth = (String) node.nextElement(); 21: String title = (String) node.nextElement(); 22: String url = (String) node.nextElement(); 23: LinkInfo link = new LinkInfo(Integer.parseInt(depth), title, url); 24: links.addElement(link); 25: } 26: } 27: } 28: 29: private String readTOCFile(java.net.URL url) { 30: StringBuffer result = new StringBuffer(); 31: try { 32: BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream())); 33: String line; 34: while ( (line = in.readLine()) != null ) { 35: result.append(line); 36: result.append("\r\n"); 37: } 38: in.close(); 39: } catch (IOException ioex) { 40: System.out.println("IOException : " + ioex); 41: } 42: return result.toString(); 43: }
The method loadTree contains of two parts. The first part (line 1-10) is concerned with reading in the data file (named toc.txt) from the web server. In line 6 a URL object is made that points to the file toc.txt. This text file contains a description of the site suitable for building the tree. Each line contains the title or description of a link, the URL of the link and the depth to the root for the link. Each item on the line is separated by an |. For example the line for an index page at the root of the drbob42 web site could be:
1|Index Dr.Bob's WebSite|http://www.drbob42.com/ | | | depth title or description url
Line 7 makes a call to the method readTOCFile (lines 29-43). This method opens an inputstream to the URL object (line 32). Then every line is read and appended to the StringBuffer until there are no more lines to be read (line 34-37). The inputstream is closed (line 38) and the result is returned as a string object (line 42). This string object is assigned to the toc variable in loadTree. The string is parsed line by line and item by item. The very useful java.util.StringTokenizer is used for this purpose. A LinkInfo object is created with values from the parsing process. This object is added to the instance Vector variable links containing all the LinkInfo objects. To add the links variable to the class, we add the following line after the opening bracket of the class (on a new line): Vector links;
The purpose of the LinkInfo class is to store the items for a link in a usable way. The class is straightforward: 3 properties depth, title and url, 2 constructors, and getter/setter methods for each of the properties. The class is defined as an inner class of the applet. Take a look at the complete source code for the source of this class.
Filling our tree with leaves
The next step is to fill the tree control with our links.
Therefore we make the following buildTree method.
The purpose of this method is to walk along the LinkInfo objects and build a tree according to relative depth of each linkinfo object to another.
1: private void buildTree() { 2: LinkInfo link = null; 3: int depth = 0; 4: int lastDepth = 0; 5: Hashtable roots = new Hashtable(5); 6: for (Enumeration e=links.elements(); e.hasMoreElements(); ) { 7: link = (LinkInfo) e.nextElement(); 8: depth = link.getDepth(); 9: if (depth == 1) { // This is the root node 10: GraphLocation root = treeControl1.setRoot(link.getTitle()); 11: roots.put(new Integer(depth), root); 12: lastDepth = depth; 13: } else if (depth > lastDepth) { // Next child 14: GraphLocation node = treeControl1.addChild( 15: (GraphLocation) roots.get(new Integer(lastDepth)), link.getTitle()); 16: roots.put(new Integer(depth), node); 17: lastDepth = depth; 18: } else if (depth == lastDepth) { // Sibling 19: GraphLocation node = treeControl1.addChild( 20: (GraphLocation) roots.get(new Integer(lastDepth-1)), link.getTitle()); 21: roots.remove(new Integer(depth)); 22: roots.put(new Integer(depth), node); 23: } else if (depth < lastdepth) { // child to previous root 24: graphlocation node = treecontrol1.addchild( 25: (graphlocation) roots.get(new Integer(depth-1)), link.getTitle()); 26: roots.remove(new Integer(depth)); 27: roots.put(new Integer(depth), node); 28: lastDepth = depth; 29: } 30: } 31: }
The Hashtable roots is used to store the last tree node of each depth that can be a root to a next child. The main logic of this method can be found in line 8 to 29. For the current LinkInfo object the depth is asked and then compared to the depth of the last LinkInfo object, stored in the integer lastDepth. Accordingly the node is added to the treecontrol with the addChild(GraphLocation parent, Object o) method. The first parameter of this method is the parent node to which the new node is added. The second argument is the name of the tree node that will be shown in the tree, in this case the title of the LinkInfo object. The roots and the lastDepth variable are also updated.
Now we have the two methods loadTree and buildTree to load and build the tree. We now add the calling arguments to this methods to the applet's init method. We can now try to run our applet. If we get errors when the code is compiled we should check whether all the classes are available through imports. We add the import statements after the package statement at the top of the source code and it should look like this:
import java.awt.*; import java.awt.event.*; import java.applet.*; import com.sun.java.swing.*; import java.beans.*; import java.util.*; import java.net.*; import java.io.*; import borland.jbcl.control.*; import borland.jbcl.model.*; import com.sun.java.swing.UIManager;
Interact !
By double-clicking on a tree node, the corresponding URL should be shown in the web browser.
We need to add code to handle this event.
Activate the design tab for the applet.
Select treeControl1 and click on the events tab in the Object Inspector.
All the events available from the treecontrol are shown here.
Double click the actionPerformed event.
JBuilder inserts all the code necessary for handling this event in our source code:
an innerclass named SiteMapApplet_treeControl1_actionAdapter, an adapter class for the event, and the method void treeControl1_actionPerformed(ActionEvent e), containing the actual actions that need to be executed when the event occurs.
When we double click again we return to the source code and the cursor is placed at the point where we have to enter the code that needs to be executed: in the treeControl1_actionPerformed method.
Enter the following code:
1: void treeControl1_actionPerformed(ActionEvent e) { 2: GraphLocation[] selections = treeControl1.getSelection().getAll(); 3: String title = (String) treeControl1.get(selections[0]); 4: String url = null; 5: LinkInfo tmp; 6: for (Enumeration enum=links.elements(); enum.hasMoreElements(); ) { 7: tmp = (LinkInfo)enum.nextElement(); 8: if (tmp.getTitle().equalsIgnoreCase(title)) { 9: url = tmp.getURL(); 10: break; 11: } 12: } 13: try { 14: getAppletContext().showDocument(new URL(url)); 15: } catch (MalformedURLException mfuex) { 16: } 17: }Lines 2 and 3 retrieve the selected element from the tree. Lines 5 to 12 find the URL that belongs with the selected title. And line 14 makes sure the document at the specified URL is shown in the web browser.
Finishing touch
There are now only a couple of things we need to do to finish our applet.
First we must expand the tree so all links are shown when the applet is started.
Add the following code to the init method:
treeControl1.setExpandByDefault(true);By default the items in the tree are editable by users. To disable this we add the following line of code:
treeControl1.setEditInPlace(false);Finally we need to use our applet parameter pmTreeStyle to set the style of the tree. The following code takes care of this:
if (pmTreeStyle.equalsIgnoreCase("arrows")) treeControl1.setStyle(TreeControl.STYLE_ARROWS); else treeControl1.setStyle(TreeControl.STYLE_PLUSES);And we are ready ! Right? Well, not quite. In order to deploy our applet we must use the Deployment Wizard, so all necessary files will be packed in a JAR file, and then that JAR file can be placed on our web server. The Deployment Wizard shows all files available from our project. It isn't possible to add extra dependencies inside the wizard. So if we want to add some extra files, we must first add them to our project. And that is what we got to do right now. The TreeControl uses images to display the arrows in front of a treenode. But these images will not be deployed in our JAR file, if we don't add them ourselves in our project.
ARCHIVE = "SiteMap.jar"
Here is the applet in action with a sample data file:
This applet uses the Java Plug-in. You can download the plug-in at JavaSoft (http://java.sun.com/products/plugin/). This plug-in makes it possible to run our applets in web browsers like Netscape Communicator, and Internet Explorer, without having to worry which Java version they support. Because the plug-in uses the latest Java Runtime Environment (JRE 1.1.6), we can use all features available for Java 1.1. In order to change the applet tag into a tag that will load and use the plug-in, a converting utility is also available at JavaSoft.
The complete source code for this applet.