В этой статье я расскажу об использовании Icefaces 2
После того, как мы подключили ICEfaces 2 (см. "Введение в ICEfaces 2.0") приступим к работе с самим фреймворком на примере небольшого приложения.
Наше приложение будет отвечать за работу с пользователями: добавление, отображение списка пользователей с координатами их проживания. Данные о пользователях у нас будут храниться на уровне сеанса (sessionScope). Приложение обучающее, довольно простое и показывает часть возможностей работы ICEfaces 2 вместе с JSF 2.
Приступим.
Для начала создадим template на основе facelets.
Facelets предоставляет возможности по созданию шаблонов, разбивки страниц на блоки, что увеличивает читабельность кода и удобство работы с ним. Facelets появились еще с JSF 1, но не входили тогда в его состав, и их приходилось подключать отдельно, теперь же этого делать не нужно и достаточно только подключить JSF 2.
/templates/template.xtml
<?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="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:ice="http://www.icesoft.com/icefaces/component"
xmlns:f="http://java.sun.com/jsf/core"
>
<h:head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title><ui:insert name="title">#{msgs['application.title']}</ui:insert></title>
<ice:outputStyle href="#{styleBean.themeCssFilePath}"/>
<ice:outputStyle href="#{styleBean.baseCssPath}style.css"/>
</h:head>
<h:body>
<!-- header -->
<ice:panelGroup styleClass="header">
<ice:outputLink value="http://www.tune-it.ru">
<ice:graphicImage value="#{styleBean.baseImagePath}company_logo.png" styleClass="logo"/>
</ice:outputLink>
<ice:panelGroup>
<ice:outputText value="#{msgs['application.header']}" styleClass="text"/>
</ice:panelGroup>
</ice:panelGroup>
<!-- content -->
<ice:panelGroup>
<ui:insert name="body">Default BODY</ui:insert>
</ice:panelGroup>
<!-- footer -->
<hr/>
<ice:outputLink value="http://www.tune-it.ru">
<ice:outputText value="© #{msgs['application.copyright']}" />
</ice:outputLink>
<ice:outputFormat style="float: right;" value="#{msgs['application.stage']}">
<f:param value="#{facesContext.application.projectStage}"/>
</ice:outputFormat>
</h:body>
</html>
В блок <h:head> .. </h:head> мы включаем заголовки нашей страницы (title, подключение css стилей)
В блок <h:body> .. </h:body> мы включаем тело нашей страницы (header и footer, которые будут отображаться на каждой странице, использующей шаблон, и сам контент)
В нашем шаблоне будет меняться только контент. За это отвечает блок
<ui: insert name="body">Default BODY</ui:insert>
В страницах, использующих этот шаблон, должен быть объявлен блок, который и будет заменять блок нашего шаблона с именем "body".
<ui:define name="body"> ..</ui:define>
На основе созданного нами template.xhtml создаем главную страничку. Она будет состоять из формы добавления пользователя и таблицы, отображающий список пользователей с местом их проживания. Подключение нужного нам template осуществляется в блоке <ui:composition ..>
/pages/index.xhtml
<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:c="http://java.sun.com/jsp/jstl/core"
xmlns:ace="http://www.icefaces.org/icefaces/components"
xmlns:ice="http://www.icesoft.com/icefaces/component"
template="/templates/template.xhtml"
>
<ui:define name="body">
<ice:panelGroup>
<ice:panelGrid columns="2" styleClass="wrapper" columnClasses="column1 column-top, column2 column-top">
<ice:column>
<ui:include src="userForm.xhtml" />
</ice:column>
<ice:column>
<ui:include src="userList.xhtml" />
</ice:column>
</ice:panelGrid>
</ice:panelGroup>
</ui:define>
</ui:composition>
Наша страница будет состоять из таблицы с двумя колонками. В левой будет находиться форма, в правой будут отображаться пользователи с адресом проживания.
Создадим класс формы. В форме будут поля: имя, фамилия, возраст, адрес.
UserForm.java
package ru.tuneit.example.bean.user;
import java.io.Serializable;
/**
* @author nicola
* @version 1.0 (Aug 15, 2011)
*/
public class UserForm implements Serializable {
private static final long serialVersionUID = 1L;
private String firstName = "";
private String secondName = "";
private Long age = null;
private String address = "";
public UserForm() {
}
public UserForm(String firstName, String secondName, Long age, String address) {
this.firstName = firstName;
this.secondName = secondName;
this.age = age;
this.address = address;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public Long getAge() {
return age;
}
public void setAge(Long age) {
this.age = age;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getSecondName() {
return secondName;
}
public void setSecondName(String secondName) {
this.secondName = secondName;
}
public void reset() {
firstName = "";
secondName = "";
age = null;
address = "";
}
}
Теперь создадим класс, отвечающий за пользователя. Он будет нашей моделью.
User.java
package ru.tuneit.example.model;
import java.io.Serializable;
import ru.tuneit.example.bean.user.UserForm;
/**
* @author nicola
* @version 1.0 (Jun 20, 2011)
*/
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private String firstName;
private String secondName;
private Long age;
private String address;
private boolean selected;
public User(UserForm userForm) {
this(userForm.getFirstName(), userForm.getSecondName(), userForm.getAge(), userForm.getAddress());
}
public User(String firstName, String secondName, Long age, String address) {
this.firstName = firstName;
this.secondName = secondName;
this.age = age;
this.address = address;
this.selected = false;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getSecondName() {
return secondName;
}
public void setSecondName(String secondName) {
this.secondName = secondName;
}
public Long getAge() {
return age;
}
public void setAge(Long age) {
this.age = age;
}
public boolean getSelected() {
return selected;
}
public void setSelected(boolean selected) {
this.selected = selected;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final User other = (User) obj;
if ((this.firstName == null) ? (other.firstName != null) : !this.firstName.equals(other.firstName)) {
return false;
}
if ((this.secondName == null) ? (other.secondName != null) : !this.secondName.equals(other.secondName)) {
return false;
}
if (this.age != other.age && (this.age == null || !this.age.equals(other.age))) {
return false;
}
if ((this.address == null) ? (other.address != null) : !this.address.equals(other.address)) {
return false;
}
return true;
}
@Override
public int hashCode() {
int hash = 7;
hash = 23 * hash + (this.firstName != null ? this.firstName.hashCode() : 0);
hash = 23 * hash + (this.secondName != null ? this.secondName.hashCode() : 0);
hash = 23 * hash + (this.age != null ? this.age.hashCode() : 0);
hash = 23 * hash + (this.address != null ? this.address.hashCode() : 0);
return hash;
}
}
После чего создадим бин, отвечающий за работу с нашими пользователями. Это основной класс, который мы будем использовать. Наш бин будет находиться в sessionScope.
С помощью аннотаций мы регистрируем наш бин на уровне сессии с именем "userBean". Параметр name можно не выставлять, тогда имя будет присвоено автоматически по названию класса бина, но начинаться будет со строчной буквы:
@ManagedBean (name = "userBean")
@SessionScoped
С помощью аннотации @ManagedProperty мы выставляем свойства нашего бина, которые будут выставлены ему после его создания.
UserBean.java
package ru.tuneit.example.bean.user;
import ru.tuneit.example.model.User;
import com.icesoft.faces.component.ext.RowSelectorEvent;
import com.icesoft.faces.context.effects.Effect;
import com.icesoft.faces.context.effects.Highlight;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ManagedProperty;
import javax.faces.bean.SessionScoped;
import javax.faces.event.ActionEvent;
import org.apache.log4j.Logger;
import ru.tuneit.example.utils.faces.FacesUtils;
@ManagedBean (name = "userBean")
@SessionScoped
public class UserBean implements Serializable {
private static final long serialVersionUID = 1L;
private static final Logger _log = Logger.getLogger(UserBean.class.getName());
private List<User> users;
private UserForm userForm = new UserForm();
@ManagedProperty("190103, г. Санкт-Петербург, 10-я Красноармейская ул., д.22 литера А")
private String address;
private Effect effect = new Highlight("#C4C4C4");
public UserBean() {
}
@PostConstruct
public void initUsers() {
users = new ArrayList<User>() {{
add(new User("Иван", "Иванов", 11L, "Санкт-Петербург, Измайловский пр., д2"));
add(new User("Петр", "Петров", 19L, "Санкт-Петербург, Рижский пр., д3"));
add(new User("Сергей", "Сергеев", 21L, "Санкт-Петербург, Средний пр., д4"));
add(new User("Пух", "Винни", 77L, "Санкт-Петербург, Вознесенский пр., д2"));
add(new User("Пята", "Чок", 74L, "Санкт-Петербург, Ленинский пр., д2"));
}};
}
public void selectUser(RowSelectorEvent event) {
try {
User userSelected = (User)FacesUtils.getVariable("user");
for (User u : users) {
if (!u.equals(userSelected)) {
u.setSelected(false);
}
}
address = userSelected.getAddress();
FacesUtils.refresh();
} catch (Exception e) {
_log.error(e, e);
}
}
public void save(ActionEvent event) {
users.add(new User(userForm));
userForm.reset();
effect.setFired(false);
}
public void cancel(ActionEvent event) {
userForm.reset();
}
public int getUsersSize() {
return users != null ? users.size() : 0;
}
public UserForm getUserForm() {
return userForm;
}
public void setUserForm(UserForm userForm) {
this.userForm = userForm;
}
public List<User> getUsers() {
return users;
}
public void setUsers(List<User> users) {
this.users = users;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public Effect getEffect() {
return effect;
}
public void setEffect(Effect effect) {
this.effect = effect;
}
}
В нашем бине есть два метода: save(ActionEvent event) и cancel(ActionEvent event). Один отвечает за сохранение пользователя, другой за очистку формы. Они являются ActionListener-ами и висят на соответствующих кнопках <ice:commandButton ..>.
После создания нашего бина мы можем приступить к описанию страницы формы.
/pages/userForm.xhtml
<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:c="http://java.sun.com/jsp/jstl/core"
xmlns:ace="http://www.icefaces.org/icefaces/components"
xmlns:ice="http://www.icesoft.com/icefaces/component"
>
<ice:form id="addUserForm">
<ice:panelCollapsible expanded="true">
<f:facet name="header">
<ice:outputText value="#{msgs['user.addUser']}" />
</f:facet>
<ice:panelGroup effect="#{userBean.effect}">
<ice:panelGrid columns="2">
<ice:column>
<ice:outputLabel for="firstName" value="#{msgs['user.firstName']}" />
</ice:column>
<ice:column>
<ice:inputText id="firstName"
value="#{userBean.userForm.firstName}"
required="true"
requiredMessage="#{msgs['user.firstName.requiredMessage']}"
/>
<ice:message for="firstName"/>
</ice:column>
<ice:column>
<ice:outputLabel for="secondName" value="#{msgs['user.secondName']}" />
</ice:column>
<ice:column>
<ice:inputText id="secondName"
value="#{userBean.userForm.secondName}"
required="true"
requiredMessage="#{msgs['user.secondName.requiredMessage']}"
/>
<ice:message for="secondName"/>
</ice:column>
<ice:column>
<ice:outputLabel for="age" value="#{msgs['user.age']}" />
</ice:column>
<ice:column>
<ice:inputText id="age"
value="#{userBean.userForm.age}"
validatorMessage="#{msgs['user.age.validateMessage']}"
>
<f:validateLongRange minimum="1" maximum="150"/>
</ice:inputText>
<ice:message for="age"/>
</ice:column>
<ice:column>
<ice:outputLabel for="address" value="#{msgs['user.address']}" />
</ice:column>
<ice:column>
<ice:inputTextarea id="address"
value="#{userBean.userForm.address}"
required="true"
requiredMessage="#{msgs['user.address.requiredMessage']}"
/>
<ice:message for="address"/>
</ice:column>
<ice:column />
<ice:column>
<ice:messages globalOnly="true" layout="table"/>
<ice:commandButton value="#{msgs['user.save']}"
actionListener="#{userBean.save}"
/>
<ice:commandButton value="#{msgs['user.cancel']}"
actionListener="#{userBean.cancel}"
immediate="true"
/>
</ice:column>
</ice:panelGrid>
</ice:panelGroup>
</ice:panelCollapsible>
</ice:form>
</ui:composition>
Теперь опишем страницу отображающую список наших пользователей с адресом их проживания. Мы используем <ice:dataTable ..> компонент для отображения пользователей c <ice:rowSelector ..>, который отвечает за выделение строки и реакцию на нажатие по строке. При большом количестве пользователей, автоматически производится разбивка таблицы на страницы при помощи <ice:dataPaginator ..>.
/pages/userList.xhtml
<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:c="http://java.sun.com/jsp/jstl/core"
xmlns:ace="http://www.icefaces.org/icefaces/components"
xmlns:ice="http://www.icesoft.com/icefaces/component"
>
<ice:form id="listUsersForm">
<ice:panelCollapsible expanded="true">
<f:facet name="header">
<ice:outputText value="#{msgs['user.usersTable']}" />
</f:facet>
<ice:dataTable id="usersTable"
value="#{userBean.users}"
var="user"
columnWidths="40%, 40%, 20%"
styleClass="usersTable"
rows="5"
>
<ice:column>
<ice:rowSelector id="userSelector"
value="#{user.selected}"
multiple="false"
selectionListener="#{userBean.selectUser}"
/>
<f:facet name="header">
<ice:outputText value="#{msgs['user.firstName']}" />
</f:facet>
<ice:panelGroup>
<ice:outputText value="#{user.firstName}"></ice:outputText>
</ice:panelGroup>
</ice:column>
<ice:column>
<f:facet name="header">
<ice:outputText value="#{msgs['user.secondName']}" />
</f:facet>
<ice:panelGroup>
<ice:outputText value="#{user.secondName}"></ice:outputText>
</ice:panelGroup>
</ice:column>
<ice:column>
<f:facet name="header">
<ice:outputText value="#{msgs['user.age']}" />
</f:facet>
<ice:panelGroup>
<ice:outputText value="#{user.age}"></ice:outputText>
</ice:panelGroup>
</ice:column>
</ice:dataTable>
<ice:dataPaginator rendered="#{userBean.usersSize > 5}"
for="usersTable"
fastStep="3"
paginator="true"
paginatorMaxPages="4"
style="margin-left: auto; margin-right: auto;"
>
<f:facet name="first">
<ice:graphicImage url="#{styleBean.themeImageDirectory}arrow-first.gif"
style="border:none;"
title="#{msgs['page.dataScrolling.firstpage']}"
/>
</f:facet>
<f:facet name="last">
<ice:graphicImage url="#{styleBean.themeImageDirectory}arrow-last.gif"
style="border:none;"
title="#{msgs['page.dataScrolling.lastpage']}"
/>
</f:facet>
<f:facet name="previous">
<ice:graphicImage url="#{styleBean.themeImageDirectory}arrow-previous.gif"
style="border:none;"
title="#{msgs['page.dataScrolling.previouspage']}"
/>
</f:facet>
<f:facet name="next">
<ice:graphicImage url="#{styleBean.themeImageDirectory}arrow-next.gif"
style="border:none;"
title="#{msgs['page.dataScrolling.nextpage']}"
/>
</f:facet>
<f:facet name="fastforward">
<ice:graphicImage url="#{styleBean.themeImageDirectory}arrow-ff.gif"
style="border:none;"
title="#{msgs['page.dataScrolling.fastforward']}"
/>
</f:facet>
<f:facet name="fastrewind">
<ice:graphicImage url="#{styleBean.themeImageDirectory}arrow-fr.gif"
style="border:none;"
title="#{msgs['page.dataScrolling.fastbackward']}"
/>
</f:facet>
</ice:dataPaginator>
<ice:panelGroup styleClass="map">
<ice:outputLabel for="map" value="#{msgs['user.map']}" />
<ice:outputText value="#{userBean.address}" style="clear: left; display: block; font-size: 1.2em;"/>
<ice:gMap id="map" address="#{userBean.address}" zoomLevel="8">
<ice:gMapControl id="largectrl" name="GLargeMapControl" rendered="true"/>
<ice:gMapControl id="scalectrl" name="GScaleControl" rendered="true"/>
<ice:gMapControl id="typectrl" name="GMapTypeControl" rendered="true"/>
<ice:gMapMarker id="gmarker" latitude="59" longitude="30" rendered="true">
<ice:gMapLatLng id="glatlng" latitude="59.544467" longitude="30.174242"/>
</ice:gMapMarker>
</ice:gMap>
</ice:panelGroup>
</ice:panelCollapsible>
</ice:form>
</ui:composition>
Для отображения адреса проживания используется компонет <ice:gMap ../>, который отображает Google map карту.
В итоге у нас должно было получиться что-то подобное:

Пишите. Спрашивайте. Отвечу.
Исходники
- Проект Netbeans 7.0
- Библиотеки
Читайте документацию, она намного обширней и в ней вы сможите познакомиться с основынми особенностями данных технологий.
Ресурсы:
1) facelets developer documentation
2) What`s New in JSF 2 ?
3) ICEFaces 2 tutorials