多个动态包含一个JSF标签

每个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组件。 当然,仍然需要某个模型类的列表或数组。

参考:在我们的软件开发博客上, JCG合作伙伴 Oleg Varaksin的一个JSF标签包含了多个动态

翻译自: https://www.javacodegeeks.com/2013/06/multiple-dynamic-includes-with-one-jsf-tag.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值