Xpages: Validation using java bean with multiple instances of a custom control - xpages

I have a custom control for an editable field, and validation is done via a java bean. I want to set the isValid() property to set bootstrap styling like so:
However, this does not work if I place the cc on a page more than once, because I am searching on the component's ID to set the Valid property.
I need to either compute the id of the field in the CC, or do something else like this post describes.
I believe I can compute the ID of the text field like so:
<xp:inputText
id="${javascript:compositeData.fieldName}"
type="text"
loaded="${!empty compositeData.placeholder}"
required="${compositeData.required}"
disableClientSideValidation="true">
<xp:this.attrs>
<xp:attr
name="placeholder"
value="${compositeData.placeholder}" />
</xp:this.attrs>
<xp:this.binding><![CDATA[#{javascript:compositeData.dataSource[compositeData.fieldName]}]]></xp:this.binding>
</xp:inputText>
However, there are a few places where I need to compute the component, such as
<xp:this.styleClass><![CDATA[#{javascript:"form-group" + (getComponent("inputText1").isValid() ? "" : " has-error" )}]]></xp:this.styleClass>
I have to replace the "inputText1" with the computed ID, but cannot figure out how to do so.
Is there a better/easier way?

Why would you not use the compositeData.fieldName everywhere you reference the id?
<xp:this.styleClass><![CDATA[#{javascript:"form-group" + (getComponent(compositeData.fieldName).isValid() ? "" : " has-error" )}]]></xp:this.styleClass>

Related

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

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 ", ").

XPages inputTextArea is not editable

When i use multiLine editbox (shown in FIRST ONE) It cannot be editable If the document is in editMode.
But SECOND ONE is editable. My point is if use formula at the value of inputTextArea It can not be editable.
I could not find what it is that i missed?
FIRST ONE:
<xp:inputTextarea id="muvName" rows="2" cols="70"><xp:this.value><![CDATA[#{javascript:#Implode(document1.getItemValue("muvName"))+ #NewLine() + "C/o";}]]></xp:this.value></xp:inputTextarea>
SECOND ONE:
<xp:inputTextarea id="muvName" rows="2" cols="70" value="#{document1.muvName}">
</xp:inputTextarea>
Use the property defaultValue to define a default value:
<xp:inputTextarea
id="muvName"
rows="2"
cols="70"
value="#{document1.muvName}">
<xp:this.defaultValue><![CDATA[#{javascript:
#Implode(document1.muvName)+ #NewLine() + "C/o"
}]]></xp:this.defaultValue>
</xp:inputTextarea>
and use property value for binding a document's item (or a scope variable) to the editable field. value has to be an object to which XPage can write the submitted content to.
In your first example you calculate a string and it's impossible to write something back to a calculated string. That's why the field is read only.
Update:
If you want to correct document field's value before editing then use a custom converter instead:
<xp:inputTextarea
id="muvName"
rows="10"
cols="70"
value="#{document1.muvName}">
<xp:this.converter>
<xp:customConverter
getAsObject="#{javascript:value}">
<xp:this.getAsString><![CDATA[#{javascript:
if (!value.endsWith("C/o")) {
value += #NewLine() + "C/o";
}
value
}]]></xp:this.getAsString>
</xp:customConverter>
</xp:this.converter>
</xp:inputTextarea>

xpages hiding/showing fields based on a combobox value

I'm using a <xe:djTabContainer> with 10 <xe:djTabPane> containing numerous fields components.
There is a principal combobox whose value will determine which fields to be showed or not and in this way the document structure will be achieved.
If I will use this approach, then for all my >50 fields which I want to show/hide, I will use only the onChange event of the combobox?
Considering the fact that there are >50 fields which enter in this category < showing&hiding them >, should I use another approach / method? Thanks for your time.
<xp:comboBox value="#{Contr.txt_tipcontractcv}" id="comboBox4"> <xp:selectItems id="selectItems1">
<xp:this.value><![CDATA[#{javascript:return ""}]]></xp:this.value>
</xp:selectItems>
<xp:selectItems id="selectItems2">
<xp:this.value><![CDATA[#{javascript:#DbColumn(#DbName(),"SetupvwTipuriContracteC",1);}]]> </xp:this.value>
</xp:selectItems>
<xp:eventHandler event="onchange" submit="false"> <xp:this.script><![CDATA[XSP.partialRefreshPost("#{id:FisaP}", {
});
]]></xp:this.script> </xp:eventHandler> </xp:comboBox>
and the panel:
<xp:panel id="FisaP">
<xp:label id="label4"
style="color:rgb(128,0,0);font-family:verdana;font-size:9pt;font-weight:bold">
<xp:this.value><![CDATA[#{javascript:"Fisa contract "+ Contr.getItemValueString("txt_tipcontractcv")}]]></xp:this.value>
<xp:this.rendered><![CDATA[#{javascript:
Contr.getItemValueString("txt_tipcontractcv") != ""
}]]></xp:this.rendered>
</xp:label>
</xp:panel>
I would turn it around. Let the labels and fields ask the combobox if they should be rendered or not. Let the combobox's onchange event initiate a partial refresh of a panel which includes all fields you want to show/hide.
If your >50 fields are all on one place you can frame them with a panel and set the rendered property there.
If your combobox is bound to a viewScope variable the rendered property of fields/labels would be
rendered="#{javascript:viewScope.tipcontractcv1 == 'Vanzare-Cumparare'}"
or if it is bound to a document field then
rendered="#{javascript:document1.getItemValueString('txt_tipcontractcv1') === 'Vanzare-Cumparare'}"
Update:
Based on your code in your answer https://stackoverflow.com/a/25636661/2065611 take the following steps so get it to work with the Dojo Tab Container:
1.
Put the labels and fields in panels which do have an id but don't have a rendered attribute
<xp:panel id="panel1">
<xp:label value="Persoane spre informare" ... id="label2">
<xp:this.rendered><![CDATA[#{javascript:
Contr.getItemValueString("txt_tipcontractcv1") == "Vanzare-Cumparare"
}]]></xp:this.rendered>
</xp:label>
... other label and fields ...
</xp:panel>
You can create other panels "panel2", "panel3", ... too. They can be placed in different djTabPanes.
2.
Change the onchange event of your combobox and execute client side code to refresh the panels
<xp:eventHandler
event="onchange"
submit="false">
<xp:this.script><![CDATA[
XSP.partialRefreshPost("#{id:panel1}", {
onComplete: function() {
XSP.partialRefreshPost("#{id:panel2}");
}
});
]]></xp:this.script>
</xp:eventHandler>
3.
You can optimize your code if you put labels and fields with the same rendered attribute together into an additional panel
<xp:panel id="panel1">
<xp:panel id="panelRendered1"
<xp:this.rendered><![CDATA[#{javascript:
Contr.getItemValueString("txt_tipcontractcv1") == "Vanzare-Cumparare"
}]]></xp:this.rendered>
<xp:label value="Persoane spre informare" ... id="label2" />
... other label and fields ...
</xp:panel>
</xp:panel>
First... never do this:
if (comboVal == "Vanzare-Cumparare")
Even though it's called "SSJS".. it's really not "JavaScript"... you're pretty much working with Java. In Java everything is an object. Even a literal string. So by entering "Vanzare-Cumparare" you're pretty much creating a new Object. You can see this in the typeahead of the SSJS editor. Try typing "anything". <-- Note you NEED to type in that period.
The way to do that if statement is:
if ("Vanzare-Cumparare".equalsIgnoreCase(comboVal)
you could reverse it I think:
if (comboVal.equalsIgnoreCase("Vanzare-Cumparare")
Should give you the same result. I think sometimes you can get away with using the == but mostly likely it's going to bite you at some point no matter what. So I recommend never doing that.
I'm not sure I'm following your approach here. I guess it makes some sense but if it were me I'd do it differently. I know that personally I've NEVER tried to grab the component in SSJS like this: var combo:javax.faces.component.UIComponent - I've never seen the need.
I'd prefer to use a scoped variable and then in the label's rendering property grab that scoped variable and use that to determine your rendering value - true or false.
I see a real problem. If the component is not currently rendered, you cannot get a handle to it with getComponent. If you simply change the display value, then the component is always there, even if it's not displayed.
Something like this:
var combo:javax.faces.component.UIComponent = getComponent("txt_tipcontractcv1");
var comboVal = combo.getValue();
if (comboVal.equalsIgnoreCase("Vanzare-Cumparare")) {
document.getElementById("#{id:label2}").style.display = "block"; // or "inline"
} else {
document.getElementById("#{id:label2}").style.display = "none";
}
You need to ensure that the label2 element is always rendered, so that the style indicates whether it is visible.

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

Convert double to int in inline-text

<o:importFunctions type="java.lang.Math" />//omnifaces, see http://showcase.omnifaces.org/taghandlers/importFunctions
<c:set var="ordersToShow" value="${Math:min(5, processedOrders.size())}" /> // processedOrders is a List
<p:fieldset legend="Last Stuff (${ordersToShow})">[...]</p:fieldset>
${ordersToShow} is of type java.lang.Double (output of "${ordersToShow.class}") and prints sth. like "Last Stuff 2.0".
I want to have the output like "Last Stuff 2", how can I do that?
f:convertNumber will throw error ( Parent not an instance of ValueHolder) for p:fieldSet if its used inside.
Instead you can keep an string in managed bean and in its getter you can use String.format("%.0f", ordersToShow);
Else keep an binding variable for fieldSet and set the above formatted value in bean.
You can use f:convertNumber. Keeping minFractionDigits="0" should do the trick.
See here http://www.tutorialspoint.com/jsf/jsf_convertnumber_tag.htm
I have not used p:fieldset so now sure how the f:convertNumber can be used with the legend but seems not possible.
You can use this, replace c:set and p:fieldset lines with the below. The fmt tag explained here http://www.tutorialspoint.com/jsp/jstl_format_formatnumber_tag.htm
<fmt:formatNumber var="ordersToShow" type="number" maxFractionDigits="0" value="${...}" />
<p:fieldset legend="Last Stuff (${ordersToShow})">[...]</p:fieldset>

Resources