【问题标题】:Custom JSF component renderer: how to render HTML around another element自定义 JSF 组件渲染器:如何围绕另一个元素渲染 HTML
【发布时间】:2016-11-06 22:33:47
【问题描述】:

我有 2 个元素,banana 和一个 outputText,其中banana 是一个自定义 JSF 组件,在香蕉渲染器中,我想生成包含指定元素的 HTML。

xhtml:

<h:outputText id="theApple" value="This is an Apple" />
.
.
.
<my:banana for="theApple" link="http://www.banana.com" />

bananaRenderer(将目标元素包含在锚链接中):

@Override
public void encodeBegin(FacesContext context, UIComponent component) throws IOException {
    ResponseWriter responseWriter = context.getResponseWriter();
    Banana banana = Banana.class.cast(component);
    responseWriter.startElement("a", null);
    responseWriter.writeAttribute("id", banana.getClientId(context), null);
    responseWriter.writeAttribute("href", banana.getLink(), null);
}

@Override
public void encodeEnd(FacesContext context, UIComponent component) throws IOException {
    ResponseWriter responseWriter = context.getResponseWriter();
    Banana banana = Banana.class.cast(component);
    responseWriter.endElement("a");
}

我想要达到的目标:

<a href="http://www.banana.com">This is an Apple</a>
.
.
.

如您所见,我不想在香蕉组件上编码,而是在它使用“for”属性的目标元素上编码。 我看到的最接近的例子是 PrimeFaces 工具提示,但我不太明白它是如何利用“for”属性的。 http://www.primefaces.org/showcase/ui/overlay/tooltip/tooltipOptions.xhtml#

如果有人能指出我正确的方向,那将不胜感激。谢谢。

【问题讨论】:

    标签: jsf encoding hyperlink custom-component custom-renderer


    【解决方案1】:

    您可以在PreRenderViewEvent 期间操作组件树。在那一刻,所有组件都保证存在于树中,并且通过在树中添加/移动/删除组件来保证操作组件树是安全的。您可以通过UIComponent#findComponent() 在树中找到其他组件。您可以使用UIComponent#getParent()UIComponent#getChildren() 遍历组件树。 getChildren() 返回一个可变列表,保证在PreRenderViewEvent 期间可以安全地进行操作。

    鉴于您想用超链接包装给定组件,并且已经存在一个 JSF 超链接组件 &lt;h:outputLink&gt;,因此更容易扩展和重用它以保持代码简单和干燥。这是一个基本的启动示例:

    @FacesComponent(createTag=true)
    public class Banana extends HtmlOutputLink implements SystemEventListener {
    
        public Banana() {
            getFacesContext().getViewRoot().subscribeToViewEvent(PreRenderViewEvent.class, this);
        }
    
        @Override
        public boolean isListenerForSource(Object source) {
            return source instanceof UIViewRoot;
        }
    
        @Override
        public void processEvent(SystemEvent event) throws AbortProcessingException {
            String forClientId = (String) getAttributes().get("for");
    
            if (forClientId != null) {
                UIComponent forComponent = findComponent(forClientId);
                List<UIComponent> forComponentSiblings = forComponent.getParent().getChildren();
                int originalPosition = forComponentSiblings.indexOf(forComponent);
                forComponentSiblings.add(originalPosition, this);
                getChildren().add(forComponent);
            }
        }
    
    }
    

    <!DOCTYPE html>
    <html lang="en"
        xmlns="http://www.w3.org/1999/xhtml"
        xmlns:h="http://xmlns.jcp.org/jsf/html"
        xmlns:my="http://xmlns.jcp.org/jsf/component"
    >
        <h:head>
            <title>SO question 38197494</title>
        </h:head>
        <h:body>
            <h:outputText id="theApple" value="This is an Apple" />
            <my:banana for="theApple" value="http://www.banana.com" />
        </h:body>
    </html>
    

    请注意,当您已经执行children.add() 时,不需要显式调用children.remove()。它会自动照顾孩子的父母。

    【讨论】:

    • 只要苹果先行,您的解决方案就会运作良好。不过,谢谢。
    • 我已经修复了代码以涵盖另一种情况。不过,谢谢。
    【解决方案2】:

    如果我正确理解了这个问题,您想定义 2 个单独的 JSF 组件来生成通用 HTML 代码。一种可能的解决方案(如@BalusC 所示)是组件树操作,但这与在 xhtml 中以更自然的方式定义几乎相同:

    <my:banana link="http://www.banana.com">
        <h:outputText value="This is an Apple" />
    </my:banana>
    

    如果出于某些需要您想(重新)渲染通过for-attribute 引用的组件,我建议在客户端使用javascript 进行DOM 操作。您只需要使用适当的 JavaScript 代码在 banana 渲染器中定义 script 标记:

    @Override
    public void encodeEnd(FacesContext context, UIComponent component) throws IOException {
        ResponseWriter writer = context.getResponseWriter();
        writer.startElement("script", null);
        writer.writeAttribute("type", "text/javascript", null);
        // find DOM-element by 'for'-attribute, and wrap it with a hyperlink 
        writer.endElement("script");
    }
    

    这几乎就是 Primefaces 工具提示的构建方式。

    好处是banana 渲染器不知道&lt;h:outputText&gt;(或任何其他)将如何渲染。

    【讨论】:

    • 来自问题:“正如你所看到的,我不想在香蕉组件上编码,而是在它使用“for”属性的目标元素上编码。“ 所以一部分你的答案不是“正确的”。如果您查看@BalusC 的答案中的代码,您会发现它不知道 outputText 是如何呈现的。所以你的第二个答案的第二部分不是“增强”
    猜你喜欢
    • 2023-03-25
    • 2012-09-13
    • 1970-01-01
    • 2023-03-13
    • 2023-03-23
    • 2021-10-01
    • 2013-01-20
    • 2022-07-05
    • 2013-07-03
    相关资源
    最近更新 更多