Using a Repeater to show multi-value field values - xpages

I've setup a repeater and am reading the value from the multivalue field using GetItemValueArray. This returns the array and If I use a listbox it displays. I want to cross reference some other data with it though so I need to use a repeater. But I'm not sure how to have the repeater use an index that increments for each row. The code below "return rowdata[i]" doesn't recognize i.
<xp:repeat id="repeat1" var="rowdata" rows="30">
<xp:this.value>
<![CDATA[#{javascript:var myArray:Array = myDataSource.getItemValueArray("MyMultiValueFld")}]]>
</xp:this.value>
<xp:label id="lbl">
<xp:this.value><![CDATA[#{javascript:return rowdata[i];}]]></xp:this.value>
</xp:label>
</xp:repeat>

rowdata is not a reference to the myArray value as a whole, but the iterated entry in myArray. In other words... you already have what you need.
<xp:label value="#{rowdata}" />

Maybe you can simplify your code by naming using just the "rowdata" as value for your text item. You then should only change the repeat source to
myDataSource.getItemValue("myValueFld")
as this returns always an array of data. It's just depending on the datatype that this item stores, so you might have to convert in the text control.

Hi there your code has another issue, you do not have any return statement in this line:
<xp:this.value>
<![CDATA[#{javascript:var myArray:Array = myDataSource.getItemValueArray("MyMultiValueFld")}]>
</xp:this.value>
so the code does not return your myArray it only Returns the name of it as a string wich gets repeatet one time. Use this a valueBinding:
value="#{myDataSource.MyMultiValueFld}"
or add a return:
<xp:this.value>
<![CDATA[#{javascript:var myArray:Array = myDataSource.getItemValueArray("MyMultiValueFld");
return myArray;}]>
</xp:this.value>
Then you should be able to use Chris Tooheys Answer:
<xp:label value="#{rowdata}"/>

Use the indexVar property of the repeat control to setup a var containing the index.

Got it. The final piece of code is that I did have to create an indexVar="I" in my repeat1 tag but then I also had to change my label to a computed field. The final piece of code looks like:
<xp:table>
<xp:repeat id="repeat1" var="rowdata" rows="30" indexVar="i" first="0">
<xp:this.value><![CDATA[#{javascript:myDataSource.getItemValueArray("myMultivalueFld");}]]>
</xp:this.value>
<xp:tr><xp:td>
<xp:text escape="true" id="computedField1">
<xp:this.value><![CDATA[#{javascript:var repeat1:com.ibm.xsp.component.xp.XspDataIterator = getComponent("repeat1");
repeat1.getValue()[i];}]]></xp:this.value>
</xp:text>
</xp:td>
</xp:tr>
</xp:repeat>
</xp:table>
don't think I could have gotten it without the tidbit on the indexVar. Thanks

You can trim that code further. If all you need is the values, you don't need the indexvar and you don't need to reference the repeat as an XspDataIterator. Just use the rowdata as a variable.
<xp:table>
<xp:repeat id="repeat1" var="rowdata" rows="30">
<xp:this.value><![CDATA[#{javascript:myDataSource.getItemValueArray("myMultivalueFld");}]]>
</xp:this.value>
<xp:tr><xp:td>
<xp:text escape="true" id="computedField1">
<xp:this.value><![CDATA[#{javascript:rowdata;}]]></xp:this.value>
</xp:text>
</xp:td>
</xp:tr>
</xp:repeat>
Each value in the multi-value field is a "row" as referenced by rowdata. Similarly, if the data source were a view, each row would reference a document object. Don't dispose of the framework available to you by accessing the repeat as a component from inside the component itself.

Related

#Name within repeat

I have an app I am build which will allow the users to enter the approvers for each document manually. I wanted to put this in a repeat control instead of hard coding all the fields in the xpage. All is working as I want, except the names are stored in the document in the Canonical format. I don't want to present this to the user, I want to just show the Abbreviated name. I can't seem to get it working. Below is my repeat. I want to put and #name around the value of ApproverName_#
Thanks in Advance
Walt
<xp:repeat
id="repeat4"
var="rowItem"
indexVar="indexVar">
<xp:this.value><![CDATA[#{javascript:["1", "2", "3","4","5","6","7","8","9","10","11","12","13","14","15","16","17","18"]}]]></xp:this.value>
<xp:panel>
<xp:repeat
id="repeat3"
var="fieldName">
<xp:label id="label31"><xp:this.value><![CDATA[#{javascript:"Approver - " + rowItem}]]></xp:this.value></xp:label><xp:label id="label33" value="Name"></xp:label>
<xp:this.value><![CDATA[#{javascript:["ApproverName_"+rowItem]}]]></xp:this.value>
<xp:inputText id="nameinputText"
value="#{document1[fieldName]}"
style="width:333.0px" rendered="#{javascript:!document1.isEditable()}">
</xp:inputText>
<xp:inputText id="inputText9" value="#{document1[fieldName]}" style="width:333.0px">
</xp:inputText>
<xe:namePicker id="namePicker1"
for="nameinputText">
<xe:this.dataProvider>
<xe:dominoNABNamePicker
nameList="peopleByLastName"
addressBookSel="db-name" groups="false"
people="true">
<xe:this.addressBookDb><![CDATA[#{javascript:#Subset(#DbName(), 1) + "!!names.nsf"}]]></xe:this.addressBookDb>
</xe:dominoNABNamePicker>
</xe:this.dataProvider>
</xe:namePicker></xp:repeat>
<xp:repeat id="repeat5" var="fieldName">
<xp:this.value><![CDATA[#{javascript:["Title_"+rowItem]}]]></xp:this.value>
<xp:label id="label32" value="Title"></xp:label>
<xp:inputText id="inputText10"
value="#{document1[fieldName]}">
</xp:inputText>
</xp:repeat>
<xp:repeat
id="repeat6"
var="fieldName">
<xp:this.value><![CDATA[#{javascript:["ApprovalFlag_"+rowItem]}]]></xp:this.value>
<xp:inputText id="inputText11" value="#{document1[fieldName]}">
</xp:inputText>
</xp:repeat>
<xp:repeat
id="repeat7"
var="fieldName">
<xp:this.value><![CDATA[#{javascript:["Reason"+rowItem]}]]></xp:this.value>
<xp:inputText id="inputText12" value="#{document1[fieldName]}">
</xp:inputText>
</xp:repeat>
</xp:panel>
</xp:repeat>
Use the Dojo Name Text Box instead of Input Field. It prevents typing, gives a better UX for removing entries, and does what you want out-of-the-box.

Edit box in repeat control with default value does not update on delete

I have a Repeat control in which I have a edit box which has passed a default value.There is a Delete button to delete that row.
just for test I have used a computed field along with the edit box.Computed field is also passed the same value as edit box.Now both computed Field and Edit Box has same value,When I click on Delete button randomly,it gets delete but only computed field updates properly in repeat control wherever deleted,
In case of Edit box,it shows the last record disappears.
So the issue is, the value in edit box does not get update like computed field does.I have done the test by writing following code.
<?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="testing">
</xp:dominoDocument>
</xp:this.data>
<xp:inputText id="inputText1" multipleSeparator=",">
<xp:this.defaultValue><![CDATA[#{javascript:var v:java.util.Vector = new java.util.Vector();
v.add('1');v.add('2');v.add('3');v.add('4');v.add('5');v.add('6');
return v;}]]></xp:this.defaultValue>
</xp:inputText>
<xp:br></xp:br>
<xp:repeat id="repeat1" rows="30" var="r" indexVar="i" first="0">
<xp:this.value><![CDATA[#{javascript:return getComponent("inputText1").getValue();}]]></xp:this.value>
<xp:br></xp:br>
<xp:div style="text-align:center">
<xp:label value="#{javascript:r}" id="label1"
style="text-align:center">
</xp:label>
<xp:button value="Delete" id="button1">
<xp:eventHandler event="onclick" submit="true"
refreshMode="complete">
<xp:this.action><![CDATA[#{javascript:var v:java.util.Vector = getComponent("inputText1").getValue();
if(v != null){
var sdtString = getComponent("inputText2").getValue();
if(v.contains(sdtString))
v.remove(sdtString);
};}]]></xp:this.action>
</xp:eventHandler>
</xp:button>
<xp:inputText id="inputText2">
<xp:this.defaultValue><![CDATA[#{javascript:getComponent("label1").getValue();}]]></xp:this.defaultValue>
</xp:inputText>
</xp:div>
<xp:br></xp:br>
</xp:repeat>
</xp:view>
This is the example code for testing to get the exact issue.
Edit 1: Real Scenario
We have an application to select multiple dates for a meeting and hence using the notes free time we mark multiple dates and then users are allowed to edit/delete the same in another window..Since, it was not possible/feasible enough to save all the fields in SSJS, we rather preferred binding each field with the data-source under the repeat control by using custom properties of the custom control (hope I am making some sense). Every fields is mapped as #chintan pointed out as follows:
dataSource[compositeData.to]
As described in the first part of the question the fields are not getting updated since we set them using default values. However, based on the suggestion we tried to set the field using the script library which does work well, however, might seem stupid but it messes the date field. When we set the value using the following:
var d:java.util.Date = new java.util.Date(compositeData.selectedDate);
getComponent("from").setValue(d);
It jumbles up the month, date and year field and shows a complete different value (the same code works well when put as the default value).
Hope this makes sense.
Any kind of solution can be appreciated.
inputText2's defaultValue gets executed only once at first page load. Then the XPage memorized that repeat 1 has inputText2 defaultValue 1, repeat 2 has inputText2 defaultValue 2 and so on.
Example: as you can see in print log, inputText2's defaultValue doesn't get calculated again after deleting row 3 and later row 1.
Turn it around and set inputText2's value in label1's value code:
<xp:label
value="#{javascript:getComponent('inputText2').setValue(r); r}"
id="label1"
style="text-align:center">
</xp:label>
then it will set inputText2's value properly. You don't need inputText2's defaultValue property anymore.
As an alternative you can define inputText2's value property and bind it to a row-specific data source or viewScope variable.
As you described in your "Edit 1 Real Scenario", you want to edit and delete pairs of dates. I created the following example. Instead of dates I just use strings for simplicity. Keep the dates in a viewScope variable and edit/delete them right there:
<?xml version="1.0" encoding="UTF-8"?>
<xp:view
xmlns:xp="http://www.ibm.com/xsp/core">
<xp:this.beforePageLoad><![CDATA[#{javascript:viewScope.dates=[
{"from":"a", "to":"b"},
{"from":"c", "to":"d"},
{"from":"x", "to":"y"}];}]]></xp:this.beforePageLoad>
<xp:br />
<xp:repeat
id="repeat1"
rows="30"
var="r"
indexVar="i"
value="#{viewScope.dates}">
<xp:br></xp:br>
<xp:div>
<xp:button
value="Delete"
id="button1">
<xp:eventHandler
event="onclick"
submit="true"
refreshMode="complete">
<xp:this.action><![CDATA[#{javascript: viewScope.dates.remove(i);
}]]></xp:this.action>
</xp:eventHandler>
</xp:button>
<xp:inputText
id="inputText1"
value="#{r.from}">
</xp:inputText>
<xp:inputText
id="inputText2"
value="#{r.to}">
</xp:inputText>
<xp:button
value="Update"
id="button2">
<xp:eventHandler
event="onclick"
submit="true"
refreshMode="complete">
<xp:this.action><![CDATA[#{javascript: "";
}]]></xp:this.action>
</xp:eventHandler>
</xp:button>
</xp:div>
<xp:br></xp:br>
</xp:repeat>
<xp:text
escape="false"
id="computedField1">
<xp:this.value><![CDATA[#{javascript:
var length = viewScope.dates.length;
var result = "";
for (var i = 0; i < length; i++) {
result += viewScope.dates[i].from + " - " + viewScope.dates[i].to + "<br />";
}
result
}]]></xp:this.value>
</xp:text>
</xp:view>
Create the viewScope variable as an array of objects.

how to store and save correctly field values from repeat controls

I created a simple repeat control, having inside it 2 simple inputTexts. Outside this repeat control, there is an editable inputText3 ( binded to dataSource ). I'm using it to make some calculations inside the repeat control. The calculations are displayed correctly. I have 2 buttons, for newLine and delete/hideLine. For the moment, those 2 fields inside the repeat control are not binded to the datasource.
i have a button which save the doc., and list it inside a viewPanel, having the first column inputText3, clickable. I noticed that if I open the document ( in edit or read mode ) this editable field value is correctly displayed, but the fields inside the repeat control are all null, even if I add some values before using the save method.
I try also to create 2 fields inside the form ( / datasource ) and binding this inputTexts to them, but again the repeat control fields are empty.
<xp:repeat id="repeat1" var="varRepeat" indexVar="index">
<xp:this.value><![CDATA[#{javascript:parseInt(sessionScope.dField)
}]]></xp:this.value>
...
<xp:inputText id="inputText1"></xp:inputText>
<xp:inputText id="inputText2"></xp:inputText>
The editable field outside the repeat:
<xp:inputText id="number" value="#{docrepeat.valtotala}"
defaultValue="100">
</xp:inputText>
.
<xp:this.data>
<xp:dominoDocument var="docrepeat" formName="docrepeat"></xp:dominoDocument>
</xp:this.data>
I'm definitely missing something here, hope to reach to a functional solution.
Should I bind the 2 inputText using the var property of the repeat?
Or how can I achieve this?
repeat control code:
<xp:repeat id="repeat1" var="test" indexVar="index" rows="8">
<xp:this.value><![CDATA[#{javascript:parseInt(sessionScope.DField)
}]]></xp:this.value>
<xp:panel>
<xp:table >
<xp:tr>
<xp:td>
<xp:inputText id="inputText1">
<xp:eventHandler event="onchange"
submit="false">
<xp:this.script><![CDATA[try
{
var idx="view:_id1:inputText3";
var index=document.getElementById(idx).value;
var number="view:_id1:number";
var val=document.getElementById(number).value;
var sum = val;
for(var i=0;i<index;i++) {
var input1="view:_id1:repeat1:"+i+":inputText1"
var nr1=document.getElementById(input1).value;
sum-=nr1;
document.getElementById("view:_id1:repeat1:"+i+":inputText2").value = sum;
}
// calculating some %
document.getElementById("view:_id1:test").value = parseInt((sum*100)/val);
}
catch(e)
{
alert("not working");
}]]></xp:this.script>
</xp:eventHandler>
</xp:inputText>
<xp:inputText id="inputText2"></xp:inputText>
</xp:td>
</xp:tr>
</xp:table>
</xp:panel>
</xp:repeat>
My little scenario was described here: Xpages how to obtain/create this calculations module
I think your datasource inside the repeat is a view. You need to add a datasource of object data inside the repeat. The way to do this is to create a panel inside the repeat and give that panel a datasource as follows.
<xp:repeat id="repeat1" rows="30" value="#{view1}"
var="rowData">
<xp:panel>
<xp:dominoDocument var="objectData1"
formName="Item"
documentId="#{javascript:return rowData.getNoteID();}"
ignoreRequestParams="true" action="openDocument">
</xp:dominoDocument>
!!!put your stuff in the panel!!!
</xp:panel>
</xp:repeat>
So what is going on is the repeat grabs the datasource which is a view. So it will iterate through all of the entries in the view. But the view datasource does not know what to do with documents. So you create the objectData which will grab the noteID of that specific document and make that available to the repeat as a document(referenced by noteID). Making it available as a document will allow it to save values. You probably won't be able to use the value picker but just type in the field names and it will work.
Not sure I am completely understanding your problem. But you also need to save the datasource. So you could either put a save button in the panel to save that specific document or make the save button save all data sources. I prefer being able to save each document separately as it allows for applications that multiple can edit at the same time.

Xpages datatable cell colour based on cell value

I am trying to use an xpages datatable and set the cell colour of the cells in a column to be different based on the cell value. What I am finding is that although the datatable is bound to a view and the collection is specified (to get the row value) this seems to not be available to the style section.
Here is an example:
<xp:dataTable id="dataTable8" rows="30" var="doc1">
<xp:this.value><![CDATA[#{javascript:var View:NotesView = DivisionsView;
View.getAllEntriesByKey("ViewName")}]]></xp:this.value>
<xp:column id="column1" style="width:75px;font-family:Tahoma">
<xp:text escape="true" id="computedField1">
<xp:this.value><![CDATA[#{javascript:doc1.getColumnValues()[1]
}]]></xp:this.value>
<xp:this.style><![CDATA[#{javascript:v=doc1.getColumnValues()[1];
if(v=="Yes"){"background-color:rgb(255,0,0)"}}]]></xp:this.style>
</xp:text>
<xp:this.facets>
<xp:label value="Header" id="label1" xp:key="header">
</xp:label>
</xp:this.facets>
</xp:column>
</xp:dataTable>
This just shows a doc1 not found error. Does this mean the data bound to the datatable is not available to the style part of it? Is there a way to do this?
Any suggestions would be appreciated!
Edit: I cannot change the table cell style based on the view entries value, here is an exmaple that throws the doc1 not found error:
<xp:column id="column1">
<xp:this.style><![CDATA[#{javascript:v=doc1.getColumnValues()[1];
if(v=="Yes"){"background-color:rgb(255,0,0)"}}]]></xp:this.style>
<xp:text escape="true" id="computedField1">
<xp:this.value><![CDATA[#{javascript:doc1.getColumnValues()[1]
}]]></xp:this.value>
</xp:text>
<xp:this.facets>
<xp:label value="Header" id="label1" xp:key="header">
</xp:label>
</xp:this.facets>
</xp:column>
The stylesheet property is rendered not only for every value in the column, it is calulated for each row. This incudes the facet too.
This means if your column has a header and/or a footer, the stylesheet property is calculated for these invisible rows, but there is no row value (doc1).
If you change your code and add a try/catch you can see the result.
<xp:column id="column1">
<xp:this.style>
<![CDATA[#{javascript:
try{
v=doc.getColumnValues()[1];
if(v=="Yes"){"background-color:rgb(255,0,0)"}
}catch(e){
return "background-color:rgb(255,0,255)";
}}]]>
</xp:this.style>
...
...
</xp:column>
There are a few things I would like you to check in your code.
First the code to bind data:
var View:NotesView = DivisionsView;
View.getAllEntriesByKey("ViewName")
What is DivisionsView? The actual code to bind would look something like this:
var View:NotesView = database.getView("ViewName");
View.getAllEntriesByKey("KeyName")
Second, in your code to add style you have used single = rather than == in if condition. So the code would be something like this:
v = doc1.getColumnValues()[1];
if (v=="Yes") {
"background-color:rgb(255,0,0)"
}
Do these suggestions make it work?
I have given up on a datatable and got things to work using a repeat on a table row. That way I can control the style of the entire row and therefore each cell, it appears you cannot do this in a datatable as the options are all data column based. Thanks for your help though

Trying to dynamically bind to the id in the accordion extension library control

I am building a custom control using the accordion control included with the Xpages Extension Library. I am attempting to bind to the AccordianPane id and I receive the error:
The value of the property id cannot be a run time binding
The error is referring to this line of code:
<xe:djAccordionPane
title="#{javascript:sectiontitles.getColumnValue('Section')}" id="#{javascript:sectiontitles.getColumnValue('Section')}"
parseOnLoad="false">
I saw Paul Withers post here:
http://www.intec.co.uk/combining-and-an-alternative-approach/
Which makes me think this is possible and that I am just not quite there. Where do I use $ instead of # ?
Here is the code I am using:
<?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.data>
<xp:dominoView var="view1" viewName="MenuLinks"></xp:dominoView>
<xp:dominoView var="view2" viewName="MenuLinksSections"></xp:dominoView>
</xp:this.data>
<xp:panel
style="float:left;padding-left:20.0px;padding-right:20.0px; padding-top:20.0px">
<xe:djAccordionContainer id="djBorderContainer1"
style="width:200px; height:540px" styleClass="soria">
<xe:this.selectedTab><![CDATA[#{javascript:var selectedTab = context.getUrlParameter("tab");
if (selectedTab == "") {
""
} else {
selectedTab
}
}]]></xe:this.selectedTab>
<xp:repeat id="repeat1" rows="30" var="sectiontitles"
value="#{view2}" disableOutputTag="true">
<xe:djAccordionPane
title="#{javascript:sectiontitles.getColumnValue('Section')}" **id="#{javascript:sectiontitles.getColumnValue('Section')}"**
parseOnLoad="false">
<xp:text escape="true" id="computedField1">
<xp:this.value><![CDATA[#{javascript:var id = "#{id:djAccordionPane}";
id}]]></xp:this.value>
</xp:text>
<xp:text escape="true" id="computedField2"></xp:text>
<xp:repeat id="repeat2" rows="30" var="menulinks"
disableTheme="true">
<xp:this.value><![CDATA[#{javascript:var tview = database.getView("MenuLinks");
var v = sectiontitles.getColumnValue("unid");
var vc:NotesViewEntryCollection = null;
if (v != null) {
vc = tview.getAllEntriesByKey(v);
}
vc
}]]></xp:this.value>
<xp:text escape="false">
<xp:this.value><![CDATA[#{javascript:menulinks.getColumnValues()[3]}]]></xp:this.value>
</xp:text>
</xp:repeat>
</xe:djAccordionPane>
</xp:repeat>
</xe:djAccordionContainer>
</xp:panel>
</xp:view>
Any help is appreciated.
Thanks,
Elijah Lapson
It's almost certainly the id property that cannot be a runtime binding. That's because the ID is used by the server to create a map of the elements on the XPage. So it cannot be calculated dynamically using the #.
You won't be able to use $ within the repeat unless repeatContents property is set to true. The $ means it will try to calculate the ID at page load, but the number of elements in the repeat will not have been calculated yet. They'll be calculated dynamically. So you'll need repeatContents="true" on the repeat, so the repeat control's contents are calculated at page load. The repeatControls property can have knock-on implications to things like paging, but it doesn't sound like that's going to be a problem for you.
However, why are you trying to compute the ID? Is it so you can use the ID somewhere in CSJS to get a handle on each individual element? If so, an alternative may be to add a normal HTML element like a div within the djAccordionPane and compute the ID of that. You will not have runtime binding issues with that. Alternatively compute the styleClass property instead. Then you can use dojo.query to select the elements based on the class property. You should be able to use dojo.query(.#{javascript:sectiontitles.getColumnValue('Section')}) to get a handle on the element with the class that matches sectiontitles.getColumnValue('Section')

Resources