Dynamic binding within a repeat control - xpages

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.

Related

XPages: Server side access to input values via getComponent().getValue() vs. Value Binding with Scope Variables

In XPages you can access the value(s) of an input control (e.g. xp:inputText) on the server side in three (see answer of Sven Hasselbach) different ways:
Use javax.faces.component.UIComponent to get the base object for a UI component in combination with an appropriate getter (e.g. getComponent("txtRootFolder") .getValueAsString())
Use a Scope Variable (e.g. requestScope.rootfolder)
I know the usage/purpose of scope variables, that's not the questions here.
I want to know what are the main differences (advantages/ disadvantages, best practices, etc.) between this two possible solutions?
There is a third way which I prefer: Add a binding to your component.
Then you can access the component and it's value directly.
Here is simple example:
<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
<xp:inputText id="inputText1" binding="#{myValue}" />
<xp:label value="#{myValue.value}" id="label1" />
<xp:button value="Label" id="button1">
<xp:eventHandler event="onclick" submit="true" refreshMode="partial" />
</xp:button>
</xp:view>
The general idea: your components represent a view on your data, while your variables represent the model. When you use value= you decouple the view from the model. Your variable doesn't need to know WHAT component did update it. So when you change the UI or decide that the variable will get updated from code only, nothing in your (controller) code needs to change.
When you use binding (which has very valid use cases) you are dealing with a specific type of component, not only the value anymore. This is good when you need to manipulate the component (eg dynamic dropdown values), but overkill when you only need the value.
So IMHO use bound values any time unless you have a good justification to do otherwise.
Bonus tip: instead of scattering values around in scopes use a managed bean to keep them. This will make controller logic easier and consistent.

Can I filter an Xpages search bar

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

Replace embedded views with multiple data sources

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.

Cannot get Repeat control to break out multi-value field

What I am trying to do should be very simple. I have a multi-value text field called activityLog. The field is set via a java method when various things happen. If I look at the backend data, it clearly is a multivalue field.
I went through the steps in Dave Leedy's Notes-in-9 #14, as well as tried to follow Tommy Valand's blog post here --> http://dontpanic82.blogspot.com/2011/03/repeat-controls-and-multivalue-fields.html. Neither seems to work for me. The result is that they list contents that are concatenated together in both cases.
The values ARE returned as a VECTOR. I can verify this by getting the size(), and by isolating specific elements using elementAt(). I was about to just make a for loop to get around this, but I want to know why it is failing. Repeats always seem to give me trouble. I was also want to leave straight forward code for me or future developers. There is probably something easy that I am missing.
<xp:repeat id="repeat1" var="rowData">
<xp:this.value><![CDATA[#{javascript:document1.getItemValue("activityLog")}]]></xp:this.value>
<xp:text escape="true" id="computedField14" value="#{javascript:rowData}">
</xp:text>
</xp:repeat>
UPDATE: The Repeat control is reading the Vector, but is NOT adding a new line automatically after each iteration. Trying to add a new line manually is not working, having tried JS "/n" & "/r" and #NewLine()
The use of <br/> or <xp:br /> suggested by Tim is an effective approach. But consider fixing the issue in the <xp:text/ > element instead.
The <xp:text />element creates a <span /> in the output HTML by default. Spans are, by default, displayed inline meaning that they do not have a line feed ahead of them. But you can add a CSS rule to the element which makes it a block element instead of inline. Add this CSS using a style tag: <xp:text style="display:block;" />or in a CSS class that include the rule {display: block}
A final way is to tell the the page to render a <div /> tag instead of a <span /> tag. DIVs are, by default, block elements. Do this with: <xp:text tagName="div"/>. (however, be careful because the default behavior of both DIVs and SPANs can be changed).
Browsers ignore whitespace except inside tags specifically intended to display whitespace. Add a break tag (<br />) or component (<xp:br />) inside the repeat, and the browser will display a new line for each iteration of the repeat.
There is a simple way of breaking out a multivalue fields, you only missed one thing. Accessing each row, I've added the rowColl variable indexVar="rowColl" and so you access it rowData[rowColl]
<xp:repeat id="repeat1" var="rowData" indexVar="rowColl">
<xp:this.value><![CDATA[#{javascript:document1.getItemValue("activityLog")}]]></xp:this.value>
<xp:text escape="true" id="computedField14" value="#{javascript:rowData[rowColl]}">
</xp:text>
</xp:repeat>

Dynamic Table from Lotus Notes to XPages within a form

I am taking an existing Lotus Notes database and converting to Xpages. There is one of those tables containing 3 multi-value fields, with New Line as the seperator and the "Add New", "Modify" and "Delete" buttons controlling how the data is entered and removed. The customer would like the XPage to look as similar to the Notes GUI as possible, and I was thinking I would use the dijit dialog box to do the add new line and figure out the delete and modify. But from what I can tell, the dialog box can only be used on client-side and the data input into the dialog box can't be brought down onto the Xpage. Is this true? I was thinking I would use an editable field within a repeat, but I also couldn't that working properly.
Basically, it the solution has to show the multi-value fields for past documents and also be able to allow users to edit those older document...plus work similar/exactly the way as in the past when creating new docs.
Thanks in advance for any help I can get on this as it seems a ton easier than I am probably making it out to be.
I just wanted to update after the solution below, which appears to be an excellent way to solve this problem. However, as an admitted XPages novice, I am really struggling with the application of this concept. This is what I have, and it obviously isn't working.
Logically, this sounds like a great solution. However, I am no xpages expert and I simply can't get this working properly even to get started. Anything at all that would make this easier for me to even get started would be a big help. I'm not one to usually look for "the answer"...I'm just having difficulty getting a handle on this Multi-value field table issue. Thanks again in advance...here's what I wrote that is coming up with a 500 error. "A" is the multi-value field name.
<xp:table>
<xp:tr>
<xp:td>
<xp:repeat id="repeat1" rows="30" var="rowdata">
<xp:this.value><![CDATA[#{javascript:document1.getItemValue("A")}]]></xp:this.value>
<xp:tr id="valueRow">
<xp:td>
<xp:text
value="#{javascript: return rowdata[i]}" />
</xp:td>
<xp:eventHandler event="onclick" submit="false"
refreshMode="partial" execMode="partial" execId="valueRow"
immediate="true">
<xp:this.action>
<![CDATA[#{javascript:document1.getItemValue("A")}]]>
</xp:this.action>
</xp:eventHandler>
</xp:tr>
</xp:repeat>
</xp:td>
</xp:tr>
</xp:table>
I would say do the following
Create a repeat control which will extract the data from the multi value fields and print them read only. The repeat control will generate a tr structure with a event handler bound to it on the onclick event. something like this:
2 In the onclick event change the style of the tr clientside (using dojo) so people know they selected that row and set the id / identifier of that row in a scoped var
3 Above the repeat control add controls like add, remove, update. The add and update will open a dialog box and will read the data from the selected row ( or none if its a add action). The delete control will remove the data from the multiline value fields, save the document and refres the repeat control.
This should work.

Resources