views:

64

answers:

3

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  A: 

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:

  • 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!

The Alchemist
I was thinking about creating a custom JSF component, but the amount of work with JSF internals cannot be really justified by the simple job the component should do. It's the same <ui:include/> but a dynamic one. So I decided to create a temporary file and use <ui:include/> to include it into the facelet.
theorm
I agree that a creation of a component is the best solution (maybe not by extending the `MediaOutput`). See my answer for that.
romaintaz
A: 

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}"/>
theorm
+1  A: 

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&lt;/namespace&gt;
    <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"&gt;
    ...
    <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.

romaintaz
This is definitely the best solution for simple cases like including an image or a link. However if we need to support virtually any possible facelet component, it's better to offload these tasks to JSF engine. In my case I was avoiding duplicating code which was already in a4j:mediaOutput because the image that replaces the token is generated dynamically on the fly.
theorm