validation错误发生后,如何使用PrimeFaces AJAX填充文本字段?
我有一个窗体在视图中执行自动完成和gmap本地化ajax部分处理。 我的支持bean实例化一个实体对象“地址”,是这个对象的forms的input被引用:
@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; }
在我提交整个表单之前,自动完成和映射ajax请求正常工作。 如果validation失败,ajax仍然可以正常工作,除了在视图中无法更新的字段fullAddress,即使在ajax请求之后在backing bean上正确设置了值。
<h:outputLabel for="address1" value="#{label.addressLine1}"/> <p:inputText required="true" id="address1" value="#{mybean.address.addressLine1}"> <p:ajax update="latLng,fullAddress" listener="#{mybean.handleAddressChange}" process="@this"/> </p:inputText> <p:message for="address1"/> <h:outputLabel for="address2" value="#{label.addressLine2}"/> <p:inputText id="address2" value="#{mybean.address.addressLine2}" label="#{label.addressLine2}"> <f:validateBean disabled="#{true}" /> <p:ajax update="latLng,fullAddress" listener="#{mybean.handleAddressChange}" process="address1,@this"/> </p:inputText> <p:message for="address2"/> <h:outputLabel for="city" value="#{label.city}"/> <p:inputText required="true" id="city" value="#{mybean.address.city}" label="#{label.city}"> <p:ajax update="latLng,fullAddress" listener="#{mybean.handleAddressChange}" process="address1,address2,@this"/> </p:inputText> <p:message for="city"/> <h:outputLabel for="state" value="#{label.state}"/> <p:autoComplete id="state" value="#{mybean.address.state}" completeMethod="#{mybean.completeState}" selectListener="#{mybean.handleStateSelect}" onSelectUpdate="latLng,fullAddress,growl" required="true"> <p:ajax process="address1,address2,city,@this"/> </p:autoComplete> <p:message for="state"/> <h:outputLabel for="fullAddress" value="#{label.fullAddress}"/> <p:inputText id="fullAddress" value="#{mybean.fullAddress}" style="width: 300px;" label="#{label.fullAddress}"/> <p:commandButton value="#{label.locate}" process="@this,fullAddress" update="growl,latLng" actionListener="#{mybean.findOnMap}" id="findOnMap"/> <p:gmap id="latLng" center="#{mybean.center}" zoom="18" type="ROADMAP" style="width:600px;height:400px;margin-bottom:10px;" model="#{mybean.mapModel}" onPointClick="handlePointClick(event);" pointSelectListener="#{mybean.onPointSelect}" onPointSelectUpdate="growl" draggable="true" markerDragListener="#{mybean.onMarkerDrag}" onMarkerDragUpdate="growl" widgetVar="map"/> <p:commandButton id="register" value="#{label.register}" action="#{mybean.register}" ajax="false"/>
如果刷新页面,validation错误消息消失,并且ajax按预期完成fullAddress字段。
在validation过程中还会出现另一个奇怪的行为:我已经禁用了表单字段的beanvalidation,如代码所示。 这个工作正常,直到find其他validation错误,然后,如果我重新提交表单,JSF使这个领域的beanvalidation!
我想我在validation状态中错过了一些东西,但我无法弄清楚它有什么问题。 有谁知道如何debuggingJSF的生命周期? 有任何想法吗?
通过考虑以下事实可以理解问题的原因:
-
在validation阶段,当JSFvalidation成功执行特定的input组件时,提交的值将设置为
null
,并将validation的值设置为input组件的本地值。 -
在validation阶段,当特定input组件的JSFvalidation失败时,提交的值将保留在input组件中。
-
当validation阶段后,至less有一个input组件是无效的,那么JSF将不会更新任何input组件的模型值。 JSF将直接进行响应阶段。
-
当JSF呈现input组件时,它将首先testing提交的值是否为
null
,然后显示它,否则如果本地值不为null
然后显示它,否则将显示模型值。 -
只要您使用相同的JSF视图进行交互,就可以处理相同的组件状态。
所以,当validation失败了一个特定的表单提交,你碰巧需要通过不同的ajax动作或甚至不同的ajaxforms更新input字段的值(例如根据下拉select填充字段或某些modal dialogforms等),那么你基本上需要重置目标input组件,以便让JSF显示在调用动作期间编辑的模型值。 否则,JSF将仍然显示其validation失败期间的本地值,并使其保持无效状态。
其中一种方法是手动收集由PartialViewContext#getRenderIds()
更新/重新呈现的input组件的所有ID,然后通过EditableValueHolder#resetValue()
手动重置其状态并提交值。
FacesContext facesContext = FacesContext.getCurrentInstance(); PartialViewContext partialViewContext = facesContext.getPartialViewContext(); Collection<String> renderIds = partialViewContext.getRenderIds(); for (String renderId : renderIds) { UIComponent component = viewRoot.findComponent(renderId); EditableValueHolder input = (EditableValueHolder) component; input.resetValue(); }
您可以在handleAddressChange()
侦听器方法内部执行此操作,也可以在作为<f:actionListener>
附加到正在调用handleAddressChange()
侦听器方法的input组件附加的可<f:actionListener>
ActionListener
实现内部执行此操作。
回到具体的问题,我想这是JSF2规范中的一个疏忽。 当JSF规范要求如下时,对我们JSF开发人员来说更有意义:
- 当JSF需要通过ajax请求更新/重新呈现input组件,并且该input组件未包含在ajax请求的进程/执行中时,JSF应重置input组件的值。
这已经被报告为JSF问题1060 ,并且已经在OmniFaces库中以ResetInputAjaxActionListener
( 这里的源代码和showcase demo 在这里 )实现了完整且可重复使用的解决scheme。
更新1:从版本3.4开始,PrimeFaces基于这个想法也引入了一个完整且可重用的<p:resetInput>
风格的解决scheme。
更新2:自4.0版本以来, <p:ajax>
得到了一个新的布尔属性resetValues
,它也应该解决这种问题,而不需要额外的标签。
更新3: JSF 2.2引入了<f:ajax resetValues>
,遵循与<p:ajax resetValues>
相同的概念。 该解决scheme现在是标准JSF API的一部分。
正如BalusC所解释的那样,您还可以添加一个可重用的侦听器来清除所有input值,例如:
public class CleanLocalValuesListener implements ActionListener { @Override public void processAction(ActionEvent actionEvent) throws AbortProcessingException { FacesContext context = FacesContext.getCurrentInstance(); UIViewRoot viewRoot = context.getViewRoot(); List<UIComponent> children = viewRoot.getChildren(); resetInputValues(children); } private void resetInputValues(List<UIComponent> children) { for (UIComponent component : children) { if (component.getChildCount() > 0) { resetInputValues(component.getChildren()); } else { if (component instanceof EditableValueHolder) { EditableValueHolder input = (EditableValueHolder) component; input.resetValue(); } } } } }
只要你需要清理你的本地值,就使用它:
<f:actionListener type="com.cacib.bean.CleanLocalValuesListener"/>
在你的标签<p:ajax/>
,请添加一个属性resetValues="true"
来告诉视图再次获取数据,这样应该可以解决你的问题。