Ways to include a dynamically generated facelet
Asked Answered
O

4

1

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?

Osrock answered 10/9, 2010 at 7:4 Comment(0)
O
0

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}"/>
Osrock answered 14/9, 2010 at 6:40 Comment(0)
W
3

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.

Watts answered 14/9, 2010 at 7:23 Comment(1)
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.Osrock
T
1

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!

Tied answered 10/9, 2010 at 13:47 Comment(2)
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.Osrock
I agree that a creation of a component is the best solution (maybe not by extending the MediaOutput). See my answer for that.Watts
M
1

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);
  }
}
Majoriemajority answered 1/8, 2013 at 6:23 Comment(0)
O
0

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}"/>
Osrock answered 14/9, 2010 at 6:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.