Minggu, 13 April 2014

Reset non-processed input components on ajax update


Introduction


When JSF validation has failed for a particular form submit and you happen to need to update the values of invalidated input fields later 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, or clearing out all values with some clear action, 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.



This problem is the easiest to understand if you see it yourself. Use this simple testcase view:






id="form1">

input1: Enter something (but don't enter "Updated!").



id="input1" value="#{bean.input1}" required="true"
styleClass="#{component.valid ? '' : 'error'}" />
for="input1" errorClass="error" />


input2: Leave this field empty or enter a non-numeric value to cause a validation failure.



id="input2" value="#{bean.input2}" required="true"
styleClass="#{component.valid ? '' : 'error'}" />
for="input2" errorClass="error" />


Press "submit" and then "update". The "update" simulates changing model values externally.



value="Submit" action="#{bean.submit}">
execute="@form" render="@form" />

value="Update" action="#{bean.update}">
execute="@this" render="@form" />







With this bean:





@ManagedBean
@ViewScoped
public class Bean {

private String input1;
private Integer input2;

public void submit() {
//
}

public void update() {
input1 = "Updated!";
input2 = 42;
}

// Getters/setters.
}



Problem: input1 is not updated with text "Updated!" and input2 is still marked invalid! The problem can be understood by the following JSF 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.

  • When JSF validation fails for a particular input component during the validations phase, then the submitted value is kept in the input component.

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

  • 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 not null and then display it, else it will display the model value.

  • As long as you're interacting with the same JSF view, you're dealing with the same component state.



Ideally, 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. This has been requested as JSF spec issue 1060.



OmniFaces to the rescue


OmniFaces has recently got a ResetInputAjaxActionListener which solves exactly this problem (source code here).



If you register it as in faces-config.xml, or add it as to the update command button as follows,






value="Update" action="#{bean.update}">
execute="@this" render="@form" />
type="org.omnifaces.eventlistener.ResetInputAjaxActionListener" />





Then this problem will completely disappear. The principle is rather simple, here's a (simplified) extract of relevance from its source code:






FacesContext context = FacesContext.getCurrentInstance();
PartialViewContext partialViewContext = context.getPartialViewContext();

if (!partialViewContext.isAjaxRequest()) {
return; // Ignore synchronous requests. All executed and rendered inputs are the same anyway.
}

Collection renderIds = partialViewContext.getRenderIds();

if (renderIds.isEmpty()) {
return; // Nothing to render, thus also nothing to reset.
}

UIViewRoot viewRoot = context.getViewRoot();
Set inputs = new HashSet();

// First find all to be rendered inputs and add them to the set.
for (String renderId : renderIds) {
findAndAddEditableValueHolders(viewRoot.findComponent(renderId), inputs);
}

// Then find all executed inputs and remove them from the set.
for (String executeId : partialViewContext.getExecuteIds()) {
findAndRemoveEditableValueHolders(viewRoot.findComponent(executeId), inputs);
}

// The set now contains inputs which are to be rendered, but which are not been executed. Reset them.
for (EditableValueHolder input : inputs) {
input.resetValue();
}



Source:http://balusc.blogspot.com/2012/03/reset-non-processed-input-components-on.html

Tidak ada komentar:

Posting Komentar