Как я могу получить доступ к содержимому чего-либо, созданного с помощью <ui: define> программным путем?

Где в контекстах я могу найти информацию для чего-то, построенного с помощью <ui:define>? Я хочу получить доступ к заголовку страницы, который был определен с помощью <ui:define name="title">Some title</ui:define> в моем bean.

Чтобы проиллюстрировать мой вопрос, я могу получить доступ к переменной, определенной с помощью

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

посмотрев на переменную mapper в EL-контексте, например

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

Это работает для <ui:param>, но как это делается для <ui:define>?

Ответ 1

Это невозможно с помощью стандартного API. Xtreme Biker опубликовал блестящий трюк, в котором значение <ui:param> указано в <ui:insert>, которое было бы переопределено (и, следовательно, отсутствует), когда <ui:define> фактически указано как ответ на Проверить, была ли указана ui: insert в клиенте шаблона

Альтернативой A (hacky) было бы создание специального обработчика меток для задания. <ui:define> находятся по имени, собранному в поле Map handlers класса тега CompositionHandler позади <ui:composition>. Это (к сожалению) конкретная реализация, Mojarra и MyFaces имеют свои собственные реализации, в соответствии с которыми Моджарра назвал поле handlers и MyFaces _handlers.

Поскольку поле просто protected, самым чистым было бы просто расширить класс тегов-меток CompositionHandler и выставить по крайней мере ключ в методе apply() как атрибут FaceletContext. Однако, поскольку сам класс CompositionHandler объявлен final, мы не можем его подклассы. Поэтому мы не можем обойти его как делегата и использовать хакерство для отражения в любом случае.

Вот пример kickoff, основанный на Mojarra, который собирает все объявленные имена обработчиков <ui:define> в Map<String, Boolean>, так что вы можете красиво использовать их в EL так, чтобы #{defined.foo ? '...' : '...'} соответственно #{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);
    }

}

Зарегистрируйте его в своем пользовательском my.taglib.xml:

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

Вы можете использовать его, как показано ниже:

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

Опять же, это хакерский (как его реализация), я бы не рекомендовал его использовать.

См. также: