Hello,

I am trying to build a container component that encapsulates the complete screen:

<html>
<head>
 ...include scripts and CSS files
</head>
<body>
 ...JSF form
</body>
</html>

So I have a UIScreen component and a ScreenRenderer component which
creates the basic layout.  OK, that's not hard.

What is *hard* is that I want to be able to include JS and CSS files within the
<head> tag based on the set of components in the whole screen.  For example:

<html>
<head>
 <script language="javascript" type="text/javascript" 
src="myComponent.js"></script>
 <script language="javascript" type="text/javascript" 
src="otherComponent.js"></script>
 <link rel="stylesheet" type="text/css" href="myComponent.css" media="screen" />
</head>
<body>
 ...JSF form that contains <myComponent .../> and <otherComponent .../>
</body>
</html>

So...  ScreenRenderer requires the creation of the complete JSF component
tree before tit can render the set of script and styles tags in the <head> tag.
Normally, renderers allow their children to render themselves, but in my
scenario I cannot allow the children to render until the complete tree is built.

I have investigated the source code for UIComponentTag and UIComponentBase
and have discovered a fairly complex relationship between the doStartTag, doEndTag,
getRendersChildren, encodeBegin, encodeChildren, and encodeEnd.  Meaning:
I have researched just enough to be dangerous.  ;-)

Here is a concrete example:

<f:view>
<c:screen id='RouterDash'>
<h:form id='x'>
 <c:division id='header'>
   <f:verbatim><h1>RouterDash</h1></f:verbatim>
 </c:division>
 <c:division id='container'>
   <f:verbatim><h1>BODY</h1></f:verbatim>
 </c:division>
 <c:division id='footer'>
   <f:verbatim><h1>footer</h1></f:verbatim>
 </c:division>
</h:form>
</c:screen>
</f:view>

The behavior I am seeing is that the UIScreen component renders (using the
encodeChildren and encodeEnd methods) and the UIForm component renders,
but none of the UIDivision components (whose renderer generates <div> tags)
get rendered.

As a work-around I created a FormRenderer that tells its subcomponents to
also render.  So this is working, but it does not appear very elegant to me.
Has anyone else tried to do something like this? If so, how did you solve this
problem?

Attached are the relevant renderer classes for your inspection.

Thanks,
Bryan

package org.stillsecure.cobia.web.comps.renderers;

import org.stillsecure.cobia.web.comps.ScriptedComponent;
import org.stillsecure.cobia.web.comps.StyledComponent;

import java.io.IOException;
import java.util.List;
import java.util.ResourceBundle;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;

public class ScreenRenderer extends ChildRendererBase
{
    @Override    @SuppressWarnings("unchecked")
    public void encodeChildren(FacesContext context, UIComponent component) 
throws IOException
    {
        ResourceBundle res = ResourceBundle.getBundle("web", 
context.getViewRoot().getLocale());
        ResponseWriter out = context.getResponseWriter();
        String titleKey = String.format("%s_Title", component.getId());
        out.write(String.format(HEAD_TEXT,
                res.getString(titleKey),
                createScriptTags(component),
                createStyleTags(component)));
        
        // Let the superclass handle the rest of the encoding
        super.encodeChildren(context, component);
    }

    @SuppressWarnings("unchecked")
    private String createScriptTags(UIComponent component) {
        StringBuilder buffer = new StringBuilder();
        if ( component instanceof ScriptedComponent )
        {
            buffer.append("  ");
            buffer.append(((ScriptedComponent)component).getScriptTag());
            buffer.append('\n');
        }
        List<UIComponent> children = component.getChildren();
        for ( UIComponent child : children )
        {
            buffer.append(createScriptTags(child));
        }
        return buffer.toString();
    }

    @SuppressWarnings("unchecked")
    private String createStyleTags(UIComponent component) {
        StringBuilder buffer = new StringBuilder();
        if ( component instanceof StyledComponent )
        {
            buffer.append("  ");
            buffer.append(((StyledComponent)component).getStyleTag());
            buffer.append('\n');
        }
        List<UIComponent> children = component.getChildren();
        for ( UIComponent child : children )
        {
            buffer.append(createStyleTags(child));
        }
        return buffer.toString();
    }
    
    private static final String HEAD_TEXT
    = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" 
\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\";>%n"
      +"<html xmlns=\"http://www.w3.org/1999/xhtml\";>%n"
      +"<head>%n"
      +"  <meta http-equiv=\"Content-Type\" content=\"text/html; 
charset=iso-8859-1\" />%n"
      +"  <title>%s</title>%n"
      +"%s"              // collection of <script> tags for UI components
      +"%s"              // collection of <style> tags for UI components
      +"</head>%n"
      +"<body>%n";
    
    @Override
    public void encodeEnd(FacesContext context, UIComponent component) throws 
IOException
    {
        ResponseWriter out = context.getResponseWriter();
        out.write(TAIL_TEXT);
    }

    private static final String TAIL_TEXT
    = "\n</body>\n"
      +"</html>\n";
}
package org.stillsecure.cobia.web.comps.renderers;

import org.apache.myfaces.renderkit.html.HtmlFormRenderer;

import java.io.IOException;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;

public class FormRenderer extends ChildRendererBase
{
    private static final HtmlFormRenderer delegate = new HtmlFormRenderer();
    
    @Override
    public void encodeBegin(FacesContext context, UIComponent component) throws 
IOException
    {
        delegate.encodeBegin(context, component);
    }
    
    @Override
    public void encodeEnd(FacesContext context, UIComponent component) throws 
IOException
    {
        delegate.encodeEnd(context, component);
    }
}
package org.stillsecure.cobia.web.comps.renderers;

import java.io.IOException;
import java.util.List;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.render.Renderer;

public class ChildRendererBase extends Renderer
{

    @Override
    public boolean getRendersChildren()
    {
        return true;
    }

    @Override    @SuppressWarnings("unchecked")
    public void encodeChildren(FacesContext context, UIComponent component) 
throws IOException
    {
        List<UIComponent> children = component.getChildren();
        for ( UIComponent child : children )
        {
            child.encodeBegin(context);
            if ( child.getRendersChildren() ) child.encodeChildren(context);
            child.encodeEnd(context);
        }
    }
    
}
package org.stillsecure.cobia.web.comps.renderers;


import java.io.IOException;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
        
public class DivisionRenderer extends ChildRendererBase
{
        public DivisionRenderer()
        {
                // do nothing
        }

        @Override
        public void encodeBegin(FacesContext context, UIComponent component) 
throws IOException
        {
        ResponseWriter out = context.getResponseWriter();
        out.write(String.format(START_TEXT, component.getId()));
        }
    private static final String START_TEXT
    = "\n<div id='%s'>\n";
    
    @Override
    public void encodeEnd(FacesContext context, UIComponent component) throws 
IOException
    {
        ResponseWriter out = context.getResponseWriter();
        out.write(END_TEXT);
    }
    private static final String END_TEXT
    = "\n</div>\n";
}

Reply via email to