I have been learning Xpages programming. We are currently using domino 8.5.2. I am gaining familiarity with the display/input controls and I have had some success using them to display information from backend domino documents, views, scoped variables that are not an array. What I have not been able to discover is how to display the elements of a scoped variable array that is created dynamically.
For instance: I create an array with a number of elements. I can print the elements to the domino log with the following code:
for (var i=0; i<array.length; i++) {
print(array[i])
}
What do I use to display the individual elements on webpage? I apologize if the answer if obvious. I did find one posting about displaying a 2 dimensional array - but was unable to interpret the answer.
Thanks for any guidance.
---Lisa&
Use a repeat control and show elements within repeat in a computed field:
<xp:this.beforePageLoad><![CDATA[#{javascript:
var myTest = [];
for (var i=0; i<9; i++) {
myTest[i] = i;
}
viewScope.myTest = myTest;
}]]></xp:this.beforePageLoad>
<xp:repeat
id="repeat1"
rows="30"
var="row"
value="#{viewScope.myTest}">
<xp:text
escape="true"
id="computedField1"
value="#{javascript:row.toString()}">
</xp:text>
<br />
</xp:repeat>
The array is in viewScope.myTest in this example.
A very quick way to display the content of an array is using the join function. No repeat required but the display is limited.
<xp:this.beforePageLoad><![CDATA[#{javascript:
var arrTest= ["a","b","c","d"];
viewScope.myTest = myTest;
}]]></xp:this.beforePageLoad>
<xp:text id="testField"
value="#{javascript:viewScope.arrTest.join('; ')">
</xp:text>
This shows
a; b; c; d
You could add HTML around it to pretty it up, and set the display type of the text field to be HTML whcih gives you the opton to join with HTML eg
value="#{javascript:viewScope.arrTest.join('<br>')"
Not as complete a solution as a repeat control, but good for quick things.
Related
I have some SSJS that does a FTSearch on a view and I get a viewEntryCollection returned:
var veCol:NotesViewEntryCollection = thisAppDB.getView("vwFTSearch").getAllEntries()
veCol.FTSearch(queryString);
viewScope.vsColCount = veCol.getCount();
I know that veCol contains the viewEntries that I want and they are in the correct order. Now I would like to define a dataSource that I will use as the dataSource for a repeat control. I think the answer involves creating an Object Data Source but I can not find any documentation on how to do this.
Any pointers greatly appreciated.
You can use your view entry collection as input for a repeat by doing this:
<xp:repeat id="repeat1" var="rowEntry" removeRepeat="true">
<xp:this.value><![CDATA[#{javascript:
var veCol:NotesViewEntryCollection = thisAppDB.getView("vwFTSearch").getAllEntries()
veCol.FTSearch(queryString);
}]]></xp:this.value>
<!-- add elements to be repeated here -->
</xp:repeat>
I have two forms in a database: fmKopf (head) and fmPos (position). I have a DataView which I am using to show me all the fmKopf documents - this works perfectly. The var name for the records are "rowHandle". I have added a repeat control to the details section of the dataView where I would like to display the fmPos documents for the fmKopf. The repeat var name is "rowData". The value for the repleat control is:
var posView:NotesView = database.getView("xpPositions");
if (rowHandle.isDocument()) {
var key = rowHandle.getColumnValue("searchKey");
var vecLieferscheine:NotesViewEntryCollection = posView.getAllDocumentsByKey(key);
return vecLieferscheine;
} else {
return null;
}
I then added a computed field within the repeat control but for the life of me do not know what to use to display the corresponding data from the fmPos document. If I just display rowData I get the UID of the Notes documents - this makes sense as we are returning a NotesViewEntryCollection. I then tried the following code:
var doc:NotesDocument = database.getDocumentByUNID(rowData);
return doc.getItemValue("DLNTLP");
to display the field DLNTLP from the fmPos document - this cannot work as rowData is a NotesViewEntryCollection and not a single value. This causes the page to crash.
What would be the code that I need to get the computed field to display the values from the underlying documents?
Thank you for any help given.
Ursus
Sorry i had to edit the answer multiple times dont know why but stackoverflow timed me out.
getAllDocumentsByKey(key) does not return a NotesViewEntryCollection it returns a NotesDocumentCollection wich does not really mather you just skip the .getDocument();
So in your xp:repeat you already have NotesDocuments, you can work with them you dont need to get the documents from your database using database.getDocumentByUNID().
You can use following code to access your documents via a datasource inside your repeat (wich does more or less the same as database.getDocumentByUNID()):
<xp:repeat
id="repeat1" rows="30"
value="YourCode" var="detailDoc">
<xp:panel
<xp:this.data>
<xp:dominoDocument var="document1"
action="openDocument"
documentId="#{javascript:detailDoc.getUniversalID()}">
</xp:dominoDocument>
</xp:this.data>
</xp:panel>
</xp:repeat>
or direkt access every Document from your Collection:
<xp:repeat
id="repeat1" rows="30"
value="YourCode" var="detailDoc">
<xp:panel
<xp:text>
<xp:this.value><![CDATA[#{javascript://
var doc:NotesDocument = detailDoc;
return doc.getItemValueString('DLNTLP');
</xp:this.value>
</xp:text>
</xp:panel>
</xp:repeat>
If you just need one or two fields from your documents i would use the secound methode, the first is very usefull if you also want to manipulate those fields because you can use the document datasource with all it's features. The first example also is usefull if you want to display attachments.
One of my bright ideas is looking a little less bright than I first thought.
Background - I needed to do the classic repeat control solution to the view with DBLookups in one of the columns. I need to have totals for the columns, so I decided that I would use a viewScope variable to hold the subtotal, and add the values as I compute them in the rows of the repeat control.
Sounds good in theory, but for some reason, if I use a Computed Field inside the repeat control, the value is computed (and appended to the subtotal) twice. I have established this with code along the following lines, using both a computed field and an edit box with the same code inside the repeat:
<xp:text escape="true" id="computedField9">
<xp:this.value><![CDATA[#{javascript:
//some calculations, nothing to see here*
var subTotal = viewScope.get("valueColumnTotal");
viewScope.put("valueColumnTotal", subTotal + sumVals);
sessionScope.put("Testing", sessionScope.get("Testing") + "~" + sumVals);
return sumVals;
}]]></xp:this.value>
//converters and stuff, nothing to see here
</xp:text>
<xp:inputText id="inputText1">
<xp:this.value><![CDATA[#{javascript:
//Same as the computed field above
</xp:this.value>
</xp:inputText>
For values of 1,2 and 3, my sessionScope variable shows 0~1~1~1~2~2~2~3~3~3 and the subtotal comes out as 18
If I remove the text box, I get 0~1~1~2~2~3~3 and a subtotal of 12
In other words, I'm getting double values for the computed field and single values for the edit box. I'm also getting a zero in there that I can't explain, but I'm assuming that's just an initial value and not something to worry about.
I thought it might have been a case of the repeat being refreshed by something, but then I would expect 1~2~3~1~2~3.
I'm guessing that I just don't understand something fundamental about when computed fields are computed, but I'm stumped. I guess I could switch to edit boxes, but I think Computed Fields are more appropriate here.
Can anybody shed some light on this one?
The JSF Lifecycle sheds some light on the challenge you are facing. The formula you use is executed in multiple phases as Per pointed out. The easiest way to ensure that they are computed only once is to use this code:
if (view.isRenderingPhase() {
var subTotal = viewScope.get("valueColumnTotal");
viewScope.put("valueColumnTotal", subTotal + sumVals);
sessionScope.put("Testing", sessionScope.get("Testing") + "~" + sumVals);
return sumVals;
}
You just need to make sure to reset the value at the appropriate time (e.g. beforeRenderResponse)
The other option to be independent from cycles (you might want to compute a value even if you don't render the page) is to use a small Java class:
public class SumItUp {
private HashMap<String, Integer> totals = new HashMap<String,Integer>();
public void add(String key, int value) {
Integer i = new Integer(value);
this.totals.put(key,i);
}
public int getTotal() {
int result = 0;
for (Map.Entry<String, Integer> me : this.totals.entrySet()) {
result += me.getValue().toInt(); // <-- check that one - off my memory
}
}
Use this as data context or initialize it in your SSJS. Regardless how often the computation is called. When you submit to a Hashmap there won't be duplicate values. Use the clientid (not the id, that's the same for all controls in a repeat) or the document's UNID as key - they are unique
If I read the requirement correctly, the requirement is to show totals for a column.
If the view has categorized columns, here is the solution I used, based off of Notes In 9 – 019 – Xpages: Getting Column Totals From a View
A datacontext is used to pull in the column total from the view:
{** This line of code if (entry==null) {return false}else{return entry} returns a zero if there are no categories for the key value}
The display of the totals in the table used to display the values using a row within a repeat control, is done using a xp:facet with the below code as the Total line:
With the entire facet looking like:
{** Column #4, 5th column over, in the view was the column showing the category total}
Cut-N-Paste Below {**Only to be used by professional Cut-n-Paste'ers}
<xp:dataContext var="contractCategory">
<xp:this.value>
<![CDATA[#{javascript:
try{
var nav:NotesViewNavigator = contractView.createViewNav();
var query = new java.util.Vector();
query.addElement(sGrantId);
var entry:NotesViewEntry = contractView.getEntryByKey(query);
var entry = nav.getPrev(entry);
if (entry==null) {return false}else{return entry}
}catch(e){
return false;
}
}]]>
</xp:this.value>
</xp:dataContext>
<xp:this.facets>
<xp:panel xp:key="footer" id="TotalDisplayArea">
<xp:tr style="background-color:#c9c9c9">
<xp:this.rendered>
<![CDATA[#{javascript:getComponent("repeat1").getRowCount();}]]>
</xp:this.rendered>
<xp:td colspan="2" styleClass="budget_grid_body lotusBold" style="text-align:left">
Totals:</xp:td>
<xp:td styleClass="budget_grid_body" style="text-align:right"> </xp:td>
<xp:td styleClass="budget_grid_body" style="text-align:center;">
<xp:div style="text-align:left;float:left;padding-left:20px;">
<xp:span>$</xp:span>
</xp:div>
<xp:div style="float:right;padding-right:5px;">
<xp:text escape="true" id="totalCostDisp">
<xp:this.converter>
<xp:convertNumber pattern="#,##0.00"></xp:convertNumber>
</xp:this.converter>
<xp:this.value>
<![CDATA[#{javascript:
(operatingCategory)?
operatingCategory.getColumnValues()[4]:
"CodeError:4:2"}]]>
</xp:this.value>
</xp:text>
</xp:div>
</xp:td>
<xp:td style="background-color:#ffffff"></xp:td>
</xp:tr>
</xp:panel>
</xp:this.facets>
I know how to bind a computed field to a document field. And I know how to use javascript to compute a computed field. But how do I do both?
Say my javascript in the computed field is:
#Name("[ABBREVIATE]" ,#UserName());
How do I bind that to say document1.ReqName field?
I use two fields:
a hidden input field that computes the required value (using the xp:inputHidden control). I use "default value" to emulate Computed when composed - and converters to emulate Computed (inspired by Tommy Valand).
a visible computed field or a visible input field set to read only that does the same calculation. Perhaps this can be changed to display the value of the hidden input field using getComponent("").getValue()?
Maybe I miss the Point but why don't You simply do the following?
<xp:inputText id="inputText1" value="#{document1.Reader}">
<xp:this.defaultValue>
<![CDATA[#{javascript:#Name("[ABBREVIATE]",#UserName());}]]>
</xp:this.defaultValue>
</xp:inputText>
This is a Textfield bound per EL to the DocumentField. If the field is empty it is calculated by the default value.
Great! - I missed the point. But You can try this one:
<xp:text escape="true" id="computedField3">
<xp:this.value>
<![CDATA[#{javascript:
if(#IsNewDoc()){
document1.replaceItemValue("Reader",#Name("[ABBREVIATE]",#UserName()));
}
return document1.getItemValue("Reader");}]]>
</xp:this.value>
</xp:text>
For the computed field's Value, use Advanced data binding and select Expression Language (EL).
The expression to use is simply "document1.ReqName" (no quotes).
I wouldn't bind in this case. I would bind every other control and in this case manually add that into the document field directly with script before the save action.
so something like: (forgive the code not at my work comp)
<eventhandler>
<actionGroup>
<script><![CDATA[#{javascript:
doc.replaceItemValue("ReqName", #Name("[ABBREVIATE]" ,#UserName()));
}]]><script>
<saveDocument></saveDocument>
</actionGroup>
</eventhandler>
<xp:text escape="true" id="computedFieldTest1">
[CDATA[#{javascript:
var userName = "Username: "+("Reader",#Name("[ABBREVIATE]",#UserName()));
currentDocument.replaceItemValue("computedFieldTest1",userName);
return userName;
}]]></xp:this.value>
</xp:text>
I would like to start with x no. of fields (in my app I have a pair of textual data field and numeric data field) on a xpage application (say 10 pairs) and then when the user clicks on "more field", I want more pairs to appear dynamically without a full refresh on the page, but would like unlimited no. of fields (as long as the page doesn't crash) and then I would like to submit the form and the data for all those fields. What's the best way to implement this?
Usually, fields are bound to a document data source using dot notation:
<inputText value="#{contact.firstName}" />
However, array notation is also supported:
<inputText value="#{contact['firstName']}" />
Because the field name in this latter syntax is being treated as a string, not as an implicit property of the bean, it can be dynamically computed. What I've found to be the easiest way to define these dynamic fields is to create a custom control for each of the basic types of fields, and define each as accepting the data source and the field name. So the field itself then ends up with a syntax similar to the following:
<inputText value="#{compositeData.dataSource[compositeData.fieldName]}" />
By using that syntax, a calculation of any complexity can be used to determine what field name to pass to the custom control. In the scenario you're attempting to accomplish, specifying an indexVar on the repeat control that surrounds the field pair would allow you to designate a field suffix for each... perhaps something like the following:
<xp:repeat indexVar="fieldSuffix" value="#{viewScope.rowCount}">
<xp:div>
<xc:dynamicInputText dataSource="#{contact}" fieldName="fullName_#{fieldSuffix}" />
<xc:dynamicInputNumber dataSource="#{contact}" fieldName="phoneNumber_#{fieldSuffix}" />
</xp:div>
</xp:repeat>
With this approach, you would end up with fields named "fullName_0", "fullName_1", etc., up to the limit specified in the viewScope. Typically, the only complication is ensuring that when an existing document is opened, the viewScope variable is set back to the correct limit. Another approach to that, of course, is actually saving the limit as another item on the document and binding the repeat value to it instead.
You also can have a look at the exercise 23 "Tablewalker". It doesn't do multiple fields but does Multi-value fields which might be better in terms of processing and storage (you can do an #Elements to find out how many are there in a document). The exercise is here:
http://www-10.lotus.com/ldd/ddwiki.nsf/dx/Tutorial-Introduction-to-XPages-Exercise-23
While the button only adds one row at a time, it is easy to adjust.
What you could do is have a Bean with 2 String values Label and Data and a managed bean that has a ArrayList of that object so inside of your repeat control you bind the repeat to the ArrayList and then bind your xp:inputText to rowData.Data and your xp:label to rowData.Label then when you want to add another 5 rows you just add However many more objects into the ArrayList then refresh your page, your data will still live in your arraylist and you will have 5 new Empty objects where you can add data.
public class Data {
private String label;
private String data;
public Data() {
}
//getters and setters
}
public class ManagedBean {
private ArrayList<Data> datalist; // add a managed property for this one so It will create a new one when needed.
// getters and setters
public addFiveMoreObjects() {
Data newItem;
for (int i=0; i<5; i++) {
newItem = new Data();
datalist.add(newItem);
}
}
}
<xp:repeat value="#{managedBean.datalist}" var="rowData">
<xp:text value="#{rowData.label}" />
<xp:inputText value="#{rowData.data} />
</xp:repeat>
<xp:button value="Add 5 More"> // call #{managedBean.addFiveMoreObjects}