JSF 2 + Maven + Jetty. CDI, форма и AJAX

В этот раз у нас появится Java-код. Будем писать форму и логику обработки введенных данных. Пост НЕ для тех, кто знает что такое JSF 2 и/или JSF 1.2, а faces-config.xml — не просто файл в проекте. Смотрите, я предупреждал…

CDI


С версии JSF 2.3.0 стандартная аннотация @ManagedBean признана устаревшей. Т.е. чтобы обратиться к своему Java-коду из формы нам потребуется добавить в проект CDI (Context and Dependency Injection) — Weld.

В файл pom.xml добавляем:

    <dependencies>
        ...
        <dependency>
            <groupId>org.jboss.weld.servlet</groupId>
            <artifactId>weld-servlet</artifactId>
            <version>2.4.3.Final</version>
        </dependency>
    </dependencies>

Создадим файл /src/main/webapp/WEB-INF/beans.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
       http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"
       bean-discovery-mode="all">
</beans>

Вредный совет
В комментариях к прошлому посту заметили:

Там прям особая специфика в регистрации listener'а?

ОК! В файл /src/main/webapp/WEB-INF/web.xml добавляем:
<web-app ...
    <listener>
        <listener-class>org.jboss.weld.environment.servlet.Listener</listener-class>
    </listener>
    ...
</web-app>

Запускаем: mvn jetty:run

Читаем traceback почему не работает.

Создадим файл /src/main/webapp/WEB-INF/jetty-env.xml:

<?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN"
        "http://www.eclipse.org/jetty/configure.dtd">
<Configure id="webAppCtx" class="org.eclipse.jetty.webapp.WebAppContext">
    <Call name="prependServerClass">
        <Arg>-org.eclipse.jetty.server.handler.ContextHandler</Arg>
    </Call>

    <Call name="prependServerClass">
        <Arg>-org.eclipse.jetty.servlet.FilterHolder</Arg>
    </Call>

    <Call name="prependServerClass">
        <Arg>-org.eclipse.jetty.servlet.ServletContextHandler</Arg>
    </Call>

    <Call name="prependServerClass">
        <Arg>-org.eclipse.jetty.servlet.ServletHolder</Arg>
    </Call>

    <New id="BeanManager" class="org.eclipse.jetty.plus.jndi.Resource">
        <Arg>
            <Ref id="webAppCtx"/>
        </Arg>
        <Arg>BeanManager</Arg>
        <Arg>
            <New class="javax.naming.Reference">
                <Arg>javax.enterprise.inject.spi.BeanManager</Arg>
                <Arg>org.jboss.weld.resources.ManagerObjectFactory</Arg>
                <Arg/>
            </New>
        </Arg>
    </New>
</Configure>

В файл /src/main/webapp/WEB-INF/web.xml добавляем:

<web-app ...
    <resource-env-ref>
        <resource-env-ref-name>BeanManager</resource-env-ref-name>
        <resource-env-ref-type>javax.enterprise.inject.spi.BeanManager</resource-env-ref-type>
    </resource-env-ref>
    ...
</web-app>

Теперь вместо @ManagedBean будет работать @Named. Учитывая что CDI штука хорошая, а с версии JSF 2.3.0 — критичная, стоит понимать что CDI в JSF используется по своему прямому назначению и к JSF непосредственно отношения не имеет.
Больше к CDI мы возвращаться не будем!

Классы FormData и FormCtrl


Теперь давайте напишем немного кода. Нам потребуется 2 класса — в 1-ом мы будем передавать данные, во 2-ом будет функция обработки этих данных.

Отступление
С точки зрения реализации — не обязательно выделять логику формы в 2 отдельных класса, но это «правильно» как с точки зрения тестирования, так и сточки зрения архитектуры.

Класс-данных /src/main/java/ru/habr/FormData.java:

package ru.habr;

import javax.enterprise.context.RequestScoped;
import javax.inject.Named;

@Named
@RequestScoped
public class FormData {
    private String username;
    private String password;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

Класс-обработчик /src/main/java/ru/habr/FormCtrl.java:

package ru.habr;

import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.inject.Named;

@Named
@RequestScoped
public class FormCtrl {

    @Inject
    FormData formData;

    public String doAction() {
        // Очень полезные действия с данными...
        System.out.println(formData.getUsername());
        System.out.println(formData.getPassword());
        return null;
    }
}

Если с FormData все понятно, то на FormCtrl стоит взглянуть пристальней:

  1. Благодаря @Inject, данные формы будут доступны в классе-обработчике в поле formData.
  2. Рекомендую придерживаться тезиса «метод-обработчик должен возвращать строку» хоть это и не обязательно. Строка в дальнейшем используется в навигации, но об этом позже.

Форма


Тут все просто! В файл /src/main/webapp/index.xhtml добавляем:

<h:body>
    ...
    <h:form id="habrForm">
        <h:inputText value="#{formData.username}"/><br/>
        <h:inputSecret value="#{formData.password}"/><br/>
        <h:commandButton action="#{formCtrl.doAction}" value="Send">
        </h:commandButton>
    </h:form>
</h:body>

Запускаем: mvn jetty:run
Проверяем: http://127.0.0.1:8080/

AJAX


На дворе 2017 год, а формы все еще без AJAX?! В файл /src/main/webapp/index.xhtml внесем пару правок:

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://xmlns.jcp.org/jsf/html"
      xmlns:f="http://xmlns.jcp.org/jsf/core">
...
<h:body>
    ...
        <h:inputText value="#{formData.username}"/><br/>
        <h:inputSecret value="#{formData.password}"/><br/>
        <h:commandButton action="#{formCtrl.doAction}" value="Send">
            <f:ajax execute="@form" render="@form"/>
        </h:commandButton>
</h:body>
</html>

Что изменилось:

  1. Добавилось определение библиотеки тегов xmlns:f="http://xmlns.jcp.org/jsf/core". Без этого можно реализовать AJAX, но обрабатывать придется ручками, что долго и неблагодарно.
  2. Внутри h:commandButton появился <f:ajax execute="@form" render="@form"/>. В execute — указываем что необходимо отправить на сервер (всю форму/все поля формы), в render — что необходимо обновить с сервера после выполнения AJAX.

Про f:ajax можно писать отдельный. Сейчас достаточно знать только что с его помощью можно отправить/обновить не только текущую форму на странице, а любой элемент страницы.

Послесловие


Проект все еще очень скромен в своих возможностях, но я буду продолжать!

Комментарии 6

    0
    … но я буду продолжать!
    Мыши плакали, кололись, но продолжали жрать кактус.
      0

      Подождём немного до того как автору понадобятся конверсейшн и вью скоуп бины. Тогда возможно придёт осознание того что пора перестать тревожить древнее зло и взять что-то свеженькое и интересное.

        0
        Извините пожалуйста. Но может быть вы мне подскажете на что стоит смотреть из свежего из интересного? Spring? Или что-то другое. Просто начал интересоваться этой темой. Но выбрать из сегодняшнего множества — очень сложно. Спасибо заранее.
          0

          Да можно махнуть в последний спринг со своими интеграциями с клаудом и распределением. Ui уже давно на js и тоже можно организовать server rendering если тяготеет к нему душа. Если же в вашей задаче ещё есть и рендер в зависимости от ролей пользователя то можно ещё и тут хорошо так поломать голову если список ролей в js, на ваш взгляд, это плохая идея

            0
            Спасибо за ответ. Нужно обязательно воспользоваться.
        0
        Хочется верить, что это какие-то садисты с включенным паяльником заставили человека написать про JSF.

        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

        Самое читаемое