For some reason, I can't get my code to pull up the view I'm calling. Even worse, I'm not getting any type of error message on my XPage, the combo box is just blank when I click on it.
I'm trying to pull a list of departments. The View named (DepartmentLookup) actually contains about 135 entries. I want the second column, which contains the department name. Here is my computed values code: #DbColumn(["DomApps01/Hendricks", "aApplications/HCHPhoneBk.nsf"], "(DepartmentLookup)", 2)
My xsp source code for the combo box is here:
<xp:comboBox
id="department"
value="#{document1.department}"
style="width:180px"
rendered="#{javascript:document1.isEditable()}">
<xp:this.validators>
<xp:validateExpression
message="You must select a Department">
<xp:this.expression><![CDATA[#{javascript:value != "Select One"}]]></xp:this.expression>
</xp:validateExpression>
</xp:this.validators>
<xp:selectItem
itemLabel="Select One"
itemValue="Select One"
id="selectItem1">
</xp:selectItem>
<xp:selectItems
id="selectItems1">
<xp:this.value><![CDATA[#{javascript:
#DbColumn(["DomApps01/Hendricks", "aApplications/HCHPhoneBk.nsf"], "(DepartmentLookup)", 2)
}]]></xp:this.value>
</xp:selectItems>
</xp:comboBox>
Use this code to get an error string result if database or view isn't available:
<xp:selectItems
id="selectItems1">
<xp:this.value><![CDATA[#{javascript:
var db = session.getDatabase("DomApps01/Hendricks", "aApplications/HCHPhoneBk.nsf");
if (!db.isOpen()) {
return "Error reading database";
}
var result = #DbColumn(db, "(DepartmentLookup)", 2);
if (typeof result === 'undefined') {
return "Error reading view";
}
result
}]]></xp:this.value>
</xp:selectItems>
In my tests #DbColumn returns nothing if it fails - no Exception gets thrown (so, try-catch-block doesn't help). That's why you don't get an error message.
As an alternative you could use a Java version of DbLookup & DbColumn, with cache, sort and unique and without 64K limit.
Related
I would like to ask how to use the computed field to display listbox value?
My idea is there is a listbox and a computed field. In the listbox, I use partial update in the onchange event and the partial update id is computed field
I may post the code about the listbbox and the computed field here:
For the listbox:
<xp:listBox id="listBox2">
<xp:selectItems>
<xp:this.value><![CDATA[#{javascript:viewScope.get("vs1");
}]]></xp:this.value>
</xp:selectItems>
<xp:eventHandler event="onchange" submit="true"
refreshMode="partial" refreshId="computedField1">
</xp:eventHandler></xp:listBox>
For the computed field: In this part I have read this post: How to get display text of combobox and not the alias? to learn how display the listbox value.
function getComponentLabel(componentId) {
var select = getComponent(componentId);
var value = select.getValue();
try {
var list = select.getChildren();
if (value) {
for (var i = 0; i < list.length; i++) {
if ((typeof list[i]).indexOf("SelectItems") > -1) {
items = list[i].getValue();
for (var k = 0; k < items.length; k++) {
if (items[k].getValue() === value) {
return items[k].getLabel();
}
}
}
else if((typeof list[i]).indexOf("SelectItem") > -1) {
if (list[i].getItemValue() === value) {
return list[i].getItemLabel();
}
}
}
}
} catch (e) {
}
return value;
}
var dspValue = getComponentLabel("listBox1");//yes, it's listbox 1 not listbox2, if type listbox2, the computed field will show null (not sure why)
return "The listbox selected value(s) is: " + dspValue;
So far when I run the code at the first time, I put the value in the listbox, the computed field can display the selected value from the list box. But when I put multiple values in the listbox, the computed field can only show the latest value that I've put.
I have tried to use for loop in the computed field to get the selected values in list box:
var text="";
for (var i = 0; i < dspValue.length; i++)
{
text += dspValue[i];
}
return "you select value is: " + text;
Then I run the code, I get the error message said the dspValue is null. I also try to display the dspValue.length without the for loop and get the same error.
Instead of use for loop, I don't have the idea to get all values from the listbox and show in the computed field.
How can I use the computed field to display all the selected values in the listbox? I appreciate for any advice please.
May be you need the submittedValue of the component, see the answer of Paul Withers in the question, xPage dateTime picker validation does not work on date change
One possible solution is to simplify it and instead of a Computed Field, use a duplicate 'read-only' list box instead. This way the List Box's Read-Only renderer will do the heavy lifting of determining the appropriate Labels. You just need to make sure both listboxes have the same select values.
<!-- This is your List Box -->
<xp:listBox id="listBox1" value="#{viewScope.yourValue}" multiple="true">
<xp:selectItem itemLabel="Label One" itemValue="1"></xp:selectItem>
<xp:selectItem itemLabel="Label Two" itemValue="2"></xp:selectItem>
<xp:selectItem itemLabel="Label Three" itemValue="3"></xp:selectItem>
<xp:eventHandler event="onchange" submit="true" refreshMode="partial" refreshId="listBox2"></xp:eventHandler>
</xp:listBox>
<!-- This is instead of your 'Computed Field' -->
<xp:listBox id="listBox2" value="#{viewScope.yourValue}" readonly="true">
<xp:selectItem itemLabel="Label One" itemValue="1"></xp:selectItem>
<xp:selectItem itemLabel="Label Two" itemValue="2"></xp:selectItem>
<xp:selectItem itemLabel="Label Three" itemValue="3"></xp:selectItem>
</xp:listBox>
The only downside of this is that the readonly values are rendered as a vertical table with each label on each row. You may have preferred comma separated.
You might be able to use the same concept with another read-only control that might support comma separated display or something you prefer.
or you could make a custom renderer but that is another kettle of fish!
I have two forms in a database: fmKopf (head) and fmPos (position). I have a DataView which I am using to show me all the fmKopf documents - this works perfectly. The var name for the records are "rowHandle". I have added a repeat control to the details section of the dataView where I would like to display the fmPos documents for the fmKopf. The repeat var name is "rowData". The value for the repleat control is:
var posView:NotesView = database.getView("xpPositions");
if (rowHandle.isDocument()) {
var key = rowHandle.getColumnValue("searchKey");
var vecLieferscheine:NotesViewEntryCollection = posView.getAllDocumentsByKey(key);
return vecLieferscheine;
} else {
return null;
}
I then added a computed field within the repeat control but for the life of me do not know what to use to display the corresponding data from the fmPos document. If I just display rowData I get the UID of the Notes documents - this makes sense as we are returning a NotesViewEntryCollection. I then tried the following code:
var doc:NotesDocument = database.getDocumentByUNID(rowData);
return doc.getItemValue("DLNTLP");
to display the field DLNTLP from the fmPos document - this cannot work as rowData is a NotesViewEntryCollection and not a single value. This causes the page to crash.
What would be the code that I need to get the computed field to display the values from the underlying documents?
Thank you for any help given.
Ursus
Sorry i had to edit the answer multiple times dont know why but stackoverflow timed me out.
getAllDocumentsByKey(key) does not return a NotesViewEntryCollection it returns a NotesDocumentCollection wich does not really mather you just skip the .getDocument();
So in your xp:repeat you already have NotesDocuments, you can work with them you dont need to get the documents from your database using database.getDocumentByUNID().
You can use following code to access your documents via a datasource inside your repeat (wich does more or less the same as database.getDocumentByUNID()):
<xp:repeat
id="repeat1" rows="30"
value="YourCode" var="detailDoc">
<xp:panel
<xp:this.data>
<xp:dominoDocument var="document1"
action="openDocument"
documentId="#{javascript:detailDoc.getUniversalID()}">
</xp:dominoDocument>
</xp:this.data>
</xp:panel>
</xp:repeat>
or direkt access every Document from your Collection:
<xp:repeat
id="repeat1" rows="30"
value="YourCode" var="detailDoc">
<xp:panel
<xp:text>
<xp:this.value><![CDATA[#{javascript://
var doc:NotesDocument = detailDoc;
return doc.getItemValueString('DLNTLP');
</xp:this.value>
</xp:text>
</xp:panel>
</xp:repeat>
If you just need one or two fields from your documents i would use the secound methode, the first is very usefull if you also want to manipulate those fields because you can use the document datasource with all it's features. The first example also is usefull if you want to display attachments.
One of my bright ideas is looking a little less bright than I first thought.
Background - I needed to do the classic repeat control solution to the view with DBLookups in one of the columns. I need to have totals for the columns, so I decided that I would use a viewScope variable to hold the subtotal, and add the values as I compute them in the rows of the repeat control.
Sounds good in theory, but for some reason, if I use a Computed Field inside the repeat control, the value is computed (and appended to the subtotal) twice. I have established this with code along the following lines, using both a computed field and an edit box with the same code inside the repeat:
<xp:text escape="true" id="computedField9">
<xp:this.value><![CDATA[#{javascript:
//some calculations, nothing to see here*
var subTotal = viewScope.get("valueColumnTotal");
viewScope.put("valueColumnTotal", subTotal + sumVals);
sessionScope.put("Testing", sessionScope.get("Testing") + "~" + sumVals);
return sumVals;
}]]></xp:this.value>
//converters and stuff, nothing to see here
</xp:text>
<xp:inputText id="inputText1">
<xp:this.value><![CDATA[#{javascript:
//Same as the computed field above
</xp:this.value>
</xp:inputText>
For values of 1,2 and 3, my sessionScope variable shows 0~1~1~1~2~2~2~3~3~3 and the subtotal comes out as 18
If I remove the text box, I get 0~1~1~2~2~3~3 and a subtotal of 12
In other words, I'm getting double values for the computed field and single values for the edit box. I'm also getting a zero in there that I can't explain, but I'm assuming that's just an initial value and not something to worry about.
I thought it might have been a case of the repeat being refreshed by something, but then I would expect 1~2~3~1~2~3.
I'm guessing that I just don't understand something fundamental about when computed fields are computed, but I'm stumped. I guess I could switch to edit boxes, but I think Computed Fields are more appropriate here.
Can anybody shed some light on this one?
The JSF Lifecycle sheds some light on the challenge you are facing. The formula you use is executed in multiple phases as Per pointed out. The easiest way to ensure that they are computed only once is to use this code:
if (view.isRenderingPhase() {
var subTotal = viewScope.get("valueColumnTotal");
viewScope.put("valueColumnTotal", subTotal + sumVals);
sessionScope.put("Testing", sessionScope.get("Testing") + "~" + sumVals);
return sumVals;
}
You just need to make sure to reset the value at the appropriate time (e.g. beforeRenderResponse)
The other option to be independent from cycles (you might want to compute a value even if you don't render the page) is to use a small Java class:
public class SumItUp {
private HashMap<String, Integer> totals = new HashMap<String,Integer>();
public void add(String key, int value) {
Integer i = new Integer(value);
this.totals.put(key,i);
}
public int getTotal() {
int result = 0;
for (Map.Entry<String, Integer> me : this.totals.entrySet()) {
result += me.getValue().toInt(); // <-- check that one - off my memory
}
}
Use this as data context or initialize it in your SSJS. Regardless how often the computation is called. When you submit to a Hashmap there won't be duplicate values. Use the clientid (not the id, that's the same for all controls in a repeat) or the document's UNID as key - they are unique
If I read the requirement correctly, the requirement is to show totals for a column.
If the view has categorized columns, here is the solution I used, based off of Notes In 9 – 019 – Xpages: Getting Column Totals From a View
A datacontext is used to pull in the column total from the view:
{** This line of code if (entry==null) {return false}else{return entry} returns a zero if there are no categories for the key value}
The display of the totals in the table used to display the values using a row within a repeat control, is done using a xp:facet with the below code as the Total line:
With the entire facet looking like:
{** Column #4, 5th column over, in the view was the column showing the category total}
Cut-N-Paste Below {**Only to be used by professional Cut-n-Paste'ers}
<xp:dataContext var="contractCategory">
<xp:this.value>
<![CDATA[#{javascript:
try{
var nav:NotesViewNavigator = contractView.createViewNav();
var query = new java.util.Vector();
query.addElement(sGrantId);
var entry:NotesViewEntry = contractView.getEntryByKey(query);
var entry = nav.getPrev(entry);
if (entry==null) {return false}else{return entry}
}catch(e){
return false;
}
}]]>
</xp:this.value>
</xp:dataContext>
<xp:this.facets>
<xp:panel xp:key="footer" id="TotalDisplayArea">
<xp:tr style="background-color:#c9c9c9">
<xp:this.rendered>
<![CDATA[#{javascript:getComponent("repeat1").getRowCount();}]]>
</xp:this.rendered>
<xp:td colspan="2" styleClass="budget_grid_body lotusBold" style="text-align:left">
Totals:</xp:td>
<xp:td styleClass="budget_grid_body" style="text-align:right"> </xp:td>
<xp:td styleClass="budget_grid_body" style="text-align:center;">
<xp:div style="text-align:left;float:left;padding-left:20px;">
<xp:span>$</xp:span>
</xp:div>
<xp:div style="float:right;padding-right:5px;">
<xp:text escape="true" id="totalCostDisp">
<xp:this.converter>
<xp:convertNumber pattern="#,##0.00"></xp:convertNumber>
</xp:this.converter>
<xp:this.value>
<![CDATA[#{javascript:
(operatingCategory)?
operatingCategory.getColumnValues()[4]:
"CodeError:4:2"}]]>
</xp:this.value>
</xp:text>
</xp:div>
</xp:td>
<xp:td style="background-color:#ffffff"></xp:td>
</xp:tr>
</xp:panel>
</xp:this.facets>
I added the new boolean converter to a checkbox and bound this to a number field on the Domino form. When I view the XPage I get an error: Exception
java.lang.String incompatible with java.lang.Boolean.
My source is
<xp:checkBoxGroup
id="checkBoxGroup1"
value="#{document1.Good}"
defaultValue="true">
<xp:this.converter>
<xp:convertBoolean></xp:convertBoolean>
</xp:this.converter>
<xp:selectItems>
<xp:this.value><![CDATA[#{javascript:var arr = ["true", "false"];
return arr;}]]></xp:this.value>
</xp:selectItems>
</xp:checkBoxGroup>
Just wondering how this new feature in ND9 works???
Howard I posted a tip on this here: http://xpagetips.blogspot.com/2013/01/before-ibm-connect-5-days-5-tips-tip-1.html.
I think you're right. It is only there to make it easier work with boolean values but does not enable actually storing that data type in a Notes document.
Perhaps there is object storage coming in our future??
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>