每个JSF开发人员都知道ui:include和ui:param标签。 您可以包括一个facelet(XHTML文件)并传递一个对象,该对象将在包含的facelet中可用,如下所示:
<ui:include src="/sections/columns.xhtml">
<ui:param name="columns" value="#{bean.columns}"/>
</ui:include>
因此,您可以在带有dynamich列的PrimeFaces DataTable中使用它(p:columns)
<p:dataTable value="#{bean.entries}" var="data" rowKey="#{data.id}" ...>
...
<ui:include src="/sections/columns.xhtml">
<ui:param name="data" value="#{data}"/>
<ui:param name="columns" value="#{bean.columns}"/>
</ui:include>
</p:dataTable>
其中包含的facelet可能包含此代码
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:p="http://primefaces.org/ui"
xmlns:ui="http://java.sun.com/jsf/facelets"
...>
<p:columns value="#{columns}" var="column">
<f:facet name="header">
<h:outputText value="#{msgs[column.header]}"/>
</f:facet>
// place some input / select or complex composite component for multiple data types here.
// a simple example for demonstration purpose:
<p:inputText value="#{data[column.property]}"/>
</p:columns>
</ui:composition>
#{bean.columns}表示描述这些列的特殊对象的列表。 我将此类对象命名为ColumnModel。 所以,这是一个
列出<ColumnModel>。 ColumnModel具有例如属性标头和属性。
继续。 现在,如果要添加对排序/过滤的支持,我们可以使用动态路径,该路径引用包含排序或/和过滤功能的特定facelet文件。 简单地将src属性绑定到bean属性。
<ui:include src="#{bean.columnsIncludeSrc}">
<ui:param name="data" value="#{data}"/>
<ui:param name="columns" value="#{bean.columns}"/>
</ui:include>
豆有类似的东西
private boolean isFilterRight;
private boolean isSortRight
// setter / getter
public String getColumnsIncludeSrc() {
if (isFilterRight && isSortRight) {
return "/include/columnsTableFilterSort.xhtml";
} else if (isFilterRight && !isSortRight) {
return "/include/columnsTableFilter.xhtml";
} else if (!isFilterRight && isSortRight) {
return "/include/columnsTableSort.xhtml";
} else {
return "/include/columnsTable.xhtml";
}
}
根据所设置的布尔权限,包含了不同的方面。 因此,将要包含的文件的决定放在Bean中。 为了更加灵活,我们可以将表封装在一个复合组件中,并将决策逻辑移至组件类。
<cc:interface componentType="xxx.component.DataTable">
<cc:attribute name="id" required="false" type="java.lang.String"
shortDescription="Unique identifier of the component in a NamingContainer"/>
<cc:attribute name="entries" required="true"
shortDescription="The data which are shown in the datatable. This is a list of object representing one row."/>
<cc:attribute name="columns" required="true" type="java.util.List"
shortDescription="The columns which are shown in the datatable. This is a list of instances of type ColumnModel."/>
...
</cc:interface>
<cc:implementation>
<p:dataTable value="#{cc.attrs.entries}" var="data" rowKey="#{data.id}" ...>
...
<ui:include src="#{cc.columnsIncludeSrc}">
<ui:param name="data" value="#{data}"/>
<ui:param name="columns" value="#{cc.attrs.columns}"/>
</ui:include>
</p:dataTable>
</cc:implementation>
ui:include如何工作? 这是在构建视图时应用的标记处理程序。 在JSF 2中,组件树根据POST请求构建两次,一次在RESTORE_VIEW阶段,一次在RENDER_RESPONSE阶段。 在GET上,它在RENDER_RESPONSE阶段构建一次。 此行为在JSF 2规范中指定,并且在Mojarra和MyFaces中相同。 如果页面作者使用条件包含或条件模板,则必须在RENDER_RESPONSE中建立视图。 因此,您可以确保ui:include的src属性在呈现阶段之前不久就得到了评估。
但是到了重点! 到目前为止,我所写的内容只是介绍了扩展ui:include的动机。 最近,我有一项任务要使用带有动态列的ap:dataTable和p:rowEditor。 在PrimeFaces展示柜中就是这样的 。 问题只是–这种编辑功能不支持p:columns。 我的想法是动态地多次添加p:column标签,但是具有不同的上下文参数。 您可以将其想象为ui:include和ui:param在循环中。 在上面的示例中,我们打算遍历List <ColumnModel>。 每次循环迭代都应在所包含的facelet中提供ColumnModel类型的实例。 因此,我编写了一个自定义标签处理程序以多次包含任何facelet。
package xxx.taghandler;
import xxx.util.VariableMapperWrapper;
import java.io.IOException;
import java.util.List;
import java.util.UUID;
import javax.el.ExpressionFactory;
import javax.el.ValueExpression;
import javax.el.VariableMapper;
import javax.faces.component.UIComponent;
import javax.faces.view.facelets.FaceletContext;
import javax.faces.view.facelets.TagAttribute;
import javax.faces.view.facelets.TagAttributeException;
import javax.faces.view.facelets.TagConfig;
import javax.faces.view.facelets.TagHandler;
/**
* Tag handler to include a facelet multiple times with different contextes (objects from "value").
* The attribute "value" can be either of type java.util.List or array.
* If the "value" is null, the tag handler works as a standard ui:include.
*/
public class InlcudesTagHandler extends TagHandler {
private final TagAttribute src;
private final TagAttribute value;
private final TagAttribute name;
public InlcudesTagHandler(TagConfig config) {
super(config);
this.src = this.getRequiredAttribute("src");
this.value = this.getAttribute("value");
this.name = this.getAttribute("name");
}
@Override
public void apply(FaceletContext ctx, UIComponent parent) throws IOException {
String path = this.src.getValue(ctx);
if ((path == null) || (path.length() == 0)) {
return;
}
// wrap the original mapper - this is important when some objects passed into include via ui:param
// because ui:param invokes setVariable(...) on the set variable mappper instance
VariableMapper origVarMapper = ctx.getVariableMapper();
ctx.setVariableMapper(new VariableMapperWrapper(origVarMapper));
try {
this.nextHandler.apply(ctx, null);
ValueExpression ve = (this.value != null) ? this.value.getValueExpression(ctx, Object.class) : null;
Object objValue = (ve != null) ? ve.getValue(ctx) : null;
if (objValue == null) {
// include facelet only once
ctx.includeFacelet(parent, path);
} else {
int size = 0;
if (objValue instanceof List) {
size = ((List) objValue).size();
} else if (objValue.getClass().isArray()) {
size = ((Object[]) objValue).length;
}
final ExpressionFactory exprFactory = ctx.getFacesContext().getApplication().getExpressionFactory();
final String strName = this.name.getValue(ctx);
// generate unique Id as a valid Java identifier and use it as variable for the provided value expression
final String uniqueId = "a" + UUID.randomUUID().toString().replaceAll("-", "");
ctx.getVariableMapper().setVariable(uniqueId, ve);
// include facelet multiple times
StringBuilder sb = new StringBuilder();
for (int i = 0; i < size; i++) {
if ((strName != null) && (strName.length() != 0)) {
// create a new value expression in the array notation and bind it to the variable "name"
sb.append("#{");
sb.append(uniqueId);
sb.append("[");
sb.append(i);
sb.append("]}");
ctx.getVariableMapper().setVariable(strName,
exprFactory.createValueExpression(ctx, sb.toString(), Object.class));
}
// included facelet can access the created above value expression
ctx.includeFacelet(parent, path);
// reset for next iteration
sb.setLength(0);
}
}
} catch (IOException e) {
throw new TagAttributeException(this.tag, this.src, "Invalid path : " + path);
} finally {
// restore original mapper
ctx.setVariableMapper(origVarMapper);
}
}
}
最重要的调用是ctx.includeFacelet(parent,path)。 JSF API中的 includeFacelet(…)方法在相对于当前标记的某个路径上包含了facelet标记。 类VariableMapperWrapper用于通过ui:param从名称到值的映射。 对于带有列的示例,在每次调用includeFacelet(...)之前,变量列还将映射到表达式#{columns [0]},#{columns [1]}等。 好吧,不完全是这些表达式,在列的位置应该是一个唯一的名称,该名称再次映射到columns对象(以避免可能的名称冲突)。 映射器类如下所示
package xxx.util;
import java.util.HashMap;
import java.util.Map;
import javax.el.ELException;
import javax.el.ValueExpression;
import javax.el.VariableMapper;
/**
* Utility class for wrapping a VariableMapper. Modifications occur to the internal Map instance.
* The resolving occurs first against the internal Map instance and then against the wrapped VariableMapper
* if the Map doesn't contain the requested ValueExpression.
*/
public class VariableMapperWrapper extends VariableMapper {
private final VariableMapper wrapped;
private Map<String, ValueExpression> vars;
public VariableMapperWrapper(VariableMapper orig) {
super();
this.wrapped = orig;
}
@Override
public ValueExpression resolveVariable(String variable) {
ValueExpression ve = null;
try {
if (this.vars != null) {
// try to resolve against the internal map
ve = this.vars.get(variable);
}
if (ve == null) {
// look in the wrapped variable mapper
return this.wrapped.resolveVariable(variable);
}
return ve;
} catch (Throwable e) {
throw new ELException("Could not resolve variable: " + variable, e);
}
}
@Override
public ValueExpression setVariable(String variable, ValueExpression expression) {
if (this.vars == null) {
this.vars = new HashMap<String, ValueExpression>();
}
return this.vars.put(variable, expression);
}
}
在taglib XML文件中注册标签处理程序,您就可以完成。
<tag>
<tag-name>includes</tag-name>
<handler-class>xxx.taghandler.InlcudesTagHandler</handler-class>
<attribute>
<description>
<![CDATA[The relative path to a XHTML file to be include one or multiple times.]]>
</description>
<name>src</name>
<required>true</required>
<type>java.lang.String</type>
</attribute>
<attribute>
<description>
<![CDATA[Objects which should be available in the included XHTML files. This attribute can be either
of type java.util.List or array. If it is null, the tag handler works as a standard ui:include.]]>
</description>
<name>value</name>
<required>false</required>
<type>java.lang.Object</type>
</attribute>
<attribute>
<description>
<![CDATA[The name of the parameter which points to an object of each iteration over the given value.]]>
</description>
<name>name</name>
<required>false</required>
<type>java.lang.String</type>
</attribute>
</tag>
现在我可以在复合组件中使用它了
<p:dataTable value="#{cc.attrs.entries}" var="data" rowKey="#{data.id}" ...>
...
<custom:includes src="#{cc.columnsIncludeSrc}" value="#{cc.attrs.columns}" name="column">
<ui:param name="data" value="#{data}"/>
</custom:includes>
</p:dataTable>
典型的facelet文件(和组件树)包含一个非常规则的p:column标记,这意味着我们能够使用DataTable的所有功能!
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:p="http://primefaces.org/ui"
xmlns:ui="http://java.sun.com/jsf/facelets"
...>
<p:column headerText="#{msgs[column.header]}">
<p:cellEditor>
<f:facet name="output">
<custom:typedOutput outputType="#{column.outputTypeName}"
typedData="#{column.typedData}"
value="#{data[column.property]}"
timeZone="#{cc.timeZone}"
calendarPattern="#{cc.calendarPattern}"
locale="#{cc.locale}"/>
</f:facet>
<f:facet name="input">
<custom:typedInput inputType="#{column.inputTypeName}"
typedData="#{column.typedData}"
label="#{column.inputTypeName}"
value="#{data[column.property]}"
onchange="highlightEditedRow(this)"
timeZone="#{cc.timeZone}"
calendarPattern="#{cc.calendarPattern}"
locale="#{cc.locale}"/>
</f:facet>
</p:cellEditor>
</p:column>
</ui:composition>
注意 :此方法可以应用于其他组件和用例。 InlcudesTagHandler可以正常运行。 例如,我可以想象在没有基础MenuModel的情况下在PrimeFaces中创建一个动态Menu组件。 当然,仍然需要某个模型类的列表或数组。
翻译自: https://www.javacodegeeks.com/2013/06/multiple-dynamic-includes-with-one-jsf-tag.html