Заполнить 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() ) );
}