documentId resolving code doesn't get executed XPages - xpages

In my XPage I need to set a data source (Domino document)
I try to do it as follows:
<xp:this.data>
<xp:dominoDocument var="requestDocument" action="openDocument" databaseName="#{javascript: print('db ok'); return database.getFilePath();}"
documentId="#{javascript:
print('heloooo');
var conclusion = database.getDocumentByUNID(doc_source.getDocument().getParentDocumentUNID());
var oConclusion = new OsnovaUI_document(conclusion);
var requestDoc = oConclusion.getMainDocument();
print('docID: ' + requestDoc.getUniversalID());
return requestDoc.getUniversalID();
}">
</xp:dominoDocument>
</xp:this.data>
The thing I've noticed is that code section in documentId doesn't get executed. At all. That's why I've put heloooo in there. However, the databaseName works as expected. In console I always see
09.03.2020 00:52:11 HTTP JVM: db ok
But not heloooo :(
What am I doing wrong? Thanks in advance

The most likely cause is ignoreRequestParams is not set to true. Unless you set that, the data source is retrieving all specifics about which document to edit based on the URL query string parameters (the HTTP request parameters). As a result, the URL query string parameters take precedence, and in the case of nothing being set, that means "use a new document". If you've defined that the URL query string parameters should take precedence, running your code to merely ignore it afterwards is inefficient. As a result, action="openDocument" is also being ignored - if you have a docId in the query string it will open that, otherwise it will create a new document.
documentId can only get processed once, when the page first loads. Depending on whether the datasource is bound to a panel or an XPage / Custom Control, it will run before the beforePageLoad event as well. So runtime binding (#{javascript:...) has no effect. ${javascript:... will avoid confusion.
Error handling may help identify if there is an error. XPages OpenLog Logger is one of the most pervasive (disclaimer, I'm the author) https://openntf.org/main.nsf/project.xsp?r=project/XPages%20OpenLog%20Logger.

Change documentId to be computed on page load ($) and not dynamically (#):
<xp:this.data>
<xp:dominoDocument var="requestDocument" action="openDocument">
<xp:this.documentId><![CDATA[${javascript:
var conclusion = database.getDocumentByUNID(doc_source.getDocument().getParentDocumentUNID());
var oConclusion = new OsnovaUI_document(conclusion);
var requestDoc = oConclusion.getMainDocument();
print('docID: ' + requestDoc.getUniversalID());
return requestDoc.getUniversalID();
}]]></xp:this.documentId>
</xp:dominoDocument>
</xp:this.data>
The databaseName is not required if the database is itself.

Related

ssjs to save multiple documents

With the onclick event of a button I would like to save multiple documents, but only the last one gets saved.
<xp:this.data>
<xp:dominoDocument var="document1" formName="tg"></xp:dominoDocument>
</xp:this.data>
and in the onclick event of the button :
...
while (re.next()) {
document1.replaceItemValue("TGARKD",tgarkd);
document1.replaceItemValue("TGKDOM",tgkdom);
document1.replaceItemValue("TGARGR",tgargr);
document1.replaceItemValue("TGDLGR",tgdlgr);
document1.save();
}
If you want to create multiple Documents in the NSF, you'll need to use backend classes. The DominoDocument datasource is tied to a single backend document. var doc = document1.getDocument(true) will get a handle on the (first) backend document, then in your loop use
var doc2 = database.createDocument();
doc.copyAllItems(doc2, true);
doc2.save(true, false);
Alternatively you could define your data source inside a repeat control and bind the fields to that one. Then outside the repeat you call save() which saves all data sources

How to get marked rows in the XPages Extension Library <xe:dataView> design element?

I have a XPage with design element. How can I get list of checked rows to post it to an agent?
<xe:dataView id="dataView1" columnTitles="true"
expandedDetail="true" var="dview1"
openDocAsReadonly="false" rows="15" showCheckbox="true"
showHeaderCheckbox="true">
Thank you!
For the client-side, you can use Dojo. The following CSJS script will return NoteIds for all selected rows:
dojo.query(".lotusFirstCell > input:checked").attr('value')
For the server side, you can grab the IDs of selected documents by:
var idList = getComponent("dataView1").getSelectedIds();
This will return a string array of NoteIDs. Then pass it to an in-memory document and call the agent.
var doc = database.createDocument();
doc.replaceItemValue("IDList", IDList);
var agent:NotesAgent=database.getAgent("SomeAgent");
agent.runWithDocumentContext(doc);
Perfect!
This is exactly what I need:
var IDs = dojo.query(".xspFirstCell > input:checked").attr('value');

XPages Domino Document datasource and documentid : how to trap error?

When we declare a dominoDocument as an XPages datasource, we can specify the documentid programmaticaly. However, I've not found a way to trap the error if the specfied id does not exist. I get an error 500 / Could not open document error on the log.
I would expext to get a null "document1" or something but be able to catch error nicely.
<xp:this.data>
<xp:dominoDocument var="document1" action="openDocument" documentId="some noteId here" formName="Document" ignoreRequestParams="true">
<xp:this.databaseName>...</xp:this.databaseName>
</xp:dominoDocument>
</xp:this.data>
Any hint ?
thanks
You can put the error handling in your code for calculating the documentid.
<xp:this.documentId><![CDATA[#{javascript:
var id = "your calculated id";
try {
database.getDocumentByUNID(id);
} catch(e) {
context.redirectToPage("pageError", true);
}
return id}]]>
</xp:this.documentId>
Like in example above you can open e.g. an error page.

Repeat control with checkbox?

I have a repeat control which displays a view. I now want to include a checkbox, so that I can perform actions on the selected documents. I have no problem adding the check box, but how can I check if the checkbox is checked then get to the associated document for that row?
Thoughts I had were:
Have the check box change event add or remove the document's UNID from a array scope variable. Then just perform actions on documents in that array.
Forget the checkbox and just popup a list box, to allow the user to select from that.
But is there an easier way?
For maximum flexibility, it's best to not bind our user interface components directly to data; rather, if we introduce a middle "data model" layer (typically, one that describes the real-world objects / people / processes the data is representative of, rather than thinking in "documents", which are ultimately just evidence that these real-world things exist), our UI code becomes very clean, easy to understand, and easy to maintain. It also makes it much easier to introduce features that are otherwise frustrating to implement when we continue to think in documents.
Suppose, for instance, that we use the Object Data Source from the Extension Library to create an arbitrary object (for example, let's call it pendingRequests) that we can later bind our repeat control to (instead of binding it directly to the view):
// Create an empty array to return at the end:
var results = [];
// Create a view navigator instance for iterating the view contents:
var pendingView = database.getView("pendingRequests");
var entryNavigator = pendingView.createViewNav();
var eachEntry = entryNavigator.getFirst();
while (eachEntry != null) {
// Add metadata about each entry to result array:
var metaData = eachEntry.getColumnValues();
results.push({
startDate: metaData.get(0).getDateOnly(),
endDate: metaData.get(1).getDateOnly(),
employeeName: metaData.get(2),
status: metaData.get(3),
unid: eachEntry.getUniversalID(),
selected: "0"
});
// In case any column values were Domino objects:
recycleAll(metaData);
// Cruise on to the next:
eachEntry = navigateToNext(entryNavigator, eachEntry);
}
// Final Domino handle cleanup:
recycleAll(entryNavigator, pendingView);
// Return our now populated array:
return results;
Before proceeding, I should point out that the above example includes two pieces of syntactical candy that aren't native to the platform: recycleAll() and navigateToNext(). Both of these are just utility functions for making the stupid recycle stuff easier to handle:
recycleAll
* More convenient recycling
*/
function recycleAll() {
for(var i = 0; i < arguments.length; i++) {
var eachObject = arguments[i];
// assume this is a collection
try {
var iterator = eachObject.iterator();
while (iterator.hasNext()) {
recycleAll(iterator.next());
}
} catch (collectionException) {
try {
eachObject.recycle();
} catch (recycleException) {
}
}
}
}
navigateToNext
/*
* Safe way to navigate view entries
*/
function navigateToNext(navigator, currentEntry) {
var nextEntry = null;
try {
nextEntry = navigator.getNext(currentEntry);
} catch (e) {
} finally {
recycleAll(currentEntry);
}
return nextEntry;
}
Okay, now back to the data model... specifically, this block:
var metaData = eachEntry.getColumnValues();
results.push({
startDate: metaData.get(0).getDateOnly(),
endDate: metaData.get(1).getDateOnly(),
employeeName: metaData.get(2),
status: metaData.get(3),
unid: eachEntry.getUniversalID(),
selected: "0"
});
So for each view entry, we create a very simple object that has all the pertinent info we want to allow the user to interact with, as well as two extra properties that are there for our own code's convenience: unid, which allows to get back to the document if we need to, and selected, which gives us a way to bind a checkbox to a property of this metadata object... which means the user can toggle its value via the checkbox.
So here's a basic example of how we might represent this data to the user:
<ul style="list-style-type: none;">
<xp:repeat var="vacationRequest" value="#{pendingRequests}">
<li style="margin-bottom:10px;">
<strong>
<xp:checkBox value="#{vacationRequest.selected}" text="#{vacationRequest.startDate} - #{vacationRequest.endDate}"
checkedValue="1" uncheckedValue="0" />
</strong>
<xp:text value="#{vacationRequest.employeeName} (#{vacationRequest.status})" tagName="div" />
</li>
</xp:repeat>
</ul>
Each checkbox in the repeat control is now bound directly to the selected property of the metadata object each "row" represents... which also has a unid property, so acting on the actual documents that correspond to this data model is simple:
for (var i = 0; i < pendingRequests.length; i++) {
var eachRequest = pendingRequests[i];
if (eachRequest.selected == "1") {
var requestDataSource = database.getDocumentByUNID(eachRequest.unid);
requestDataSource.replaceItemValue("status", "Approved");
if (requestDataSource.save()) {
// update in-memory metadata:
eachRequest.status = "Approved";
}
}
}
Since our data source is just an array of these metadata objects, we can just loop through each, ask whether the user has toggled the selected property of each, and, if so, get a handle on its corresponding document, modify one or more items, and save it. NOTE: because we're using a data source in this example, it won't reload the back end view data on every event. For performance reasons, this is a Very Good Thing (tm). But it does mean that we have to update the in-memory metadata object to match what we changed on the document (i.e. eachRequest.status = "Approved")... but it also means we can update only that, instead of having to scrap our entire data source and have it read everything back in from the view.
As a bonus, adding something like a "Select All" button is even simpler:
for (var i = 0; i < pendingRequests.length; i++) {
pendingRequests[i].selected = "1";
}
So, in summary, we have an in-memory data model where, in many cases, equivalent operations will execute more rapidly, but also allows us to write less code -- and more readable code -- to do fancier things.
If you want to play with this pattern live (and / or download all of the above source code in context), I've immortalized it here.
Bruce,
I did a Video on selecting documents from a repeat control. I did not use check boxes but used an Add and Remove button and then did some CSS to highlight the selected documents. I'm sure a checkbox could be used with basically the same code as in the add/remove buttons.
Basically I created a java.util.ArrayList in memory to hold the unids and then populated that array when a repeat row is clicked on. I computed the CSS for each row and if that UNID exists in the Array I change the background color to show it's "selected". I don't actually show any processing on the selected unids but since that array is in scoped memory you can pretty much do anything you want with it. Anyway the video demo is here:
http://notesin9.com/index.php/2011/04/01/notesin9-025-selecting-documents-from-a-repeat-control/
As always, Tim's answer is your best bet for long-term health, sanity, and code-maintainability.
There's also another route I've taken in the past, before I started using Java for all of my back-end logic. You can create a page-load-bound dataContext containing a HashMap and then bind each checkbox to that - it will fill in true or false for each key, so you can then loop through the map entries and find the ones that are true, which are the checked values.
I put together a quick example that pulls in the list from the names database to show what I mean:
<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
<xp:this.data>
<xp:dominoView var="names" databaseName="names.nsf" viewName="$NamesFieldLookup"/>
</xp:this.data>
<xp:this.dataContexts>
<xp:dataContext var="checkedNames" value="${javascript: new java.util.HashMap() }"/>
</xp:this.dataContexts>
<xp:div id="refresher">
<xp:repeat value="#{names}" var="name" rows="3">
<xp:this.facets>
<xp:pager xp:key="header" id="pager1" layout="Previous Group Next" />
</xp:this.facets>
<div>
<xp:checkBox value="#{checkedNames[name.$9]}">
<xp:eventHandler event="onclick" submit="true" refreshMode="partial" refreshId="refresher"/>
</xp:checkBox>
<xp:text value="#{name.$9}"/>
</div>
</xp:repeat>
<p><xp:text value="#{checkedNames}"/></p>
</xp:div>
</xp:view>

save() method on datasource does not fire querySave/postSave events

My save button uses SSJS with some logic. I want to save datasource, so I use
document1.save();
Script works, but querySave/postSave code is not executed.
Only workaround is to use simple action and divide button event to blocks for "execute script", "Save document (simple action)" and "execute script" (just to return "navigation" string).
Is it possible to save datasource in SSJS and fire qS/pS events?
please try this SSJS code:
var dsName = "document1.DATASOURCE";
var app = facesContext.getApplication();
var ds = app.getVariableResolver().resolveVariable(facesContext, dsName);
ds.save( facesContext, true );
The variable dsName contains the name of your datasource followed by ".DATASOURCE". To use it f.e. with current document, you have to change to "currentDocument.DATASOURCE".
Hope this helps
Sven
Sven what is the difference between your code and currentDocument.save() is something else happening than querysave and postsave?

Resources