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.

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
drodil
  • 2,292
  • 1
  • 22
  • 37
  • 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`? – Kalle Richter Jun 21 '17 at 16:27

1 Answers1

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");
BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • 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. – fnst Apr 10 '13 at 08:45
  • 1
    @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). – BalusC Apr 10 '13 at 10:57
  • Thanks for your answer, have to try that :) Is it also possible to add value expressions as attributes for the composite components? – drodil Apr 11 '13 at 11:45
  • You're welcome. I'm not sure what you're concretely asking there. – BalusC Apr 11 '13 at 11:52
  • 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 can be achieved with new function parameter Map valueExpressions and ExpressionFactory factory = application.getExpressionFactory(); ELContext ctx = context.getELContext(); for (Map.Entry entry : valueExpressions.entrySet()) { ValueExpression expr = factory.createValueExpression(ctx, entry.getValue(), String.class); composite.setValueExpression(entry.getKey(), expr); } – drodil Apr 11 '13 at 12:28
  • 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. – BalusC Apr 11 '13 at 12:50
  • And here is some follow up for this question: http://stackoverflow.com/questions/16055863/datatable-inside-programatically-added-composite-component – drodil Apr 17 '13 at 10:09
  • @BalusC 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 please – user390525 Oct 12 '17 at 16:43
  • Hi :) I just tried the example and it throws `Argument Error: Parameter componentResource is null`. Give me a tip please – user390525 Oct 13 '17 at 13:10