Bin letzte Woche im Zuge einer Fehleranalyse auf ein Problem gestossen, das auch für andere relevant sein könnte. Im XPages Forum bei IBM habe ich das Problem schon gepostet; dort hält man sich aber liever vornehm zurück.
Ich erkläre die Situation am besten einmal anhand eines kleinen code snippets,mit dem ich den Fehler unter 8.5.3UP1 reproduzieren kann
<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
<xp:button value="Label" id="button1">
<xp:eventHandler event="onclick" submit="true"
refreshMode="partial" refreshId="col1" />
</xp:button>
<xp:table>
<xp:tr>
<xp:td id="col1">
<xp:text escape="true" id="computedField2" value="#{javascript:@Now()}">
<xp:this.converter>
<xp:convertDateTime type="both" />
</xp:this.converter>
</xp:text>
</xp:td>
<xp:td id="col2">
<xp:text escape="true" id="computedField1" value="#{javascript:@Now()}">
<xp:this.converter>
<xp:convertDateTime type="both" />
</xp:this.converter>
</xp:text>
<xp:dataTable id="dataTable1" rows="30">
<xp:this.value><![CDATA[#{javascript:
print('You shall not refresh');
}]]></xp:this.value>
<xp:column id="column1" />
</xp:dataTable>
</xp:td>
</xp:tr>
</xp:table>
</xp:view>
EinfacheXPage mit einer 2 spaltige Tabelle
col1 enthält ein computedText control mit @Now()
col2 enthält ein computedText control mit @Now() + einer datatable. In den Values der datatable gibt es lediglich ein print statement.
Das erwartete Verhalten ist:
Click auf den Button, der sich über der Tabelle befindet; dies löst einen partial refresh auf col1 aus. Die Anzeige wird dort aktualisiert. Die Anzeige in col2 nicht.
Das funktioniert.
Was aber ist mit der datatable? Man könnte erwarten, daß diese nicht vom partial refresh aktualisiert wird; sie befindet sich ja in col2
Aber weit gefehlt: Blick auf die ServerConsole. Und was sehen meine Augen dort bei jedem Click auf den Button?
Richtig: You shall not refresh
Das kann doch nicht wirklich richtig sein, oder ??
Hallo,
ich habe im XPages Forum eine kurze, aber einfache Antwort gegeben ;)
Es ist leider ein "normales" Verhalten seit es XPages gibt: Alle Datencontainer (Datatable, Repeats, Datacontexts ) werden bei Partial Refreshs neu berechnet, sogar mehrfach, auch Partial Execution hilf da nicht.
Für Datatables geschieht dies in der "Apply Request Values"-Phase, als auch in der "Render Response"-Phase, bei Data Context-Variablen sieht das ganz noch übler aus (in JEDER Phase findet die Berechnung statt).
Beispel Code für eine "verzögerte" Data Context-Variable, da kann man das auf der Konsole besser sehen:
<xp:this.dataContexts>
<xp:dataContext var="x">
<xp:this.value><![CDATA[#{
javascript:print( java.lang.System.currentTimeMillis() + " -> DC Recalc! ");
java.lang.Thread.currentThread().sleep(100);
'1'}]]>
</xp:this.value>
</xp:dataContext>
</xp:this.dataContexts>
Das Beispiel verdeutlich auch nochmal, das die Berechnung wirklich mehrmals ausgeführt wird. Richtig ist das nicht...
Ich habe mir damit beholfen, die Refresh-Id abzufangen, und dann die Berechnung ggf. Abzubrechen
<xp:repeat id="repeat1" rows="30" var="rCol">
<xp:this.value><![CDATA[#{javascript:
function calculate(){
var data:java.util.Vector = new java.util.Vector();
data.add(java.lang.System.currentTimeMillis());
return data;
}
if( viewScope.containsKey("data") == false){
viewScope.data = calculate();
return viewScope.data;
}
if( com.ibm.xsp.ajax.AjaxUtil.isAjaxPartialRefresh(facesContext) === true ){
var pId = com.ibm.xsp.ajax.AjaxUtil.getAjaxComponentId( facesContext );
if( pId !== getClientId( 'repeat1' ) )
return viewScope.data;
}
viewScope.data = calculate();
viewScope.data}]]>
</xp:this.value>
<xp:label value="#{javascript:rCol }" id="label1"></xp:label>
</xp:repeat>
Sven
Das ist meine Beobachtung, die leider ein anderes Ergebnis auf der Konsole liefert. Ich habe die obige XPage um einen Zeitstempel für den Output ergänzt und einen Debug-PhaseListener dazwischen geschaltet. Zur Verdeutlichung habe ich jeweils den Code des Buttons inkl. der Ausgabe auf der Serverkonsole gepostet.
Hier der Code der Datatable:
<xp:dataTable id="dataTable1" rows="30">
<xp:this.value><![CDATA[#{javascript:
print(java.lang.System.currentTimeMillis() + ' You shall not refresh');
}]]></xp:this.value>
<xp:column id="column1" />
</xp:dataTable>
Button
<xp:button value="Label" id="button1">
<xp:eventHandler event="onclick" submit="true" refreshMode="partial" refreshId="col1" />
</xp:button>
1. Öffnen der XPage
19.02.2012 18:02:17 HTTP JVM: Before phase: RENDER_RESPONSE 6
19.02.2012 18:02:17 HTTP JVM: 1329670937472 You shall not refresh
19.02.2012 18:02:17 HTTP JVM: After phase: RENDER_RESPONSE 6
2. Partial Refresh
19.02.2012 18:05:37 HTTP JVM: Before phase: RESTORE_VIEW 1
19.02.2012 18:05:37 HTTP JVM: After phase: RESTORE_VIEW 1
19.02.2012 18:05:37 HTTP JVM: Before phase: APPLY_REQUEST_VALUES 2
19.02.2012 18:05:37 HTTP JVM: 1329671137733 You shall not refresh
19.02.2012 18:05:37 HTTP JVM: After phase: APPLY_REQUEST_VALUES 2
19.02.2012 18:05:37 HTTP JVM: Before phase: PROCESS_VALIDATIONS 3
19.02.2012 18:05:37 HTTP JVM: After phase: PROCESS_VALIDATIONS 3
19.02.2012 18:05:37 HTTP JVM: Before phase: UPDATE_MODEL_VALUES 4
19.02.2012 18:05:37 HTTP JVM: After phase: UPDATE_MODEL_VALUES 4
19.02.2012 18:05:37 HTTP JVM: Before phase: INVOKE_APPLICATION 5
19.02.2012 18:05:37 HTTP JVM: After phase: INVOKE_APPLICATION 5
19.02.2012 18:05:37 HTTP JVM: Before phase: RENDER_RESPONSE 6
19.02.2012 18:05:37 HTTP JVM: 1329671137769 You shall not refresh
19.02.2012 18:05:37 HTTP JVM: After phase: RENDER_RESPONSE 6
Die Berechung der DataTable erfolgt zwei Mal, das Element wurde nicht refresht.
Ändere ich den Code des Buttons auf Partial Execution, ist das Ergebnis wie folgt:
Button
<xp:button value="Label" id="button1" execMode="partial">
<xp:eventHandler event="onclick" submit="true" refreshMode="partial" refreshId="col1" />
</xp:button>
1. Öffnen der XPage
19.02.2012 18:13:54 HTTP JVM: Before phase: RENDER_RESPONSE 6
19.02.2012 18:13:54 HTTP JVM: 1329671634490 You shall not refresh
19.02.2012 18:13:54 HTTP JVM: After phase: RENDER_RESPONSE 6
2. Partial Refresh
19.02.2012 18:14:02 HTTP JVM: Before phase: RESTORE_VIEW 1
19.02.2012 18:14:02 HTTP JVM: After phase: RESTORE_VIEW 1
19.02.2012 18:14:02 HTTP JVM: Before phase: APPLY_REQUEST_VALUES 2
19.02.2012 18:14:02 HTTP JVM: 1329671642321 You shall not refresh
19.02.2012 18:14:02 HTTP JVM: After phase: APPLY_REQUEST_VALUES 2
19.02.2012 18:14:02 HTTP JVM: Before phase: PROCESS_VALIDATIONS 3
19.02.2012 18:14:02 HTTP JVM: After phase: PROCESS_VALIDATIONS 3
19.02.2012 18:14:02 HTTP JVM: Before phase: UPDATE_MODEL_VALUES 4
19.02.2012 18:14:02 HTTP JVM: After phase: UPDATE_MODEL_VALUES 4
19.02.2012 18:14:02 HTTP JVM: Before phase: INVOKE_APPLICATION 5
19.02.2012 18:14:02 HTTP JVM: After phase: INVOKE_APPLICATION 5
19.02.2012 18:14:02 HTTP JVM: Before phase: RENDER_RESPONSE 6
19.02.2012 18:14:02 HTTP JVM: 1329671642360 You shall not refresh
19.02.2012 18:14:02 HTTP JVM: After phase: RENDER_RESPONSE 6
Selbst wenn ich das Event explizit auf Partial Execution setze, wird der Code berechnet (aber wenigstens nur noch einmal im Render Response)
Button
<xp:button value="Label" id="button1" execMode="partial">
<xp:eventHandler event="onclick" submit="true" refreshMode="partial" refreshId="col1" execMode="partial"/>
</xp:button>
1. Öffnen der XPage
19.02.2012 18:23:17 HTTP JVM: Before phase: RENDER_RESPONSE 6
19.02.2012 18:23:17 HTTP JVM: 1329672197161 You shall not refresh
19.02.2012 18:23:17 HTTP JVM: After phase: RENDER_RESPONSE 6
2. Partial Refresh
19.02.2012 18:23:18 HTTP JVM: Before phase: RESTORE_VIEW 1
19.02.2012 18:23:18 HTTP JVM: After phase: RESTORE_VIEW 1
19.02.2012 18:23:18 HTTP JVM: Before phase: APPLY_REQUEST_VALUES 2
19.02.2012 18:23:18 HTTP JVM: After phase: APPLY_REQUEST_VALUES 2
19.02.2012 18:23:18 HTTP JVM: Before phase: PROCESS_VALIDATIONS 3
19.02.2012 18:23:18 HTTP JVM: After phase: PROCESS_VALIDATIONS 3
19.02.2012 18:23:18 HTTP JVM: Before phase: UPDATE_MODEL_VALUES 4
19.02.2012 18:23:18 HTTP JVM: After phase: UPDATE_MODEL_VALUES 4
19.02.2012 18:23:18 HTTP JVM: Before phase: INVOKE_APPLICATION 5
19.02.2012 18:23:18 HTTP JVM: After phase: INVOKE_APPLICATION 5
19.02.2012 18:23:18 HTTP JVM: Before phase: RENDER_RESPONSE 6
19.02.2012 18:23:18 HTTP JVM: 1329672198875 You shall not refresh
19.02.2012 18:23:18 HTTP JVM: After phase: RENDER_RESPONSE 6
Selbst das Setzen der execId auf col1 ändert nichts - der Refresh führt immer zu einer Neuberechnung der DataTable (wenn auch nur einmal)...
Sven
Bei Output-Scriptblöcken war das unter 8.5.2 auch der Fall, aber ist wohl mitlerweile (8.5.3) behoben worden.
Das der Partial Refresh erst komplett ist, wenn die Renderphase abgeschlossen ist, ist aber so wie es sein sollte, denn dann wird die Response erst an den Client gesendet.
Auch ein
var response = facesContext.getExternalContext().getResponse();
response.flushBuffer();
hilft da nix...
Ich denke das es sich hierbei schlichtweg um einige Bugs in der Implementierung handelt.
Hier eine Version des Workarounds, der noch die Mehrfach-Berechnung abfängt. Es fehlt eigentlich nur noch die Überprüfung, ob der Refresh auf eine elterliche Komponente ausgeführt wurde.
Den Code in z.B. die Werte-Berechnung des Repeat Controls einbauen, die bisherige Berechnung in die Funktion "calculate" übertragen.
/*****
*** Workaround fuer Refresh-Problem und Mehrfach-Berechnung
***
*****/
/*** Der eigentliche Code in die Funktion "calculate" einfügen ***/
function calculate(){
var data:java.util.Vector = new java.util.Vector();
data.add(java.lang.System.currentTimeMillis());
return data;
}
/*** ab hier gehts los ***/
var clientId = getClientId( this.id );
var data = null;
/*** Bisher keine Berechnung **/
if( viewScope.containsKey("fixData-" + clientId ) === false){
data = calculate();
viewScope.put( "fixData-" + clientId, data );
return data;
}
/*** Element wurde nicht refresht ***/
if( com.ibm.xsp.ajax.AjaxUtil.isAjaxPartialRefresh(facesContext) === true ){
var rId = com.ibm.xsp.ajax.AjaxUtil.getAjaxComponentId( facesContext );
if( rId !== clientId )
return viewScope.get( "fixData-" + clientId );
}
/*** Mehrfach-Berechnung abfangen ***/
if( requestScope.containsKey("fixData-" + clientId ) == true ){
value = viewScope.get( "fixData-" + clientId );
}else{
value = calculate();
viewScope.put( "fixData-" + clientId, value );
requestScope.put("fixData-" + clientId , true );
}
value
Sven