2013-03-22 6 views
6

Beim Versuch, das einfache HTML5-Attribut 'Autofokus' in meiner JSF/Primefaces-Webanwendung zu implementieren, wurde ich darauf aufmerksam gemacht, dass die Komponenten nicht alle unbekannten Attribute übergeben auf die endgültige Markup. Ich kann Gründe dafür verstehen, da Komponenten komplexe Kombinationen von HTML-Markup sein können, und es wäre nicht klar, wo die Attribute platziert werden sollen, wenn sie nicht bereits von der Komponente gut definiert sind.Hinzufügen von benutzerdefiniertem Attribut (HTML5) zu Primefaces (3.4)

Aber die beste Lösung für mich ist die Unterstützung für den Autofokus (und alle anderen möglichen Arten von Attributen, die ich in meiner Anwendung unterstützen möchte, die von primefaces nicht definiert wurde).

Ich habe Adding custom attribute (HTML5) support to JSF 2.0 UIInput component gesehen, aber das scheint für die grundlegenden JSF-Komponenten zu gelten und funktioniert nicht für PrimeFaces-Komponenten.

Wie kann ich die Komponente/Rendering von Primefaces erweitern, um dies zu unterstützen?

Antwort

10

Statt einen benutzerdefinierten Renderer für jede einzelne einzelne Komponente von Homegrow, können Sie auch erstellen nur eine einzige RenderKit wobei Sie eine benutzerdefinierte bieten ResponseWriter wobei die startElement() Methode überschriebenen ist der Name des Elements und/oder Komponenteninstanz zu prüfen und dann schreiben zusätzliche Attribute entsprechend.

Hier ist ein Kick-off Beispiel der HTML5 machen Kit:

public class Html5RenderKit extends RenderKitWrapper { 

    private RenderKit wrapped; 

    public Html5RenderKit(RenderKit wrapped) { 
     this.wrapped = wrapped; 
    } 

    @Override 
    public ResponseWriter createResponseWriter(Writer writer, String contentTypeList, String characterEncoding) { 
     return new Html5ResponseWriter(super.createResponseWriter(writer, contentTypeList, characterEncoding)); 
    } 

    @Override 
    public RenderKit getWrapped() { 
     return wrapped; 
    } 

} 

Der Autor HTML5 Antwort:

public class Html5ResponseWriter extends ResponseWriterWrapper { 

    private static final String[] HTML5_INPUT_ATTRIBUTES = { "autofocus" }; 

    private ResponseWriter wrapped; 

    public Html5ResponseWriter(ResponseWriter wrapped) { 
     this.wrapped = wrapped; 
    } 

    @Override 
    public ResponseWriter cloneWithWriter(Writer writer) { 
     return new Html5ResponseWriter(super.cloneWithWriter(writer)); 
    } 

    @Override 
    public void startElement(String name, UIComponent component) throws IOException { 
     super.startElement(name, component); 

     if ("input".equals(name)) { 
      for (String attributeName : HTML5_INPUT_ATTRIBUTES) { 
       String attributeValue = component.getAttributes().get(attributeName); 

       if (attributeValue != null) { 
        super.writeAttribute(attributeName, attributeValue, null); 
       } 
      } 
     } 
    } 

    @Override 
    public ResponseWriter getWrapped() { 
     return wrapped; 
    } 

} 

Um es zu laufen zu bekommen, erstellen diese Ausrüster HTML5 machen:

public class Html5RenderKitFactory extends RenderKitFactory { 

    private RenderKitFactory wrapped; 

    public Html5RenderKitFactory(RenderKitFactory wrapped) { 
     this.wrapped = wrapped; 
    } 

    @Override 
    public void addRenderKit(String renderKitId, RenderKit renderKit) { 
     wrapped.addRenderKit(renderKitId, renderKit); 
    } 

    @Override 
    public RenderKit getRenderKit(FacesContext context, String renderKitId) { 
     RenderKit renderKit = wrapped.getRenderKit(context, renderKitId); 
     return (HTML_BASIC_RENDER_KIT.equals(renderKitId)) ? new Html5RenderKit(renderKit) : renderKit; 
    } 

    @Override 
    public Iterator<String> getRenderKitIds() { 
     return wrapped.getRenderKitIds(); 
    } 

} 

Und registrieren Sie es wie folgt in faces-config.xml:

<factory> 
    <render-kit-factory>com.example.Html5RenderKitFactory</render-kit-factory> 
</factory> 

Das JSF-Utility-Bibliothek OmniFaces hat auch einen solchen Kit machen, die Html5RenderKit (source code here), die theoretisch auch sollte auf PrimeFaces Komponenten funktionieren. Diese Frage zwang mich jedoch, noch einmal nachzusehen, und es war mir peinlich, dass das component Argument in ResponseWriter#startElement()null in <p:inputText> ist (siehe line 74 of InputTextRenderer, stattdessen sollte es writer.startElement("input", inputText) gewesen sein). Ich bin mir nicht sicher, ob dies Absicht oder ein Versehen im Design des PrimeFaces-Renderers ist oder nicht, aber Sie könnten stattdessen UIComponent#getCurrentComponent() verwenden, um es zu bekommen.


aktualisieren: dies in OmniFaces 1,5 fixiert ist.


soll beachtet werden, dass die kommende JSF 2.2 wird individuelle Unterstützung definieren, die in der Ansicht Attribute über den neuen passthrough Namespace oder den <f:passThroughAttribute>-Tag. Siehe auch What's new in JSF 2.2? - HTML5 Pass-through attributes.

So so:

<html ... xmlns:p="http://java.sun.com/jsf/passthrough"> 
... 
<h:inputText ... p:autofocus="true" /> 

(Sie a statt p als Namespacepräfix verwenden möchten Standard-Namespace Zusammenstoß mit PrimeFaces zu vermeiden)

Oder:

<h:inputText ...> 
    <f:passThroughAttribute name="autofocus" value="true" /> 
</h:inputText> 
+0

Großartig, das sieht viel schöner aus als die pro-Komponenten-Lösung. Ich bin sicher, dass bestimmte Anwendungsfälle Argumente liefern könnten. Wenn ich Zeit bekomme, probiere ich das aus, da es viel schneller ist, als alle Komponenten zu entwickeln. – Rich

+0

Die UIComponent # getCurrentComponent (FacesContext) scheint die Signatur zu sein, also im Kontext der startElement-Methode, die nicht verfügbar ist. Ich habe in Ihrem Omnifaces-Commit gesehen, dass Sie Components.getCurrentCompnent() verwendet haben, das den Kontext nicht benötigt, obwohl das eine OmniFaces-Klasse ist, die nicht nur in PrimeFaces verfügbar ist. An diesem Punkt wäre es gefährlicher für mich, auch jede Komponente zu überschreiben, die Null an startElement übergeben könnte. Schlägst du vor, ich ziehe OmniFaces ein oder gibt es einen anderen Weg? – Rich

+0

Das Formular 'Components # getCurrentComponent()' OmniFaces delegiert nur an 'UIComponent # getCurrentComponent()'. Du bist nicht klar, ob du es getestet hast oder nicht, aber es funktioniert für mich. – BalusC

3

Die Lösung, die ich fand, war, die encodeMarkup-Methoden für die Eingabe-Renderer zu erweitern und neu zu implementieren. Ich wollte eine allgemeinere Lösung, aber nachdem ich den Primefaces-Quellcode angeschaut hatte, sah ich keine generischen Hooks, damit die Komponenten-Renderer benutzerdefinierte Attribute hinzufügen konnten. Das Markup ist in den encodeMarkup(FacesContext context, InputText inputText) Methoden der Renderer ausgeschrieben. Es ruft die Klassenhierarchie zu renderPassThruAttributes(FacesContext context, UIComponent component, String[] attributes) auf aber es speist nur statische endgültige String [] - Arrays von org.primefaces.util.HTML.

In meinem Fall wollte ich Unterstützung für das 'Autofokus' Attribut auf InputMask, InputText, InputTextarea und Passwort-Komponenten. Darüber hinaus ist die Implementierung für jede Komponente die gleiche, daher werde ich den Autofokus der InputText-Komponente implementieren, aber es sollte offensichtlich sein, wie er erweitert werden kann, um mehr Attribute und mehr Komponenten zu unterstützen.

Um einen Renderer zu erweitern/zu überschreiben, müssen Sie die Primefaces-Quelle verfügbar haben und die encodeMarkup-Methode suchen und deren Inhalt kopieren. Hier ist das Beispiel für InputTextRenderer:

protected void encodeMarkup(FacesContext context, InputText inputText) throws IOException { 
    ResponseWriter writer = context.getResponseWriter(); 
    String clientId = inputText.getClientId(context); 

    writer.startElement("input", null); 
    writer.writeAttribute("id", clientId, null); 
    writer.writeAttribute("name", clientId, null); 
    writer.writeAttribute("type", inputText.getType(), null); 

    String valueToRender = ComponentUtils.getValueToRender(context, inputText); 
    if(valueToRender != null) { 
     writer.writeAttribute("value", valueToRender , null); 
    } 

    renderPassThruAttributes(context, inputText, HTML.INPUT_TEXT_ATTRS); 

    if(inputText.isDisabled()) writer.writeAttribute("disabled", "disabled", null); 
    if(inputText.isReadonly()) writer.writeAttribute("readonly", "readonly", null); 
    if(inputText.getStyle() != null) writer.writeAttribute("style", inputText.getStyle(), null); 

    writer.writeAttribute("class", createStyleClass(inputText), "styleClass"); 

    writer.endElement("input"); 
} 

Erweiterung/Aufschalten den Renderer mit Ihrem eigenen (siehe Kommentare zu wichtigen Code):

public class HTML5InputTextRenderer extends InputTextRenderer { 

    Logger log = Logger.getLogger(HTML5InputTextRenderer.class); 

    //Define your attributes to support here 
    private static final String[] html5_attributes = { "autofocus" }; 

    protected void encodeMarkup(FacesContext context, InputText inputText) throws IOException { 
    ResponseWriter writer = context.getResponseWriter(); 
    String clientId = inputText.getClientId(context); 

    writer.startElement("input", null); 
    writer.writeAttribute("id", clientId, null); 
    writer.writeAttribute("name", clientId, null); 
    writer.writeAttribute("type", inputText.getType(), null); 

    String valueToRender = ComponentUtils.getValueToRender(context, inputText); 
    if (valueToRender != null) { 
     writer.writeAttribute("value", valueToRender, null); 
    } 

    renderPassThruAttributes(context, inputText, HTML.INPUT_TEXT_ATTRS); 

    //Make an extra call to renderPassThruAttributes with your own attributes array 
    renderPassThruAttributes(context, inputText, html5_attributes); 

    if (inputText.isDisabled()) 
     writer.writeAttribute("disabled", "disabled", null); 
    if (inputText.isReadonly()) 
     writer.writeAttribute("readonly", "readonly", null); 
    if (inputText.getStyle() != null) 
     writer.writeAttribute("style", inputText.getStyle(), null); 

    writer.writeAttribute("class", createStyleClass(inputText), "styleClass"); 

    writer.endElement("input"); 
    } 
} 

Konfigurieren der Rendering Überschreibung in faces-config.xml

<?xml version='1.0' encoding='UTF-8'?> 
<faces-config xmlns="http://java.sun.com/xml/ns/javaee" 
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
     xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd" 
     version="2.0"> 

    <!-- snip... --> 

    <render-kit> 
     <renderer> 
      <component-family>org.primefaces.component</component-family> 
      <renderer-type>org.primefaces.component.InputTextRenderer</renderer-type> 
      <renderer-class>com.mycompany.HTML5InputTextRenderer</renderer-class> 
     </renderer> 
    </render-kit> 

    <!-- snip... --> 
</faces-config> 

und Just-in-Case, wenn Sie die faces-config in Ihrer web.xml nicht konfiguriert haben. Add:

Dann ist diese in einem gewissen Markup zu verwenden:

<p:inputText id="activateUserName" value="${someBean.userName}" 
    autofocus="on"> 
</p:inputText> 

Hinweis: JSF mit Attributen nicht glücklich ist, die Werte nicht haben. Während der Autofokus in HTML5 keinen Wert verwendet, gibt JSF einen Fehler aus, wenn einer nicht angegeben wird. Stellen Sie daher sicher, dass Sie einen Wegwerfwert definieren, wenn Sie solche Attribute hinzufügen.

+0

Haben Sie innerhalb von 2 Minuten eine Antwort erhalten? Gut gemacht! –

+2

SO gibt Ihnen die Möglichkeit, eine Fragen-und-Antwort-Frage zu stellen, die Sie von Anfang an beantworten. Ich hatte keine spezifische Antwort darauf gefunden, als ich versuchte, es zu lösen, also dachte ich, ich würde es in die Gemeinschaft einbringen. – Rich

+0

@Rich es sieht vielversprechend aus. Ich werde es am Wochenende testen. –

2

JSF 2.2 bietet auch Pass-Through-Attribute-Funktion für HTML5 und darüber hinaus, also wenn PrimeF Asse unterstützt offiziell JSF 2.2, Sie können jedes Attribut von Komponenten an HTML-Elemente übergeben.

+0

Großartig, ich behalte das im Auge. – Rich

Verwandte Themen