如何在 dataTable 或 ui: repeat 中将选定的行传递给 commandLink?

我在 JSF2应用程序中使用 Primefaces。我有一个 <p:dataTable>,我希望用户能够直接对单独的行执行各种操作,而不是选择行。为此,我在上一篇专栏文章中介绍了几个 <p:commandLink>

我的问题是: 如何将行 ID 传递给由命令链接启动的操作,以便我知道要对哪一行进行操作?我尝试使用 <f:attribute>:

<p:dataTable value="#{bean.items}" var="item">
...
<p:column>
<p:commandLink actionListener="#{bean.insert}" value="insert">
<f:attribute name="id" value="#{item.id}" />
</p:commandLink>
</p:column>
</p:dataTable>

但它总是产生0-显然行变量 f不可用时,属性呈现(它工作时,我使用一个固定的值)。

有人有别的办法吗?

177162 次浏览

In JSF 1.2 this was done by <f:setPropertyActionListener> (within the command component). In JSF 2.0 (EL 2.2 to be precise, thanks to BalusC) it's possible to do it like this: action="${filterList.insert(f.id)}

As to the cause, the <f:attribute> is specific to the component itself (populated during view build time), not to the iterated row (populated during view render time).

There are several ways to achieve the requirement.

  1. If your servletcontainer supports a minimum of Servlet 3.0 / EL 2.2, then just pass it as an argument of action/listener method of UICommand component or AjaxBehavior tag. E.g.

     <h:commandLink action="#{bean.insert(item.id)}" value="insert" />
    

    In combination with:

     public void insert(Long id) {
    // ...
    }
    

    This only requires that the datamodel is preserved for the form submit request. Best is to put the bean in the view scope by @ViewScoped.

    You can even pass the entire item object:

     <h:commandLink action="#{bean.insert(item)}" value="insert" />
    

    with:

     public void insert(Item item) {
    // ...
    }
    

    On Servlet 2.5 containers, this is also possible if you supply an EL implementation which supports this, like as JBoss EL. For configuration detail, see this answer.


  2. Use <f:param> in UICommand component. It adds a request parameter.

     <h:commandLink action="#{bean.insert}" value="insert">
    <f:param name="id" value="#{item.id}" />
    </h:commandLink>
    

    If your bean is request scoped, let JSF set it by @ManagedProperty

     @ManagedProperty(value="#{param.id}")
    private Long id; // +setter
    

    Or if your bean has a broader scope or if you want more fine grained validation/conversion, use <f:viewParam> on the target view, see also f:viewParam vs @ManagedProperty:

     <f:viewParam name="id" value="#{bean.id}" required="true" />
    

    Either way, this has the advantage that the datamodel doesn't necessarily need to be preserved for the form submit (for the case that your bean is request scoped).


  3. Use <f:setPropertyActionListener> in UICommand component. The advantage is that this removes the need for accessing the request parameter map when the bean has a broader scope than the request scope.

     <h:commandLink action="#{bean.insert}" value="insert">
    <f:setPropertyActionListener target="#{bean.id}" value="#{item.id}" />
    </h:commandLink>
    

    In combination with

     private Long id; // +setter
    

    It'll be just available by property id in action method. This only requires that the datamodel is preserved for the form submit request. Best is to put the bean in the view scope by @ViewScoped.


  4. Bind the datatable value to DataModel<E> instead which in turn wraps the items.

     <h:dataTable value="#{bean.model}" var="item">
    

    with

     private transient DataModel<Item> model;
    
    
    public DataModel<Item> getModel() {
    if (model == null) {
    model = new ListDataModel<Item>(items);
    }
    return model;
    }
    

    (making it transient and lazily instantiating it in the getter is mandatory when you're using this on a view or session scoped bean since DataModel doesn't implement Serializable)

    Then you'll be able to access the current row by DataModel#getRowData() without passing anything around (JSF determines the row based on the request parameter name of the clicked command link/button).

     public void insert() {
    Item item = model.getRowData();
    Long id = item.getId();
    // ...
    }
    

    This also requires that the datamodel is preserved for the form submit request. Best is to put the bean in the view scope by @ViewScoped.


  5. Use Application#evaluateExpressionGet() to programmatically evaluate the current #{item}.

     public void insert() {
    FacesContext context = FacesContext.getCurrentInstance();
    Item item = context.getApplication().evaluateExpressionGet(context, "#{item}", Item.class);
    Long id = item.getId();
    // ...
    }
    

Which way to choose depends on the functional requirements and whether the one or the other offers more advantages for other purposes. I personally would go ahead with #1 or, when you'd like to support servlet 2.5 containers as well, with #2.

In my view page:

<p:dataTable  ...>
<p:column>
<p:commandLink actionListener="#{inquirySOController.viewDetail}"
process="@this" update=":mainform:dialog_content"
oncomplete="dlg2.show()">
<h:graphicImage library="images" name="view.png"/>
<f:param name="trxNo" value="#{item.map['trxNo']}"/>
</p:commandLink>
</p:column>
</p:dataTable>

backing bean

 public void viewDetail(ActionEvent e) {


String trxNo = getFacesContext().getRequestParameterMap().get("trxNo");


for (DTO item : list) {
if (item.get("trxNo").toString().equals(trxNo)) {
System.out.println(trxNo);
setSelectedItem(item);
break;
}
}
}

Thanks to this site by Mkyong, the only solution that actually worked for us to pass a parameter was this

<h:commandLink action="#{user.editAction}">
<f:param name="myId" value="#{param.id}" />
</h:commandLink>

with

public String editAction() {


Map<String,String> params =
FacesContext.getExternalContext().getRequestParameterMap();
String idString = params.get("myId");
long id = Long.parseLong(idString);
...
}

Technically, that you cannot pass to the method itself directly, but to the JSF request parameter map.