Como posso passar a linha selecionada para commandLink dentro de dataTable ou ui: repeat?

99

Estou usando Primefaces em um aplicativo JSF 2. Eu tenho um <p:dataTable>e, em vez de selecionar linhas, quero que o usuário possa executar diretamente várias ações em linhas individuais. Para isso, tenho vários <p:commandLink>s na última coluna.

Meu problema: como posso passar um ID de linha para a ação iniciada pelo link de comando para que eu saiba em qual linha atuar? Tentei usar um <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>

Mas sempre resulta em 0 - aparentemente, a variável de linha fnão está disponível quando o atributo é renderizado (funciona quando eu uso um valor fixo).

Alguém tem uma solução alternativa?

Michael Borgwardt
fonte

Respostas:

215

Quanto à causa, <f:attribute>é específico para o próprio componente (preenchido durante o tempo de construção da visualização), não para a linha iterada (preenchida durante o tempo de renderização da visualização).

Existem várias maneiras de atingir o requisito.

  1. Se o seu servletcontainer suporta um mínimo de Servlet 3.0 / EL 2.2, apenas passe-o como um argumento do método de ação / ouvinte do UICommand componente ou AjaxBehaviortag. Por exemplo

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

    Em combinação com:

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

    Isso requer apenas que o modelo de dados seja preservado para a solicitação de envio do formulário. O melhor é colocar o bean no escopo de visualização@ViewScoped .

    Você pode até mesmo passar todo o objeto do item:

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

    com:

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

    Em contêineres Servlet 2.5, isso também é possível se você fornecer uma implementação EL que suporte isso, como JBoss EL. Para detalhes de configuração, veja esta resposta .


  2. Use <f:param>no UICommandcomponente. Ele adiciona um parâmetro de solicitação.

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

    Se o seu bean tiver escopo de solicitação, deixe o JSF configurá-lo por @ManagedProperty

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

    Ou se o seu bean tem um escopo mais amplo ou se você deseja uma validação / conversão mais refinada, use <f:viewParam>na visualização de destino, consulte também f: viewParam vs @ManagedProperty :

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

    De qualquer forma, isso tem a vantagem de que o modelo de dados não precisa necessariamente ser preservado para o envio do formulário (para o caso de seu bean ter escopo de solicitação).


  3. Use <f:setPropertyActionListener>no UICommandcomponente. A vantagem é que isso elimina a necessidade de acessar o mapa de parâmetros da solicitação quando o bean tem um escopo mais amplo do que o escopo da solicitação.

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

    Em combinação com

     private Long id; // +setter

    Estará disponível apenas por propriedade idno método de ação. Isso requer apenas que o modelo de dados seja preservado para a solicitação de envio do formulário. O melhor é colocar o bean no escopo de visualização por @ViewScoped.


  4. Vincule o valor da tabela de dados a, DataModel<E>que por sua vez envolve os itens.

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

    com

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

    (torná-lo transiente instanciá-lo preguiçosamente no getter é obrigatório quando você está usando isso em uma visão ou bean com escopo de sessão, uma vez DataModelque não implementaSerializable )

    Em seguida, você poderá acessar a linha atual DataModel#getRowData()sem passar nada (o JSF determina a linha com base no nome do parâmetro de solicitação do link / botão de comando clicado).

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

    Isso também requer que o modelo de dados seja preservado para a solicitação de envio do formulário. O melhor é colocar o bean no escopo de visualização por @ViewScoped.


  5. Use Application#evaluateExpressionGet()para avaliar programaticamente o atual #{item}.

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

O caminho a ser escolhido depende dos requisitos funcionais e se um ou outro oferece mais vantagens para outros fins. Eu pessoalmente iria em frente com o # 1 ou, quando você gostaria de oferecer suporte a containers servlet 2.5 também, com o # 2.

BalusC
fonte
1
+1, embora minha preferência vá para # 2 (se 2,5 tiver que ser suportado).
Bozho
Obrigado pela resposta exaustiva. Infelizmente, tenho que relatar que # 1 foi a única coisa que funcionou em uma tabela de dados filtrada de faces primárias (que é exatamente o cenário para o qual preciso). Todos os outros trabalharam apenas em uma mesa não filtrada. Eu vejo isso mais como um bug em faces primárias do que em sua resposta, no entanto.
Michael Borgwardt de
A solicitação ou visualização do bean tem escopo?
BalusC de
2
Com "filtrado" você quer dizer como neste exemplo de demonstração ? Os sintomas indicam que a ação do filtro ocorre apenas no lado do cliente e que o modelo no lado do servidor não é mantido. Não tenho certeza se isso é intencional. Você sempre pode deixar um relatório de problema.
BalusC de
Sua postagem está entre as mais úteis que já li. Usei o método 5 porque sou forçado a usar o servlet 2.5. Minha dúvida agora é se é possível enviar um parâmetro com commandLink (como no seu exemplo), mas usando ajax?
Aditzu
11

No JSF 1.2, isso era feito por <f:setPropertyActionListener>(dentro do componente de comando). No JSF 2.0 (EL 2.2 para ser preciso, graças ao BalusC) é possível fazer assim:action="${filterList.insert(f.id)}

Bozho
fonte
6
Este recurso não é específico do JSF 2.0 (que pode ser executado sozinho em contêineres Servlet 2.5), mas do EL 2.2 (que faz parte do Servlet 3.0).
BalusC de
11

Na minha página de visualização:

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

feijão de apoio

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

Graças a este site da Mkyong , a única solução que realmente funcionou para passarmos um parâmetro foi esta

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

com

public String editAction() {

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

Tecnicamente, você não pode passar diretamente para o método em si, mas para o JSF request parameter map.

EpicPandaForce
fonte
1
Você tem um problema diferente do que perguntado aqui. Você deseja reter os parâmetros de solicitação do #{param}mapa para solicitações subsequentes, não para passar um parâmetro arbitrário. Suas perguntas e respostas são abordadas em stackoverflow.com/questions/17734230
BalusC