четверг, 21 марта 2013 г.

Primefaces: selectors. Селекторы в ajax запросах

Primefaces продолжает радовать интересными фичами.

Начиная с версии 3.3 появились селекторы для определения контролов, которые надо обновить или запроцессить. Для реализации используется одноимённый механизм из jQuery, соответственно, здесь полный мануал к тому, какие возможности при написании селекторов нам предоставлены. Непосредственный пример для primefaces ниже.

Постановка задачи:
Есть некая таблица (или другой UINamingContainer), в которой отображаются иконки со статусами чего-либо. При этом статусы нужно обновлять, скажем, раз в 5 секунд.

Решение:
Описываем таблицу, в ней одной из колонок будет картинка со статусом (зелёная или красная).
<p:dataTable id="bigTable"
    var="item"
    value="#{myBigController.objectList}">
...
    <p:column id="operationTypeIcon">
        <f:facet name="header">
            <h:outputText value="Статус" />
        </f:facet>
        <p:graphicImage id="statusImage"
            styleClass="for-update"
            value="/resources/images/#{statusController.getStatusImageName(item)}"/>
    </p:column>
...

</p:dataTable>
Для автообновления будем использовать компонент primefaces p:poll. В его параметрах указывается:
  • интервал, через который нужно выполнять аякс-запрос;
  • метод-листенер, который нужно выполнять;
  • идентификаторы компонентов, которые нужно обновить.
Соответственно, раньше нам приходилось обновлять, например, целиком таблицу (пример ниже), хотя на самом деле достаточно обновить только иконки статусов.
<p:poll interval="5" widgetVar="statusPoll"
    global="false"
    listener="#{statusController.refreshStatuses}"
    update="bigTable"
    process="@this" />
И вот теперь есть возможность указать некие правила, по которым будет определено, какие компоненты надо обновлять. При этом в критериях можно оперировать и css-классами, и типами контролов.
Например:
update="@(.ui-panel :input:not(select))"
Здесь мы определяем, что надо обновить все контролы с css-классом ui-panel и типом input, но не select-контролы (к последним, например, относится комбобокс).

Как это поможет нам при решении задачи?
Будем обновлять только те элементы, которые обладают css-классом for-update (не зря же я его так назвал?).
<p:poll interval="5" widgetVar="statusPoll"
    global="false"
    listener="#{statusController.refreshStatuses}"
    update="@(.for-update)"
    process="@this" />
На что обратить внимание?
Есть один важный аспект, который несправедливо опускается в мануалах: у компонентов, которые должны найтись селектором, необходимо вручную проставлять идентификаторы. Если не сделать этого, то они будут генерироваться слишком поздно и primefaces не сможет подставить их в параметры аякс-запроса. Так что обратите внимание на:
<p:graphicImage id="statusImage" ...>

среда, 20 марта 2013 г.

j_security_check request и генерация идентификаторов контролов

Наткнулся на интересную ошибку (особенность?) работы JSF.
Честно говоря, особого желания нет копаться до истины и "а почему же всё-таки так", но случай интересный.

История возникновения
На странице, куда осуществляется редирект после некорректного логина было несколько контролов (в моём случае h:link на попробовать ещё раз и на страницу восстановления пароля). Страница очень простая, никаких аяксов, лишь эти 2 ссылки и статический текст о том, что авторизация не удалась.

Запускаем, вводим неверный пароль, вместо страницы ошибки входа видим в браузере internal server error 500, а в логе:
Caused by: java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
 at java.util.ArrayList.RangeCheck(ArrayList.java:547)
 at java.util.ArrayList.get(ArrayList.java:322)
 at javax.faces.component.AttachedObjectListHolder.restoreState(AttachedObjectListHolder.java:165)
 at javax.faces.component.UIComponentBase.restoreState(UIComponentBase.java:1560)
А если не поленимся обновить страницу, то всё отобразится корректно.

Кто виноват и что делать?
Оказалось, всё дело в том, что стандартный j_security_check request обрабатывается напрямую веб-контейнером, а не JSF. Собственно, это не секрет, секрет в том, что из этого следует: на странице ошибки входа JSF не сгенерирует id-шников для контролов. Поэтому всё, что нужно, это явно указать идентификаторы:
...
    try login again
    restore your password


Почему после обновления страницы всё работает?
Потому что это будет обычный запрос "файла с хтмл" на сервере, а не j_security_check, поэтому его обработает движок JSF.

Почему такой странный лог restoreState : IndexOutOfBoundsException, если всё дело в идентификаторах контролов?
Не знаю, как раз в этом разбираться у меня желания не было. Надеюсь, разработчики это пофиксят со временем.