Ни для кого не секрет, что сохранять состояние между разными views в JSF это проблема. Иногда это решается при помощи скрытых полей (работает только внутри одной формы, если на странице несколько форм, то данные сохраяются только в одной из них), есть ребята, использующие для этого сессию. В общем такие решения могут пройти, если нет требования о поддержке многооконного интерфейса. К счастью, есть более элегантное решение - использование тэга t:saveState из библиотеки Tomahawk. Он позволяет сохранять состояние между разными views, если в каждом view есть t:saveState с одним и тем же бином.
Однако и здесь есть свои нюансы. О них мы поговорим в этой статье.
Предположим, что вам нужно отображать на странице объект message, и на странице несколько форм - естественно вам нужно сохранять объект сообщения пока вы находитесь в пределах этого view. Самый простой способ решения проблемы:
<t:saveState value="#{messageBean.id}"/>
А теперь добавим условий. Вам необходимо поддерживать несколько popup окон, на которых вы управляете разными сообщениями и эти окна вам нужно контролировать из главного окна, например при помощи javascript. Это значит, что конструкции вроде приведенной ниже использовать нельзя:
<h:actionLink target="_blank"
action="messageBean.displayMessage">
<f:attribute name="message" value="#{message}"/>
</h:actionLink>
потому что окно, открытое таким образом, контролировать из javascript нельзя.
Чтобы открыть окно с другим сообщением, можно воспользоваться проверенным решением - небольшим кусочком javascript и GET вместо POST.
<h:outputLink id="messageLink"
onclick="popupMessage('#{message.id}'); return false;"
value="javascript:void(0);" >
<h:outputText value="#{message.id}"/>
</h:outputLink>
и скрипт, который все это открывает:
function popupMessage(id) {
window.open(
'${pageContext.request.contextPath}/message.faces?id='
+ id, 'msg_' + id, "");
}
Это позволяет создать управляемое окно из javascript и показывать в разных окнах разные сообщения. Да, в faces-config.xml нужно добавить соответствующие настройки, чтобы id сообщения бралось из параметров запроса:
...
<managed-property>
<property-name>id</property-name>
<value>#{param.id}</value>
</managed-property>
...
что ж, похоже, что задача решена ... но ... не тут-то было. Если вы откроете всплывающее окошко первый раз, в нем отобразится нужная информация. Если вы нажмете на другую ссылку с другим id вы получите тот же самый messageBean.id, что и в первом запросе. Все дело в том, что t:saveState сохранил messageBean внутри view и еще где-то запомнил, что сейчас вы находитесь на этом view. Следовательно вызов всплывающего окна с тем же view приводит к десериализации уже существующего messageBean, несмотря на то, что в параметре запроса id имеет другое значение - система управления бинами просто не получает возможности сконфигурировать объект как нужно.
Решается проблема до безобразия просто, хоть и не очень красиво - в методе getId() из messageBean нужно добавить следующие строчки:
...
FacesContext ctx = FacesContext.getCurrentInstance();
ExternalContext eCtx = ctx.getExternalContext();
Map requestMap = eCtx.getRequestParameterMap();
String id = requestMap.get("id");
if (id != null) {
this.id = id;
}
return id;
...
Теперь, несмотря на то, что мы получаем десериализованный бин, мы каждый раз имеем новый id (если он конечно присутствует в параметрах запроса) и отдельные всплывающие окна теперь работают совершенно независимо.
