如何更新 <ui: repeat> 内的按钮?

How to update button inside <ui: repeat>?

这是我的问题。我在一个 <ui: repeat> 里面有两个 <p: commandLink>。 单击它们时,我需要通过模拟复选框来更新这些按钮。 在 <ui:repeat> 附近更新 <panelGroup> 有效。但是,对于包含许多项目的列表,在单击更新按钮后,它会使屏幕返回到列表的顶部,这使得 select 许多项目变得不切实际。有什么方法可以在 <p:commandLink> 中提供更新特定行吗?

<tbody>
    <ui:repeat value="#{myBean.itens}" var="item" varStatus="index">
        <tr class="myCustomClass" id="item#{item.id}">
            <th>
                <p:commandLink
                    rendered="#{myConditionRendered}"
                    actionListener="#{myBean.deselect(item)}"
                    update=":panelGroupArroundId"
                    process="@this">
                    <i style="font-size: 15px;" class="fa fa-check-square-o" />
                </p:commandLink>
                <p:commandLink
                    rendered="#{!myConditionRendered}"
                    actionListener="#{myBean.select(item)}"
                    update=":panelGroupArroundId"
                    process="@this">
                    <i style="font-size: 15px;" class="fa fa-square-o" />
                </p:commandLink>
            </th>
            <td>
                <h:outputText value="#{item.field1}"/>
            </td>
            ...                                                         
        </tr>
    </ui:repeat>
</tbody>

JSF 版本:2.1
Primefaces 版本:3.5

这里的技巧是只更新您需要的部分 - 如果您更新整个容器,浏览器将从最近更新发生的位置重置和刷新。在 JSF 中,有几种方法可以完成您的需要。较新版本的 JSF(尤其是 2.3)具有可用的选择器,例如 :parent 以允许更轻松地针对页面的特定部分进行更新。对于 2.1,你的限制稍微多一些。

我进行了快速测试,并针对您的特定用户案例提出了一个工作示例。下图使用的是 Font Awesome 和 PrimeFaces 3.5;

我稍微调整了您的代码并制作了一个完整且有效的示例。在此示例中,控制器和辅助 bean 已分离到单独的 类 中。首先让我们看一下backing bean:

@Data @Named @ViewScoped
public class MyBean implements Serializable {
    @Data @RequiredArgsConstructor
    public static class Item {
        @NonNull private String name;
        private boolean selected;
    }

    private List<Item> items;

    @PostConstruct
    private void init() {
        items = List.of(new Item("a"), new Item("b"), new Item("c"),
            new Item("d"), new Item("e"), new Item("f"), new Item("g"), 
            new Item("h"), new Item("i"), new Item("j")
        );
    }
}

这定义了我们的数据集并使用 Lombok 来最小化样板代码的数量(@Data@RequiredArgsConstructor)。我还添加了一个 selected 标志来跟踪复选框是否被选中。然后在选择和取消选择时在控制器 bean 中使用它:

@Data @Named @RequestScoped
public class MyBeanController {
    @Inject
    private MyBean myBean;

    public void select(Item item) {
        item.setSelected(true);
    }

    public void deselect(Item item) {
        item.setSelected(false);
    }
}

此外,这里是视图 (XHTML) 定义:

<html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://xmlns.jcp.org/jsf/html" xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
      xmlns:p="http://primefaces.org/ui">
    <h:head>
        <title>Button update in UI repeat example</title>
        <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.0.0/css/font-awesome.css" />
    </h:head>
    <h:body>
        <table>
            <tbody>
                <ui:repeat value="#{myBean.items}" var="item">
                    <tr style="float: left; padding-right: 10px">
                        <th>
                            <h:form>
                                <h:panelGroup id="content">
                                    <p:commandLink rendered="#{item.selected}"
                                        actionListener="#{myBeanController.deselect(item)}"
                                        update="content" process="@this">
                                        <i class="fa fa-check-square-o" />
                                    </p:commandLink>
                                    <p:commandLink rendered="#{!item.selected}"
                                        actionListener="#{myBeanController.select(item)}"
                                        update="content" process="@this">
                                        <i class="fa fa-square-o" />
                                    </p:commandLink>
                                </h:panelGroup>
                            </h:form>
                        </th>
                        <td><h:outputText value="#{item.name}"/></td>                                                       
                    </tr>
                </ui:repeat>
            </tbody>
        </table>
    </h:body>
</html>

如您所见,我们将按钮包装在一个表单中,并定义了一个容器 content,我们在 p:commandLink 标记的 update 属性中引用了该容器。因为我们被包装在一个表单中,所以 panelGroup 的实际 ID 实际上变成了 j_idt6:9:j_idt8:content 之类的东西,每个 content ID 都是唯一的(因为每个表单都有一个唯一的 ID 生成)。

这使我们能够专门针对和更新该行的数据。

理论上,您可以直接 update 表单而无需将其包装在 content 中。然而,当包裹在 ui:repeat 中时,这样做会干扰表单的生命周期和处理。添加一个额外的 panelGroup 并更新它,就像在这个例子中一样,解决了这个问题。