Java Platform, Enterprise Edition (Java EE) 8
Учебник по Java EE

Назад Вперёд Содержание

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

Как объясняется в Когда использовать кастомный компонент, класс компонента определяет состояние и поведение компонента пользовательского интерфейса. Информация о состоянии включает тип компонента, идентификатор и локальное значение. Поведение, определённое классом компонента, включает следующее:

  • Декодирование (преобразование параметра запроса в локальное значение компонента)

  • Кодирование (преобразование локального значения в соответствующую разметку)

  • Сохранение состояния компонента

  • Обновление значения компонента локальным значением

  • Обработка валидации по локальному значению

  • Очередь событий

Класс javax.faces.component.UIComponentBase определяет поведение компонента по умолчанию. Все классы, представляющие стандартные компоненты, являются дочерними для UIComponentBase. Эти классы переопределяют поведение по умолчанию, как будет делать и класс вашего компонента.

Класс вашего кастомного компонента должен расширять либо UIComponentBase непосредственно, либо расширять класс, представляющий один из стандартных компонентов. Эти классы находятся в пакете javax.faces.component и их имена начинаются с UI.

Если ваш кастомный компонент выполняет те же функции, что и какой-либо стандартный компонент, предпочтительно расширять этот стандартный компонент, а не UIComponentBase. Для примера предположим, что вы хотите создать редактируемый компонент меню. Имеет смысл расширять UISelectOne, а не UIComponentBase, потому что в этом случае вы можете повторно использовать поведение, уже определённое в UISelectOne. Единственная новая функциональность, которую вам нужно определить, — сделать меню редактируемым.

Независимо от того, будете вы расширять UIComponentBase или стандартный компонент, вы также можете захотеть, чтобы ваш компонент реализовал один или несколько из этих поведенческих интерфейсов, определённых в пакете javax.faces.component:

  • ActionSource: указывает, что компонент может запустить javax.faces.event.ActionEvent

  • ActionSource2: расширяет ActionSource и позволяет свойствам компонентов, ссылающимся на методы, которые обрабатывают события действия, использовать выражения метода, как определено в EL

  • EditableValueHolder: расширяет ValueHolder и указывает дополнительные функции для редактируемых компонентов, такие как валидация и генерация событий изменения значения

  • NamingContainer: требует, чтобы каждый компонент, имеющий родителем этот компоненте, имел уникальный идентификатор

  • StateHolder: обозначает, что компонент имеет состояние, которое должно быть сохранено между запросами

  • ValueHolder: указывает, что компонент поддерживает локальное значение, а также возможность доступа к данным слоя модели

Если ваш компонент расширяет UIComponentBase, он автоматически реализует только StateHolder. Поскольку все компоненты прямо или косвенно расширяют UIComponentBase, все они реализуют StateHolder. Любой компонент, который реализует StateHolder, также реализует интерфейс StateHelper, который расширяет StateHolder и определяет Map -подобный контракт, который упрощает для компонентов сохранение и восстановление состояния частичного просмотра.

Если ваш компонент расширяет один из других стандартных компонентов, он может также реализовать другие поведенческие интерфейсы в дополнение к StateHolder. Если ваш компонент расширяет UICommand, он автоматически реализует ActionSource2. Если ваш компонент расширяет UIOutput или один из классов компонентов, расширяющих UIOutput, он автоматически реализует ValueHolder. Если ваш компонент расширяет UIInput, он автоматически реализует EditableValueHolder и ValueHolder. См. документацию API JavaServer Faces, чтобы узнать, что реализуют другие классы компонентов.

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

Пример карты изображения Duke's Bookstore состоит из двух классов компонентов: AreaComponent и MapComponent. Класс MapComponent расширяет UICommand и поэтому реализует ActionSource2, что означает, что он может инициировать события действия, когда пользователь кликает на карту. Класс AreaComponent расширяет стандартный компонент UIOutput. Аннотация @FacesComponent регистрирует компоненты с JavaServer Faces:

@FacesComponent("DemoMap")
public class MapComponent extends UICommand {...}

@FacesComponent("DemoArea")
public class AreaComponent extends UIOutput {...}

Класс MapComponent представляет компонент, соответствующий тегу bookstore:map:

<bookstore:map id="bookMap"
               current="map1"
               immediate="true"
               action="bookstore">
    ...
</bookstore:map>

Класс AreaComponent представляет компонент, соответствующий тегу bookstore:area:

<bookstore:area id="map1" value="#{Book201}"
                onmouseover="resources/images/book_201.jpg"
                onmouseout="resources/images/book_all.jpg"
                targetImage="mapImage"/>

Объект MapComponent содержит один или несколько дочерних обхектов AreaComponent. Его поведение состоит из следующих действий:

  • Получение значения текущей выбранной области

  • Определение свойств, соответствующих значениям компонента

  • Генерация события, когда пользователь кликает на карту изображения

  • Очередь на событие

  • Сохранение своего состояния

  • Отрисовка тегов HTML map и input

MapComponent делегирует отображение HTML-тегов map и input классу MapRenderer.

AreaComponent связан с бином, который хранит форму и координаты области карты изображения. Вы увидите, как все эти данные доступны через выражение значения в Создание класса отрисовщика. Поведение AreaComponent состоит из:

  • Получение данных формы и координат из компонента

  • Установка id этого компонента значением скрытого тега

  • Отрисовка тега area, включая JavaScript для функций onmouseover, onmouseout и onclick

Хотя эти задачи фактически выполняются AreaRenderer, AreaComponent должен делегировать задачи AreaRenderer. Смотрите Делегирование отрисовки отрисовщику для получения дополнительной информации.

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

Указание семейства компонентов

Если ваш кастомный класс компонентов делегирует отрисовку, ему необходимо переопределить метод getFamily класса UIComponent, чтобы вернуть идентификатор семейства компонентов, который используется для ссылки на компонент или набор компонентов, которые могут быть предоставлены отрисовщиком или набором отрисовщиков. Семейство компонентов используется вместе с типом отрисовщика для поиска отрисовщиков, которые могут отобразить компонент:

public String getFamily() {
    return ("Map");
}

Идентификатор семейства компонентов Map должен совпадать с тем, который определён элементами component-family, включёнными в конфигурации компонента и отрисовщика в файле конфигурации приложения. Регистрация кастомного отрисовщика с помощью инструментария отрисовки объясняет, как определить семейство компонентов в конфигурации отрисовщика. Регистрация кастомного компонента объясняет, как определить семейство компонентов в конфигурации компонента.

Выполнение кодирования

В фазе отрисовки ответа JavaServer Faces обрабатывает методы кодирования всех компонентов и связанных с ними отрисовщиков представления. Методы кодирования преобразуют текущее локальное значение компонента в соответствующую разметку, которая представляет его в ответе.

Класс UIComponentBase определяет набор методов для отрисовки разметки: encodeBegin, encodeChildren и encodeEnd. Если у компонента есть дочерние компоненты, вам может потребоваться использовать более одного из этих методов для отрисовки компонента. В противном случае вся отрисовка должна выполняться в encodeEnd. Кроме того, вы можете использовать метод encodeALL, который охватывает все методы.

Поскольку MapComponent является родительским компонентом AreaComponent, теги area должны отображаться между начальным и конечным тегами map. Для этого класс MapRenderer отображает начальный тег map в encodeBegin, а остальную часть тега map в encodeEnd.

JavaServer Faces автоматически вызывает метод отрисовщика encodeEnd у AreaComponent после того, как он вызвал метод encodeBegin у MapRenderer и прежде чем он вызовет метод encodeEnd у MapRenderer. Если компонент должен выполнить отрисовку своих дочерних элементов, он делает это в методе encodeChildren.

Вот методы encodeBegin и encodeEnd для MapRenderer:

@Override
public void encodeBegin(FacesContext context, UIComponent component)
        throws IOException {
    if ((context == null)|| (component == null)) {
        throw new NullPointerException();
    }
    MapComponent map = (MapComponent) component;
    ResponseWriter writer = context.getResponseWriter();
    writer.startElement("map", map);
    writer.writeAttribute("name", map.getId(), "id");
}

@Override
public void encodeEnd(FacesContext context, UIComponent component)
        throws IOException {
    if ((context == null) || (component == null)){
        throw new NullPointerException();
    }
    MapComponent map = (MapComponent) component;
    ResponseWriter writer = context.getResponseWriter();
    writer.startElement("input", map);
    writer.writeAttribute("type", "hidden", null);
    writer.writeAttribute("name", getName(context,map), "clientId");
    writer.endElement("input");
    writer.endElement("map");
}

Обратите внимание, что encodeBegin отображает только начальный тег map. Метод encodeEnd визуализирует тег input и конечный тег map.

Методы кодирования принимают в аргументах UIComponent и javax.faces.context.FacesContext. Объект FacesContext содержит всю информацию, связанную с текущим запросом. Аргумент UIComponent — это компонент, который необходимо отобразить.

Остальная часть метода отображает разметку для объекта javax.faces.context.ResponseWriter, который записывает разметку для текущего ответа. В основном это включает передачу имён тегов HTML и имён атрибутов в объект ResponseWriter в виде строк, извлечение значений атрибутов компонента и передачу этих значений в объект ResponseWriter.

Метод startElement принимает String (имя тега) и компонент, которому соответствует тег (в данном случае, map). (Передача этой информации в объект ResponseWriter помогает среде разраборки понять, какие части сгенерированной разметки связаны с какими компонентами.)

После вызова startElement вы можете вызвать writeAttribute, чтобы отобразить атрибуты тега. Метод writeAttribute принимает имя атрибута, его значение и имя свойства или атрибута содержащего его компонента, соответствующего этому атрибуту. Последний параметр может быть нулевым, и он не будет отображаться.

Значение атрибута name тега map извлекается с помощью метода getId класса UIComponent, который возвращает уникальный идентификатор компонента. Значение атрибута name тега input извлекается с использованием метода getName(FacesContext, UIComponent) из MapRenderer.

Если вы хотите, чтобы ваш компонент выполнял сам свою собственную отрисовку, но при наличии отрисовщика делегировал её ему, добавьте следующие строки в метод кодирования, чтобы проверить, существует ли отрисовщик, связанный с этим компонентом:

if (getRendererType() != null) {
    super.encodeEnd(context);
    return;
}

Если доступен отрисовщик, этот метод вызывает метод encodeEnd родительского класса, который выполняет работу по поиску отрисовщика. Класс MapComponent делегирует всю отрисовку MapRenderer, поэтому ему не нужно проверять наличие доступных отрисовщиков.

В некоторых кастомных классах компонентов, которые расширяют стандартные компоненты, вам может понадобиться реализовать другие методы в дополнение к encodeEnd. Например, если нужно получить значение компонента из параметров запроса, вы также должны реализовать метод decode.

Выполнение декодирования

В фазе применения параметров запроса ​JavaServer Faces обрабатывает методы decode всех компонентов в дереве. Метод decode извлекает локальное значение компонента из параметров входящего запроса и использует реализацию javax.faces.convert.Converter для преобразования значения в тип, приемлемый для класса компонента.

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

Вот метод decode для MapRenderer:

@Override
public void decode(FacesContext context, UIComponent component) {
    if ((context == null) || (component == null)) {
        throw new NullPointerException();
    }
    MapComponent map = (MapComponent) component;
    String key = getName(context, map);
    String value = (String) context.getExternalContext().
            getRequestParameterMap().get(key);
    if (value != null)
        map.setCurrent(value);
    }
}

Метод decode сначала получает имя скрытого поля input, вызывая getName(FacesContext, UIComponent). Затем он использует это имя как ключ в отображении (Map) параметров запроса, чтобы получить текущее значение поля input. Это значение представляет текущую выбранную область. Наконец, он устанавливает значение атрибута current класса MapComponent в значение поля input.

Добавление компоненту возможности принимать выражения

Почти все атрибуты стандартных тегов JavaServer Faces могут принимать выражения, будь то выражения значений или выражения методов. Рекомендуется также включить атрибуты вашего компонента для принятия выражений, поскольку это даёт вам гораздо большую гибкость при написании страниц Facelets.

Чтобы атрибуты могли принимать выражения, класс компонента должен реализовывать get- и set- методы для свойств компонента. Эти методы могут использовать средства, предлагаемые интерфейсом StateHelper, для хранения и извлечения не только значений для этих свойств, но и состояния компонентов в нескольких запросах.

Поскольку MapComponent расширяет UICommand, класс UICommand уже выполняет работу по получению ValueExpression и MethodExpression объекты, связанные с каждым из поддерживаемых им атрибутов. Точно так же класс UIOutput, который расширяет AreaComponent, уже получает объекты ValueExpression для своих поддерживаемых атрибутов. Для обоих компонентов get- и set-методы сохраняют и извлекают значения ключей и состояния для атрибутов, как показано в этом фрагменте кода из AreaComponent:

enum PropertyKeys {
    alt, coords, shape, targetImage;
}
public String getAlt() {
    return (String) getStateHelper().eval(PropertyKeys.alt, null);
}
public void setAlt(String alt) {
    getStateHelper().put(PropertyKeys.alt, alt);
}
...

Однако если у вас есть кастомный класс компонентов, который расширяет UIComponentBase, вам нужно будет реализовать методы, которые получают объекты ValueExpression и MethodExpression, связанные с теми атрибутами, которые могут принимать выражения. Например, вы можете включить метод, который получает объект ValueExpression для атрибута immediate:

public boolean isImmediate() {
    if (this.immediateSet) {
        return (this.immediate);
    }
    ValueExpression ve = getValueExpression("immediate");
    if (ve != null) {
        Boolean value = (Boolean) ve.getValue(
            getFacesContext().getELContext());
        return (value.booleanValue());
    } else {
        return (this.immediate);
    }
}

Свойства, соответствующие атрибутам компонента, которые принимают выражения методов, должны принимать и возвращать объект MethodExpression. Например, если MapComponent расширяет UIComponentBase вместо UICommand, ему потребуется предоставить свойство action, которое возвращает и принимает объект MethodExpression:

public MethodExpression getAction() {
    return (this.action);
}
public void setAction(MethodExpression action) {
    this.action = action;
}

Сохранение и восстановление состояния

Как описано в Добавление компоненту возможности принимать выражения, использование интерфейса StateHelper позволяет вам сохранять состояние компонента одновременно с тем, как вы устанавливаете и извлекаете значения свойств. Реализация StateHelper позволяет частично сохранить состояние. Она сохраняет только изменения состояния с момента первоначального запроса, а не всё состояние, поскольку полное состояние можно восстановить в фазе восстановления представления.

Классы компонентов, которые реализуют StateHolder, могут предпочесть реализовать методы saveState(FacesContext) и restoreState(FacesContext, Object), чтобы помочь JavaServer Faces и восстановить состояние компонентов по нескольким запросам.

Чтобы сохранить набор значений, вы можете реализовать метод saveState(FacesContext). Этот метод вызывается в фазе отрисовки ответа, во время которой состояние ответа сохраняется для обработки последующих запросов. Вот гипотетический метод из MapComponent, который имеет только один атрибут current:

@Override
public Object saveState(FacesContext context) {
    Object values[] = new Object[2];
    values[0] = super.saveState(context);
    values[1] = current;
    return (values);
}

Этот метод инициализирует массив, который будет содержать сохранённое состояние. Затем сохраняется всё состояние, связанное с компонентом.

Компонент, который реализует StateHolder, может также предоставить реализацию для restoreState(FacesContext, Object), которая восстанавливает состояние компонента до того, которое было сохранено с помощью метода saveState(FacesContext). Метод restoreState(FacesContext, Object) вызывается в фазе восстановления представления, во время которой JavaServer Faces проверяет, было ли сохранено какое-либо состояние в последней фазе отрисовки ответа, и должно ли оно быть восстановлено при подготовке к следующему повторному отображению.

Вот гипотетический метод restoreState(FacesContext, Object) из MapComponent:

public void restoreState(FacesContext context, Object state) {
    Object values[] = (Object[]) state;
    super.restoreState(context, values[0]);
    current = (String) values[1];
}

Этот метод принимает объекты FacesContext и Object. Второй представляет массив, содержащий состояние компонента. Этот метод устанавливает свойства компонента в значения, сохранённые в массиве Object.

Независимо от того, реализуете ли вы эти методы в своём классе компонентов, вы можете использовать контекстный параметр javax.faces.STATE_SAVING_METHOD, чтобы указать в дескрипторе развёртывания, где вы хотите сохранить состояние: client или server. Если состояние сохраняется на клиенте, состояние всего представления отображается в скрытом поле на странице. По умолчанию состояние сохраняется на сервере.

Веб-приложения в примере Duke's Forest сохраняют состояние представления на клиенте.

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


Назад Вперёд Содержание
Логотип Oracle  Copyright © 2017, Oracle и/или её дочерних компаний. Все права защищены. Версия перевода 1.0.5 (Java EE Tutorial — русскоязычная версия)