XPages custom validator - xpages

Ultimately I need to do some cross field validation and thought I would use a custom validator to do this. But I can't even get a simple example to work. The following code (which is pretty similar to that on p.116 of "Mastering XPages" allows any value (including an empty field). Am I missing something obvious?
<xp:inputText
id="field1"
value="#{document1.field1}">
<xp:this.validators>
<xp:customValidator>
<xp:this.validate><![CDATA[#{javascript:if (value == "") {
return new javax.faces.application.FacesMessage("Please enter a value");
}}]]></xp:this.validate>
</xp:customValidator>
</xp:this.validators>
</xp:inputText>

It is not possible to validate an empty field with a validator. A validator runs only if a value exist. In XPages you have the required property for fields which allows to check for empty fields; this is a workaround for this problem, and it is (as far as I know) not possible to create your own "required" validator.
If you want to create your own one, you have to create a converter instead
UPDATE 21.06.2013
It is possible to create an own required validator with a small workaround: http://hasselba.ch/blog/?p=764

You need to return a string with the error message in it - and not a FacesMessage object.
So in your case, do this instead:
<xp:inputText id="field1" value="#{document1.field1}">
<xp:this.validators>
<xp:customValidator>
<xp:this.validate><![CDATA[#{javascript:
if (value == "") {
return "Please enter a value";
}
}]]></xp:this.validate>
</xp:customValidator>
</xp:this.validators>
</xp:inputText>

Related

xp:selectItem value property throws Conversion Error

Within xp:RadioGroup there is xp:selectItem which requires itemLabel.
Data-Properties of xp:selectItem is either itemValue or value:
itemValue - is the value returned to the server
value - specifies the contest of the selection item (Can be literal string or expression)
Loading values in both value & itemValue throws an error e.g.
<xp:radioGroup id="radioGroup"
layout="lineDirection"
dojoType="dijit/form/RadioButton"
styleClass="zmdi">
<xp:selectItem itemLabel='ssh'
itemValue="serverValue"
value="SelectionContentsValue">
</xp:selectItem>
</xp:radioGroup>
The error thrown is:
Exception
Conversion Error setting value ''{0}'' for ''{1}''.
java.lang.IllegalArgumentException: Conversion Error setting value ''{0}'' for ''{1}''.
com.sun.faces.util.Util.getSelectItems(Util.java:489)
com.sun.faces.renderkit.html_basic.SelectManyCheckboxListRenderer.encodeEnd(SelectManyCheckboxListRenderer.java:130)
com.ibm.xsp.renderkit.html_basic.RadioRenderer.encodeEnd(RadioRenderer.java:48)
com.ibm.xsp.renderkit.ReadOnlyAdapterRenderer.encodeEnd(ReadOnlyAdapterRenderer.java:180)
javax.faces.component.UIComponentBase.encodeEnd(UIComponentBase.java:1005)
com.ibm.xsp.component.UISelectOneEx.encodeEnd(UISelectOneEx.java:331)
com.ibm.xsp.util.FacesUtil.renderComponent(FacesUtil.java:858)
Removing the [ value="SelectionContentsValue" ] attribute from the xp:selectItem renders the radioGroup without issue.
ideas? thx
The value property of the is intended to be evaluated to something that is a UISelectItem.
Here, you are assigning it to a String "SelectionContentsValue".
During rendering, the Renderer asks the radioGroup "Please give me your all your selectItems".
It does this by iterating through it's children, and checking if each child is a UISelectItem.
For each UISelectItem that it finds, it evaluates the value property.
If value evaluates to null, it will then check for itemLabel, itemValue etc. and create the selectItem using these properties.
else if value evaluates to a UISelectItem, it will use this UISelectItem
else If value evaluates to something that is not a UISelectItem, it throws an IllegalArgumentException
Your situation is throwing the IllegalArgumentException because you have assigned a String to the 'value' property instead of a UISelectItem
So you should either use the itemLabel, itemValue properties and manually set those options that way. Or instead you can use the value property to compute the selectItem to one that has been prepared in some other place, or is dynamically loaded e.g. a managedBean or scoped variable.
I hope this helps let me know if any questions
Thank you Cameron, you comment sparked an idea to go track down, which I found here:
IBM Domino Designer> IBM Domino Designer 9.0.0> IBM Domino Designer User Guide> Designing XPages applications> Adding controls> Control reference selectItems - Selection Items
» Checkboxes can each checked "true", no mutual exclusivity with checkboxes.
Checkboxes are kinda like Hippies, "It's all cool man".
» Radio buttons are much more finicky:
» Radios require itemLabel with itemValue aliase optional, for each selection option:
» The 'value' data-property is for checkboxes. Value contains pipe-delimited selection values, in an array/vector of checkbox choices.
<xp:checkBoxGroup id="checkBoxGroup1" value="#{document1.fruit}">
<xp:selectItems>
<xp:this.value>
<![CDATA[${javascript:["Apples|apples|Garden of Eden|false","Oranges|oranges"]}]]>
</xp:this.value>
</xp:selectItems>
</xp:checkBoxGroup>

Xpages and computed field in a composite control

I've been able to successfully create dynamic field names and save the values for input fields using the technique described here: http://lpar.ath0.com/2014/04/07/repeated-xpages-input-controls-with-number-indexed-field-names/
I also have a computed field which has a number indexed name but whose value is computed based on a keyword choice. I can assign the dynamic field name for it like so:
<xp:text escape="true" id="computedField1">
<xp:this.value><![CDATA[#{compositeData.dataSource[compositeData.fieldName2]}]]></xp:this.value>
</xp:text>
The property definition for this field looks like this:
and the call to the composite control looks like this:
<xc:cc_dynamicAssetItems
row="#{(rownum lt 10)? '0':''}#{rownum}"
dataSource="#{document1}"
fieldName1="replace#{(rownum lt 10)? '0':''}#{rownum}"
fieldName2="budget#{(rownum lt 10)? '0':''}#{rownum}" >
</xc:cc_dynamicAssetItems>
I am at a loss however on how to pass the value to this computed field. The SSJS for it would have been:
var projectNumber = getComponent("ProjectNumber").getValue();
if(projectNumber == ""){
return "Nothing found";
}else{
return projectNumber + "A";
}
I'd appreciate some direction.
Thanks,
Dan
Instead of setting the value of your component, you should set the value of the underlying datasource. This means that your logic should not run in your computed field, than in your onChange event of the ProjectNumber component.
Then you have to update e.g. document1.budget01 only, which is a lot faster.
As an alternative, you can still pass a method binding* to your custom control, as described here: Pass javascript code to Custom Control
(*: in your case a value binding)
did you try javascript instead expression language
ex:
fieldName1="#{javascript: 'replace'+(rownum > 10? '0':'')+rownum}"

Concatenating parameters

I'm trying to concatenate a string in Expression Language to access a property of an object and failing to do so.
Within an XPage in Lotus Notes, I want to programatically select which field to which I want to bind the control on the current XPage.
The result I would like to achieve is as follows.
#{poDoc[advertisingDateStart];}
I have a variable named fieldName that would supply "advertisingDate" and simply want to append "Start" to this field and "End" to the end date field. I tried several variations that do not work, such as:
#{poDoc[fieldName{'Start'}];}
Note that it would work if I passed in "advertisingDateStart" and used
#{poDoc[fieldName];}
The goal is to be able to place a start date field and an end date field while dynamically binding based on configuration documents. That is, adding fields to my XPage using configuration documents and repeats instead of changing the design. Here is one of the ways I tried to create the ending date field:
<xp:inputText id="inputText5"
style="padding-top:2px;text-align:left">
<xp:this.rendered><![CDATA[#{javascript:rowData.getColumnValue("FieldType") == "Date Range"; }]]></xp:this.rendered>
<xp:dateTimeHelper id="dateTimeHelper3"></xp:dateTimeHelper>
<xp:this.converter>
<xp:convertDateTime type="date"></xp:convertDateTime>
</xp:this.converter>
<xp:this.value><![CDATA[#{javascript:poDoc[fieldName+"End"];}]]></xp:this.value>
</xp:inputText>
I just can't figure it out.
Unfortunately, you cannot bind using 'javascript:' notation. In SSJS there is no way of pointing to an 'object property' (getter and setter) that you would like bound to component property.
Only Expression Language can do this with dot notation.
If you really need to bind to dynamic fields, you have to compute the fieldname before using it as Per Henrik Lausten suggested (xp:dataContext is the way to go). Since you want two date fields from one entry, you should use different variables for the new values, computing the value using Javascript
<xp:this.dataContexts>
<xp:dataContext var="fieldName">
<xp:this.value>
<![CDATA[#{javascript:rowData.getColumnValue ("FieldName");}]]>
</xp:this.value>
</xp:dataContext>
<xp:dataContext var="fieldNameDateStart">
<xp:this.value>
<![CDATA[#{javascript:return rowData.getColumnValue ("FieldName") + "Start";}]]>
</xp:this.value>
</xp:dataContext> <xp:dataContext var="fieldNameDateEnd">
<xp:this.value>
<![CDATA[#{javascript:return rowData.getColumnValue ("FieldName") + "End";}]]>
</xp:this.value>
</xp:dataContext>
</xp:this.dataContexts>
fieldName would be used for single field entries, while the others would be used only for start dates and end dates.

How do you make a computed field both computed and databound to a field?

I know how to bind a computed field to a document field. And I know how to use javascript to compute a computed field. But how do I do both?
Say my javascript in the computed field is:
#Name("[ABBREVIATE]" ,#UserName());
How do I bind that to say document1.ReqName field?
I use two fields:
a hidden input field that computes the required value (using the xp:inputHidden control). I use "default value" to emulate Computed when composed - and converters to emulate Computed (inspired by Tommy Valand).
a visible computed field or a visible input field set to read only that does the same calculation. Perhaps this can be changed to display the value of the hidden input field using getComponent("").getValue()?
Maybe I miss the Point but why don't You simply do the following?
<xp:inputText id="inputText1" value="#{document1.Reader}">
<xp:this.defaultValue>
<![CDATA[#{javascript:#Name("[ABBREVIATE]",#UserName());}]]>
</xp:this.defaultValue>
</xp:inputText>
This is a Textfield bound per EL to the DocumentField. If the field is empty it is calculated by the default value.
Great! - I missed the point. But You can try this one:
<xp:text escape="true" id="computedField3">
<xp:this.value>
<![CDATA[#{javascript:
if(#IsNewDoc()){
document1.replaceItemValue("Reader",#Name("[ABBREVIATE]",#UserName()));
}
return document1.getItemValue("Reader");}]]>
</xp:this.value>
</xp:text>
For the computed field's Value, use Advanced data binding and select Expression Language (EL).
The expression to use is simply "document1.ReqName" (no quotes).
I wouldn't bind in this case. I would bind every other control and in this case manually add that into the document field directly with script before the save action.
so something like: (forgive the code not at my work comp)
<eventhandler>
<actionGroup>
<script><![CDATA[#{javascript:
doc.replaceItemValue("ReqName", #Name("[ABBREVIATE]" ,#UserName()));
}]]><script>
<saveDocument></saveDocument>
</actionGroup>
</eventhandler>
<xp:text escape="true" id="computedFieldTest1">
[CDATA[#{javascript:
var userName = "Username: "+("Reader",#Name("[ABBREVIATE]",#UserName()));
currentDocument.replaceItemValue("computedFieldTest1",userName);
return userName;
}]]></xp:this.value>
</xp:text>

Xpages more fields (unlimited) at the click of a button

I would like to start with x no. of fields (in my app I have a pair of textual data field and numeric data field) on a xpage application (say 10 pairs) and then when the user clicks on "more field", I want more pairs to appear dynamically without a full refresh on the page, but would like unlimited no. of fields (as long as the page doesn't crash) and then I would like to submit the form and the data for all those fields. What's the best way to implement this?
Usually, fields are bound to a document data source using dot notation:
<inputText value="#{contact.firstName}" />
However, array notation is also supported:
<inputText value="#{contact['firstName']}" />
Because the field name in this latter syntax is being treated as a string, not as an implicit property of the bean, it can be dynamically computed. What I've found to be the easiest way to define these dynamic fields is to create a custom control for each of the basic types of fields, and define each as accepting the data source and the field name. So the field itself then ends up with a syntax similar to the following:
<inputText value="#{compositeData.dataSource[compositeData.fieldName]}" />
By using that syntax, a calculation of any complexity can be used to determine what field name to pass to the custom control. In the scenario you're attempting to accomplish, specifying an indexVar on the repeat control that surrounds the field pair would allow you to designate a field suffix for each... perhaps something like the following:
<xp:repeat indexVar="fieldSuffix" value="#{viewScope.rowCount}">
<xp:div>
<xc:dynamicInputText dataSource="#{contact}" fieldName="fullName_#{fieldSuffix}" />
<xc:dynamicInputNumber dataSource="#{contact}" fieldName="phoneNumber_#{fieldSuffix}" />
</xp:div>
</xp:repeat>
With this approach, you would end up with fields named "fullName_0", "fullName_1", etc., up to the limit specified in the viewScope. Typically, the only complication is ensuring that when an existing document is opened, the viewScope variable is set back to the correct limit. Another approach to that, of course, is actually saving the limit as another item on the document and binding the repeat value to it instead.
You also can have a look at the exercise 23 "Tablewalker". It doesn't do multiple fields but does Multi-value fields which might be better in terms of processing and storage (you can do an #Elements to find out how many are there in a document). The exercise is here:
http://www-10.lotus.com/ldd/ddwiki.nsf/dx/Tutorial-Introduction-to-XPages-Exercise-23
While the button only adds one row at a time, it is easy to adjust.
What you could do is have a Bean with 2 String values Label and Data and a managed bean that has a ArrayList of that object so inside of your repeat control you bind the repeat to the ArrayList and then bind your xp:inputText to rowData.Data and your xp:label to rowData.Label then when you want to add another 5 rows you just add However many more objects into the ArrayList then refresh your page, your data will still live in your arraylist and you will have 5 new Empty objects where you can add data.
public class Data {
private String label;
private String data;
public Data() {
}
//getters and setters
}
public class ManagedBean {
private ArrayList<Data> datalist; // add a managed property for this one so It will create a new one when needed.
// getters and setters
public addFiveMoreObjects() {
Data newItem;
for (int i=0; i<5; i++) {
newItem = new Data();
datalist.add(newItem);
}
}
}
<xp:repeat value="#{managedBean.datalist}" var="rowData">
<xp:text value="#{rowData.label}" />
<xp:inputText value="#{rowData.data} />
</xp:repeat>
<xp:button value="Add 5 More"> // call #{managedBean.addFiveMoreObjects}

Resources