If you use composite component directly in xhtml file, you can attach facet to component. However, the JSF 2.2 does not give a way to do. The standard API in FaceletFactory is like this
You can only pass attributes to it. Attach facet is a really neat feature since you can insert arbitrary objects to the component and use component as a template.
I digged into the reference implementation to see how RI implement this method. RI just creates a temporary file exactly like the static file you would write and asks
FaceletFactory to create a component from the file. I can definitely use this approach to include f:facet into the generated file. I verified this approach and it worked. Through this approach, you can do anything programmatically if you can do it in a static approach. You just need to generate a file like the static file and let FaceletFactory to do the rest work.
There is a trick: If you have any "ui:include", you need to transfer the src path so it is relative to the temporary generated file.
Here I give an example how I do it.
private File createTempFile(FacesContext context)
{
File tmpDir = (File) context.getExternalContext().getApplicationMap().get(
"javax.servlet.context.tempdir");
File tempFile;
try
{
tempFile = File.createTempFile("faceletutils", ".xhtml", tmpDir);
} catch (IOException e)
{
throw new RuntimeException(e);
}
return tempFile;
}
/**
*
* return a string which navigates from fromfile to tofile.
*
* @param fromfile
* absolute file path
* @param tofile
* absolute file path
*
* @return
*/
public String fileToFile(String fromfile, String tofile)
{if (tofile.startsWith(File.separator))
{
tofile = tofile.substring(1);
}
if (fromfile.startsWith(File.separator))
{
fromfile = fromfile.substring(1);
}
String[] froms = fromfile.split(File.separator);
String[] tos = tofile.split(File.separator);
List<String> fs = new LinkedList<String>();
int i = 0;
for (i = 0; i < froms.length - 1; i++)
{
// strip common directory.
if (!froms[i].equals(tos[i]))
{
break;
}
}
if (i == froms.length - 1)
{
// reach the last file, all path are the same.
fs.add(".");
} else
{
for (int j = i; j < froms.length - 1; j++)
{
fs.add("..");
}
}
for (int j = i; j < tos.length; j++)
{
fs.add(tos[j]);
}
return StringUtils.join(fs, "/");
}
private String getCompositeComponentString(String library,
String name, Map<String, Object> attrs, FacesContext context,
File tempFile, String tempId, Object... facets)
{
StringBuilder sb = new StringBuilder();
sb.append("<?xml version='1.0' encoding='UTF-8' ?>");
sb.append("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">");
sb.append("<html xmlns=\"http://www.w3.org/1999/xhtml\"\n");
sb.append(" xmlns:f=\"http://java.sun.com/jsf/core\"\n");
sb.append(" xmlns:ui=\"http://java.sun.com/jsf/facelets\"\n");
sb.append(" xmlns:comp=\"http://java.sun.com/jsf/composite/")
.append(library).append("\">\n");
sb.append(" <comp:").append(name).append(" ");
// have all the attributes
if (attrs != null && !attrs.isEmpty())
{
for (Map.Entry<String, Object> attr : attrs.entrySet())
{
sb.append(attr.getKey()).append("=\"")
.append(attr.getValue().toString()).append("\"");
}
}
// give component An ID
sb.append(" id=\"").append(tempId).append("\" >\n");
// add the facet
for (int i = 0; i < facets.length; i++)
{
sb.append("<f:facet name=\"").append(facets[i++].toString())
.append("\" >\n");
sb.append("<ui:include src=\"")
.append(fileToFile(
tempFile.getAbsolutePath(),
context.getExternalContext().getRealPath(
facets[i].toString()))).append("\" />\n");
sb.append("</f:facet>\n");
}
// end the tag.
sb.append("</comp:").append(name).append(">");
sb.append("</html>");
return sb.toString();
}
/** * * Construct a composite comonent given attribute and context-relative facet * file. * * @param library * library of the composite component * @param name * name of the composite component * @param attrs * a series of attrs value for the component. * @param facets * a series of paired value. The first value in pair is facet * name. The second value is CONTEXT-RELATIVE facet file. * @return UIComponent represent the constructed composite component * @throws IOException */ public static UIComponent applyFacetFileToCompositeComponent( String library, String name, Map<String, Object> attrs, Object... facets) { UIComponent result = null; FaceletFactory faceletFactory = (FaceletFactory) FactoryFinder .getFactory(FactoryFinder.FACELET_FACTORY); FacesContext context = FacesContext.getCurrentInstance(); Application app = context.getApplication(); String tempId = context.getViewRoot().createUniqueId(context, name); File tempFile = createTempFile(context); String content = getCompositeComponentString(library, name, attrs, context, tempFile, tempId, facets); try { FileUtils.write(tempFile, content); URL furl = tempFile.toURI().toURL(); Facelet f = faceletFactory.getFacelet(furl); UIForm form = (UIForm) app.createComponent(UIForm.COMPONENT_TYPE); form.setPrependId(false); form.setId(context.getViewRoot().createUniqueId()); f.apply(context, form); result = form.findComponent(tempId); form.getChildren().clear(); return result; } catch (IOException e) { throw new RuntimeException(e); } finally { tempFile.delete(); } }With these methods, I can create my composite with facet like this
applyFacetFileToCompositeComponent("test", "component", null, "firstfacet", "test/firstfragment.xhtml");
where "test" and "component" defines one composite component in resource syntax, and "test/firstfragment.xhtml" is a regular facelet file under web application root directory.