Search code examples
jsfjsf-2.2composite-component

Dynamically instantiate composite components on page, on clicking CommandButton


Following on from the response by the legendary BalusC to this post:

How to programmatically or dynamically create a composite component in JSF 2

Had I sufficient points I would attach a comment to that post -- but I don't have sufficient points .

Problem as follows.

I'm trying to set up a commandButton which adds a JSF composite component dynamically to an xhtml file. With the intention that clicking it multiple times will put multiple instances of the component on the page.

So I have a button on an XHTML file:

<h:commandButton action="#{assessingController.addQuestion}" value="Add a   Question"></h:commandButton>

Which calls a method on AssessingController:

public void addQuestion() {
    UIComponent parent = null;
    includeCompositeComponent(parent, "http://xmlns.jcp.org/jsf/composite/components", "questionComposite", "someId");   
}

UIComponent parent = null; -- because it has to be instantiated or referenced somehow, before being passed into includeCompositeComponent . But as noted below - making it null might be causing the null pointer exception, (so what should I do instead?)

includeCompositeComponent method is as per the JSF 2.2 method referred to by BalusC in the above post:

public void includeCompositeComponent(UIComponent parent, String taglibURI, String tagName, String id) {
    FacesContext context = FacesContext.getCurrentInstance();
    UIComponent composite = context.getApplication().getViewHandler()
        .getViewDeclarationLanguage(context, context.getViewRoot().getViewId())
        .createComponent(context, taglibURI, tagName, null);
composite.setId(id);
    parent.getChildren().add(composite);
}

When I click on the commandButton, logs as follows:

javax.faces.el.EvaluationException: java.lang.NullPointerException
at javax.faces.component.MethodBindingMethodExpressionAdapter.invoke(MethodBindingMethodExpressionAdapter.java:101)
at com.sun.faces.application.ActionListenerImpl.processAction(ActionListenerImpl.java:102)
at javax.faces.component.UICommand.broadcast(UICommand.java:315) ...
Caused by: java.lang.NullPointerException
at controllers.AssessingController.includeCompositeComponent(AssessingController.java:123)

AssessingController.java:123 is this line:

parent.getChildren().add(composite);

Composite is not null (checked that). So, obviously perhaps, - parent is null and that's where the problem is.

So how can I better reference UIComponent parent to begin with? Do I need to make it refer to something on the xhtml file? I'm presuming it needs some kind of a placeholder which will serve as the parent(?). Right now all there is on the xhtml page is the commandButton.

Thank you all.


Solution

  • The parent is supposed to represent the component where you'd like to include the composite component in.

    Imagine that you ultimately want to end up with this plain XHTML representation:

    <h:panelGroup id="questions">
        <your:questionComposite />
    </h:panelGroup>
    

    You should then supply exactly that <h:panelGroup> component as parent.

    <h:form> 
        <h:commandButton ... action="#{bean.addQuestion}" />
    </h:form>
    <h:panelGroup id="questions" />
    
    public void addQuestion() {
        UIComponent parent = context.getViewRoot().findComponent("questions");
        // ...
    }
    

    Or, by passing the concrete component itself:

    <h:form> 
        <h:commandButton ... action="#{bean.addQuestion(questions)}" />
    </h:form>
    <h:panelGroup id="questions" binding="#{questions}" />
    
    public void addQuestion(UIComponent parent) {
        // ...
    }
    

    Unrelated to the concrete problem: there's a thinking/design mistake here. You should rather use an <ui:repeat><your:compositeComponent> and then feed from a @ViewScoped bean to the <ui:repeat> a dynamically sized list of entities representing the composite's model value.

    <h:form> 
        <h:commandButton ... action="#{bean.addQuestion}" />
    </h:form>
    <ui:repeat value="#{bean.questions}" var="question">
        <your:questionComposite value="#{question}" />
    </ui:repeat>
    
    private List<Question> questions;
    
    public void addQuestion() {
        questions.add(new Question());
    }
    

    Anytime you need to deal with raw UIComponent instances in a backing bean, take a pause and carefully research or ask if you're really doing things the right way. Perhaps it belongs in a backing component instead, or could just be done with pure XHTML.

    See also: