2013-07-12 18 views
12

Ho un <p:dataTable> con carico pigro. In due delle colonne, c'è uno <p:selectOneMenu> in ciascuna di esse.Popolare p: selectOneMenu basato su un altro p: selectOneMenu in ogni riga di un p: dataTable

La prima colonna contiene un elenco di paesi e il secondo contiene un elenco di stati da un database.

Voglio il secondo menu (quello che contiene un elenco di stati) per visualizzare solo quegli stati in ogni riga della tabella di dati che corrispondono al paese nel primo menu a ogni riga dei dati tavolo.

Durante la modalità di modifica, quando viene modificato un Paese nel suo menu, gli stati corrispondenti a tale paese devono essere compilati nel relativo menu in quella riga corrente.

Come caricare tali elenchi di stati che corrispondono ai rispettivi paesi in ciascuna riga della tabella dati?


Queste due colonne nella tabella di dati non sono complete, poiché non ho un'idea precisa su come ottenere ciò.

<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 recupera tutti i paesi che è necessario, ma cityBean.selectedStates recupera anche tutti gli stati dal database, che non è necessario e deve essere modificato in modo da recuperare solo quegli stati che corrispondono al suo paese in un altro menu.

Come posso procedere da qui?

+0

Ecco dove approfitti della tua classe ausiliaria. Il tuo 'countryId' non si trova direttamente nel tuo bean gestito, ma nella tua classe ausiliaria (ricorda che la lista che usi per costruire la tabella è composta da questi ausiliari). Poi hai il listener di eventi ajax (questo va nel MB) che riceverà la riga che è stata cambiata. Il MB prende solo l'oggetto ausiliario che è stato cambiato (il suo 'countryId') e carica il suo elenco di stato aggiornato che entra nell'ausilio. –

risposta

12

Mentre la soluzione iniziale funziona, è in effetti inefficiente. Questo approccio richiede fondamentalmente l'intero grafico degli oggetti delle tabelle Country e State (anche con riferimenti circolari) per essere completamente caricati nella memoria Java per ogni vista JSF o sessione anche se si utilizzano solo e.g. 5 dei 150 paesi (e quindi teoricamente 5 elenchi di stati sarebbero stati sufficienti invece di 150 liste statali).

Non ho una visione completa dei vostri requisiti funzionali e tecnici. Forse in realtà stai usando simultaneamente tutti questi 150 paesi. Forse hai molte pagine in cui sono necessari tutti i (almeno, "molti") paesi e stati. Forse hai un hardware server all'avanguardia con molta memoria in modo che tutti i paesi e gli stati possano essere duplicati senza sforzo su tutte le viste JSF e le sessioni HTTP in memoria.

Se questo non è il caso, allora sarebbe vantaggioso per non ardentemente recuperare l'elenco dello stato di ogni singolo Paese (cioè @OneToMany(fetch=LAZY) deve essere utilizzato su Country#states e State#cities). Dato che gli elenchi dei paesi e degli stati sono (probabilmente) dati statici che cambiano molto poche volte in un anno, almeno sufficienti per essere modificati solo in base alla distribuzione, è meglio archiviarli in un bean con scope applicazione che viene riutilizzato in tutte le viste e le sessioni invece di essere duplicate in ogni vista JSF o sessione HTTP.

Prima di continuare con la risposta, vorrei segnalare che c'è un errore logico nel codice. Dato che stai modificando un elenco di città, e quindi #{row} è essenzialmente #{city}, è strano che tu faccia riferimento al paese tramite lo stato come in #{city.state.country} nel valore di inserimento a discesa. Anche se potrebbe funzionare per la visualizzazione, non funzionerebbe per la modifica/salvataggio. Fondamentalmente, stai qui cambiando il paese in base allo stato anziché sulla base di una singola città. Lo stato attualmente selezionato otterrebbe il nuovo paese anziché la città attualmente iterata. Questo cambiamento si rifletterà in tutte le città di questo stato!

Questo non è certo banale se desideri continuare con questo modello di dati.Idealmente, ti piacerebbe avere una proprietà (virtuale) Country separata su City in modo che le modifiche non influiscano sulla proprietà State della città. Potresti renderlo solo @Transient in modo che JPA non lo consideri come @Column come predefinito.

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

Tutto sommato, si dovrebbe in ultima analisi, avere questo (irrilevante/default/attributi evidenti omesse per brevità):

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

Con un #{applicationBean} qualcosa di simile:

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

} 

(questo è l'approccio di caricamento lento, puoi anche recuperarli immediatamente tutti in @PostConstruct, basta vedere cosa è meglio per te)

+0

Completo esempio/spiegazione, ha funzionato alla grande. Grazie. – Tiny

+0

Come nota a margine, in genere non termino il periodo di tolleranza dell'utenza per nessun motivo preciso. C'è qualcosa di irritante che sto facendo? :) – Tiny

+3

Prego. Nessun problema per quanto riguarda la taglia. Più a lungo la domanda rimane nell'elenco "in primo piano", maggiori sono le possibilità che hai su alcuni upvotes alla fine in modo che tu possa guadagnare indietro la reputazione spesa. – BalusC

0

In questo caso, è abbastanza semplice. Non è necessario codificare ulteriormente. Nel menu di stato, il seguente,

<f:selectItems var="state" value="#{cityManagedBean.selectedStates}" 
       itemLabel="#{state.stateName}" itemValue="#{state}" 
       itemLabelEscaped="true" rendered="true"/> 

deve essere modificato come segue.

<f:selectItems var="state" value="#{row.state.country.stateTableSet}" 
       itemLabel="#{state.stateName}" itemValue="#{state}" 
       itemLabelEscaped="true" rendered="true"/> 

Poiché l'oggetto entità (row in questo caso) contiene un oggetto incorporato di state che a sua volta, contiene un oggetto di country che infine contiene un elenco di stati corrispondenti a tale country solo come ovvio.

In modo che

cityManagedBean.selectedStates 

un metodo bean gestito in più non è ora richiesto a tutti e aveva bisogno di essere modificato come

row.state.country.stateTableSet 

Dove stateTableSet è un Set<StateTable> che contiene un elenco di oggetti del StateTable entità.


Inoltre, listener in <p:ajax> non è più necessaria. Dovrebbe semplicemente apparire come il seguente.

<p:ajax update="cmbStateMenu"/> 

E 'lì solo per lo scopo di aggiornare il menu di stato, quando si seleziona un elemento (paese) nel menu Paese.


Il codice in questione dovrebbe essere simile al seguente.

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

Apologia: Non ho citato nella domanda che mi è stato il recupero di un elenco di città.

+0

Anche se può funzionare, questa è essenzialmente la duplicazione dei dati e quindi la memoria inefficiente. – BalusC

+0

Posso conoscere un altro modo preciso? – Tiny

+0

cache JPA + getter parametrizzato in un bean con ambito applicazione (o se si utilizza CDI, un metodo '@ Produces', non si sa quale sia l'equivalente di Spring a quello che non faccio Spring) – BalusC

0

È necessario recuperare l'elenco di stati in base al Paese selezionato. Hai bisogno di un valoreChangeListener per questo.

Prova questo (ho messo sia selezionare un menu di nella stessa colonna per ora)

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

in voi controller

public void handleCountrySelect(ValueChangeEvent event) 
{ 
    setStates(((Country) event.getNewValue())); 
} 
+0

In questo caso, il risposta che ho postato fa la funzione prevista ma l'espressione EL come 'row.state.country.stateTableSet' (' java.util.Set 'è stata cambiata in' java.util.List' molto tempo dopo questo post) deve essere omessa/rimossa in è interamente, dal momento che è molto costoso. In caso di EJB remoti, richiede un rapporto ** pieno di entusiasmo ** o una raccolta di raccolta costosa in JPA affinché funzioni correttamente, il che è una cosa peggiore da implementare e dovrebbe ** non ** essere utilizzato util e a meno che non sia **assolutamente necessario. – Tiny

+1

Grazie per lo sforzo. – Tiny

Problemi correlati