A common question which keeps returning is "How to display HTML in <h:messages>?". One would logically think to add an escape="false" attribute to the component, like as you would do in a <h:outputText>. Unfortunately, this is not possible in the standard JSF implementation. The component and the renderer does officially not support this attribute. The <h:outputText> and <f:selectItem> are as far the only which supports the escape attribute. Your best bet is to homegrow a renderer which handles this.
First some background information: JSF by default uses ResponseWriter#writeText() to write the tag body, which escapes HTML by default. We'd like to let it use ResponseWriter#write() instead like as with <h:outputText escape="false" />.
So, we'd like to extend the MessageRenderer of the standard JSF implementation and override the encodeEnd() method accordingly. But since the MessageRenderer#encodeEnd() contains pretty a lot of code (~180 lines) which we prefer not to copypaste to just change one or two lines after all, it's a better idea to replace the ResponseWriter with a custom implementation with help of ResponseWriterWrapper wherein the writeText() method is been overriden to handle the escaping.
So, I ended up with this:
package com.example; import java.io.IOException; import javax.faces.component.UIComponent; import javax.faces.context.FacesContext; import javax.faces.context.ResponseWriter; import javax.faces.context.ResponseWriterWrapper; import javax.faces.render.FacesRenderer; import com.sun.faces.renderkit.html_basic.MessagesRenderer; @FacesRenderer(componentFamily="javax.faces.Messages", rendererType="javax.faces.Messages") public class EscapableMessagesRenderer extends MessagesRenderer { @Override public void encodeEnd(FacesContext context, UIComponent component) throws IOException { final ResponseWriter originalResponseWriter = context.getResponseWriter(); context.setResponseWriter(new ResponseWriterWrapper() { @Override public ResponseWriter getWrapped() { return originalResponseWriter; } @Override public void writeText(Object text, UIComponent component, String property) throws IOException { String string = String.valueOf(text); String escape = (String) component.getAttributes().get("escape"); if (escape != null && !Boolean.valueOf(escape)) { super.write(string); } else { super.writeText(string, component, property); } } }); super.encodeEnd(context, component); context.setResponseWriter(originalResponseWriter); // Restore original writer. } }
But, in spite of the @FacesRenderer annotation, it get overriden by the default MessagesRenderer implementation. Since I suspect a bug here, I reported issue 1748. To get it to work anyway, we have to fall back to the faces-config.xml:
<render-kit> <renderer> <component-family>javax.faces.Messages</component-family> <renderer-type>javax.faces.Messages</renderer-type> <renderer-class>com.example.EscapableMessagesRenderer</renderer-class> </renderer> </render-kit>
And it works! :) Use it as follows:
<h:messages escape="false" />
To do the same for <h:message>, just copy the above and replace anywhere "Messages" appears in the code (component family, renderer type and class names) by "Message".
The above is written with JSF 2.0 in mind, but it should also just work in JSF 1.2, you only have to remove the @FacesRenderer annotation. It will not work in JSF 1.1 or older since there's no ResponseWriter#writeText() method which takes an UIComponent as argument.
Update: a ready to use solution is available in OmniFaces as <o:messages>
.
19 comments:
Indeed. I also stumbled upon this question. Thx for this solution!
Hello!
Can i translate some of your posts into Russian language? Sure with all needed copyrights. Please email me about this:
ahriman@tpu.ru
Doing this is probably unwise because of HTML/code injection issues - unless you exercise a strong degree of control over your application and its dependencies (including all 3rd party libraries and the JEE platform implementation). You don't know what data will be added to the message queue.
If you replaced the h:message (no S) renderer instead, you'd be on safer ground because each component targets something you've specified in the view.
I'm not saying "don't do this", but I think the post is incomplete without a follow-up describing all the bad things you could do with it.
Right, as long as you don't redisplay user-controlled input in messages, this is fine.
good article tnx...
I tried this, too, but the JSP compiler then fails saying that the "escape" attribute is not valid for the h:messages tag. I suppose I need to hack the TLD, too? That seems a bit kludgy.
thanks for saving me a lot of time with this neat solution.
what problems can cuase this i read mcdowells post and i got confused if its a good solution to apply or not, BalusC, can you please explain what might cause in my app if i use this class, cause i tried it for H:message, as u said i replaced it and it works perfectly but i want to know waht can happen..what messages can not be redisplayed???... thx
can i do the same for the rich:message? which package is it??
Just what I needed... Thanks man!
This is not working for me! I have added the class, and changed faces-config.xml to look as follows:
<?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">
<render-kit>
<renderer>
<component-family>javax.faces.Messages</component-family>
<renderer-type>javax.faces.Messages</renderer-type>
<renderer-class>microworks.voyagernetz.bazaar.EscapableMessagesRenderer</renderer-class>
</renderer>
</render-kit>
</faces-config>
The contents of the <h:messages escape="false"> tag is still escaped.
I use Glassfish 3.1.2. Any ideas?
Myfaces 2.1, HtmlMessageRendererBase, calls writer.writeText(detail, null);
so you need to override writeText(text, property)
not writeText(text, component, property)
Regards,
Neil
@Neil: indeed, that depends on the JSF implementation you're currently using. The article also explicitly mentions "the standard JSF implementation", referring to Mojarra (com.sun.faces.*).
Could somebody tell me how to import com.sun.faces.renderkit.html_basic.MessagesRenderer? I use Websphere Application Server.
Hi, I know this is an old article, but I am still using JSF 1.2 (can't upgrade to 2.0 just yet). I followed the instructions to the letter, also removing the annotation, and I get the following server error :
Unable to locate tag attribute info for tag attribute escape.
Is there something else I need to set up?
Thanks in advance
Excellent Job. What if I want the message text, icon to appear from right to left. What changes are needed on the code? Thanks a lot.
Thank you, you save my day on this legacy issue with JSF, Why people still using JSF
Thanks, I worked without problems with the following JAR:
jsf-api-1.2_12.jar
jsf_facelets-1.1.15.B1.jar
jsf_impl-1.2_12.jar
My faces-config:
com.sun.facelets.FaceletViewHandler
es
es
cl.gov.subdere.seam.CustomPhaseListener
javax.faces.Messages
javax.faces.Messages
cl.gov.subdere.util.EscapableMessagesRenderer
My File Path the class in EJB:
project\trunk\ejb\src\main\java\cl\gov\subdere\util\EscapableMessagesRenderer.java
My hero!!! Worked like a charm!!!! Thanks a lot!
Post a Comment