开发者

Ways to include a dynamically generated facelet

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: {niceIma开发者_C百科ge}</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?


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.


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 niceImage with references to #{myBean.generateNiceImage} or whatever
  • forward the actual work to the superclass, org.ajax4jsf.MediaOutput

Hope that helps!


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


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}"/>
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜