I am looking to create an Xpage self registration site that registers users onto the Domino name and address book. I am just doing a proof of concept.
I will put the code below, but it is a fairly simple matter of capturing the user details, dropping their details and password into the NAB and then, hey presto the user should be able to immediately log into the app.nsf.
At the moment I am manually putting the user into a group that is listed in the ACL as manager on app.nsf (for testing, I am putting them in the group prior to creating the user - just mentioning it in case it is important).
It basically works, BUT, there is a rather large delay. As in, it takes many minutes and sometimes more. After some research I discovered the console command "show nlcache reset" and a lotusscript/java/javascript code version of it. But it seems to have no effect, either coded or manually from the console - (there is also no response from the console that the command has been initiated, just a new line, is this normal?).
The only quirky thing is that the OU=99123456789 (or something similar, it is a company identifier). So a user will look something like this Fred Citizen/99123456789/Domain (don't think this should matter). The user will however log in as "Fred Citizen" and password.
Any ideas?
We are running 9.0.1
Thanks in advance.
Cheers
Damien
Code Below:
<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
<xp:this.data>
<xp:dominoDocument var="userreg" databaseName="names.nsf"
formName="Person">
</xp:dominoDocument>
</xp:this.data>
<fieldset class="userreg">
<label for="FirstName">First Name</label>
<xp:inputText id="FirstName" value="#{userreg.FirstName}">
</xp:inputText>
<br />
<label for="LastName">Last Name</label>
<xp:inputText id="LastName" value="#{userreg.LastName}">
</xp:inputText>
<br />
<label for="CompanyName">Company Name - ABN</label>
<xp:inputText id="CompanyName" value="#{userreg.CompanyName}">
</xp:inputText>
<br />
<label for="Level0_1">Service ID</label>
<xp:inputText id="Level0_1" value="#{userreg.Level0_1}">
</xp:inputText>
<br />
<label for="HTTPPassword">Password</label>
<xp:inputText id="HTTPPassword" value="#{userreg.HTTPPassword}"
password="true">
</xp:inputText>
<br />
<label for="InternetAddress">Email Address</label>
<xp:inputText id="InternetAddress" value="#{userreg.InternetAddress}">
</xp:inputText>
<br />
<xp:text escape="true" id="type" value="#{userreg.type}"
rendered="false">
</xp:text>
<xp:button value="Register" id="userreg_submit">
<xp:eventHandler event="onclick" submit="true"
refreshMode="complete">
<xp:this.action>
<xp:actionGroup>
<xp:actionGroup>
<xp:actionGroup>
<xp:modifyField name="type" value="Person">
</xp:modifyField>
<xp:modifyField name="FullName">
<xp:this.value><![CDATA[#{javascript:var fullNameArray = new Array();
var first = getComponent("FirstName").getValue();
var last = getComponent("LastName").getValue();
var abn = getComponent("CompanyName").getValue();
fullNameArray[0]= "CN=" + first + " " + last + "/OU=" + abn + "/O=RR1";
fullNameArray[1] = first + " " + last;
return fullNameArray;
}]]></xp:this.value>
</xp:modifyField>
<xp:saveDocument></xp:saveDocument>
</xp:actionGroup>
</xp:actionGroup>
</xp:actionGroup>
</xp:this.action>
</xp:eventHandler>
</xp:button>
<xp:br></xp:br>
<xp:br></xp:br></fieldset></xp:view>
I had the same issue with my portal users registering and have been able to over come this with the following code. It's virtually instant. This code is in my register button after my bean creates the user in the nab and updates all the groups, acl etc....
sessionAsSigner.sendConsoleCommand( session.getServerName(), "lo updall yourNabDBName -t ($VIMGroups)" );
sessionAsSigner.sendConsoleCommand( session.getServerName(), "lo updall yourNabDBName -t ($Users)" );
sessionAsSigner.sendConsoleCommand( session.getServerName(), "lo updall yourNabDBName -t ($ServerAccess)" );
sessionAsSigner.sendConsoleCommand( session.getServerName(), "sh nl r" );
For a lotuscript version of this you need to manually refresh a couple of views in the nab and then it worked.
Dim nabFullNameView As NotesView
Dim nabServerAccessView As NotesView
Dim nabUsersView As notesview
Set nabUsersView = dbNAB.GetView("($Users)")
Set nabFullNameView = dbNAB.GetView("($LDAPCN)")
Set nabServerAccessView = dbNAB.GetView("($ServerAccess)")
Call nabFullNameView.Refresh
Call nabServerAccessView.Refresh
Call nabUsersView.Refresh
'Closing session commits person document to NAB.
Call s.Close
Related
From examples I've seen in the Mastering XPages book and around the web, an xp:dataContext variable should be writable but I can't get it to work.
Below is a simplified version of my code. A button calls an xe:dialog containing a panel with a dataContext variable attended and initial value of "", expecting to be able to write to it from the radioGroup data binding. When the radio is set to a certain value (in this case, != "Yes") I want to unhide a div but it doesn't work. The div does not appear and the txt1 computed text field never changes to reflect the value of the radio.
I have tried both an onclick and onchange event handler but neither seems effective. Are dataContext variables read-only?
<?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:button id="btn" value="Button">
<xp:eventHandler event="onclick" submit="true" refreshMode="partial">
<xp:this.action><![CDATA[#{javascript:getComponent('dlg').show()}]]></xp:this.action>
</xp:eventHandler>
</xp:button>
<xe:dialog id="dlg" title="Dialog">
<xp:panel id="panel1" readonly="false">
<xp:this.dataContexts>
<xp:dataContext var="attended" value="" />
</xp:this.dataContexts>
<xp:radioGroup id="activityAttended" value="#{attended}">
<xp:selectItem itemLabel="Yes" />
<xp:selectItem itemLabel="No" />
<xp:selectItem itemLabel="Maybe" />
<xp:eventHandler id="event1" event="onclick" submit="true"
refreshMode="partial" refreshId="target" />
</xp:radioGroup>
<xp:div id="target">
radio: <xp:text id="txt1" value="#{attended}" />
<xp:div id="div1" rendered="#{attended != 'Yes'}">
<xp:text id="txt2" value="#{attended}" />
</xp:div>
</xp:div>
</xp:panel>
</xe:dialog>
</xp:view>
To my knowledge, you can't assign to the variable name for a dataContext. What you could do would be to make the value bound to something that contains another value, like a Map or custom object, and then alter properties on that.
For example, if you made it like var="contextProps" value="${javascript:new java.util.HashMap()}", you could then do things like #{contextProps.attended != 'Yes'} and value="#{contextProps.attended}". Usually, you'll want a more-fleshed-out object than a generic HashMap for this, but it can serve as an example or a useful container in a pinch.
I am trying to write a fileUpload/fileDownload custom control with a Bootstrap like look. I am fairly happy with the look (see below).
However, I am getting very inconsistent behavior. I would very much like the user to be able to click the delete button and the attachment is removed and the repeat control is refreshed. In a similar fashion if the user selects "Upload" I think the attachment should be uploaded to the document and the repeat control refreshed as well.
I am storing the attachments in a single document that is separate from the main document [it is stored in a separate db from the code].
I am using the js fileInput library with the upload control.
I am using a repeat control to roll my own file download viewer.
The code works some of the time, but not always. When the page fails I get this error message (see below).
Any suggestions would be greatly appreciated.
Context Path: /scoApps/OTM1/OTM1.nsf
Page Name: /xpTest.xsp
javax.faces.FacesException
at com.sun.faces.lifecycle.ApplyRequestValuesPhase.execute(ApplyRequestValuesPhase.java:106)
at com.sun.faces.lifecycle.LifecycleImpl.phase(LifecycleImpl.java:210)
at com.sun.faces.lifecycle.LifecycleImpl.execute(LifecycleImpl.java:96)
at com.ibm.xsp.controller.FacesControllerImpl.execute(FacesControllerImpl.java:256)
at com.ibm.xsp.webapp.FacesServlet.serviceView(FacesServlet.java:228)
at com.ibm.xsp.webapp.FacesServletEx.serviceView(FacesServletEx.java:157)
at com.ibm.xsp.webapp.FacesServlet.service(FacesServlet.java:160)
at com.ibm.xsp.webapp.FacesServletEx.service(FacesServletEx.java:138)
at com.ibm.xsp.webapp.DesignerFacesServlet.service(DesignerFacesServlet.java:103)
at com.ibm.designer.runtime.domino.adapter.ComponentModule.invokeServlet(ComponentModule.java:576)
at com.ibm.domino.xsp.module.nsf.NSFComponentModule.invokeServlet(NSFComponentModule.java:1335)
at com.ibm.designer.runtime.domino.adapter.ComponentModule$AdapterInvoker.invokeServlet(ComponentModule.java:853)
at com.ibm.designer.runtime.domino.adapter.ComponentModule$ServletInvoker.doService(ComponentModule.java:796)
at com.ibm.designer.runtime.domino.adapter.ComponentModule.doService(ComponentModule.java:565)
at com.ibm.domino.xsp.module.nsf.NSFComponentModule.doService(NSFComponentModule.java:1319)
at com.ibm.domino.xsp.module.nsf.NSFService.doServiceInternal(NSFService.java:662)
at com.ibm.domino.xsp.module.nsf.NSFService.doService(NSFService.java:482)
at com.ibm.designer.runtime.domino.adapter.LCDEnvironment.doService(LCDEnvironment.java:357)
at com.ibm.designer.runtime.domino.adapter.LCDEnvironment.service(LCDEnvironment.java:313)
at com.ibm.domino.xsp.bridge.http.engine.XspCmdManager.service(XspCmdManager.java:272)
Caused by: java.lang.IllegalArgumentException
at javax.faces.model.ListDataModel.getRowData(ListDataModel.java:141)
at com.ibm.xsp.component.UIRepeatContainer.addIndexedDataContext(UIRepeatContainer.java:173)
at com.ibm.xsp.component.UIRepeatContainer.getDataContexts(UIRepeatContainer.java:86)
at com.ibm.xsp.util.DataPublisher.getDataContextList(DataPublisher.java:349)
at com.ibm.xsp.util.DataPublisher.revokeControlData(DataPublisher.java:271)
at com.ibm.xsp.component.UIDataPanelBase.revokeControlData(UIDataPanelBase.java:319)
at com.ibm.xsp.component.UIDataPanelBase.processDecodes(UIDataPanelBase.java:334)
at javax.faces.component.UIComponentBase.processDecodes(UIComponentBase.java:1177)
at com.ibm.xsp.component.UIRepeat.processDecodes(UIRepeat.java:526)
at javax.faces.component.UIComponentBase.processDecodes(UIComponentBase.java:1177)
at com.ibm.xsp.component.UIDataPanelBase.processDecodes(UIDataPanelBase.java:331)
at javax.faces.component.UIForm.processDecodes(UIForm.java:166)
at javax.faces.component.UIComponentBase.processDecodes(UIComponentBase.java:1177)
at javax.faces.component.UIComponentBase.processDecodes(UIComponentBase.java:1177)
at javax.faces.component.UIViewRoot.processDecodes(UIViewRoot.java:343)
at com.ibm.xsp.component.UIViewRootEx._processDecodes(UIViewRootEx.java:1438)
at com.ibm.xsp.component.UIViewRootEx.processDecodes(UIViewRootEx.java:1399)
at com.sun.faces.lifecycle.ApplyRequestValuesPhase.execute(ApplyRequestValuesPhase.java:98)
<?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">
<xp:this.data>
<xp:dominoDocument var="document1" databaseName="scoApps\OTM1\OTM1Data.nsf" formName="attachment" action="editDocument" documentId="349CDB2FB259D5D3862581090076AC50" />
</xp:this.data>
<script src="fileinput/js/fileinput.js" type="text/javascript" />
<link href="fileinput/css/fileinput.min.css" media="all" rel="stylesheet" type="text/css" />
<xp:scriptBlock id="scriptBlockInitFile">
<xp:this.value><![CDATA[$(document).ready(
function() {
$('input[type=file]').fileinput({
previewFileType: "image",
browseClass: "btn btn-primary",
browseLabel: "Browse...",
browseIcon: '<i class="glyphicon glyphicon-plus"></i>',
removeClass: "btn btn-danger",
removeLabel: "Delete",
removeIcon: '<i class="glyphicon glyphicon-trash"></i>',
uploadClass: "btn btn-info",
});
}
);]]></xp:this.value>
</xp:scriptBlock>
<xp:div themeId="container" style="width:800px">
<xp:br />
<xp:fileUpload id="fileUpload1" value="#{document1.attachments}">
<xp:this.attrs>
<xp:attr name="multiple" value="true" />
<xp:attr name="data-show-preview" value="false" />
</xp:this.attrs>
</xp:fileUpload>
<xp:br />
<xp:button value="Save Changes" id="button1" styleClass="btn btn-primary">
<xp:eventHandler event="onclick" submit="true" refreshMode="complete" disableValidators="true">
<xp:this.action>
<xp:actionGroup>
<xp:save />
</xp:actionGroup>
</xp:this.action>
</xp:eventHandler>
</xp:button>
</xp:div>
<xp:repeat rows="30" id="attrepeat" first="0" var="att" indexVar="attachmentIndex">
<xp:this.facets>
<xp:text disableTheme="true" xp:key="header" escape="false">
<xp:this.value><![CDATA[<table class="table table-striped table-bordered table-hover"><col width="150"><col width="450"><th>File Name</th><th style="text-align:right">Delete</th>]]></xp:this.value>
</xp:text>
<xp:text disableTheme="true" xp:key="footer" escape="false">
<xp:this.value><![CDATA[</table>]]></xp:this.value>
</xp:text>
</xp:this.facets>
<xp:this.value><![CDATA[#{javascript:var bckDoc = document1.getDocument()
var attachments:java.util.Vector = session.evaluate("#AttachmentNames",bckDoc);
attachments}]]></xp:this.value>
<xp:tr>
<xp:td>
<xp:link escape="true" id="link1" target="_blank" text="#{javascript:att.toString();}">
<xp:this.value><![CDATA[#{javascript:var tmpStr:String;
var str:String;
var bckDoc = document1.getDocument()
var attachments:java.util.Vector = session.evaluate("#AttachmentNames",bckDoc);
tmpStr = attachments.toString;
tmpStr
var bckDoc = document1.getDocument()
var attachments:java.util.Vector = session.evaluate("#AttachmentNames",bckDoc);
tmpStr = attachments.elementAt(0);
var unid = "349CDB2FB259D5D3862581090076AC50"
var dbPath = "scoApps/OTM1/OTM1.nsf/"
var dbDataPath = "scoApps/OTM1/OTM1Data.nsf/"
var url = "http://localhost/";
url += dbPath;
url += "/xsp/.ibmmodres/domino/OpenAttachment/";
url += dbDataPath + "/";
url += unid
url += "/" + "attachments" + "/";
url += tmpStr;
url}]]></xp:this.value>
<xp:image id="image1" rendered="false">
<xp:this.url><![CDATA[#{javascript:var pdfImage = 'pdf.gif';
//if(attachment.indexOf("pdf")> 0)
return pdfImage; }]]></xp:this.url>
</xp:image>
 
</xp:link>
</xp:td>
<xp:td style="text-align:right">
<xp:button value="Delete" id="button2" styleClass="btn btn-danger">
<xp:eventHandler event="onclick" submit="true" refreshMode="complete">
<xp:this.action><![CDATA[#{javascript:function removeAttachment(targDB,doc_Url,fileName,docUnid2) {
//passing parameters by reference
var docUrl:String= doc_Url;
var targetDB:NotesDatabase = session.getDatabase(session.getCurrentDatabase().getServer(),targDB);
var attachmentName:String =fileName;
//deducing the document's unid from the document's url
//docUrl=#Left(docUrl,"?");
//var docUnid=#RightBack(docUrl,"/");
print (docUnid2);
//setting the handle to the document
var docContext:NotesDocument=targetDB.getDocumentByUNID(docUnid2);
if (docContext==null) {
viewScope.CodeError="Either the UNID is invalid or the target db does not contain the doc or both";
return;
}
//getting the handle to the concerned attachment
var embObj:NotesEmbeddedObject=docContext.getAttachment(attachmentName);
if (embObj==null) {
viewScope.CodeError="No attachment is found by the name "+ attachmentName;
return;
}
//remove the attachment
embObj.remove()
docContext.save(true,false);
}
var fleNme = att.toString();
print (fleNme);
removeAttachment("scoApps/OTM1/OTM1Data.nsf","http://localhost/scoApps/OTM1/OTM1.nsf//xsp/.ibmmodres/domino/OpenAttachment/scoApps/OTM1/OTM1Data.nsf//349CDB2FB259D5D3862581090076AC50/attachments/ITReport.xlsx",fleNme,"349CDB2FB259D5D3862581090076AC50");}]]></xp:this.action>
</xp:eventHandler>
</xp:button>
</xp:td>
</xp:tr>
</xp:repeat>
</xp:view>
It's failing in the ApplyRequestValues phase, something related to running code for rows within the repeat. I've had that before in a Data View with using the caching was ID only. The key is identifying which component's code is failing (the value of the repeat, the link, the image or the button - if it's a component, which property) and whether it needs to run in that phase. It may not.
Looking at the stack trace, the "caused by" bit failing on getRowData() implies it might be a problem with value of the repeat, but I'm not totally sure. It could be one of the repeat's child components that's failing.
If it's a read-only bit of code, it could be wrapped in an if (view.isRenderingPhase()) block. Just make sure you output something valid for other phases! E.g. for a rendered property, set the result for other phases to true (I've missed that before and caused myself problems!)
If it's because the document datasource has not been initialised at that phase, wrapping everything in a Panel may solve the problem, by associating the dominoDocument datasource to be a child of the Panel, which may encourage it to be better re-initialised during the restoreView phase. This is a total stab in the dark, but I know during page load a dominoDocument datasource gets loaded at a different time when it's a child of a Panel as opposed to a child of the XPage itself.
I have a custom control with an editbox in a modal:
<xp:inputText id="inputText1"></xp:inputText>
In the onclick event of a button , I would like to read the value of this editbox
var demo = getComponent("inputText1").getValue();
Of course this doesn't work in a custom control, since he doesn't has a handle to inputText1. How can this be done ?
EDIT
Herewith I'm posting my 'whole' code.
Even with a scoped variable it isn't working .... :(
<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
<xp:this.resources>
<xp:styleSheet href="bootstrap-modal.css"></xp:styleSheet>
<xp:script src="/bootstrap-modalmanager.js" clientSide="true"></xp:script>
<xp:script src="/bootstrap-modal.js" clientSide="true"></xp:script>
<xp:script src="/JQueryXSnippet.js" clientSide="true"></xp:script>
</xp:this.resources>
<div id="Modal1" class="modal fade" tabindex="-1" data-focus-on="input:first" style="display: none;">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h4 class="modal-title">Modal One</h4>
</div>
<div class="modal-body">
<p>Modal 1 </p>
<div class="form-group">
<label for="inputText1">First Name:</label>
<xp:inputText id="inputText1" value="#{viewScope.input1}">
<xp:this.attrs>
<xp:attr name="class" value="form-control"></xp:attr>
<xp:attr name="data-tabindex" value="1"></xp:attr>
</xp:this.attrs>
</xp:inputText>
</div>
<xp:button value="Demo" id="button2">
<xp:eventHandler event="onclick" submit="true"
refreshMode="partial" refreshId="demoPanel">
<xp:this.action><![CDATA[#{javascript:viewScope.message = "input1 = "+viewScope.input1;}]]></xp:this.action>
</xp:eventHandler>
</xp:button>
<xp:panel id="demoPanel">
<xp:text escape="true" id="computedField1"
value="#{viewScope.message}">
</xp:text>
</xp:panel>
</div>
<div class="modal-footer">
<button type="button" data-dismiss="modal" class="btn btn-primary">Close</button>
</div>
</div>
<xp:scriptBlock id="scriptBlock1">
<xp:this.value><![CDATA[
$(document).ready(function(){
x$("#{id:button1}").click(function(){
x$("#{id:Modal1}").modal(
{backdrop: true,
keyboard: false,
show: true
}
);
});
});
]]></xp:this.value>
</xp:scriptBlock>
<xp:button value="Login" id="button1" styleClass="btn btn-info btn-lg">
</xp:button>
</xp:view>
The advice from Tim Tripcony was to go to the model layer, not the component. As ever, he was spot on. Bind the Edit Box to something, probably in this scenario a viewScope variable. Then retrieve the value from that. It's likely to be more efficient than getComponent().
There are two ways that spring to mind are worth trying:
Add the button to the custom control and that way, it'll just work as is.
Set the id to be computed from a custom property of the control control-id and pass in a string e.g. my-made-up-id and then refer to it from any button out-with the custom control. Note the ${} binding is needed when computing the id.
Custom Control
<xp:inputText
id="${javascript:compositeData.control-id}">
</xp:inputText>
XPage
<xc:FieldTest control-id="my-made-up-id"></xc:FieldTest>
<xp:button
value="Get Value"
id="button1">
<xp:eventHandler
event="onclick"
submit="true"
refreshMode="complete">
<xp:this.action><![CDATA[#{javascript:var demo = getComponent("my-made-up-id").getValue();
print("Demo: " + demo);}]]></xp:this.action>
</xp:eventHandler>
</xp:button>
I'm not sure if I am missing out a very simple thing. Normally, XPages maintain "aria-required" and "aria-invalid" attributes for validation.
However, for DateTime Picker (standard one), it's always aria-invalid="false".
Here is a simple test I have used in Domino 9.0.1:
<?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="TestForm"></xp:dominoDocument>
</xp:this.data>
<xp:button value="Label" id="button1">
<xp:eventHandler event="onclick" submit="true"
refreshMode="partial" refreshId="panel1">
</xp:eventHandler>
</xp:button>
<xp:panel id="panel1">
<xp:inputText id="inputText1" required="true">
<xp:dateTimeHelper id="dateTimeHelper1"></xp:dateTimeHelper>
<xp:this.converter>
<xp:convertDateTime type="date"></xp:convertDateTime>
</xp:this.converter>
</xp:inputText>
<xp:messages id="messages1"></xp:messages>
</xp:panel>
</xp:view>
Generated HTML before the button clicked contains:
<input type="text" aria-haspopup="true" role="textbox" data-dojo-attach-point="textbox,focusNode" autocomplete="off" class="dijitReset dijitInputInner" aria-invalid="false" tabindex="0" aria-required="true" id="view:_id1:inputText1" value="">
After clicking, I can see Messages component has been aggregated but aria-invalid is false.
<input type="text" aria-haspopup="true" role="textbox" data-dojo-attach-point="textbox,focusNode" autocomplete="off" class="dijitReset dijitInputInner" aria-invalid="false" tabindex="0" aria-required="true" id="view:_id1:inputText1" value="">
(I removed dojo stuff wrapping the input)
After some digging, I found that this problem is not related to XPages.
XPages provides the aria-invalid attribute correctly if validation fails. However, dojo controls automatically change it during rendering. So it's not possible unless you inject a correction into Dojo component rendering code.
How to create link to another database using link control? I thought that I simply start URL with slash but it doesnt work as xpages prepends path to current database to this link and I'm getting wrong link as this one: /app/projects.nsf/database.nsf/page.xsp
<xp:link escape="true" text="Link" id="link1" value="/database.nsf/page.xsp">
</xp:link>
I know that I can create absolute link starting with http:// but I would like to avoid this ...
You can use the xp:text element and convert it to an anchor:
<xp:text escape="true" id="link1" tagName="a" value="Link">
<xp:this.attrs>
<xp:attr name="href" value="/database.nsf/page.xsp"></xp:attr>
</xp:this.attrs>
</xp:text>
Or add the link as Passthrough Tag.
If the database is in the same sub-directory on the server, you can use ../xxx.nsf
For every sub-folder you want to go back, you can prepend a ../
Example in your case:
<xp:link escape="true" text="Link" id="link1" value="../database.nsf/page.xsp">
</xp:link>
If the database would be one folder above the current databases' folder you can use this:
<xp:link escape="true" text="Link" id="link1" value="../../database.nsf/page.xsp">
</xp:link>
Hope that helps.
Michael
To build on Sven's answer, I computed the URL, and set the target to be a new tab.
So my xp:text element is:
<xp:text id="label4" escape="true"
styleClass="btn btn-default" value="Open Fastworks Document"
tagName="a">
<xp:this.attrs>
<xp:attr name="href">
<xp:this.value><![CDATA[#{javascript:var sUNID = document1.getItemValueString("FWUNID");
var sNSF = document1.getItemValueString("FWNSF").replace("\\","/");
//sys_all/A4DC4CFDA12A1A4E80257F48003DD8F9?OpenDocument
"/"+sNSF + "/sys_all/"+sUNID;}]]></xp:this.value>
</xp:attr>
<xp:attr name="target"
value="_blank">
</xp:attr>
</xp:this.attrs>
</xp:text>
This then produces HTML such as :
<a class="btn btn-default" id="view:_id1:_id2:callback2:label4" href="/Fastworks/Version52m/Accident.nsf/sys_all/31F7D581D23BCFE580257FA1002E3B43" target="_blank">Open Fastworks Document</a>