Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Custom JSF component renderer: how to render HTML around another element

I have 2 elements, banana and an outputText, where banana is a custom JSF component and in banana renderer, I would like to generate HTML enclosing the specified element.

xhtml:

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

bananaRenderer(encloses the target element in anchor links):

@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");
}

What I want to achieve:

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

As you can see I want to encode not on the banana component but on the element it targets using "for" attribute. The closest example I saw is PrimeFaces tooltip, but I can't quite grasp how it utilizes the "for" attribute. http://www.primefaces.org/showcase/ui/overlay/tooltip/tooltipOptions.xhtml#

If someone can point me to the right direction, it would really be appreciated. Thank you.

like image 491
Jacobski Avatar asked Jul 05 '16 07:07

Jacobski


1 Answers

You can manipulate the component tree during PreRenderViewEvent. During that moment, all components are guaranteed to be present in the tree, and it's guaranteed safe to manipulate the component tree by adding/moving/removing components in the tree. You can find other components in the tree by UIComponent#findComponent(). You can traverse the component tree using UIComponent#getParent() and UIComponent#getChildren(). The getChildren() returns a mutable list which is guaranteed safe to manipulate during PreRenderViewEvent.

Given that you'd like to wrap a given component with a hyperlink, and there exist already a JSF hyperlink component, the <h:outputLink>, it's easier to extend and reuse it to keep the code simple and DRY. Here's a basic kickoff example:

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

Do note that explicit children.remove() call is not necessary when you already perform a children.add(). It will automatically take care about the child's parent.

like image 85
BalusC Avatar answered Sep 21 '22 00:09

BalusC