xPages DataView and Repeat control - xpages

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.

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: Using postNewDocument to populate fields from an agent

I am trying to pre-populate some fields on an XPage (that creates a new doc) using an old LotusScript agent. My code on the XPage is:
<xp:dominoDocument var="document1"
formName="myForm">
<xp:this.postNewDocument><![CDATA[#{javascript:
var agent = database.getAgent("MyAgent");
document1.save();
agent.runOnServer(document1.getNoteID());
}]]></xp:this.postNewDocument>
</xp:dominoDocument>
<xp:inputText value="#{document1.fname}" id="fname"
styleClass="formInputText">
<xp:this.defaultValue><![CDATA[#{javascript:
document1.getItemValueString("fname");}]]></xp:this.defaultValue>
</xp:inputText>
The agent (for this example) is:
Dim agent As NotesAgent
Dim db As NotesDatabase
Sub Initialize
Dim rDoc As NotesDocument
Dim s As New NotesSession
Set db = s.CurrentDatabase
Set agent = s.CurrentAgent
Set rDoc = db.GetDocumentByID(agent.Parameterdocid)
rDoc.fname = "Barney"
rDoc.lname = "Rubble"
Call rDoc.Save(True, True)
End Sub
I know the agent is running (Agent log shows this and the fields are completed on the doc if I check the doc properties in Notes Client) however the field on the XPage is always blank? Is it possible to prepopulate from a LS agent? I added the document1.save() so I know I get a valid NoteID passed over (again which is the same - checked by logging) - any insight gratefully received...
You can pass a document into an agent run. The method passing the unid into the agent context won't get you there. You need
agent.runwithDocumentContext(doc)
See some example here: http://www.wissel.net/blog/d6plinks/SHWL-8SF7AH
Don't save the document. You will want to recast your agent into a JavaBean. Shaves off processing time.
What I actually would do: use a bean as the data source, makes it easier to deal with validation, default values etc.
It is less work than it might look like and it allows to pay down some technical debt (there is always technical debt).
Update
I tried it in a sample application. This is my agent:
Sub Initialize
Dim db As NotesDatabase
Dim rDoc As NotesDocument
Dim s As New NotesSession
Set db = s.CurrentDatabase
Set rDoc = s.Documentcontext
rDoc.fname = "Barney"
rDoc.lname = "Rubble"
End Sub
2 key differences to your code: a) DocumentContext instead of parameterdocid and no saving of the document.
Then the page looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
<xp:this.data>
<xp:dominoDocument var="document1" formName="person">
<xp:this.postNewDocument><![CDATA[#{javascript:var agent = database.getAgent("Background");
var doc = document1.getDocument();
agent.runWithDocumentContext(doc);}]]></xp:this.postNewDocument>
</xp:dominoDocument>
</xp:this.data>
<xp:label value="First name:" id="fName_Label1" for="fName1">
</xp:label>
<xp:inputText value="#{document1.fName}" id="fName1"></xp:inputText>
<br />
<xp:label value="Last name:" id="lName_Label1" for="lName1"></xp:label>
<xp:inputText value="#{document1.lName}" id="lName1"></xp:inputText>
<xp:button value="Save" id="button1"><xp:eventHandler event="onclick" submit="true" refreshMode="complete" immediate="false" save="true"></xp:eventHandler></xp:button>
</xp:view>
Key differences here:
No saving of a document
No population of default values
calling of the agent with DocumentContext
Finally: the agent must be set to "run as web user" (which it is probably already). Works like a charm (and I still would go for a bean).
Be careful with saving the document from different events/sources as you do with your agent. Consider to re-code your agent's code to the postNewDocument event of your datasource of your Xpage. You can set values there, too. If you want to compute fields when creating a new document you can achieve this by setting the values in the postNewDocument event:
datasourceName.setValue("fieldName", "value")
The cause for not getting the fields set by agent in XPage is that the agent runs "too late". The XPage fields are already set by the empty new values. Seems, there is no easy way to refresh the data source after agent execution.
So, I'd suggest to create an additional XPage ("XAgent") "MyFormNew.xsp" which:
creates the document,
runs the agent to set the values in that document and
redirects strait to your original XPage with the documentId as parameter.
The additional XPage could look like this:
<?xml version="1.0" encoding="UTF-8"?>
<xp:view
xmlns:xp="http://www.ibm.com/xsp/core"
rendered="false">
<xp:this.beforePageLoad><![CDATA[#{javascript:
var doc:NotesDocument = database.createDocument();
doc.replaceItemValue("form", "myForm");
doc.save();
var agent = database.getAgent("MyAgent");
agent.runOnServer(doc.getNoteID());
var pageUrl = "MyForm.xsp?action=editDocument&documentId=" +
doc.getUniversalID().toString();
context.redirectToPage(pageUrl);
}]]></xp:this.beforePageLoad>
</xp:view>
The data source in your original XPage wouldn't need the postNewDocument event anymore.
This is a quick and dirty solution for staying with your LotusScript agent (as you pointed out in your comments you want/have to).
Its all about timing. I am using this:
<xp:this.beforePageLoad><![CDATA[#{javascript:var tempdoc:NotesDocument=compositeData.WFDoc.getDocument(true)
if (tempdoc.isNewNote())
{
var agent:NotesAgent=database.getAgent("(A_WF)")
if(agent!=null)
{
agent.runWithDocumentContext(tempdoc)
}
}
}]]></xp:this.beforePageLoad>
By using the before pageload event you can set the document before something might come from the xpage itself. You dont even have to save the document in the agent.
With this approach you can stay in the current document and you dont have to use any temporary documents as suggested by ibm.

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

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>

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.

Resources