Xpages Navigator Menu - xpages

My database has a nice navigator that is dynamic (although it is not a dynamic navigator - I don't understand what that is).
I have some sessionScope variables that I load. The variables are driven by the user entering data in an administration page.
The user enters Apps (Application Bar), Titles (Title Bar) - tied to one or more Apps, and finally Pages, tied to one or more titles.
I use repeats in the nav control for the Pages, then a container node below that for the description, and then another repeat for the categories of the docs in the page, and then a basic node for the views. It works well.
What I want is for some sessionScope.page variables to have a special attribute to indicate that they will not be views of documents, but clicking on that page in the navigator will present one document to the user and they can edit or save it.
I cannot figure out how to do it.
So in the screen shot below, if the user clicks "Best Practices" it should just open a doc in the middle pane, NOT an entry with "All" below it.
I think this might be easier in a repeat control, but an open to any suggestions.
Thanks!
<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core"
xmlns:xe="http://www.ibm.com/xsp/coreex">
<xp:this.resources>
<xp:styleSheet href="/menus.css"></xp:styleSheet>
<xp:styleSheet href="/buttons.css"></xp:styleSheet>
</xp:this.resources>
<xp:this.beforePageLoad><![CDATA[#{javascript:sessionScope.filterContent = "All";
setTags(sessionScope.selectedPage);}]]></xp:this.beforePageLoad>
<xp:panel>
<xe:navigator id="navigator1">
<xe:this.treeNodes>
<xe:repeatTreeNode var="entry" indexVar="index">
<xe:this.value><![CDATA[#{javascript:var tmpKey:String = sessionScope.selectedTitle;
sessionScope.render[tmpKey]}]]></xe:this.value>
<xe:this.children>
<xe:basicContainerNode
submitValue="#{javascript:entry}" style="font-size:10pt">
<xe:this.label><![CDATA[#{javascript:sessionScope.descriptions[entry]}]]></xe:this.label>
<xe:this.children>
<xe:repeatTreeNode var="entry2"
indexVar="index2">
<xe:this.value><![CDATA[#{javascript:var tmpArr = [];
tmpArr.unshift("All");
tmpArr2 = #Unique(#Trim(#DbColumn(#DbName(),sessionScope.selectedPage,1)));
if (tmpArr2.length == 0)
{var tmpArr3 = tmpArr}
else
{var tmpArr3 = tmpArr.concat(tmpArr2)}
tmpArr3}]]></xe:this.value>
<xe:this.children>
<xe:basicLeafNode
label="#{javascript:entry2}"
submitValue="#{javascript:entry2}" style="font-size:8pt">
<xe:this.rendered><![CDATA[#{javascript:if (sessionScope.selectedPage == entry)
{return true}}]]></xe:this.rendered>
<xe:this.selected><![CDATA[#{javascript:if (sessionScope.filterContent == entry2)
{return true}
else
{return false}}]]></xe:this.selected>
</xe:basicLeafNode>
</xe:this.children>
</xe:repeatTreeNode>
</xe:this.children>
</xe:basicContainerNode>
</xe:this.children>
</xe:repeatTreeNode>
</xe:this.treeNodes>
<xp:eventHandler event="onItemClick" submit="true"
refreshMode="complete">
<xe:this.action><![CDATA[#{javascript:var tmpStr:String = context.getSubmittedValue();
var act:String;
if (tmpStr.substring(0,4) == "page")
{act = "page"}
if (tmpStr.substring(0,4) != "page")
{act = "cat"}
switch (act)
{
case "page":
// Do something
sessionScope.selectedPage = tmpStr;
sessionScope.filterType = "Categories";
sessionScope.filterContent = "All";
setTags(sessionScope.selectedPage);
viewScope.tag = "";
break;
case "cat":
// Do something else
sessionScope.filterType = "Categories";
sessionScope.filterContent = tmpStr;
viewScope.tag = "";
break;
default:
// Default case
break;
}
</xp:view>

Create a session scope variable "type". It defines for every entry a type "doc" or "view".
Depending on entry's type render a basicLeafNode for just the menu entry (in your example "Best Practices") or a basicContainerNode/basicLeafNode like before. Use property rendered:
<xp:panel>
<xe:navigator id="navigator1">
<xe:this.treeNodes>
<xe:repeatTreeNode var="entry" indexVar="index"
value="#{javascript:sessionScope.render[sessionScope.selectedTitle]}">
<xe:this.children>
<xe:basicLeafNode
rendered="#{javascript:sessionScope.type[entry] === 'doc'}"
label="#{javascript:sessionScope.descriptions[entry]}"
submitValue="#{javascript:entry}">
</xe:basicLeafNode>
<xe:basicContainerNode
rendered="#{javascript:sessionScope.type[entry] === 'view'}"
label="#{javascript:sessionScope.descriptions[entry]}"
submitValue="#{javascript:entry}"
style="font-size:10pt">
<xe:this.children>
<xe:repeatTreeNode
var="entry2"

Related

XPages: image onCclick needs 2 clicks to do what is expected

I have a custom control that contains 2 images: add and remove from favorites.
There is some code on the onCLick event of the image, and both images use a SSJS function to see if the current document is already in the favorites or not, in the visible property of each image.
All works well, execpt that I need to click twice on the image in order to see the changes in the UI. Both onClick events are set to FullUpdate (also tried partial update with the panel that contains the images).
I could move all the favorites logic into session scope variables, but I think this should work as is. I just don't understand why I need to click twice, as if the partial refresh doesn't do anything (though it is, as I see the reload Arrow in the browser!).
Can it be that the code takes too long to execute and the refresh doesn'T get the updated info???
Here's the custom control code:
<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core" xmlns:xe="http://www.ibm.com/xsp/coreex">
<xp:this.resources>
<xp:script src="/AppUtils.jss" clientSide="false"></xp:script>
</xp:this.resources>
<xp:image url="/favorites-add.png" id="image1"
alt="Add to Favorites" title="Add to Favorites">
<xp:this.rendered><![CDATA[#{javascript:!isInFavorites(document1.getDocument().getUniversalID(), userBean.canonicalName);
}]]></xp:this.rendered>
<xp:eventHandler event="onclick" submit="true" refreshMode="complete">
<xp:this.action><![CDATA[#{javascript:addToFavorites(document1.getDocument().getUniversalID(),userBean.canonicalName);}]]></xp:this.action>
</xp:eventHandler>
</xp:image>
<xp:image url="/favorites-remove.png" id="image2"
alt="Remove from Favorites" title="Remove from Favorites">
<xp:this.rendered><![CDATA[#{javascript:isInFavorites(document1.getDocument().getUniversalID(),userBean.canonicalName);
}]]></xp:this.rendered>
<xp:eventHandler event="onclick" submit="true"
refreshMode="complete">
<xp:this.action><![CDATA[#{javascript:removeFromFavorites(document1.getDocument().getUniversalID(),userBean.canonicalName);}]]></xp:this.action>
</xp:eventHandler>
</xp:image>
</xp:view>
And here's the SSJS:
function addToFavorites(unid, userName) {
var newDoc:NotesDocument = database.createDocument();
newDoc.appendItemValue("Form","Favorite");
newDoc.appendItemValue("UserName", userName);
newDoc.appendItemValue("DocUNID", unid);
newDoc.save();
return;
}
function removeFromFavorites(unid, userName) {
var notesView:NotesView = database.getView("(lkpFavorites)");
var keys = new java.util.Vector();
keys.addElement(userName);
keys.addElement(unid);
var coll:NotesDocumentCollection = notesView.getAllDocumentsByKey(keys, true);
if(coll.getCount()==1) {
coll.getFirstDocument().remove(true);
}
return;
}
function isInFavorites(unid, userName) {
var notesView:NotesView = database.getView("(lkpFavorites)");
var keys = new java.util.Vector();
keys.addElement(userName);
keys.addElement(unid);
var coll:NotesDocumentCollection = notesView.getAllDocumentsByKey(keys, true);
if(coll.getCount()>0) {
return true;
} else {
return false;
}
}
I'd suggest you to put an xp:link around your image to cast the event code instead of using the evennts of the image directly.
OK, not sure what happened there but I manually edited the custom control's source to see wheter I had empty eventHandler as suggested by Oliver, and it started to work as expected. I am totally unsure of what I changed: to my knowledge, all I did was to add extra "returns" in the source view, to make it more readable... Christmas gift I guess.
All is good now. Thanks to all :)

xpages get UNID for a document/datasource

I'm using Mark Hughes picklist on my xpage which is using a datasource. The view from which I pick the values is listing documents having another datasource.
I put the selected value into an <xp:inputText>. I do want to create a link which should redirect me to the listed document from the view ( from it I took the value ). In other words, I do want to find out the UNID of the document from the view, which I did selected it.
I tried the following code for the ssjsSelectFunction:
var unid = viewScope.unid;
if(typeof unid != "undefined" && unid != null)
{
var doc = database.getDocumentByUNID(unid);
var val1 = doc.getItemValueString("txt_numeAcord_1");
var val2 = doc.getUniversalID();
getComponent("inputText24").setValue(val1);
getComponent("inputText25").setValue(val2);
}
But after selecting the desired doc. from the picklist, only inputText25 is updated with the value ( UNID ), the inputText24 is empty. Only if I open again the picklist and select the doc., the inputText24 field value is added. I guess I'm missing something.
How can I achieve this?
My xpage code:
<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core"
xmlns:xc="http://www.ibm.com/xsp/custom"
xmlns:xe="http://www.ibm.com/xsp/coreex" >
<xp:this.data>
<xp:dominoDocument var="Contr" formName="(fmFormularCIP)"></xp:dominoDocument>
<xp:dominoView var="view1" viewName="vwAcord"></xp:dominoView>
</xp:this.data>
<xp:panel id="AcordCadru">
   
<xp:br></xp:br>
  
<xp:checkBox text="Acord cadru" id="checkBox6"checkedValue="Da" uncheckedValue="Nu" value="#{Contr.chkAcord}">
<xp:eventHandler event="onchange" submit="true"
refreshMode="partial" refreshId="AcordCadru">
</xp:eventHandler>
</xp:checkBox>
 
<xp:inputText id="inputText24" value="#{Contr.acord}">
</xp:inputText>
<xc:viewpicklist rowsPerPage="10"
buttonImage="./folder_explore.png" tableClass="tablecellgreen"
headerClass="headerclass" rowClass="odd, even" searchBar="false"
searchButtonText="Search" searchButtonClass="button2"
searchBarClass="headerclass" pagerStyleFirst="navbutton1"
pagerStylePrevious="navbutton2" pagerStyleCurrent="navbutton4"
pagerStyleNext="navbutton2" pagerStyleLast="navbutton3"
typeAheadBar="false" select="UNID" onReturn="Set Scope Value"
bottomBarClass="bottomround headerclass" cancelButtonText="Cancel"
cancelButtonClass="button2 floatthisright" type="Single Value"
finishButtonText="Finish" finishButtonClass="button2 floatthisright"
multiSelectButtonAddImg="./add.png"
multiSelectButtonRemoveImg="./delete.png"
picklistButtonClass="button" openDialogWith="Button"
picklistLinkImg="./add.png" multiSelectLinkAddImg="./add.png"
multiSelectLinkRemoveImg="./delete.png" selectWith="Button"
multiValueSeparator="," clearSearchImg="./cross.png"
SelectCellWidth="30px" dialogID="dialog1"
dialogTitle="Alegeti nr. acord cadru" fieldName="inputText24"
refreshID="AcordCadru" datasrc="view1" selectColumn="0"
varName="viewScope.unid">
<xc:this.viewColumn>
<xp:value>0</xp:value>
<xp:value>1</xp:value>
<xp:value>2</xp:value>
<xp:value>3</xp:value>
<xp:value>4</xp:value>
</xc:this.viewColumn>
<xc:this.ssjsSelectFunction><![CDATA[#{javascript:
var unid = viewScope.unid;
if(typeof unid != "undefined" && unid != null)
{
var doc = database.getDocumentByUNID(unid);
var val1 = doc.getItemValueString("txt_numeAcord_1");
var val2 = doc.getUniversalID();
Contr.setValue("acord",val1);
Contr.setValue("sv",val2);
}}]]></xc:this.ssjsSelectFunction>
</xc:viewpicklist>
<xp:br></xp:br>
   
<xp:inputText id="inputText25" value="#{Contr.sv}">
</xp:inputText>
</xp:panel>
</xp:view>
inputText24 doesn't get the selected value because the execution of ssjsSelectFunction's code is too late. It gets executed during the refresh of panel "AcordCadru" caused by parameter refreshID. All fields positioned in front of xc:viewpicklist get refreshed before ssjsSelectFunction's code execution. That's why inputText24 doesn't get the new selected value but inputText25 which comes after xc:viewpicklist does.
If you put inputText24 behind xc:viewpicklist then it will get the new selected value.
But, probably you want to have field inputText24 first and the picklist button xc:viewpicklist after. For this
delete the ssjsSelectFunction code
add a computed field in front of inputText24 with pretty the same code
<xp:text
escape="true"
id="computedField1">
<xp:this.value><![CDATA[#{javascript:
var unid = viewScope.unid;
if (unid && unid !== Contr.sv) {
var doc = database.getDocumentByUNID(unid);
var val1 = doc.getItemValueString("txt_numeAcord_1");
Contr.setValue("acord",val1);
Contr.setValue("sv",unid);
}
return "";
}]]></xp:this.value>
</xp:text>
It will set the new selected values to document and made them visible in inputText24 and inputText25 right away.

Xpages repeat control division by zero

I'm using a repeat control displaying names. The content is displayed proberly, but I needed a pager because there can be more than 30 entries.
As soon as I add a pager for this repeat control and press a button doing a partial refresh on the site I get a "division by zero" error.
Here's my repeat control:
<xp:repeat id="repeat1" rows="30" var="namesList" repeatControls="true" indexVar="rowIndex">
<xp:table>
<xp:tr>
<xp:td>
<xp:link escape="true" id="Employee">
<xp:this.text><![CDATA[#{javascript:namesList.getItemValueString("Employee")}]]></xp:this.text></xp:link>
</xp:td>
</xp:tr>
</xp:table>
<xp:this.value><![CDATA[#{javascript:try {
var wfDoc:NotesDocument = docApplication.getDocument(true);
var dcNoAnswer:NotesDocumentCollection = database.createDocumentCollection();
var count:Integer = 0;
//Gets all response documents, but only adds those to the document collection, without a valid
//response to the cycle
if (wfDoc.getResponses() != null) {
var dc:NotesDocumentCollection = wfDoc.getResponses();
var doc:NotesDocument = dc.getFirstDocument();
while (doc != null) {
if (doc.getItemValueString("Response") == "") {
dcNoAnswer.addDocument(doc);
}
doc = dc.getNextDocument();
}
dc.recycle();
}
docApplication.replaceItemValue("MissingResponsesCount", dcNoAnswer.getCount());
wfDoc.recycle();
return dcNoAnswer;
} catch(e) {
dBar.error("Show no response repeat control data: " + e);
}}]]></xp:this.value>
</xp:repeat>
And here's my pager:
<xp:pager layout="Previous Group Next" id="pager3" for="repeat1"></xp:pager>
As I said, doing a partial refresh on the site oder panel, I'll get this error:
I've been looking for an error for some time, but I can't see anything wrong here. Did anybody else have such a problem and maybe has a solution?
You have repeatControls="true" set. That means "create the controls once and never again", so you can't use a pager to navigate, as mentioned here. Remove repeatControls="true" and it will work.
If you look at the stack trace, it will fail on getStart().
Try adding alwaysCalculateLast="false" to your pager. The repeat control could have 0 values (because of dcNoAnswer) and maybe you have set xsp.repeat.allowZeroRowsPerPage=true in your xsp properties settings.

Using view.postscript in xpages

I have a xpage that use view.postscript like this:
<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
<xp:this.resources>
<xp:dojoModule name="dojox.widget.Toaster"></xp:dojoModule>
<xp:styleSheet href="/.ibmxspres/dojoroot/dojox/widget/Toaster/Toaster.css"></xp:styleSheet>
</xp:this.resources>
<xp:scriptBlock id="scriptBlock1">
<xp:this.value><![CDATA[var Toaster = function(id, msg, type, duration, pos) {
type = (type == null) ? "message" : type;
duration = (duration == null) ? "5000" : duration;
pos = (pos == null) ? "br-up" : pos;
var obj = dijit.byId(id);
obj.positionDirection = pos;
obj.setContent(msg, type, duration);
obj.show();
}]]></xp:this.value>
</xp:scriptBlock>
<xp:div id="toaster" themeId="toaster" dojoType="dojox.widget.Toaster"></xp:div>
<xp:button value="Toaster" id="button1">
<xp:eventHandler event="onclick" submit="true"
refreshMode="complete">
<xp:this.action><![CDATA[#{javascript:view.postScript("Toaster('"+getComponent("toaster").getClientId(facesContext)+"', 'toaster message!')");}]]></xp:this.action>
</xp:eventHandler>
</xp:button>
</xp:view>
But when I click button,the toaster can't show.Why?
And I need a way to show the toaster called by ssjs function.
Thanks a lot.
As #Frantisek Kossuth said, you're re-submitting the page. When your refreshMode is complete it will re-render the page based on the server values. If you want to make client side changes and reflect them in your UI, you can't do a refresh of complete. Doing a partial refresh of the page on an element that won't change (like button1) should update your UI without rebuilding the page.
Similarly, why do you need this to be done through SSJS? Why not just use Client actions in your button activation? If this isn't part of a larger function call that you're not including, simply change the event code from Server to Client and call the Toaster function that way. This would also allow you to not have to submit anything, leaving submit="false" in your eventHandler to avoid unnecessary functionality.

Onclick event in Outline gets executed on page open

I have an extension library outline control. I have two basicLeafNodes in the outline. The onclick event on each of these nodes is supposed to run some code. The problem is both of those onClick events are being executed when the page is open but does not seem to execute when you actually click on the node.
Any idea what could be wrong?
<xe:outline id="outline1">
<xe:this.treeNodes>
<xe:basicLeafNode label="Set Value 1">
<xe:this.onClick><![CDATA[#{javascript:getComponent("inputText1").value = "123";}]]></xe:this.onClick>
</xe:basicLeafNode>
<xe:basicLeafNode label="Set Value2">
<xe:this.onClick><![CDATA[#{javascript:getComponent("inputText2").value = "456";}]]></xe:this.onClick>
</xe:basicLeafNode>
</xe:this.treeNodes>
</xe:outline>
<xp:br></xp:br>
<xp:br></xp:br>
<xp:br></xp:br>Value 1: 
<xp:inputText id="inputText1"></xp:inputText>
<xp:br></xp:br>Value 2: 
<xp:inputText id="inputText2"></xp:inputText>
The onClick event of the basicLeafNode is for client-side JS only. You need to use the submitValue property of each basicLeafNode to and then add SSJS to the onItemClick event of the outline control. You can then use context.getSubmittedValue() to check what node was clicked and then act accordingly:
<xe:outline id="outline1">
<xe:this.treeNodes>
<xe:basicLeafNode label="Set Value 1" submitValue="1"></xe:basicLeafNode>
<xe:basicLeafNode label="Set Value2" submitValue="2"></xe:basicLeafNode>
</xe:this.treeNodes>
<xe:this.onItemClick><![CDATA[#{javascript:
if (context.getSubmittedValue() == "1") {
getComponent("inputText1").value = "123"
} else if (context.getSubmittedValue() == "1") {
getComponent("inputText2").value = "456"
}
}]]></xe:this.onItemClick>
</xe:outline>
From the XPages Extension Library book (page 240):
The onClick property allows the developer to execute a piece of
Client-Side JavaScript code, and the submit- Value property allows the
developer to specify a value that is passed back to the server. This
value is accessed from the onItemClick event of the control that
contains the tree nodes.

Resources