Spring Web Flow — отображение представлений (rendering views)
5. Отображение представлений
5.1. Введение
Эта глава покажет вам как использовать элемент view-state для отрисовки представлений в пределах потока.
5.2. Определение view states
Используйте элемент view-state для определения шага в потоке, в котором будет отрисовано представление и будет ожидаться событие пользователя для возобновления потока:
1 2 3 |
<view-state id="enterBookingDetails"> <transition on="submit" to="reviewBooking" /> </view-state> |
По соглашению view-state отображает свой id в шаблон представления в директорию, в которой находится данный поток. Например из примера выше, состояние должно быть отрисовано в /WEB-INF/hotels/booking/enterBookingDetails.xhtml, если поток находится в директории /WEB-INF/hotels/booking/.
Ниже приведен пример структуры пакетов, показывающих представления и другие ресурсы, такие как message bundles, находящиеся в том же месте где и их поток:
5.3. Указание идентификаторов отображения
Используйте атрибут view для явного указания какое представление нужно отрисовать для данного id.
5.3.1. Путь к представлению конкретного view id относительно потока
Путь для представления определенного view id может быть указан относительно рабочей директории потока:
1 |
<view-state id="enterBookingDetails" view="bookingDetails.xhtml"> |
5.3.2. Абсолютный путь к представлению конкретного view id
Путь к представлению определенного view id может быть абсолютным в корневой директории webapp:
1 |
<view-state id="enterBookingDetails" view="/WEB-INF/hotels/booking/bookingDetails.xhtml"> |
5.3.3. Логическое указание представления конкретного view id
В некоторых фреймворках, таких как Spring MVC, view id может иметь логический идентификатор, который будет вычислен фреймворком:
1 |
<view-state id="enterBookingDetails" view="bookingDetails"> |
Смотрите раздел интеграции с Spring MVC для более подробной информации, как интегрировать инфраструктуру Spring MVC ViewResolver.
5.4. Область видимости представления
view-state выделяет новую область видимости viewScope на входе. Эта область видимости может быть доступна из этого view-state для выделения переменных, которые должны жить на протяжении жизненного цикла этого состояния. Эта область видимости полезна для управления объектами на протяжении серии запросов из этого же отображения, например — Ajax запросы. view-state уничтожает область видимости viewState при выходе из состояния.
5.4.1. Определение переменных представления
Используйте тег var для определения переменных представления. Так же как и переменные потока, любая @Autowired ссылка будет автоматически восстановлена при возврате к этому view-state.
1 |
<var name="searchCriteria" class="com.mycompany.myapp.hotels.SearchCriteria" /> |
5.4.2. Назначение переменных представления
Используйте тег on-render, чтобы назначить переменную для результата действия до отрисовки представления:
1 2 3 |
<on-render> <evaluate expression="bookingService.findHotels(searchCriteria)" result="viewScope.hotels" /> </on-render> |
5.4.3. Управление объектами в области видимости представления
Объекты в области видимости представления часто управляются сериями запросов из одного и того же представления. Следующий пример демонстрирует состояние, в котором отображается страница с списком результатов поиска. Этот список обновляется в области видимости прежде, чем произойдет отрисовка представления. Асинхронный обработчик событий обновляет текущие данные страницы, а затем перерисовывает фрагмент с результатами поиска.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<view-state id="searchResults"> <on-render> <evaluate expression="bookingService.findHotels(searchCriteria)" result="viewScope.hotels" /> </on-render> <transition on="next"> <evaluate expression="searchCriteria.nextPage()" /> <render fragments="searchResultsFragment" /> </transition> <transition on="previous"> <evaluate expression="searchCriteria.previousPage()" /> <render fragments="searchResultsFragment" /> </transition> </view-state> |
5.5. Вычисление действий при рендере представления
Используйте элемент on-render для вычисления одного или более действий до отрисовки представления. Действия при обновлении страницы выполняются при инициализации рендера так же, как и при любых других последующих обновлениях страницы, включая частичное обновление представления.
1 2 3 |
<on-render> <evaluate expression="bookingService.findHotels(searchCriteria)" result="viewScope.hotels" /> </on-render> |
5.6. Связывание с моделью
Используйте атрибут model для объявление модели объекта, с которым связано представление. Атрибут обычно используется в соединении представлений, с объектами, которые выполняют управление данными, например формой. Атрибут включает связь данных формы и проведение валидации, которая будет управляться из метаданных из вашей модели объекта:
Следующий пример определяет состояние enterBookingDetails и управляет моделью booking:
1 |
<view-state id="enterBookingDetails" model="booking"> |
Модель может быть объектом, доступным из области видимости, такой как flowScope или viewScope. Указание модели model запускает следующее поведение при возникновении событий представления:
- Для связки модель-представление. Введенные пользователям значения будут связаны с моделью при обратной передаче данных.
- Валидации модели. Если объект модели требует валидации, то после связывания будет встроен алгоритм проверки.
Для событий потока, которые должны быть сгенерированы для управления состояниями перехода, связывание с моделью должно пройти успешно. Иначе, в случае ошибки связывания, будет выполнено обновление представления для того, чтобы пользователь мог внести свои правки.
5.7. Выполнение преобразования типов
Когда запрашиваемые параметры используются для заполнения модели (как правило называется связыванием данных), то преобразование типов требуется для парсинга строковых значений параметров запроса до установки свойств целевой модели. Преобразование типов доступны по умолчанию для многих типов Java, таких как числа, примитивные типы, перечисления и даты. Пользователи также имеют возможность зарегистрировать собственную логику преобразования типов для определенных пользователем типов и переопределить конвертеры, использующиеся по умолчанию.
5.7.1. Варианты преобразования типов
Начиная с версии Spring Web Flow 2.1 используется преобразование типов и форматирование, введенное в Spring 3 почти для всех нужд по преобразованию типов. В прошлом, Web Flow веб-приложения использовали механизм преобразования типов отличный от такового в Spring MVC, который основывался на абстракции java.beans.PropertyEditor. Spring 3 предоставил современную альтернативу PropertyEditors для преобразования типов, которая фактически повлияла на систему преобразования типов Spring Web Flow. Следовательно, пользователи Spring Web Flow смогут естественным образом работать с новым преобразованием типов от Spring 3. Другим очевидным и очень важным преимуществом является то, что единый механизм преобразования типов может быть использован совместно Spring MVC и Spring Web Flow.
5.7.2. Переход на версию преобразование типов и форматирование Spring 3
Что это означает для уже существующих приложений? Существующие приложения, скорее всего, регистрировали свои собственные конвертеры типов org.springframework.binding.convert.converters.Converter через подкласс DefaultConversionService доступный в Spring Binding. Эти конвертеры могут быть зарегистрированы как и прежде. Они будут адаптированы как типы Spring 3 GenericConverter и зарегистрированы в экземпляре Spring 3 org.springframework.core.convert.ConversionService. Другими словами существующие конвертеры будут запускаться через службу преобразования типов Spring 3.
Единственное исключение из этого правила — в именованных конвертерах, на которые могут ссылаться из элемента binding в каком-либо view-state:
1 2 3 4 5 6 7 |
public class ApplicationConversionService extends DefaultConversionService { public ApplicationConversionService() { addDefaultConverters(); addDefaultAliases(); addConverter("customConverter", new CustomConverter()); } } |
1 2 3 4 5 |
<view-state id="enterBookingDetails" model="booking"> <binder> <binding property="checkinDate" required="true" converter="customConverter" /> </binder> </view-state> |
Именованные конвертеры больше не поддерживаются и не могут быть использованы с сервисом преобразования типов из Spring 3. Поэтому такие конвертеры не будут адаптированы и продолжат работать как и раньше, т.е. не будут включать в себя преобразование типов Spring 3. Тем не менее этот механизм является устаревшим и рекомендуется отдавать предпочтение преобразованию типов и форматированию Spring 3.
Так же обратите внимание, что существующий Spring Binding DefaultConversionService больше не регистрирует никаких конвертеров по умолчанию. Вместо этого Web Flow полагается на стандартные конвертеры и форматирование из Spring 3.
Хотя существующие приложения могут работать без каких-либо изменений, мы рекомендуем двигаться в сторону унификации преобразования типов, используемого в Spring MVC и Spring Web Flow.
5.7.3. Настройка преобразования типов и форматирования
В Spring MVC переменная экземпляра FormattingConversionService создается автоматически через собственное пространство имен Spring MVC:
1 2 3 4 5 6 7 8 9 10 11 |
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation=" http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd"> <mvc:annotation-driven/> |
Внутри это сделано с помощью FormattingConversionServiceFactoryBean, который регистрирует множество конвертеров и средств форматирования по умолчанию. Вы можете настроить экземпляр службы преобразования, используемый Spring MVC с помощью атрибута conversation-service:
1 |
<mvc:annotation-driven conversion-service="applicationConversionService" /> |
В Web Flow экземпляр Spring Binding DefaultConversionService, который создается автоматически, не регистрирует каких либо конвертеров. Вместо этого это делегируется экземпляру FormattingConversionService для всех необходимых преобразований типов. По умолчанию это не тот же самый экземпляр FormattingConversionService, который используется в Spring 3. Однако это не будет иметь значения до тех пор, пока вы не зарегистрируете собственные средства форматирования.
DefaultConversionService используемый Web Flow, может быть настроен с помощью элемента flow-builder-services:
1 |
<webflow:flow-builder-services id="flowBuilderServices" conversion-service="defaultConversionService" /> |
Подключите следующие этапы по порядку, для того, чтобы зарегистрировать собственные средства форматирования для совместного использования в Spring MVC и Spring Web Flow.
Создайте класс для указания собственного средства форматирования:
1 2 3 4 5 6 7 8 |
public class ApplicationConversionServiceFactoryBean extends FormattingConversionServiceFactoryBean { @Override protected void installFormatters(FormatterRegistry registry) { // ... } } |
Настройте его для использования в Spring MVC:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation=" http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd"> <mvc:annotation-driven conversion-service="applicationConversionService" /> <!-- Alternatively if you prefer annotations for DI: 1. Add @Component to the factory bean. 2. Add a component-scan element (from the context custom namespace) here. 3. Remove XML bean declaration below. --> <bean id="applicationConversionService" class="somepackage.ApplicationConversionServiceFactoryBean"> |
Подключите Web Flow DefaultConversionService к тому же «applicationConversionService» бину, который использует Spring MVC:
1 2 3 4 5 6 7 |
<webflow:flow-registry id="flowRegistry" flow-builder-services="flowBuilderServices" ... /> <webflow:flow-builder-services id="flowBuilderServices" conversion-service="defaultConversionService" ... /> <bean id="defaultConversionService" class="org.springframework.binding.convert.service.DefaultConversionService"> <constructor-arg ref="applicationConversionSevice"/> </bean> |
Конечно, так же можно смешивать и сочетать это. Регистрируйте новые средства форматирования Spring 3 через «applicationConversionService«. Регистрируйте существующие Spring Binding конвертеры типов через «defaultConversionService «.
5.7.4. Работа с преобразованием типов и форматированием Spring 3
Важно понимать различие между конвертером типов и форматированием.
Конвертеры типов в Spring 3, предоставленные в org.springframework.core, предназначены для общего преобразования типов между любыми двумя типами объекта. В дополнение к наиболее простому конвертеру типов существует два других интерфейса: ConverterFactory и GenericConverter.
Системы форматирования в Spring 3, предоставленные в org.springframework.context, имеют более специализированное назначение для представления объектов в виде строк. Интерфейс Formatter расширяет интерфейсы Printer и Parser для преобразования объекта в строку и обратно.
Веб-разработчики найдут интерфейс Formatter более подходящим, потому что он соответствует потребностям преобразованию типов в веб-приложениях.
Обращаем внимание!
Важно, что преобразование Объект-в-Объект это обобщение от более конкретного преобразования Объект-в-Строка. На самом деле, в конце концов форматтеры, зарегистрированные как типы GenericConverter c Spring’овым GenericConversationService делают их эквивалентными любому другому конвертеру.
5.7.5. Аннотации форматирования
Одной из лучших особенностей нового преобразования типов является возможность использовать аннотации в краткой форме для лучшего контроля над форматированием. Аннотации могут быть размещены на атрибутах модели и на аргументах методов Controller, которые отображаются на запросы. По умолчанию Spring предоставляет две аннотации: NumberFormat и DateTimeFormat, но вы можете создать свою собственную аннотацию и использовать её для указания собственного алгоритма форматирования.
5.7.6. Работа с датами
Аннотация DateTimeFormat подразумевает использование Joda Time. Если она указана на пути classpath, использование этой аннотации включается автоматически. По умолчанию ни Spring MVC, ни Web Flow не регистрируют любой другой конвертер или форматтер даты. Поэтому важно, чтобы приложения регистрировали системы форматирования, созданные пользователем, чтобы указать как по умолчанию будут отображаться и парситься даты. С другой стороны, аннотации DateTimeFormat обеспечивает более точный контроль там, где необходимо отклониться от заданного по умолчанию.
Для получения более подробной информации о работе с преобразованием типов и форматированием Spring 3, пожалуйста, обратитесь к соответствующим разделам документации Spring.
5.8. Подавление связывания
Используйте атрибут bind для подавления связывания с моделью и валидацией для конкретного события представления. Следующий простой пример покажет как подавить связывание при вызове события cancel:
1 2 3 4 |
<view-state id="enterBookingDetails" model="booking"> <transition on="proceed" to="reviewBooking"> <transition on="cancel" to="bookingCancelled" bind="false" /> </view-state> |
5.9. Явное указание связывания
Используйте элемент binder для настройки точной модели с которой будет связано представление. Это особенно полезно в окружении Spring MVC для ограничения «доступных полей» в представлении.
1 2 3 4 5 6 7 8 9 10 |
<view-state id="enterBookingDetails" model="booking"> <binder> <binding property="creditCard" /> <binding property="creditCardName" /> <binding property="creditCardExpiryMonth" /> <binding property="creditCardExpiryYear" /> </binder> <transition on="proceed" to="reviewBooking" /> <transition on="cancel" to="cancel" bind="false" /> </view-state> |
Если элемент binder не указан, все публичные свойства модели подходят для связывания с представлением. Если элемент binder указан, то будут использоваться только указанные для связывания свойства.
Каждое связывание так же может применить конвертер для форматирования свойства модели для отображения согласно настройкам пользователя. Если конвертер не определен, то будет использован конвертер, заданный по умолчанию.
1 2 3 4 5 6 7 8 9 10 11 12 |
<view-state id="enterBookingDetails" model="booking"> <binder> <binding property="checkinDate" converter="shortDate" /> <binding property="checkoutDate" converter="shortDate" /> <binding property="creditCard" /> <binding property="creditCardName" /> <binding property="creditCardExpiryMonth" /> <binding property="creditCardExpiryYear" /> </binder> <transition on="proceed" to="reviewBooking" /> <transition on="cancel" to="cancel" bind="false" /> </view-state> |
В примере выше, конвертер shortDate связан с свойствами checkinDate и checkoutDate. Пользовательский конвертер может быть зарегистрирован с помощью ConversionService.
Каждому связыванию может быть установлен атрибут required. В результате будет выходить ошибка валидации в случае, если пользователь предоставит значение null при отправке формы:
1 2 3 4 5 6 7 8 9 10 11 12 |
<view-state id="enterBookingDetails" model="booking"> <binder> <binding property="checkinDate" converter="shortDate" required="true" /> <binding property="checkoutDate" converter="shortDate" required="true" /> <binding property="creditCard" required="true" /> <binding property="creditCardName" required="true" /> <binding property="creditCardExpiryMonth" required="true" /> <binding property="creditCardExpiryYear" required="true" /> </binder> <transition on="proceed" to="reviewBooking"> <transition on="cancel" to="bookingCancelled" bind="false" /> </view-state> |
В примере выше, все связанные свойства имею атрибут required. Если одно или более пустое поле ввода будет связано, то произойдет ошибка валидации и представление будет обновлено с этими ошибками.
5.10. Валидация модели
Валидация модели управляется ограничениями, указанными по отношению к модели объекта. Web Flow поддерживает соблюдение этих ограничений как программно, так и декларативно с использованием аннотаций JSR-303 Bean Validation.
5.10.1. JSR-303 Bean Validation
Web Flow обеспечивает встроенную поддержку JSR-303 Bean Validation API, построенную на эквивалентной поддержке из Spring MVC. Чтобы включить валидацию JSR-303, необходимо настроить flow-builder-service с использованием LocalValidatorFactoryBean Spring MVC:
1 2 3 4 5 |
<webflow:flow-registry flow-builder-services="flowBuilderServices" /> <webflow:flow-builder-services id="flowBuilderServices" validator="validator" /> <bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" /> |
С учетом сказанного выше, настроенный валидатор будет применён ко всем моделям атрибутов после связывания данных.
Обратите внимание, что валидация JSR-303 и валидация согласно соглашению (пояснение в следующем разделе) не являются взаимоисключающими. Другими словами Web Flow сможет применять все имеющиеся механизмы валидации.
Частичная валидация
JSR-303 Bean Validation поддерживает частичную валидацию с помощью групп валидации. Пример:
1 2 3 |
@NotNull @Size(min = 2, max = 30, groups = State1.class) private String name; |
В описании потока вы можете указать validation-hints для состояния (view-state) или перехода (transition) и они будут проверятся этими группами. Пример:
1 |
<view-state id="state1" model="myModel" validation-hints="'group1,group2'"> |
Атрибут validation-hints, как показано в примере выше, вычислит значение, заключенное в одинарные кавычки, где каждый член разделен запятой. Для обработки этого выражения используется ValidationHintResolver. По умолчанию BeanValidationHintResolver пытается распределить эти строки к Class-based бинам групп валидации. Для этого ищется соответствие по внутренним типам в модели или у родителя.
Для приведенного примера с org.example.MyModel и внутренними типами Group1 и Group1 достаточно подставить простые имена типов, т.е. «group1 » и «group2 «. Вы также можете предоставить их полные имена.
hint с значением по умолчанию имеет особое значение и передается в группу проверки по умолчанию в Bean Validation javax.validation.groups.Default.
Созданный пользователем ValidationHintResolver, при необходимости, может быть настроен с помощью свойства validationHintResolver элемента flow-builder-services:
1 2 3 4 |
<span class="hl-tag"><webflow:flow-registry</span> <span class="hl-attribute">flow-builder-services</span>=<span class="hl-value">"flowBuilderServices"</span><span class="hl-tag"> /></span> <span class="hl-tag"><webflow:flow-builder-services</span> <span class="hl-attribute">id</span>=<span class="hl-value">"flowBuilderServices"</span> <span class="hl-attribute">validator</span>=<span class="hl-value">".."</span> <span class="hl-attribute">validation-hint-resolver</span>=<span class="hl-value">".."</span><span class="hl-tag"> /> </span> |
5.10.2. Программная валидация
Выполнить проверку программно можно двумя способами. Первый заключается в реализации логики проверки в модели объекта. Второй — в реализации внешнего Validator. Оба способа предоставят вам ValidationContext для записи сообщений об ошибках и получению доступа к информации о текущем пользователе.
Реализация метода модели валидации
Определение логики проверки в модели объекта — это самый простой способ для проверки его состояния. После того, как такая логика построена в соответствии со стандартами Web Flow, Web Flow будет автоматически вызывать ее во время жизненного цикла view-state при обратной передачи данных. По соглашению, используемому Web Flow, позволяет построить модель проверки с помощью view-state, что позволяет легко проверить подмножество свойств модели, которые доступны для редактирования в этом состоянии. Для этого нужно просто создать публичный метод с именем validate${state}, где ${state} — это идентификатор view-state, в котором необходимо запустить проверку. Пример:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public class Booking { private Date checkinDate; private Date checkoutDate; ... public void validateEnterBookingDetails(ValidationContext context) { MessageContext messages = context.getMessageContext(); if (checkinDate.before(today())) { messages.addMessage(new MessageBuilder().error().source("checkinDate"). defaultText("Check in date must be a future date").build()); } else if (!checkinDate.before(checkoutDate)) { messages.addMessage(new MessageBuilder().error().source("checkoutDate"). defaultText("Check out date must be later than check in date").build()); } } } |
В приведенном выше примере, при выполнении перехода, вызванного в enterBookingDetails view-state, в котором изменяется модель Booking, Web Flow вызовет метод validateEnterBookingDetails(ValidationContext) автоматически, если проверка не была подавлена для этого перехода. Пример такого view-state показан ниже:
1 2 3 |
<view-state id="enterBookingDetails" model="booking"> <transition on="proceed" to="reviewBooking"> </view-state> |
Можно определить любое количество методов проверки. Как правило, поток изменяет модель для нескольких представлений. В этом случае, метод проверки будет определен для каждого view-state, в котором должна быть запущена проверка.
Реализация Validator
Второй способ заключается в определении отдельного объекта — Validator, который будет проверять вашу модель объекта. Чтобы сделать это, необходимо сначала создать класс, имя которого соответствует шаблону ${model}Validator, где ${model} — это модель формы для модели выражения, такой как booking. Затем нужно определить публичный метод с именем validate${state}, где $ {state} — идентификатор вашего view-state, такого, как enterBookingDetails. Класс должен быть определен как Spring bean. Можно определить любое количество методов проверки. Например:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
@Component public class BookingValidator { public void validateEnterBookingDetails(Booking booking, ValidationContext context) { MessageContext messages = context.getMessageContext(); if (booking.getCheckinDate().before(today())) { messages.addMessage(new MessageBuilder().error().source("checkinDate"). defaultText("Check in date must be a future date").build()); } else if (!booking.getCheckinDate().before(booking.getCheckoutDate())) { messages.addMessage(new MessageBuilder().error().source("checkoutDate"). defaultText("Check out date must be later than check in date").build()); } } } |
В приведенном выше примере, когда переход запускается в view-state enterBookingDetails, в котором редактируется модель Booking, Web Flow вызовет метод validateEnterBookingDetails(Booking, ValidationContext) автоматически, если проверка не была подавлена для этого перехода.
Валидатор также может принимать объект Spring MVC — Errors , который необходим для вызова существующих Spring валидаторов.
Валидаторы должны быть зарегистрированы как Spring бины, с использованием шаблона присвоения имени — ${model}Validator, чтобы они могли быть обнаружены и вызваны автоматически. В приведенном выше примере, когда Spring 2.5 сканирует classpath, то он обнаружит аннотацию @Component и автоматически зарегистрирует его в качестве бина с именем bookingValidator. Следовательно, в любое время когда модель booking должна быть проверена, будет вызван экземпляр bookingValidator.
Метод валидации по умолчанию
Класс Validator так же может определить метод, названный validate, не связанный (по соглашению) с каким-либо конкретным состоянием view-state.
1 2 3 4 5 6 |
@Component public class BookingValidator { public void validate(Booking booking, ValidationContext context) { //... } } |
В приведенном выше примере метод проверки будет вызываться каждый раз, когда будет необходимо проверить модель типа Booking (если проверка не была подавлена для этого перехода). При необходимости метод по умолчанию также может быть вызван в дополнение к конкретно указанному методу в view-state. Рассмотрим следующий пример:
1 2 3 4 5 6 7 8 9 |
@Component public class BookingValidator { public void validate(Booking booking, ValidationContext context) { //... } public void validateEnterBookingDetails(Booking booking, ValidationContext context) { //... } } |
В приведенном выше примере метод validateEnterBookingDetails будет вызван первым. Метод по умолчанию validate будет вызван следующим.
5.10.3. ValidationContext
ValidationContext позволяет получить MessageContext для записи сообщения во время проверки. Он также предоставляет информацию о текущем пользователе, например, созданным userEvent и текущим пользователем с проверкой его прав. Эта информация может быть использована для настройки логики проверки на основе того, какая кнопка или ссылка была активирована в пользовательском интерфейсе, или в том, где пройдена аутентификация. Смотрите API Javadocs для ValidationContext, чтобы получить дополнительную информацию.
5.11. Подавление валидации
Используйте атрибут validate для подавления проверки модели для конкретных событий view events:
1 2 3 4 |
<view-state id="chooseAmenities" model="booking"> <transition on="proceed" to="reviewBooking"> <transition on="back" to="enterBookingDetails" validate="false" /> </view-state> |
В этом примере проверка при связывании данных с моделью должна происходить при событии back, но будет подавлена.
5.12. Выполнение view transitions
Определите один или несколько элементов transition для обработки пользовательских событий, которые могут произойти в представлении. Переход может перевести пользователя в другое представление, или он может просто выполнить действие и повторно обновить текущее представление. Переход может также выполнить частичное обновление представления, которое называется «фрагменты» (fragments) при обработке событий Ajax. Наконец, можно определить «глобальные» переходы («global» transitions), которые будут видны для всех представлений.
Реализация переходов в состоянии проиллюстрирована в следующих разделах.
5.12.1. Transition actions
view-state transition может выполнить одно или несколько действий, прежде чем будет выполнен переход. Эти действия могут возвращать в качестве результата ошибки, чтобы была возможность предотвратить переход и выход из текущего view-state. Если в результате действию будут получены ошибки, то представление будет обновлено с отображением соответствующей информацией для пользователя.
Если действия при переходе вызываются из простого Java метода (POJO), то вызываемый метод должен возвращать false для предотвращения перехода и выхода из состояния. Эта техника может быть использована для обработки исключений, выброшенными методами на уровне обслуживания. Пример ниже вызывает действие, которое вызывает сервис-метод и обрабатывает исключительную ситуацию:
1 2 3 |
<transition on="submit" to="bookingConfirmed"> <evaluate expression="bookingAction.makeBooking(booking, messageContext)" /> </transition> |
1 2 3 4 5 6 7 8 9 10 11 12 |
public class BookingAction { public boolean makeBooking(Booking booking, MessageContext context) { try { bookingService.make(booking); return true; } catch (RoomNotAvailableException e) { context.addMessage(new MessageBuilder().error(). .defaultText("No room is available at this hotel").build()); return false; } } } |
Обратите внимание:
Когда определено больше одного действия при переходе, и если хотя бы один из них возвращает в результате ошибку, то остальные действия не будут выполнены. Если вам нужно гарантировать, чтобы результат одного из действий при переходе не влиял на вычисление остальных, то необходимо определить одиночное действие при переходе, которое будет вызывать метод, инкапсулирующий логику всех остальных действий.
5.12.2. Global transitions
Используйте элемент потока global-transition для создания переходов, которые применяются во всех представлениях. Глобальные переходы часто используются для обработки глобальных ссылок в меню, являющимися частью макета.
1 2 3 4 |
<global-transitions> <transition on="login" to="login" /> <transition on="logout" to="logout" /> </global-transitions> |
5.12.3. Event handlers
Переходы без цели могут быть определены из view-state. Такие переходы называются обработчиками событий («event handlers»):
1 2 3 |
<transition on="event"> <!-- Handle event --> </transition> |
Этот обработчик событий не изменяет состояние потока.Он просто выполняет его действия и обновляет текущее представление или один или более фрагментов этого представления.
5.12.4. Rendering fragments
Используйте элемент render в пределах перехода с запросом с частичным обновлением представления после обработки события:
1 2 3 4 |
<transition on="next"> <evaluate expression="searchCriteria.nextPage()" /> <render fragments="searchResultsFragment" /> </transition> |
Атрибут fragments должен ссылаться на один или несколько id элемента(ов) представления, которые должны быть обновлены. Укажите множество элементов для обновления с помощью разделения их через запятую.
Такое частичное обновление используется в событиях Ajax для обновления конкретных областей отображения.
5.13. Работа с сообщениями
MessageContext Spring Web Flow — это API для записи сообщений в ходе выполнений потоков. Простые текстовые сообщения могут быть добавлены в контексте, а также интернационализированны с использованием Spring MessageSource. Сообщения отрисовываются представлением и автоматически переходят при выполнении редиректа в потоке. Существует три типа сообщений: info, warnings и error. Кроме того, существует MessageBuilder, который позволяет свободно создавать сообщения.
5.13.1. Добавление простого текста сообщений
1 2 3 4 5 6 7 8 |
MessageContext context = ... MessageBuilder builder = new MessageBuilder(); context.addMessage(builder.error().source("checkinDate") .defaultText("Check in date must be a future date").build()); context.addMessage(builder.warn().source("smoking") .defaultText("Smoking is bad for your health").build()); context.addMessage(builder.info() .defaultText("We have processed your reservation - thank you and enjoy your stay").build()); |
5.13.2. Добавление интернационализированных сообщений
1 2 3 4 5 6 |
MessageContext context = ... MessageBuilder builder = new MessageBuilder(); context.addMessage(builder.error().source("checkinDate").code("checkinDate.notFuture").build()); context.addMessage(builder.warn().source("smoking").code("notHealthy") .resolvableArg("smoking").build()); context.addMessage(builder.info().code("reservationConfirmation").build()); |
5.13.3. Использование message bundles
Интернационализированные сообщение определяются в message bundles и доступны в Spring MessageSource. Для создание message bundle для конкретного потока, просто определите файл messages.properties в директории потока. Создайте файл по умолчанию messages.properties и файлы .properties для каждой поддерживаемой локали (например messages_ru.properties).
1 2 3 4 |
#messages.properties checkinDate=Check in date must be a future date notHealthy={0} is bad for your health reservationConfirmation=We have processed your reservation - thank you and enjoy your stay |
С помощью переменной EL resourceBundle, можно получить доступ к ресурсам сообщения (message resources) в пределах представления или потока:
1 |
<h:outputText value="#{resourceBundle.reservationConfirmation}" /> |
5.13.4. Понимание созданных системных сообщений
Есть несколько мест, где сам Web Flow будет генерировать сообщения для отображения их пользователю. Одним из важных мест является привязка данных к модели из представления. При возникновении ошибки связывания, например, такой как ошибка преобразования типа, Web Flow будет отображать эту ошибку с сообщением, извлеченным автоматически из вашего resource bundle. Для поиска сообщение, которое необходимо отобразить, Web Flow будет пытаться использовать ключи ресурсов, которые содержат код ошибки при связывании и целевое имя свойства (другими словами, будет вытаскивать сообщение из resource bundle по ключу (имени) ошибки).
В качестве примера, рассмотрим связывание свойства checkinDate объекта Booking. Предположим, что пользователь вводит в буквенную строку. В этом случае будет выброшена ошибка преобразования типа. Web Flow отобразит код ошибки ‘typeMismatch‘ в сообщении, найденным в первом resource bundle с следующим ключом:
1 |
booking.checkinDate.typeMismatch |
Первая часть ключа — краткое имя модели класса. Вторая часть ключа — имя свойства. Третья часть — код ошибки. Такая конструкция позволяет выполнять поиск уникального сообщения для отображения пользователю при получении ошибки связывания модели. Это сообщение может выглядеть так:
1 |
booking.checkinDate.typeMismatch=The check in date must be in the format yyyy-mm-dd. |
Если не будет найдено сообщение по такому ключу, то произойдет попытка использования более общего ключа. Этот ключ упростит код ошибки. Имя поля свойства будет предоставляться в качестве аргумента сообщения.
1 |
typeMismatch=The {0} field is of the wrong type. |
5.14. Отображение всплывающих сообщений
Используйте атрибут popup для рендера представления в всплывающем модальном диалоговом окне:
1 |
<view-state id="changeSearchCriteria" view="enterSearchCriteria.xhtml" popup="true"> |
При использовании Web Flow и Spring Javascript, нет необходимости в коде на стороне клиента для отображения всплывающих диалогов. Web Flow отправит ответ (response) на запрос клиента о перенаправлении на представление с всплывающим окном, и запрос выполнится на клиентской стороне.
5.15. Возвращение к исходному представлению
По умолчанию при выходе из view state и переходе в новое состояние, вы можете вернуться в предыдущее состоянии с помощью кнопки браузера «Назад». Политика сохранения истории состояний настроена для каждого перехода с использованием атрибута history.
5.15.1. Сброс истории
Установите в атрибуте history attribute значение discard для предотвращения возможности возврата к исходному представлению:
1 |
<transition on="cancel" to="bookingCancelled" history="discard"> |
5.15.2. Прекращение истории
Установите атрибут history как invalidate для предотвращения возможности возврата к любым показанным в прошлом представлениям:
1 |
<transition on="confirm" to="bookingConfirmed" history="invalidate"> |