Как расширить компонент UISelectOne для свойства enum, чтобы автоматически заполнять все значения enum как выбранные элементы?

Я хочу использовать упрощенный тег select one, который автоматически генерирует список элементов для перечислений. Итак, результат будет:

<s:enumSelectOneMenu value="#{myBean.enumValue}"/>

Итак, внутри компонента я могу получить тип enum и получить все значения enum с помощью отражения. Итак, в основном мне нужно переопределить единственный метод validateValue(..) из UiSelectOne и поместить туда список UiSelectItems в качестве дочернего элемента (так же, как это делается в Tomahawk, см. Компонент SelectOneLanguage).

Но что еще нужно сделать? Мне нужно описать атрибуты тега в моем собственном taglib.xml, но jsf-impl.jar не содержит xml-файлов facelets - только файл taglib, поэтому я не могу просто скопировать все оттуда. Кроме того, если я статически опишу тег в моем taglib.xml - мне придется обновлять его вручную в каждой новой версии JSF, что совсем нехорошо. Итак, каков лучший способ расширить компонент в JSF и избежать большого количества ручной работы по копированию и вставке?

PS Я использую JSF 2.0, но способ составных граней мне не подходит, так как он создает много проблем, так как составной элемент оборачивается компонентом NamingContainer. Поэтому мне нужен только "олдскульный" способ создания пользовательских компонентов.

Благодарю.

1 ответ

Решение

Один из лучших (но не самых простых) способов расширения некоторых компонентов JSF и добавления поведения без потери атрибутов - использование плагина JSF PrimeFaces. Некоторая информация об этом находится здесь: http://code.google.com/p/primefaces/wiki/BuildingFromSource. Хотя он имеет некоторые жестко запрограммированные значения (имя лицевой стороны taglib и путь к выходному каталогу, куда помещается сгенерированная taglib), его можно изменить и перестроить локально. Вот пример плагина PF jsf.

   <!-- Primefaces maven plugin -->
            <plugin>
                <groupId>org.primefaces</groupId>
                <artifactId>maven-jsf-plugin</artifactId>
                <version>1.2.1-SNAPSHOT</version>
                <executions>
                    <execution>
                        <id>generate-ui</id>
                        <phase>generate-sources</phase>
                        <configuration>
                            <uri>http://www.mycompany.com/tags</uri>
                            <name>rstk-tag</name>
                            <jsfVersion>2</jsfVersion>
                            <templatesDir>src/main/java-templates</templatesDir>
                            <componentConfigsDir>src/main/resources-maven-jsf/ui</componentConfigsDir>
                            <!-- <standardFacesConfig>src/main/resources-maven-jsf/standard-faces-config.xml</standardFacesConfig> -->
<!-- These are new attributes added manually to plugin source code! -->
                            <standardFaceletsTaglib>src/main/resources-maven-jsf/standard-facelets-taglib.xml</standardFaceletsTaglib>
                            <faceletsOutputDirectory>target/generated-sources/maven-jsf-plugin/META-INF</faceletsOutputDirectory>
                        </configuration>
                        <goals>
                            <goal>generate-components</goal>
                            <goal>generate-facelets-taglib</goal>
                        </goals>
                    </execution>                    
                </executions>
            </plugin>

После этого следующие метаданные xml могут быть использованы для генерации:

 <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE component SYSTEM "../misc/component.dtd" [
<!ENTITY standard_uicomponent_attributes        SYSTEM "../entities/standard_uicomponent_attributes.xml">
<!ENTITY output_component_attributes            SYSTEM "../entities/output_component_attributes.xml">
<!ENTITY input_component_attributes             SYSTEM "../entities/input_component_attributes.xml">
]>
<component>
    <tag>enumSelectOneMenu</tag>
    <tagClass>com.rstk.kasko.component.EnumSelectOneMenuTag</tagClass>
    <componentClass>com.rstk.kasko.component.EnumSelectOneMenu</componentClass>
    <componentType>com.rstk.kasko.component.EnumSelectOneMenu</componentType>
    <componentFamily>javax.faces.SelectOne</componentFamily>
    <rendererType>javax.faces.Menu</rendererType>
    <parent>javax.faces.component.html.HtmlSelectOneMenu</parent>
    <description>The tag for select one menu, which renders the enumerations list. No children necessary for this</description>
    <attributes>
        &input_component_attributes;
    </attributes>

</component>

Это вместе с EnumSelectOneMenuTemplate.java (шаблон, который вставляется в сгенерированный код компонента) позволяет генерировать:

  1. taglib.xml со ВСЕМ стандартным html выберите один атрибут меню
  2. Класс компонента, который содержит пользовательские логики для рендеринга clidhren:

    public class EnumSelectOneMenu extends HtmlSelectOneMenu {
    ... // Generated staff here
    public boolean getRendersChildren() {
            return true;
        }

        /**
         * @see javax.faces.component.UIComponentBase#encodeBegin(javax.faces.context.FacesContext)
         */
        @Override
        public void encodeBegin(FacesContext context) throws IOException {
            super.encodeBegin(context);

            if (context.isPostback()) {
                return;
            }

            UISelectItems selectItems = new UISelectItems();
            try {
                selectItems.setValue(getEnumValuesList());
            } catch (Exception e) {
                log.error("Failed to create enum list", e);
            }
            getChildren().add(selectItems);

        }

        /**
         * Creates the list of select items of format [ENUM, enum.getDisplay()] 
         * @return
         */
        private List getEnumValuesList() {
            List result = new ArrayList();
            ValueExpression ve = getValueExpression("value");
            Class enumClass = ve.getType(getFacesContext().getELContext());
            Method method = ReflectionUtils.findMethod(enumClass, "getDisplay", null);
            for (Object e : ve.getType(getFacesContext().getELContext()).getEnumConstants()) {
                result.add(new SelectItem(e, (String) ReflectionUtils.invokeMethod(method, e)));
            }
            return result;
        }
    }

Затем этот компонент можно использовать как простой JSF-компонент для выбора одного компонента (со ВСЕМИ стандартными атрибутами), но не требует, чтобы элементы выбора добавлялись каждый раз, и позволяет помещать туда любые другие дочерние элементы:

<s:enumSelectOneMenu value="#{polis.osMatrixType}" id="registryType">
        <p:ajax listener="#{osagoPolisBean.rollOsagoEndDate}" update="osagoUsagePeriod" process="osagoTable" event="change"/>                               
</s:enumSelectOneMenu>  
Другие вопросы по тегам