问题
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?
回答1:
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.MyHtmlComponentthat will extendjavax.faces.component.html.HtmlInputTextand do nothing more.my.package.component.MyHtmlComponentRendererthat will extend thecom.sun.faces.renderkit.html_basic.TextRendererclass.
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.
回答2:
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:
- http://www.theserverside.com/news/1364786/Building-Custom-JSF-UI-Components
- http://www.ibm.com/developerworks/library/j-jsf1/
- http://www.theserverside.com/news/1364786/Building-Custom-JSF-UI-Components
- http://download.oracle.com/javaee/5/tutorial/doc/bnavh.html
- http://www.ibm.com/developerworks/java/library/j-jsf4/
- https://matthiaswessendorf.wordpress.com/2008/02/29/custom-jsf-components-with-facelets/
I'm no JSF expert, but you could probably:
- extend
org.ajax4jsf.MediaOutput - parse out all the text in curly braces
- replace things like
niceImagewith references to#{myBean.generateNiceImage}or whatever - forward the actual work to the superclass,
org.ajax4jsf.MediaOutput
Hope that helps!
回答3:
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);
}
}
回答4:
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}"/>
来源:https://stackoverflow.com/questions/3682789/ways-to-include-a-dynamically-generated-facelet