Computed field inside repeat control - computing twice (and ruining sub-total!) - xpages

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>

Related

Xpages SSJS How to display an array?

I have been learning Xpages programming. We are currently using domino 8.5.2. I am gaining familiarity with the display/input controls and I have had some success using them to display information from backend domino documents, views, scoped variables that are not an array. What I have not been able to discover is how to display the elements of a scoped variable array that is created dynamically.
For instance: I create an array with a number of elements. I can print the elements to the domino log with the following code:
for (var i=0; i<array.length; i++) {
print(array[i])
}
What do I use to display the individual elements on webpage? I apologize if the answer if obvious. I did find one posting about displaying a 2 dimensional array - but was unable to interpret the answer.
Thanks for any guidance.
---Lisa&
Use a repeat control and show elements within repeat in a computed field:
<xp:this.beforePageLoad><![CDATA[#{javascript:
var myTest = [];
for (var i=0; i<9; i++) {
myTest[i] = i;
}
viewScope.myTest = myTest;
}]]></xp:this.beforePageLoad>
<xp:repeat
id="repeat1"
rows="30"
var="row"
value="#{viewScope.myTest}">
<xp:text
escape="true"
id="computedField1"
value="#{javascript:row.toString()}">
</xp:text>
<br />
</xp:repeat>
The array is in viewScope.myTest in this example.
A very quick way to display the content of an array is using the join function. No repeat required but the display is limited.
<xp:this.beforePageLoad><![CDATA[#{javascript:
var arrTest= ["a","b","c","d"];
viewScope.myTest = myTest;
}]]></xp:this.beforePageLoad>
<xp:text id="testField"
value="#{javascript:viewScope.arrTest.join('; ')">
</xp:text>
This shows
a; b; c; d
You could add HTML around it to pretty it up, and set the display type of the text field to be HTML whcih gives you the opton to join with HTML eg
value="#{javascript:viewScope.arrTest.join('<br>')"
Not as complete a solution as a repeat control, but good for quick things.

Making a viewEntryCollection an objectDataSource

I have some SSJS that does a FTSearch on a view and I get a viewEntryCollection returned:
var veCol:NotesViewEntryCollection = thisAppDB.getView("vwFTSearch").getAllEntries()
veCol.FTSearch(queryString);
viewScope.vsColCount = veCol.getCount();
I know that veCol contains the viewEntries that I want and they are in the correct order. Now I would like to define a dataSource that I will use as the dataSource for a repeat control. I think the answer involves creating an Object Data Source but I can not find any documentation on how to do this.
Any pointers greatly appreciated.
You can use your view entry collection as input for a repeat by doing this:
<xp:repeat id="repeat1" var="rowEntry" removeRepeat="true">
<xp:this.value><![CDATA[#{javascript:
var veCol:NotesViewEntryCollection = thisAppDB.getView("vwFTSearch").getAllEntries()
veCol.FTSearch(queryString);
}]]></xp:this.value>
<!-- add elements to be repeated here -->
</xp:repeat>

xPages DataView and Repeat control

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.

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>

Resources