Динамическое создание пользовательского компонента JSF 2.2

Я написал (точнее, изменил) пользовательский компонент для переключателей, появляющихся внутри таблицы данных, на основе этой статьи. Я немного изменил его для JSF 2.2, используя теги @FacesRenderer и @FacesComponent и исключив класс пользовательского тега. На моей XHTML-странице все работает нормально

xmlns:custom="http://mynamespace"
...
<h:dataTable id="mySampleTable3"
             value="#{myBackingBean.sampleTable3}"
             var="item"
             width="100%"
             border="1"
             first="0">           
    <f:facet name="header">
         <h:panelGrid id="gridHeader3" columns="2" width="100%" >
              <h:outputText id="outputText3" 
                       value="Radios grouped in row(With our custom tag)"/>   
         </h:panelGrid>
    </f:facet>
    <h:column> 
         <f:facet name="header">
                 <h:outputText value="Emp Name" />
         </f:facet>
         <h:outputText id="empName" value="#{item.empName}"/>
    </h:column>         
    <h:column> 
         <f:facet name="header">
                 <h:outputText value="Excellent" />
         </f:facet>
         <custom:customradio id="myRadioId2" 
                             name="myRadioRow" 
                             value="#{item.radAns}"
                             itemValue="E" />                     
    </h:column>         
    <h:column> 
         <f:facet name="header">
                  <h:outputText value="Good" />
         </f:facet>
         <custom:customradio id="myRadioId3" 
                             name="myRadioRow" 
                             value="#{item.radAns}"
                             itemValue="G" />                     
     </h:column>   
     ...      
</h:dataTable>

и данные радиокнопок работают правильно (они сгруппированы по строкам). Проблема возникает, когда я использую этот компонент динамически. Так что я

HtmlPanelGrid radioGrid = (HtmlPanelGrid) getApplication()
                          .createComponent(HtmlPanelGrid.COMPONENT_TYPE);

...

UICustomSelectOneRadio customSelectOneRadio = (UICustomSelectOneRadio) getApplication().
                    createComponent(UICustomSelectOneRadio.COMPONENT_TYPE);


customSelectOneRadio.setId("rb_" + question.getQuestionId() + "_" + row + "_" + col);
customSelectOneRadio.setName("myRadioRow");

String binding = "#{" + beanName + "." + propName + 
              "[\"" + question.getQuestionId().toString() + "_" + 
              subQuestion.getId() + "\"]}";

ValueExpression ve = getExpressionFactory().
                    createValueExpression(getELContext(), binding, String.class);
customSelectOneRadio.setValueExpression("value", ve);

customSelectOneRadio.setItemValue(value);

radioGrid.getChildren().add(customSelectOneRadio);

К сожалению, несмотря на то, что данные отображаются, переключатели не отображаются. Появляются пустые столбцы. Мне было интересно, знает ли кто-нибудь, есть ли какой-то метод, который я должен переопределить в своем рендерере. Мой выглядит так:

@FacesRenderer(componentFamily = UICustomSelectOneRadio.COMPONENT_FAMILY, rendererType = HTMLCustomSelectOneRadioRenderer.RENDERER_TYPE)
public class HTMLCustomSelectOneRadioRenderer extends Renderer {

    public static final String RENDERER_TYPE = "com.myapp.jsf.renderers.HTMLCustomSelectOneRadioRenderer";

    @Override
    public void decode(FacesContext context, UIComponent component) {
        if ((context == null) || (component == null)) {
            throw new NullPointerException();
        }

        UICustomSelectOneRadio customSelectOneRadio;
        if (component instanceof UICustomSelectOneRadio) {
            customSelectOneRadio = (UICustomSelectOneRadio) component;
        } else {
            return;
        }

        Map map = context.getExternalContext().getRequestParameterMap();
        String name = getName(customSelectOneRadio, context);
        if (map.containsKey(name)) {
            String value = (String) map.get(name);
            if (value != null) {
                setSubmittedValue(component, value);
            }

        }
    }

    private void setSubmittedValue(UIComponent component, Object obj) {
        if (component instanceof UIInput) {
            ((UIInput) component).setSubmittedValue(obj);
        }
    }


    private String getName(UICustomSelectOneRadio customSelectOneRadio,
            FacesContext context) {

        UIComponent parentUIComponent
            = getParentDataTableFromHierarchy(customSelectOneRadio);
        if (parentUIComponent == null) {
            return customSelectOneRadio.getClientId(context);
        } else {
            if (customSelectOneRadio.getOverrideName() != null
                    && customSelectOneRadio.getOverrideName().equals("true")) {
                return customSelectOneRadio.getName();
            } else {
                String id = customSelectOneRadio.getClientId(context);
                int lastIndexOfColon = id.lastIndexOf(":");
                String partName = "";
                if (lastIndexOfColon != -1) {
                    partName = id.substring(0, lastIndexOfColon + 1);
                    if (customSelectOneRadio.getName() == null) {
                        partName = partName + "generatedRad";
                    } else {
                        partName = partName + customSelectOneRadio.getName();
                    }
                }
                return partName;
            }
        }
    }

    private UIComponent getParentDataTableFromHierarchy(UIComponent uiComponent) {
        if (uiComponent == null) {
            return null;
        }
        if (uiComponent instanceof UIData) {
            return uiComponent;
        } else {
            //try to find recursively in the Component tree hierarchy
            return getParentDataTableFromHierarchy(uiComponent.getParent());
        }
    }

    @Override
    public void encodeEnd(FacesContext context, UIComponent component)
            throws IOException {
        if ((context == null) || (component == null)) {
            throw new NullPointerException();
        }

        UICustomSelectOneRadio customSelectOneRadio
            = (UICustomSelectOneRadio) component;

        if (component.isRendered()) {
            ResponseWriter writer = context.getResponseWriter();

            writer.startElement("input", component);
            writer.writeAttribute("type", "radio", "type");
            writer.writeAttribute("id", component.getClientId(context), "id");
            writer.writeAttribute("name", getName(customSelectOneRadio, context), "name");

            if (customSelectOneRadio.getStyleClass() != null && customSelectOneRadio.getStyleClass().trim().
                    length() > 0) {
                writer.writeAttribute("class", customSelectOneRadio.getStyleClass().trim(), "class");
            }
            if (customSelectOneRadio.getStyle() != null && customSelectOneRadio.getStyle().trim().length() > 0) {
                writer.writeAttribute("style", customSelectOneRadio.getStyle().trim(), "style");
            }

         ...

            if (customSelectOneRadio.getValue() != null
                    && customSelectOneRadio.getValue().equals(customSelectOneRadio.getItemValue())) {
                writer.writeAttribute("checked", "checked", "checked");
            }

            if (customSelectOneRadio.getItemLabel() != null) {
                writer.write(customSelectOneRadio.getItemLabel());
            }
            writer.endElement("input");
        }
    }

}

Подводя итог, можно сказать, что это работает, когда я использую пользовательский тег в файле facelets/XHTML, но не когда я использую его динамически, например.

<h:panelGroup id="dynamicPanel" layout="block" binding="#{documentController.dynamicPanel}"/>

Если у кого-то есть какие-либо идеи о том, что я мог пропустить, я был бы признателен.


person Quantum    schedule 24.06.2014    source источник


Ответы (1)


Попался! Все вращалось вокруг изменения свойства rendererType. Из приведенного выше класса Renderer вы можете видеть, что я установил его самостоятельно, т.е.

public static final String RENDERER_TYPE = "com.myapp.jsf.renderers.HTMLCustomSelectOneRadioRenderer";

(1) я изменил его на

public static final String RENDERER_TYPE = "javax.faces.Radio";

(2) Я также изменил свой файл xxx.taglib.xml, чтобы отразить это, например.

<namespace>http://mycomponents</namespace>
<tag>
    <tag-name>customradio</tag-name>
    <component>
        <component-type>CustomRadio</component-type>
        <renderer-type>javax.faces.Radio</renderer-type>     /*<----- See Here */
    </component>
</tag>

(3) Обратите внимание на последний шаг. Мне также пришлось создать следующий конструктор в моем компоненте UICustomSelectOneRadio. Без этого тоже не получалось.

public UICustomSelectOneRadio() {
    this.setRendererType("javax.faces.Radio"); 
}

Помните - я расширил свой компонент от UIInput, поэтому javax.faces.Text был бы установлен. Оставить мой component family как «CustomRadio» было нормально.

Я до сих пор не на 100% знаком с типами рендереров и т. д., но для тех, кому нужна небольшая информация, BalusC хорошо объясняет это здесь.

person Quantum    schedule 26.06.2014