如何在<h:dataTable>或<ui:repeat>中使用<h:selectBooleanCheckbox>选择多个项目?

21

我有一个Facelets页面,其中包含一个<h:dataTable>。在每一行中都有一个<h:selectBooleanCheckbox>。如果选中了复选框,则应将相应行后面的对象设置在bean中。

  1. 我该如何做到这一点?
  2. 如何在后备bean中获取所选行或其数据?
  3. 或者最好使用<h:selectManyCheckbox>吗?
3个回答

53

您最好将<h:selectBooleanCheckbox>的值与一个Map<Item, Boolean>属性绑定,其中Item表示相应行背后的对象。

<h:dataTable value="#{bean.items}" var="item">
    <h:column>
        <h:selectBooleanCheckbox value="#{bean.checked[item]}" />
    </h:column>
    ...
</h:dataTable>
<h:commandButton value="submit" action="#{bean.submit}" />
public class Bean {
    private Map<Item, Boolean> checked = new HashMap<Item, Boolean>();
    private List<Item> items;

    public void submit() {
        List<Item> selectedItems = checked.entrySet().stream()
            .filter(Entry::getValue)
            .map(Entry::getKey)
            .collect(Collectors.toList());

        checked.clear(); // If necessary.

        // Now do your thing with selectedItems.
    }

    // ...
}

你看,这个Map会自动填充所有表格项作为键,复选框的值会自动设置为与该项作为键关联的Map值。

这仅要求 Item#equals()Item#hashCode() 根据它们的契约正确实现

如果无法保证,则最好使用一个Map<RowId, Boolean>代替,其中RowId表示行标识符的类型。让我们举一个例子,假设你有一个Item对象,其标识符属性id是一个Long类型:

public class Item {
    private Long id;
    // ...
}
<h:dataTable value="#{bean.items}" var="item">
    <h:column>
        <h:selectBooleanCheckbox value="#{bean.checked[item.id]}" />
    </h:column>
    ...
</h:dataTable>
<h:commandButton value="submit" action="#{bean.submit}" />
public class Bean {
    private Map<Long, Boolean> checked = new HashMap<Long, Boolean>();
    private List<Item> items;

    public void submit() {
        List<Item> selectedItems = items.stream()
            .filter(item -> checked.get(item.getId()))
            .collect(Collectors.toList());

        checked.clear(); // If necessary.

        // Now do your thing with selectedItems.
    }

    // ...
}

使用类型为 <Long,Item>Map,只需迭代映射而不是整个列表,这样做难道不更容易吗? - user748187
@Markus:这是不可能的。<h:selectBooleanCheckbox> 只支持 booleanBoolean 值。 - BalusC
1
数据表格和按钮应该在同一个表单中吗?您能否提供更多关于视图方面的细节,或者只有按钮应该在表单中? - Mahmoud Saleh
@Msaleh:肯定是要以相同的形式发送。你想在按下按钮时发送复选框的值,对吧? - BalusC
这个解决方案对我也起作用了。以下文章也补充了这个解决方案。http://www.andygibson.net/blog/tutorial/binding-dynamic-multi-select-checkboxes-with-jsf/ - Mr. Port St Joe

4
在下面的例子中,我使用复选框来选择两个或更多产品,以便用户可以使用JSF 2.0在新网页上比较产品规格。花了我一些时间才找到以下问题(现在当然显而易见),所以认为对于那些试图使用BalusC上面的代码进行分页的人值得一提(很好的答案BalusC,比我想象的简单得多)。
如果您正在使用分页,您将在以下行中获得nullpointers:
if (checked.get(item.getId()))
-在BalusC的代码中。
这是因为只有显示的复选框被添加到Map中(doh;拍额头)。对于那些由于分页而从未显示其复选框的产品,这行将导致空指针错误,并且需要添加检查以忽略这些空指针(假设所有复选框在页面加载时都未选中)。为了让用户勾选复选框,他们需要显示分页页面,因此在那之后所有内容都可以正常工作。
如果第一页需要选中某些或所有复选框,则这对您没有帮助...您必须手动将它们添加到Map中,以便在页面加载时正确显示它们。
注意:因为我正在使用JPA“从数据库派生的实体类”对象,所以我还需要在我的ProductTbl Entity Class中使用@Transient来表示id,因为默认情况下JPA将所有变量都视为数据库中的列,除非以@Transient为前缀。此外,我还使用了第二个链接来重置复选框,该链接调用clearSelections(),而我的“提交”是调用compareSelectedProducts()的链接,而不是提交按钮。
完整代码如下:
在从数据库派生的“ProductTbl”实体类中:
@Transient
private Long id;

public Long getId()
{
    return id;
}

public void setId(Long id)
{
    this.id = id;
}

在后端Bean“ProductSelection”中:
private Map<Long, Boolean> checked = new HashMap<Long, Boolean>();
private String errorMessage = "";
// List of all products.
private List<ProductTbl> products;
// List of products to compare.
private List<ProductTbl> compareProducts;

// Setters and getters for above...

public String compareSelectedProducts() 
{
    // Reset selected products store.
    compareProducts = new ArrayList();

    for (ProductTbl item: products) 
    {
        // If there is a checkbox mapping for the current product then...
        if(checked.get(item.getId()) != null)
        {
           // If checkbox is ticked then...
           if (checked.get(item.getId())) 
            {
                // Add product to list of products to be compared.
                compareProducts.add(item);
            } 
        }
    }

    if(compareProducts.isEmpty())
    {
        // Error message that is displayed in the 'ErrorPage.xhtml' file.
        errorMessage = "No Products selected to compare specifications. Select two or more products by ticking the check box in the second column 'Cmpr'";
        return "process_ErrorPage";
    }

    // Rest of code to get product specification data ready to be displayed.

    return "process_CompareSelected";
}

public String clearSelections()
{
    // Untick all checkbox selections.
    checked.clear();

    return "process_MainSearchResult";
}

在JSF网页“MainSearchResult.xhtml”中:
<h:commandLink action="#{productSelection.compareSelectedProducts()}" value="Cmpr Specification Comparison Table" /> 
<h:commandLink action="#{productSelection.clearSelections()}" value="Clear Selected" />

<h:dataTable value="#{productSelection.products}" rows="#{productSelection.numberRowsToDisplay}" first="#{productSelection.rowStart}" var="item" headerClass="table-header" >
    <h:column>
       <f:facet name="header">
          <h:outputText style="font-size:12px" value="Cmpr" />
       </f:facet>
       <div style="text-align:center;" >
          <h:selectBooleanCheckbox value="#{productSelection.checked[item.id]}" />
       </div>
    </h:column>
</h:dataTable>

在“faces-config.xml”文件中:
<navigation-rule>
    <navigation-case>
        <from-outcome>process_MainSearchResult</from-outcome>
        <to-view-id>/MainSearchResult.xhtml</to-view-id>
    </navigation-case>
</navigation-rule>
<navigation-rule>
    <navigation-case>
        <from-outcome>process_CompareSelected</from-outcome>
        <to-view-id>/CompareSelected.xhtml</to-view-id>
    </navigation-case>
</navigation-rule>
<navigation-rule>
    <navigation-case>
        <from-outcome>process_ErrorPage</from-outcome>
        <to-view-id>/ErrorPage.xhtml</to-view-id>
    </navigation-case>
</navigation-rule>

1
通过发送参数的一种方式是使用<code><h:selectBooleanCheckbox></code>。在<code>ValueChangeListener</code>中,您可以使用<code>getAttributes().get("title")</code>从组件获取它。这有助于在您希望将id值作为参数发送(而不是所选行索引)的情况下使用。

我不确定你所说的“与所选行索引相反”的意思是什么,但如果你针对我的回答,那请注意我并没有传递行索引。只有所选项目本身的ID。 - BalusC

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接