Reputation: 66
In current project I need to create a panel that will contain an HTML content created by the user elsewhere in the application. This content can be easily inserted like this:
<h:outputText value="#{myBean.dynamicHTMLContent}" escape="false"/>
An example content:
<p>User text</p>
Now we need to give the user more freedom and allow him to use tokens in the HTML code that will be resolved by the application later:
<p>User text</p><p>User image: {niceImage}</p>
The application parses user content in myBean.dynamicHTMLContent and replaces {niceImage(param)} with
<a4j:mediaOutput element="img" createContent="{myBean.generateNiceImage}"/>
This is already a facelet snippet and cannot be evaluated and rendered in h:outputText.
I was looking for a good way to include this kind of dynamic content within a facelet at the stage when EL expressions are not yet evaluated. Something like
<ui:include src="src"/>
but for dynamic components would be the best solution.
Any ideas?
Upvotes: 1
Views: 3813
Reputation: 5155
You can use includeFacelet(UIComponent, URL)
also for including dynamically generated facelets. The trick is using the data
URL scheme and a custom URLStreamHandler
:
String encoded = Base64.encodeBase64String(myDynamicFacelet.getBytes());
context.includeFacelet(uiComponent, new URL(null, "data://text/plain;base64," + encoded, new DataStreamHandler()));
If you have a generic handler for data://
URLs, it's the best option to use. I needed a handler only for this specific use case, so it's pretty limited:
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import org.apache.commons.codec.binary.Base64;
public class DataStreamHandler extends URLStreamHandler {
private static class DataURLConnection extends URLConnection {
@Override
public InputStream getInputStream() throws IOException {
return new ByteArrayInputStream(this.content.getBytes());
}
private static String PREFIX = "data://text/plain;base64,";
private static int PREFIX_LEN = PREFIX.length();
protected DataURLConnection(URL url) {
super(url);
this.url = url;
String encoded = this.url.toString().substring(PREFIX_LEN);
this.content = new String(Base64.decodeBase64(encoded));
}
@Override
public void connect() throws IOException {
// Do nothing
}
private URL url;
private String content;
}
@Override
protected URLConnection openConnection(URL url) throws IOException {
return new DataURLConnection(url);
}
}
Upvotes: 1
Reputation: 81667
I agree with user423943 in the idea of creating a component for that. However, I would extend the <h:outputText>
instead. In your case, you will not have a lot of work to do. First, create a my.taglib.xml
file:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE facelet-taglib PUBLIC "-//Sun Microsystems, Inc.//DTD Facelet Taglib 1.0//EN" "facelet-taglib_1_0.dtd">
<facelet-taglib>
<namespace>http://my.components/jsf</namespace>
<tag>
<tag-name>myComponent</tag-name>
<component>
<component-type>my.component.myComponent</component-type>
<renderer-type>my.renderkit.myComponent</renderer-type>
</component>
</tag>
</facelet-taglib>
This file just need to be present in the classpath of your application and it will be loaded automatically by Facelets (because it ends with .taglib.xml
).
Then, in the faces-config.xml
defines the Java classes for this component:
<component>
<component-type>my.component.myComponent</component-type>
<component-class>my.package.component.MyHtmlComponent</component-class>
</component>
<render-kit>
<render-kit-id>HTML_BASIC</render-kit-id>
<renderer>
<component-family>javax.faces.Output</component-family>
<renderer-type>my.renderkit.myComponent</renderer-type>
<renderer-class>my.package.component.MyHtmlComponentRenderer</renderer-class>
</renderer>
Then, you will have to create two classes:
my.package.component.MyHtmlComponent
that will extend javax.faces.component.html.HtmlInputText
and do nothing more.my.package.component.MyHtmlComponentRenderer
that will extend the com.sun.faces.renderkit.html_basic.TextRenderer
class.Your renderer class will do all the job, by generating the HTML code for the value of your component, exactly as the <h:outputText>
does. You can have a look at HtmlBasicRenderer.encodeEnd(FacesContext, UIComponent)
and TextRenderer.getEndTextToRender(FacesContext, UIComponent, String)
methods, that are involved in this part.
Of course, when you are facing a {niceImage}
code in your text, you simply need to generate a HTML img
tag. For that, you can use the adequate methods of the ResponseWriter
to build an HTML tag and attributes:
writer.startElement("img", component);
writer.writeAttribute("src", urlToImage);
writer.endElement("img");
Once everything is created, you have to use your new component in your JSF page:
<html xmlns:my="http://my.components/jsf">
...
<my:myComponent value="#{myBean.dynamicHTMLContent}" escape="false"/>
...
Two links that can help you in addition to the ones provided by user423943:
http://www.jsftutorials.net/helpDesk/standardRenderKit_component-class_renderer-slass.html
http://www.jsftutorials.net/helpDesk/standardRenderKit_component-type_renderer-type.html
You will find, for all HTML JSF components their types and classes.
Upvotes: 3
Reputation: 66
Eventually I took the easy way by replacing all custom (curly braces) tokens in the user HTML with corresponding JSF elements and generating a temporary ui:composition
facelet file:
public String getUserHtmlContentPath() {
File temp = File.createTempFile("userContent", ".tmp");
temp.deleteOnExit();
FileWriter fw = new FileWriter(temp);
fw.write(getUserHtmlContentComposition());
fw.close();
return "file://" + temp.getAbsolutePath();
}
and in the parent facelet:
<ui:include src="#{myBean.userHtmlContentPath}"/>
Upvotes: 0
Reputation: 3425
What makes this complex, I think, is that #{myBean.dynamicHTMLContent}
isn't quite HTML content but JSF content. I think the most flexible solution would be to write your own JSF component. Perhaps someone will correct me, but I don't think there's a way to replace text like {niceImage}
JSF code.
There's some articles about this:
I'm no JSF expert, but you could probably:
org.ajax4jsf.MediaOutput
niceImage
with references to #{myBean.generateNiceImage}
or whateverorg.ajax4jsf.MediaOutput
Hope that helps!
Upvotes: 1