Como posso preencher um campo de texto usando o PrimeFaces AJAX após a ocorrência de erros de validação?

Eu tenho um formulário em uma visão que executa o processamento parcial de ajax para autocomplete e localização de gmap. Meu backing bean instancia um object de entidade “Address” e é para esse object que as inputs do formulário são referenciadas:

@ManagedBean(name="mybean") @SessionScoped public class Mybean implements Serializable { private Address address; private String fullAddress; private String center = "0,0"; .... public mybean() { address = new Address(); } ... public void handleAddressChange() { String c = ""; c = (address.getAddressLine1() != null) { c += address.getAddressLine1(); } c = (address.getAddressLine2() != null) { c += ", " + address.getAddressLine2(); } c = (address.getCity() != null) { c += ", " + address.getCity(); } c = (address.getState() != null) { c += ", " + address.getState(); } fullAddress = c; addMessage(new FacesMessage(FacesMessage.SEVERITY_INFO, "Full Address", fullAddress)); try { geocodeAddress(fullAddress); } catch (MalformedURLException ex) { Logger.getLogger(Mybean.class.getName()).log(Level.SEVERE, null, ex); } catch (UnsupportedEncodingException ex) { Logger.getLogger(Mybean.class.getName()).log(Level.SEVERE, null, ex); } catch (IOException ex) { Logger.getLogger(Mybean.class.getName()).log(Level.SEVERE, null, ex); } catch (ParserConfigurationException ex) { Logger.getLogger(Mybean.class.getName()).log(Level.SEVERE, null, ex); } catch (SAXException ex) { Logger.getLogger(Mybean.class.getName()).log(Level.SEVERE, null, ex); } catch (XPathExpressionException ex) { Logger.getLogger(Mybean.class.getName()).log(Level.SEVERE, null, ex); } } private void geocodeAddress(String address) throws MalformedURLException, UnsupportedEncodingException, IOException, ParserConfigurationException, SAXException, XPathExpressionException { // prepare a URL to the geocoder address = Normalizer.normalize(address, Normalizer.Form.NFD); address = address.replaceAll("[^\\p{ASCII}]", ""); URL url = new URL(GEOCODER_REQUEST_PREFIX_FOR_XML + "?address=" + URLEncoder.encode(address, "UTF-8") + "&sensor=false"); // prepare an HTTP connection to the geocoder HttpURLConnection conn = (HttpURLConnection) url.openConnection(); Document geocoderResultDocument = null; try { // open the connection and get results as InputSource. conn.connect(); InputSource geocoderResultInputSource = new InputSource(conn.getInputStream()); // read result and parse into XML Document geocoderResultDocument = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(geocoderResultInputSource); } finally { conn.disconnect(); } // prepare XPath XPath xpath = XPathFactory.newInstance().newXPath(); // extract the result NodeList resultNodeList = null; // c) extract the coordinates of the first result resultNodeList = (NodeList) xpath.evaluate( "/GeocodeResponse/result[1]/geometry/location/*", geocoderResultDocument, XPathConstants.NODESET); String lat = ""; String lng = ""; for (int i = 0; i < resultNodeList.getLength(); ++i) { Node node = resultNodeList.item(i); if ("lat".equals(node.getNodeName())) { lat = node.getTextContent(); } if ("lng".equals(node.getNodeName())) { lng = node.getTextContent(); } } center = lat + "," + lng; } 

O preenchimento automático e o mapeamento de solicitações ajax funcionam bem antes de processar todo o formulário no envio. Se a validação falhar, o ajax ainda funciona ok, exceto para o campo fullAddress, que não pode ser atualizado na exibição, mesmo quando seu valor é definido corretamente no bean de apoio após a solicitação do ajax.

  

Se eu atualizar a página, as mensagens de erro de validação desaparecerão e o ajax completará o campo fullAddress conforme o esperado.

Outro comportamento estranho ocorre também durante a validação: Desativei a validação do bean para um campo de formulário, conforme visto no código. Este trabalho funciona bem até que outros erros de validação sejam encontrados, então, se eu reenviar o formulário, o JSF faz a validação do bean para este campo!

Acho que estou perdendo alguma coisa durante o estado de validação, mas não consigo descobrir o que há de errado com isso. Alguém sabe como depurar o ciclo de vida do JSF? Alguma ideia?

A causa do problema pode ser entendida considerando os seguintes fatos:

  • Quando a validação do JSF é bem-sucedida para um componente de input específico durante a fase de validações, o valor enviado é definido como null e o valor validado é configurado como valor local do componente de input.

  • Quando a validação JSF falha para um componente de input específico durante a fase de validações, o valor enviado é mantido no componente de input.

  • Quando pelo menos um componente de input é inválido após a fase de validações, o JSF não atualizará os valores do modelo para nenhum dos componentes de input. O JSF irá proceder diretamente para renderizar a fase de resposta.

  • Quando o JSF renderiza os componentes de input, ele primeiro testará se o valor enviado não é null e, em seguida, o exibirá, senão se o valor local não for null e, em seguida, exibirá, caso contrário, exibirá o valor do modelo.

  • Enquanto você estiver interagindo com a mesma visualização do JSF, estará lidando com o mesmo estado do componente.

Então, quando a validação falhou para um formulário em particular e você precisa atualizar os valores dos campos de input por uma ação ajax diferente ou até mesmo um formulário ajax diferente (por exemplo, preenchendo um campo dependendo de uma seleção suspensa ou o resultado de algum formulário de modal dialog, etc), então você basicamente precisa redefinir os componentes de input de destino para obter o JSF para exibir o valor do modelo que foi editado durante a ação de chamada. Caso contrário, o JSF ainda exibirá seu valor local como estava durante a falha de validação e os manterá em um estado invalidado.

Uma das maneiras em seu caso particular é coletar manualmente todos os IDs de componentes de input que devem ser atualizados / re-renderizados por PartialViewContext#getRenderIds() e, em seguida, redefinir manualmente seu estado e valores enviados por EditableValueHolder#resetValue() .

 FacesContext facesContext = FacesContext.getCurrentInstance(); PartialViewContext partialViewContext = facesContext.getPartialViewContext(); Collection renderIds = partialViewContext.getRenderIds(); for (String renderId : renderIds) { UIComponent component = viewRoot.findComponent(renderId); EditableValueHolder input = (EditableValueHolder) component; input.resetValue(); } 

Você pode fazer isso dentro do método listener handleAddressChange() ou dentro de uma implementação ActionListener reutilizável que você anexa como ao componente de input que está chamando o método listener handleAddressChange() .


Voltando ao problema concreto, imagino que isso seja um descuido na especificação JSF2. Faria muito mais sentido para nós, desenvolvedores JSF, quando a especificação JSF exigir o seguinte:

  • Quando o JSF precisa atualizar / re-renderizar um componente de input por uma solicitação ajax e esse componente de input não está incluído no processo / execução da solicitação ajax, o JSF deve reconfigurar o valor do componente de input.

Isso foi relatado como o problema JSF 1060 e uma solução completa e reutilizável foi implementada na biblioteca do OmniFaces como ResetInputAjaxActionListener (código-fonte aqui e demonstração aqui ).

Atualização 1: Desde a versão 3.4, o PrimeFaces baseou-se nesta idéia e também introduziu uma solução completa e reutilizável no sabor de .

Atualização 2: Desde a versão 4.0, o recebeu um novo atributo booleano, resetValues que também deve resolver esse tipo de problema sem a necessidade de uma tag adicional.

Atualização 3: O JSF 2.2 introduziu , seguindo a mesma ideia de . A solução agora faz parte da API JSF padrão.

Como BalusC explicou, você também pode adicionar um ouvinte reutilizável que limpe todos os valores de input, por exemplo:

 public class CleanLocalValuesListener implements ActionListener { @Override public void processAction(ActionEvent actionEvent) throws AbortProcessingException { FacesContext context = FacesContext.getCurrentInstance(); UIViewRoot viewRoot = context.getViewRoot(); List children = viewRoot.getChildren(); resetInputValues(children); } private void resetInputValues(List children) { for (UIComponent component : children) { if (component.getChildCount() > 0) { resetInputValues(component.getChildren()); } else { if (component instanceof EditableValueHolder) { EditableValueHolder input = (EditableValueHolder) component; input.resetValue(); } } } } } 

E use-o sempre que precisar limpar seus valores locais:

  

Dentro da sua tag , adicione um atributo resetValues="true" para dizer à view para buscar dados novamente, desta forma, deve ser capaz de corrigir o seu problema.