Jsf 基于p:dataTable每行中的另一个p:selectOneMenu填充p:selectOneMenu

Jsf 基于p:dataTable每行中的另一个p:selectOneMenu填充p:selectOneMenu,jsf,primefaces,jsf-2.2,selectonemenu,Jsf,Primefaces,Jsf 2.2,Selectonemenu,我有一个带有延迟加载的。在其中两列中,每列中都有一个 第一列包含国家列表,第二列包含数据库中的国家列表 我希望第二个菜单(包含一个州列表的菜单)只显示数据表每行中与数据表每行第一个菜单中的国家相对应的州 在编辑模式下,当其菜单中的国家/地区发生更改时,应在其当前行的菜单中填充与该国家/地区对应的状态 如何在数据表的每一行中加载与其国家相对应的国家列表 数据表中的这两列是不完整的,因为我不知道如何实现这一点 <p:column> <p:cellEditor>

我有一个带有延迟加载的
。在其中两列中,每列中都有一个

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

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

在编辑模式下,当其菜单中的国家/地区发生更改时,应在其当前行的菜单中填充与该国家/地区对应的状态

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


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

<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
还检索数据库中所有不必要的州,这些州应修改为仅检索与另一菜单中的国家相对应的州


我如何从这里开始?

在这种情况下,它非常简单。不需要进一步编码。在“状态”菜单中,执行以下操作:

<f:selectItems var="state" value="#{cityManagedBean.selectedStates}" 
               itemLabel="#{state.stateName}" itemValue="#{state}" 
               itemLabelEscaped="true" rendered="true"/>
现在根本不需要额外的托管bean方法,需要根据需要进行修改

row.state.country.stateTableSet
其中
stateTableSet
是一个
集合
,它包含
StateTable
实体的对象列表


此外,
中的
侦听器
不再是必需的。它应该简单地如下所示

<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>

当在国家/地区菜单中选择某个项目(国家/地区)时,它仅用于更新状态菜单


现在讨论中的代码应该如下所示

<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>


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

您需要根据所选国家获取州列表。为此,您需要一个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> 

虽然最初的解决方案有效,但实际上效率很低。这种方法基本上要求每个JSF视图或会话将
国家
状态
表的整个对象图(即使使用循环引用)完全加载到Java内存中,即使同时使用150个国家中的5个(因此,理论上5个州的名单就足够了,而不是150个州的名单)

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

如果情况并非如此,那么最好不要急于获取每个国家的州列表(即,
@OneToMany(fetch=LAZY)
应该用于
国家#州
州#城市
)。考虑到国家和州列表(可能)静态数据在一年内变化很少,至少足以在每次部署的基础上进行更改,最好将它们存储在应用程序范围的bean中,该bean在所有视图和会话中重用,而不是在每个JSF视图或HTTP会话中重复

在继续回答之前,我想指出您的代码中有一个逻辑错误。考虑到您正在编辑一个城市列表,因此
{row}
本质上是
{city}
,奇怪的是,您在
{city.state.country}中通过州引用国家
在下拉输入值中。虽然这可能用于显示,但不适用于编辑/保存。基本上,您是在按州而不是按城市更改国家。当前选定的州将获得新的国家,而不是当前迭代的城市。此更改将反映在所有城市中他是这个州的人

如果您想继续使用此数据模型,这确实不是一件小事。理想情况下,您希望有一个单独的(虚拟的)<代码>国家< /代码>属性>代码>锡蒂<代码>,以使更改不影响城市的<代码>状态>代码>属性。您可以使其仅为代码> @瞬态< /代码>,以使JPA不默认默认为<代码> @列>代码>。
@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;
    }
}
总而言之,您最终应该具有以下特性(为了简洁起见,省略了不相关/默认/明显的属性):


(这是延迟加载方法;您也可以立即在
@PostConstruct
中获取它们,看看有什么对您更有利)

这里是您利用辅助类的地方。您的
countryId
不是直接位于托管bean中,而是位于辅助类中(请记住,您用来构建表的列表是由这些辅助项组成的)。然后您就有了ajax事件监听器(这个监听器位于MB中),它将接收已更改的行
@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>
@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;
    }

}