Статьи

Использование функций JSF 2.2 для разработки таблицы данных с отложенной загрузкой и прокруткой

На этот раз я поделился с вами тем, что недавно узнал о возможностях JSF 2.2. Для этого я решил создать простую таблицу данных Ajax с возможностью прокрутки и отложенной загрузки.

Обратите внимание, что в любом случае это сравнимо с основными библиотеками, такими как Primefaces , Richfaces или ICEFaces . Это только для того, чтобы показать вам, что я узнал. (Конечно, никто не может помешать вам использовать его, если вы этого хотите).

Концепт

Позвольте мне сначала рассказать вам концепцию или сценарий, если вы хотите назвать это так. Это на самом деле очень просто.

У нас есть список сущностей Customer , и в основном мы собираемся реализовать таблицу данных, которая прокручивается, лениво загружает список и использует ajax.

Примечание: я предполагаю, что вы хорошо знакомы с JPA и EJB, поэтому, хотя я мог бы упомянуть их здесь и там, я не буду обсуждать их в этом посте.

Используя шаблон

Как и любое приложение JSF, я также начал с использования шаблона. Это очень простой в использовании шаблон с Netbeans. Просто выберите опцию Facelets Template и выберите ваш макет.

Обратите внимание, что Netbeans также генерирует две таблицы стилей: cssLayout.css и default.css . Также обратите внимание, что во вновь созданном шаблоне они расположены с использованием местоположения файла следующим образом:

1
2
3
4
5
6
<h:head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <link href="./../resources/css/default.css" rel="stylesheet" type="text/css" />
    <link href="./../resources/css/cssLayout.css" rel="stylesheet" type="text/css" />
    <title>Facelets Template</title>
</h:head>

Я внес ряд изменений в шаблон по умолчанию.

Во-первых, при запуске сервера приложений я столкнулся с проблемой, состоящей в том, что таблицы стилей были загружены неправильно. Итак, чтобы исправить это, я использую JSF Resource Locator (Lubke 2008).

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

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

template.xhtml

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
      xmlns:ui="http://java.sun.com/jsf/facelets"
      xmlns:h="http://java.sun.com/jsf/html">
    <h:head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
        <h:outputStylesheet library="css" name="default.css" />
        <h:outputStylesheet library="css" name="cssLayout.css" />
        <title>Facelets Template</title>
    </h:head>
    <h:body>
        <div id="top">
            <ui:insert name="header">
                <ui:include src="header.xhtml" />
            </ui:insert>
        </div>
        <div>
            <div id="left">
                <ui:insert name="menu">
                    <ui:include src="menu.xhtml" />
                </ui:insert>
            </div>
            <div id="content" class="left_content">
                <ui:insert name="content">
                    <!-- empty content goes here -->
                </ui:insert>
            </div>
        </div>
        <div id="bottom">
            <ui:insert name="bottom">               
                <ui:include src="footer.xhtml" />
            </ui:insert>
        </div>
    </h:body>
</html>

Обратите внимание, что вместо использования локатора файлов ./../resources/css/default.css я использую h:outputStylesheet . При таком подходе просто поместите все свои ресурсы в каталог resources и укажите его, обратившись к папке, содержащей ваш ресурс, через library атрибутов. Так, например, <h:outputStylesheet library="css" name="default.css" /> действительно просматривает файл в расположении resources/css/default.css .

header.xhtml, footer.xhtml, menu.xhtml

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
      xmlns:ui="http://java.sun.com/jsf/facelets">
    <body>
        <ui:composition>
            <!--
            I created each file for header, footer and menu, but I simply list
            them here just to show you
            -->
            <h1>Default Header</h1>
            <!-- h1>Default Menu</h1 -->
            <!-- h1>Default Footer</h1 -->
        </ui:composition>
    </body>
</html>

Далее используется шаблон. Итак, давайте создадим файл customers.xhtml для его использования.

customers.xhtml

01
02
03
04
05
06
07
08
09
10
11
12
13
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
      xmlns:ui="http://java.sun.com/jsf/facelets"
      xmlns:h="http://java.sun.com/jsf/html">
    <body>
        <ui:composition template="./common/template.xhtml">
            <ui:define name="content">
                <h:outputText value="List of customers goes here!!!" />
            </ui:define>
        </ui:composition>
    </body>
</html>

Затем вы можете запустить сервер приложений и получить доступ к странице через http://<the path to your application>/customers.jsf . Вывод должен выглядеть следующим образом.

шаблон

Добавить объект для создания клиента

Во-первых, в шаблоне давайте добавим средство для добавления клиентов в customers.xhtml . Мы можем просто использовать JSF ManagedBean для этого. Тем не менее, Core Javaserver Faces, 3rd рекомендует:

Это исторический случай, когда есть два отдельных механизма, bean-компоненты CDI и управляемые bean-компоненты JSF, для bean-компонентов, которые можно использовать на страницах JSF. Мы рекомендуем вам использовать компоненты CDI, если ваше приложение не должно работать на обычном сервлет-бегуне, таком как Tomcat.

(Geary, Horstmann 2010)

Следуя предложению, я буду теперь использовать компоненты CDI.

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

В основном нам нужны Model и Controller . Итак, давайте сначала создадим эти классы.

CustomerData.java

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package id.co.dwuysan.customer;
 
import javax.enterprise.inject.Model;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
 
@Model
public class CustomerData {
 
    @NotNull
    @Size(min = 1, max = 50)
    private String name;
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    void clear() {
        setName(null);
    }
}

CustomerData.java

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package id.co.dwuysan.customer;
 
import id.co.dwuysan.service.CustomerService;
import javax.ejb.EJBException;
import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.ActionEvent;
import javax.inject.Inject;
import javax.inject.Named;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
 
@Named
public class CustomerRegistationAction {
 
    @Inject
    private CustomerData customer;
 
    @Inject
    private CustomerService customerService;
 
    public void register(ActionEvent event) {
        try {
            // calls EJB and do registration here
        } catch (EJBException e) {
            FacesContext.getCurrentInstance().addMessage(event.getComponent().getClientId(), new FacesMessage(e.getMessage()));
            throw new AbortProcessingException(e.getMessage());
        }
        this.customer.clear();
    }
}

Обратите внимание, что в этом случае мы используем аннотацию @Named . Поскольку мы не предоставили ему никаких аннотаций области видимости, по умолчанию используется @RequestScoped (BalusC 2011a).

Следующим шагом является изменение нашего customers.xhtml для использования этих классов, чтобы мы могли регистрировать клиентов.

customers.xhtml

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
      xmlns:ui="http://java.sun.com/jsf/facelets"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:h="http://java.sun.com/jsf/html"
    <body>
        <ui:composition template="./common/template.xhtml">
            <ui:define name="content">
                <f:view transient="true">
                    <h:form>
                        <h:panelGrid columns="3">
                            <h:outputLabel for="txtCustomerName" value="Customer name" />
                            <h:inputText id="txtCustomerName" value="#{customerData.name}" />
                            <h:message for="txtCustomerName" styleClass="error" />
                            <div />
                            <h:commandButton id="cmdRegisterCustomer" value="Submit" action="customers" actionListener="#{customerRegistationAction.register}" />
                            <h:message for="cmdRegisterCustomer" styleClass="error" />
                        </h:panelGrid>
                    </h:form>
                </f:view>
            </ui:define>
        </ui:composition>
    </body>
</html>

Хорошо, как только это будет сделано, вы можете зарегистрировать клиента. Экран должен выглядеть следующим образом.

createcust

Определите «Контракт»

Эта часть — реальная сделка. То, что я называю «контрактом», само по себе не является контрактом. Скорее, я имею в виду, как мы начнем с разработки сначала xhtml Facelet, который будет управлять фактическим классом бина.

Итак, давайте сначала изложим требование:

  • Записи должны быть разбиты на страницы в таблице, лениво загружены из базы данных
  • Пользователь должен иметь возможность выбрать, сколько записей будет отображаться на странице
  • Пользователь должен видеть текущую страницу, общее количество страниц и записей.
  • Пользователь должен иметь возможность прокручивать записи, следующие и предыдущие
  • Используйте AJAX

В приложении должно быть много списков. Следовательно, лучший способ двигаться вперед — это создать компонент, который мы можем легко использовать повторно. Поскольку сам компонент состоит из ряда других компонентов, я считаю, что эта функция официально упоминается как составной компонент . В эпоху JSF 1.x это было довольно болезненно. Разработчик должен написать много классов / файлов. С JSF 2.x это очень просто (и, надо сказать, довольно элегантно).

Давайте начнем с создания компонента.

Создание составного компонента

Я собираюсь назвать этот компонент как paginator .

Я уверен, что вы можете найти много объяснений о том, как это сделать, поэтому я пропущу часть «объяснения» и просто покажу вам, что я сделал.

Я создал файл paginator.xhtml в каталоге resources/ezcomp . Поэтому позже я ссылаюсь на этот компонент с помощью пространства имен xmlns:ez="http://java.sun.com/jsf/composite/ezcomp .

paginatorfile

Давайте перейдем к фактической реализации paginator.xhtml . Пожалуйста, обратитесь к следующему источнику, и тогда я проведу вас через шаг за шагом.

paginator.xhtml

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
      xmlns:cc="http://java.sun.com/jsf/composite"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core">
 
    <!-- INTERFACE -->
    <cc:interface>
        <cc:attribute name="paginateModel" /> <!-- 01 -->
    </cc:interface>
 
    <!-- IMPLEMENTATION -->
    <cc:implementation>
        <!-- 02 -->
        <h:inputHidden value="#{cc.attrs.paginateModel.sortField}" />
        <h:inputHidden value="#{cc.attrs.paginateModel.ascending}" />
        <h:inputHidden value="#{cc.attrs.paginateModel.page}" />
 
        <!-- 03 -->
        <h:panelGrid columns="2" cellpadding="5" cellspacing="5">
            <h:outputLabel value="Rows-per-page" />
            <h:selectOneRadio value="#{cc.attrs.paginateModel.rowsPerPage}">
                <f:selectItem itemValue="5" itemLabel="5" />
                <f:selectItem itemValue="10" itemLabel="10" />
                <f:selectItem itemValue="20" itemLabel="20" />
                <f:selectItem itemValue="100" itemLabel="100" />
                <f:ajax execute="@form" render="@form" listener="#{cc.attrs.paginateModel.updateRowsPerPage}" />
            </h:selectOneRadio>
        </h:panelGrid>
 
        <!-- pagination -->
        <h:panelGrid columns="3" cellpadding="5" cellspacing="5">
            <!-- 04 -->
            <h:commandLink value="<<" actionListener="#{cc.attrs.paginateModel.navigatePage(false)}" style="display: #{cc.attrs.paginateModel.page gt 1 ? 'block' : 'none'}">
                <f:ajax execute="@form" render="@form" />
            </h:commandLink>
            <!-- 05 -->
            <h:outputLabel value="#{cc.attrs.paginateModel.page} ⁄ #{cc.attrs.paginateModel.totalPages} " />
            <!-- 06 -->
            <h:commandLink value=">>" actionListener="#{cc.attrs.paginateModel.navigatePage(true)}" style="display: #{cc.attrs.paginateModel.page lt cc.attrs.paginateModel.totalPages ? 'block' : 'none'}">
                <f:ajax execute="@form" render="@form" />
            </h:commandLink>
        </h:panelGrid>
        <cc:insertChildren /><!-- 07 -->
        <br />
        <!-- 08 -->
        <h:outputFormat value="There are {0} record(s).">
            <f:param value="#{cc.attrs.paginateModel.recordCount}" />
        </h:outputFormat>
    </cc:implementation>
</html>

В следующем разделе объясняется каждый пронумерованный момент, который я сделал в customers.xhtml .

01. cc:attribute и paginatedModel

Эта часть является 'interface' между реализацией в этом xhtml, а также клиентом / пользователем этого компонента позже. Чтобы выразить это простыми словами, подумайте о нем как о параметре, который необходимо указать при использовании этого компонента.

02. Скрытые параметры

Теперь эта часть имеет много объяснений.

Во-первых, мне нужно немного перейти вперед, чтобы сказать вам, что я использую компонент RequestScoped для поддержки списка записей. Еще одна важная вещь, на которую стоит обратить внимание, что я на самом деле использую <f:view transient="true"> , которая является функцией, доступной в JSF 2.2, и, следовательно, делает эту страницу без состояния. Если вы открыли исходный код в своем браузере, состояние теперь:

1
<input type="hidden" name="javax.faces.ViewState" id="j_id1:javax.faces.ViewState:0" value="stateless" autocomplete="off" />

Поскольку эта страница не имеет состояния, вам необходимо управлять собственным состоянием между вызовами (Рим 2013). Отсюда и использование скрытых параметров. Итак, представьте, предположим, что вы отображали страницу 4 из 100 страниц, и пользователь решил перейти на следующую страницу, поскольку она не имеет состояния, вам нужно знать, какая страница сейчас находится, отображается ли текущий дисплей по возрастанию или по убыванию. и по тому, что он в настоящее время сортируется.

03. Выбор строк на странице

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

1
<f:ajax execute="@form" render="@form" listener="#{cc.attrs.paginateModel.updateRowsPerPage}" />

В этом разделе говорится в основном о выборе пользователя, выполните метод updateRowsPerPage и обновите эту form . 'execute' также подразумевает, что мы принимаем во внимание все параметры в этой form , как указано в разделе 02.

04/06. Перейдите «назад» и «вперед»

Я объясню части 04 и 06 вместе в этом разделе, потому что они очень похожи.

Давайте начнем с части 04. Это в основном отобразит ссылку, позволяющую пользователю перемещаться 'Back' . Я просто играю с атрибутом display для условного рендеринга, только если текущая страница не является первой страницей (в противном случае было бы невозможно вернуться назад на первой странице, не так ли?). Часть 06 очень похожа, с условием рендеринга, только если текущая страница не последняя.

Следующая часть, которую я хотел бы объяснить, это actionListener при нажатии на эту страницу. В основном мы знаем на данный момент, что текущая страница из-за h:inputHidden (см. Раздел 02). Итак, с помощью двух командных ссылок в частях 04 и 06 нам нужно указать, что это за навигация. Для этого я просто использую логическое значение.

Наконец, поскольку мы хотим выполнять навигацию с использованием AJAX, нам также необходимо использовать <f:ajax> . Кроме того, та же причина, что и в предыдущих разделах, касающихся execute и render в @form .

05 Текущая страница и общее количество страниц

Эта часть просто отображает текущую страницу и общее количество доступных страниц. Совершенно очевидно

06 Смотрите раздел 04

Смотрите раздел 04

07 insertChildren

Хотя мы можем повторно использовать paginator, я подумал, что будет очень трудно создать компонент, который автоматически отобразит вашу модель данных в таблицу. По крайней мере, дизайнер страницы должен определить, какие столбцы отображать (я уверен, что должен быть волшебный способ автоматической визуализации всего в табличной форме. Возможно, Oracle ADF Faces сделает это за вас. В любом случае, я не мог не узнать об этом).

При использовании этого тега, в основном позже, когда мы используем <ezcomp:paginator>...</ezcomp:paginator> , все, что мы помещаем между ними, будет отрисовано в части 07. Подумайте об этом немного как <ui:decorate> ,

08 Отображение общего количества записей

Для этого раздела мы просто отображаем общее количество записей. Совершенно очевидно.

Использование составного компонента

Теперь нам нужно использовать этот составной компонент. Поэтому нам нужно изменить наш customers.xhtml следующим образом:

customers.xhtml

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
      xmlns:ui="http://java.sun.com/jsf/facelets"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:ez="http://java.sun.com/jsf/composite/ezcomp"> <!-- 01 -->
    <body>
        <ui:composition template="./common/template.xhtml">
            <ui:define name="content">
                <f:view transient="true">
                    <h:form id="frmCreateCustomer">
                        <h:panelGrid columns="3">
                            <h:outputLabel for="txtCustomerName" value="Customer name" />
                            <h:inputText id="txtCustomerName" value="#{customerData.name}" />
                            <h:message for="txtCustomerName" styleClass="error" />
                            <div />
                            <h:commandButton id="cmdRegisterCustomer" value="Submit" action="customers" actionListener="#{customerRegistationAction.register}">
                                <f:actionListener binding="#{listCustomers.sortByDefault}" />
                                <f:ajax execute="@form" render="@form :frmListCustomers" /> <!-- 03 -->
                            </h:commandButton>
                            <h:message for="cmdRegisterCustomer" styleClass="error" />
                        </h:panelGrid>
                    </h:form>
                    <br />
                    <h:form id="frmListCustomers"> <!-- 02 -->
                        <ez:paginator paginateModel="#{listCustomers}"> <!-- 04 -->
                            <h:dataTable value="#{listCustomers.data}" var="cust"> <!-- 05 -->
                                <h:column>
                                    <f:facet name="header">
                                        <h:commandLink value="Name" actionListener="#{listCustomers.sort('name')}">
                                            <f:ajax execute="@form" render="@form" /> <!-- 06-->
                                        </h:commandLink>
                                    </f:facet>
                                    <h:outputText value="#{cust.name}" />
                                </h:column>
                            </h:dataTable>
                        </ez:paginator>
                    </h:form>
                </f:view>
            </ui:define>
        </ui:composition>
    </body>
</html>

Я снова проведу вас по одному:

01 Пространство имен

Как упоминалось ранее, поскольку мы помещаем наш paginator в resources/ezcomp/paginator.xhtml , JSF позволяет нам ссылаться на него, используя пространство имен http://java.sun.com/jsf/composite/ezcomp . Поэтому позже (как показано в 04) вы можете использовать вновь созданный paginator, используя ezcomp:paginator .

02 Список таблицы в своем собственном виде

Давайте перейдем немного дальше к части 02. Часть 02 показывает, что в этом customers.xhtml мы используем две формы. Главным образом потому, что я хочу, чтобы взаимодействие, которое вы выполняете с таблицей, вы не хотите, чтобы оно влияло или зависело от другой формы, в данном случае от раздела «Создание клиента».

Допустим, предположим, что мы используем только одну форму для всего customers.xhtml . При нажатии кнопки для сортировки (часть 06) может быть запущена проверка в текстовом поле создания, например, имя клиента не может быть пустым. Мы этого не хотим.

Мы также должны дать этой форме имя, по причине, изложенной в разделе 03.

03 Повторно сделать другую форму

Намерение здесь состоит в том, чтобы иметь возможность обновлять таблицу каждый раз, когда добавляется новый клиент. Обратите внимание, что контейнер именования (по отношению к разделу 02) является второй h:form . Следовательно, поскольку клиент Create и таблица находятся в другом контейнере именования, давайте просто обновим всю вторую форму, и это будет сделано с использованием правильного «поискового выражения». Использование «:» в начале означает, что JSF будет искать дерево в корне документа (BalusC, 2011b).

04 Использование нашего недавно созданного составного компонента

Это просто способ использовать наш недавно созданный компонент paginator , то есть путем передачи в него соответствующего параметра (позже).

Смотрите также paginator.xhtml и его объяснение для более подробной информации.

05 Таблица фактических данных

Это фактическая таблица данных. Как я уже говорил ранее, хотя мы можем сделать универсальный paginator, я думал, что будет очень сложно сделать универсальный рендеринг таблиц. Итак, в данном случае я просто хочу показать вам случай, когда мы определяем, какой столбец отображать.

Следует отметить, что мы вызвали listCustomers.data чтобы предоставить javax.faces.DataModel для нашей таблицы данных. Эти «данные» должны быть правильными данными, основанными на странице, размере, сортировке и т. Д. Если вы все еще не понимаете, пожалуйста, не беспокойтесь. Следующий раздел станет понятнее.

06 Сортировка

В соответствии с требованиями выше, мы должны осуществить сортировку. Это довольно очевидно, и использование f:ajax имеет те же причины, что и упомянутые ранее.

Создать класс для поддержки нумерации страниц

Итак, мы создали «контракт / требование» с paginator.xhtml а затем используем его на customers.xhtml . Теперь нам нужно реализовать класс, поддерживающий эту функциональность.

То, что я собираюсь сделать, — это пройти paginator.xhtml и paginator.xhtml . Вы также можете сделать это, чтобы вы могли легко понять каждый из пунктов, перечисленных ниже:

  • Во-первых, глядя на customers.xhtml класс называется listCustomers. Поскольку мы используем CDI, нам нужно использовать @Named для представления компонента в среду JSF. Мы можем создать ListCustomers.java или заполнить value в аннотации @Named .
  • В прошлом я использовал @ViewScoped для этого компонента. Однако, поскольку мы используем функцию «без сохранения состояния» JSF с f:view transient="true" , мы больше не можем использовать @ViewScoped . Эти виды bean-компонентов привязаны к представлениям, управляемым средой JSF. Но с этой функцией без сохранения состояния представление всегда воссоздается, как и компоненты этой области (Busscher 2013). Итак, лучшая область применения — @RequestScoped .
  • Он должен иметь getData() который возвращает javax.faces.DataModel<T> . Это должно вернуть список Customer для отображения.
  • Он должен поддерживать public void sort(final String sortField) для поддержки сортировки заголовков.
  • String getSortField() для String getSortField() , boolean isAscending() , int getPage() И их соответствующие мутаторы / сеттеры. Они необходимы, связанные с нашими скрытыми входами.
  • Accessor / mutator для rowsPerPage для поддержки нашего выбора строк на страницу.
  • Он должен поддерживать public void updateRowsPerPage(AjaxBehaviorEvent event) , чтобы мы могли обновлять список на основе выбора строк на страницу пользователя.
  • Он должен поддерживать public void navigatePage(final boolean forward) .
  • Accessor для recordCount

В любом случае, вы должны быть в состоянии реализовать класс сейчас.

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

Во-первых, базовый класс.

DataListingSupport.java

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
package id.co.dwuysan.util;
 
import java.io.Serializable;
import javax.faces.event.AjaxBehaviorEvent;
import javax.faces.model.DataModel;
 
public abstract class DataListingSupport<T extends Serializable> implements Serializable {
    private int recordCount = 0;
    private int totalPages = 0;
    private DataModel<T> data;
 
    private int page = 1;
    private Integer rowsPerPage = null;
    private boolean ascending = true;
    private String sortField;
 
    public void navigatePage(final boolean forward) {
        setPage((forward) ? ++page : --page);
        refresh();
    }
 
    public void sort(final String sortField) {
        setSortField(sortField);
        setAscending(getSortField().equals(sortField) ? !isAscending() : true);
        refresh();
    }
 
    public void updateRowsPerPage(final AjaxBehaviorEvent event) {
        setPage(1); // page must reset to the first one
        refresh();
    }
 
    public void refresh() {
        // hook to populate count and data
        populateCountAndData();
        // compute total pages
        setTotalPages(countTotalPages(getRecordCount(), getRowsPerPage()));
    }
 
    /**
     * The concreate implementation of this class must perform data retrieval
     * based on the current information available (accessible via methods such
     * as {@link #getSortField()}, {@link #isAscending()}, etc.
     * <p>
     * The implementation is responsible in populating the values for {@link #setRecordCount(int)}
     * and {@link #setData(javax.faces.model.DataModel)}
     */
    protected abstract void populateCountAndData();
 
    /************************************************************** HELPER(S) */
 
    private static int countTotalPages(int totalRecord, int rowsPerPage) {
        int pageCounter = 0;
        for (int pageCountTracker = 0; pageCountTracker < totalRecord; ++pageCounter) {
            pageCountTracker += rowsPerPage;
        }
        return pageCounter;
    }
 
    /************************************************* ACCESSORS AND MUTATORS */
 
    public int getPage() {
        return page;
    }
 
    public void setPage(int page) {
        this.page = page;
    }
 
    public boolean isAscending() {
        return ascending;
    }
 
    public void setAscending(boolean ascending) {
        this.ascending = ascending;
    }
 
    public Integer getRowsPerPage() {
        return rowsPerPage;
    }
 
    public void setRowsPerPage(Integer rowsPerPage) {
        this.rowsPerPage = rowsPerPage;
    }
 
    public DataModel<T> getData() {
        return data;
    }
 
    public void setData(DataModel<T> data) {
        this.data = data;
    }
 
    public String getSortField() {
        return sortField;
    }
 
    public void setSortField(String sortField) {
        this.sortField = sortField;
    }
 
    public int getRecordCount() {
        return recordCount;
    }
 
    public void setRecordCount(int recordCount) {
        this.recordCount = recordCount;
    }
 
    public int getTotalPages() {
        return totalPages;
    }
 
    public void setTotalPages(int totalPages) {
        this.totalPages = totalPages;
    }
}

Следующее — как мы расширяем этот класс для создания нашего вспомогательного компонента для поддержки листинга Customer .

DataListingSupport.java

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package id.co.dwuysan.customer;
 
import id.co.dwuysan.entity.Customer;
import id.co.dwuysan.service.CustomerService;
import id.co.dwuysan.util.DataListingSupport;
import javax.enterprise.context.RequestScoped;
import javax.faces.model.ListDataModel;
import javax.inject.Inject;
import javax.inject.Named;
 
@RequestScoped
@Named
public class ListCustomers extends DataListingSupport<Customer> {
 
    @Inject
    private CustomerService customerService;
 
    public ListCustomers() {
        setSortField("name");
        setRowsPerPage(10);
    }
 
    @Override
    protected void populateCountAndData() {
        /*
         * This is where we call an EJB (or whatever service layer you have)
         * to perform data retrieval.
         *
         * You need to make sure to retrieve the result (paginated, sorted), and
         * also the total number of records.
         */
        setRecordCount(result.getCount());
        setData(new ListDataModel<>(result.getResult()));
    }
}

Итак, вы видите, используя этот подход, если мне нужны другие виды списков, то есть списки заказов, мы можем легко расширить DataListingSupport<> .

Результат

Прежде чем я покажу вам полный результат, одну вещь, которую мы могли бы добавить, — это действие по загрузке всех текущих клиентов из базы данных при отображении страницы. JSF 2.2 добавляет функцию под названием View Action, которая делает это очень легко (McGinn 2011).

Все, что вам нужно сделать, это добавить это f:viewAction после f:view следующим образом:

customers.xhtml

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
      xmlns:ui="http://java.sun.com/jsf/facelets"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:ez="http://java.sun.com/jsf/composite/ezcomp"> <!-- 01 -->
    <body>
        <ui:composition template="./common/template.xhtml">
            <ui:define name="content">
                <f:view transient="true">
                    <!-- to load on display of the page -->
                    <f:metadata>
                        <f:viewAction transient="true" action="#{listCustomers.refresh}"/>
                    </f:metadata>
                    <h:form id="frmCreateCustomer">
                        <!-- the rest of the code remains unchanged -->
                    </h:form>
                </f:view>
            </ui:define>
        </ui:composition>
    </body>
</html>

Обратите внимание, что мы просто вызываем DataListingSupport<>#refresh() . viewAction фактически анализирует возвращаемое значение String для выполнения неявной навигации. В этом случае наш метод refresh() фактически возвращает void , поэтому навигация не выполняется (McGinn 2011).

Результат должен быть таким:

результат

Я взял список клиентов из NASDAQ 100 .

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

использованная литература