根据 p:dataTable 的每一行中的另一个 p:selectOneMenu 填充 p:selectOneMenu

Posted

技术标签:

【中文标题】根据 p:dataTable 的每一行中的另一个 p:selectOneMenu 填充 p:selectOneMenu【英文标题】:Populate p:selectOneMenu based on another p:selectOneMenu in each row of a p:dataTable 【发布时间】:2013-07-10 21:14:16 【问题描述】:

我有一个延迟加载的<p:dataTable>。在其中的两列中,每一列都有一个<p:selectOneMenu>

第一列包含国家列表,第二列包含数据库中的州列表。

我希望第二个菜单(包含州列表的菜单)仅显示数据表的每一行中与中第一个菜单中的国家相对应的那些州数据表的每一行

在编辑模式期间,当其菜单中的国家/地区发生更改时,对应于该国家/地区的州应填充到其当前行的菜单中。

如何在数据表的每一行中加载与其国家相对应的州列表?


数据表中的这两列不完整,因为我不知道如何实现这一点。

<p:column>
    <p:cellEditor>
        <f:facet name="output">
            <h:outputText value="#row.state.country.countryName"/>
        </f:facet>

        <f:facet name="input">
            <p:selectOneMenu value="#row.state.country">
                <f:selectItems var="country"
                               value="#cityBean.selectedCountries"
                               itemLabel="#country.countryName"
                               itemValue="#country"/>

                <p:ajax update="states" listener="#cityBean.getStates"/>
            </p:selectOneMenu>
        </f:facet>
    </p:cellEditor>
</p:column>

<p:column>
    <p:cellEditor>
        <f:facet name="output">
            <h:outputText value="#row.state.stateName"/>
        </f:facet>

        <f:facet name="input">
            <p:selectOneMenu id="states">

                <f:selectItems var="state"
                               value="#cityBean.selectedStates"
                               itemLabel="#state.stateName"
                               itemValue="#state"/>
            </p:selectOneMenu>
        </f:facet>
    </p:cellEditor>
</p:column>

cityBean.selectedCountries 检索所有必要的国家,但cityBean.selectedStates 还从数据库中检索所有不必要的州,应修改为仅检索与另一个菜单中的国家相对应的那些州。

我该如何从这里开始?

【问题讨论】:

这里是您利用辅助课程的地方。您的 countryId 不是直接位于您的托管 bean 中,而是位于您的辅助类中(请记住,您用于构建表的列表是由这些辅助工具组成的)。然后你有 ajax 事件侦听器(这个在 MB 中),它将接收已更改的行。 MB 只获取已更改的辅助对象(其countryId)并加载其更新的状态列表,该列表进入辅助对象。 【参考方案1】:

虽然您的初始解决方案有效,但实际上效率低下。这种方法基本上需要CountryState 表的整个对象图(即使带有循环引用)在每个 JSF 视图或会话中完全加载到 Java 的内存中,即使您同时仅使用例如150 个国家中的 5 个(因此理论上 5 个国家列表就足够了,而不是 150 个国家列表)。

我无法全面了解您的功能和技术要求。也许您实际上同时使用了所有这 150 个国家/地区。也许您有很多页面需要所有(至少是“许多”)国家和州。也许您拥有具有大量内存的最先进的服务器硬件,因此所有国家和州都可以轻松地在内存中的所有 JSF 视图和 HTTP 会话中复制。

如果不是这样,那么不要急切地获取每个国家/地区的州列表将是有益的(即@OneToMany(fetch=LAZY) 应该用于Country#statesState#cities)。鉴于国家和州列表(可能)是静态数据,在一年中变化很少,至少足以仅在每次部署的基础上进行更改,最好将它们存储在应用程序范围的 bean 中,该 bean 可以重复使用所有视图和会话,而不是在每个 JSF 视图或 HTTP 会话中重复。

在继续回答之前,我想说明您的代码中存在逻辑错误。鉴于您正在编辑一个城市列表,因此#row 本质上是#city,因此您在下拉输入值中通过#city.state.country 中的状态引用国家是很奇怪的。虽然这可能适用于显示,但不适用于编辑/保存。基本上,您在这里是按州而不是按城市更改国家/地区。当前选择的州将获得新的国家,而不是当前迭代的城市。这种变化将反映在该州的所有城市!

如果您想继续使用此数据模型,这确实不是一件容易的事。理想情况下,您希望在 City 上拥有一个单独的(虚拟)Country 属性,以便更改不会影响城市的 State 属性。您可以将其设为 @Transient,这样 JPA 就不会默认将其视为 @Column

@Transient // This is already saved via City#state#country.
private Country country;

public Country getCountry() 
    return (country == null && state != null) ? state.getCountry() : country;


public void setCountry(Country country)  
    this.country = country;

    if (country == null) 
        state = null;
    

总而言之,您最终应该拥有这个(为简洁起见,省略不相关/默认/明显的属性):

<p:dataTable value="#someViewScopedBean.cities" var="city">
    ...
    <p:selectOneMenu id="country" value="#city.country">
        <f:selectItems value="#applicationBean.countries" />
        <p:ajax update="state" />
    </p:selectOneMenu>
    ...
    <p:selectOneMenu id="state" value="#city.state">
        <f:selectItems value="#applicationBean.getStates(city.country)" />
    </p:selectOneMenu>
    ...
</p:dataTable>

#applicationBean 是这样的:

@Named
@ApplicationScoped
public class ApplicationBean 

    private List<Country> countries;
    private Map<Country, List<State>> statesByCountry;

    @EJB
    private CountryService countryService;

    @EJB
    private StateService stateService;

    @PostConstruct
    public void init() 
        countries = countryService.list();
        statesByCountry = new HashMap<>();
    

    public List<Country> getCountries() 
        return countries;
    

    public List<State> getStates(Country country) 
        List<State> states = statesByCountry.get(country);

        if (states == null) 
            states = stateService.getByCountry(country);
            statesByCountry.put(country, states);
        

        return states;
    


(这是延迟加载的方法;你也可以立即在@PostConstruct 中获取它们,看看什么更适合你)

【讨论】:

完整的示例/解释,效果很好。谢谢。 附带说明,我通常不会无缘无故地在宽限期结束赏金。我在做什么烦人的事吗? :) 不客气。赏金没有问题。问题在“精选”列表中停留的时间越长,您最终获得一些赞成票的机会就越大,这样您就可以赢回花费的声誉。【参考方案2】:

在这种情况下,它非常简单。无需进一步编码。在状态菜单中,如下,

<f:selectItems var="state" value="#cityManagedBean.selectedStates" 
               itemLabel="#state.stateName" itemValue="#state" 
               itemLabelEscaped="true" rendered="true"/>

只需如下修改即可。

<f:selectItems var="state" value="#row.state.country.stateTableSet" 
               itemLabel="#state.stateName" itemValue="#state" 
               itemLabelEscaped="true" rendered="true"/>

因为,实体对象(在本例中为row)包含一个嵌入对象state,该对象又包含一个country对象,该对象最终包含一个状态列表对应于@987654326 @ only 很明显。

这样

cityManagedBean.selectedStates

现在根本不需要额外的托管 bean 方法,需要修改为

row.state.country.stateTableSet

其中stateTableSet 是一个Set&lt;StateTable&gt;,其中包含StateTable 实体的对象列表。


另外,&lt;p:ajax&gt; 中的 listener 不再需要。它应该如下所示。

<p:ajax update="cmbStateMenu"/>

它只是为了更新状态菜单,当在国家菜单中选择一个项目(国家)时。


有问题的代码现在应该如下所示。

<p:column id="country" headerText="Country" resizable="true" sortBy="#row.state.country.countryName" filterBy="#row.state.country.countryName" filterMatchMode="contains" filterMaxLength="45">
    <p:cellEditor>
        <f:facet name="output">
            <h:outputLink value="Country.jsf">
                <h:outputText value="#row.state.country.countryName"/>
                <f:param name="id" value="#row.state.country.countryId"/>
            </h:outputLink>                                                                                
        </f:facet>
        <f:facet name="input">
            <p:selectOneMenu id="cmbCountryMenu" converter="#countryConverter" value="#row.state.country" label="Country" required="true" filter="true" filterMatchMode="contains" effect="fold" rendered="true" editable="false" style="width:100%;">
                <f:selectItems var="country" value="#cityManagedBean.selectedCountries" itemLabel="#country.countryName" itemValue="#country" itemLabelEscaped="true" rendered="true"/>
                <p:ajax update="cmbStateMenu"/>
            </p:selectOneMenu>
        </f:facet>
    </p:cellEditor>
</p:column>

<p:column id="state" headerText="State" resizable="false" sortBy="#row.state.stateName" filterBy="#row.state.stateName" filterMatchMode="contains" filterMaxLength="45">
    <p:cellEditor>
        <f:facet name="output">
            <h:outputLink value="State.jsf">
                <h:outputText value="#row.state.stateName"/>
                <f:param name="id" value="#row.state.stateId"/>
            </h:outputLink>
        </f:facet>
        <f:facet name="input">
            <p:selectOneMenu id="cmbStateMenu" converter="#stateConverter" value="#row.state" label="State" required="true" filter="true" filterMatchMode="contains" effect="fold" rendered="true" editable="false" style="width:100%;">
                <f:selectItems var="state" value="#row.state.country.stateTableSet" itemLabel="#state.stateName" itemValue="#state" itemLabelEscaped="true" rendered="true"/>
            </p:selectOneMenu>
        </f:facet>
    </p:cellEditor>
</p:column>

道歉:我没有在问题中提到我正在检索城市列表。

【讨论】:

虽然这可能有效,但这本质上是数据重复,因此内存效率低。 我可以知道另一种精确的方法吗? JPA 缓存 + 应用程序范围 bean 中的参数化 getter(或者如果您使用 CDI,则使用 @Produces 方法;不确定 Spring 的等价物是什么,因为我不使用 Spring) 【参考方案3】:

您需要根据所选国家/地区获取州列表。为此,您需要一个 valueChangeListener。

试试这个(我现在将两个选择一个菜单放在同一列中)

在你的 xhtml

<p:column id="state" headerText="State" sortBy="#row.state.stateName" filterBy="#row.state.stateName">
<p:cellEditor>  
        <f:facet name="output">  
        <f:facet name="output">
                    <h:outputLink value="State.jsf">
                        <h:outputText value="#row.state.stateName"/>
                        <f:param name="id" value="#row.state.stateId"/>
                    </h:outputLink>
            </f:facet>
        <f:facet name="input">  
        <h:selectOneMenu id="cmbCountryMenu" style="width:100px" value="#row.state.country" converterMessage="Error message." label="Country" valueChangeListener = "#countryController.handleCountrySelect" immediate="true"  converter="#countryConverter">  
            <f:selectItem itemLabel = "Select" itemValue="#null" /> 
            <f:selectItems var="country" value="#cityManagedBean.selectedCountries" itemLabel="#country.countryName" itemValue="#country"/>
            <p:ajax update="cmbStateMenu" />
        </h:selectOneMenu>
        <h:selectOneMenu style="width:100px" value="#row.state" valueChangeListener = "#stateController.handleStateSelect" immediate="false" id="cmbStateMenu"  converter = "#stateConverter">

             <f:selectItems var="state" value="#row.state.country.stateTableSet" itemLabel="#state.stateName" itemValue="#state" itemLabelEscaped="true" rendered="true"/>
            <p:ajax update="@this" />
        </h:selectOneMenu>
        </f:facet>  
    </p:cellEditor> 

在你的控制器中

  public void handleCountrySelect( ValueChangeEvent event )

    setStates( ( ( Country) event.getNewValue() ) );

【讨论】:

在这种情况下,我发布的答案具有预期的功能,但是像row.state.country.stateTableSetjava.util.Set 在这篇文章之后很长时间被更改为java.util.List)之类的 EL 表达式必须在其中省略/删除完全,因为它非常昂贵。在远程 EJB 的情况下,它需要完全热切 加载的关系或 JPA 中昂贵的集合提取才能正常运行,这是最糟糕的实现,不应该使用 util 并且除非它是绝对必要的。 感谢您的努力。

以上是关于根据 p:dataTable 的每一行中的另一个 p:selectOneMenu 填充 p:selectOneMenu的主要内容,如果未能解决你的问题,请参考以下文章

根据同一行中的另一个值从行中选择某个值

使用AWK中的另一个文件查询文件的内容

根据另一列的另一个值和/或另一行中的同一列更新设置值:-ORA 1427

Oracle SQL:根据变量值将一行划分到其他表中的另一行而不遣返

如何在 <p:dataTable> 中一次展开一行?

Primefaces:从p:dataTable中的行选择中排除列