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

时间:2013-07-12 10:38:10

标签: jsf primefaces jsf-2.2 selectonemenu

我有一个<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也从数据库中检索所有状态,这是不必要的,应该修改以仅检索与其他菜单中的国家/地区对应的状态。

我如何从这里开始?

3 个答案:

答案 0 :(得分:13)

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

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

如果情况并非如此,那么急切地获取每个国家/地区的州名单将是有益的(即@OneToMany(fetch=LAZY)应该在Country#statesState#cities上使用{1}})。鉴于国家和州列表(可能)静态数据在一年内变化很少,至少足以在每个部署的基础上进行更改,最好将它们存储在应用程序范围内的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中获取它们,只看看哪个更适合你)

答案 1 :(得分: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

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


此外,listener中的<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>

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

答案 2 :(得分: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() ) );
}