Developing Great Software

Wednesday, March 30, 2011

Java EE6 & Wicket - Article #5–Building Out The Guest Book Web Application Using NetBeans

Welcome to the 5th in a series of detailed articles on Java EE6 & Wicket. If you haven’t already, please read the following previous articles before continuing.
  1. http://jeff-schwartz.blogspot.com/2011/03/java-ee6-wicket.html
  2. http://jeff-schwartz.blogspot.com/2011/03/java-ee6-wicket-article-1-requirements.html
  3. http://jeff-schwartz.blogspot.com/2011/03/java-ee6-wicket-article-2-creating.html
  4. http://jeff-schwartz.blogspot.com/2011/03/java-ee6-wicket-article-3-generating.html
  5. http://jeff-schwartz.blogspot.com/2011/03/java-ee6-wicket-article-4-adding-jpa.html

In this, the 5th article in this series, we will complete the implementation of the GuestBook Web application. So lets get started.

Modifying The Generated Markup And Code

If you haven’t already, fire up NetBeans because we are going to make a few changes to the generated code so that our GuestBook’s home page will look like the image in article #1. In order to do that we will compose the HomePage using Wicket Markup Inheritance.

Our HomePage displays a Wicket logo which you can download here. Place the logo  in the com.myapp.wicket package.

Next, we need to make a few changes to the generated code. Create a new HTML file in the com.myapp.wicket package and name it BasePage.

  1. Right click on the com.myapp.wicket package, select New | Other to open the New File window
  2. Select Web in the Categories panel and then select HTML in the File Types panel and click the Next button.2011-03-30 08h45_57
  3. Enter BasePage for the HTML File Name and click the Finish button. NetBeans will generate the BasePage.html file and open it in the editor.

Replace the generated markup with the following markup and save the file:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html>
<html xmlns:wicket="http://wicket.apache.org">
<head>
<meta name="description" content="A guest list of the rich and famous written in Java and Wicket">
</head>
<body>
<div wicket:id="headerpanel"></div>
<wicket:child/>
</body>
</html>

Replace all the code in com.myapp.wicket.BasePage.java with the following code and save the file:

package com.myapp.wicket;           

import org.apache.wicket.markup.html.WebPage;

/**
*
* @author Jeff
* @version
*/

public class BasePage extends WebPage {

/**
* Construct.
* @param model
*/
public BasePage() {
super();
add(new HeaderPanel("headerpanel"));
}
}

Replace the HTML markup in com.myapp.wicket.HeaderPanel.htm with the following markup and save the file:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns:wicket="http://wicket.apache.org">
<head><title></title></head>
<body>
<wicket:panel>
<wicket:link>
<img src="Apache_Wicket_logo.png" style="float:left;"/>
</wicket:link>
<h1 style="margin-left: 180px; color: #E9601A; font-style: italic; font-size: 1.5em; font-family: sans-serif; font-weight: bold; line-height: 59px; white-space: nowrap">
<span wicket:id="headerpanneltext">Java EE6 And Wicket EJB Tutorial</span>
</h1>
<hr style="color: #E9601A; background-color: #E9601A; height: 1px;"/>
</wicket:panel>
</body>
</html>

Replace the Java code in com.myapp.wicket.HeaderPanel.java with the following code and save the file:

package com.myapp.wicket;           

import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.panel.Panel;

/**
*
* @author Jeff
* @version
*/

public class HeaderPanel extends Panel {

public HeaderPanel(String id)
{
super(id);
add(new Label("headerpanneltext", "Java EE6 And Wicket EJB Tutorial"));
}

}

Replace all the HTML markup in com.myapp.wicket.HomePage.html with the following markup and save the file:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html>
<html xmlns:wicket="http://wicket.apache.org">
<head>
<wicket:head>
<title>Guests</title>
<wicket:link>
<link rel="stylesheet" type="text/css" href="style.css"/>
</wicket:link>
</wicket:head>
</head>
<body>
<wicket:extend>
<div>
<div style="margin-top: 20px; padding-left: 15px;">
<h2 style="color:#333333; white-space: nowrap;">Please Sign Our Guest List</h2>
<div wicket:id="feedback"></div>
<form wicket:id="guestform">
<label>First Name:
<span wicket:id="firstNameBorder">
<input wicket:id="firstName" type="text"/>
</span>
</label>
<br/>
<label>Last Name:
<span wicket:id="lastNameBorder">
<input wicket:id="lastName" type="text"/>
</span>
</label>
<br/>
<input type="submit"/><input type="reset"/>
</form>
</div>
<hr style="background-color: #E9601A; color: #E9601A; height: 1px; margin-top: 20px;"/>
<div style="margin-top: 20px; padding-left: 15px;">
<h2 style="color:#333333; white-space: nowrap;">Visitors Who Have Signed Our Guest List</h2>
<div wicket:id="namelist" style="white-space: nowrap;">
<span wicket:id="name" style="font-size: 1.2em; color: #333333;">name</span>
</div>
</div>
</div>
</wicket:extend>
</body>
</html>

Replace all the Java code in com.myapp.wicket.HomePage.java with the following code and save the file:

package com.myapp.wicket;

import com.myapp.wicket.components.NameTextField;
import com.myapp.wicket.dataproviders.GuestDataProvider;
import com.myapp.entities.Guest;
import com.myapp.sessionbeans.AbstractFacade;
import com.myapp.sessionbeans.GuestFacade;
import javax.ejb.EJB;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.validation.FormComponentFeedbackBorder;
import org.apache.wicket.markup.html.panel.FeedbackPanel;
import org.apache.wicket.markup.repeater.Item;
import org.apache.wicket.markup.repeater.data.DataView;
import org.apache.wicket.model.CompoundPropertyModel;
import org.apache.wicket.model.LoadableDetachableModel;

public class HomePage extends BasePage {

@EJB(name = "GuestFacade")
private GuestFacade guestFacade;

public HomePage() {
super();

add(new FeedbackPanel("feedback"));

/*
* A loadable and detachable model whose sole purpose is to always
* return a new Guest object when its load method is called.
*/
LoadableDetachableModel<Guest> newGuestLoadableDetachableModel = new LoadableDetachableModel<Guest>() {

@Override
protected Guest load() {
return new Guest();
}
};

/*
* The guestForm uses a nested model. The outer model is a compound property
* model and the inner model is a light weight loadable and detachable model.
*/
Form<Guest> guestForm = new Form<Guest>("guestform", new CompoundPropertyModel<Guest>(newGuestLoadableDetachableModel)) {

@Override
protected void onSubmit() {
Guest guest = getModelObject();
guestFacade.create(guest);
setModelObject(new Guest());
guest = new Guest();
}
};

/*
* Add the custom NameTextField compent for
* first and last name to the form, nesting each
* in a FormComponentFeedbackBorder for displaying
* data entry errors.
*/
guestForm
.add(new FormComponentFeedbackBorder("firstNameBorder")
.add(new NameTextField("firstName")));
guestForm
.add(new FormComponentFeedbackBorder("lastNameBorder")
.add(new NameTextField("lastName")));
add(guestForm);

GuestDataProvider gdp = new GuestDataProvider() {

@Override
public AbstractFacade<Guest> getFacade() {
return guestFacade;
}
};

/*
* When dealing with potentially large lists of data it is
* better to use a DataView whose constructor takes a data
* provider as its second parameter.
*/
DataView<Guest> guestListView = new DataView<Guest>("namelist", gdp) {

@Override
protected void populateItem(Item<Guest> item) {
Guest guest = item.getModelObject();
item.add(new Label("name", guest.toString()));
}

};

add(guestListView);

}
}

Replace the content of com.myapp.wicket.style.css with the following content and save the file:

body {
white-space: nowrap;
}

.feedbackPanelERROR {
color: red !important;
list-style: circle;
font-weight: bold;
}

.feedbackPanelINFO {
color: green;
list-style: circle;
font-weight: bold;
}

We need to create a properties file in which we will declare the warning messages that Wicket will use when validating the input form on the HomePage.

  1. Right click on the com.myapp.wicket package in the Projects panel and select New | Other to open the New File Window.
  2. Select Other from Categories and select Properties File from File Types and click the Next button.
  3. Enter HomePage for the File Name and click the Finish button. NetBeans will open the properties file in the eiditor.

Add the following content to the the com.myapp.wicket.HomePage.properties file and save the file:

firstName.Required=First Name Is Required. 
    
lastName.Required=Last Name Is Required.

Next, we will add a custom TextField component to the project.

  1. Right click on Source Packages in the Projects panel and select New | Other to open the New File window.
  2. Select Java from Categories and Java Class from File Types and click the Next button.
  3. Enter NameTextField for the Class Name and enter com.myapp.wicket.components for the Package name and click the Finish button. NetBeans will open the NameTextField.java file in the editor.

Replace all the Java code in the NameTextField.java file with the following and save the file:

package com.myapp.wicket.components;

import org.apache.wicket.markup.ComponentTag;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.model.IModel;

/**
* A domain specific extension of a Wicket TextField
* that sets the required flag and adds the maxlength=45
* and size=55 attributes to be rendered.
*
* Demonstrates how you can easily extend Wicket
* components to provide domain specific requirement.
*
* @author Jeff
*/
public class NameTextField extends TextField<String>{

/**
*
* @param id
* @param model
*/
public NameTextField(String id, IModel<String> model) {
super(id,model);
setRequired(true);
}

/**
*
* @param id
*/
public NameTextField(String id) {
this(id,null);
}

@Override
protected void onComponentTag(ComponentTag tag) {
tag.put("maxlength", "45");
tag.put("size", "55");
super.onComponentTag(tag);
}


}

Next, we will add an interface to the project.

  1. Right click on Source Packages in the Projects panel and select New | Other to open the New File window.
  2. Select Java from Categories and select Java Interface from File Types and click the Next button.
  3. Enter IEjbDataProvider.java for the Class Name and enter com.myapp.wicket.dataproviders for the Package name and click the Finish button. NetBeans will open the IEjbDataProvider.java file in the editor.

Replace all the Java code in the IEjbDataProvider.java file with the following and save the file:

package com.myapp.wicket.dataproviders;

import com.myapp.sessionbeans.AbstractFacade;
import org.apache.wicket.markup.repeater.data.IDataProvider;

/**
* Extended interface definition of IDataProvider<T> that provides type safe access to session beans.
* Provides the ability to access a session bean in any object.
*
* Use Case: Need to access session in objects other than a Wicket WebPage.
*
* Background - The JavaEEComponentInjector enables Java EE 5 resource injection in Wicket Pages but
* often we need to access to session beans from other objects such as DataProviders and Models. This
* interface supports such a use cases.
*
* Usage
* public class HomePage extends BasePage {
* @EJB(name = "GuestFacade")
* private GuestFacade guestFacade;
* private Guest guest;
*
* public HomePage() {
* super();
* guest = new Guest();
* GuestDataProvider gdp = new GuestDataProvider() {
*
* @Override
* public AbstractFacade<Guest> getFacade() {
* return guestFacade;
* }
* };
*
*
* @param <T>
* @author Jeff
*/
public interface IEjbDataProvider<T> extends IDataProvider<T> {
AbstractFacade<T> getFacade();
}

Next, we will add a Java class to the project.

  1. Right click on com.myapp.wicket.dataproviders in the Projects panel and select New | Other to open the New File window.
  2. Select Java from Categories and select Java Class from File Types and the click the Next button.
  3. Enter GuestDataProvider for Class Name and click the Finish button.

Replace all the Java code in the GuestDataProvider.java file with the following and save the file:

package com.myapp.wicket.dataproviders;

import com.myapp.entities.Guest;
import java.util.Iterator;
import java.util.List;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.LoadableDetachableModel;

abstract public class GuestDataProvider implements IEjbDataProvider<Guest>{

@Override
public Iterator<? extends Guest> iterator(int first, int count) {
int[] range = {first, count};
List<Guest> guests = getFacade().findRange(range);
return getFacade().findRange(range).iterator();
}

@Override
public IModel<Guest> model(final Guest object) {

final Integer id = object.getId();

LoadableDetachableModel<Guest> ldm = new LoadableDetachableModel<Guest>(object) {
@Override
protected Guest load() {
return getFacade().find(id);
}
};

return ldm;
}

@Override
public int size() {
return getFacade().count();
}

@Override
public void detach() {}

}

The last change we must make is to com.myapp.wicket.Application.java file. Replace all the code in Application.java with the following code and save the file:

package com.myapp.wicket;           

import org.apache.wicket.protocol.http.WebApplication;
import org.wicketstuff.javaee.injection.JavaEEComponentInjector;
/**
*
* @author Jeff
* @version
*/

public class Application extends WebApplication {

public Application() {
}

@Override
protected void init() {
super.init();
addComponentInstantiationListener(new JavaEEComponentInjector(this));
}

@Override
public Class getHomePage() {
return HomePage.class;
}
}

With all these changes in place you should now be able to run the project in your browser.

In the next and last article we will examine the code, focusing on the integration we provided for consuming the EJB in our HomePage. I will also discuss some best practices that I’ve used here, especially pertaining to Wicket components and models. If you don’t know the difference between static and dynamic models or you aren’t familiar with loadable/detachable models you can read my article here to get up to speed on this subject. Stay tuned!

1 comment:

Note: Only a member of this blog may post a comment.

About Me

My photo
New York, NY, United States
Software Developer