Using a4j:support to update the model and view, ready for the next button/submit action - jsf

The Problem
We have a swing based front end for an enterprise application and now are implementing a (for now simpler) JSF/Seam/Richfaces front end for it.
Some of the pages include fields that, when edited, should cause other fields to change as a result. We need this change to be shown to the user immediately (i.e. they should not have to press a button or anything).
I have implemented this successfully using h:commandButton and by adding onchange="submit()" to the fields that cause other fields to change. That way, a form submit occurs when they edit the field, and the other fields are updated as a result.
This works fine functionally, but especially when the server is under significant load (which happens often) the form submits can take a long time and our users have been continuing to edit fields in the meantime which then get reverted when the responses to the onchange="submit()" requests are rendered.
To solve this problem, I was hoping to achieve something where:
Upon editing the field, if required, only that field is processed and only the fields it modifies are re-rendered (so that any other edits the user has made in the meantime do not get lost).
Upon pressing a button, all fields are processed and re-rendered as normal.
The (Unstable) Solution
Okay, I think it might be easiest to show a bit of my page first. Note that this is only an excerpt and that some pages will have 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 value of age causes the value of dob to change in the model and vice versa. I use reRender="dob" and reRender="age,dob" to display the changed values from the model. This works fine.
I am also using the global queue to ensure ordering of AJAX requests.
However, the onchange event does not occur until I click somewhere else on the page or press tab or something. This causes problems when the user enters a value in say, age, and then presses calculateButton without clicking somewhere else on the page or pressing tab.
The onchange event does appear to occur first as I can see the value of dob change but the two values are then reverted when the calculateButton request is performed.
So, finally, to the question: Is there a way to ensure that the model and view are updated completely before the calculateButton request is made so that it does not revert them? Why is that not happening already since I am using the AJAX queue?
The Workarounds
There are two strategies to get around this limitation but they both require bloat in the facelet code which could be confusing to 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 to firstName.
The first step ensures that calculateButton does not overwrite the values in age or dob since it no longer processes them. Unfortunately it has the side effect that it no longer processes firstName either. The second step is added to counter this side effect by processing firstName before calculateButton is pressed.
Keep in mind though that there could be 20+ fields like firstName. A user filling out a form could then cause 20+ requests to the server! Like I mentioned before this is also bloat that may confuse other developers.
Workaround 2: Using the process list
Thanks to #DaveMaple and #MaxKatz for suggesting this strategy, it is as follows:
Add the ajaxSingle="true" attribute to calculateButton.
Add the process="firstName" attribute to calculateButton.
The first step achieves the same as it did in the first workaround but has the same side effect. This time the second step ensures that firstName is processed with calculateButton when it is pressed.
Again, keep in mind though that there could be 20+ fields like firstName to include in this list. Like I mentioned before this is also bloat that may confuse other developers, especially since the list must include some fields but not others.
Age and DOB Setters and Getters (just in case they are the cause of the issue)
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;
}
}

What is the scope of your bean? When the button is executed, it's a new request and if your bean is in request scope, then previous values will be gone.

So, finally, to the question: Is there a way to ensure that the model and view are updated completely before the calculateButton request is made so that it does not revert them?
What you could do is disable the submit button from the time you initiate your ajax request until your ajax request completes. This will effectively prevent the user from pressing the submit button 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 additionally helpful with this approach is if you display an "ajaxy" image to the end user while this is happening so that they intuitively understand that stuff is happening that will resolve soon. You can show this image in the onsubmit method as well and then hide it oncomplete.
EDIT:
The issue may just be that you need to add the process attribute to your a4j:commandButton. This attribute specifies 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" />

i guess i can provide another work-around.
we should have two flags on js level:
var requestBlocked = false;
var requestShouldBeSentAfterBlock = false;
h:inputText element blocks the ajax request on blur:
<h:inputText id="dob" onblur="requestBlocked = true;" ...
a4j:commandButton sends the request if requestBlocked is false:
<a4j:commandButton id="calculateButton"
onkeyup="requestShouldBeSentAfterBlock = requestBlocked;
return !requestBlocked;" ...
a4j:support sends the request if requestShouldBeSentAfterBlock is true:
<a4j:support event="onchange"
oncomplete="requestBlocked = false;
if (requestShouldBeSentAfterBlock) {
requestShouldBeSentAfterBlock = false;
document.getElementById('calculateButton').click();
}" ...
since oncomplete block works after all needed elements are re-rendered, things will work in the needed order.

It looks like a reason is somewhere in getters/setters.
For example one of the possibilities: when no ajaxSingle=true and the calculate button is clicked. All the values are set to the model, so both setAge and setDateOfBirth are invoked. And it may happen so that setDateOfBirth is invoked before setAge.
Looking closer at setAge method it in fact resets the date to the beginning of the year, even though the date could have had the right year already.
I would recommend simplifying the logic first. For example have separate disconnected fields for year and birth day, and check if the issue is still reproducible to find minimal required conditions to reproduce.
Also from user experience standpoint, a common practice is to do something like the following, so the event fires once user stops typing:
<a4j:support event="onkeyup"
ajaxSingle="true"
ignoreDupResponses="true"
requestDelay="300"
reRender="..."/>

Related

Problem with data table selection and how to access var outside of repeating block

I have a screen that I show some records with datatable from primefaces
enter code here
1) In my bean, I have two lists, the first one I enter the records returning from the database,
and the other is used to store the selected screen items.
#Getter #Setter
private List<RecorsDTO> recordsList;
#Getter #Setter
private List<RecorsDTO> selectedRecordsList;
In another screen, I show the IDs selected in the previous screen, for that I used the repeat
function of primefaces
<h:panelGroup>
<h:outputText value="#{selectedRecords.id}" />
</h:panelGroup>
The problem here is that it only shows the first list item, for example:
Records Selected in first screen: 100, 101
Records displayed on second screen: 100
2) I also need to show other attributes of the List, but these outside the repeat block, for example:
<h:panelGroup>
<h:outputText value="#{selectedRecords.id}" />
</h:panelGroup>
</ui:repeat>
<h:panelGroup>
<h:outputText value="#{selectedRecords.nome}" />
</h:panelGroup>
The problem here is that I can't access var = selectedRecords out of the repeating block, I tried to use
the dataGrid component to have is a var visible inside and outside the repeating block, but it didn't work either.
Any ideas that might help me in this case?
thanks for your feedback, I managed to solve the problem as below:
1) This issue was related to duplicate records, my Bean myBBean.selectedRecordsList was returning two records
equal (100, 100) and then within the repeat block he would perform a sort of "DISTINCT" and display only a record (100).
2) The var = selected records that I defined within block repetition are valid only within it and for access outside of
repetition block, I failed not to mention the index as it is a list as below
value="#{myBBean.selectedRecordslist.get(0).name}"

JSF input processing order

Is there a way to specify the order in which the inputs should be set after a submit?
Here is my case:
<h:inputText id="fieldA" value=#{myBean.myObject.fieldA}" />
<h:inputText id="fieldB" value=#{myBean.myObject.fieldB}" />
<p:autoComplete id="myObject" value=#{myBean.myObject" converter="myObjectConverter" />
<h:inputText id="fieldC" value=#{myBean.myObject.fieldD}" />
<h:inputText id="fieldD" value=#{myBean.myObject.fieldC}" />
The issue I am encountering is that, as the inputs are processed in the ordered they are declared, fieldA and fieldB are set in the initial instance of myObject, then myObject is set (with a new instance thus filedA and fieldB values are lost), and finally fieldC and fieldD are set with no problem.
If I could manage to start by setting myObject first, that would solve my problem.
I will temporarily set the fields and myObject into two different attributes of my bean, and populate myObject after clicking a save button. But it looks more like a hack than a real solution.
Needless to say that declaring the autocomplete before the inputtexts is not an option.
Thanks in advance.
In shortcut:
You can use <p:inputText> tag from primefaces. Then, you can disable all inputs. Add ajax to your autoComplete, and update other inputs after processing autoComplete. Inputs disable attribute can be set to depend on whether the autoComplete is not null. This way you will make the user to enter the autoComplet first.
you can try to set immediate="true" to p:autocomplete, so that it will be processed in the APPLY_REQUEST_VALUES phase, before all other components.
The simple solution is to update h:inputTexts when p:autocomplete item is selected to reflect its values:
<p:autoComplete id="myObject" value="#{myBean.myObject}" ...>
<p:ajax event="itemSelect" process="#this" update="fieldA fieldB fieldC fieldD" />
</p:autoComplete>
but this reverts user inputs on h:inputTexts. And since you can't move p:autocomplete on top, probably this is not acceptable too.
In case you can't/don't want to use ajax, you can force an early model update:
<p:autoComplete id="myObject" value="#{myBean.myObject}" immediate="true"
valueChangeListener="#{component.updateModel(facesContext)}" ... />
but, in my opinion, this is not very user friendly...
P.S. this time it's tested :)
There's no pretty way to get around this; your situation is already less than ideal and is hacky (re: not being able to simply reorder the fields). One workaround is for you to set fieldA and fieldB as attributes of myObject. In the converter, you could then pull the values off the components. Observe
Set attributes thus
<h:inputText id="fieldA" binding=#{fieldA}" />
<h:inputText id="fieldB" binding=#{fieldB}" />
<p:autoComplete id="myObject" value=#{myBean.myObject" converter="myObjectConverter">
<f:attribute name="fieldA" value="#{fieldA}"/>
<f:attribute name="fieldB" value="#{fieldB}"/>
</p:autoComplete>
The binding attribute effectively turns those components into page-scoped variables, allowing you to then pass them as attributes on your p:autocomplete
Get the values of those variables in your converter
//Retrieve the fields and cast to UIInput, necessary
//to retrieve the submitted values
UIInput fieldA = (UIInput) component.getAttributes().get("fieldA");
UIInput fieldB = (UIInput) component.getAttributes().get("fieldB");
//Retrieve the submitted values and do whatever you need to do
String valueA = fieldA.getSubmittedValue().toString();
String valueB = fieldB.getSubmittedValue().toString();
More importantly, why can't you just reorder the fields/logical flow of your form? You can avoid all this nasty business if you did

How can I add attributes to components which don't have their own renderers using the f:attribute component?

I want to write a custom renderer for the h:selectOneMenu component and eventually make use of the description property of the UISelectItem class to add a title a.k.a. tooltip to f:selectItems following BalusC's profound guides in https://stackoverflow.com/a/25512124/3280015 and http://balusc.blogspot.de/2008/08/styling-options-in-hselectonemenu.html.
Now I did extend the com.sun.faces.renderkit.html_basic.MenuRenderer in my own CustomMenuRenderer, registered it with the faces-config.xml and overrode the renderOption method, adding the following code before option tag is terminated by the Responsewriter:
String titleAttributeValue = (String) component.getAttributes().get("title");
if (titleAttributeValue != null) {
String indexKey = component.getClientId(context)
+ "_currentOptionIndex";
Integer index = (Integer) component.getAttributes().get(indexKey);
if (index == null) {
index = 0;
}
component.getAttributes().put(indexKey, ++index);
}
I'm not quite sure I'm doing the indexKey thing right or whether I need it for the title attribute or should use a writer.writeAttribute("title", titleAttributeValue, null); instead because I don't have a list like in the optionClasses tutorial, but the code works so far!
In the actual view definition use case I did:
<h:selectOneMenu value="#{cc.data}">
<f:attribute name="title" value="outerTEST" />
<c:forEach items="#{cc.list}" var="val">
<f:selectItem value="#{val}" itemValue="#{val}" itemLabel="#{val.label}">
<f:attribute name="title" value="innerTEST #{val.description}" />
</f:selectItem>
</c:forEach>
</h:selectOneMenu>
(I just put the #{val.description} there in the title value to clarify my intention, it is currently still empty and I will have to think about how to populate it per element later, but for the sake of the question we can assume it is already filled.)
But now I'm getting the "outerTEST" properly showing up in the title attribute of the option in the resulting XHTML in the Browser, yet I'm not seeing any of the "innerTEST" which would and should be individual per selectItem and which is what this is eventually all about.
I understand the f:selectItem and f:selectItemscomponents do not have their own renderers but rendering of options is generally handled by the MenuRenderer via its renderOption method.
But how then would I add individual titles to the individual selectItems??
Thanks

Values in rich:dataTable not getting updated in the bean

This problem is when we are migrating to richfaces4.3 and JSF2.0.
The context is: A rich:dataTable takes answerId, answerDefinition etc in their respective columns. An onchange event is executed on the answerid column, wherein a new row is added.
<rich:extendedDataTable rowKeyVar="currentRowVar"
value="#{DiscQuestMgtController.answerChoiceList}"
var="dataItem" rowClasses="row1, row2" id="ansChoiceList">
<rich:column width="160" sortable="false">
<h:inputText id="answerID" value="#{dataItem.answerId}"
onchange="addNewRowToAnsChoice('DiscQuestMgtForm',#{fn:length(DiscQuestMgtController.answerChoiceList)});" />
</rich:column>
<rich:column sortable="false">
<h:inputText id="choiceValue" value="#{dataItem.choiceValue}"/>
</rich:column>
<rich:column sortable="false">
<h:inputText id="definitionId" value="#{dataItem.definition}"/>
</rich:column>
</rich:extendedDataTable>
When retrieving the values into the dataTable, the working is fine. But when I enter the value into answerId(the first cell) and shift focus, the existing row is totally eliminated creating a new row.
addNewRowToAnsChoice , JS method, does few validations of the entered values and in turn calls a a4j function .. addEmptyAnsChoice .. The below is the code, that adds a new row, in the backing bean.
//AddNewRow
public void addEmptyRow( ActionEvent ae )
{
removeEmptyChoiceRow();
String logStr = " DiscQuestMgtController:addEmptyRow()>>>";
logger.debug( logStr + " Entered" );
AnswerChoiceBean bean = new AnswerChoiceBean();
bean.setQuestionId( discQuestBean.getQuestionId() );
logger.debug( logStr + "Choice list size: answerChoiceList size-->",answerChoiceList.size() ); //#abc
if ( answerChoiceList.size() == 1 )
answerChoiceList.get( 0 ).setQuestionId( discQuestBean.getQuestionId() );
if ( !answerChoiceList.contains( bean ) )
answerChoiceList.add( bean );
logger.debug( logStr + " Returned" );
}
removeEmptyChoiceRow() checks if there are any existing empty rows and deletes them, to add a new row. #abc, the list size is always the default one. (there is always one row on page load), which is just not changing though the values are entered. Pls suggest!!
This is the a4j:function:
<a4j:jsFunction name="addEmptyAnsChoice"
render="ansChoiceList,addDiscQuest,applyDiscQuest"
actionListener="#{DiscQuestMgtController.addEmptyRow}"
oncomplete="focusToNewCell('DiscQuestMgtForm',#{fn:length(DiscQuestMgtController.answerChoiceList)});">
</a4j:jsFunction>
A major change from JSF 1.2 to JSF 2 is the introduction of the attributes render and execute replacing rerender.
execute partly updates the model of the marked part of the tree
render renders the marked part of the tree and sends it via AJAX for replacing the original part.
This is handy, when e.g. a Listener updates values also being shown as input-fields on the page.
Update: You are calling addNewRowToAnsChoice on change of answerId, calling the a4j:jsFunction addEmptyAnsChoice later on.
There the elements ansChoiceList ,addDiscQuest,applyDiscQuest get rerendered, but nothing is being sent for execute. So the line you entered the answerId in - well - is never updated on server side.
In the next step, in the actionListener you remove all empty lines (so also this one, too) and add a fresh line on the bottom.
IMO, try skipping the jsFunction call and instead letting the same stuff being executed as a f:ajax or a4j:ajax. The (untested) code might look like following...
<h:inputText id="answerID" value="#{dataItem.answerId}"
onchange="<yourValidationFunction>">
<a4j:ajax execute="#this"
render="ansChoiceList addDiscQuest applyDiscQuest"
actionListener="#{DiscQuestMgtController.addEmptyRow}"
oncomplete="focusToNewCell('DiscQuestMgtForm',#{fn:length(DiscQuestMgtController.answerChoiceList)});">
</a4j:ajax>
</h:inputText>
This way you keep the ID from the value to be updated on server side.
Hope it works and helps...

JSF PrimeFaces inputText inside dataTable

JSF-2.0, Mojarra 2.1.19, PrimeFaces 3.4.1
Summary of the problem: Have a p:inputText inside p:dataTable and inputText action fired by p:remoteCommand which passes the dataTable row index as a parameter with f:setPropertyActionListener. But it always passes the last row of the dataTable, not the index of the row which includes currently clicked p:inputText.
As it can be seen from my previous questions, I am trying to use p:inputText as a comment taker for a status like in Facebook or etc. Implementation includes a p:dataTable. It's rows represents each status. Seems like:
<p:dataTable id="dataTable" value="#{statusBean.statusList}" var="status"
rowIndexVar="indexStatusList">
<p:column>
<p:panel id="statusRepeatPanel">
<p:remoteCommand name="test" action="#{statusBean.insertComment}"
update="statusRepeatPanel">
<f:setPropertyActionListener
target="#{statusBean.indexStatusList}"
value="#{indexStatusList}">
</f:setPropertyActionListener>
</p:remoteCommand>
<p:inputText id="commentInput" value="#{statusBean.newComment}"
onkeypress="if (event.keyCode == 13) { test(); return false; }">
</p:inputText>
</p:panel>
</p:column>
</p:dataTable>
Upper code says when the press enter key, fire p:remoteCommand which calls the insert method of the managed bean.
#ManagedBean
#ViewScoped
public class StatusBean {
List<Status> statusList = new ArrayList<Status>();
public int indexStatusList;
public String newComment
//getters and setters
public void insertComment() {
long statusID = findStatusID(statusList.get(indexStatusList));
statusDao.insert(this.newComment,statusID)
}
Let's debug together; assuming there are three statuses shown in the p:dataTable, click in the p:inputText which in the second status(index of 1), type "relax" and press the enter key.
In the debug console, it correctly shows "relax", but it finds the wrong status because indexStatusList has the value of 2 which belongs the last status in the p:statusList. It must be 1 which is the index of p:inputText that clicked on the dataTable row.
I think problem is about p:remoteCommand which takes the last index on the screen.
How it works?
Let's imagine there is a p:commandLink instead of p:remoteCommand and p:inputText:
<p:commandLink action=#{statusBean.insertComment>
<f:setPropertyActionListener target="#{statusBean.indexStatusList}"
value="#{indexStatusList}"></f:setPropertyActionListener>
This component successfully passes the indexStatusList as currently clicked one.
Conceptual problem in this solution lies in way how p:remoteCommand works. It creates JavaScript function whose name is defined in name attribute of p:remoteCommand. As you putted this in dataTable it will iterate and create JavaScript function called test as many times as there is rows in this table, and at the end last one will be only one. So, solution can be in appending index at the name of the remoteCommand but that is bad, because you will have many unnecessary JavaScript functions. Better approach would be to create one function an pass argument to it. So define remoteCommand outside of datatable:
<p:remoteCommand name="test" action="#{statusBean.insertComment}" update="statusRepeatPanel">
and call test function like this in your onkeypress event:
test([{ name: 'rowNumber', value: #{indexStatusList} }])
This will pass rowNumber parameter in your AJAX request. In backing bean's insertComment() method you can read this parameter and do with it anything you want:
FacesContext context = FacesContext.getCurrentInstance();
Map map = context.getExternalContext().getRequestParameterMap();
Integer rowNumber = Integer.parseInt(map.get("rowNumber").toString());
NOTE: as you are updating panel in each row, maybe you can change update attribute of remoteCommand to #parent so this will work for all rows.
EDIT: You can update the specific panel in specific row with following code in Java method:
RequestContext.getCurrentinstance().update("form:dataTable:" + rowNumber + ":statusRepeatPanel")

Resources