Johannes
Johannes

Reputation: 868

How can I access the content of something created with <ui:define> programmatically?

Where in the contexts can I find the information for something built with a <ui:define>? I want to access a page title that has been defined with <ui:define name="title">Some title</ui:define> in my bean.

To illustrate my question, I can access a variable defined with

<ui:param name="myVariable" value="This is my variable!"/>

by looking at the variable mapper in the EL context, like this

VariableMapper variableMapper = elContext.getVariableMapper();
String myVariable = variableMapper.resolveVariable("myVariable").getValue(elContext).toString();

This works for <ui:param>, but how is it done for <ui:define>?

Upvotes: 2

Views: 1271

Answers (1)

BalusC
BalusC

Reputation: 1109625

This is not possible via standard API. Xtreme Biker has posted a brilliant trick whereby a "default" <ui:param> value is specified inside the <ui:insert> which would be overriden (and thus absent) when a <ui:define> is actually specified as answer on Test if ui:insert has been defined in the template client

A (hacky) alternative would be to create a custom taghandler for the job. The <ui:define>s are by their name collected in Map handlers field of the CompositionHandler taghandler class behind <ui:composition>. This is (unfortunately) implementation specific, Mojarra and MyFaces have their own implementations whereby Mojarra has named the field handlers and MyFaces _handlers.

As the field is just protected, cleanest would be to just extend the CompositionHandler taghandler class and expose at least the keyset in the apply() method as attribute of FaceletContext. However, as the CompositionHandler class itself is declared final, we can't subclass it. Therefore, we can't go around wrapping it as a delegate and use some reflection hackery to grab the field anyway.

Here's a kickoff example based on Mojarra which collects all declared <ui:define> handler names in a Map<String, Boolean> so that you can nicely use them in EL like so #{defined.foo ? '...' : '...'} respectively #{not defined.foo ? '...' : '...'}.

public class DefineAwareCompositionHandler extends TagHandlerImpl implements TemplateClient {

    private CompositionHandler delegate;
    private Map<String, Boolean> defined;

    @SuppressWarnings("unchecked")
    public DefineAwareCompositionHandler(TagConfig config) {
        super(config);
        delegate = new CompositionHandler(config);

        try {
            Field field = delegate.getClass().getDeclaredField("handlers");
            field.setAccessible(true);
            Map<String, DefineHandler> handlers = (Map<String, DefineHandler>) field.get(delegate);

            if (handlers != null) {
                defined = new HashMap<>();

                for (String name : handlers.keySet()) {
                    defined.put(name, true);
                }
            }
        }
        catch (Exception e) {
            throw new FaceletException(e);
        }
    }

    @Override
    public void apply(FaceletContext ctx, UIComponent parent) throws IOException {
        ctx.setAttribute("defined", defined);
        delegate.apply(ctx, parent);
    }

    @Override
    public boolean apply(FaceletContext ctx, UIComponent parent, String name) throws IOException {
        return delegate.apply(ctx, parent, name);
    }

}

Register it as follows in your custom my.taglib.xml:

<tag>
    <tag-name>composition</tag-name>
    <handler-class>com.example.DefineAwareCompositionHandler</handler-class>
</tag>

You could make use of it as below:

<my:composition
    xmlns="http://www.w3.org/1999/xhtml"
    xmlns:f="http://xmlns.jcp.org/jsf/core"
    xmlns:h="http://xmlns.jcp.org/jsf/html"
    xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
    xmlns:my="http://example.com/ui"
>
    <ui:insert name="foo">
        ...
    </ui:insert>

    <div class="#{defined.foo ? 'style1' : 'style2'}">
        ...
    </div>
</my:composition>

Again, this is hacky (as it's implementation specific), I'd not recommend using it.

See also:

Upvotes: 2

Related Questions