如何以编程方式访问使用 <ui:define> 创建的内容?

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

在上下文中的什么位置可以找到有关使用 <ui:define> 构建的内容的信息?我想访问在我的 bean 中用 <ui:define name="title">Some title</ui:define> 定义的页面标题。

为了说明我的问题,我可以访问用

定义的变量
<ui:param name="myVariable" value="This is my variable!"/>

通过查看 EL 上下文中的变量映射器,像这样

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

这适用于 <ui:param>,但它如何适用于 <ui:define>

通过标准 API 这是不可能的。 Xtreme Biker 发布了一个绝妙的技巧,即在 <ui:insert> 中指定了一个“默认”<ui:param> 值,当 <ui:define> 实际上被指定为答案时,该值将被覆盖(因此不存在)Test if ui:insert has been defined in the template client

一个(hacky)替代方案是为该作业创建一个自定义标签处理程序。 <ui:define> 的名称收集在 <ui:composition> 后面的 CompositionHandler 标记处理程序 class 的 Map handlers 字段中。这是(不幸的)具体实现,Mojarra and MyFaces 有自己的实现,Mojarra 将字段命名为 handlers 和 MyFaces _handlers.

由于该字段只是 protected,最干净的方法是扩展 CompositionHandler taghandler class 并至少公开 apply() 方法中的键集作为属性FaceletContext。但是,由于 CompositionHandler class 本身被声明为 final,我们不能对其进行子class。因此,我们不能绕过将其包装为委托并使用一些反射 hackery 来获取该字段。

这是一个基于 Mojarra 的启动示例,它在 Map<String, Boolean> 中收集所有声明的 <ui:define> 处理程序名称,以便您可以在 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>

同样,这很老套(因为它是特定于实现的),我不建议使用它。

另请参阅:

  • Custom Facelet component in JSF