Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Asynchronous validators in JSF

I have a simple form containing an edit box and a button:

<h:form id="addForm">
    <h:outputLabel value="Add item here" /><br />
    <p><h:inputText id="itemInput" value="#{myController.itemToAdd}" validator="myValidator" />
    <h:message for="itemInput" /></p>
    <h:commandButton value="Add" action="#{myController.addItemToList}">
        <f:ajax execute="@form" render="@form :addedItems" />
    </h:commandButton>
</h:form>

When the user clicks on the button, some basic validation is done and if everything is OK, the item is added to a data table:

<h:panelGrid id="addedItems">
    <h:dataTable value="#{myController.itemMap.entrySet()}" var="itemMapEntry" >
        <h:column>
            <f:facet name="header">
                <h:outputText value="Items" />
            </f:facet>
            <h:commandButton value="Remove/Modify" action="#{myController.removeItemFromList(itemMapEntry.getKey())}">
                <f:ajax render=":addedItems :addForm" />
            </h:commandButton>
            <h:outputText value="#{itemMapEntry.getKey()}" />
        </h:column>
        <h:column>
            <f:facet name="action">
                <h:outputText value="" />
            </f:facet>
            <h:outputText value="#{itemMapEntry.getValue().toString()}" id="itemValidationStatus" />
        </h:column>
    </h:dataTable>
</h:panelGrid>

Now, what I want to achieve is when the user enters an item, the first, preliminary, validation is done and if the validation succeeded the item is added to the table where the list is displayed together with the validation status (e.g. validating, invalid, valid). When the item is added to the table, another, more time consuming, validation starts and when the validation completes the itemValidationStatus in the table is updated. I don't want to block the user during the time consuming validation. Instead I want to allow the user to add more items while the already added items are being validated.

I see three ways to solve this:

  1. To register some kind of JS observer that will react on an object is being updated in the managed bean

  2. To trigger the rendering from the managed bean which would make it possible to spawn a validation thread which would, upon finishing, trigger the rendering

  3. There is some way to asynchronously start the validation via JS and register a callback function that will either trigger the rendering or update the value directly

The problem is that I can't find anything online that would point me in the right direction. I am trying to solve this with plain JSF (without Prime Faces or similar tools).

EDIT:

I was suggested another question as a possible duplicate (JSF, refresh periodically a component with ajax?). I am not looking for a solution to poll peridically, but to update when the underlying data changes. And it will only change once so the obvious solution would be an event listener or similar. Of course, this could also be solved by periodical polls but that seems like very low level for what I am after.

like image 671
gabga Avatar asked Nov 10 '22 11:11

gabga


1 Answers

For now I solved it with the following changes (inspired by this JQuery related question How to trigger JSF render from jQuery):

  1. I added onevent="validateItemList" in the <f:ajax> tag of the form:

    <h:form id="addForm">
        <h:outputLabel value="Add item here" /><br />
        <p><h:inputText id="itemInput" value="#{myController.itemToAdd}" validator="myValidator" />
        <h:message for="itemInput" /></p>
        <h:commandButton value="Add" action="#{myController.addItemToList}">
            <f:ajax execute="@form" onevent="validateItemList" render="@form :addedItems" />
        </h:commandButton>
    </h:form>
    
  2. I added a hidden form with a hidden button that triggers the time consuming validation and re-renders the table:

    <h:form id="hiddenForm">
        <h:commandButton value="" action="#{myController.validateItemMap}" id="validatorButton" style="display:none">
            <f:ajax execute="@form" render=":addedItems" />
        </h:commandButton>
    </h:form>
    
  3. I added a callback JS function that "pushes" the hidden button:

    <script type="text/javascript">
        function validateItemList(data){
            if(data.status == "success"){
                document.getElementById("hiddenForm:validatorButton").click();
            }
        }
    </script>
    
like image 74
gabga Avatar answered Nov 14 '22 21:11

gabga