Возможно я предвзят, но технологию JSF (по собственному опыту и набитым шишкам) считаю довольно тормознутой и неприспособленной для оживленных бизнес приложений. Однако совершенно недавно вышел в Open Source некогда платный набор компонент от IceFaces и должен вам сказать, это что-то. А началось все вот с чего...
У нас, как обычно, начался новый проект. И как обычно мы начали изучать текущее положение дел на фронте AJAX для Java. Самым удачным решением, как нам показалось, было бы решение на GWT. Однако, кастомер считает иначе - у них, как обычно, созвали совет высоколобых архитекторов и те порешили, что будет только JSF. Агрументация обычная - это стандарт и никаких гвоздей. Так что, порывшись по JSF фреймворкам, не нашли лучшего. И оказалось, все совсем неплохо.
Итак, как выглядит работа с IceFaces? Очень просто, качаем любой из сэмплов от производителя и надстраиваем над тем, что есть. Сначала я попробовал рисовать JSP и понял, что здесь придется действовать в обход - как, например, в Swing или Echo или GWT. За основу берется JSP с вот таким примерно кодом:
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h"%>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f"%>
<%@ taglib uri="http://www.icesoft.com/icefaces/component" prefix="ice"%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Core 4.0</title>
<link rel="stylesheet" type="text/css" href="./xmlhttp/css/xp/xp.css" />
<link rel="stylesheet" type="text/css" href="./css/main.css" />
</head>
<body>
<f:view>
<ice:panelGroup id="container" binding="#{testController.master}"/>
</f:view>
</body>
</html>
В сеттере setMaster UIPanel привязывается к нужному свойству и можно тут же положить в панельку свои компоненты, созданные программно.
Типичная ошибка, которая довольно часто выкидывается при использовании API IceFaces:
java.lang.NullPointerException java.lang.String.endsWith(String.java:1296) com.icesoft.faces.application.D2DViewHandler.findComponent(D2DViewHandler.java:896) com.icesoft.faces.application.D2DViewHandler.findComponent(D2DViewHandler.java:771) com.icesoft.faces.application.D2DViewHandler.findComponent(D2DViewHandler.java:776) com.icesoft.faces.application.D2DViewHandler.findComponent(D2DViewHandler.java:776) com.icesoft.faces.application.D2DViewHandler.findComponent(D2DViewHandler.java:776) com.icesoft.faces.application.D2DViewHandler.findComponent(D2DViewHandler.java:776) com.icesoft.faces.webapp.http.core.ReceiveSendUpdates.service(ReceiveSendUpdates.java:39) com.icesoft.faces.webapp.http.core.ViewBoundServer.service(ViewBoundServer.java:65) com.icesoft.faces.webapp.http.core.RequestVerifier.service(RequestVerifier.java:44) com.icesoft.faces.webapp.http.common.standard.PathDispatcherServer$Matcher.serviceOnMatch(PathDispatcherServer.java:50) com.icesoft.faces.webapp.http.common.standard.PathDispatcherServer.service(PathDispatcherServer.java:19) com.icesoft.faces.webapp.http.servlet.ThreadBlockingAdaptingServlet.service(ThreadBlockingAdaptingServlet.java:19) com.icesoft.faces.webapp.http.servlet.EnvironmentAdaptingServlet.service(EnvironmentAdaptingServlet.java:63) com.icesoft.faces.webapp.http.servlet.MainSessionBoundServlet.service(MainSessionBoundServlet.java:139) com.icesoft.faces.webapp.http.servlet.SessionDispatcher.service(SessionDispatcher.java:53) com.icesoft.faces.webapp.http.servlet.PathDispatcher$Matcher.serviceOnMatch(PathDispatcher.java:52) com.icesoft.faces.webapp.http.servlet.PathDispatcher.service(PathDispatcher.java:29) com.icesoft.faces.webapp.http.servlet.MainServlet.service(MainServlet.java:82) javax.servlet.http.HttpServlet.service(HttpServlet.java:803) com.icesoft.faces.webapp.xmlhttp.BlockingServlet.service(BlockingServlet.java:46)
не забываем вызывать setId(String) у каждого созданного в коде элемента - хотя со стандартными компонентами JSF этого не требуется, IceFaces иногда забывает автоматически сгенерировать id - устанавливаем его руками.
Еще одна деталь. Если вы собираетесь использовать master-detail паттерн, используя таблицу с row listener и форму для редактирования выбранного объекта, то запомните - изменять значения в объекте формы можно только в фазе UPDATE_MODEL_VALUES. Делается это просто - в слушателе нужного ивента нужно просто сделать так:
public void rowSelected(RowSelectionEvent rowSelectionEvent) {
if (phaseHadArrived(rowSelectionEvent, UPDATE_MODEL_VALUES)) {
// do something with my model
}
}
/**
* Checks if current event phase matches the desired phase and if so returns <code>true</code>.
* Otherwise <code>false</code> is returned and event is queued.
* @param event
* @param desiredPhase
* @return <code>true</code> if the desired phase had arrived
*/
public static boolean phaseHadArrived(FacesEvent event, PhaseId desiredPhase) {
PhaseId phaseId = event.getPhaseId();
if (phaseId.equals(desiredPhase)) {
return true;
}
event.setPhaseId(desiredPhase);
event.queue();
return false;
}
иными словами, отложить выполнение события до нужной нам фазы.
Для упрощения работы с API IceFaces я даже написал небольшой Builder, но об этом чуть позже, когда сам билдер будет достаточно стабилен :)
