Wednesday, July 10, 2013

immediate action for data under UIData

Suppose I have a list of integers. I want to perform CRUD(create, retrieve, update and delete) operations on them. I use UIData structure to design the UI.  The final UI is like the figure below.


The facelet xhtml is like this
<h:panelGroup layout="block" id="duppanel">
        <ui:repeat value="#{execCtx.prop.valueHolders}" var="valueHolder" varStatus="status">
            <h:inputText value="#{valueHolder.value}">
                 <f:validateLongRange maximum="50"></f:validateLongRange>
            </h:inputText>
            <fi:button styleClass="text-warning" immediate="true"
                actionListener="#{execCtx.prop.removeSimpleValue(status.index)}">
                        <i class="icon-remove"></i>
                        <f:ajax execute=":#{cc.clientId}:duppanel"
                            render=":#{cc.clientId}:duppanel"></f:ajax>
                    </fi:button>
        </ui:repeat>
    </h:panelGroup>
    <div>
        
         <fi:button>
                <i class="icon-plus"></i>
                <f:ajax execute="@this duppanel" render="duppanel"
                    listener="#{execCtx.prop.addSimpleValue}" immediate="true"></f:ajax>
            </fi:button>
           
    </div>


The remove button and add button are backed up by ajax call.  Suppose before I clicked the "add/remove" button, I changed the value 35 to 500, which will fail the validation. I'd like
  1. have the value I filled. It is 500 (instead of 35) in this case.
  2. I do not want validation.
Since I do not want to validation, I have "immediate" property set to true for both add and remove button.

  Add case

public void addSimpleValue(javax.faces.event.AjaxBehaviorEvent event)
            throws javax.faces.event.AbortProcessingException
    {
        getValues().add(null);
        FacesContext.getCurrentInstance().renderResponse();
       
    }
The add action above will execute at APPLY_REQUEST_VALUE phase since I set the immediate to true.  In order to skip validation, I use this line of code

 FacesContext.getCurrentInstance().renderResponse();

When "add" button is clicked, all values added or edited are lost. This is not how input behaves when it is out of UIData.
When input is out of UIData, the submitted value is kept in the UInput component itself. At render time, this value is re-displayed.

However, each input inside of UIData is a virtual component. The submitted value for the virtual component is kept at the UIData itself.  At render time, the saved submitted value at UIData is USUALLY wiped out. The values from underlying data model are re-displayed.  Since validation and update model are skipped in our case, the submitted values are never pushed to model layer. So the ORIGINAL value from data model instead of submitted value are re-displayed. This is why any added/edited value are lost.

To correct this problem, first we still keep immediate true for add button so 'add' operation always is executed whether validation passes or not.

Secondly, we remove this line
FacesContext.getCurrentInstance().renderResponse();
from action code. The validation and update model phase will be executed. Our submitted value will be pushed to model layer and re-displayed at render time.

What occurs if there are validation error and submitted value are not pushed to model layer? UIData takes care of this case. The submitted value will not be wiped out  and re-displayed. How can UIData know some validation fails?  Very strange, mojarra checkes whether there are error messages in FacesContext or not. If there are, UIData assumes the validation failed.

Remove case

public void removeSimpleValue(int index)

            throws javax.faces.event.AbortProcessingException

    {

        getValues().remove(index);
      //  FacesContext.getCurrentInstance().renderResponse();

    }  
As it is in "add", we should comment out the red line code so validation and update model can take place. Moreover, we should set immediate to false for the remove button. If we remove the one record from underlying data model at APPLY_REQUEST_VALUE, validation/update model phase will still process the submitted data as if the record still exists. Data could be corrupted in this case.