Xpages showing a cell table after field value is changing - xpages

In a djTabPane I put all the content in a table with 1 row and 1 column:
<xe:djTabPane id="djTabPane3" title="Valoare contract">
<xp:table id="CellVal"> ... </xp:table> </xe:djTabPane>
In this main table I have numerous tables. Certain table has only 2 rows. On the first row there is this field:
<xp:inputText id="inputText23" value="#{Contr.vff1}">
<xp:eventHandler event="onchange" submit="true" refreshMode="partial" refreshId="CellVal">
</xp:eventHandler>
</xp:inputText>
I want the next <tr> from this table to be rendered when this field is changing:
<xp:tr>
<xp:this.rendered>
<![CDATA[#{javascript:Contr.getItemValueString("vff1") != ""}]]>
</xp:this.rendered>
<xp:td>s</xp:td>
</xp:tr>
But still the row isn;t shown. Am I missing something?

Step 1: add an Error control to your page you can see. The problem might be a failing validation.
Step 2: Strip the example down to a minimal problem
Step 3: consider a client side show routine instead of SSJS. Saves you the roundtrip.
Something like this (off my head) in a script block:
XSP.addOnLoad(function() {
document.getElementById("#{inputText23}").addEventListener("onchange", function(){
document.getElementById("#{give-the-tr-an-id}").className = "showup";
});
});
Then use classes and CSS to show/hide it.

Related

Xpages repeat control & dynamic field creation/display

I need to create 3 fields in one row every time a new objective is needed. objective1, midYear1, endYear1. Then if I add an objective, objective2, midYear2, endYear2 and so on. Everything seems to work, first time, but second time it creates loads of fields. I assume its the way I'm nesting / not nesting/using my repeats correctly as my viewScope variables are all correct, so it's just displaying of the fields that I'm confusing myself with. I just need each of the 3 fields in one column each, then new row and repeat.....Code below, however am also open to suggestions if anyone has a better approach..... Thanks
<xp:this.data>
<xp:dominoDocument
var="document1"
formName="objective">
</xp:dominoDocument>
</xp:this.data>
<xp:repeat id="repeat1" rows="100" value="#{viewScope.fields}"
var="fieldName">
<xp:repeat id="repeat2" rows="100" value="#{viewScope.fields2}"
var="fieldName2">
<xp:repeat id="repeat3" rows="100" value="#{viewScope.fields3}"
var="fieldName3">
<div class="row">
<div class="col-xs-4">
<xp:label value="#{fieldName}" for="inputText1">
</xp:label>
<xp:inputText id="inputText1">
<xp:this.value><![CDATA[#{document1[fieldName]}]]></xp:this.value>
</xp:inputText>
</div>
<div class="col-xs-4">
<xp:label value="#{fieldName2}" for="inputText2">
</xp:label>
<xp:inputText id="inputText2">
<xp:this.value><![CDATA[#{document1[fieldName2]}]]></xp:this.value>
</xp:inputText>
</div>
<div class="col-xs-4">
<xp:label value="#{fieldName3}" for="inputText3">
</xp:label>
<xp:inputText id="inputText3">
<xp:this.value><![CDATA[#{document1[fieldName3]}]]></xp:this.value>
</xp:inputText>
</div>
</div>
</xp:repeat>
</xp:repeat>
</xp:repeat>
<xp:button
value="Add Objective"
id="button1">
<xp:eventHandler event="onclick" submit="true" refreshMode="partial"
refreshId="repeat1">
<xp:this.action><![CDATA[#{javascript:
if (!viewScope.fields) {
viewScope.fields = [];
viewScope.fields2 = [];
viewScope.fields3 = [];
var count:integer = 1;
}
viewScope.fields.push("Objective" + (viewScope.fields.length + 1));
viewScope.fields2.push("MidYear" + (viewScope.fields2.length + 1));
viewScope.fields3.push("EndYear" + (viewScope.fields3.length + 1));
count = count+1;
}]]></xp:this.action>
</xp:eventHandler>
The problem is that by nesting, for each entry in coumn 1, you're creating n instances of column 2. Then for each entry in column, you're creating n instances of column 3. If you want this approach, you're best off creating a single viewScope variable, where each element has the fieldnames for each objective (as an object or array), then just using a single repeat using that single viewScope variable.
Building this for Notes Client / traditional Domino web was historically a huge pain because the restriction was you could not edit multiple documents (easily) in the same UI, because the Form was both the schema for storage and the design for the UI.
XPages means that's no longer a restriction. The XPage is the UI, (dominoDocument / bean) data sources are the schema for storage.
As a result, my preferred approach is to break the structure for storage up into more granular elements. So each Objective would be its own document, with fixed bindings to "objective", "midYear", "endYear".
There are then a variety of approaches for creating a new entry. One is a single fixed row at the top with a dominoDocument datasource with scope="request" ignoreRequestParams="true". Save can then add the NoteID / UNID to a repeat that then retrieves the values from the relevant document(s) to display / edit.
A more advanced approach would be to have a viewScope variable with an additional option (at beginning or end), then compute the dominoDocument datasource that's within the repeat to either retrieve the relevant document (i.e. action="openDocument") or create a new one (i.e. action="newDocument").

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.

Strange behaviour with Typeahead - search string returned along with results

Apologies for the length. I'm having a play with fancy typeahead, and I ran across an issue that I can't explain. I've found a fix, but the fix doesn't make sense, so I'm wondering if anybody can look at my example and explain what is happening, and if it is a bug of some sort? I have stripped out most of the functionality to provide a bare bones example to demonstrate.
The scenario: I've got a text box, with fancy typeahead. Beside that, I have a button to append the newly found value into a second field. I use the second field to drive a repeat control showing all values chosen to date. Code below
The result: To test this, type 'ab' in the field, and select any choice, then click on the 'add' link. The repeat shows (, abcd) as you would expect. Test 2, repeat that process twice. The repeat will show (, abcd, ab, abcd). For some reason, the 'ab' is being added into the second field. The third test - type ab, choose a choice, add. Type 'bc', choose a choice, add. Type cd, but instead of choosing a choice, click the save button. The repeat then shows (, abcd, bc, abce, cd). I can't explain that at all.
The workaround: To get this to work properly, I can change the link's refresh type from partial to full. I have no idea why this makes it work, but it does. I don't know if I really want to do that, but I will if I have to. This is on a fairly complex form and I'd rather not do a full refresh unless I have to.
The code: Here's a cut-down xpage that demonstrates the problem:
<?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" formName="Test">
</xp:dominoDocument>
</xp:this.data>
<xp:panel id="mainPanel">
<xp:button value="Save" id="button1">
<xp:eventHandler event="onclick" submit="true"
refreshMode="complete">
<xp:this.action>
<xp:saveDocument var="document1"></xp:saveDocument>
</xp:this.action>
</xp:eventHandler>
</xp:button>
<xp:inputText id="objName1" value="#{document1.objName}">
<xp:typeAhead mode="full" minChars="2" valueMarkup="true"
var="searchValue" id="typeAhead1">
<xp:this.valueList><![CDATA[#{javascript: //In here I usually do some searching to find a result set, but for the purposes of a test, let's just do an array
var mapResults:java.util.TreeMap = new java.util.TreeMap();
mapResults.put("abcd", "abcd");
mapResults.put("abce", "abce");
mapResults.put("abcf", "abcf");
mapResults.put("abcg", "abcg");
mapResults.put("abch", "abch");
mapResults.put("abci", "abci");
//Now format the results
var returnList = "<ul>";
//All results are in the TreeMap and are sorted. Now add them to the output
var iter:Iterator = mapResults.entrySet().iterator();
while (iter.hasNext()) {
var nextEntry = iter.next();
returnList += "<li>" + nextEntry.getValue() +"</li>";
}
//Lastly, close off the UL tag and return
returnList += "</ul>";
return returnList;}]]></xp:this.valueList>
</xp:typeAhead>
</xp:inputText>
<xp:link escape="true" text="Add Another Object" id="link1">
<xp:eventHandler event="onclick" submit="true"
refreshMode="partial" id="eventHandler1" refreshId="mainPanel">
<xp:this.action><![CDATA[#{javascript:var currentVals = document1.getItemValueArray("addObjName");
//check for null value in first item in array - happens
//if we clear the array from the 'cross' buttons in the repeat
if (currentVals[0] == null) {
currentVals = new Array("");
}
//get the value from the object name field and whack it on the end
var newValue = getComponent("objName1").getValue();
//Put the value into a test field so we can prove that the value wasn't added below
document1.replaceItemValue("test1", newValue);
//Now add it to the field - where is the search text coming from?
currentVals.push(newValue);
document1.replaceItemValue("addObjName", currentVals);
getComponent("objName1").setValue("");
}]]></xp:this.action>
</xp:eventHandler>
</xp:link>
<xp:repeat id="repeat1" rows="30" var="rowData" indexVar="index"
repeatControls="false">
<xp:this.value><![CDATA[#{javascript:document1.getItemValueArray("addObjName")}]]></xp:this.value>
<xp:panel tagName="div">
<xp:link escape="true" id="link2" title="Remove from list">
<xp:image url="/vwicn081.gif" id="image1">
<xp:eventHandler event="onclick" submit="true"
refreshMode="partial" refreshId="mainPanel" id="eventHandler2">
<xp:this.action><![CDATA[#{javascript://remove an entry (string) from a list of strings (thanks to Mark Leusink)
Array.prototype.removeEntry = function( entry:String ) {
if ( #IsNotMember(entry, this)) {
return this;
}
var res = #Trim( #Replace(this, entry, "") );
return (typeof res == "string" ? (res.length==0 ? [] : [res]) : res);
}
var oldArray:Array = document1.getItemValueArray("addObjName");
var myArray = oldArray.removeEntry(oldArray[index]);
document1.replaceItemValue("addObjName", myArray);
}]]></xp:this.action>
</xp:eventHandler>
</xp:image>
</xp:link>
<xp:text escape="true" id="computedField1" value="#{javascript:rowData}">
</xp:text>
</xp:panel>
</xp:repeat>
<xp:text escape="true" id="computedField2">
<xp:this.value><![CDATA[#{javascript:"and here is what was added in the link:"}]]></xp:this.value>
</xp:text>
<xp:text escape="true" id="computedField3" value="#{document1.test1}">
</xp:text>
</xp:panel>
</xp:view>
Any takers? Is there a logical explanation for this, or should I just use my work-around and stop being a pedant?
UPDATE
Using the XPage above, I can also replicate the following scenario:
Step 1: Type 'abc' in the field, select 'abcd from the choices, click on the 'add' link. All good.
Step 2: Type 'ab' then wait, then type 'c', then choose 'abce'. Add again. This adds ab, abc, abce.
Step 3: Type ab then wait, then type c, then don't choose a choice, but hit Save instead. This adds ab, abc - even though the link has not been clicked to add the value to the second field.
Step 4: Type ab, wait, type c, choose 'abcf' and click Add. Only abcf is added this time.
I'm trying to reconcile this against Sven's answer below. Partial update works, so I know he's right. In step 2 above, I'm thinking that the typeahead code is executing twice, each time triggering the onClick event for the link, and lastly the onClick event for the link is running. Similar thing in Step 3 - the link isn't being clicked at all in that example, but the onClick code is still running. In step 4, after the save, typeahead is executing twice, but this time it isn't executing the onClick code.
Now I think I get it, even if I don't like it. As Sven says, The $$xspsubmitid identifies the element which submitted the data to the server - in the last submit action. So in this case it remembers the previous submit state and repeats that - so when you are doing it immediately after a plain save, you don't get any extra code, but when you are doing it after an event in the link, it submits the page in the same way that the link did - complete with onClick code.
There's probably a reason why doing this is a good thing, but my head hurts and the problem is solved! Thanks Sven!
It is your typeahead which updates the value of your field / your array. When typing a second or more characters in the input box, the current data in your input box is sent to the server and this updates the value in the document. When clicking onto the link, the value is added again.
EDIT:
You can fix this by changing the mode of the typeahead to "partial"
EDIT 2:
The difference between the refresh modes of a typeahed is how the data are sent to the server: In case of a full refresh, the data are sent with a POST request, in case of a partial refresh, the data are sent as a GET request.
Let's have a look at the different HTTP requests:
1.) Partial mode
2.) Full mode
As you can see in the two picutrures, in full mode there are more "fields" sent to the server. The interessting one is the $$xspsubmitid: Currently it is empty. But as soon we click the link, the field is filled with the id of the link:
The $$xspsubmitid identifies the element which submitted the data to the server. In this case the link, and the server processes the code of the link.
And now comes the problem with the typeahead in full mode:
As you can see, the $$xspsubmitid is always added to the POST request, and that's why the server executes the click event of the link for each typeahead request.

Xpages 8.5.3 Bug ? xp:input text

I have 2 edit Boxes, 1 is editable, the other just prints the name of the sessionScope, both fields are in a in 2 cells next to each other , see code below, inputText1 is a editable text field, inputText2 is non editable text, printing #{sessionScope.Tmp1_ABC}
<xp:td>
<xp:inputText id="inputText1"
loaded="true">
<xp:this.value><![CDATA[${javascript:var fieldName = "Tmp_" + #ReplaceSubstring(varcollection," ","");
return '#{sessionScope.' + #ReplaceSubstring(fieldName,#List(" ",")","("),"") + '}';}]]></xp:this.value>
<xp:eventHandler
event="onchange" submit="true" refreshMode="partial"
refreshId="panel_1" />
</xp:inputText>
</xp:td>
<xp:td>
<xp:inputText id="inputText2"
loaded="true">
<xp:this.value><![CDATA[#{javascript:var fieldName = "Tmp1_" + #ReplaceSubstring(varcollection," ","");
return '#{sessionScope.' + #ReplaceSubstring(fieldName,#List(" ",")","("),"") + '}';}]]></xp:this.value>
<xp:eventHandler
event="onchange" submit="true" refreshMode="partial"
refreshId="panel_1" />
</xp:inputText>
</xp:td>
AFAIK you can't assemble your data binding that way. Data binding is an EL expression, not SSJS. You could try to trick using ${} to compute #{}
As per Stefan, using SSJS (javascript:) to define a value for an input control results in it only being read-only. For values to be editable they must be bound using Expression Language (EL). For something as complex as the example (looping over a list of field/variable) you will most likely have to learn a little Java to allow you to connect your input controls to the bean via EL. If the purpose of the code is to have one field editable and the other read-only you should (as Per suggested) change the control from being an inputText to just (computed) text or a label and remove the event handler. Somebody reading the code could easily be confused in thinking you intended both to be editable.

Resources