@FacesComponent("DemoMap")
public class MapComponent extends UICommand {...}
@FacesComponent("DemoArea")
public class AreaComponent extends UIOutput {...}
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.
Назад | Вперёд | Содержание |