2013-03-22 18 views
6

Nel tentativo di implementare l'attributo html5 'autofocus' nella mia applicazione web JSF/Primefaces, sono stato avvisato del fatto che i componenti non passano tutti gli attributi sconosciuti fino al markup finale. Posso capire i ragionamenti per questo, poiché i componenti possono essere combinazioni complesse di markup html e non sarebbe chiaro dove posizionare gli attributi se non sono già ben definiti dal componente.Aggiunta del supporto attributo personalizzato (HTML5) a Primefaces (3.4)

Ma la soluzione migliore per me è avere il supporto per l'autofocus (e qualsiasi altro possibile tipo di attributi che potrei voler supportare nella mia applicazione che primefaces non ha definito).

Ho visto Adding custom attribute (HTML5) support to JSF 2.0 UIInput component ma sembra che si applichi ai componenti JSF di base e non funzioni per i componenti di PrimeFaces.

Come estendere il componente/rendering di Primefaces per supportare questo?

risposta

10

Invece di homegrowing un renderer personalizzato per ogni singolo componente individuale, si potrebbe anche solo creare una singola RenderKit in cui si fornisce una personalizzata ResponseWriter in cui il metodo di startElement() è sovresposta per controllare il nome dell'elemento e/o istanza del componente e poi scrivere supplementari attribuisce di conseguenza.

Ecco un esempio calcio d'inizio del kit HTML5 di rendering:

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; 
    } 

} 

Lo scrittore risposta HTML5:

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; 
    } 

} 

Per farlo funzionare, creare questa HTML5 rendere kit fabbrica:

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(); 
    } 

} 

E registrarlo come segue in faces-config.xml:

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

La libreria di utilità JSF OmniFaces ha anche un tale kit di rendering, il Html5RenderKit (source code here), che dovrebbe teoricamente anche funzionare bene su componenti primefaces. Tuttavia, questa domanda mi ha costretto a prendere di nuovo una seconda occhiata e io ero imbarazzato a vedere che l'argomento component in ResponseWriter#startElement() è null in <p:inputText> (vedi line 74 of InputTextRenderer, che avrebbe dovuto essere writer.startElement("input", inputText) invece). Non sono sicuro se questo sia intenzionale o una svista nel design del renderer di PrimeFaces o meno, ma potresti usare UIComponent#getCurrentComponent() per ottenerlo.


Aggiornamento: questo è stato risolto in OmniFaces 1.5.


Notato dovrebbe essere che il prossimo JSF 2.2 supporterà la definizione di attributi personalizzati nella vista attraverso la nuova passthrough spazio dei nomi o il tag <f:passThroughAttribute>. Vedi anche What's new in JSF 2.2? - HTML5 Pass-through attributes.

Così, in modo da:

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

(si consiglia di utilizzare a invece di p come prefisso di namespace per evitare lo scontro con namespace di default primefaces')

Oppure:

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

Ottimo, sembra molto più bello della soluzione per componente. Sono sicuro che casi d'uso specifici potrebbero rendere gli argomenti in entrambi i modi. Se avrò tempo, proverò a farlo perché sembra che sarebbe molto più rapido di distribuire tutti i componenti. – Rich

+0

UIComponent # getCurrentComponent (FacesContext) sembra essere la firma, quindi nel contesto del metodo startElement che non è disponibile. Ho visto nel commit Omnifaces che hai utilizzato Components.getCurrentCompnent() che non richiede il contesto, sebbene si tratti di una classe OmniFaces che non è disponibile solo in PrimeFaces. A quel punto per me sarebbe più pericoloso dover sostituire anche qualsiasi componente che potrebbe passare null a startElement. Mi suggerisci di inserire OmniFaces o esiste un altro modo? – Rich

+0

Il modulo 'Components # getCurrentComponent()' OmniFaces si limita a delegare a 'UIComponent # getCurrentComponent()'. Non sei chiaro se lo hai testato o meno, ma funziona per me. – BalusC

3

La soluzione che ho trovato era di estendere e ri-implementare i metodi encodeMarkup per i renderer di input. Volevo una soluzione più generale, ma dopo aver esaminato il codice sorgente Primefaces, non ho visto alcun hook generico per i renderer di componenti per aggiungere attributi personalizzati. Il markup è scritto nei metodi encodeMarkup(FacesContext context, InputText inputText) dei renderer. Richiama la gerarchia di classi su renderPassThruAttributes(FacesContext context, UIComponent component, String[] attributes) ma si nutre solo di array String [] finali statici da org.primefaces.util.HTML.

Nel mio caso volevo il supporto per l'attributo 'autofocus' su InputMask, InputText, InputTextarea e Password. Inoltre, l'implementazione è la stessa per ogni componente, quindi passerò attraverso l'implementazione di "autofocus" nel componente InputText, ma dovrebbe essere ovvio come possa essere esteso per supportare più attributi e più componenti.

Per estendere/sovrascrivere un renderer, è necessario disporre della sorgente Primefaces disponibile e trovare il metodo encodeMarkup e copiarne il contenuto. Ecco l'esempio per 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"); 
} 

Estendere/Override del renderer con il proprio (vedi commenti per il codice importante):

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

Configurazione l'override di rendering 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> 

e just-in-caso se non ha avuto il faces-config configurato nel web.xml aggiuntivo:

<context-param> 
     <param-name>javax.faces.CONFIG_FILES</param-name> 
     <param-value> 
      /WEB-INF/faces-config.xml, /faces-config.xml 
     </param-value> 
    </context-param> 

quindi di utilizzare questo in qualche markup:

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

Nota: JSF non è felice con gli attributi che non hanno valori. Mentre l'autofocus in HTML5 non utilizza un valore, JSF genera un errore se non ne viene fornito uno, quindi assicurati di definire un valore di throw-away quando aggiungi tali attributi.

+0

Ti sei dato una risposta entro 2 minuti? Bel lavoro! –

+2

SO ti offre la possibilità di fare una domanda sullo stile di domande e risposte in cui rispondi dall'inizio. Non avevo trovato una risposta specifica a questo quando stavo cercando di risolverlo, quindi ho pensato di contribuire alla comunità. – Rich

+0

@Rich sembra promettente. Lo proverò nel fine settimana. –

2

JSF 2.2 fornisce anche funzionalità di pass through progettato per HTML5 e oltre, quindi quando PrimeF aces supporta ufficialmente JSF 2.2, è possibile passare qualsiasi attributo dai componenti agli elementi html.

+0

Ottimo, terrò d'occhio per questo. – Rich

Problemi correlati