LotusScript in XPages (1): Basics

LotusScript in XPages

Dies ist der erste Teil des Artikels „LotusScript in XPages“. Der zweite Teil befindet sich hier.

 

Wäre es nicht schön, wenn man in XPages direkt mit Lotus Script arbeiten könnte? Wenn es einen Weg gäbe, mit der sich Lotus Script-Code direkt in der XPage einbetten liesse, und wie folgt zu verwenden wäre?

Prinzipiell ist das möglich, aber der hier dargestellte Weg ist eher als Workaround anzusehen und wird das Alpha-Stadium wohl eher nicht verlassen. Aber es lässt sich anhand dieser Anleitung zeigen, wie man XPages flexibel erweitern und weitere Interpreter-Sprachen der XPages-Engine hinzufügen kann.

Was mit der hier vorgestellten Lösung jedoch möglich ist, kann man in dieser kleinen Beispiel-XPage sehen:

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

   <xp:label id="label1">
      <xp:this.value>
         <![CDATA[#{lotusscript:returnValue=-1 }]]>
      </xp:this.value>
   </xp:label>
   <xp:br/>
   <xp:br/>
   <xp:label id="label2">
      <xp:this.value><![CDATA[${lotusscript:
         Dim session as New NotesSession
         Dim db as NotesDatabase
         Set db = session.CurrentDatabase

         returnValue = db.AllDocuments.getFirstDocument.UniversalID
      }]]></xp:this.value>
   </xp:label>
</xp:view>

In der XPage kann der neue Tag „lotusscript:“ verwendet und beliebiger LotusScript-Code direkt in die XPage eingebettet werden. Das Ergebnis im Browser sieht dann wie folgt aus:

Soviel vorab, nun zu erst einmal zu den Basics…

Grundlagen: Method-Bindings & Value-Bindings

Um einen neuen Tag für XPages bereitzustellen, müssen zu aller erst Bindings für Methoden- und für Werte-Zuweisungen erstellt werden. Die beiden Binding-Arten unterscheiden sich – grob formuliert* – wie folgt: Value-Bindings werden überall dort verwendet, wo ein Wert einer Komponente zugewiesen wird und (wie der Name erahnen lässt) eine value-Eigenschaft exitsiert. Dies ist bei praktisch allen UIKomponenten der Fall. Method-Bindings hingegen kommen bei der Zuweisung bei samtlichen Events ins Spiel: Der SSJS-Code eines BeforePageLoad-Events wird mit einem Method-Binding gesetzt, oder der Code bei Button-Events usw.

Die Value-Binding-Klasse, die für die hier geschilderten Zwecke benötigt wird, sieht wie folgt aus:

package ch.hasselba.xpages.jsf.el;

import com.ibm.xsp.binding.ValueBindingEx;
import javax.faces.context.FacesContext;
import javax.faces.el.EvaluationException;
import javax.faces.el.PropertyNotFoundException;

public class LSValueBinding extends ValueBindingEx {
    private String data;

    public LSValueBinding(String data) {
        this.data = data;
    }

    @Override
    public Object getValue(FacesContext context)
        throws EvaluationException, PropertyNotFoundException {
            return LSExecutor.execLotusScriptCode( this.data );
    }

    @Override
    public void setValue(FacesContext context, Object obj)
       throws EvaluationException, PropertyNotFoundException {}

    @Override
    public boolean isReadOnly(FacesContext context)
        throws EvaluationException, PropertyNotFoundException {
            return true;
    }

    @SuppressWarnings("unchecked")
    @Override
    public Class getType(FacesContext context)
        throws EvaluationException, PropertyNotFoundException {
            return Object.class;
    }
}

Das Value-Binding erweitert die Klasse com.ibm.xsp.binding.ValueBindingEx und überschreibt deren Eigenschaften, wo es nötig ist. Im Konstruktor der Klasse findet die Wertezuweisung statt; es wird hier der Teil, der in der XPage nach dem „lotusscript:“ folgt, übergeben. Auch mehrzeiliger Code in der XPage wird als einfacher String durchgereicht, getrennt durch Zeilenumbrüche.

Um an den Wert des Bindings zu gelangen, wird während der Verarbeitung durch das JSF Framework die Methode getValue() aufgerufen; dies ist die Stelle, an der die Daten des Value-Bindings verarbeitet werden, und wie man hier sehen kann, findet der Aufruf des LotusScript-Codes genau an dieser Stelle statt.

Das notwendige Method-Binding ist so ähnlich aufgebaut:

package ch.hasselba.xpages.jsf.el;

import com.ibm.xsp.binding.MethodBindingEx;
import javax.faces.context.FacesContext;
import javax.faces.el.EvaluationException;
import javax.faces.el.MethodNotFoundException;

public class LSMethodBinding extends MethodBindingEx {
    private String data;

    public LSMethodBinding () {
        super();
    }

    public LSMethodBinding (String expr) {
        super();
        this.data = expr;
    }

    @Override
    public Object invoke(FacesContext context, Object[] obj)
        throws EvaluationException, MethodNotFoundException {
            return LSExecutor.execLotusScriptCode( this.data );
    }
    @SuppressWarnings("unchecked")
    @Override
    public Class getType(FacesContext context)
        throws MethodNotFoundException {
            return null;
    }
}

Hier wird wie Klasse com.ibm.xsp.binding.MethodBindingEx erweitert und – wenn nötig – überschrieben. Anders als beim Value-Binding erfolgt der „Abruf“ der Daten eines Method-Bindings durch das JSF Framework nicht durch getValue(), sondern durch die Methode invoke(). Hierbei können theoretisch noch Parameter übergeben werden, die für die Verarbeitung relevant sein könnten. In diesem Fall kann dies aber getrost ignoriert werden.

Zu guter Letzt müssen die beiden Bindings in eine BindingFactory-Klasse zusammen geführt und mit dem „lotusscript„-Tag verbunden werden:

package ch.hasselba.xpages.jsf.el;

import com.ibm.xsp.util.ValueBindingUtil;
import com.ibm.xsp.binding.BindingFactory;
import javax.faces.application.Application;
import javax.faces.el.MethodBinding;
import javax.faces.el.ValueBinding;

public class LSBindingFactory implements BindingFactory {

    public String getPrefix() {
        return "lotusscript";
    }

    @SuppressWarnings("unchecked")
    public MethodBinding createMethodBinding(
         Application app, String expr, Class[] obj) {
        String script = ValueBindingUtil.parseSimpleExpression(expr);
        return new LSMethodBinding(script);
    }

    public ValueBinding createValueBinding(Application app, String expr) {
        String script = ValueBindingUtil.parseSimpleExpression( expr );
        return new LSValueBinding(script);
    }
}

Die Klasse erweitert com.ibm.xsp.binding.BindingFactory und ist die „Weiterleitung“ innerhalb des JSF-Frameworks: Die Methode getPrefix() liefert den String zurück, für den diese BindingFactory zuständig ist; es ist praktisch jeder Bezeichner verwendbar, nur id und javascript sind bereits belegt.

Während der Verarbeitung der Bindings sucht das JSF-Framework zur Laufzeit nach der passenden Factory. Dabei werden alle bekannten Factories nach dem passenden Prefix durchsucht, weshalb die BindingFactory dem Framework noch bekannt gemacht werden muss, um verwendet werden zu können.

Im zweiten Teil wird eine Quick-n-Dirty-Variante gezeigt, die BindingFactory bekannt zu machen und der LotusScript-Wrapper wird vorgestellt.

*: Anmerkung:

Aus Sicht der JSF-Spezifikation ist der Unterschied zwischen Value-Binding und Method-Binding etwas komplexer, als hier dargestellt.

Veröffentlicht unter Allgemein, Expression Language, Extensibility API, Java, JSF, Lotus Script, XPages, XSP | Verschlagwortet mit , , , , , , , , , | 2 Kommentare

ObjectDataSource: Kleines „How To“

Mit der Extension Library bzw. dem Upgrade Pack 1 ist für XPages eine neue Datasource-Komponente hinzugekommen, die ObjectDataSource. Diese Datasource kann wie die Standard-Datasources für View und Dokument ebenfalls an den verschiedensten Elementen einer XPage angehangen werden, d.h. sowohl an die UIViewRoot der XPage, als auch in Custom Controls, Repeat Controls oder sonstigen Elementen, die eine DataSource verwenden.

Grundlegend gilt für ObjectDataSources, das sie beliebige Java-Objekte beinhalten können, solange die Objekte selbst serialisierbar sind, also das Interface java.io.Serializable implementieren. Hier ein einfaches Beispiel eines Java-Objektes:

package ch.hasselba.extlib.demo;

import java.io.Serializable;

public class ObjectDataDemo implements Serializable {

    long timeStamp;

    public ObjectDataDemo(){
        this.update();
    }

    public void update(){
        this.timeStamp = java.lang.System.currentTimeMillis();
    }

    public long getTimeStamp() {
        return timeStamp;
    }

}

Die Java-Klasse ist einfach aufgebaut und liefert den Zeitstempel zurück, der beim letzten Aufruf der update()-Methode gesetzt wurde. Um die Klasse in der XPage verwenden zu können, muss sie nur in einem Source-Folder bereitgestellt sein, der dem Build-Path hinzugefügt wurde bzw. die Klasse im neuen „JavaCode“-Ordner  abgelegt sein – eine Modifikation der faces-config.xml ist nicht nötig.

Mittels der createObject-Eigenschaft der ObjectDataSource wird die XPage dazu veranlasst, eine Instanz der Klasse anzulegen, womit das instanzierte Objekte der ObjectDataSource gehört; um die Stabilität einer Applikation nicht zu gefährden und unerwartete Ergebnisse zu provozieren, sollten die Objekte daher nicht anderweitig gespeichert werden (z.B. in Managed Beans o.ä.).

<xp:this.data>
   <xe:objectData var="objectData1" scope="view"
      createObject="#{javascript:
         new ch.hasselba.extlib.demo.ObjectDataDemo();}">
   </xe:objectData>
</xp:this.data>

Eine ObjectDataSource hat neben dem zu verwendenden scope auch noch die Eigenschaft var, die (wie bei den anderen Datasources auch) den programmatischen Namen darstellt, mit dem die DataSource referenziert werden kann. In diesem Fall ist das Objekt und alle Eigenschaften/Methoden des Objektes über objectData1 zu erreichen:

<xp:label id="label1"
   value="#{javascript:objectData1.getTimeStamp()}" />

Da z.B. ein Partial Refresh die Datasources nicht jedesmal neuberechnet, muss ein eventuell benötigter Update-Mechanismus selbst implementiert werden. In diesem Beispiel dient dafür die Methode update(), die im Backend aufgerufen werden kann:

<xp:button value="Label" id="button1">
   <xp:eventHandler event="onclick" submit="true"
      refreshMode="partial" refreshId="label1">
      <xp:this.action>
         <![CDATA[#{javascript:objectData1.update()}]]>
      </xp:this.action>
   </xp:eventHandler>
</xp:button>

[Button ruft die Update-Funktion auf, label1 wird refresht und neuer Zeitstempel angezeigt]

Die ObjectDataSources besitzen eine weitere Eigenschaft namens saveObject. Diese Eigenschaft ist optional und nur erforderlich, wenn man beabsichtigt, auf ein Speichern der DataSources reagieren zu wollen (um z.B. Daten ins Backend zu speichern, o.ä.).

Achtung:

Einige Methodennamen dürfen in der Java-Klasse nicht verwendet werden, da dies sonst zu Problemen mit der bestehenden Architektur kommen kann. Namen wie save() oder load() sind zu vermeiden!

Veröffentlicht unter ExtLib, Java, JSF, ServerSide JavaScript, XPages | Verschlagwortet mit , , , , , , , | Schreib einen Kommentar

Bug: facesContext.getRenderResponse()

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:

Veröffentlicht unter Bug, JSF, ServerSide JavaScript, XPages | Verschlagwortet mit , , , , , , , , | Schreib einen Kommentar

Quick-n-Dirty: Id der UIViewRoot ändern

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.

Veröffentlicht unter Allgemein, JSF, XPages | Verschlagwortet mit , , , | Schreib einen Kommentar

Quick-n-Dirty: SSJS Code vor BeforPageLoad ausführen

Um SSJS Code vor dem „BeforePageLoad„-Event auszuführen, kann der Code einfach in eine DataContext-Variable gepackt werden. Die Berechnung der DataContext-Variable sollte auf „ComputeOnPageLoad“ gesetzt sein, dann wird der Code auch nur einmal ausgeführt. Als Rückgabewert sollte null verwendet werden, dann verschwindet Sie aus dem Speicher und die Variable ist auch nicht via SSJS referenzierbar.

Natürlich bleiben die im SSJS-Code definierten Variablen Ereignisübergreifend erhalten. Hier ein Beispiel, indem die Variable testVar vor dem „BeforePageLoad„-Event befüllt und während des Events ausgewertet wird:

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

   <xp:this.beforePageLoad>
      <![CDATA[#{javascript:
         print("Before PageLoad: " + testVar )}]]>
   </xp:this.beforePageLoad>

   <xp:this.dataContexts>
      <xp:dataContext var="dummy">
         <xp:this.value>
            <![CDATA[${javascript:print("Data Context!");
               testVar = "Data Context!"
               return null}]]>
         </xp:this.value>
      </xp:dataContext>
   </xp:this.dataContexts>
   
</xp:view>
Veröffentlicht unter Allgemein, Java Script, ServerSide JavaScript, XPages | Verschlagwortet mit , , , , | 3 Kommentare

Performance-Killer in der XPage

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.

Veröffentlicht unter Expression Language, JSF, Performance, ServerSide JavaScript, XPages | Verschlagwortet mit , , , , , , , , | 3 Kommentare

Bug: Invalider Java-Code durch berechnete Tag-Attribute

António A Ramos hat einen interessanten Bug entdeckt: Werden die Attribute eines HTML-Tags im Designer berechnet, wird die XPage nicht mehr verarbeitet und ein Internal Server Error tritt auf

So wird der folgende HTML-Tag ordnungsgemäß gerendert…

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

… jedoch ist eine Berechung nicht zulässig:

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
   <li data-theme="#{javascript:'e'}"></li>
</xp:view>

Der Grund ist dabei die Umsetzung in den Java-Code, denn dieser verwendet das Attribute ungeprüft als Variablenname – was natürlich fehl schlagen muss.

So sieht der generierte Java-Code aus:

Ein Workaround ist, den generierten Java-Code direkt im Designer zu ändern und fehlerhafte Variablendeklaration von Hand zu bereinigen – allerdings muss der Vorgang nach jedem Build wiederholt werden.

Veröffentlicht unter Allgemein, Bug, Expression Language, Java, ServerSide JavaScript, XPages | Verschlagwortet mit , , , , , , , , , | 4 Kommentare

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

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

Veröffentlicht unter Allgemein, Expression Language, Java, JSF, XPages | Verschlagwortet mit , , , , , , , | Schreib einen Kommentar

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

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.

Veröffentlicht unter Bug, Expression Language, JSF, XPages | Verschlagwortet mit , , , , , , , | 2 Kommentare

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

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.

 

EDIT:

Die unteren beiden Beispiele OHNE Anführungsszeichen sind soweit JSF-konform. Details siehe hier.

Veröffentlicht unter Bug, Expression Language, Java Script, JSF, ServerSide JavaScript, XPages | Verschlagwortet mit , , , , , , , , | Ein Kommentar