2016-07-05 25 views
4

Ich habe 2 Elemente, Banane und ein outputText, wo Banane eine benutzerdefinierte JSF-Komponente ist und in Banane Renderer möchte ich HTML um das angegebene Element umschließen generieren.Benutzerdefinierte JSF Component Renderer: wie HTML um ein anderes Element rendern

xhtml:

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

bananaRenderer (umschließt das Zielelement in Anker-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"); 
} 

Was ich erreichen möchte:

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

Wie Sie sehen Ich möchte nicht mit der Bananenkomponente, sondern mit dem Attribut "für" auf dem Zielelement zu kodieren. Das nächste Beispiel, das ich gesehen habe, ist PrimeFaces Tooltip, aber ich kann nicht ganz verstehen, wie es das Attribut "for" verwendet. http://www.primefaces.org/showcase/ui/overlay/tooltip/tooltipOptions.xhtml#

Wenn mir jemand in die richtige Richtung zeigen kann, würde es wirklich geschätzt werden. Vielen Dank.

Antwort

3

Sie können den Komponentenbaum während PreRenderViewEvent manipulieren. In diesem Moment sind alle Komponenten garantiert im Baum vorhanden, und es ist garantiert sicher, den Komponentenbaum durch Hinzufügen/Verschieben/Entfernen von Komponenten in dem Baum zu manipulieren. Sie können andere Komponenten im Baum durch UIComponent#findComponent() finden. Sie können den Komponentenbaum mit UIComponent#getParent() und UIComponent#getChildren() durchqueren. Die getChildren() gibt eine veränderbare Liste zurück, die während PreRenderViewEvent garantiert sicher zu manipulieren ist.

Angesichts der Tatsache, dass Sie eine bestimmte Komponente mit einem Hyperlink umschließen möchten und es bereits eine JSF-Hyperlink-Komponente gibt, die <h:outputLink>, ist es einfacher zu erweitern und wieder zu verwenden, um den Code einfach und trocken zu halten. Hier ist ein grundlegendes Kickoff Beispiel:

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

Sie zu beachten, dass expliziter children.remove() Anruf nicht erforderlich ist, wenn Sie bereits ein children.add() ausführen. Es kümmert sich automatisch um die Eltern des Kindes.

+0

Ihre Lösung funktioniert gut, solange der Apple zuerst kommt. Trotzdem, danke. – Jacob

+0

Ich habe den Code in Antwort auf den anderen Fall auch behoben. Trotzdem, danke. – BalusC

0

Wenn ich das Problem richtig verstehe, möchten Sie 2 separate JSF-Komponenten definieren, die gemeinsamen HTML-Code erzeugen. Eine der möglichen Lösungen (wie @BalusC dargestellt) ist Komponentenbaum Manipulation, aber das ist fast das gleiche wie in xhtml auf eine natürlichere Weise zu definieren:

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

Wenn für einige Bedürfnisse Sie wollen (re) machen Komponenten referenziert über for -Attribut, ich schlage vor, JavaScript für DOM-Manipulationen auf der Client-Seite zu verwenden. Sie müssen nur script Tag in Ihrem banana Renderer mit entsprechenden JavaScript-Code definieren:

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

Diese fast ist wie Primefaces Tooltip gebaut.

Der Vorteil ist, dass banana Renderer nicht bewusst ist, wie <h:outputText> (oder andere) gerendert werden.

+0

Aus der Frage: "Wie Sie sehen können, möchte ich nicht auf der Bananenkomponente, sondern auf dem Element, das es mit dem Attribut" for "anspricht, kodieren." _ So ist Teil 1 Ihrer Antwort nicht "richtig". Und wenn Sie sich den Code in der Antwort von @BalusC ansehen, werden Sie feststellen, dass er weder weiß, wie der outputText gerendert wird. Der zweite Teil Ihrer zweiten Antwort ist also keine "Verbesserung" – Kukeltje