Как стать автором
Обновить

Руководство: Thymeleaf + Spring. Часть 3

Время на прочтение11 мин
Количество просмотров19K
Первая часть
Вторая часть

7 Проверка и сообщения об ошибках


Большинство наших форм должны показывать сообщения проверки, чтобы информировать пользователя об ошибках, которые он сделал.

Thymeleaf предлагает несколько инструментов для этого: несколько функций в объекте #fields, атрибуты th:errors и th:errorclass.

7.1 Field errors


Давайте посмотрим, как мы можем установить конкретный класс CSS для поля, если оно содержит ошибку:

<input type="text" th:field="*{datePlanted}" 
                   th:class="${#fields.hasErrors('datePlanted')}? fieldError" />

Как видите, функция #fields.hasErrors(…) получает выражение поля в качестве параметра (datePlanted) и возвращает логическое значение, указывающее, существуют ли какие-либо ошибки проверки для этого поля.

Мы также можем получить все ошибки для этого поля и повторить их:

<ul>
  <li th:each="err : ${#fields.errors('datePlanted')}" th:text="${err}" />
</ul>

Вместо итерации мы могли бы также использовать th:errors, специализированный атрибут, который создает список со всеми ошибками для указанного селектора, разделенными <br />:

<input type="text" th:field="*{datePlanted}" />
<p th:if="${#fields.hasErrors('datePlanted')}" th:errors="*{datePlanted}">Incorrect date</p>

Упрощение стилей CSS на основе ошибок: th:errorclass

Пример, который мы видели выше, установка CSS-класса для input формы, если в этом поле есть ошибки, настолько распространена, что Thymeleaf предлагает специальный атрибут для точного выполнения: th:errorclass.

Примененный к тегу поля формы (input, select, textarea…), он будет считывать имя поля, которое нужно проверить, из любых существующих атрибутов name или th:field в том же теге, а затем добавлять указанный класс CSS к тегу, если такое поле имеет какие-либо связанные ошибки:

<input type="text" th:field="*{datePlanted}" class="small" th:errorclass="fieldError" />

Если в datePlanted есть ошибки, это будет выглядеть так:

<input type="text" id="datePlanted" name="datePlanted" value="2013-01-01" class="small fieldError" />

7.2 Все ошибки


А что если мы хотим показать все ошибки в форме? Нам просто нужно запросить методы #fields.hasErrors(…) и #fields.errors(…) с константами '*' или 'all' (которые эквивалентны):

<ul th:if="${#fields.hasErrors('*')}">
  <li th:each="err : ${#fields.errors('*')}" th:text="${err}">Input is incorrect</li>
</ul>

Как и в приведенных выше примерах, мы могли бы получить все ошибки и итерировать по ним …

<ul>
  <li th:each="err : ${#fields.errors('*')}" th:text="${err}" />
</ul>

… а также создать разделенный <br /> список:

<p th:if="${#fields.hasErrors('all')}" th:errors="*{all}">Incorrect date</p>

Наконец, обратите внимание, что #fields.hasErrors(‘*’) эквивалентно #fields.hasAnyErrors(), и #fields.errors(‘*’) эквивалентно #fields.allErrors(). Используйте тот синтаксис, который вы предпочитаете:

<div th:if="${#fields.hasAnyErrors()}">
  <p th:each="err : ${#fields.allErrors()}" th:text="${err}">...</p>
</div>

7.3 Глобальные ошибки


В форме Spring существует третий тип ошибок: глобальные ошибки. Это ошибки, которые не связаны с какими-либо конкретными полями в форме, но все еще существуют.
Thymeleaf предлагает константу global для доступа к этим ошибкам:

<ul th:if="${#fields.hasErrors('global')}">
  <li th:each="err : ${#fields.errors('global')}" th:text="${err}">Input is incorrect</li>
</ul>

<p th:if="${#fields.hasErrors('global')}" th:errors="*{global}">Incorrect date</p>

… a также эквивалентные вспомогательные методы #fields.hasGlobalErrors() и #fields.globalErrors():

7.4 Отображение ошибок вне форм


Ошибки проверки формы также могут отображаться вне форм с помощью переменных (${…}) вместо выражений выбора (*{…}) и префикса имени компонента, поддерживающего форму:

<div th:errors="${myForm}">...</div>
<div th:errors="${myForm.date}">...</div>
<div th:errors="${myForm.*}">...</div>

<div th:if="${#fields.hasErrors('${myForm}')}">...</div>
<div th:if="${#fields.hasErrors('${myForm.date}')}">...</div>
<div th:if="${#fields.hasErrors('${myForm.*}')}">...</div>

<form th:object="${myForm}">
    ...
</form>

7.5 Богатые объекты ошибок


Thymeleaf предлагает возможность получения информации об ошибках формы в виде bean-компонентов (вместо простых строк) с атрибутами fieldName (String), message (String) и global (boolean).

Эти ошибки могут быть получены с помощью служебного метода #fields.detailedErrors():

<ul>
    <li th:each="e : ${#fields.detailedErrors()}" th:class="${e.global}? globalerr : fielderr">
        <span th:text="${e.global}? '*' : ${e.fieldName}">The field name</span> |
        <span th:text="${e.message}">The error message</span>
    </li>
</ul>

8 Это все еще прототип!


Наше приложение готово. Но давайте еще раз посмотрим на .html страницу, которую мы создали …

Одним из самых приятных последствий работы с Thymeleaf является то, что после всех этих функций, которые мы добавили в наш HTML, мы все равно можем использовать этот HTML в качестве прототипа (мы говорим, что это Natural Template). Давайте откроем seedstartermng.html прямо в нашем браузере, не запуская наше приложение:

image

Вот оно! Это не работающее приложение, это не реальные данные … но это совершенно правильный прототип, составленный из прекрасно отображаемого HTML-кода.

9 Служба конверсии (The Conversion Service)


9.1 Конфигурирование


Как объяснялось ранее, Thymeleaf может использовать Службу преобразования, зарегистрированную в контексте приложения. Наш класс конфигурации приложения, расширяя собственный помощник Spring WebMvcConfigurerAdapter, автоматически зарегистрирует такой сервис преобразования, который мы можем настроить, добавив необходимые средства форматирования. Давайте еще раз посмотрим, как это выглядит:

@Override
public void addFormatters(final FormatterRegistry registry) {
    super.addFormatters(registry);
    registry.addFormatter(varietyFormatter());
    registry.addFormatter(dateFormatter());
}

@Bean
public VarietyFormatter varietyFormatter() {
    return new VarietyFormatter();
}

@Bean
public DateFormatter dateFormatter() {
    return new DateFormatter();
}

9.2 Синтаксис двойной скобки


Служба преобразования может быть легко применена для преобразования/форматирования любого объекта в строку. Это делается с помощью синтаксиса выражений в двойных скобках:

  • Для переменных выражений: ${{…}}
  • Для выражения выбора: *{{…}}

Так, например, учитывая преобразователь Integer-to-String, который добавляет запятые в качестве разделителя тысяч, это:

<p th:text="${val}">...</p>
<p th:text="${{val}}">...</p>

… должно привести к:

<p>1234567890</p>
<p>1,234,567,890</p>

9.3 Использование в формах


Ранее мы видели, что каждый атрибут th:field всегда будет применять сервис преобразования, так что это:

<input type="text" th:field="*{datePlanted}" />

… фактически эквивалентно:

<input type="text" th:field="*{{datePlanted}}" />

Обратите внимание, что согласно требованию Spring, это единственный сценарий, в котором служба преобразования применяется в выражениях с использованием синтаксиса с одной скобкой.

9.4 #conversions объект преобразования


Служебный объект преобразования #conversions позволяет вручную запускать службу преобразования там, где это необходимо:

<p th:text="${'Val: ' + #conversions.convert(val,'String')}">...</p>

Синтаксис для этого служебного объекта:

  • #conversions.convert(Object, Class): преобразует объект в указанный класс
  • #conversions.convert(Object, String): то же, что и выше, но с указанием целевого класса в виде String (обратите внимание, что пакет java.lang. может быть опущен)

10 Отрисовка фрагментов шаблона Template Fragments (AJAX etc)


Thymeleaf предлагает возможность визуализации только части шаблона в результате его выполнения: фрагмента.

Это может быть полезным инструментом компонентизации. Например, его можно использовать на контроллерах, которые выполняются при вызовах AJAX, которые могут возвращать фрагменты разметки страницы, которая уже загружена в браузер (для обновления выбора, включения/выключения кнопок…).

Фрагментарный рендеринг может быть достигнут с использованием спецификаций фрагментов Thymeleaf: объектов, реализующих интерфейс org.thymeleaf.fragment.IFragmentSpec.

Наиболее распространенной из этих реализаций является org.thymeleaf.standard.fragment.StandardDOMSelectorFragmentSpec, которая позволяет указывать фрагмент с помощью селектора DOM в точности так же, как те, что используются в th:include или th:replace.

10.1 Определение фрагментов в бине представления


Bean-компоненты представления — это bean-компоненты класса org.thymeleaf.spring4.view.ThymeleafView, объявленные в контексте приложения (аннотация Bean, если вы используете конфигурацию Java). Они позволяют задавать фрагменты следующим образом:

@Bean(name="content-part")
@Scope("prototype")
public ThymeleafView someViewBean() {
    ThymeleafView view = new ThymeleafView("index"); // templateName = 'index'
    view.setMarkupSelector("content");
    return view;
}

Учитывая приведенное выше определение bean-компонента, если наш контроллер возвращает content-part (имя вышеупомянутого bean-компонента)…

@RequestMapping("/showContentPart")
public String showContentPart() {
    ...
    return "content-part";
}

… Thymeleaf вернет только фрагмент content шаблона индекса — расположение которого, вероятно, будет примерно таким же, как /WEB-INF/templates/index.html, после применения префикса и суффикса. Таким образом, результат будет полностью эквивалентен указанию index :: content:

<!DOCTYPE html>
<html>
  ...
  <body>
    ...
    <div th:fragment="content">
      Only this div will be rendered!
    </div>
    ...
  </body>
</html>

Также обратите внимание, что благодаря мощным селекторам разметки Thymeleaf мы можем выделять фрагмент в шаблоне без каких-либо атрибутов th:fragment. Давайте используем атрибут id, например:

@Bean(name="content-part")
@Scope("prototype")
public ThymeleafView someViewBean() {
    ThymeleafView view = new ThymeleafView("index"); // templateName = 'index'
    view.setMarkupSelector("#content");
    return view;
}

10.2 Определение фрагментов в возвращаемом значении контроллера


Вместо декларирования view beans, фрагменты могут быть определены из контроллера с использованием синтаксиса выражений фрагментов (fragment expressions). Просто как в аттрибутах th:insert или th:replace.

@RequestMapping("/showContentPart")
public String showContentPart() {
    ...
    return "index :: content";
}

Разумеется, снова доступна вся мощь селекторов DOM, поэтому мы можем выбрать наш фрагмент на основе стандартных атрибутов HTML, таких как id=«content»:

@RequestMapping("/showContentPart")
public String showContentPart() {
    ...
    return "index :: #content";
}

И мы также можем использовать параметры, такие как:

@RequestMapping("/showContentPart")
public String showContentPart() {
    ...
    return "index :: #content ('myvalue')";
}

11 Продвинутые интеграционные фичи


11.1 Интеграция с RequestDataValueProcessor


Thymeleaf легко интегрируется с интерфейсом Spring RequestDataValueProcessor. Этот интерфейс позволяет перехватывать URL-адреса ссылок, URL-адреса формы и значения полей формы до их записи в результат разметки, а также прозрачно добавлять скрытые поля формы, которые включают функции безопасности, например, такие как: защита против CSRF (подделка межсайтовых запросов).

Реализация RequestDataValueProcessor может быть легко настроена в контексте приложения. Он должен реализовать интерфейс org.springframework.web.servlet.support.RequestDataValueProcessor и иметь requestDataValueProcessor в качестве имени бина:

@Bean
public RequestDataValueProcessor requestDataValueProcessor() {
  return new MyRequestDataValueProcessor();
}

…и Thymeleaf будет использовать это следующим образом:

  • th:href и th:src вызывают RequestDataValueProcessor.processUrl(…) перед отрисовкой URL
  • th:action вызывает RequestDataValueProcessor.processAction(…) перед рендерингом атрибута action формы, и дополнительно он обнаруживает, когда этот атрибут применяется к тегу <form>, который в любом случае должен быть единственным местом, и в этом случае вызывает RequestDataValueProcessor.getExtraHiddenFields(…) и добавляет возвращенные скрытые поля непосредственно перед закрывающим тегом </form>
  • th:value вызывает RequestDataValueProcessor.processFormFieldValue(…) для отрисовки значения, на которое оно ссылается, если только в том же теге нет th:field (в этом случае th:field сам позаботится)
  • th:field вызывает RequestDataValueProcessor.processFormFieldValue(…) для отрисовки значения поля, к которому применяется (или тела тега, если им является <textarea>)

Обратите внимание, что существует очень мало сценариев, в которых вам нужно было бы явно реализовать RequestDataValueProcessor в вашем приложении. В большинстве случаев это будет автоматически использоваться библиотеками безопасности, которые вы прозрачно используете, например, например, Spring Security’s CSRF.

11.1 Построение URIs к контроллерам


Начиная с версии 4.1, Spring дает возможность создавать ссылки на аннотированные контроллеры непосредственно из представлений, без необходимости знать URI, на которые эти контроллеры отображаются.

В Thymeleaf это может быть достигнуто с помощью выражения #mvc.url(…), который позволяет задавать методы контроллера заглавными буквами класса контроллера, в котором они находятся, за которым следует имя метода. Это эквивалентно пользовательской функции spring:mvcUrlx(…) в JSP.

Например, для:

public class ExampleController {

    @RequestMapping("/data")
    public String getData(Model model) { ... return "template" }

    @RequestMapping("/data")
    public String getDataParam(@RequestParam String type) { ... return "template" }

}

Следующий код создаст ссылки на методы:

<a th:href="${(#mvc.url('EC#getData')).build()}">Get Data Param</a>
<a th:href="${(#mvc.url('EC#getDataParam').arg(0,'internal')).build()}">Get Data Param</a>

Вы можето почитать об этом механизме по ссылке http://docs.spring.io/spring-framework/docs/4.1.2.RELEASE/spring-framework-reference/html/mvc.html#mvc-links-to-controllers-from-views

12 Spring WebFlow интеграция


Интеграционные пакеты Thymeleaf + Spring включают интеграцию с Spring WebFlow (2.3+).

WebFlow включает некоторые возможности AJAX для рендеринга фрагментов отображаемой страницы при срабатывании определенных событий (переходов), и чтобы Thymeleaf мог отслеживать эти запросы AJAX, нам нужно будет использовать другую реализацию ViewResolver, настроенную так:

<bean id="thymeleafViewResolver" class="org.thymeleaf.spring4.view.AjaxThymeleafViewResolver">
    <property name="viewClass" value="org.thymeleaf.spring4.view.FlowAjaxThymeleafView" />
    <property name="templateEngine" ref="templateEngine" />
</bean>

… и тогда этот ViewResolver может быть сконфигурирован в WebFlow ViewFactoryCreator как:

<bean id="mvcViewFactoryCreator" 
      class="org.springframework.webflow.mvc.builder.MvcViewFactoryCreator">
    <property name="viewResolvers" ref="thymeleafViewResolver"/>
</bean>

Отсюда вы можете определить шаблоны Thymeleaf в вашем view-state’s:

<view-state id="detail" view="bookingDetail">
    ...
</view-state>

В приведенном выше примере bookingDetail — это шаблон Thymeleaf, указанный обычным способом, понятный любому из резолверов шаблона, настроенных в TemplateEngine.

12.2 AJAX фрагменты в Spring WebFlow


Обратите внимание, что здесь объясняется только способ создания фрагментов AJAX для использования с Spring WebFlow. Если вы не используете WebFlow, создание контроллера Spring MVC, который отвечает на запрос AJAX и возвращает кусок HTML, так же просто, как и создание любого другого контроллера, возвращающего шаблон, с единственным исключением, что вы, вероятно, будете возвращать фрагмент наподобие "main :: admin" из вашего метода контроллера.

WebFlow позволяет определить отрисовку через AJAX с <render> тегов, например так:

<view-state id="detail" view="bookingDetail">
    <transition on="updateData">
        <render fragments="hoteldata"/>
    </transition>
</view-state>

Эти фрагменты (в данном случае hoteldata) могут представлять собой разделенный запятыми список фрагментов, указанных в разметке с помощью th:fragment:

<div id="data" th:fragment="hoteldata">
    This is a content to be changed
</div>

Всегда помните, что указанные фрагменты должны иметь атрибут id, чтобы библиотеки Spring JavaScript, работающие в браузере, могли заменить разметку.

Теги <render> также можно указывать с помощью селекторов DOM:

<view-state id=«detail» view=«bookingDetail»>
/>

</view-state>

…и это означает, что нет нужды в th:fragment:

<div id="data">
    This is a content to be changed
</div>

Что касается кода, который запускает переход updateData, он выглядит следующим образом:

<script type="text/javascript" th:src="@{/resources/dojo/dojo.js}"></script>
<script type="text/javascript" th:src="@{/resources/spring/Spring.js}"></script>
<script type="text/javascript" th:src="@{/resources/spring/Spring-Dojo.js}"></script>

  ...

<form id="triggerform" method="post" action="">
    <input type="submit" id="doUpdate" name="_eventId_updateData" value="Update now!" />
</form>

<script type="text/javascript">
    Spring.addDecoration(
        new Spring.AjaxEventDecoration({formId:'triggerform',elementId:'doUpdate',event:'onclick'}));
</script>
Теги:
Хабы:
Всего голосов 12: ↑11 и ↓1+10
Комментарии0

Публикации

Истории

Работа

Java разработчик
350 вакансий

Ближайшие события