自定义JSF组件渲染器:如何围绕另一个元素呈现HTML

时间:2016-07-05 07:21:15

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

我有2个元素,banana和outputText,其中banana是自定义JSF组件,在banana呈现器中,我想生成包含指定元素的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>
.
.
.

正如你所看到的,我想不是编码香蕉组件,而是编码它使用&#34;&#34;属性。 我看到的最接近的例子是PrimeFaces工具提示,但是我无法理解它如何利用&#34; for&#34;属性。 http://www.primefaces.org/showcase/ui/overlay/tooltip/tooltipOptions.xhtml#

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

2 个答案:

答案 0 :(得分:3)

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

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

@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.remove()时,无需进行显式children.add()调用。它会自动照顾孩子的父母。

答案 1 :(得分:0)

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

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

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

@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渲染器不知道如何呈现<h:outputText>(或任何其他)。