Заполнить p: selectOneMenu на основе другого p:selectOneMenu в каждой строке p:dataTable
у меня есть <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 ответов
пока ваше начальное решение работает, оно фактически неэффективно. Этот подход в основном требует всего графа объектов Country
и State
таблицы (даже с круговыми ссылками) должны быть полностью загружены в память Java на представление или сеанс JSF, хотя при одновременном использовании только, например, 5 из 150 стран (и, таким образом, теоретически 5 списков состояний было бы достаточно вместо 150 списков состояний).
я не имею полное представление о вашем функциональном и технические условия. Возможно, вы на самом деле одновременно используя все эти 150 стран. Возможно, у вас есть много страниц, где все (по крайней мере, "многие") страны и государства необходимы. Возможно, у вас есть современное серверное оборудование с большим количеством памяти, чтобы все страны и государства могли легко дублироваться во всех представлениях JSF и сеансах HTTP в памяти.
если это не так, то было бы полезно не жадно загружать Государственный список каждой отдельной страны (т. е. @OneToMany(fetch=LAZY)
следует использовать на Country#states
и State#cities
). Учитывая, что списки стран и состояний являются (вероятно) статическими данными, которые меняются очень мало раз в год, по крайней мере достаточными для изменения только на основе развертывания, лучше просто хранить их в компоненте приложения, который повторно используется во всех представлениях и сеансах, а не дублируется в каждом представлении 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
, просто посмотрите, что лучше для вас)
в этом случае это довольно просто. Нет необходимости кодировать дальше. В меню Состояние, следующее,
<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
здесь stateTableSet
это Set<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>
извинение: я не упомянул в вопросе, что я получаю список городов.
вам нужно получить список состояний на основе выбранной страны. Для этого нужен 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() ) );
}