How to extend UISelectOne component for enum property to automatically populate all enum values as select items?

StackOverflow https://stackoverflow.com/questions/7770768

Question

I want to use simplified select one tag, which would generate select items list for enums automatically. So, the result would be:

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

So, inside the component I can get the enum type and get all enum values using reflection. So, basically I need to override the only one method validateValue(..) from UiSelectOne and put the UiSelectItems list as the child there (in the same manner as it is done in Tomahawk, see SelectOneLanguage component).

But what else should be done? I need to describe tag attributes in my own taglib.xml but jsf-impl.jar does not contain facelets xml - only taglib file, so I cannot simply copy everything from there. Also, if I statically describe tag in my taglib.xml - I will have to update it manually on each new version of JSF, which is not good at all. So, which is the best way to extend the component in JSF and avoid lots of manual copy-paste work?

P.s. I'm using JSF 2.0, but composite-facelets way is not suitable for me as it produces lots of problems as composite element is wrapped by NamingContainer component. So I need only "oldschool" way to create custom components.

Thanks.

Was it helpful?

Solution

One of best (but not the easiest) way to extend some JSF components and add the behaviour while not loosing attributes is using PrimeFaces JSF plugin. Some information about it is here: http://code.google.com/p/primefaces/wiki/BuildingFromSource. Though it has some hardcoded values (name of facelets taglib and way to output directory, where generated taglib is put) and can be changed and rebuilt locally Here is the example of PF jsf plugin

   <!-- 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>

After this the following metadata xml can be used for generation:

 <?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>

This together with EnumSelectOneMenuTemplate.java (the template, which is inserted into generated component code) allows to generate:

  1. taglib.xml with ALL standard html select one menu attributes
  2. The component class, which contains custom logics for rendering 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;
        }
    }

Then this component can be used as simple JSF select one component (with ALL standard attributes), but does not require select items to be added each time and allows any other childre be placed there:

<s:enumSelectOneMenu value="#{polis.osMatrixType}" id="registryType">
        <p:ajax listener="#{osagoPolisBean.rollOsagoEndDate}" update="osagoUsagePeriod" process="osagoTable" event="change"/>                               
</s:enumSelectOneMenu>  
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top