Posts Tagged: ‘JSF’

Bug: facesContext.getRenderResponse()

11. März 2012 Posted by Sven Hasselbach

Eigentlich sollte die Methode  getRenderResponse() true zurück liefern, wenn der Code in der Render Response-Phase ausgeführt wird, doch leider ist die Funktion nicht korrekt implementiert. So liefert die Funktion bei einem normalen Seitenaufruf falsche Ergebnisse, bei einem Partial Refresh hingegen funktioniert sie jedoch wie sie soll.

Zur Erläuterung verwende ich folgende XPage:

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">

   <xp:label value="Label" id="label1">
      <xp:this.rendered>
         <![CDATA[#{javascript:
            if( facesContext.getRenderResponse() ){
               print( "Render Response!" );
            }else{
               print( "NOT Render Response!" );
            }
            return true}]]>
      </xp:this.rendered>
   </xp:label>

   <xp:button value="Label" id="button1">
      <xp:eventHandler event="onclick" submit="true"
           refreshMode="partial" refreshId="label1">
      </xp:eventHandler>
   </xp:button>
   
</xp:view>

Öffnet man die XPage im Browser, wird auf der Serverkonsole folgendes ausgegeben:

Bei einem Partial Refresh hingegen wird die Phase korrekt erkannt:

Quick-n-Dirty: Id der UIViewRoot ändern

11. März 2012 Posted by Sven Hasselbach

Setzt man dem UIViewRoot-Element eine Id…

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core" id="abc">
   <xp:inputText id="inputText1"></xp:inputText>
</xp:view>

… ändert sich der generierte HTML-Code entsprechend ab:

...
<form id="abc:_id1" method="post"
 action="/TESTXPages.nsf/viewId.xsp"
 class="xspForm" enctype="multipart/form-data">
<input type="text" id="abc:_id1:inputText1"
  name="abc:_id1:inputText1" class="xspInputFieldEditBox">
<input type="hidden" name="$$viewid" id="abc:_id1__VUID"
 value="!d38auuyoeb!">
<input type="hidden" name="$$xspsubmitid">
<input type="hidden" name="$$xspexecid">
<input type="hidden" name="$$xspsubmitvalue">
<input type="hidden" name="$$xspsubmitscroll">
<input type="hidden" name="abc:_id1" value="abc:_id1">
</form>
...

Auch kann man auf die UIViewRoot nun mittels der Id zugreifen, ein getComponent(“abc”) liefert die Komponente zurück.

Performance-Killer in der XPage

20. Februar 2012 Posted by Sven Hasselbach

Dank einer Frage von Ulrich Krause im XPages Developer Forum wurde ein Thema “wiederbelebt”, das mir vor einiger Zeit in einem Projekt aufgefallen ist und sich als wahre Bremse bei XPages-Applikationen herausstellt:

Sämtliche Datenquellen (DataContext-Variablen, Repeat Controls, usw.) werden bei jedem Partial Refresh neu berechnet, auch wenn sie nicht das Ziel (bzw. ein Kind-Element des Ziels) des Refreshs sind.

Ich nutze als Beispiel hierfür die von Ulrich Krause gepostete Beispiel-XPage, um das Problem zu verdeutlichen. Den fett markierten Code ist von mir zur Verdeutlichung auf der Serverkonsole eingebaut worden, dass es sich nicht um einen fehlerhaftes print-Statement handelt, sondern der Code wirklich neu berechnet wird.

<?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(java.lang.System.currentTimeMillis() + ' You shall not refresh');
}]]></xp:this.value>
 <xp:column id="column1" />
 </xp:dataTable>
 </xp:td>
 </xp:tr>
 </xp:table>
</xp:view>

Wird der Button geklickt, wird ein Refresh auf das Element mit der Id “col1″ ausgelöst, und eigentlich dürfte die Data Table nicht neu berechnet werden, doch genau das ist aber der Fall!

[Die Ausgabe auf der Serverkonsole habe ich mit einem Debug PhaseListener kombiniert, um die Phasen im Lifecycle hervorzuheben]

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 währen des Partial Refreshs.

Ändert man die Ausführung des Buttons auf Partial Excecution, erhält man folgendes Ergebnis:

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

Wieder erfolgt die Berechung der DataTable zwei Mal währen des Partial Refreshs.

Eine Änderung des Execution-Modes des Events verringert die Berechnung zumindest auf eine Neuberechnung pro Partial Refresh:

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.

Da diese Neuberechnung bei jedem Partial Refresh ausgelöst wird, werden Applikationen extrem ausgebremst. Daher sollte die Berechnung der Datenquelle wenn möglich auf “Computed On Page Load” gesetzt werden.

Wenn dies nicht möglich ist, habe ich hier ein kleines Snippet, das die Berechnung nur dann durchführt, wenn die Datenquelle auch wirklich refresht wird:

<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>

[Hier ein Beispiel mit einem Repeat Control]

Die Version ist recht simpel aufgebaut, eine verbesserte Version werde ich in den nächsten Tagen als XSnippet veröffentlichen.

“Compute Dynamically” Vs. “Compute on Page Load”

11. Februar 2012 Posted by Sven Hasselbach

Der Artikel von Mark Roden über den Mix von ${} und #{} zur gleichen Zeit brachte mich dazu, mich noch einmal ausführlich mit dem Thema “Compute Dynamically” und “Compute on Page Load” zu befassen, denn der hervorragende Artikel von Paul Withers erläutert zwar die grundsätzlichen Unterschiede zwischen den beiden Berechnungsvarianten, allerdings gibt es noch ein paar Ungereimtheiten.

Um einen Überblick über die Unterschiede bei der Verwendung aufzuzeigen, sind hier zehn Varianten aufgeführt, die in einigen Fällen interessante Ergebnisse liefern.

Zur Erläuterung:

  • XSP-Code ist der Code in der XPage
  • Java Code ist ein Screenshot des generierten Java Codes
  • Page Load ist ein Screenshot, wenn die Test-XPage geöffnet wird
  • Partial Refresh ist ein Screenshot nach eine Refresh des Computed Fields
  • Ergebnis stellt die Bewertung der Variante dar. Ob der Code wirklich mehrfach ausgeführt wurde, ist nicht geprüft worden, sondern nur das “sichtbare” Ergebnis im Browser

 

Variante 1: Compute Dynamically

XSP Code

<xp:text id="computedField1">
   <xp:this.value>
      <![CDATA[#{javascript:java.lang.System.currentTimeMillis()}]>
   </xp:this.value>
</xp:text>

Java Code

Page Load

Partial Refresh

Ergebnis

Wird jedesmal neu berechnet.

 

Variante 2: Compute on Page Load

XSP Code

<xp:text id="computedField1">
   <xp:this.value>
      <![CDATA[${javascript:java.lang.System.currentTimeMillis()}]>
   </xp:this.value>
</xp:text>

Java Code

Page Load

Partial Refresh

Ergebnis

Berechnung nur bei Page Load.

 

Variante 3: Compute Dynamically inside Compute on Page Load

XSP Code

<xp:text id="computedField1">
   <xp:this.value>
      <![CDATA[${javascript:#{javascript:java.lang.System.currentTimeMillis()}}]]>
   </xp:this.value>
</xp:text>

Java Code

Page Load

Partial Refresh

Ergebnis

Berechnung nur bei Page Load.

 

Variante 4: Compute on Page load inside Compute Dynamically

XSP Code

<xp:text id="computedField1">
   <xp:this.value>
      <![CDATA[#{javascript:${javascript:java.lang.System.currentTimeMillis()}}]]>
   </xp:this.value>
</xp:text>

Java Code

Page Load

Partial Refresh

Ergebnis

Berechnung nur bei Page Load.

 

Variante 5: Compute Dynamically inside Compute on Page Load (Hochkomma)

XSP Code

<xp:text id="computedField1">
   <xp:this.value>
      <![CDATA[${javascript:'#{javascript:java.lang.System.currentTimeMillis()}']]>
   </xp:this.value>
</xp:text>

Java Code

Page Load

Partial Refresh

Ergebnis

Wird jedesmal neu berechnet.

 

Variante 6: Compute on Page Load inside Compute Dynamically (Hochkomma)

XSP Code

<xp:text id="computedField1">
   <xp:this.value>
      <![CDATA[#{javascript:'${javascript:java.lang.System.currentTimeMillis()}']]>
   </xp:this.value>
</xp:text>

Java Code

Page Load

Partial Refresh

Ergebnis

Berechnung des “inneren” Codes findet nicht mehr statt.

 

Variante 7: Compute on Page Load inside inside Compute on Page Load (Hochkomma)

XSP Code

<xp:text id="computedField1">
   <xp:this.value>
      <![CDATA[${javascript:'${javascript:java.lang.System.currentTimeMillis()}']]>
   </xp:this.value>
</xp:text>

Java Code

Page Load

Partial Refresh

Ergebnis

Berechnung des “inneren” Codes findet nicht statt.

 

Variante 8: Compute Dynamically inside Compute Dynamically (Hochkomma)

XSP Code

<xp:text id="computedField1">
   <xp:this.value>
      <![CDATA[#{javascript:'#{javascript:java.lang.System.currentTimeMillis()}']]>
   </xp:this.value>
</xp:text>

Java Code

Page Load

Partial Refresh

Ergebnis

Berechnung des “inneren” Codes findet nicht statt.

 

Variante 9: Compute Dynamically inside Compute Dynamically (Hochkomma + Leerzeichen)

XSP Code

<xp:text id="computedField1">
   <xp:this.value>
      <![CDATA[#{javascript:' #{javascript:java.lang.System.currentTimeMillis()} ']]>
   </xp:this.value>
</xp:text>

Java Code

Page Load

Partial Refresh

Ergebnis

Berechnung des “inneren” Codes findet nicht statt.

 

Variante 10: Compute on Page Load inside Compute on Page Load (Hochkomma)

XSP Code

<xp:text id="computedField1">
   <xp:this.value>
      <![CDATA[${javascript:' ${javascript:java.lang.System.currentTimeMillis()} ']]>
   </xp:this.value>
</xp:text>

Java Code

Page Load

Partial Refresh

Ergebnis

Berechnung des “inneren” Codes findet immer statt.

“It’s not a feature, it’s a bug!”

10. Februar 2012 Posted by Sven Hasselbach

In meinem letzten Beitrag habe ich einen Bug entdeckt, den ich an dieser Stelle noch etwas ausführlicher darstellen möchte, denn es handelt sich hierbei nicht um ein normales Verhalten von JSF, sondern schlichtweg um einen Bug während der Transformation nach Java.

Im Vorfeld möchte ich jedoch auf einen sehr guten Artikel von Paul Withers aufmerksam machen, in dem ausführlich dargestellt wird, wie es sein müsste:

http://www.intec.co.uk/xpages-bindings-when-runs-at-page-load/

Der Einfachheit halber greife ich das von Paul gegebene Beispiel auf, um den Bug zu verdeutlichen. Ergänzt man nämlich den Code um Anführungszeichen, dann wird der “On Page Load“-Code nicht mehr ausgeführt:

<xp:text id="computedField2" escape="true"
 value="You are logged in as '${javascript:@UserName()}'.
The fields id is #{id:computedField1}"></xp:text>

[Fett: Der "On Page Load"-Code // In Rot: Die zusätzlichen Anführungszeichen]

Das Ergebnis ist dann folgendes:

Zurückzuführen ist das auf einen Fehler bei der Transformierung, der generierte Javacode sieht wie folgt aus:

Dies ist ein Bug im Designer, denn jedwede Form der ${}-Syntax wird ungeprüft als “On Page Load” interpretiert. So wird der folgende Code trotz Fehler in EL übersetzt…

… hingegen wird diese Variante ordnungsgemäß als Fehler markiert und lässt sich nicht speichern:

Irrtümlicherweise habe ich in meinem vorigen Artikel weitere Beispiele aufgeführt, die Code enthalten, der ohne das Anführungszeichen ausgeführt wird. Dies war im Zuge des Schreiben des letzten Artikels, als ich noch weitere Test gemacht habe. Hier ist das Verhalten natürlich JSF-konform und der Code wird ordnungsgemäß ausgeführt.

Meine Beobachtung bezüglich der fehlerhaften Transformation jedoch bezieht sich nicht nur auf Output Scripts, sondern um jede Art des Value Bindings: Sobald eine x-beliebige Kombination von ${} (auch über mehrere Zeilen etc.) vorkommt, tritt der Fehler auf.

Bug: ${} in Output Script-Blöcken

8. Februar 2012 Posted by Sven Hasselbach

Bei der Verwendung eines Output Scripts muss darauf geachtet werden, dass kein Code verwendet wird, der eine Zeichenfolge beinhaltet, die eine “Compute On Load“-ähnliche Syntax hat: Ein Bug sorgt dafür, das bei der Verwendung von ${} (mit oder ohne Inhalt) einiges durcheinander gerät, und der komplette SSJS-Code falsch verarbeitet wird.

So gibt folgender Code wie zu erwarten eine Messagebox mit der Id des Labels aus…

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">

   <xp:label value="Label" id="label1"></xp:label>
   
   <xp:scriptBlock id="scriptBlock1">
      <xp:this.value>
         <![CDATA[$
            var id = '#{id:label1}';
            alert( id );
         ]]>
      </xp:this.value>
   </xp:scriptBlock>
   
</xp:view>

… wird aber an irgendeiner Stelle im Script Block die genannte Kombination verwendet, gerät alles in Schieflage:

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">

   <xp:label value="Label" id="label1"></xp:label>
   
   <xp:scriptBlock id="scriptBlock1">
      <xp:this.value>
         <![CDATA[
            var id = '#{id:label1}';
            var bug = '${id:label1}';
             alert( "id: " + id + "\nbug: " + bug);
         ]]>
      </xp:this.value>
   </xp:scriptBlock>
   
</xp:view>

Die Variable id ist leer, und die Variable bug wird nicht verändert:

Es spielt keine Rolle an, welcher Stelle im Script Block die fehlerhafte Variante vorkommt, auch der Inhalt zwischen den eckigen Klammern ist unbedeutent: Ein auskommentierter Code über mehrer Zeilen hat die gleiche Auswirkung!

Varianten wie z.B.

//var bug = '${
// Kein Text!
//}';

oder

var bug = ${X}

werfen keine Fehler, sondern generieren im besten Fall “nur” fehlerhaften CSJS-Code.

Erst wenn keine Anführungszeichen verwendet werden und die EL-Syntax fehlerhaft ist, tritt ein Laufzeitfehler auf.

var bug = ${/EL}

 

Der Bug existiert in 8.5.2 als auch in 8.5.3. Andere Versionen können ebenfalls betroffen sein.

DataContext-Variablen

1. Februar 2012 Posted by Sven Hasselbach

Will man auf die DataContext-Variablen einer XPage zugreifen, gibt es die Möglichkeit, die Methode getDataContexts() zu verwenden, die für jede UIComponent existiert. Die Methode liefert eine java.util.List zurück, die sich z.B. in einen Array verwandeln lässt, um auf die einzelnen Mitglieder zu zugreifen. Die Mitglieder wiederum sind vom Typ com.ibm.xsp.model.DataContext, mit getVars() lässt sich auf den Inhalt der jeweiligen Variable zugreifen.

Hier ein Beispiel:

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">

   <xp:this.dataContexts>
      <xp:dataContext var="dcVar" value="1" />
   </xp:this.dataContexts>

   <xp:text escape="true" id="computedField1">
      <xp:this.value>
         <![CDATA[#{javascript:
         var lc:java.util.List = view.getDataContexts();
         var dc:com.ibm.xsp.model.DataContext = lc.toArray()[0];
         dc.getVars().toString()
         }]]>
      </xp:this.value>
   </xp:text>
</xp:view>

[Fett: Die DataContext-Variable // Rot: Die UIComponent, an der die Variable "hängt"]

In diesem Beispiel ist die Variable direkt der UIViewRoot-Komponente zugeordnet worden. Ist die Variable an einer anderen UIComponent (z.B. einem Custom Control), muss auf den DataContext dieser Komponente zugegriffen werden. Ist also eine DataContext-Variable im Custom Control definiert, nicht jedoch in der eigentlichen XPage, so würde ein view.getDataContexts() nichts zurück liefern.

Um auf die Variablen eines Custom Controls zuzugreifen, muss also entweder der komplette Komponentenbaum durchlaufen werde, oder das Custom Control muss mit id definiert sein, um referenziert werden zu können.

Hier das Custom Control ccDCVar:

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">

   <xp:this.dataContexts>
      <xp:dataContext var="dcVarCC" value="2" />
   </xp:this.dataContexts>

</xp:view>

Die XPage, die auf das Custom Control zugreift, kann dann wie folgt aufgebaut sein:

<?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">

   <xc:ccDCVar id="cc1"></xc:ccDCVar>

   <xp:text escape="true" id="computedField1">
      <xp:this.value>
         <![CDATA[#{javascript:
         var lc:java.util.List = getComponent("cc1").getDataContexts();
         var dc:com.ibm.xsp.model.DataContext = lc.toArray()[0];
         dc.getVars().toString()
         }]]>
      </xp:this.value>
   </xp:text>
</xp:view>

 [Fett: Zugriff auf die UIComponent mittels getComponent()  // Rot: Id des Custom Controls]

Security: Fernsteuerbare DocumentDataSources

12. Januar 2012 Posted by Sven Hasselbach

Analog zu den ViewDataSources lassen sich auch DocumentDataSources über die Adresszeile des Browsers manipulieren: Der URL-Parameter databaseName ermöglicht hierbei die Steuerung der zugrundliegenden Datenbank, und überschreibt einen eventuell in der XPage hinterlegten Wert.

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
   <xp:this.data>
      <xp:dominoDocument
         var="document1"
         databaseName="DB1.nsf">
      </xp:dominoDocument>
   </xp:this.data>
     <xp:button value="Label" id="button1">
      <xp:eventHandler event="onclick"
         submit="true" refreshMode="complete">
         <xp:this.action>
            <xp:save></xp:save>
         </xp:this.action>
      </xp:eventHandler>
   </xp:button>
</xp:view>

Wird diese XPage mit dem Parameter databaseName=DB2.nsf aufgerufen und abgeschickt bzw. gespeichert, erfolgt das Anlegen des Dokumentes nicht wie definiert in der DB1.nsf, sondern in DB2.nsf.

Ebenso verhält es sich, wenn ein Dokument geöffnet wird: Auch hierbei lässt sich steuern, aus welcher Datenbank das Dokument stammten soll (in diesem Fall nur, solange keine documentId  in der XPage eingestellt ist.)

Werden mehrere DataSources auf einer XPage verwendet, werden die URL-Parameter dort angewendet, “wo es passt”, d.h. es wird die nächste DataSource (View oder Document) verwendet, die nicht mit ignoreRequestParam=”true” abgesichert wurde.

Quick-n-Dirty: Locale setzen (2)

12. Januar 2012 Posted by Sven Hasselbach

Wie die Locale-Einstellung für eine einzelne Xpage gesetzt wird, ist hier beschrieben. Um die Einstellung jedoch Applikationsweit zu setzen, kann dies durch einen eigenen ViewHandler umgesetzt werden.

Dazu benötigt man eine eigene Java-Klasse, die die bestehende Methode calculateLocale überschreibt:

package ch.hasselba.xpages.debug;

import java.util.Locale;
import javax.faces.context.FacesContext;
import javax.faces.application.ViewHandler;

public class ViewHandlerSH extends
   com.ibm.xsp.application.ViewHandlerExImpl {

   @Override
   public Locale calculateLocale(FacesContext arg0) {
      return Locale.CHINESE;
   }

   public ViewHandlerSH(ViewHandler paramViewHandler){
      super(paramViewHandler);
   }

}

[In Fett: Die Spracheinstellung wird generell auf Chinesisch gesetzt]

Die Klasse muss dann noch in die faces-config.xml eingebunden werden.

<?xml version="1.0" encoding="UTF-8"?>
<faces-config>
   <application>
      <view-handler>ch.hasselba.xpages.debug.ViewHandlerSH</view-handler>
   </application>
</faces-config>

Und so sieht dann das Ergebnis aus, wenn ein Fehler auftritt:

 

Security: Fernsteuerbare ViewDataSources

30. Dezember 2011 Posted by Sven Hasselbach

Ist bei einer Datenbank die Option “Don’t allow URL open” gesetzt, ist sie nicht mehr im Web erreichbar. Mit den URL-Parametern databaseName und viewName lässt sich in Verbindung mit einer ViewDatasource dieser Schutzmechanismus jedoch aushebeln, und könnte ein Problem darstellen, wenn sich nur auf diese Option verlassen wurde.

Als Beispiel dient hier eine lokales NAB, dass zu Demonstrationszwecken eine offene ACL hat (Default = Manager), bei dem die genannte Option jedoch aktiviert ist:

Öffnet man diese Datenbank im Browser, so erscheint eine Fehlermeldung:

Legt man jedoch eine XPages mit einer ViewDatasource in einer anderen Datenbank an, kann dieser einfache Schutzmechanismus umgangen werden, da die beiden Paramter die in der XPage definierte Quelle überschreiben; dadurch wird eine “Fernsteuerung” der ViewDataSource ermöglicht.

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">

   <xp:viewPanel rows="30" var="viewCol">
      <xp:this.data>
         <xp:dominoView var="view1" viewName="" />
      </xp:this.data>
      <xp:viewColumn
         value="#{javascript:viewCol.getUniversalID()}">
     </xp:viewColumn>
   </xp:viewPanel>
   
</xp:view>

Öffnet man folgende XPage, ist die darzustellende Ansicht natürlich leer…

… hängt man allerdings die passenden URL-Parameter an, sieht die gleiche XPage entsprechend anders aus:

Es handelt sich dabei um die beiden Dokumente aus dem persönlichen Addressbuch.

Zwar müssen immer noch Berechtigungen auf die jeweilige Datenbank gegeben sein, damit Daten ausgelesen werden können, dennoch sollte prinzipiell immer ignoreRequestParams=”true” gesetzt sein.

Domino Datasources On-the-Fly (3): ValueBinding & MethodBinding

29. Dezember 2011 Posted by Sven Hasselbach

Das ist der dritte Teil der Serie “Domino Datasources On-the-Fly”. Der zweite Teil findet sich hier.

Will man eine Wertezuweisung berechnen lassen, muss ein ValueBinding erstellt werden. Damit lässt sich dann z.B. der Formname eines DominoDocumentData-Objektes berechnen (zugegebenermaßen ein kleinwenig sinnlos, da man dies im Code sowieso erledigen könnte…).

Hierfür existiert die Methode createValueBinding() der Application-Klasse, mit der ein neues ValueBinding-Objekt instanziert werden kann:

var app = facesContext.getApplication();
var vb = app.createValueBinding("#{javascript:@Text(@Now())}");

Dieses ValueBinding muss dem jeweiligen Datasource-Objekt über die Methode setValueBinding() zugewiesen werden. Als erster Parameter muss der Name der zu setzenden Eigenschaft angegeben werden, dann folgt das jeweilige ValueBinding-Objekt.

var data = new com.ibm.xsp.model.domino.DominoDocumentData();
data.setVar("document1");
data.setValueBinding("formName", vb);
view.addData(data);

Die Fett hervorgehobene Eigenschaft entspricht dem Parameter im XPages Source:

<?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="#{javascript:@Text(@Now())}" />
    </xp:this.data>
</xp:view>

Allerdings ist diese Zuweisung nicht bei allen Eigenschaften möglich! Bei noch auszuführenden Methoden wie z.B. dem QuerySaveDocument-Event muss stattdessen mit einem MethodBinding-Objekt gearbeitet werden. Wieder findet die Instanzierung über das Application-Objekt statt:

var code = "";
code += "#{javascript:document1.replaceItemValue(\"Test\"";
code += ", @Text(@Now()));}";
var mb = app.createMethodBinding( code, null);

Eine Methode setMethodBinding() existiert jedoch nicht; das MethodBinding-Objekt muss daher über spezielle Methoden dem jeweiligen DataSource-Objektes zugewiesen werden. Die Namensgebung der Methoden ist hierbei jedoch trivial, es muss nur ein “set” vor die jeweilige Methode gestellt werden:

var data = new com.ibm.xsp.model.domino.DominoDocumentData();
data.setVar("document1");
data.setQuerySaveDocument(mb);
view.addData(data);

Hier noch der Source einer vollständigen Beispiel-Implementierung:

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">

<xp:button value="Create Datasource" id="button1">
   <xp:eventHandler event="onclick" submit="true"
       refreshMode="complete">
       <xp:this.action>
           <![CDATA[#{javascript:
              var app = facesContext.getApplication();
           var data = new com.ibm.xsp.model.domino.DominoDocumentData();

           data.setVar("document1");

           var value = "#{javascript:@Text(@Now())}";
           var vb = app.createValueBinding(value);
           data.setValueBinding("formName", vb);

           var code = "";
           code += "#{javascript:document1.replaceItemValue(\"Test\"";
           code += ", @Text(@Now()));}";
           var mb = app.createMethodBinding( code, null);
           data.setQuerySaveDocument(mb); 

           view.addData(data);
        }]]>
       </xp:this.action>
   </xp:eventHandler>
</xp:button>
<xp:button value="Use Datasource" id="button2">
   <xp:eventHandler event="onclick" submit="true"
     refreshMode="complete">
      <xp:this.action>
         <xp:actionGroup>
            <xp:executeScript>
               <xp:this.script>
                  <![CDATA[#{javascript:
                     document1.setValue("Feld", "ABC");
                  }]]>
               </xp:this.script>
              </xp:executeScript>
           <xp:saveDocument var="document1" />
         </xp:actionGroup>
      </xp:this.action>
   </xp:eventHandler>
</xp:button>
</xp:view>

Domino Datasources On-the-Fly (2): DominoDataView

27. Dezember 2011 Posted by Sven Hasselbach

Das ist der zweite Teil der Serie “Domino Datasources On-the-Fly”. Der erste Teil findet sich hier.

Views lassen sich ebenfalls programmatisch erstellen, allerdings muss mindestens der Name der Ansicht angegeben sein (und ausserdem muss die Ansicht existieren):

var data = new com.ibm.xsp.model.domino.DominoViewData();
data.setVar("view1");
data.setViewName("All");
view.addData(data);

Hier eine Beispiel-Implementierung in eine XPage (in der Datenbank muss die Ansicht “All” vorhanden sein) :

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">

<xp:button value="Create Datasource" id="button1">
   <xp:eventHandler event="onclick" submit="true"
      refreshMode="partial" refreshId="label1">
      <xp:this.action>
         <![CDATA[#{javascript:
            var data = new com.ibm.xsp.model.domino.DominoViewData();
            data.setVar("view1");
            data.setViewName("All");
            view.addData(data);
         }]]>
      </xp:this.action>
   </xp:eventHandler>
</xp:button>
<xp:label id="label1">
   <xp:this.value><![CDATA[#{javascript:
      try{
         view1.getName();
      }catch(e){e}
    }]]>
    </xp:this.value>
   </xp:label>
</xp:view>

Folgende Eigenschaften können bei einem DominoViewData-Objekt gesetzt werden (Stand 8.5.3):

  • setParentId(String)
  • setSearchList(String)
  • setDatabaseName(String)
  • setVar(String)
  • setSearch(String)
  • setExpandLevel(int)
  • setSearchExactMatch(boolean)
  • setViewName(String)
  • setSortOrder(String)
  • setRequestParamPrefix(String)
  • setSortColumn(String)
  • setKeys(String)
  • setScope(String)
  • setStartKeys(String)
  • setSearchVariants(boolean)
  • setCategoryFilter(String)
  • setSearchMaxDocs(int)
  • setDataCache(String)
  • setKeysExactMatch(String)
  • setIgnoreRequestParams(boolean)
  • setSearchFuzzy(boolean)

Eine vollständige Liste findet sich hier.

Im nächsten Teil werden die Eigenschaften des DominoDataDocument-Objekts vorgestellt.

Domino Datasources On-the-Fly (1): Basics

23. Dezember 2011 Posted by Sven Hasselbach

Um eine Datasource On-the-Fly mittels SSJS zu erstellen, muss zum Einen ein neues Datasource-Objekt instanziert werden, zum Anderen die neue Datenquelle der XPage bekannt gegeben werden.

Mit dem folgenden Code wird eine neue Document Datasource namens document1 erstellt und dann dem UIViewRoot-Element bekannt gemacht:

var data = new com.ibm.xsp.model.domino.DominoDocumentData();
data.setVar("document1");
view.addData(data);

Direkt nach der Anlage lässt sich die Datenquelle leider noch nicht im Code verwenden, d.h. ein “document1.save()” schlägt an dieser Stelle fehl. Die neue Datasource kann aber in einem anderen Code-Segment wie gewohnt verwendet werden.

Hier eine Beispiel-Implementierung in eine XPage:

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">

<xp:button value="Create Datasource" id="button1">
   <xp:eventHandler event="onclick" submit="true"
      refreshMode="complete">
      <xp:this.action>
         <![CDATA[#{javascript:
            var data = new com.ibm.xsp.model.domino.DominoDocumentData();
            data.setVar("document1");
            data.setFormName("DynData");  
            view.addData(data);
         }]]>
      </xp:this.action>
   </xp:eventHandler>
</xp:button>
<xp:button value="Use Datasource" id="button2">
   <xp:eventHandler event="onclick" submit="true"
     refreshMode="complete">
      <xp:this.action>
         <xp:actionGroup>
            <xp:executeScript>
               <xp:this.script>
                  <![CDATA[#{javascript:
                     document1.setValue("Feld", "ABC");
                  }]]>
               </xp:this.script>
              </xp:executeScript>
           <xp:saveDocument var="document1" />
         </xp:actionGroup>
      </xp:this.action>
   </xp:eventHandler>
</xp:button>
</xp:view>

Die XPage besteht aus zwei Buttons: Mit “Create Datasource” wird eine neue Datasource angelegt, mit “Use Datasource” die neue DocumentDatasource verwendet und gespeichert. Wird der Button “Use Datasource” geklickt, bevor die neue Datasource angelegt wurde, tritt (logischerweise) ein Fehler auf.

Im nächsten Teil geht es um die möglichen Eigenschaften des Domino Document Data- und des Domino View Data-Objektes.

Quick-n-Dirty: Die HTTP-Session des Domino-Servers

14. Dezember 2011 Posted by Sven Hasselbach

Um mit SSJS auf die HTTP-Session eines Domino-Servers zuzugreifen, ist nicht viel Code nötig:

var exCon = facesContext.getExternalContext();
var sess = exCon.getRequest().getSession();

Dadurch lassen sich folgende Informationen ermitteln:

  • getAttribute(Name:String)

Liefert gebundenes Objekt des Attributes “Name” zurück

  • getAttributeNames()

Liefert die Namen aller Attribute als java.util.Enumeration zurück.

  • getCreationTime()

Zeit der Session-Erstellung, Millisekunden seit 1.1.1970 GMT, z.B. “1.323785660873E12″

  • getId()

Liefert die aktuelle SessionID zurück, z.B. “D18P2LFFB5″

  • getLastAccessedTime()

Zeit, an der die Session zuletzt aufgerufen wurde. In Millisekunden seit 1.1.1970 GMT, z.B. “1.323785660873E12″

  • getMaxInactiveInterval()

Länge der maximalen Gültigkeit einer Session (in Sekunden) , die vom Servlet ohne Clientaktivität abgewartet wird.

  • isNew()

True, wenn der Client die Sessio noch nicht kennt, false wenn der Client einer bestehende Session beitritt.

  • invalidate()

Setzt eine Session zurück und löscht alle Daten der Session

  • removeAttribute(Name:String)

Löscht das Attribut “Name” und entfernt das gebundene Objekt

  • setAttribute(Name:String, obj:Object)

Setzt das Attribut “Name” mit dem Objekt “obj“.

  • setMaxInactiveInterval(int interval)

Setzt maximale Länge der Session in Sekunden, bis das Servlet die Session ablaufen lässt.

 

Um die Attribute einer Session auszugeben, kann z.B. folgender Code verwendet werden:

var attr = sess.getAttributeNames();
while( attr.hasMoreElements() ){
   var elem = attr.nextElement();
   result += elem + " -> ";
   result += sess.getAttribute( elem );
   result += " [";
   result += typeof(sess.getAttribute( elem ));
   result += "]";
 }

result

OpenNTF XSnippets Beta gestartet

22. November 2011 Posted by Sven Hasselbach

XSnippets – The next generation code bin, ist als Beta gestartet.

Das neue OpenNTF Projekt dient als Sammlung für kleine Code-Schnippsel, die von der Community für die Community zur Verfügung gestellt werden. Hier sind die ersten Schnippsel zu finden.