Jak mogę przekazać wybrany wiersz do polecenia commandLink w dataTable lub ui: powtarzaj?

99

Używam Primefaces w aplikacji JSF 2. Mam <p:dataTable>i zamiast wybierać wiersze, chcę, aby użytkownik mógł bezpośrednio wykonywać różne akcje w poszczególnych wierszach. W tym celu mam kilka <p:commandLink>znaków w ostatniej kolumnie.

Mój problem: jak mogę przekazać identyfikator wiersza do akcji uruchomionej przez łącze polecenia, aby wiedzieć, na którym wierszu należy działać? Próbowałem użyć <f:attribute>:

<p:dataTable value="#{bean.items}" var="item">
    ...
    <p:column>
        <p:commandLink actionListener="#{bean.insert}" value="insert">
            <f:attribute name="id" value="#{item.id}" />
        </p:commandLink>
    </p:column>
</p:dataTable>

Ale zawsze daje 0 - najwyraźniej zmienna wierszowa fnie jest dostępna, gdy atrybut jest renderowany (działa, gdy używam stałej wartości).

Czy ktoś ma alternatywne rozwiązanie?

Michael Borgwardt
źródło

Odpowiedzi:

215

Jeśli chodzi o przyczynę, <f:attribute>jest on specyficzny dla samego komponentu (wypełniany podczas budowania widoku), a nie dla iterowanego wiersza (wypełnianego podczas renderowania widoku).

Istnieje kilka sposobów spełnienia tego wymagania.

  1. Jeśli twój servletcontainer obsługuje co najmniej Servlet 3.0 / EL 2.2, to po prostu przekaż go jako argument metody działania / detektora UICommand komponentu lub AjaxBehaviortagu. Na przykład

     <h:commandLink action="#{bean.insert(item.id)}" value="insert" />

    W połączeniu z:

     public void insert(Long id) {
         // ...
     }

    Wymaga to jedynie zachowania modelu danych dla żądania przesłania formularza. Najlepiej jest umieścić fasolę w zakresie widoku według @ViewScoped.

    Możesz nawet przekazać cały przedmiot:

     <h:commandLink action="#{bean.insert(item)}" value="insert" />

    z:

     public void insert(Item item) {
         // ...
     }

    W kontenerach Servlet 2.5 jest to również możliwe, jeśli dostarczysz implementację EL, która to obsługuje, na przykład JBoss EL. Szczegółowe informacje na temat konfiguracji można znaleźć w tej odpowiedzi .


  2. Użyj <f:param>w UICommandkomponencie. Dodaje parametr żądania.

     <h:commandLink action="#{bean.insert}" value="insert">
         <f:param name="id" value="#{item.id}" />
     </h:commandLink>

    Jeśli Twój bean ma zakres żądania, pozwól JSF ustawić go według @ManagedProperty

     @ManagedProperty(value="#{param.id}")
     private Long id; // +setter

    Lub jeśli twój bean ma szerszy zakres lub jeśli chcesz dokładniejszej weryfikacji / konwersji, użyj <f:viewParam>w widoku docelowym, zobacz także f: viewParam vs @ManagedProperty :

     <f:viewParam name="id" value="#{bean.id}" required="true" />

    Tak czy inaczej, ma to tę zaletę, że model danych nie musi być koniecznie zachowywany w celu przesłania formularza (w przypadku, gdy komponent bean jest objęty zakresem żądań).


  3. Użyj <f:setPropertyActionListener>w UICommandkomponencie. Zaletą jest to, że eliminuje to potrzebę uzyskiwania dostępu do mapy parametrów żądania, gdy komponent bean ma szerszy zakres niż zakres żądania.

     <h:commandLink action="#{bean.insert}" value="insert">
         <f:setPropertyActionListener target="#{bean.id}" value="#{item.id}" />
     </h:commandLink>

    W połączeniu z

     private Long id; // +setter

    Będzie dostępny tylko według właściwości idw metodzie akcji. Wymaga to jedynie zachowania modelu danych dla żądania przesłania formularza. Najlepiej jest umieścić fasolę w zakresie widoku według @ViewScoped.


  4. Powiąż wartość datatable z, DataModel<E>która z kolei zawija elementy.

     <h:dataTable value="#{bean.model}" var="item">

    z

     private transient DataModel<Item> model;
    
     public DataModel<Item> getModel() {
         if (model == null) {
             model = new ListDataModel<Item>(items);
         }
         return model;
     }

    (tworzenie jej transienti leniwe tworzenie instancji w funkcji pobierającej jest obowiązkowe, gdy używasz tego w komponencie bean o zasięgu widoku lub sesji, ponieważ DataModelnie implementuje Serializable)

    Wtedy będziesz mógł uzyskać dostęp do bieżącego wiersza DataModel#getRowData()bez przekazywania czegokolwiek (JSF określa wiersz na podstawie nazwy parametru żądania klikniętego linku / przycisku polecenia).

     public void insert() {
         Item item = model.getRowData();
         Long id = item.getId();
         // ...
     }

    Wymaga to również zachowania modelu danych dla żądania przesłania formularza. Najlepiej jest umieścić fasolę w zakresie widoku według @ViewScoped.


  5. Służy Application#evaluateExpressionGet()do programowej oceny bieżącego #{item}.

     public void insert() {
         FacesContext context = FacesContext.getCurrentInstance();
         Item item = context.getApplication().evaluateExpressionGet(context, "#{item}", Item.class);
         Long id = item.getId();
         // ...
     }

Wybór sposobu zależy od wymagań funkcjonalnych i od tego, czy jedno czy drugie zapewnia więcej korzyści do innych celów. Osobiście poszedłbym dalej z numerem 1 lub, jeśli chciałbyś również obsługiwać kontenery serwletów 2.5, z numerem 2.

BalusC
źródło
1
+1, chociaż moje preferencje idą do nr 2 (jeśli ma być obsługiwane 2.5).
Bozho,
Dzięki za wyczerpującą odpowiedź. Niestety, muszę zgłosić, że # 1 był jedyną rzeczą, która działała w filtrowanym datatable primefaces (co jest dokładnie takim scenariuszem, do którego go potrzebuję). Wszyscy inni pracowali tylko na niefiltrowanym stole. Jednak widzę to bardziej jako błąd w primefaces niż w twojej odpowiedzi.
Michael Borgwardt
Czy żądanie lub widok fasoli ma określony zakres?
BalusC
2
„Filtrowane” masz na myśli, jak w tym przykładzie ? Symptomy wskazują, że akcja filtru ma miejsce tylko po stronie klienta i że model po stronie serwera nie jest obsługiwany. Nie jestem pewien, czy jest to zamierzone. Zawsze możesz zostawić zgłoszenie problemu.
BalusC
Twój post znajduje się między najbardziej przydatnymi postami, jakie kiedykolwiek czytałem. Użyłem metody 5, ponieważ jestem zmuszony używać serwletu 2.5. Moje pytanie brzmi teraz, czy możliwe jest wysłanie parametru za pomocą commandLink (jak w twoim przykładzie), ale przy użyciu ajax?
Aditzu,
11

W JSF 1.2 zostało to zrobione przez <f:setPropertyActionListener>(w ramach komponentu poleceń). W JSF 2.0 (a dokładniej EL 2.2, dzięki BalusC) można to zrobić tak:action="${filterList.insert(f.id)}

Bozho
źródło
6
Ta funkcja nie jest specyficzna dla JSF 2.0 (który sam może działać w kontenerach Servlet 2.5), ale dla EL 2.2 (który jest częścią Servlet 3.0).
BalusC
11

Na mojej stronie widoku:

<p:dataTable  ...>
<p:column>
<p:commandLink actionListener="#{inquirySOController.viewDetail}" 
               process="@this" update=":mainform:dialog_content"
           oncomplete="dlg2.show()">
    <h:graphicImage library="images" name="view.png"/>
    <f:param name="trxNo" value="#{item.map['trxNo']}"/>
</p:commandLink>
</p:column>
</p:dataTable>

fasola

 public void viewDetail(ActionEvent e) {

    String trxNo = getFacesContext().getRequestParameterMap().get("trxNo");

    for (DTO item : list) {
        if (item.get("trxNo").toString().equals(trxNo)) {
            System.out.println(trxNo);
            setSelectedItem(item);
            break;
        }
    }
}
Arfan J
źródło
-1

Dzięki tej witrynie Mkyong , jedynym rozwiązaniem, które faktycznie działało, aby przekazać parametr, było to

<h:commandLink action="#{user.editAction}">
    <f:param name="myId" value="#{param.id}" />
</h:commandLink>

z

public String editAction() {

  Map<String,String> params = 
            FacesContext.getExternalContext().getRequestParameterMap();
  String idString = params.get("myId");
  long id = Long.parseLong(idString);
  ...
}

Technicznie rzecz biorąc, nie można przejść bezpośrednio do samej metody, ale do pliku JSF request parameter map.

EpicPandaForce
źródło
1
Masz inny problem niż zadany tutaj. Chcesz zachować parametry żądania z #{param}mapy dla kolejnych żądań, a nie przekazywać dowolnego parametru. Twoje pytania i odpowiedzi są omówione na stackoverflow.com/questions/17734230
BalusC