Archive for the 'JSF' Category

31
Jan
10

Collapsible Navigation Bar in Apache Tobago

Typical Web applications contain some form of left navigation bar. This page describes how to create a simple tree like collapsible navigation bar using Apache MyFaces Tobago.

Requirements

At this time please complete a simple Hello World application (previous link) before proceeding…

For the site navigation we will use the tc:tree element to display a collapsible table.

Implementing the Model

The code listed below sets up a static Tree Structure that represents the left navigation bar. Each tree element will specify the key and the “outcome”. The key is used to find the Text to display for that item. And the “outcome” will be used by JSF to identify what page to forward the request to.

package com.test;

import java.util.Enumeration;

import javax.faces.context.FacesContext;
import javax.swing.tree.DefaultMutableTreeNode;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.myfaces.tobago.context.ResourceManagerUtil;
import org.apache.myfaces.tobago.model.TreeState;

/**
 * The following constructs a very basic static navigation bar.
 */
public class Navigation {

  private static final Log LOG = LogFactory.getLog(Navigation.class);

  private DefaultMutableTreeNode tree;

  private TreeState state;

  public Navigation() {

    tree = new DefaultMutableTreeNode(new Node("test", "Root", null));

    DefaultMutableTreeNode home = new DefaultMutableTreeNode(new Node("test", "home", "pages/home"));
    DefaultMutableTreeNode products = new DefaultMutableTreeNode(new Node("test", "products", "pages/products"));

    DefaultMutableTreeNode portableAudio = new DefaultMutableTreeNode(new Node("test", "portable_audio", "pages/portable_audio"));
    portableAudio.add(new DefaultMutableTreeNode(new Node("test", "cd", "pages/cd")));
    portableAudio.add(new DefaultMutableTreeNode(new Node("test", "mp3", "pages/mp3")));

    DefaultMutableTreeNode books = new DefaultMutableTreeNode(new Node("test", "books", "pages/books"));

    DefaultMutableTreeNode newReleases = new DefaultMutableTreeNode(new Node("test", "new_releases", "pages/new_releases"));

    newReleases.add(new DefaultMutableTreeNode(new Node("test", "action_adventure","pages/action_advendure")));

    books.add(newReleases);

    products.add(portableAudio);
    products.add(books);

    tree.add(home);
    tree.add(products);

    state = new TreeState();
    state.expand(tree, 1);
    state.setMarker(home);
  }

  public String navigate() {
    Node selected = (Node) state.getMarker().getUserObject();
    return selected.getOutcome();
  }

  public void updateMarker(String viewId) {
    Enumeration enumeration = tree.depthFirstEnumeration();
    while (enumeration.hasMoreElements()) {
      DefaultMutableTreeNode maybeMarker = ((DefaultMutableTreeNode) enumeration.nextElement());
      Node node = (Node) maybeMarker.getUserObject();
      if (node.getOutcome() != null && viewId.contains(node.getOutcome())) {
        state.setMarker(maybeMarker);
        break;
      }
    }
  }

  public DefaultMutableTreeNode getTree() {
    return tree;
  }

  public void setTree(DefaultMutableTreeNode tree) {
    this.tree = tree;
  }

  public TreeState getState() {
    return state;
  }

  public void setState(TreeState state) {
    this.state = state;
  }

  public String gotoFirst() {
    DefaultMutableTreeNode first = tree.getNextNode();
    state.setMarker(first);
    return ((Node) first.getUserObject()).getOutcome();
  }

  public String gotoPrevious() {
    DefaultMutableTreeNode previousNode = state.getMarker().getPreviousNode();
    if (previousNode != null) {
      state.setMarker(previousNode);
      return ((Node) previousNode.getUserObject()).getOutcome();
    }
    return null;
  }

    public String gotoNext() {
    DefaultMutableTreeNode nextNode = state.getMarker().getNextNode();
    if (nextNode != null) {
      state.setMarker(nextNode);
      return ((Node) nextNode.getUserObject()).getOutcome();
    }
    return null;
  }

  public boolean isFirst() {
    return state.getMarker().getPreviousNode().isRoot();
  }

  public boolean isLast() {
    return state.getMarker().getNextNode() == null;
  }

  public static class Node {

    private String title;
    private String id;
    private String outcome;

    public Node(String resourceBundle, String key, String outcome) {
      this.title = ResourceManagerUtil.getProperty(
          FacesContext.getCurrentInstance(), resourceBundle, key);
      this.id = key;
      this.outcome = outcome;
    }

    public String getTitle() {
      return title;
    }

    public void setTitle(String title) {
      this.title = title;
    }

    public String getId() {
      return id;
    }

    public void setId(String id) {
      this.id = id;
    }

    public String getOutcome() {
      return outcome;
    }

    public void setOutcome(String outcome) {
      this.outcome = outcome;
    }
  }
}

Create the resource bundle file

need to create a resource bundle file in

src/main/webapp/tobago-resource/html/standard/standard/property/test.properties.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
	<entry key="neeraj_name">Neeraj Verma</entry>
	<entry key="home">Home</entry>
	<entry key="products">Products</entry>
	<entry key="portable_audio">Portable Audio</entry>
	<entry key="cd">CD</entry>
	<entry key="mp3">MP3</entry>
	<entry key="books">Books</entry>
	<entry key="new_releases">New Releases</entry>
	<entry key="action_adventure">Action Adventure</entry>
</properties>

Modify Navigation Rules

The next step is to setup the navigation rules. Make the changes highlighted below to the faces-config.xml file. The navigation rules section describes how to forward the user to the next page when they click on the navigation bar. The navigation bar contains the “outcome” for each entry.

src/main/webapp/WEB-INF/faces-config.xml

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE faces-config PUBLIC
  "-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.1//EN"
  "http://java.sun.com/dtd/web-facesconfig_1_1.dtd">

<faces-config>

  <application>
    <locale-config>
      <default-locale>en</default-locale>
      <supported-locale>de</supported-locale>
      <supported-locale>de_DE</supported-locale>
      <supported-locale>de_AT</supported-locale>
      <supported-locale>de_CH</supported-locale>
    </locale-config>
  </application>

	<navigation-rule>
		<navigation-case>
			<from-outcome>pages/home</from-outcome>
			<to-view-id>pages/home.jsp</to-view-id>
		</navigation-case>
		<navigation-case>
			<from-outcome>pages/products</from-outcome>
			<to-view-id>pages/products.jsp</to-view-id>
		</navigation-case>
		<navigation-case>
			<from-outcome>pages/portable_audio</from-outcome>
			<to-view-id>pages/portable_audio.jsp</to-view-id>
		</navigation-case>
		<navigation-case>
			<from-outcome>pages/cd</from-outcome>
			<to-view-id>pages/cd.jsp</to-view-id>
		</navigation-case>
	</navigation-rule>

  <managed-bean>
    <managed-bean-name>navigation</managed-bean-name>
    <managed-bean-class>com.test.Navigation</managed-bean-class>
    <managed-bean-scope>session</managed-bean-scope>
  </managed-bean>
</faces-config>

Implement the Target Pages

As you can see above each “outcome” leads to a different jsp page. For that reason please take the time to create the 4 jsp’s listed above

  1. src/main/webapp/pages/home.jsp
  2. src/main/webapp/pages/products.jsp
  3. src/main/webapp/pages/portable_audio.jsp
  4. src/main/webapp/pages/cd.jsp

You can put anything you want in those JSP’s “Test 123 [jsp name]” whatever…

Implement the page

Modify the helloWorld.jsp page to look like this: The lines highlighted below are the differences from the file posted in my previous article.

<%@ taglib uri="http://myfaces.apache.org/tobago/component" prefix="tc" %>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>

<tc:loadBundle basename="test" var="testBundle"/>

<f:view>
  <tc:page>
    <f:facet name="layout">
      <tc:gridLayout/>
    </f:facet>
    <tc:tree value="#{navigation.tree}"
             mode="menu"
             id="nav"
             nameReference="userObject.title"
             idReference="userObject.id"
             tipReference="userObject.title"
             state="#{navigation.state}"
             showIcons="false"
             showJunctions="false"
             showRoot="false">
      <f:facet name="treeNodeCommand">
        <tc:link action="#{navigation.navigate}" immediate="true"/>
      </f:facet>
    </tc:tree>

  </tc:page>
</f:view>

Test the Application

Start the application in jetty.

mvn jetty:run

Navigate to http://localhost:8080/

You should see a navigation menu smack in top of the html page. It will most likely take up the whole browser window. Next we need to create a layout and place this navigation bar on the left of the page. Please read my next article where I describe how this is done.

That’s All for now…

31
Jan
10

Page Layout with Apache MyFaces Tobago

This page describes the process of setting up a reusable layout for your web application.

Requirements

This page will continue where we left off. Therefore if you have not done so already please follow the instructions to create a simple hello world application + navigation bar.

In this page we will create a banner and footer pages. We will take the navigation bar and place it on the left side of the page. In the body section we will display a simple calendar component.

Tag File

src/main/webapp/WEB-INF/tags/layout/overview.tag

<%@ taglib uri="http://myfaces.apache.org/tobago/component" prefix="tc" %>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<f:view >
  <tc:page applicationIcon="icon/favicon.ico" label="Test Page Title" id="page" width="1000px" height="750px">

    <jsp:include page="/menu.jsp" />

    <f:facet name="layout">
      <tc:gridLayout border="0" columns="*;4*"
        margin="10px" rows="100px;*;fixed"  />
    </f:facet>

    <tc:cell spanX="2">
      <jsp:include page="/header.jsp"/>
    </tc:cell>

    <tc:cell spanY="2" >
      <jsp:include page="/navigation.jsp"/>
    </tc:cell>

    <tc:cell>
      <jsp:doBody/>
    </tc:cell>

    <tc:cell>
      <jsp:include page="/footer.jsp" />
    </tc:cell>

  </tc:page>
</f:view>

Header

src/main/webapp/header.jsp

<%@ taglib uri="http://myfaces.apache.org/tobago/component" prefix="tc" %>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<f:subview id="header" >

  <tc:panel>
    <tc:out value="This is the header..."/>
  </tc:panel>
</f:subview>

Menu

src/main/webapp/menu.jsp

<%@ taglib uri="http://myfaces.apache.org/tobago/component" prefix="tc" %>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<f:subview id="menu" >
<!-- This is a place holder for the top menu -->
</f:subview>

Footer

src/main/webapp/footer.jsp

<%@ taglib uri="http://myfaces.apache.org/tobago/component" prefix="tc" %>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<f:subview id="footer" >

  <tc:panel>
    <tc:out value="This is the footer..."/>
  </tc:panel>
</f:subview>

Navigation

src/main/webapp/navigation.jsp

<%@ taglib uri="http://myfaces.apache.org/tobago/component" prefix="tc" %>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<f:subview id="navigation" >

  <tc:panel>
		<tc:box label="Navigation">
			<tc:tree value="#{navigation.tree}" mode="menu" id="nav"
				nameReference="userObject.title" idReference="userObject.id"
				tipReference="userObject.title" state="#{navigation.state}"
				showIcons="false" showJunctions="false" showRoot="false">
				<f:facet name="treeNodeCommand">
					<tc:link action="#{navigation.navigate}" immediate="true" />
				</f:facet>
			</tc:tree>
		</tc:box>
  </tc:panel>
</f:subview>

Actual Page

This page uses the layout we defined above. It specifies only the body of the page. This is typically where we will put the components that interact with the user.
src/main/webapp/helloLayout.jsp

<%@ taglib uri="http://myfaces.apache.org/tobago/component" prefix="tc" %>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<%@ taglib tagdir="/WEB-INF/tags/layout" prefix="layout" %>
<%@ taglib uri="http://myfaces.apache.org/tobago/extension" prefix="tx" %>

<layout:overview>
  <jsp:body>
  	<tc:out value="This is the main body..."/>
  	<tc:panel >
  		<f:facet name="layout">
  			<tc:gridLayout rows="fixed;*" columns="fixed;*"/>
  		</f:facet>
  	<tc:box label="calendar component" height="225" id="box">
  		<tc:calendar />
  	</tc:box>
  	<tc:cell id="abc"/>
  	<tc:cell id="abcd"/>
  	</tc:panel>
  </jsp:body>
</layout:overview>

Test the application

Start jetty and test the application by navigating to:

http://localhost:8080/tobagoTestWeb/faces/helloLayout.jsp

That’s all for now…

31
Jan
10

Using Resource Bundles with Apache Tobago

This page describes the steps necessary to use resource bundles in your Apache Tobago based applications.

This page builds upon the Hello World Application and modifies it to use resource bundles to display messages. If you have not done so already please click on the above link to complete the hello world application before proceeding.

To allow tobago to use property files add the following to:

src/main/webapp/WEB-INF/tobago-config.xml

<resource-dir>tobago-resource</resource-dir>

The above specifies that the tobago-resource directory contains files used for application display. The resource directory follows a standard directory structure. This allow for customization for different themes and locales.

src/main/webapp/tobago-resource/html/standard/standard/property contains the properties files.

add the following
src/main/webapp/tobago-resource/html/standard/standard/property/test.properties.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
 <entry key="neeraj_name">Neeraj Verma</entry>
 </properties>

src/main/webapp/hello.jsp should look like this. The lines highlighted below indicate where the changes were made.

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<%@ taglib uri="http://myfaces.apache.org/tobago/component" prefix="tc" %>
<%@ taglib uri="http://myfaces.apache.org/tobago/extension" prefix="tx" %>

<tc:loadBundle basename="test" var="testBundle"/>  

<f:view>
  <tc:page>
    <f:facet name="layout">
      <tc:gridLayout/>
    </f:facet>
    <tc:out value="Hello World"/>
    <tc:out escape="false" value="#{testBundle.neeraj_name}" />
  </tc:page>
</f:view>

Save and run the page in jetty

mvn jetty:run

Navigate to http://localhost:8080/

You should see hello world up top and somewhere in the middle you will see my name.

That’s all for now.

28
Jan
10

Hello World with Apache Tobago

This page describes the process of setting up and running a Hello World Application using Apache MyFaces Tobago. This is similar to the blank application that is published by the apache group however unlike the blank application this one is totally independent. It is a standalone application that will run on its own without any complicated parent project relationships. Using this page you should be up and running in about 20 minutes.

Requirements

  • A basic understanding of JSF
  • Maven installed and configured

Overview

The hello world application consists of the following files

  1. pom.xml file that describes the project
  2. web.xml that describes the application
  3. faces-config.xml – very basic faces configuration file
  4. tobago-config.xml – very basic tobago configuration file
  5. hello.jsp – The actual hello world jsp that prints the message at “/faces/hello.jsp”

Generate the project

We start with the pom.xml file.

mvn archetype:generate -DarchetypeArtifactId=maven-archetype-webapp

groupId: com.test
artifactId: tobagoTestWeb
Leave the rest defaults

Import it into Eclipse

Import the project by clicking import existing project into workspace.

Modify the POM.xml file

Once the project is built make sure the poml.xml file looks like this:
I know the pom.xml file is a bit large but “It is what it is…”
pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>test</groupId>
	<artifactId>jsfTestWeb</artifactId>
	<packaging>war</packaging>
	<version>1.0-SNAPSHOT</version>
	<name>jsfTestWeb Maven Webapp</name>
	<url>http://maven.apache.org</url>
	<dependencies>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>3.8.1</version>
			<scope>test</scope>
		</dependency>

<!-- This is tobago itself + 1 theme -->
		<dependency>
			<groupId>org.apache.myfaces.tobago</groupId>
			<artifactId>tobago-core</artifactId>
			<version>1.0.24</version>
			<exclusions>
				<exclusion>
					<groupId>sun.jdk</groupId>
					<artifactId>tools</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
		<dependency>
			<groupId>org.apache.myfaces.tobago</groupId>
			<artifactId>tobago-theme-speyside</artifactId>
			<version>1.0.24</version>
		</dependency>

<!-- Start Tobago dependencies -->
		<dependency>
			<groupId>commons-beanutils</groupId>
			<artifactId>commons-beanutils</artifactId>
			<version>1.7.0</version>
		</dependency>
		<dependency>
			<groupId>commons-collections</groupId>
			<artifactId>commons-collections</artifactId>
			<version>3.1</version>
		</dependency>
		<dependency>
			<groupId>commons-digester</groupId>
			<artifactId>commons-digester</artifactId>
			<version>1.8</version>
		</dependency>
		<dependency>
			<groupId>commons-fileupload</groupId>
			<artifactId>commons-fileupload</artifactId>
			<version>1.2</version>
		</dependency>
		<dependency>
			<groupId>commons-io</groupId>
			<artifactId>commons-io</artifactId>
			<version>1.1</version>
		</dependency>
		<dependency>
			<groupId>commons-lang</groupId>
			<artifactId>commons-lang</artifactId>
			<version>2.1</version>
		</dependency>
		<dependency>
			<groupId>commons-logging</groupId>
			<artifactId>commons-logging</artifactId>
			<version>1.1</version>
		</dependency>
<!-- End Tobago dependencies -->
<!-- Start JSTL -->
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>jstl</artifactId>
			<version>1.1.2</version>
		</dependency>
		<dependency>
			<groupId>taglibs</groupId>
			<artifactId>standard</artifactId>
			<version>1.1.2</version>
		</dependency>
<!-- End JSTL -->

<!-- Start JSF and MyFaces -->
		<dependency>
			<groupId>org.apache.myfaces.core</groupId>
			<artifactId>myfaces-api</artifactId>
			<version>1.1.7</version>
		</dependency>
		<dependency>
			<groupId>org.apache.myfaces.core</groupId>
			<artifactId>myfaces-impl</artifactId>
			<version>1.1.7</version>
		</dependency>
<!-- End JSF and MyFaces -->

	</dependencies>
	<build>
		<finalName>jsfTestWeb</finalName>
		<plugins>
			<plugin>
				<groupId>org.mortbay.jetty</groupId>
				<artifactId>jetty-maven-plugin</artifactId>
				<version>7.0.0.v20091005</version>
				<configuration>
					<scanIntervalSeconds>2</scanIntervalSeconds>
				</configuration>
			</plugin>
		</plugins>
	</build>
</project>

web.xml

that describes the application
src/main/webapp/WEB-INF/web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
    version="2.4">

  <display-name>Hello World Tobago Application</display-name>
  <context-param>
    <param-name>javax.faces.PARTIAL_STATE_SAVING_METHOD</param-name>
    <param-value>false</param-value>
  </context-param>  

  <filter>
    <filter-name>multipartFormdataFilter</filter-name>
    <filter-class>org.apache.myfaces.tobago.webapp.TobagoMultipartFormdataFilter</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>multipartFormdataFilter</filter-name>
    <url-pattern>/faces/*</url-pattern>
  </filter-mapping>

<!--  workaround (e.g. for Oracle AS 10.1.2.0.0)-->
  <listener>
    <listener-class>org.apache.myfaces.tobago.webapp.TobagoServletContextListener</listener-class>
  </listener>

  <!-- servlet -->

  <servlet>
    <servlet-name>FacesServlet</servlet-name>
    <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
    <load-on-startup>3</load-on-startup>
  </servlet>

  <servlet>
    <servlet-name>ResourceServlet</servlet-name>
    <servlet-class>org.apache.myfaces.tobago.servlet.ResourceServlet</servlet-class>
  </servlet>

  <!-- servlet-mapping -->

  <servlet-mapping>
    <servlet-name>FacesServlet</servlet-name>
    <url-pattern>/faces/*</url-pattern>
  </servlet-mapping>

  <servlet-mapping>
    <servlet-name>ResourceServlet</servlet-name>
    <url-pattern>/org/apache/myfaces/tobago/renderkit/*</url-pattern>
  </servlet-mapping>

  <!-- The Usual Welcome File List -->
  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>

</web-app>

faces-config.xml

very basic faces configuration file
src/main/webapp/WEB-INF/faces-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE faces-config PUBLIC
  "-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.1//EN"
  "http://java.sun.com/dtd/web-facesconfig_1_1.dtd">

<faces-config>
  <application>
    <locale-config>
      <default-locale>en</default-locale>
      <supported-locale>de</supported-locale>
      <supported-locale>de_DE</supported-locale>
      <supported-locale>de_AT</supported-locale>
      <supported-locale>de_CH</supported-locale>
    </locale-config>
  </application>
</faces-config>

tobago-config.xml

src/main/webapp/WEB-INF/tobago-config.xml
very basic tobago configuration file

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE tobago-config PUBLIC
    "-//The Apache Software Foundation//DTD Tobago Config 1.0//EN" "tobago-config_1_0.dtd">

<tobago-config>
  <theme-config>
    <default-theme>speyside</default-theme>
  </theme-config>
</tobago-config>

hello.jsp

And Lastly the most important file in the whole application… The actual hello world jsp that prints the message when you navigate to: “/faces/hello.jsp”
src/main/webapp/hello.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<%@ taglib uri="http://myfaces.apache.org/tobago/component" prefix="tc" %>
<%@ taglib uri="http://myfaces.apache.org/tobago/extension" prefix="tx" %>
<f:view>
  <tc:page>
    <f:facet name="layout">
      <tc:gridLayout/>
    </f:facet>
    <tc:out value="Hello World"/>
  </tc:page>
</f:view>

index.jsp

src/main/webapp/index.jsp

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<html>
<head>
<title>Your Page Title</title>
<meta http-equiv="REFRESH" content="0;url=<c:url value='/faces/hello.jsp'/>"></HEAD>
<BODY>
Optional page text here.
</BODY>
</HTML>

Run the Application

At this time you can run the application in Jetty.

mvn jetty:run

navigate to http://localhost:8080/

Limitations

  • JSF only supports one renderkit (renderkitId) per page (f:view). Because Tobago has it’s own renderkitId you cannot use any non-Tobago components that need a renderer. This means that you will not be able to use tomahawk components on tobago pages.

That’s all for now…

03
Jan
10

Using JSF to change Log4j Log Levels at Runtime

This page will show you how you can easily code a JSF form to inspect the current log4j logging levels and allow the user to change them at runtime. The changes take effect immediately. The complete implementation should take about 45 minutes.

Requirements

Configuration

Just add the following to the pom.xml file.

    <dependency>
    	<groupId>commons-logging</groupId>
    	<artifactId>commons-logging</artifactId>
    	<version>1.1</version>
    </dependency>
    <dependency>
	<groupId>log4j</groupId>
	<artifactId>log4j</artifactId>
	<version>1.2.15</version>
	<exclusions>
		<exclusion>
			<groupId>javax.mail</groupId>
			<artifactId>mail</artifactId>
		</exclusion>
		<exclusion>
			<groupId>javax.jms</groupId>
			<artifactId>jms</artifactId>
		</exclusion>
		<exclusion>
			<groupId>com.sun.jdmk</groupId>
			<artifactId>jmxtools</artifactId>
		</exclusion>
		<exclusion>
			<groupId>com.sun.jmx</groupId>
			<artifactId>jmxri</artifactId>
		</exclusion>
		<exclusion>
			<groupId>javax.activation</groupId>
			<artifactId>activation</artifactId>
		</exclusion>
	</exclusions>
</dependency>
	<dependency>
		<groupId>org.apache.myfaces.tomahawk</groupId>
		<artifactId>tomahawk</artifactId>
		<version>1.1.9</version>
	</dependency>

and regenerate the eclipse / idea project.

mvn eclipse:clean eclipse:eclipse

Refresh the eclipse project.

Put the following file into the

src/main/resources/log4j.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">

<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
	<appender name="A1" class="org.apache.log4j.ConsoleAppender">
		<param name="Target" value="System.out" />
		<layout class="org.apache.log4j.PatternLayout">
			<param name="ConversionPattern" value="%d %-5p %c - %m%n" />
		</layout>
	</appender>

	<root>
		<priority value="fatal" />
		<appender-ref ref="A1" />
	</root>
	<logger name="org.vtechfw">
		<level value="debug" />
		<appender-ref ref="A1" />
	</logger>
	<logger name="org.vtechfw.utils">
		<level value="trace" />
		<appender-ref ref="A1" />
	</logger>
</log4j:configuration>

Managed Logger

This is just a wrapper bean to help configure the loggers.

src/main/java/org/vtechfw/utils/logging/ManagedLogger.java

package org.vtechfw.utils.logging;

import org.apache.log4j.Level;
import org.apache.log4j.Logger;

/**
 * Wrapper to the Log4j Logger to help configure the logging levels.
 */
public class ManagedLogger {
	private Logger logger;

	
	public ManagedLogger() {
		super();
	}

	public ManagedLogger(Logger logger) {
		super();
		this.logger = logger;
	}

	public void setLogger(Logger logger) {
		this.logger = logger;
	}

	public final String getName() {
		return logger.getName();
	}

	public Logger getLogger() {
		return logger;
	}
	public Level getLevel() {
		return logger.getEffectiveLevel();
	}
	public void setLevel(Level level) {
		if(level!=null) {			
			logger.setLevel(level);
		}
	}
}

Managed Bean

Create the following class in the src/main/java/org/vtechfw/utils/logging folder

Log4jManager.java

package org.vtechfw.utils.logging;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.List;

import javax.faces.component.html.HtmlDataTable;

import org.apache.log4j.Level;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;

public class Log4jManager {
	// used for regular logging.
	protected Logger logger = Logger.getLogger(getClass());
	private Logger currentLogger;
	private String logLevel;
	private HtmlDataTable htmlDataTable;

	@SuppressWarnings("unchecked")
	public List<Logger> getLoggerList() {
		Enumeration<Logger> enumeration = LogManager.getCurrentLoggers();
		List<Logger> list = Collections.<Logger>list(enumeration);
		list.add(LogManager.getRootLogger());

		Collections.sort(list, new Comparator<Logger>() {

			@Override
			public int compare(Logger o1, Logger o2) {
				String o1Name = o1.getName();
				String o2Name = o2.getName();

				return o1Name.compareTo(o2Name);
			}
		});

		return list;
	}

	public List<ManagedLogger> getManagedLoggerList() {
		List<Logger> list = getLoggerList();
		List<ManagedLogger> managedLoggerList = new ArrayList<ManagedLogger>();
		for (Logger logger : list) {
			ManagedLogger e = new ManagedLogger(logger);
			managedLoggerList.add(e);
		}
		return managedLoggerList;
	}

	public Logger getRootLogger() {
		return LogManager.getRootLogger();
	}

	public void setCurrentLogger(Logger currentLogger) {
		this.currentLogger = currentLogger;
	}

	public Logger getCurrentLogger() {
		return currentLogger;
	}

	public void setHtmlDataTable(HtmlDataTable htmlDataTable) {
		this.htmlDataTable = htmlDataTable;
	}

	public HtmlDataTable getHtmlDataTable() {
		return htmlDataTable;
	}

	public String setLoggerLevel() {

		// find out the currently selected logger.
		currentLogger = (Logger)htmlDataTable.getRowData();
		Level level = Level.toLevel(logLevel);
		currentLogger.setLevel(level);
		return "refresh";
	}

	public void setLogLevel(String logLevel) {
		this.logLevel = logLevel;
	}

	public String getLogLevel() {
		return logLevel;
	}
}

Converter

The following class converts between the text representation of the Logger and the actual logger instance.

src/main/java/org/vtechfw/utils/logging/LevelConverter.java

package org.vtechfw.utils.logging;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.convert.ConverterException;

import org.apache.log4j.Level;

public class LevelConverter implements Converter {

	@Override
	public Object getAsObject(FacesContext context, UIComponent component,
			String value) throws ConverterException {
		Level level = null;

		if(value==null || "".equals(value)) {
			level = null;
		} else {
			level = Level.toLevel(value);
		}
		return level;
	}

	@Override
	public String getAsString(FacesContext context, UIComponent component,
			Object value) throws ConverterException {
		if(value instanceof String) {
			return (String)value;
		}
		if(value instanceof Level) {
			Level level = (Level) value;
			return level.toString();
		}
		return "parent";
	}

}

JSP file

This is where all the magic happens. Its a short file that hopefully is self explanatory.

src/main/webapp/log4jManager.jsp

<%@taglib uri="http://myfaces.apache.org/tomahawk" prefix="t"%><%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="f"  uri="http://java.sun.com/jsf/core"%>
<%@ taglib prefix="h"  uri="http://java.sun.com/jsf/html"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Log4j Management Console</title>
</head>
<body>
<f:view>
	<h:form id="levelconfigform">
	<h3>Log Level Configuration Util (Log4j)</h3>
		<t:dataTable border="1" value="#{log4jManager.managedLoggerList}" var="logger" binding="#{log4jManager.htmlDataTable}" sortable="true">
			<h:column id="column1">
				<f:facet name="header">
					<h:outputText value="Name"></h:outputText>
				</f:facet>
				<h:outputText value="#{logger.name}"></h:outputText>
			</h:column>
			<h:column id="column3">
				<f:facet name="header">
					<h:outputText value="Level"></h:outputText>
				</f:facet>
				<h:selectOneMenu id="logLevel" value="#{logger.level}" onchange="submit();">
					<f:selectItem itemLabel="ALL" itemValue="ALL"/>
					<f:selectItem itemLabel="TRACE" itemValue="TRACE"/>
					<f:selectItem itemLabel="DEBUG" itemValue="DEBUG"/>
					<f:selectItem itemLabel="INFO" itemValue="INFO"/>
					<f:selectItem itemLabel="WARN" itemValue="WARN"/>
					<f:selectItem itemLabel="ERROR" itemValue="ERROR"/>
					<f:selectItem itemLabel="FATAL" itemValue="FATAL"/>
					<f:selectItem itemLabel="OFF" itemValue="OFF"/>
				</h:selectOneMenu>
			</h:column>
		</t:dataTable>
	</h:form>
</f:view>
</body>
</html>

Faces Config File

I have a lot of junk in mine so I am not going to include the whole thing. Here are the important sections.

src/main/webapp/WEB-INF/faces-config.xml

	<converter>
		<display-name>Level Converter</display-name>
		<converter-for-class>org.apache.log4j.Level</converter-for-class>
		<converter-class>org.vtechfw.utils.logging.LevelConverter</converter-class>
	</converter>

	<managed-bean>
		<managed-bean-name>log4jManager</managed-bean-name>
		<managed-bean-class>org.vtechfw.utils.logging.Log4jManager</managed-bean-class>
		<managed-bean-scope>request</managed-bean-scope>
	</managed-bean>

	<navigation-rule>
	        <display-name>log4jManager</display-name>
		<from-view-id>/log4jManager.jsp</from-view-id>
		<navigation-case>
			<from-outcome>refresh</from-outcome>
			<to-view-id>/log4jManager.jsp</to-view-id>
		</navigation-case>
	</navigation-rule>

Running the project

Drop to the command line and type in the following:

mvn jetty:run

Using your browser navigate to the following URL:

http://localhost:8080/log4jManager.jsf

You will be presented with a table of loggers and their associated level. The table is sortable using the table headings. Using the drop down you can change the log level of each logger. The changes take effect immediately.

The only bug with this jsf component is that when you select a logging level using the drop down the logging level of the item adjacent to it changes. But it starts working as expected with the second attempt. Having figured out what the problem is. If you guys figure it out let me know by commenting below.

That’s all for now!

02
Jan
10

User Interactions with JSF Forms

This page is about JSF Managed Beans and Forms. Its about how to break up the form submission process to handle large amount of fields. We will also cover how to avoid having a Flat Bean with only String based attributes. Instead we will create create a domain rich bean that will be easy to understand and maintain in the long run. We will cover techniques of creating/reading/updating/deleting these sub-beans. We will also cover a technique of cleaning up session beans once they are no longer needed.

  • Accept and validate large amount of data in multiple steps wizard approach.
  • Managed Beans and how handle sub-entities
  • Cleaning up session after completing a workflow

Managed Beans

JSF Managed beans hold data that is submitted by the user. Each field is an attribute of the managed bean. The submit buttons on the form can execute methods on the managed bean. The methods don’t need any arguments since the managed bean already contains the input elements of the form. Managed beans are not shared between users of the web application. Managed Beans have different types of scopes but the request and session scope are the most popular.

Large Forms

JSF forms are represented by Managed Beans. Sometimes forms get quite large and should be broken up into smaller chunks. Forms can be broken up into multiple pages by setting the scope to session and just having the pages bind to only a part of the Managed Bean. Provide “previous” and “next” buttons for user navigation. The “previous” buttons should not validate the form. You can do this by specifying immediate=’true’ in the command button. In general if you don’t want the page to get validated you can specify this attribute. Since validation is handled on a page by page basis you don’t need to worry about the step number like you did in the struts 1 framework.

Adding Sub-Beans to the Managed Bean

Another method of dealing with large forms is to break the backing bean into smaller chunks. This is where sub-beans and the Java Collections framework comes in handy. A typical case for this is when you have a bean that holds the user’s entire annual tax return. There is an area in the form where you specify zero one or many dependants. This is a good opportunity to use a Dependant Bean to hold each dependant and have a dependentList that is responsible for holding a list of them. All this can be maintained by the main tax Return Managed Bean. (see below)

	private Dependant dependant = new Dependant();
	private List<Dependant> dependantList = Dependant.getSample();

As you can see above we have a dependant, and a dependantList. One holds the information that was submitted by the user and the other holds the collection of dependents that was submitted so far.

One of the effective methods of allowing the user to add sub-beans is by presenting the user with a table and an add button. The table would contain a summary of already present sub-beans and allow the user to add more. At this point there are two options. 1) present the user with another page and have them enter the information or 2) have them enter the information on a popup window or similar. Once the user submits, validate and add the information to the main Bean. In this model the sub-bean plays the role of the data structure. None of the action methods are executed on the sub-bean.

When the user clicks add we do the following:

public String addDependant() {
	System.out.println("Adding dependant to list.");
	dependantList.add(dependant);
	// assign a new dependent object for the next submission.
	dependant = new Dependant();
	return "success";
}

The highlighted line is necessary since we are adding a reference to the user’s submitted form we don’t want the form that is subitted after this one to have the same reference. Hence we instantiate a new Dependent object for the next form submission.

Reading / Editing / Deleting Sub Entities

The system needs to know what the user clicked. The HtmlDataTable class is an extension to the dataTable component that the user is interacting with. When the user clicks a link, the component knows what data item the link was clicked for. The action handling method of the main bean needs to get the object and inspect it for further processing. In order for all this to work we need to “bind” the dataTable with the HTMLDataTable class in the managed Bean.

Binding to HTMLDataTable

The “binding” attribute is used to bind the dataTable to the HtmlDataTable attribute of the Managed Bean.

<h:dataTable border="1" value="#{taxReturn.dependantList}"  var="dependantrow" binding="#{taxReturn.dependantTable}">
<!--jsf column elements go here.-->
</h:dataTable>

In the TaxReturn has the following defined as an attribute.

private HtmlDataTable dependantTable;

Action Column

<h:column id="action_column">
<f:facet name="header"><h:outputText id="text6" value="action"></h:outputText></f:facet>
<h:commandLink id="remove" action="#{taxReturn.removeDependant}" immediate="true">
<h:outputText value="remove"></h:outputText>
</h:commandLink>
</h:column>

In the following code example shows the Dependant Bean being removed from a list of Dependant Objects.

public String removeDependant() {
	System.out.println("Removing dependant");
	Dependant dependantToRemove = (Dependant) dependantTable.getRowData();
	dependantList.remove(dependantToRemove);
	return "refresh";
}

Note: it is important to implement the equals() and hashCode() in order for this to work. Since the “business” key is used to identify if the objects are equal we don’t need to evaluate each and every field to determine equality. See my other page on implementing equals and hashcode.
Here is the equals and hashcode implementation.

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((ssn == null) ? 0 : ssn.hashCode());
		return result;
	}
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Dependant other = (Dependant) obj;
		if (ssn == null) {
			if (other.ssn != null)
				return false;
		} else if (!ssn.equals(other.ssn))
			return false;
		return true;
	}

JSF Managed Bean Session Cleanup

Once a JSF workflow is complete and the managed bean is in the session it should be cleaned up. Otherwise the object will stay around until the session expires. On a high volume website this can take up precious memory. To cleanup the session the Managed bean should execute the following.

public String processAndCleanup() {
	ExternalContext externalContext = FacesContext.getCurrentInstance().getExternalContext();
	Map map = externalContext.getSessionMap();
	map.remove("taxReturn"); // the name of the jsf managed bean.
	return "success";
}

The only drawback with this approach I see, and it might not be a big deal is that the Managed Bean is aware of the ExternalContext which is a JSF artifact. If you have a better way to do this please comment below.

Snoop Servlet

During development you may verify that the bean is out of the session by viewing the output of the SnoopServlet.

Thats all for now!

30
Dec
09

Listbox Picker Using JSF and Tomahawk

This page is about creating an HTML Switchlist or Picker using JSF and the Tomahawk library. We will start by creating a basic multiselect listbox and then convert that to a switchlist. If you have Maven installed you may follow along. The full program should not take more then 20 minutes to complete and have up and running.

Requirements

  • Maven
  • Eclipse or IDEA Java IDE
  • That’s about it :)

Step 1: Project Setup

cd to your workspace directory and create a new project using the maven archetype “maven-archetype-webapp”.

I did it the following way

mvn archetype:generate -DarchetypeArtifactId=maven-archetype-webapp

Answer the rest of the questions like this… hit enter for the defaults.

Define value for groupId: : com.test 
Define value for artifactId: : testSwitchList
Define value for version:  1.0-SNAPSHOT: : 
Define value for package:  com.test: : 
Confirm properties configuration:
groupId: com.test
artifactId: testSwitchList
version: 1.0-SNAPSHOT
package: com.test
 Y: : 

Make sure you Answer the highlighted items exactly as above. The rest of the page depends on this.

cd testSwitchList; mvn eclipse:clean eclipse:eclipse (if you have IDEA then mvn idea:idea)

Import the project Into your IDE.

The pom.xml file should look like this…

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.test</groupId>
  <artifactId>testSwitchList</artifactId>
  <packaging>war</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>testSwitchList Maven Webapp</name>
  <url>http://maven.apache.org</url>
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
<dependency>
    <groupId>org.apache.myfaces.core</groupId>
    <artifactId>myfaces-api</artifactId>
    <version>1.2.8</version>
</dependency>

<dependency>
    <groupId>org.apache.myfaces.tomahawk</groupId>
    <artifactId>tomahawk</artifactId>
    <version>1.1.9</version>
</dependency>

<dependency>
    <groupId>org.apache.myfaces.core</groupId>
    <artifactId>myfaces-impl</artifactId>
    <version>1.2.8</version>
</dependency>
<dependency>
    <groupId>org.apache.geronimo.specs</groupId>
    <artifactId>geronimo-servlet_2.5_spec</artifactId>
    <version>1.2</version>
    <type>jar</type>
    <scope>provided</scope>
</dependency>
  </dependencies>
  <build>
    <finalName>testSwitchList</finalName>
<plugins>
    <plugin>
        <groupId>org.mortbay.jetty</groupId>
        <artifactId>jetty-maven-plugin</artifactId>
                <version>7.0.0pre1</version>
        <configuration>
            <scanIntervalSeconds>2</scanIntervalSeconds>
        </configuration>
    </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.0.2</version>
                <configuration>
                    <source>1.5</source>
                    <target>1.5</target>
                </configuration>
            </plugin>
</plugins>
  </build>
</project>

Create a folder named java in the src/main directory of your project. By default this archetype does not create one.

Regenerate the eclipse or idea project (ex. mvn eclipse:clean eclipse:eclipse) and refresh the project in your IDE.

Step 2: Configuration

Replace the web.xml file in the src/main/webapp/WEB-INF folder with this:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
	id="WebApp_ID" version="2.5">
	<display-name>Archetype Created Web Application</display-name>

	<servlet>
		<servlet-name>Faces Servlet</servlet-name>
		<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
		<load-on-startup>1</load-on-startup>
	</servlet>

	<servlet-mapping>
		<servlet-name>Faces Servlet</servlet-name>
		<url-pattern>*.jsf</url-pattern>
	</servlet-mapping>
	<welcome-file-list>
		<welcome-file>index.jsp</welcome-file>
	</welcome-file-list>
</web-app>

faces-config.xml

Create a faces-config.xml file in the src/main/webapp/WEB-INF folder.

<?xml version='1.0' encoding='UTF-8'?>
<faces-config xmlns="http://java.sun.com/xml/ns/javaee"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_1_2.xsd"
	version="1.2">

	<converter>
	    <converter-for-class>com.test.Genre</converter-for-class>
	    <converter-class>com.test.GenreConverter</converter-class>
	</converter>

	<managed-bean>
		<managed-bean-name>genreBean</managed-bean-name>
		<managed-bean-class>com.test.GenreBean</managed-bean-class>
		<managed-bean-scope>session</managed-bean-scope>
	</managed-bean>

</faces-config>

Implementation

The following is the Genre Entity Class.

src/main/java/com/test/Genre.java

package com.test;

import java.io.Serializable;

public class Genre implements Serializable {
	private static final long serialVersionUID = 1L;
	private Integer id;
	private String name;

	public Genre() {
		super();
	}

	public Genre(Integer id, String name) {
		this.id = id;
		this.name = name;
	}

	public Integer getId() {
		return id;
	}
	public void setId(Integer id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	@Override
	public String toString() {
		return "Genre [id=" + id + ", name=" + name + "]";
	}
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((id == null) ? 0 : id.hashCode());
		return result;
	}
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Genre other = (Genre) obj;
		if (id == null) {
			if (other.id != null)
				return false;
		} else if (!id.equals(other.id)) {
			return false;
		}
		return true;
	}
}

The following is the Genre Managed Bean.

src/main/java/com/test/GenreBean.java

package com.test;

import java.util.ArrayList;
import java.util.List;

import javax.faces.model.SelectItem;

public class GenreBean {
    private Genre[] selectedDataList;

	public Genre[] getSelectedDataList() {
		return selectedDataList;
	}

	public void setSelectedDataList(Genre[] selectedDataList) {
		this.selectedDataList = selectedDataList;
	}

    public List<SelectItem> getSelectItemsStatic() {
        List<SelectItem> selectItems = new ArrayList<SelectItem>();
        for (Genre genre : GenreBean.getAllGenreStatic()) {
            selectItems.add(new SelectItem(genre, genre.getName()));
        }
        return selectItems;
    }

    public static Genre getById(Integer id) {
    	List<Genre> list = getAllGenreStatic();
    	int index = list.indexOf(new Genre(id, "doesnt matter based on equals()"));
    	Genre genre = null;
    	if(index != -1) {
    		genre = list.get(index);
    	}
    	return genre;
    }

    private static List<Genre> getAllGenreStatic() {
    	List<Genre> list = new ArrayList<Genre>();
    	list.add(new Genre(1, "Action"));
    	list.add(new Genre(2, "Drama"));
    	list.add(new Genre(3, "Documentary"));
		return list;
	}

    public String countSelected() {
    	System.out.println("selected: " + selectedDataList.length);
    	selectedDataList = null;
    	return "delete";
    }
}

The following is the Converter

The converter helps to translate between the string representation of the object and the object itself. Typical objects like String dont need converters. Any object that is an entity that you want displayed in Any JSF component needs a converter. Below you see an object that converts the Genre bean from a string representation to an actual Genre bean. In reality you would replace the source of the data to be a object cache or database datamanager. But for now we are using hard coded data in the GenreBean.

src/main/java/com/test/GenreConverter.java

package com.test;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.convert.ConverterException;

public class GenreConverter implements Converter {

	/**
	 * This method is responsible for re-constructing the object from the text
	 * representation of the object from the HTML form element. Typically this
	 * method would use a datamanager that will "lookup" the information.
	 */
	@Override
	public Object getAsObject(FacesContext arg0, UIComponent arg1, String arg2)
			throws ConverterException {
		Genre genre = new Genre();
		if("".equals(arg2)) {
			return genre;
		}
		// typically would go to the database to get the object.
		Integer id = Integer.parseInt(arg2);
		// call a static method for now. But this really should be coming
		// from a datamanager.
		genre = GenreBean.getById(id);
		return genre;
	}

	@Override
	public String getAsString(FacesContext arg0, UIComponent arg1, Object arg2)
			throws ConverterException {		
		if(arg2 instanceof Genre) {
			Genre genre = (Genre)arg2;
			return ""+ genre.getId();
		}
		return "";
	}
}

The following is the JSP

/src/main/webapp/index.jsp

<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<%@ page contentType="text/html;charset=UTF-8" language="java"%>
<%@ taglib prefix="f" uri="http://java.sun.com/jsf/core"%>
<%@ taglib prefix="h" uri="http://java.sun.com/jsf/html"%>

<f:view>
	<html>
	<head>
	<title>Hello World JSF Example</title>
	</head>
	<body>
	<h:form id="myform">
	<h3>Select Movie Genre</h3>
		<h:selectManyListbox id="genre_select"
			value="#{genreBean.selectedDataList}" size="5">
			<f:selectItems value="#{genreBean.selectItemsStatic}" />
		</h:selectManyListbox>
		<br />
		<h:commandButton action="#{genreBean.countSelected}" />
	</h:form>
	</body>
	</html>
</f:view>

test the application

mvn jetty:run
http://localhost:8080/testSwitchList/index.jsf

Convert to a Switch List

The final step is to convert the listbox to a switchlist. We are going to use the advanced features provided by the tomahawk jsf plugin to generate ourselves the switchlist as well as the javascript that supports it.

The old school method of doing this is described here. As you can see this process was tedious because the javascript had to be manually customized and some additional frameworks like JQuery needed to be used in order to make things easier.

On this page we will describe the easier way of getting this done with JSF.

Go ahead and add the following to the web.xml file.

<filter>
        <filter-name>MyFacesExtensionsFilter</filter-name>
        <filter-class>org.apache.myfaces.webapp.filter.ExtensionsFilter</filter-class>
</filter>

<!-- extension mapping for adding <script/>, <link/>, and other resource tags to JSF-pages  -->
<filter-mapping>
    <filter-name>MyFacesExtensionsFilter</filter-name>
    <!-- servlet-name must match the name of your javax.faces.webapp.FacesServlet entry -->
    <servlet-name>Faces Servlet</servlet-name>
</filter-mapping>

<!-- extension mapping for serving page-independent resources (javascript, stylesheets, images, etc.)  -->
<filter-mapping>
    <filter-name>MyFacesExtensionsFilter</filter-name>
    <url-pattern>/faces/myFacesExtensionResource/*</url-pattern>
</filter-mapping>

Include the following up top in the jsp file.

<%@ taglib uri="http://myfaces.apache.org/tomahawk" prefix="t"%>

Simply changing the h:selectManyListbox to the t:selectManyPicklist enables the switchlist functionality on the page.

		<t:selectManyPicklist id="genre_select"
			value="#{genreBean.selectedDataList}" size="5">
			<f:selectItems value="#{genreBean.selectItemsStatic}" />
		</t:selectManyPicklist>

The selectManyPicklist imports all the necessary javascript on the page in addition to the command buttons that let you move items from left to right.

test the application

mvn jetty:run

http://localhost:8080/testSwitchList/index.jsf

By viewing the source for the page you notice that most of the coding is already done for you. JSF makes all this easy.

Thats all for now!

24
Dec
09

Managing JSF Checkboxes

This page describes the process of using checkboxes in your JSF enabled application to select all, select one or select multiple records in a dataTable.

In the examples below JavaScript is required. Submitting an http request to get the component state updated is not acceptable for a good user experience.

JSF Datatable

The following JSF code uses javascript defined below to check and un-check boxes.

<t:datatable renderedifempty="false" cellpadding="2" cellspacing="1" columnclasses="leftAlignCol" headerclass="list-header" id="dataTable1" rowclasses="list-row-even,list-row-odd" styleclass="dataTable" value="#{myBackingBean.queryResult}" var="currentRow" width="100%">
   <h:column>
       <t:selectbooleancheckbox forceid="true" id="chk" onclick="onChangeSelect(this);" value="#{myBackingBean.selected}"></t:selectbooleancheckbox>
       <f:facet name="header">
           <h:outputtext value="Selected"></h:outputtext>
       </f:facet>
  </h:column>
      :
    more columns
      :
</t:datatable>

JQuery

Using JQuery makes working with javascript easier. For example to implement the select all feature all you need to do is the following.

To get jquery just download it from their homepage. It is a small js file. Put it in the same directory as your html. Include this in the header of the page.

      <script src="jquery-1.3.2.min.js" type="text/javascript"></script>

include the following code that checks and unchecks the checkboxes.

   <a href="#" onclick="$('form :checkbox').attr('checked', 'checked');">Select All</a> 
   <a href="#" onclick="$('form :checkbox').attr('checked', '');">Un-Select All</a>

Thats all there is to it!

Doing it the OLD School way

If you guys insist in doing it the old way please read on. However take note that when you use pagination the second page will not work. The will only work when all the results are displayed on one page.

The old school method will only work if you use the tomahawk tag library and specify (forceId=”true” id=”chk” ). This allows us to write javascript that can check / uncheck the boxes referencing them via id. Otherwise JSF would have generated all sort of random id’s that would be difficult. JQuery does not have this problem since we are using the selector functionality. Even if the id’s were random JQuery would be able to check/uncheck the boxes.

Javascript Functions

To get all of this done we need the following javascript functions.

  1. Generic Function to get an element by its’ id.
  2. Generic function to  check to see if any item in the array has been checked
  3. Generic Function to call function 2 and enable a command button if it returns true
  4. Generic function to  hide or show an object using CSS.
  5. Select All Javascript

1. Gets an object based on its element id.

function getObj(objId, formId) {
        var fullId = objId;
        if (formId != null &amp;&amp; formId.length &gt; 0) {
            fullId = formId + ':' + objId;
        }
        //alert('getting object: ' + fullId);
        var elem = null;
        if (document.getElementById) {
            elem = document.getElementById(fullId);
        } else if (document.all) {
            elem = document.all[fullId];
        } else if (document.layers) {
            elem = document.layers[fullId];
        }
        return elem;
}

2. Generic function to check to see if any item in the array has been checked

/*
 * Browser-safe. Checks to see if an array of check boxes has any which are
 * checked. Boxes have ids like check[0], check[1], ... , check[n] where 'check'
 * is the base Id that has been assigned to the group.
 *
 * Param:               arrayId - String - id of element group to change
 * Returns:             boolean (true one or more checked) false (else)
 */
function checkBoxArrayHasChecked(arrayId) {
    for (i = 0; ; i++) {
        id = arrayId + '[' + i + ']';
        elem = getObj(id);
        if (elem == null) {
            break;
        } else if (elem.checked) {
           return true;
        }
    }
    return false;
}

3. Generic Function to call function 2 and enable a command button if it returns true

Custom function that will enable a component if at least one checkbox is checked.

var cmdBtnId = 'cmdbtn';
var formId = 'frm1';
var checkBoxArrayId = 'chk';
function onChangeSelect(checkbox) {
    // Render the transfer button if one or more checkboxes are selected
    hideOrShowObject(formId, cmdBtnId, checkBoxArrayHasChecked(checkBoxArrayId));
}

4. Generic function to check or un-check all elements in an array

/*
 * Browser-safe. Check or uncheck an array of checkboxes. Boxes have ids
 * like check[0], check[1], ... , check[n] where 'check' is the base Id that
 * has been assigned to the group.
 *
 * Param:               arrayId - String - id of element group to change
 * Param:               state - boolean - true (check all elements) false (uncheck all elements)
 * Returns:             nothing
 */
function checkBoxArraySet(arrayId, state) {
    for (i = 0; ; i++) {
        id = arrayId + '[' + i + ']';
        elem = getObj(id);
        if (elem == null) {
            break;
        } else {
            elem.checked = state;
        }
    }
}

5. Select All Javascript

The next javascript is implementation specific that will run when user clicks (select or un-select) all.

function setAll(state) {
    // Set the checkBox array on or off
    checkBoxArraySet(checkBoxArrayId, state);
}

Example

An example of a component that calls the above function.

<h:graphicimage onclick="setAll(true);" id="all" url="resources/add.gif" title="Select all records..."></h:graphicimage>

Another Example

		<a href="#" onclick="setAll(true);">
		     <h:outputtext value="Select all"></h:outputtext>
		</a><br>
		<a href="#" onclick="setAll(false);">
		     <h:outputtext value="de-Select all"></h:outputtext>
		</a>
20
Dec
09

Generating an Excel Sheet using JSF

This page describes the process of generating an excel sheet using JSF dataTable custom tag.

Requirements

  • Maven
  • Basic Understanding of JSF

Installation

The following should be inserted into the pom.xml file.

	<dependency>
		<groupId>org.apache.myfaces.core</groupId>
		<artifactId>myfaces-api</artifactId>
		<version>1.2.8</version>
	</dependency>

	<dependency>
		<groupId>org.apache.myfaces.tomahawk</groupId>
		<artifactId>tomahawk</artifactId>
		<version>1.1.9</version>
	</dependency>

	<dependency>
		<groupId>org.apache.myfaces.core</groupId>
		<artifactId>myfaces-impl</artifactId>
		<version>1.2.8</version>
	</dependency>
	<dependency>
		<groupId>org.apache.tiles</groupId>
		<artifactId>tiles-jsp</artifactId>
		<version>2.1.4</version>
	</dependency>

The following is the bean used to generate the excel data

Coding the JSP

<%@ taglib prefix="f" uri="http://java.sun.com/jsf/core" %>
<%@ taglib prefix="h" uri="http://java.sun.com/jsf/html" %>
<%@ taglib prefix="tiles" uri="http://tiles.apache.org/tags-tiles"  %>
<%@ taglib uri="http://myfaces.apache.org/tomahawk" prefix="t" %>

<f:subview id="genre_tile">

    <h3>Genre Table</h3>

    <h:form>
    <p>
    	<h:commandLink action="#{export.exportHtmlTableToExcel}">
		     <h:outputText value="Export table to Excel"/>
		</h:commandLink>    
    <t:buffer into="#{export.htmlBuffer}">
    	<t:dataTable styleClass="data_table" value="#{genreBean.allGenre}" var="genre">
    		<t:column sortable="true">
    		    <f:facet name="header">
		        	<h:outputText value="id" />
        		</f:facet> 
    			<h:outputText value="#{genre.id}"/>
    		</t:column>
    		<t:column sortable="true">
    		    <f:facet name="header">
		        	<h:outputText value="Name" />
        		</f:facet> 
    			<t:outputText value="#{genre.name}"/>
    		</t:column>
    	</t:dataTable>
    	</t:buffer> 
		<h:outputText value="#{export.htmlBuffer}" escape="false"/>		
    </h:form>
    </p>
</f:subview>

JSF Managed Bean

The above code makes a call to the following Bean.

test/Export.java

package test;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.Serializable;

import javax.faces.context.FacesContext;
import javax.servlet.http.HttpServletResponse;

public class Export implements Serializable {
    /**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	private String htmlBuffer;
    
    
    public Export() {
        
    }
    
    public void exportHtmlTableToExcel() throws IOException{        

//        //Set the filename
//        DateTime dt = new DateTime();
//        DateTimeFormatter fmt = DateTimeFormat.forPattern("yyyy-MM-dd_HHmmss");
        String filename = ""+System.currentTimeMillis() + ".xls";
        
        
        //Setup the output
        String contentType = "application/vnd.ms-excel";
        FacesContext fc = FacesContext.getCurrentInstance();
        
        HttpServletResponse response = (HttpServletResponse)fc.getExternalContext().getResponse();
        response.setHeader("Content-disposition", "attachment; filename=" + filename);
        response.setContentType(contentType);
        
        //Write the table back out
        PrintWriter out = response.getWriter();
        out.print(htmlBuffer);
        out.close();
        fc.responseComplete();
    }  
    
    public String getHtmlBuffer() {
        return htmlBuffer;
    }
    
    public void setHtmlBuffer(String htmlBuffer) {
        this.htmlBuffer = htmlBuffer;
    }

}
20
Dec
09

column sorting using JSF and Tomahawk

This page describes the process of sorting results generated by a JSF based application.

Installation

		<dependency>
			<groupId>org.apache.myfaces.tomahawk</groupId>
			<artifactId>tomahawk</artifactId>
			<version>1.1.9</version>
		</dependency>  

Configuration

Not 100% sure if the following is needed in web.xml or not. Just here for reference.

<filter>
        <filter-name>MyFacesExtensionsFilter</filter-name>
        <filter-class>org.apache.myfaces.webapp.filter.ExtensionsFilter</filter-class>
    <init-param>
        <param-name>uploadMaxFileSize</param-name>
        <param-value>20m</param-value>
        <description>Set the size limit for uploaded files.
            Format: 10 - 10 bytes
                    10k - 10 KB
                    10m - 10 MB
                    1g - 1 GB
        </description>
    </init-param>
</filter>

<!-- extension mapping for adding <script/>, <link/>, and other resource tags to JSF-pages  -->
<filter-mapping>
    <filter-name>MyFacesExtensionsFilter</filter-name>
    <!-- servlet-name must match the name of your javax.faces.webapp.FacesServlet entry -->
    <servlet-name>Faces Servlet</servlet-name>
</filter-mapping>

<!-- extension mapping for serving page-independent resources (javascript, stylesheets, images, etc.)  -->
<filter-mapping>
    <filter-name>MyFacesExtensionsFilter</filter-name>
    <url-pattern>/faces/myFacesExtensionResource/*</url-pattern>
</filter-mapping>

Programming the JSP

Ensure that you have the following taglib in each JSP.

<%@ taglib uri="http://myfaces.apache.org/tomahawk" prefix="t" %>

Example Page

Notice in the following example the h: tags are converted to t: tomahawk tags. Also observe the highlighted lines below.

<%@ taglib prefix="f" uri="http://java.sun.com/jsf/core" %>
<%@ taglib prefix="h" uri="http://java.sun.com/jsf/html" %>
<%@ taglib prefix="tiles" uri="http://tiles.apache.org/tags-tiles"  %>
<%@ taglib uri="http://myfaces.apache.org/tomahawk" prefix="t" %>

<f:subview id="genre_tile">

    <h3>Genre Table</h3>

    <h:form>
    <p>
    	<t:dataTable styleClass="data_table" value="#{genreBean.allGenre}" var="genre">
    		<t:column sortable="true">
    		    <f:facet name="header">
		        	<h:outputText value="id" />
        		</f:facet> 
    			<h:outputText value="#{genre.id}"/>
    		</t:column>
    		<t:column sortable="true">
    		    <f:facet name="header">
		        	<h:outputText value="Name" />
        		</f:facet> 
    			<t:outputText value="#{genre.name}"/>
    		</t:column>
    	</t:dataTable>
		<h:commandButton value="Submit" action="#{genreBean.process}"/>
    </h:form>    	
    </p>
</f:subview>



Follow

Get every new post delivered to your Inbox.

Join 49 other followers