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

1

2

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>?

Baffle answered 2/4, 2015 at 10:22 Comment(5)
What exactly do you want to do with it? It basically represents a partial JSF component tree and not necessarily a plain vanilla String like ui:param.Theomania
@BalusC: I simply want to check, if something called title had been defined and if not, set a generic page title.. and I am curious, how this can be done programmatically. I was searching for the component tree with the key title in the contexts, but could not find it.Baffle
OK, you basically want to check if <ui:define name="title"> for an <ui:insert name="title"> has been defined in the template client, regardless of the content? I.e. you're only interested in having a boolean true or false based on that which you can then use elsewhere in the template? If so, is this then acceptable as dupe? stackoverflow.com/questions/26070704/…Theomania
@BalusC: The page title should have this form: AppName - PageTitle. Some pages do not define a PageTitle, they get AppName as a generic title. Some do and for these pages the defined PageTitle shall be concatenated with the AppName and dash prefix. As I was working on this, I tried to retrieve the content of the ui:define in a bean, to see how and where this information is stored in a context and could not find it. I spent hours trying to solve this and I could not find any relevant information about this on the net. Any hints in the right direction are also very welcome.Baffle
@BalusC: The link you have provided is definitely an acceptable solution for this problem, but I am interested in learning how to access the ui:define block via code.Baffle
T
2

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:

Theomania answered 2/4, 2015 at 12:52 Comment(1)
I see now why I could not find information about this so easily. Thank you very much for providing so much insight.Baffle

© 2022 - 2024 — McMap. All rights reserved.