I am building an application that will have a parent documents with up to 5 different child documents attached to the parent. Each parent can have one and only 1 of each of the 5 types of child documents.
In traditional notes programming I would probably put an embedded view within the parent documents form. I was starting down that path when I wondered if this wouldn't be better done in Xpages with one Xpage that has multiple datasources.
A complicating factor is that each form/data source will have a different group of people who own it - only they can create or edit it. But I don't think this would be a problem.
Couple of ideas to consider ...
don't use response docs ... tie the related docs together via a document key like unid
put the create/edit function for the five child docs in their own separate panels ... childpanel1, childpanel2, etc.
add the datasources for the child docs in the respective panels ... childdoc1, childdoc2, etc
restrict edit access via the acl property for each panel
with multiple data sources on an XPage remember to set ignoreRequestParams="true" for the childdoc data sources
For example, here's the panel structure for the first child doc:
<xp:panel id="childpanel1">
<xp:this.data>
<xp:dominoDocument var="childdoc1" formName="ChildForm1"
ignoreRequestParams="true">
</xp:dominoDocument>
</xp:this.data>
<xp:this.acl>
<xp:acl>
<xp:this.entries>
<xp:aclEntry type="GROUP" name="doc1group" right="EDITOR">
</xp:aclEntry>
</xp:this.entries>
</xp:acl>
</xp:this.acl>
</xp:panel>
More on ignoreRequestParams:
In a two-page application involving a "view" XPage and a "document" XPage, when the application user clicks a link on the view page to open the document on the document page the ID for the document to open is passed with the REQUEST parameters. You can see this in the resulting URL for the document page which will have the ID for the document to open appended to the URL address, for example:
&documentId=49530CA58D17CCE5852575150069D857&action=openDocument
This works perfectly when the document page has only one Domino document data source. However, in the embedded view application the "document" XPage will also include a data source for the xp:viewPanel. When ignoreRequestParams is NOT true for the Domino view data source (and for any other additional data sources on the page, like childdoc1) then the parameters passed in the request ARE evaluated to determine which view entries to display in the xp:viewPasnel. These request parameters point to a UNID that is not the desired data source for the view so no view entries are displayed.
Note, you may not need to set ignoreRequestParams to true for ALL of the data sources on the XPage ... just be aware of this setting and what it does when things get wonky for one of the view or document data sources on the page.
Not sure if your question is how to limit the form creation or how to have something like an embedded view in xPages. Both are easier to do in xPages.
Basically you just need to make the children responses of the parent. I prefer to not use normal response documents and prefer instead to add a field called uid with the unid of the parent to the children. I then filter my datasource to match the uid of the parent document. You can use view controls for this but repeats look even better.
Its a great place to start using dialogs as you can have the second data source in the dialog and the user never needs to leave the page.
As far as restricting the view creation you can do something like hide the button to create a new child based on a criteria.
This action will add the unid of document1 to document3
<xp:modifyField name="uid"
value="#{javascript:document1.getDocument().getUniversalID()}" var="document3">
</xp:modifyField>
Here is the code I use for a repeat control datasource. The view is categorized by uid field
<xp:dominoView var="view7" viewName="VIEWNAME"
keys="#{javascript:document1.getDocument().getUniversalID()}">
</xp:dominoView>
Then the repeat control just grabs the fields you want.
<xp:table styleClass="table">
<xp:tr>
<xp:td>Column Header</xp:td>
<xp:td>Column Header</xp:td>
<xp:td>Column Header</xp:td>
</xp:tr>
<xp:repeat id="repeat3" rows="30" value="#{view7}"
var="rowData">
<xp:tr>
<xp:td>
<xp:text escape="true"
id="computedField3" value="#{rowData.columnname1}">
</xp:text>
</xp:td>
<xp:td>
<xp:text escape="true"
id="computedField4" value="#{rowData.columnname2}">
</xp:text>
</xp:td>
<xp:td>
<xp:text escape="true"
id="computedField5" value="#{rowData.columnname3}">
</xp:text>
</xp:td>
</xp:tr>
</xp:repeat>
</xp:table>
Ignore some of the styleclasses as I am using bootstrap.
Related
I am using the current newest ExtLibs, and a Domino 9.0.1 Server with FP3.
I am trying set up my own multi level categorized view with repeat controls that are getting data from an SQLite Database, and I am hitting a snag with the pager controls.
Structure
I have a primary Repeat Control that displays 5 categories at a time (Sections). This is connected to the primary pager.
Inside this repeat control, I have a panel that has another repeat control for the actual data. (In some cases I put another section to create multiple levels with another repeater...)
* The problem
When I have multiple pages on the primary repeater and I start out at page one, then in one embedded pager, I select page 2, then I select a different page on the primary pager, then all embedded pagers are automatically reset to page 2. It seems to me as though the request scope variable that controls the embedded pager number is shared among all repeated instances.
What is the best way to go about solving this problem? And am I barking up the wrong tree by repeating repeaters to begin with?
******************************************* Stand alone XPage for reproduction
<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
<xp:pager layout="Previous Group Next" partialRefresh="true"
id="pager1" for="repeat1" panelPosition="left">
</xp:pager>
<xp:br></xp:br>
<xp:br></xp:br>
<xp:repeat id="repeat1" rows="5" var="primaryList">
<xp:this.value><![CDATA[#{javascript:var list:java.util.ArrayList = new java.util.ArrayList();
list.add("1")
list.add("2")
list.add("3")
list.add("4")
list.add("5")
list.add("6")
list.add("7")
list.add("8")
list.add("9")
list.add("10")
list.add("11")
list.add("12")
list.add("13")
list.add("14")
list.add("15")
list.add("16")
list.add("17")
list.add("18")
return list;}]]></xp:this.value>
<xp:section id="section1" header="#{javascript:primaryList}">
<xp:br></xp:br>
<xp:panel style="padding:0px 0px 5px 40px">
<xp:pager layout="Previous Group Next"
partialRefresh="true" id="pager2" for="repeat2"
panelPosition="left">
</xp:pager>
<xp:br></xp:br>
<xp:br></xp:br>
<xp:repeat id="repeat2" rows="5" var="innerRepeat">
<xp:this.value><![CDATA[#{javascript:var list:java.util.ArrayList = new java.util.ArrayList();
list.add("1")
list.add("2")
list.add("3")
list.add("4")
list.add("5")
list.add("6")
list.add("7")
list.add("8")
list.add("9")
list.add("10")
list.add("11")
list.add("12")
list.add("13")
list.add("14")
list.add("15")
list.add("16")
list.add("17")
list.add("18")
return list;}]]></xp:this.value>
<xp:inputText id="inputText1"
value="#{javascript:innerRepeat.toString();}">
</xp:inputText>
<xp:br></xp:br>
</xp:repeat>
</xp:panel></xp:section>
</xp:repeat>
</xp:view>
steps to reproduce
be on page one on primary pager.
in any sub pager, select page 2.
Notice correctness of other pagers
Switch main pager to page 3
Notice all sub pagers on on page 2
I don't think it's a bug. It's working as designed (!)...
The problem is that the repeat does not create multiple pagers inside for each iteration. So there is basically only one pager and that pager keeps only one PagerState. That's why they are acting weird.
When you use repeatControls="true" option, repeat will not iterate only one component, instead it will really create multiple components for each iteration. However, in that case, you can't use Pager component for such a repeat, because the new repeat component does not have an iterator anymore. It will not be able to find 'pagination' and will throw 'division of zero' error.
Here, I have explained the difference between two modes of Repeat component (from slide #36):
https://speakerdeck.com/sbasegmez/engage-2015-10-mistakes-you-and-every-xpages-developer-make-yes-i-said-you?slide=36
The solution would be creating your own pager design for inner repeats.
Update:
Another approach is marking the outer Repeat as repeatControls="true" and removing the outer pager. In such a configuration you'll have multiple pager components in the component tree but you have to sacrifice the outer pager. In this configuration, you'll need to load all elements in the outer repeat (because they can't be actively manipulated anymore).
To mimic the pager behaviour you can design a custom pager and use rendered attribute for inner repeats. It will slow the page down, but will provide 'paging' feeling to the user.
I have been given an XPages project which I did not develop. The project has a OneUILayout that includes a Search Bar "facet". Is it possible to code a filter into the search bar facet so that retrieved records are omitted that have a field with a certain value. I have very little experience with XPages. The search results are output to a OneUI_searchpage.xsp where an edit box displays the search string then a dynamic View Panel shown the retrieved records. I have attached the source code for these two items below. Thank you
<xp:label value="Search String:" id="label1"></xp:label>
<xp:inputText id="inputText1" value="#{param.search}"></xp:inputText>
<xp:panel id="maincontentpanel">
<xe:dynamicViewPanel rows="30" id="dynamicViewPanel1"
width="100%">
<xe:this.data>
<xp:dominoView viewName="ContractsFlatByYear"
var="view">
<xp:this.search><![CDATA[#{javascript:return
param.search;}]]></xp:this.search>
</xp:dominoView>
</xe:this.data>
</xe:dynamicViewPanel>
After some consultation with stwissel below, I amended the application to have a check box on the search results xpage with it checked by default and created an additional view for the same output. One view to show cancelled contracts and one to omit cancelled contracts. The relevant Xpage section now looks like as follows;
<xp:checkBox text="Omit Cancelled Contracts"
id="OmitCancelled" defaultChecked="true" checkedValue="True"
uncheckedValue="False" style="padding-left:5.0em" value="#
{viewScope.viewSel}">
<xp:eventHandler event="onchange" submit="true" refreshMode="partial"
refreshId="dynamicViewPanel1"></xp:eventHandler>
</xp:checkBox>
<xp:panel id="maincontentpanel">
<xe:dynamicViewPanel rows="30" id="dynamicViewPanel1"
width="100%" partialRefresh="true">
<xe:this.data>
<xp:dominoView var="view">
<xp:this.viewName>
<![CDATA[#{javascript:var cancelledYesNo = viewScope.viewSel
= getComponent("OmitCancelled").getValue();
if(cancelledYesNo == "True"){
viewName = "ContractsFlatByYear"}
else {
viewName = "ContractsFlatByYearandCancelled"}}]]>
</xp:this.viewName>
<xp:this.search><![CDATA[#{javascript:return param.search;}]]
></xp:this.search>
</xp:dominoView>
</xe:this.data>
</xe:dynamicViewPanel>
This appears to work but I have the check box onChange event to apply a partial refresh on the dynamicviewpanel but only refreshes when I click on the dynamicviewpanel itself
The search bar facet only captures what you want to search and sends it to the specified XPages for processing.
You have 2 options:
Alter the facet to send the additional condition to the search page
Alter the search function in the search page (the one the query gets posted to) to filter that (if it is static).
Be aware: filtering in code is not a security feature (in case you were intending that). There's reader and author fields for that.
XPages in its core is JSF with some specialities around Domino. You might want to check out my article series on them.
Update
Based on the code snippet, your can get the desired result quite fast. Edit the view selection formula and add & conContractStatus <> "cancelled". You need to check first if that view is used elsewhere to show canceled contracts too. If that is the case, copy the view (eg add Active behind the name) and make the changes there.
Update 2
Your code doesn't return a value and you don't need to get to the component
<xp:this.viewName>
<![CDATA[#{javascript:return (viewScope.viewSel=="True") ? "ContractsFlatByYear" : "ContractsFlatByYearandCancelled";}]]>
</xp:this.viewName>
Let us know how it goes
I have a XPage with an data source attached to a notes document. On this XPage I have a button that calls method in a managed bean. This method retrieves a notes document from another notes database. This notes document has some attachements in a richtext field. These attachements shall be copied to my XPage.
Is there a way to achieve this?
I know how to retrieve the attachments from the notes document. But how can I attach them to my XPage?
For my task I can't use the File Upload control, because the "File Upload" should happend automaticly within my mananged bean.
I found something call AttachmentHolderValue that may fulfil my task, but unfortunately there is no documentation for this class.
Ok for the donwload display you could use code like this:
<xp:this.data>
<xp:dominoDocument var="yourNewDocument"></xp:dominoDocument>
</xp:this.data>
<xp:panel>
<xp:this.data>
<xp:dominoDocument ignoreRequestParam="true" var="download" action="openDocument"
databaseName="otherDatabase" documentId="calculated">
</xp:dominoDocument>
</xp:this.data>
<xp:fileDownload rows="30" id="fileDownload1"
displayLastModified="false" value="#{download.richTextItem}">
</xp:fileDownload>
</xp:panel>
This will add you Document from the other Database as a dominoDocument dataSource wich you can use to bind a <xp:fileDownload> to.
Then you could add something to select the Attachments what you want to copy to your new Document for that i would recoment a repeatcontrol and use it like:
<xp:repeat id="repeat1" rows="30"
value="#{javascript:download.getAttachmentList('richTextItem');}"
var="attachment">
<xp:panel>
<xp:label value="#{javascript:attachment.getName() }" id="label1"></xp:label>
<xp:br></xp:br></xp:panel></xp:repeat>
Insted of a label you could add a checkbox and in the onSave event you can run some code wich copys the selected elements to the new Document (have to look it up in my app).
You can copy them Using NotesDocument.copyAllItems() or saving the attachments to your server disk and add them with ritem.embedObject to your new's documents richTextItem. But i recomend doing the attachment copy in backend in my expirience working with attachments can be a very tricky part.
In a purchase order module, we need to ask certain questions depending on the source selection method, competition type and total cost of the PO. These questions are likely to change over time and in between different instances of the database.
So I have a view containing the questions, so that I can add questions dynamically to my XPage without needing to change the code. The answer to each question will be stored in a field. So, the document that contains the question has a field called FieldName that is used to supply the field name that will be used. Unfortunately, I am having no luck binding these dynamic fields to the document.
<xp:this.data>
<xp:dominoView var="competitionQuestionView"
viewName="CompetitionQuestions">
</xp:dominoView>
</xp:this.data>
<xp:repeat id="repeat2" rows="30" var="rowData" style="width:700px"
value="#{competitionQuestionView}">
<xp:label id="label1">
<xp:this.value><![CDATA[#{javascript:rowData.getColumnValue("Question");}]]></xp:this.value>
</xp:label>
<xp:inputText id="inputText1">
<xp:this.rendered><![CDATA[#{javascript:rowData.getColumnValue("FieldType") == "Text Box"; }]]></xp:this.rendered>
<xp:this.value><![CDATA[#{javascript:poDoc[rowData.getColumnValue ("FieldName")];}]]></xp:this.value>
</xp:inputText>
</xp:repeat>
I've tried various ways to do this, including making a dynamicInputText custom control to pass in the field name, but without luck. The closest I got was when I used this:
<xp:this.value>
<![CDATA[#{javascript:tmp = rowData.getColumnValue ("FieldName");'#{poDoc.'+tmp+'}';}]]>
</xp:this.value>
That gave me something like #{poDoc.justification}, which was what I wanted to pass to the 'binding', but it ended up displaying as the text value.
I did try using $ to compute the value on load, but I am guessing that it didn't work because my (and the rowData) view is not available on load. That would eventually present a problem when I wanted to use partial refreshes due to updates on the criteria for which fields I want to display anyway.
Some of the answers to other questions looked promising, but no code was provided, so I couldn't figure it out.
Behind the scenes, all data sources use the methods getValue and setValue to (respectively) read and write data. In the case of a Domino document data source, the expression #{currentDocument.fieldName} gets translated at runtime to either currentDocument.getValue('fieldName') or currentDocument.setValue('fieldName', postedValue), depending on whether the current operation is a read or a write.
If you set the value attribute of an otherwise editable component to a SSJS value binding, then it can't do this auto-translation... so it just treats every operation as a read.
In other words, for read/write to work, it has to be a "prefixless" expression.
There are several ways to handle this, but the easiest is to use a data context to map a SSJS expression to a single variable. Data contexts can be attached to the view root or to a panel, so in your example, you'd want to wrap your repeat contents in a panel:
<xp:repeat id="repeat2" rows="30" var="rowData" style="width:700px"
value="#{competitionQuestionView}">
<xp:panel>
<xp:this.dataContexts>
<xp:dataContext var="fieldName">
<xp:this.value><![CDATA[#{javascript:rowData.getColumnValue ("FieldName");}]]></xp:this.value>
</xp:dataContext>
</xp:this.dataContexts>
<xp:label id="label1">
<xp:this.value><![CDATA[#{javascript:rowData.getColumnValue("Question");}]]> </xp:this.value>
</xp:label>
<xp:inputText id="inputText1" value="#{poDoc[fieldName]}">
<xp:this.rendered><![CDATA[#{javascript:rowData.getColumnValue("FieldType") == "Text Box"; }]]></xp:this.rendered>
</xp:inputText>
</xp:panel>
</xp:repeat>
So for each member of the repeat, the variable fieldName becomes the column value for that row. Then in the value attribute of the input component, the array syntax is used (instead of the usual dot syntax) since we want to use a variable to specify the field name instead of hardcoding the name.
In theory, however, you should be able to skip the data context entirely, and just set the following to be the value expression for the field:
#{poDoc[rowData.FieldName]}
In the context of the default ("prefixless") EL resolver, rowData.FieldName should return precisely the same value that rowData.getColumnValue("FieldName") returns in the context of a SSJS expression.
Finally, I would recommend reading this Expression Language tutorial to become familiar with all of the things that you can do in core EL without resorting to SSJS.
I'm using the Extension Library and XPages to build a web based workflow application. One feature is to prompt a dialog and select the next approver before submitting it forward.
For new documents, the dialog appears, and it also appears when in read mode. It never opens for a saved document. I need it to work in SSJS, though the dialog does open in CSJS. Here is some of the code:
xpMain.xsp contains two custom controls: ccButtons and ccWFloDialogs.
ccButtons "Submit" button:
getComponent('dlgNextOwner').show();
ccWFloDialogs "dlgNextOwner" dialog:
<xe:dialog id="dlgNextOwner" title="Select Supervisor">
<xe:formTable id="ftDlgNextOwner" formTitle="Select Supervisor"
formDescription="You may select a different supervisor."
disableErrorSummary="true">
<xp:this.facets>
<xe:formRow id="formRow1" xp:key="footer" for="txtWFloNextOwner"
labelWidth="125px">
<xp:inputText id="txtWFloNextOwner" style="width:200px"
value="#{viewScope.nextOwner}">
</xp:inputText>
<xe:namePicker id="npNextOwner" dialogTitle="Select Supervisor"
for="txtWFloNextOwner">
<xe:this.dataProvider>
<xe:dominoNABNamePicker addressBookSel="all-public"></xe:dominoNABNamePicker>
</xe:this.dataProvider>
</xe:namePicker>
</xe:formRow>
</xp:this.facets>
</xe:formTable>
</xe:dialog>
In CSJS, I can use the following code and it successfully opens the dialog:
XSP.openDialog("#{id:dlgNextOwner}");
I'm also using Mark Hughes' picklist from the extension library, and if I remove the panel which contains the control and data source, the dialog shows up in SSJS! The code is in a custom control, although, if it's directly in the XPage, the problem is the same. Here is the code for the panel:
<xp:panel
id="vendorDiv">
<xp:this.data>
<xp:dominoView
var="nvVend"
viewName="V_Vend"
ignoreRequestParams="true"
databaseName="other/lookupdb.nsf"
startKeys="#{javascript:viewScope.srchVend}">
</xp:dominoView>
</xp:this.data>
<xe:formTable
id="ftGetVendor"
disableErrorSummary="true"
labelPosition="above">
<xe:formRow
id="frVendor"
for="cfVendorName"
label="Name of the Vendor:">
<xp:text
escape="true"
id="cfVendorName"
value="#{currentDocument.VendorName}"
style="width:200px">
</xp:text>
<xc:viewpicklist
rowsPerPage="15"
buttonImage="./add.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="true"
select="Column"
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="Link"
picklistLinkImg="./add.png"
multiSelectLinkAddImg="./add.png"
multiSelectLinkRemoveImg="./delete.png"
selectWith="Link"
clearSearchImg="./cross.png"
SelectCellWidth="30px"
dialogID="dlgVend"
dialogTitle="Select a Vendor"
dialogWidth="80%"
refreshID="vendorDiv"
ssjsSelectFunction="getVendorInfo"
varName="viewScope.vendorInfo"
datasrc="nvVend"
selectColumn="4"
linkImg="./add.png"
typeAheadVar="viewScope.srchVend">
<xc:this.viewColumn>
<xp:value>0</xp:value>
<xp:value>1</xp:value>
<xp:value>2</xp:value>
<xp:value>3</xp:value>
</xc:this.viewColumn>
</xc:viewpicklist>
</xe:formRow>
<xe:formRow
id="frVendorStatus"
for="cfVendorStatus"
label="Vendor Registration Status:">
<xp:text
escape="true"
id="cfVendorStatus"
value="#{currentDocument.VendorStatus}">
</xp:text>
</xe:formRow>
<xe:formRow
id="frVendorCountry"
for="cfVendorCountry"
label="Country Name:">
<xp:text
escape="true"
id="cfVendorCountry"
value="#{currentDocument.VendorCountry}">
</xp:text>
</xe:formRow>
</xe:formTable>
</xp:panel>
The requested vendor information populates the fields, without any problem or errors. However, something here seems to prevent dialogs from opening up using SSJS.
Can anyone see anything obvious I'm missing? The data source is in the panel, ignoreRequestParams is true (otherwise it doesn't work).
The main data source is in the entire XPage context. I tried to add the ccWFloDialog custom control outside the main panel, and change the data source to the panel, but that didn't work.
Any ideas?
Forget the dialog for now. This is probably data source related. Suggest you get it working just on the xpage first. With visible fields. then maybe use the rendered property to get it working on the xpage similar to how the dialog would appear. Once you have that working then you should be good to apply to dialog. This idea is to just take the dialog out of the equation first to make sure it works normally.
Instead, I decided to open the dialogs using CSJS, instead of SSJS. I've changed some of logic, and will have a bit more to do to finish this part of the project. Thanks to all!