XPages: Using postNewDocument to populate fields from an agent - xpages

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.

Related

Changing multiple data in a view

I need help using Xpages. If there vas a question like this or there is an example can you tell me where to find it.
I have a main page that contains several rows and columns, like this
[Checkbox][Column1][Column2][Column3][Column 4][Column 5]
[Checkbox][Data1][Data2][Data3][Data4][Data5]
[Checkbox][Data1][Data2][Data3][Data4][Data5]
[Checkbox][Data1][Data2][Data3][Data4][Data5]
[Checkbox][Data1][Data2][Data3][Data4][Data5]
[Checkbox][Data1][Data2][Data3][Data4][Data5]
Now Data 5 is a default value and it is Undefined, the only values other that it can be set is to „Do“ or „Do not“. Is there a way to Chek several rows for which i vant to change the value to Do or Do not in Data 5 using combobox and save button, without going into detail view of the data.
For example, i have 30 rows that have Data 5 set to undefined, and i want 10 of them to set to Do. I just chek them in checkbox, select in combobox Do and press Save. So i don't have to endet evry row and edit them one by one.
Adapt the XSnippet Simple "Trash" Folder c/w Selectable Rows & Restore Functionality to your needs.
<xp:comboBox
id="comboBox1"
value="#{viewScope.DoOrDont}">
<xp:selectItem itemLabel="Do"></xp:selectItem>
<xp:selectItem itemLabel="Do not"></xp:selectItem>
</xp:comboBox>
<xp:button id="button4" value="Change selected rows">
<xp:eventHandler event="onclick" submit="true"
refreshMode="complete">
<xp:this.action><![CDATA[#{javascript:
var vp1 = getComponent("viewPanel1");
if(null != vp1){
var dm:com.ibm.xsp.model.domino.DominoViewDataModel = vp1.getDataModel();
if(null != dm){
var idsIter = dm.getSelectedIds();
while(null != idsIter && idsIter.hasNext()){
var id = idsIter.next();
if(null != id){
var doc:NotesDocument = database.getDocumentByID(id);
if(null != doc){
doc.replaceItemValue("yourField", viewScope.DoOrDont);
doc.save();
}
}
}
}
}}]]></xp:this.action>
</xp:eventHandler>
</xp:button>
The example contains a combobox where you can select "Do" or "Do not" and a button which sets this value to all selected documents.
Not sure I got the whole picture of your requirements, but here is one way to do it.
When you "tick" a checkbox, use the server side "onclick" event to set a sessionScope containing the noteid and include any value you want to update the document with. set the refresh id to the checkbox.
For each checkbox the user ticks, a new noteid is added to the sessionScope.
When the user is ready to save the changes using the save button, loop the unids in the sessionScope and update the documents in the background and clear the sessionScope
Using this approach you can also provide a "next" page that show all the documents in the scope and allow the user to confirm the save
you might also want to add a keepalive so the users scope is not discarded
The best way for that is to combine client side and server side code. Here is what I would do in this case :
Add a StyleClass to the checkboxes. In order to do so click on a checkbox, go to all properties and find StyleClass in the list. For example let's put this class name : myCheckboxClass. With this class name you will be able to retrieve easily all the checkboxes checked on the client.
Add also a custom attribute to the checkboxes that contains the unid of the document related to the row. To do so click on a checkbox, go to all properties again and find attr. Add a new attr called for example unid and compute its value to be the unid of the row. This attribute will allow you to know from the client to which document this checkbox is linked to.
Now on your save button, with a client side script, you can really easily get all the documents to modify with the following :
var documentsToUpdate = [];
var checkboxes = document.getElementsByClassName("myCheckboxClass");
for (var i = 0; i < checkboxes.length; i++) {
var checkbox = checkboxes[i];
if (checkbox.checked) {
documentsToUpdate.push[checkbox.getAttribute("unid")];
}
}
// Send the array, in a JSON format, to the server
return XSP.toJson(documentsToUpdate);
Now on your save button, with this time the server side script, retrieve the information from the client, and use the array of unids to change the value you want in the selected documents :
// Get the return value from the client
var jsonDocumentsToUpdate = context.getSubmittedValue();
// Change the JSON into an array
var documentsToUpdate = fromJson(jsonDocumentsToUpdate);
// Go through the array and update the documents
for (var i = 0; i < documentsToUpdate.length; i++) {
var doc:NotesDocument = database.getDocumentByUNID(documentsToUpdate[i]);
// DO YOUR CHANGES
doc.save();
}
Now you have everything to do what you need. You just have to check what the user chose in the combo box and to do the proper changes according to it. I think it's really important to learn this approach combining client side and server side code, that can be apply a lot in Xpages to do what you want quickly.

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.

XPages - save date only in Date field

I'm using an Edit Box control to display a date field. When the XPage is saved, I would like to save the date only (now both date and time are being saved). Is there any way of doing this?
Here is my code:
<xp:inputText id="dateReparatur" value="#{document1.dateReparatur}">
<xp:this.converter>
<xp:convertDateTime type="date" dateStyle="long">
</xp:convertDateTime>
</xp:this.converter>
<xp:dateTimeHelper></xp:dateTimeHelper>
</xp:inputText></xp:td>
UPDATE: I have now implemented the following code:
var dt = currentDocument.getItemValueDateTime("dateReparatur");
var dateonly = dt.getDateOnly();
currentDocument.replaceItemValue("dateReparatur",dateonly);
This gives me the date only, however in Notes the field type is now text rather than Date/Time, which is what I was hoping for.
This code worked for me:
<xp:this.postSaveDocument><![CDATA[#{javascript:
var dt:DateTime = document1.getItemValueDateTime("dateReparatur");
dt.setAnyTime();
currentDocument.getDocument(true).replaceItemValue("dateReparatur", dt);
currentDocument.getDocument(true).save()
}]]></xp:this.postSaveDocument>
It does work at postSaveDocument event only. If you put the same code into querySaveDocument event (without document save() line of course) the date field gets polluted with time after event during saving.
An alternative is to execute computeWithForm at querySaveDocument event:
<xp:this.querySaveDocument><![CDATA[#{javascript:
document1.getDocument(true).computeWithForm(true, true)
}]]></xp:this.querySaveDocument>
You'd have to add an Input Translation formula to date field(s) in your form:
#Date(#ThisValue)
computeWithForm has a poor performance and causes sometimes side effects on field values though but might be a good solution especially if you have a lot of such date-only-fields.
getDateOnly() returns a string. Try this:
dt.setAnyTime();
currentDocument.replaceItemValue("dateReparatur", dt);
Or you may have to get the Document:
currentDocument.getDocument(true).replaceItemValue("dateReparatur", dt);

Domino 9 has a new boolean converter for checkbox group controls - anyone get this working?

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??

Resources