13

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

我该如何从这里开始?

4

3 回答 3

13

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

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

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

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

如果您想继续使用此数据模型,这确实并非易事。理想情况下,您希望拥有一个单独的(虚拟)Country属性,City以便更改不会影响城市的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,看看什么对您更好)

于 2014-05-08T07:49:49.460 回答
0

您需要根据所选国家/地区获取州列表。为此,您需要一个 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() ) );
}
于 2014-05-07T20:15:53.500 回答
0

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

<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该对象最终包含一个状态列表,该列表 对应于country显而易见的状态。

以便

cityManagedBean.selectedStates

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

row.state.country.stateTableSet

其中stateTableSetaSet<StateTable>包含StateTable实体的对象列表。


此外,不再需要listenerin 。<p:ajax>它应该如下所示。

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

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

于 2013-07-21T22:34:10.917 回答