Using a4j: support for updating the model and viewing, ready for the next button / submitting actions

Problem

We have a swing-based front-end for an enterprise application, and now it implements (at the moment a simpler) JSF / Seam / Richfaces interface for it.

Some pages contain fields that, when edited, should lead to changes in other fields. We need this change to be displayed to the user immediately (i.e. they did not need to press a button or anything else).

I successfully implemented this with h:commandButton and adding onchange="submit()" to the fields, which cause other fields to change. Thus, the form view appears when they edit the field, and the rest of the fields are updated as a result.

This works fine, but especially when the server is under heavy load (which often happens), the presentation form can take a lot of time, and our users continue to edit the fields at the same time, which are then returned when responses to processed onchange="submit()" requests onchange="submit()" .

To solve this problem, I was hoping to achieve something where:

  • When editing a field, if necessary, only this field is processed , and only those fields that it changes are changed again (so that any other changes made by the user at the same time are not lost).
  • At the click of a button, all fields are processed and re-displayed as usual.

Solution (unstable)

Well, I think it would be easier to show a little of my page first. Please note that this is only an excerpt and that on some pages there will be many fields and many buttons.

 <a4j:form id="mainForm"> ... <a4j:commandButton id="calculateButton" value="Calculate" action="#{illustrationManager.calculatePremium()}" reRender="mainForm" /> ... <h:outputLabel for="firstName" value=" First Name" /> <h:inputText id="firstName" value="#{life.firstName}" /> ... <h:outputLabel for="age" value=" Age" /> <h:inputText id="age" value="#{life.age}"> <f:convertNumber type="number" integerOnly="true" /> <a4j:support event="onchange" ajaxSingle="true" reRender="dob" /> </h:inputText> <h:outputLabel for="dob" value=" DOB" /> <h:inputText id="dob" value="#{life.dateOfBirth}" styleClass="date"> <f:convertDateTime pattern="dd/MM/yyyy" timeZone="#{userPreference.timeZone}" /> <a4j:support event="onchange" ajaxSingle="true" reRender="age,dob" /> </h:inputText> ... </a4j:form> 

Changing the age value changes the dob value in the model and vice versa. I use reRender="dob" and reRender="age,dob" to display the changed values ​​from the model. It works great.

I also use the global queue to ensure the order of AJAX requests.

However, the onchange event onchange not occur until I click somewhere else on the page or click on a tab or something else. This causes problems when the user enters a value, such as age , and then clicks calculateButton without , by clicking elsewhere on the page or by clicking a tab.

The onchange event seems to be the first to happen, as I see the dob change, but these two values ​​are then returned when the calculateButton query is executed.

So, finally, to the question: is there a way to make sure that the model and view are completely updated before the calculateButton request is made so that it does not return them? Why is this not happening as I use the AJAX queue?

Workarounds

There are two strategies to get around this limitation, but they both require bloat in Facelet code, which can confuse other developers and cause other problems.

Workaround 1: Using a4j: support

This strategy is as follows:

  • Add the ajaxSingle="true" attribute to calculateButton .
  • Add the a4j:support tag with the ajaxSingle="true" attribute in firstName .

The first step ensures that calculateButton does not overwrite values ​​in age or dob , since it no longer processes them. Unfortunately, it has a side effect that it no longer processes firstName . The second step is added to counter this side effect by processing firstName before pressing calculateButton .

Keep in mind that there can be more than 20 fields, such as firstName . The user filling out the form can then call 20+ requests to the server! As I mentioned, this is also bloated, which can confuse other developers.

Workaround 2: Using the process list

Thanks to @DaveMaple and @MaxKatz for suggesting this strategy, it looks like this:

  • Add the ajaxSingle="true" attribute to calculateButton .
  • Add the process="firstName" attribute to calculateButton .

The first step is achieved in the same way as in the first workaround, but has the same side effect. This time, the second step ensures that firstName processed using calculateButton when clicked.

Again, keep in mind that 20 fields, such as firstName , can be included in this list. As I mentioned, this is also bloated, which can confuse other developers, especially since the list should contain some fields, but not others.

Age and DOB Setters and Getters (just in case they are the cause of the problem)

 public Number getAge() { Long age = null; if (dateOfBirth != null) { Calendar epochCalendar = Calendar.getInstance(); epochCalendar.setTimeInMillis(0L); Calendar dobCalendar = Calendar.getInstance(); dobCalendar.setTimeInMillis(new Date().getTime() - dateOfBirth.getTime()); dobCalendar.add(Calendar.YEAR, epochCalendar.get(Calendar.YEAR) * -1); age = new Long(dobCalendar.get(Calendar.YEAR)); } return (age); } public void setAge(Number age) { if (age != null) { // This only gives a rough date of birth at 1/1/<this year minus <age> years>. Calendar calendar = Calendar.getInstance(); calendar.set(calendar.get(Calendar.YEAR) - age.intValue(), Calendar.JANUARY, 1, 0, 0, 0); setDateOfBirth(calendar.getTime()); } } public Date getDateOfBirth() { return dateOfBirth; } public void setDateOfBirth(Date dateOfBirth) { if (notEqual(this.dateOfBirth, dateOfBirth)) { // If only two digits were entered for the year, provide useful defaults for the decade. Calendar calendar = Calendar.getInstance(); calendar.setTime(dateOfBirth); if (calendar.get(Calendar.YEAR) < 50) { // If the two digits entered are in the range 0-49, default the decade 2000. calendar.set(Calendar.YEAR, calendar.get(Calendar.YEAR) + 2000); } else if (calendar.get(Calendar.YEAR) < 100) { // If the two digits entered are in the range 50-99, default the decade 1900. calendar.set(Calendar.YEAR, calendar.get(Calendar.YEAR) + 1900); } dateOfBirth = calendar.getTime(); this.dateOfBirth = dateOfBirth; changed = true; } } 
+6
source share
4 answers

What is the area of ​​your bean? When the button is executed, this is a new request, and if your bean is in the request area, then the previous values ​​will disappear.

0
source

So, finally, to the question: is there a way to make sure that the model and view are completely updated before the calculateButton request is created so that it does not return them?

What you can do is disable the submit button from the moment your ajax request is launched until your ajax request is completed. This will effectively prevent the submit button from being pressed until it resolves:

 <a4j:support event="onblur" ajaxSingle="true" onsubmit="jQuery('#mainForm\\:calculateButton').attr('disabled', 'disabled');" oncomplete="jQuery('#mainForm\\:calculateButton').removeAttr('disabled');" /> 

One thing that is also useful for this approach is that you show the β€œajaxy” image to the end user when this happens so that they intuitively understand that material is coming that will be resolved soon. You can also show this image in the onsubmit method and then hide it incomplete.

EDIT: The problem may be that you need to add a process attribute to your a4j: commandButton. This attribute defines the components by id that should participate in the model update phase:

 <a4j:commandButton id="calculateButton" value="Calculate" action="#{illustrationManager.calculatePremium()}" process="firstName" reRender="mainForm" /> 
0
source

I think I can provide another job.

we must have two flags at the js level:

 var requestBlocked = false; var requestShouldBeSentAfterBlock = false; 
Element

h:inputText blocks ajax request on blur:

 <h:inputText id="dob" onblur="requestBlocked = true;" ... 

a4j:commandButton sends a request if requestBlocked is false:

 <a4j:commandButton id="calculateButton" onkeyup="requestShouldBeSentAfterBlock = requestBlocked; return !requestBlocked;" ... 

a4j:support sends a request if requestShouldBeSentAfterBlock is true:

 <a4j:support event="onchange" oncomplete="requestBlocked = false; if (requestShouldBeSentAfterBlock) { requestShouldBeSentAfterBlock = false; document.getElementById('calculateButton').click(); }" ... 

since the oncomplete block works after re-displaying all the necessary elements, everything will work in the necessary order.

0
source

It seems that the reason is somewhere in getters / seters. For example, one of the possibilities: when you click ajaxSingle=true and the calculation button. All values ​​are set for the model, therefore, both setAge and setDateOfBirth . And it may happen that setDateOfBirth is called before setAge .

Approaching the setAge method, it actually resets the date to the beginning of the year, even if the date could already be a year.

I would recommend simplifying the logic first. For example, you have separate disabled fields for year and birthday, and check if the problem is reproducing all in order to find the minimum necessary conditions for reproduction.

In addition, from the user's point of view, it is common practice to do something like the following, so that the event fires after the user stops printing:

 <a4j:support event="onkeyup" ajaxSingle="true" ignoreDupResponses="true" requestDelay="300" reRender="..."/> 
0
source

Source: https://habr.com/ru/post/888267/


All Articles