Changing a result from p:autoComplete's completeMethod before using it - jsf

I'm implementing a dialog for registering an address. In the street name field, I'm using PrimeFaces' <p:autoComplete> tag to help the user find the name of their street. The completeMethod works as expected, but to avoid confusion between similar street names I would like the drop-down list to also include the municipality the given street is in, for instance on the format "<street name>, <municipality name>".
I don't want the municipality name to be included in the actual field, so I've concluded that I need a method that performs some sort of string manipulation (substring using the position of the first comma, for instance), but I can't figure out where such a method would be called from. I've had a look through the PrimeFaces documentation, but I haven't been able to find anything that would allow me to do this. Is this at all possible in <p:autoComplete>? Alternatively, is there another autocomplete implementation which supports this, or would I have to implement my own javascript component?
EDIT: This is what the xhtml code I'm using looks like:
<div class="form-group row required">
<h:outputLabel value="#{msgs['#common.mailingAddress']}" for="address" styleClass="col-xs-12"/>
<p:autoComplete
id="address"
name="address"
size="50"
maxlength="50"
styleClass="col-xs-12 street-name"
label="#{msgs['#common.search']}"
disabled="#{not configurationController.cardCtrl.editable}"
value="#{configurationController.cardCtrl.selected.address}"
required="true"
completeMethod="#{configurationController.cardCtrl.autoCompleteTest}">
<f:validator binding="#{onlyLettersOrDigitsValidator}"/>
</p:autoComplete>
<h:message id="addressMessage" for="address" styleClass="inline-error inline-error-small"/>
</div>
The autoCompleteTest method in the controller is as follows:
public List autoCompleteTest(String input) {
AddressSearch addressSearch = AddressSearch.builder()
.streetName(input)
.municipality(municipality.getName())
.maxResultsPerPage(10)
.build();
return addressesToStreetNames(mapService.addressSearch(addressSearch).getAddresses());
}
With a helper method addressesToStreetNames which takes a list of Address objects and returns an ArrayList<String> containing those addresses' street names.
EDIT2: Based on suggestions in the comments, I tried setting itemValue and itemLabel to different values, to see if that had any effect. The new xhtml looks like the above, with the addition of the following three lines:
var="address"
itemValue="#{address.streetName}"
itemLabel="#{configurationController.cardCtrl.formatAddress(address.streetName, address.postTown)}"
The autoCompleteTest method now also returns the Address object directly rather than a String representation of the street name, so that these fields are available. The formatAddress method is simply return streetName + ', ' + postTown;
This causes the dropdown list to look how I want it to look, but when I click an item it still inserts the whole string with both street name and post town/municipality into the text field (and in fact, before I've written anything, the text field already contains ", ").

Related

rich:toolTip not handling String values that contain quotes

I have ran into a particularly strange problem when implementing a RichFaces tool tip component. In my project I have a table that displays a list of Strings that are entered by the user, and I want there to be a pop-up of additional information when a user mouses over the strings in the list.
One of the requirements is that any string that is more than one word must contain double quotes "" when input by the user--e.g. a single word would be input as Java vs. a phrase "Java is cool".
So, I added a <rich:toolTip> to render additional info if it exists--and it works, except for strings that contain double-quotes. For example (as it's displayed in the table) "sample string" will not show additional information on mouseover.
My JSF code is simply:
<h:outputText id="keywordText" value="#{keywordData.keyword}"/>
<rich:toolTip for="keywordText" rendered="#{keywordData.comments != null}" value="#{keywordData.comments}"/>
Like I said, this works for words/strings that do not contain quotes. I am wondering if there is a workaround within JSF/RichFaces I can use in order to get this to work properly with a string that contains quotes. Or perhaps some assistance in writing a custom JavaScript function that forces or "tricks" RichFaces into handling quotation marks in a string correctly?
Thanks for any help in advance!
Edit: I am using RichFaces 3.3.3
In the page source, for the string "Testing Quotes" (does not work) I found this:
<span id="j_id138:j_id144:keywordTable:"Testing Quotes":keywordText">"Testing Quotes"</span>
<span id="j_id138:j_id144:keywordTable:"Testing Quotes":j_id159" class="rich-tool-tip " style="z-index:99; ">
<span id="j_id138:j_id144:keywordTable:"Testing Quotes":j_id159content">
<p>This comment should display</p>
</span>
<span id="j_id138:j_id144:keywordTable:"Testing Quotes":j_id159script" style="display:none">
<script id="scriptj_id138:j_id144:keywordTable:" quotes":j_id159"="" testing="" type="text/javascript">
new ToolTip("j_id138:j_id144:keywordTable:"Testing Quotes":j_id159","j_id138:j_id144:keywordTable:"Testing Quotes":keywordText",{'showEvent':'mouseover'} );
</script>
</span>
You can see that the quotations in the string itself (which appears to supposed to be part of the id attribute) are being misinterpreted in the new ToolTip parameters. And for a string testkeywordawesome without quotes you can see it works (because it does not contain quotes):
<span id="j_id138:j_id144:keywordTable:testkeywordawesome:keywordText">testkeywordawesome</span>
<span id="j_id138:j_id144:keywordTable:testkeywordawesome:j_id159" class="rich-tool-tip " style="z-index: 99; visibility: hidden; display: none; left: 63.7833px; top: 210.75px;">
<span id="j_id138:j_id144:keywordTable:testkeywordawesome:j_id159content">
<p>the best comment in the world</p>
</span>
<span id="j_id138:j_id144:keywordTable:testkeywordawesome:j_id159script" style="display:none">
<script id="scriptj_id138:j_id144:keywordTable:testkeywordawesome:j_id159" type="text/javascript">
new ToolTip("j_id138:j_id144:keywordTable:testkeywordawesome:j_id159","j_id138:j_id144:keywordTable:testkeywordawesome:keywordText",{'showEvent':'mouseover'} );
</script>
</span>
Edit2: The tool tips exists in a rich:column, of which exists in a rich:extendedDataTable. Below are their code:
<rich:extendedDataTable value="#{keywordEntry.globalKeywordsDataModel}"
rendered="#{fn:length(keywordEntry.globalKeywords) gt 0}"
styleClass="removeEDTSortIcon removeEDTContextMenu"
id="keywordTable" rowClasses="row1, row2"
var="keywordData" rows="0" noDataLabel=" "
headerClass="#{displayHeader == null or displayHeader ? 'rich-table-header' : 'hide'}"
rowKeyVar="keywordRowIdx" enableContextMenu="false"
sortMode="#{globalKeywordListSort.multiSortEnabled ? 'multi' : 'single'}"
sortPriority="#{globalKeywordListSort.sortOrderList}"
width="#{eStaffUser.userKeywordAdmin ? '750px' : '750px'}"
height="#{((fn:length(keywordEntry.globalKeywords)*30 + 50) lt 480) ? (fn:length(keywordEntry.globalKeywords)*30 + 50) : 480}px"
>
and
<rich:column id="#{globalKeywordSortFieldEnumBean.KEYWORD}" selfSorted="false" width="#{eStaffUser.userKeywordAdmin ? '48%' : '52%'}" sortBy="#{keywordData.keyword}"
sortOrder="#{globalKeywordListSort.dataTableColumnSortClass[globalKeywordSortFieldEnumBean.KEYWORD].sortOrder}">
id="j_id138:j_id144:keywordTable:"Testing Quotes":keywordText"
This means that one of the parents of <h:outputText id="keywordText"> has its id defined by something that evaluates to "Testing Quotes", that's obviously bad.
OK, so the root of the issue was that the rich:extendedDataTable uses an ID supplied by each entry to generate a unique ID for each table row. So the problem ended up being in the underlying "Model Managed-Bean" that implemented a generic data entity interface, this interface defines a getEntityId() method and in this case, the "ID" returned was the keyword value itself, since there was no true (i.e., database) entity ID such as a primary key:
public class KeywordDataBean implements SummaryDataEntityIF<String>
The problem was resolved by changing the interface's implementation to Integer and returning the hash as the unique ID for each keyword object:
public class KeywordDataBean implements SummaryDataEntityIF<Integer>
This forced the html id generated by the rich:extendedDataTable to be a simple integer number instead of a (keyword) string containing quotes, allowing the rich:toolTip to work properly.

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

How to bold specific Strings in an h:column

I have a JSF data table that is displaying data based off a search successfully. However, I'm not sure how to selectively bold certain text data in a particular column.
So, for instance, I would like this text...
Here is some text that would be inside the h:column
to show up like this on the page...
Here is some text that would be inside the h:column
Here's what my data table looks like
Results:
<h:dataTable var="results"
value="#{logSearcherBean.results}"
border="1">
<h:column>#{results.logName}</h:column>
<h:column>#{results.matchLine}</h:column>
</h:dataTable>
You could either homebrew an EL function which manipulates the column value and returns the desired HTML,
<h:outputText value="#{my:highlight(results.logName, logSearcherBean.query)}" escape="false" />
(note that this is due to escape="false", which is mandatory to present HTML literally, also sensitive to XSS attacks if the logName is a value which is fully controlled by the enduser)
Or grab JavaScript/jQuery which manipulates the returned HTML, see also this related question: Highlight a word with jQuery.
Hi all,
<p:column id="lastName"
headerText="Last Name">
<h:outputText value="#{person.lastName}" style="#{myBean.getStyle(person.lastName)}"/>
</p:column>
And in the bean:
public String getStyle(String str) {
return str.equals(keyword) ? "background-color: yellow" : "";
}
All best and happy coding!

How to get values from ui:repeat in a html form

I have resolved my previous problem posted in:
ui:repeat, populate list
Now I would like to know how to get the values modified in the form and pass to my bean.
This is how I done:
in the form:
<ui:repeat value="#{myBean.myList}" var="item">
<td class="icePnlGrdCol1" id="nacionI-0-#{item.index-1}">
<input class="iceInpTxt celdaNacionI"
id="I#{item.index gt 9 ? '':0}#{item.index}"
name="I#{item.index gt 9 ? '':0}#{item.index}"
title="I#{item.index gt 9 ? '':0}#{item.index}" type="text"
value="#{item.valor}" />
</td>
</ui:repeat>
in the bean:
private List iniciosMesList = null;
CeldaGrid is a class with their getter/setter
I want the get the value of every inputText in the form an re-create the list before to pass to my database.
How can I access this values???
thanks
Use an <h:input> instead of <input>. Provide a valueChangeListener attribute that binds to your backing bean. For each value in the list that the user has changed, this listener will be called. You can additionally provide or grab the item iteration variable with this call back.
This way you can construct a list of all items that have changed, and send these to your DB. Or, if merge them with your original list and send that to the DB.
(p.s. I advice you not to use a raw List as in private List iniciosMesList = null;, but parameterize it correctly)

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

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="..."/>

Resources