Display Error Control works only once - xpages

I've one required combobox "PriceList" associated with display error control.
I've anothe field "Price" with OnChange event to set required property of "Pricelist". If price is entered the "Pricelist" combobox required property set to false else on blank, "Pricelist" is enabled.
Both fields and xpage have disabled client validation off.
The combox displays error message in the beginning when document is created. If I change and blanked the value of "price", "pricelist" 's error control does not display message though required property is true.
What is issue here?
Pricelist code:
<xp:comboBox
id="comboBox7"
value="#{document1.PList1}"
style="width:99.0px"
disableClientSideValidation="true">
<xp:this.validators>
<xp:validateRequired
message="Required">
</xp:validateRequired>
</xp:this.validators>
<xp:this.required><![CDATA[#{javascript:var price11:com.ibm.xsp.component.xp.XspInputText = getComponent("price11");
var a=price11.getValueAsString()
if (a == ""){
return true;
}else{
return false;
}}]]></xp:this.required>
<xp:this.disabled><![CDATA[#{javascript:var price11:com.ibm.xsp.component.xp.XspInputText = getComponent("price11");
var a=price11.getValueAsString();
if ( a==""){
return false;
} else {
return true;
}}]]></xp:this.disabled>
<xp:selectItems>
<xp:this.value><![CDATA[#{javascript:var result = [];
var pricelist = #DbLookup("" , "Keywords","Price List", 2)
result.push("")
for (var i = 0; i < pricelist.length; i++) {
var eachName = pricelist[i];
result.push(eachName);
}
return result;}]]></xp:this.value>
</xp:selectItems>
</xp:comboBox>
Price code:
<xp:inputText
value="#{document1.Price1}"
id="price11"
style="width:80px"
required="true"
disableClientSideValidation="true">
<xp:this.validators>
<xp:validateRequired
message="Required field">
</xp:validateRequired>
</xp:this.validators>
<xp:eventHandler event="onchange" submit="true" refreshMode="complete">
<xp:this.action>
<xp:executeScript>
<xp:this.script><![CDATA[#{javascript:var comboBox7:com.ibm.xsp.component.xp.XspSelectOneMenu = getComponent("comboBox7");
var price11:com.ibm.xsp.component.xp.XspInputText = getComponent("price11");
var a=price11.getValueAsString();
if(a !=="" ){
//if(comboBox7.isRequired()==true){
//comboBox7.setRequired(false);
//}
//var result = [];
//var pricelist = #DbLookup("" , "Keywords","Price List", 2)
//result.push("")
//for (var i = 0; i < pricelist.length; i++) {
//var eachName = pricelist[i];
//result.push(eachName);
//}
//comboBox7.setValue(result);
comboBox7.setRequired(false);
comboBox7.setDisabled(true);
} else {
comboBox7.setDisabled(false);
comboBox7.setRequired(true);
}
}]]></xp:this.script>
</xp:executeScript>
</xp:this.action></xp:eventHandler></xp:inputText>

I suggest using the SSJS debugger or print statements to work out what's happening here. The code seems very convoluted. First of all, you have code that does setRequired on the ComboBox to true or false. But that can only run if validation passes. It also does a full refresh, so it appears you're reloading the page. Furthermore, you are calculating the required property on the combo box, so even if you do a partial refresh and you set it as required or not, the RenderResponse phase will recalculate the required property and override whatever state is set by the onChange event. The same is true for the disabled property.
If you set a component as required, validation for that refresh will already have occurred. So it will only be required the next time the page is submitted by the user. At which point validation will fail, so unless you're disabling validators, you cannot set it to not disabled. But disabling validators is done server-side, before values are entered by the user.
My recommendation would be to move your validation to your save method and work from the underlying datasource rather than the components. If you want to mark components as valid or not, then there is a setValid() method and there are blog posts about adding a FacesMessage to indicate an error and binding it to a component.
For a more advanced method, Tim Tripcony did a NotesIn9 about using the binding property to link components for this kind of related validation. But that will require Java.

Related

xpages: how to make the edit box read-only and still have the session scope variable?

I have a xpage that contains 4 edit boxes and a button, they are used to export the data in excel. I use session scope in those four edit boxes because I have store the value in order to generate the excel.
The design look looks like the following:
//1st edit box: get the user name, set it to default so the user does not need to type his/her name
<xp:inputText id="inputText1" value="#{sessionScope.User}">
<xp:this.defaultValue><![CDATA[#{javascript: #Name("[CN]",#UserName())}]]></xp:this.defaultValue>
</xp:inputText>
//2nd edit box: after get the user name in edit box 1, retrieve the user's department in this edit box
<xp:inputText id="inputText2"value="#{sessionScope.Department}">
<xp:this.defaultValue><![CDATA[#{javascript:#DbLookup(#DbName(),"UserView", (getComponent("inputText1").getValue()),2 )}]]></xp:this.defaultValue>
</xp:inputText>
//3rd edit box: get the start search date in YYYYMM format, user only can type numbers
<xp:inputText id="inputText3" value="#{sessionScope.startYYYYMM}">
<xp:eventHandler event="onkeypress" submit="false">
<xp:this.script><![CDATA[if (event.keyCode >= 48 && event.keyCode <= 57)
{
event.returnValue = true;
}
else
{
event.returnValue = false;
}]]></xp:this.script>
<xp:this.parameters>
<xp:parameter name="message1" value="value">
/xp:parameter>
</xp:this.parameters>
</xp:eventHandler>
</xp:inputText>
//4th editbox: get the endsearch date in YYYYMM format, user only can type numbers
<xp:inputText id="inputText4" value="#{sessionScope.startYYYYMM}">
<xp:eventHandler event="onkeypress" submit="false">
<xp:this.script><![CDATA[if (event.keyCode >= 48 && event.keyCode <= 57)
{
event.returnValue = true;
}
else
{
event.returnValue = false;
}]]></xp:this.script>
<xp:this.parameters>
<xp:parameter name="message1" value="value">
/xp:parameter>
</xp:this.parameters>
</xp:eventHandler>
</xp:inputText>
//a button to call another page to export the excel
<xp:button value="Export" id="button11">
<xp:eventHandler event="onclick" submit="true" immediate="false" save="false" refreshMode="complete">
<xp:this.action><![CDATA[#{javascript:context.redirectToPage("ExportExcel.xsp");}]]></xp:this.action>
</xp:eventHandler>
</xp:button>
I run the code, it works fine. However here is what I would like to ask. In the first and the second edit box, if I set those edit boxes to Read-only. It seems lose the session scope variable and I run the code, the excel just contains the default header.
I find this post ReadOnly field in Xpage not submitted is very similar to my case, so I set the edit box to editable and try to apply this code from the post:
//for 1st editbox
<xp:scriptBlock id="scriptBlock1">
<xp:this.value><![CDATA[function makeFieldReadOnly() {
document.getElementById("#{id:inputText1}").readOnly = true;
}
window.onload = makeFieldReadOnly;]]></xp:this.value>
</xp:scriptBlock>
//for 2nd edit box
<xp:scriptBlock id="scriptBlock2">
<xp:this.value><![CDATA[function makeFieldReadOnly() {
document.getElementById("#{id:inputText2}").readOnly = true;
}
window.onload = makeFieldReadOnly;]]></xp:this.value>
</xp:scriptBlock>
The edit box still editable and the excel is not work properly (only display the default header).
So how can I make the edit box read-only and still have the session scope variable?
Grateful for your advice please. Thank you.
Reference:
ReadOnly field in Xpage not submitted
export data from panel
You can calculate the values for your two sessionScope variables in e.g. the beforeRenderResponse:
<xp:this.beforeRenderResponse><![CDATA[#{javascript:
sessionScope.User = #Name("[CN]",#UserName());
sessionScope.Department = #DbLookup(#DbName(),"UserView", (#Name("[CN]",#UserName()),2 )
}]]></xp:this.beforeRenderResponse>
You can then show the values of the sessionScope variables in computed text fields instead of input fields:
<xp:text escape="true" id="computedField1" value="#{sessionScope.User}" />
<xp:text escape="true" id="computedField2" value="#{sessionScope.Department}" />
I had this issue with 9.01 FP6
The best solution was to place the inputs in a xp:panel and then use the readonly attribute on the panel to make the field within it read only.
This worked well and was easy to implement

XPages: image onCclick needs 2 clicks to do what is expected

I have a custom control that contains 2 images: add and remove from favorites.
There is some code on the onCLick event of the image, and both images use a SSJS function to see if the current document is already in the favorites or not, in the visible property of each image.
All works well, execpt that I need to click twice on the image in order to see the changes in the UI. Both onClick events are set to FullUpdate (also tried partial update with the panel that contains the images).
I could move all the favorites logic into session scope variables, but I think this should work as is. I just don't understand why I need to click twice, as if the partial refresh doesn't do anything (though it is, as I see the reload Arrow in the browser!).
Can it be that the code takes too long to execute and the refresh doesn'T get the updated info???
Here's the custom control code:
<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core" xmlns:xe="http://www.ibm.com/xsp/coreex">
<xp:this.resources>
<xp:script src="/AppUtils.jss" clientSide="false"></xp:script>
</xp:this.resources>
<xp:image url="/favorites-add.png" id="image1"
alt="Add to Favorites" title="Add to Favorites">
<xp:this.rendered><![CDATA[#{javascript:!isInFavorites(document1.getDocument().getUniversalID(), userBean.canonicalName);
}]]></xp:this.rendered>
<xp:eventHandler event="onclick" submit="true" refreshMode="complete">
<xp:this.action><![CDATA[#{javascript:addToFavorites(document1.getDocument().getUniversalID(),userBean.canonicalName);}]]></xp:this.action>
</xp:eventHandler>
</xp:image>
<xp:image url="/favorites-remove.png" id="image2"
alt="Remove from Favorites" title="Remove from Favorites">
<xp:this.rendered><![CDATA[#{javascript:isInFavorites(document1.getDocument().getUniversalID(),userBean.canonicalName);
}]]></xp:this.rendered>
<xp:eventHandler event="onclick" submit="true"
refreshMode="complete">
<xp:this.action><![CDATA[#{javascript:removeFromFavorites(document1.getDocument().getUniversalID(),userBean.canonicalName);}]]></xp:this.action>
</xp:eventHandler>
</xp:image>
</xp:view>
And here's the SSJS:
function addToFavorites(unid, userName) {
var newDoc:NotesDocument = database.createDocument();
newDoc.appendItemValue("Form","Favorite");
newDoc.appendItemValue("UserName", userName);
newDoc.appendItemValue("DocUNID", unid);
newDoc.save();
return;
}
function removeFromFavorites(unid, userName) {
var notesView:NotesView = database.getView("(lkpFavorites)");
var keys = new java.util.Vector();
keys.addElement(userName);
keys.addElement(unid);
var coll:NotesDocumentCollection = notesView.getAllDocumentsByKey(keys, true);
if(coll.getCount()==1) {
coll.getFirstDocument().remove(true);
}
return;
}
function isInFavorites(unid, userName) {
var notesView:NotesView = database.getView("(lkpFavorites)");
var keys = new java.util.Vector();
keys.addElement(userName);
keys.addElement(unid);
var coll:NotesDocumentCollection = notesView.getAllDocumentsByKey(keys, true);
if(coll.getCount()>0) {
return true;
} else {
return false;
}
}
I'd suggest you to put an xp:link around your image to cast the event code instead of using the evennts of the image directly.
OK, not sure what happened there but I manually edited the custom control's source to see wheter I had empty eventHandler as suggested by Oliver, and it started to work as expected. I am totally unsure of what I changed: to my knowledge, all I did was to add extra "returns" in the source view, to make it more readable... Christmas gift I guess.
All is good now. Thanks to all :)

Xpages repeat control division by zero

I'm using a repeat control displaying names. The content is displayed proberly, but I needed a pager because there can be more than 30 entries.
As soon as I add a pager for this repeat control and press a button doing a partial refresh on the site I get a "division by zero" error.
Here's my repeat control:
<xp:repeat id="repeat1" rows="30" var="namesList" repeatControls="true" indexVar="rowIndex">
<xp:table>
<xp:tr>
<xp:td>
<xp:link escape="true" id="Employee">
<xp:this.text><![CDATA[#{javascript:namesList.getItemValueString("Employee")}]]></xp:this.text></xp:link>
</xp:td>
</xp:tr>
</xp:table>
<xp:this.value><![CDATA[#{javascript:try {
var wfDoc:NotesDocument = docApplication.getDocument(true);
var dcNoAnswer:NotesDocumentCollection = database.createDocumentCollection();
var count:Integer = 0;
//Gets all response documents, but only adds those to the document collection, without a valid
//response to the cycle
if (wfDoc.getResponses() != null) {
var dc:NotesDocumentCollection = wfDoc.getResponses();
var doc:NotesDocument = dc.getFirstDocument();
while (doc != null) {
if (doc.getItemValueString("Response") == "") {
dcNoAnswer.addDocument(doc);
}
doc = dc.getNextDocument();
}
dc.recycle();
}
docApplication.replaceItemValue("MissingResponsesCount", dcNoAnswer.getCount());
wfDoc.recycle();
return dcNoAnswer;
} catch(e) {
dBar.error("Show no response repeat control data: " + e);
}}]]></xp:this.value>
</xp:repeat>
And here's my pager:
<xp:pager layout="Previous Group Next" id="pager3" for="repeat1"></xp:pager>
As I said, doing a partial refresh on the site oder panel, I'll get this error:
I've been looking for an error for some time, but I can't see anything wrong here. Did anybody else have such a problem and maybe has a solution?
You have repeatControls="true" set. That means "create the controls once and never again", so you can't use a pager to navigate, as mentioned here. Remove repeatControls="true" and it will work.
If you look at the stack trace, it will fail on getStart().
Try adding alwaysCalculateLast="false" to your pager. The repeat control could have 0 values (because of dcNoAnswer) and maybe you have set xsp.repeat.allowZeroRowsPerPage=true in your xsp properties settings.

xpages validation on field having onChange script

There is a required field:
<xp:this.validators>
<xp:validateRequired
message="Required field. Please add some text.">
</xp:validateRequired>
</xp:this.validators>
Also, the value from this field is copied ( using the onChange event ) to other fields:
<xp:eventHandler event="onchange" submit="true"refreshMode="norefresh">
<xp:this.action><![CDATA[#{javascript:Cdoc.setValue("dlg_Localitate",Cdoc.getValue("txt_LocalitateCompanie"));
Cdoc.setValue("dlg_Localitate_1",Cdoc.getValue("txt_LocalitateCompanie"))}]]>
</xp:this.action>
</xp:eventHandler>
An inconvenient issue appears when I just click the field to fill it: the validation message appears. Is because the field initially is empty and the code I added is into the onChange event?
I'd like to use this field as required before users can save the doc.
I tried set the values by CSJS, but without a result...
var string = XSP.getElementById("#{id:inputText1}").value
XSP.getElementById("#{id:txt_LocalitateS}").value = string
XSP.getElementById("#{id:txt_LocalitateP}").value = string
Also, let say I enter a value for inputText1 and later on I enter a new value... How can I update automatically the other 2 fields with the new value?
I tried something like this:
<xp:inputText id="inputText1" value="#{Cdoc.txt_LocalitateCompanie}"
style="height:20.0px;width:122.0px;font-weight:bold;font-size:10pt;font-family:verdana"
required="true">
<xp:this.validators>
<xp:validateRequired message="Completarea localitatii este obligatorie.">
</xp:validateRequired>
</xp:this.validators>
<xp:typeAhead mode="full" minChars="1" ignoreCase="true"
id="typeAhead1">
<xp:this.valueList><![CDATA[#{javascript:#DbLookup(#DbName(),"vwLocalitati",Cdoc.txt_LocalitateCompanie,1,"[PARTIALMATCH]");}]]></xp:this.valueList>
</xp:typeAhead>
<xp:eventHandler event="onchange" submit="true"
refreshMode="norefresh">
<xp:this.action><![CDATA[#{javascript:Cdoc.setValue("dlg_Localitate",Cdoc.getValue("txt_LocalitateCompanie"));
Cdoc.setValue("dlg_Localitate_1",Cdoc.getValue("txt_LocalitateCompanie"))}]]></xp:this.action>
<xp:this.script><![CDATA[XSP.partialRefreshGet("#{id:txt_LocalitateS}", {
onComplete: function() {
XSP.partialRefreshGet("#{id:txt_LocalitateP}", {
onComplete: function(){ }
});
}
});]]></xp:this.script>
</xp:eventHandler>
</xp:inputText>
Thanks in advance
Two things here. First, you should disable validators for onChange event, therefore it won't display the validation error.
Second, when you use a CSJS script together with a SSJS, it will fire the CSJS one and if it returns true, proceed with the SSJS. So if you want your CSJS code run after SSJS, you can place it into oncomplete.
If I understood your question correctly, the following code would solve it.
<xp:inputText
id="inputText1"
value="#{Cdoc.txt_LocalitateCompanie}"
style="height:20.0px;width:122.0px;font-weight:bold;font-size:10pt;font-family:verdana"
required="true">
<xp:this.validators>
<xp:validateRequired
message="Completarea localitatii este obligatorie.">
</xp:validateRequired>
</xp:this.validators>
<xp:typeAhead
mode="full"
minChars="1"
ignoreCase="true"
id="typeAhead1">
<xp:this.valueList><![CDATA[#{javascript:#DbLookup(#DbName(),"vwLocalitati",Cdoc.txt_LocalitateCompanie,1,"[PARTIALMATCH]");}]]></xp:this.valueList>
</xp:typeAhead>
<xp:eventHandler
event="onchange"
submit="true"
refreshMode="norefresh"
disableValidators="true">
<xp:this.action><![CDATA[#{javascript:Cdoc.setValue("dlg_Localitate",Cdoc.getValue("txt_LocalitateCompanie"));
Cdoc.setValue("dlg_Localitate_1",Cdoc.getValue("txt_LocalitateCompanie"))}]]></xp:this.action>
<xp:this.onComplete><![CDATA[if(dojo.byId("#{id:txt_LocalitateP}")) {
XSP.partialRefreshGet("#{id:txt_LocalitateP}", {
onComplete: function() {
XSP.partialRefreshGet("#{id:txt_LocalitateS}", {
onComplete: function(){ }
});
}
});
}]]></xp:this.onComplete>
</xp:eventHandler>
</xp:inputText>
UPDATE: In your case, the field you want to refresh is on the second tab with partialRefresh="true". It means that at the time of partialRefreshGet, the target fields might not exist in the DOM. I have added a check now.
this is taken from my comments and put into an answer:
onChange events are generally frowned upon due to performance and user experience. If, however, the field is a listed control ie combobox it is not so dramatic. The following options/ideas are available
Take out the onChange() to test whether that makes a difference. If so, move your code.
Use an update button to change all the fields en masse also preventing information that is already inputted from being deleted unwanted-ly
create your own validation method and show/hide a label manually (hack-y)
Research how to manually put text into an errors control
If the field is in a dialog box, move the onChange() to the open/close methods of the dialog
FURTHER EDIT
An idea that I might suggest is using the xspDoc.getDocument(true) method to push all changes from the xpage to the background document. Something tells me that this might make a difference with the server reading the changes to the document and realizing that it is not empty.
ADDITIONAL IDEAS
I did not mention this because it is a bit more advanced, but should also get the job done assuming the refreshes are done. Even that is not that big of a deal. You could read all of your data from the document into a java bean. This bean is then the "data source" for your page and you bind all of your controls to the properties of this bean. You will then use EL to bind your controls to the bean. In the setters for those variables that trigger changes in other fields, change those values. So,
public PageBean(){
//read connection information out of the URL and get the correct information out of the document
//set all variables
firstName=doc.getItemValueString("firstName");
}
private String firstName;
public String getFirstName(){
return firstName;
}
public void setFirstName(String firstName){
this.firstName = firstName;
setLastName("Schmidt");
}
....
Once you register your bean with faces-config.xml, you can then use EL to access the data
#{PageBean.firstName}
#{PageBean.lastName}
Then you can get your document again in save and reset the values, save and release.

QueryDocumentDelete in Xpages

The QueryDocumentDelete in Database script only seems to run from the Notes UI client when a document is deleted. Is there a similar event when a document is deleted with the Delete Selected Documents simple action?
You're right, QueryDocumentDelete in database script is a Notes UI function and runs only from the Notes client.
Unfortunately, there is no similar event in simple action Delete Selected Documents.
An easy workaround is to write the whole functionality of "Delete Selected Documents" on your own. This way you can do things you want to before documents get actually deleted or prevent certain documents from deletion.
Here is a sample code for a button "Delete Selected Documents" which deletes documents in a view panel (with id "viewPanel1"):
<xp:button
value="Delete Selected Documents"
id="button1">
<xp:eventHandler
event="onclick"
submit="true"
refreshMode="complete">
<xp:this.script><![CDATA[
if(!XSP.isViewPanelRowSelected("#{id:viewPanel1}", "_colcbox")){
XSP.alert("Please select one or more documents to delete.");
return false;
}
if (!XSP.confirm('Are you sure you want to delete selected documents?')){
return false;
}]]></xp:this.script>
<xp:this.action><![CDATA[#{javascript:
var viewPanel = getComponent("viewPanel1");
var selectedIds = viewPanel.getSelectedIds();
for(i=0; i < selectedIds.length; i++) {
var docId = selectedIds[i];
var doc = database.getDocumentByID(docId);
// do things here you would do on QueryDocumentDelete
doc.remove(true);
doc.recycle();
}}]]></xp:this.action>
</xp:eventHandler>
</xp:button>

Resources