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