01 March 2010

The Obligatory Calculator in JSF 2.0




Checked out some of the high level JSF 2.0 new features recently using NetBeans 6.8 and GlassFish v3.  For what it's worth, I think they are very usable, feature rich and effcient pairing for conducting Java EE 6 development.

As with all old habits, some won't die.  And my habit for taking a quick look at any new programming language/framework is to build a very simple calculator with one screen and some simple calculation logic.

With NetBeans, creating a new Web project is trivial.  To add JSF support select it as a framework to use.

After a bit of navel gazing, I realized a significant part of the unholy dislike I had for JSF 1.2 and its earlier versions predominantly stemmed from the need to live in the configuration red zone -- faces-config.xml. Every damned thing that you wanted to use or access pretty much needed to be defined in the configuration file: managed beans definitions with packages/classes and names and scopes, even the most simples of page navigation paths, etc.

But as of JSF 2.0, that has mostly changed! Accomodating a convention over configuration model for page navigation and making use of annotations for declaring managed bean components (and associated runtime attributes and property values) in my simple example, I didn't have to even look at the faces-config.xml file.  Happy Days.

Here's my simple CalculatorManagedBean, which is responsible for performing the arduous task of calculating a result from a given two values and an operand. It also supplies the operand values for the screen to display.

To declare this as a ManagedBean, all I had to do was to add the @ManagedBean annotation. I also decided to bind this ManagedBean into each clients HttpSession so it was created once, and was able to store property values set from eacg clients page.

 1 package sab.demo.calc.beans;
 2 
 3 import java.io.Serializable;
 4 import javax.annotation.PostConstruct;
 5 import javax.faces.bean.ManagedBean;
 6 import javax.faces.bean.SessionScoped;
 7 
 8 @ManagedBean(eager=true)
 9 @SessionScoped
10 public class CalculatorManagedBean implements Serializable {
11 
12     public static char[] operands = { '+', '-', '*', '/' };
13 
14     int value1;
15     int value2;
16     char operand;
17     String result;
18 
19     public CalculatorManagedBean() {
20     }
21 
22     public char[] getOperands() {
23         return operands;
24     }
25     
26     public char getOperand() {
27         return operand;
28     }
29 
30     public void setOperand(char operand) {
31         System.out.printf("setOperand: %s", operand);
32         this.operand = operand;
33     }
34 
35     public String getResult() {
36         return result;
37     }
38 
39     public void setResult(String result) {
40         this.result = result;
41     }
42 
43     public int getValue1() {
44         return value1;
45     }
46 
47     public void setValue1(int value1) {
48         System.out.printf("setValue1: %s\n", value1);
49         this.value1 = value1;
50     }
51 
52     public int getValue2() {
53         return value2;
54     }
55 
56     public void setValue2(int value2) {
57         System.out.printf("setValue2: %s\n", value2);
58         this.value2 = value2;
59     }
60 
61     public void calculate() {
62         switch(getOperand()) {
63             case '+' :
64                 result = String.valueOf(value1 + value2);
65                 break;
66             case '-' :
67                 result = String.valueOf(value1 - value2);
68                 break;
69             case '*' :
70                 result = String.valueOf(value1 * value2);
71                 break;
72             case '/' :
73                 double v1 = value1;
74                 double v2 = value2;
75                 result = String.valueOf(v1 / v2);
76                 break;
77         }
78         System.out.printf("Calculate: %s %s %s = %s\n",
79                 getValue1(),
80                 getOperand(),
81                 getValue2(),
82                 getResult());
83 
84     }
85 
86     @PostConstruct
87     public void postConstruct() {
88         System.out.println("postConstruct");
89         operand = '+';
90         value1 = 0;
91         value2 = 0;
92         result = "n/a";
93     }
94 }
95 
96 

With the ManagedBean taken care of, the next step was to create the JSF page. With JSF 2.0, it now uses Facelets as the default view technology instead of JSP. This means there are lots of things that are able to be expressed more naturally in the view layer, which were previously difficult or cumbersome in the older JSP model.

A really useful thing I found in facelets was the ability to render a ManagedBean property directly in the page, without needing to surround it with other tag library calls.

So to create my calculator screen, I simply added a couple of fields, and wired them into the CalculatorManagedBean that I'd developed. Again, following convention over configuration, the default name for a ManagedBean is a lower case version of the classname, which makes them easy to remember/lookup from a project hierachy (as opposed to hunting through XML). Of course NetBeans makes this easy since it has code-insight which presents the names of known ManagedBeans and their available properties for insertion via a few keystrokes.

The list of operands values shown in the select list are populated from a property on the ManagedBean which returns an array of char.

The "=" button is wired into the calculate operation, which takes its current property values, performs the desired operation and sets the result property, which is then displayed in the result field on the page.

 1 <?xml version='1.0' encoding='UTF-8' ?>
 2 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 3 <html xmlns="http://www.w3.org/1999/xhtml"
 4       xmlns:f="http://java.sun.com/jsf/core"
 5       xmlns:h="http://java.sun.com/jsf/html">
 6     <h:head>
 7         <title>Calculator Facelet</title>
 8     </h:head>
 9     <h:body>
10         <h:column><h3 style="font-family: arial; font-variant: small-caps; color: #336699">Calculator</h3></h:column>
11         <h:form>
12             <h:panelGrid columns="1" style="text-align: center; border: 1px solid #336699; padding: 5px; ">
13             <h:column>
14                 <h:inputText id="value1" value="#{calculatorManagedBean.value1}" 
15                              size="5" maxlength="5" style="text-align: right;"/>
16             </h:column>
17             <h:column>
18                 <h:inputText id="value2" value="#{calculatorManagedBean.value2}" 
19                              size="5" maxlength="5" style="text-align: right"/>
20             </h:column>
21             <h:panelGrid columns="2">
22                 <h:column >
23                     <h:selectOneMenu id="operand" value="#{calculatorManagedBean.operand}" 
24                                      style="text-align: center;">
25                         <f:selectItems value="#{calculatorManagedBean.operands}"/>
26                     </h:selectOneMenu>
27                 </h:column>
28                 <h:column><h:commandButton value="=" action="#{calculatorManagedBean.calculate}"/></h:column>
29             </h:panelGrid>
30             <h:column>
31                 <h:inputText id="result" value="#{calculatorManagedBean.result}" 
32                              readonly="true" size="5" style="font-weight: bold; text-align: right"/>
33             </h:column>
34         </h:panelGrid>
35         </h:form>
36     </h:body>
37 </html>
38 
39 
40 

Put it all together and run using the local GlassFish instance, the page works and looks like this.


So there it is, a JSF 2.0 version of my old habit up and running in a few minutes. I like what I've seen with JSF 2.0 so far.

Of course, the use of @ManagedBean annotation in JSF 2.0 may be dead already with the arrival of CDI in Java EE 6 and its @Named annotation, but that's not an issue I'm going to explore here.