Programmatically create and add composite component in backing bean
Asked Answered
S

1

8

I am working with a dynamic dashboard where users can pin and remove items as they like. Now I have a problem that I want to add existing composite component to the view from the backing bean. I've tried to find correct way to do this from the internet but no success so far. Here is the simple composite component I want to add:

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:cc="http://java.sun.com/jsf/composite"
      xmlns:ui="http://java.sun.com/jsf/facelets"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:p="http://primefaces.org/ui"
      xmlns:composite="http://java.sun.com/jsf/composite">
    <!-- INTERFACE -->
    <cc:interface>

    </cc:interface>

    <!-- IMPLEMENTATION -->
    <cc:implementation>
        <h:outputText value="TEST"/>
    </cc:implementation>
</html>

Here is the code which should return the composite component:

public static UIComponent getCompositeComponent(String xhtml, String namespace) {
    FacesContext fc = FacesContext.getCurrentInstance();
    Application app = fc.getApplication();
    Resource componentResource = app.getResourceHandler().createResource(xhtml, namespace);

    UIPanel facet = (UIPanel) app.createComponent(UIPanel.COMPONENT_TYPE);
    facet.setRendererType("javax.faces.Group");
    UIComponent composite = app.createComponent(fc, componentResource);
    composite.getFacets().put(UIComponent.COMPOSITE_FACET_NAME, facet);

    return composite;
}

And here is how I am using the function:

Column column = new Column();
UIComponent test = HtmlUtil.getCompositeComponent("test.xhtml", "comp");
column.getChildren().add(test);

But nothing is rendered inside the column. Any ideas how this could be done? I don't want to go with the rendered="#{bean.isThisRendered}" way because it does not fit in my use case.

Shriek answered 5/4, 2013 at 7:47 Comment(1)
Out of interest (the topic is horribly badly covered and this would bring me forward light years): Where are you invoking the 3-liner calling HtmlUtil.getCompositeComponent?Reconciliatory
D
11

This code is incomplete. You need to use FaceletContext#includeFacelet() afterwards to include the composite component resource in the composite component implementation. Here's an utility method which does the job. It's important to have the parent at hands, as it is the context where the #{cc} should be created in the EL scope. So this utility method also immediately adds the composite as a child of the given parent. Further, it's important to give the composite component a fixed ID, otherwise JSF wouldn't be able to process any form/input/command components inside the composite.

public static void includeCompositeComponent(UIComponent parent, String libraryName, String resourceName, String id) {
    // Prepare.
    FacesContext context = FacesContext.getCurrentInstance();
    Application application = context.getApplication();
    FaceletContext faceletContext = (FaceletContext) context.getAttributes().get(FaceletContext.FACELET_CONTEXT_KEY);

    // This basically creates <ui:component> based on <composite:interface>.
    Resource resource = application.getResourceHandler().createResource(resourceName, libraryName);
    UIComponent composite = application.createComponent(context, resource);
    composite.setId(id); // Mandatory for the case composite is part of UIForm! Otherwise JSF can't find inputs.

    // This basically creates <composite:implementation>.
    UIComponent implementation = application.createComponent(UIPanel.COMPONENT_TYPE);
    implementation.setRendererType("javax.faces.Group");
    composite.getFacets().put(UIComponent.COMPOSITE_FACET_NAME, implementation);

    // Now include the composite component file in the given parent.
    parent.getChildren().add(composite);
    parent.pushComponentToEL(context, composite); // This makes #{cc} available.
    try {
        faceletContext.includeFacelet(implementation, resource.getURL());
    } catch (IOException e) {
        throw new FacesException(e);
    } finally {
        parent.popComponentFromEL(context);
    }
}

So, in your particular example, use it as follows:

includeCompositeComponent(column, "comp", "test.xhtml", "someUniqueId");
Disagree answered 8/4, 2013 at 16:15 Comment(9)
Very interesting! Is it possible to use this method (maybe a little bit altered) to include external components? Instead of using an existing resource I would like to include components which not defined within the application.Jesselyn
@fnst: You can if necessary control resource handling with a custom resource handler. As long as you ultimately end up with a valid URL, it can basically point to everything (including file://, http://, etc and even custom protocols so that e.g. DB access is theoretically possible).Disagree
Thanks for your answer, have to try that :) Is it also possible to add value expressions as attributes for the composite components?Shriek
You're welcome. I'm not sure what you're concretely asking there.Disagree
Here's your bounty :) Anyways I already answered my own question.. The code to add value expressions for the composite component for example when using <comp:test someValue="#{test.value}"> can be achieved with new function parameter Map<String,String> valueExpressions and ExpressionFactory factory = application.getExpressionFactory(); ELContext ctx = context.getELContext(); for (Map.Entry<String, String> entry : valueExpressions.entrySet()) { ValueExpression expr = factory.createValueExpression(ctx, entry.getValue(), String.class); composite.setValueExpression(entry.getKey(), expr); }Shriek
Oh right. Yes, it's by the way not different from regular components, that's also why I wasn't exactly sure what you asked as it sounded like something specific to composites.Disagree
And here is some follow up for this question: #16056363Shriek
@Disagree Nice example. I have similar issue. Should the method be in managed bean or it is fine to integrate it into UIComponent? How exactly it should be integrated? Give more details pleaseEncore
Hi :) I just tried the example and it throws Argument Error: Parameter componentResource is null. Give me a tip pleaseEncore

© 2022 - 2024 — McMap. All rights reserved.