Reputation: 71
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!
Upvotes: 1
Views: 479
Reputation: 10485
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.
Upvotes: 2