I have a form in a view which performs ajax partial processing for autocompletion and gmap localization. My backing bean instantiates an entity object "Address" and is to this object that the form's inputs are referenced:
我在视图中有一个表单,它执行自动完成和gmap本地化的ajax部分处理。我的支持bean实例化了一个实体对象“地址”,并指向该表单的输入所引用的对象:
@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;
}
Autocompletion and map ajax requests work fine before I process the whole form on submit. If validation fails, ajax still works ok except for the field fullAddress which is unable to update in the view, even when it's value is correctly set on the backing bean after the ajax request.
在提交整个表单之前,自动完成和映射ajax请求可以正常工作。如果验证失败,除了字段fullAddress不能在视图中更新之外,ajax仍然可以正常工作,即使在ajax请求之后,它的值被正确地设置在后台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"/>
If I refresh the page, validation error messages disappear and the ajax completes fullAddress field as expected.
如果我刷新页面,验证错误消息将消失,ajax按照预期完成fullAddress字段。
Another weird behavior occurs also during validation: I have disabled bean validation for a form field, as seen on the code. This work alright until other validation errors are found, then, if I resubmit the form, JSF makes bean validation for this field!
另一个奇怪的行为也发生在验证过程中:我禁用了表单字段的bean验证,如代码中所示。在找到其他验证错误之前,这是可以工作的,然后,如果我重新提交表单,JSF将对该字段进行bean验证!
I guess I am missing something in during the validation state but I can't figure out what's wrong with it. Does anyone knows how to debug JSF life cycle? Any ideas?
我想我在验证状态中漏掉了一些东西,但是我不知道它有什么问题。有人知道如何调试JSF生命周期吗?什么好主意吗?
3 个解决方案
#1
78
The cause of the problem can be understood by considering the following facts:
问题的原因可以通过以下事实来理解:
-
When JSF validation succeeds for a particular input component during the validations phase, then the submitted value is set to
null
and the validated value is set as local value of the input component.在验证阶段,当JSF对特定输入组件的验证成功时,提交的值被设置为null,验证的值被设置为输入组件的本地值。
-
When JSF validation fails for a particular input component during the validations phase, then the submitted value is kept in the input component.
当验证阶段中的特定输入组件的JSF验证失败时,提交的值将保存在输入组件中。
-
When at least one input component is invalid after the validations phase, then JSF will not update the model values for any of the input components. JSF will directly proceed to render response phase.
当至少一个输入组件在验证阶段之后无效,那么JSF将不会更新任何输入组件的模型值。JSF将直接开始呈现响应阶段。
-
When JSF renders input components, then it will first test if the submitted value is not
null
and then display it, else if the local value is notnull
and then display it, else it will display the model value.当JSF呈现输入组件时,它将首先测试提交的值是否为null,然后显示它,否则如果本地值不是null,然后显示它,否则它将显示模型值。
-
As long as you're interacting with the same JSF view, you're dealing with the same component state.
只要您与相同的JSF视图进行交互,就会处理相同的组件状态。
So, when the validation has failed for a particular form submit and you happen to need to update the values of input fields by a different ajax action or even a different ajax form (e.g. populating a field depending on a dropdown selection or the result of some modal dialog form, etc), then you basically need to reset the target input components in order to get JSF to display the model value which was edited during invoke action. Otherwise JSF will still display its local value as it was during the validation failure and keep them in an invalidated state.
所以,当验证特定表单提交失败,你需要更新输入字段的值不同的ajax行为,甚至不同的ajax表单(例如填充字段根据下拉选择或一些模态对话框形式的结果,等等),那么你基本上需要重置目标为了得到JSF输入组件显示的模型值编辑在调用操作。否则,JSF仍然会像在验证失败时一样显示它的本地值,并将它们保持在无效状态。
One of the ways in your particular case is to manually collect all IDs of input components which are to be updated/re-rendered by PartialViewContext#getRenderIds()
and then manually reset its state and submitted values by EditableValueHolder#resetValue()
.
在您的特定情况下,一种方法是手动收集输入组件的所有id,这些id将由PartialViewContext#getRenderIds()更新/重新呈现,然后由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();
}
You could do this inside the handleAddressChange()
listener method, or inside an reuseable ActionListener
implementation which you attach as <f:actionListener>
to the input component which is calling the handleAddressChange()
listener method.
您可以在handleAddressChange() listener方法中执行此操作,也可以在可重用的ActionListener实现中执行此操作,将其附加为
Coming back to the concrete problem, I'd imagine that this is an oversight in the JSF2 specification. It would make much more sense to us, JSF developers, when the JSF specification mandates the following:
回到具体的问题上,我认为这是JSF2规范中的一个疏忽。对于JSF开发人员来说,当JSF规范要求我们执行以下操作时,这将更有意义:
- When JSF needs to update/re-render an input component by an ajax request, and that input component is not included in the process/execute of the ajax request, then JSF should reset the input component's value.
- 当JSF需要通过ajax请求更新/重新呈现输入组件,并且该输入组件不包含在ajax请求的过程/执行中,那么JSF应该重置输入组件的值。
This has been reported as JSF issue 1060 and a complete and reuseable solution has been implemented in the OmniFaces library as ResetInputAjaxActionListener
(source code here and showcase demo here).
据报道,这是JSF第1060期,并且在OmniFaces库中实现了一个完整的、可重用的解决方案,名为ResetInputAjaxActionListener(源代码在这里,演示在这里)。
Update 1: Since version 3.4, PrimeFaces has based on this idea also introduced a complete and reusable solution in flavor of <p:resetInput>
.
更新1:自从3.4版以来,PrimeFaces基于这一理念,也引入了一种完整且可重用的解决方案,其风格为
Update 2: Since version 4.0, the <p:ajax>
got a new boolean attribute resetValues
which should also solve this kind of problem without the need for an additional tag.
更新2:自从4.0版本以来,
Update 3: JSF 2.2 introduced <f:ajax resetValues>
, following the same idea as <p:ajax resetValues>
. The solution is now part of standard JSF API.
更新3:JSF 2.2引入了
#2
2
As BalusC explained, you can also add a reusable listener that cleans all input values, for instance:
正如BalusC所解释的,您还可以添加一个可重用的侦听器来清除所有输入值,例如:
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();
}
}
}
}
}
And use it whenever you need to clean your local values:
当你需要清理你的本地价值观时,请使用它:
<f:actionListener type="com.cacib.bean.CleanLocalValuesListener"/>
#3
2
Inside your tag <p:ajax/>
, please add an attribute resetValues="true"
to tell the view to fetch data again, in this way should be able to fix your problem.
在标签
#1
78
The cause of the problem can be understood by considering the following facts:
问题的原因可以通过以下事实来理解:
-
When JSF validation succeeds for a particular input component during the validations phase, then the submitted value is set to
null
and the validated value is set as local value of the input component.在验证阶段,当JSF对特定输入组件的验证成功时,提交的值被设置为null,验证的值被设置为输入组件的本地值。
-
When JSF validation fails for a particular input component during the validations phase, then the submitted value is kept in the input component.
当验证阶段中的特定输入组件的JSF验证失败时,提交的值将保存在输入组件中。
-
When at least one input component is invalid after the validations phase, then JSF will not update the model values for any of the input components. JSF will directly proceed to render response phase.
当至少一个输入组件在验证阶段之后无效,那么JSF将不会更新任何输入组件的模型值。JSF将直接开始呈现响应阶段。
-
When JSF renders input components, then it will first test if the submitted value is not
null
and then display it, else if the local value is notnull
and then display it, else it will display the model value.当JSF呈现输入组件时,它将首先测试提交的值是否为null,然后显示它,否则如果本地值不是null,然后显示它,否则它将显示模型值。
-
As long as you're interacting with the same JSF view, you're dealing with the same component state.
只要您与相同的JSF视图进行交互,就会处理相同的组件状态。
So, when the validation has failed for a particular form submit and you happen to need to update the values of input fields by a different ajax action or even a different ajax form (e.g. populating a field depending on a dropdown selection or the result of some modal dialog form, etc), then you basically need to reset the target input components in order to get JSF to display the model value which was edited during invoke action. Otherwise JSF will still display its local value as it was during the validation failure and keep them in an invalidated state.
所以,当验证特定表单提交失败,你需要更新输入字段的值不同的ajax行为,甚至不同的ajax表单(例如填充字段根据下拉选择或一些模态对话框形式的结果,等等),那么你基本上需要重置目标为了得到JSF输入组件显示的模型值编辑在调用操作。否则,JSF仍然会像在验证失败时一样显示它的本地值,并将它们保持在无效状态。
One of the ways in your particular case is to manually collect all IDs of input components which are to be updated/re-rendered by PartialViewContext#getRenderIds()
and then manually reset its state and submitted values by EditableValueHolder#resetValue()
.
在您的特定情况下,一种方法是手动收集输入组件的所有id,这些id将由PartialViewContext#getRenderIds()更新/重新呈现,然后由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();
}
You could do this inside the handleAddressChange()
listener method, or inside an reuseable ActionListener
implementation which you attach as <f:actionListener>
to the input component which is calling the handleAddressChange()
listener method.
您可以在handleAddressChange() listener方法中执行此操作,也可以在可重用的ActionListener实现中执行此操作,将其附加为
Coming back to the concrete problem, I'd imagine that this is an oversight in the JSF2 specification. It would make much more sense to us, JSF developers, when the JSF specification mandates the following:
回到具体的问题上,我认为这是JSF2规范中的一个疏忽。对于JSF开发人员来说,当JSF规范要求我们执行以下操作时,这将更有意义:
- When JSF needs to update/re-render an input component by an ajax request, and that input component is not included in the process/execute of the ajax request, then JSF should reset the input component's value.
- 当JSF需要通过ajax请求更新/重新呈现输入组件,并且该输入组件不包含在ajax请求的过程/执行中,那么JSF应该重置输入组件的值。
This has been reported as JSF issue 1060 and a complete and reuseable solution has been implemented in the OmniFaces library as ResetInputAjaxActionListener
(source code here and showcase demo here).
据报道,这是JSF第1060期,并且在OmniFaces库中实现了一个完整的、可重用的解决方案,名为ResetInputAjaxActionListener(源代码在这里,演示在这里)。
Update 1: Since version 3.4, PrimeFaces has based on this idea also introduced a complete and reusable solution in flavor of <p:resetInput>
.
更新1:自从3.4版以来,PrimeFaces基于这一理念,也引入了一种完整且可重用的解决方案,其风格为
Update 2: Since version 4.0, the <p:ajax>
got a new boolean attribute resetValues
which should also solve this kind of problem without the need for an additional tag.
更新2:自从4.0版本以来,
Update 3: JSF 2.2 introduced <f:ajax resetValues>
, following the same idea as <p:ajax resetValues>
. The solution is now part of standard JSF API.
更新3:JSF 2.2引入了
#2
2
As BalusC explained, you can also add a reusable listener that cleans all input values, for instance:
正如BalusC所解释的,您还可以添加一个可重用的侦听器来清除所有输入值,例如:
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();
}
}
}
}
}
And use it whenever you need to clean your local values:
当你需要清理你的本地价值观时,请使用它:
<f:actionListener type="com.cacib.bean.CleanLocalValuesListener"/>
#3
2
Inside your tag <p:ajax/>
, please add an attribute resetValues="true"
to tell the view to fetch data again, in this way should be able to fix your problem.
在标签