Posts Tagged: ‘Expression Language’

The XPages EL Directory

8. Juli 2015 Posted by Sven Hasselbach

I am currently working on an overview of available objects and properties for XPages Expression Language. A first incomplete and horrible designed version can be found here.

 

XPages: Use a Method Binding as Converter

8. März 2014 Posted by Sven Hasselbach

I accidentally found a way to add a method binding as a converter to a component, because I have added a managed bean as a converter directly in the source code. The DDE does not support this.

If you go to the converter property of a component, you can only add one of the predefined converters:

But you can go to the source and add a method binding to the option, in this case my bean which implements my converter functionality.

<xp:inputText
    id="inputText1"
    value="#{sessionScope.inputText1}"
    converter="#{myConverterBean}" />

If you now reopen the saved XPage, the converter property is filled in, but cannot edited / changed anymore.

You must remove the property in the source code to get the old behaviour back.

Tested in 8.5.2, 8.5.3 & ND 9

XPages: The Problem with DataContext Variables

11. Februar 2013 Posted by Sven Hasselbach

There is a large problem with data context variables if they are bound dynamically.
They will be recomputed again and again, even when in Partial Execution mode and if they are not in use. Here is a small demo XPage:

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

   <xp:this.dataContexts>
      <xp:dataContext var="dataContext">
         <xp:this.value>
            <![CDATA[#{javascript:
               // Sleep for 100ms
               java.lang.Thread.currentThread().sleep( 100 );
               print('-=> Recalc: ' + java.lang.System.currentTimeMillis());
               return true;
            }]]>
          </xp:this.value>
       </xp:dataContext>
    </xp:this.dataContexts>

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

   <xp:button value="Refresh" id="buttonRefresh" >
      <xp:eventHandler event="onclick" submit="false"
         refreshMode="partial"
         refreshId="labelRefreshMe"
         execId="labelRefreshMe"
         execMode="partial" />
   </xp:button>

</xp:view>

If you are open the XPage, a dataContext variable will be recalculated for three times:

Clicking on the button will recalculate them up to eleven times:

Keep in mind: Partial Refresh & Partial Execution is enabled. That makes them only useable if their values are computed on page load.

LotusScript in XPages (3): Quick-n-Dirty-Aktivierung

10. April 2012 Posted by Sven Hasselbach

LotusScript in XPages

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

 

Die Quick-n-Dirty-Aktivierung

Damit die BindingFactory verwendet werden kann, müsste eigentlich ein Plugin erstellt werden, doch es gibt auch eine “Abkürzung”, denn die Factory kann auch über einen angepassten ViewHandler in XPages verwendet werden. Dies ist beim Testen / Entwickeln eine sehr praktische Angelegenheit, da sich dadurch der Aufwand deutlich verringern lässt (Vielen Dank an dieser Stelle an Jesse Gallagher für seine Idee).

Der ViewHandler ist einfach aufgebaut und sieht wie folgt aus:

package ch.hasselba.xpages.jsf.el;

import javax.faces.application.ViewHandler;
import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;
import com.ibm.xsp.application.ApplicationEx;
import com.ibm.xsp.factory.FactoryLookup;

public class LSViewHandler extends
   com.ibm.xsp.application.ViewHandlerExImpl {
    public LSViewHandler(ViewHandler paramHandler) {
        super(paramHandler);
    }

    @SuppressWarnings({ "deprecation" })
    @Override
    public UIViewRoot createView(FacesContext context,
       String paramString) {
        ApplicationEx app = (ApplicationEx) context.getApplication();
        FactoryLookup facts = app.getFactoryLookup();

        LSBindingFactory lsfac = new LSBindingFactory();
        if(facts.getFactory(lsfac.getPrefix()) == null) {
            facts.setFactory(lsfac.getPrefix(), lsfac);
        }

        return super.createView(context, paramString);
    }

}

Der aktuellen Application-Instanz wird hierbei die BindingFactory im createView() hinzugefügt, wenn diese noch nicht vorhanden ist, danach wird die ursprüngliche createView()-Methode der erweiterten Klasse com.ibm.xsp.application.ViewHandlerExImpl aufgerufen.

Dann muss nur noch die faces-config.xml modifiziert und der neue ViewHandler eingetragen werden:

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

Nun kann in der XPage mit dem neuen “lotusscript”-Tag experimentiert werden. Da der Designer den neuen Tag nicht kennt, wird immer eine Warnung ausgegeben werden bzw. die Syntax mit einem Ausufezeichen markiert:

Dies stellt aber (zumindest unter 8.5.3) kein Problem dar, ist aber erst durch eine “saubere” Plugin-Lösung behebbar.

In den nächsten Teilen wird die Plugin-Variante vorgestellt, und ein paar Erweiterungsmöglichkeiten für den LotusScript-Wrapper gezeigt.

LotusScript in XPages (2): LotusScript-Wrapper

8. April 2012 Posted by Sven Hasselbach

LotusScript in XPages

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

 

Der LotusScript-Wrapper

Um dynamischen LotusScript-Code auszuführen, bietet sich die Execute()-Funktion an: Mit der Funktion lässt sich fast der gesamte Umfang der LotusScript-eigenen Backendfunktionalität nutzen, also auch Scriptlibraries einbinden uvm.

Leider steht diese Methode jedoch nicht  in Java direkt zur Verfügung (im Gegensatz zur Session.evaluate()-Methode für @Formelsprache), so dass nur der Umweg bleibt, die Funktion durch Aufruf eines LotusScript-Agenten zu verwenden, und das Ergebnis an die XPage zurück zu liefern. Dabei wird der auszuführende LotusScript-Code und das Ergebnis der Operation über ein temporäres NotesDokument via DocumentContext hin- und hergereicht.

Hier die Klasse “LSExceutor”, die die nötigen Hilfsfunktionen bereitstellt:

package ch.hasselba.xpages.jsf.el;

import javax.faces.context.FacesContext;
import java.util.Vector;
import lotus.domino.Agent;
import lotus.domino.Database;
import lotus.domino.Document;

public class LSExecutor {
    private final static String agentName  = "(LSExecutor)";
    private final static String fieldLSResult = "LSResult";
    private final static String fieldLSCode = "LSCode";

    public static Vector execLotusScriptCode( final String lscode ){
        try{
            Database curDB =  (Database) getVariableValue("database");
            Document doc = curDB.createDocument();
            String hlp = lscode.replace( "\n", "\t" );
            doc.appendItemValue( fieldLSCode, hlp );
            Agent agent = curDB.getAgent(  agentName );
            agent.runWithDocumentContext( doc );
            return doc.getItemValue( fieldLSResult );

        }catch(Exception e){
            e.printStackTrace();
        }
        return null;
    }

     public static Object getVariableValue(String varName) {
         FacesContext context = FacesContext.getCurrentInstance();
         return context.getApplication().getVariableResolver().
          resolveVariable(context, varName);
     }
}

Die statische Methode execLotusScriptCode() wird in den Bindings verwendet, die in Teil 1 beschrieben wurden. Durch den Umstand, dass die runWithDocumentContext()-Methode auf das Ende der Agentenausführung wartet, ist eine sequentielle Verarbeitung gewährleistet.

Der Agent ist ebenfalls recht einfach aufgebaut:

%REM
    Agent LSExecutor
    Created Apr 6, 2012 by Sven Hasselbach/Sven Hasselbach
    Description: LSExecutor agent is a mapper for executing
                 LotusScript code from XPages
%END REM
Option Public
Option Declare

Const fieldLSCode = "LSCode"
Const fieldLSResult = "LSResult"

Dim returnValue
Sub Initialize
    Dim session As New NotesSession
    Dim doc As NotesDocument
    Dim lsCode, tmp , ret

    Set doc = session.Documentcontext
    lsCode = doc.Getitemvalue( fieldLSCode )
    tmp = Evaluate( | @ReplaceSubstring( "| & _
        lsCode(0) & |"; @Char(9) ; @Char(10) ) | )

    ret = Execute( tmp(0) )
    doc.Replaceitemvalue fieldLSResult , returnValue

End Sub

Um auf das Ergebnis der Berechnung zurückgreifen zu können, muss eine globale Variable verwendet werden, da die Execute()-Funktion selbst keine Rückgabemöglichkeit bietet (siehe Domino Designer Hilfe). In diesem Fall ist es “returnValue”, dessen Wert in das via DocumentContext übergebene Dokument geschrieben wird. Entsprechend muss der LotusScript-Code angepasst sein, siehe dazu auch die Beispiele am Anfang des 1. Teils des Artikels. Hier zeigt sich eine Schwachstelle: Es können keine Objekte zwischen Java und LotusScript übergeben werden; ein Zugriff auf z.B. den FacesContext ist in LotusScript nicht möglich. Soll eine DocumentCollection zurück geliefert werden, muss dieses als Array von DocumentUniversalIds geschehen usw.

Der Agent muss im Namen des Benutzers laufen, daher muss der “Run as Web user”-Haken gesetzt sein:

Im dritten Teil wird eine Quick-n-Dirty-Variante gezeigt, die BindingFactory bekannt zu machen.

 

Anmerkung:

An dieser Stelle sei nocheinmal ausdrücklich darauf hingewiesen, das der hier gezeigte Code sich maximal im “Alpha”-Status befindet.

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.

Bug: Invalider Java-Code durch berechnete Tag-Attribute

17. Februar 2012 Posted by Sven Hasselbach

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.

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

Quick-n-Dirty: Hijacking TypeAhead in CSJS

9. Dezember 2011 Posted by Sven Hasselbach

Matthias Nicklisch hat eine interessante Frage im XPages Forum gestellt, nachdem er festgestellt hat, dass im Designer zwar ein OnStart- / OnComplete-Event für die TypeAhead-Funktion angeboten wird, der Code aber als Deprecated angezeigt wird – und auf der XPage auch nicht funktioniert: Wie kann ein OnStart- / OnComplete-Event trotzdem verwendet werden?

Meine Idee dazu ist, den darunter liegenden dojo.xhr-Request zu hijacken, und auf diese Weise die Events zu erhalten. Dadurch lässt sich der Code bequem auf die jeweilige XPage einbetten, ohne das eine Manipulation der original Javascript-Dateien erfolgen muss.

Der folgender Code muß in einem CSJS-Scriptblock eingebettet werden. Dann erhält man für die TypeAhead-Funktion die Events, um zum Beispiel ein kleines “Loading”-Icon einzublenden, wenn die Daten vom Domino Server geladen werden.

var typeAheadLoad;

dojo.addOnLoad( function(){
   /*** hijacking xhr request ***/
   if( !dojo._xhr )
      dojo._xhr = dojo.xhr;

   dojo.xhr = function(){
      try{
         var args = arguments[1];
         if( args['content'] ){
            var content = args['content'];
               if( content['$$ajaxmode'] ){
                  if( content['$$ajaxmode'] == "typeahead" ){
                
                     /*** hook in load function ***/
                     typeAheadLoad = args["load"];

                     /*** overwrite error function ***/
                     args["error"] = function(){
                        alert('Error raised!')
                     };
                    
                     /*** hijack load function ***/
                     args["load"] = function(arguments){
                 
                        /*** On Start ***/
                        alert("On Start!");
                    
                        /*** call original function ***/
                        typeAheadLoad(arguments);
                    
                        /*** On Complete ***/
                        alert("On Complete!")
                     };
                 }
             }
         }
      }catch(e){}
      dojo._xhr( arguments[0], arguments[1], arguments[2] );
   }
});

Quick-n-Dirty: Das xp:hidden-Element

6. Dezember 2011 Posted by Sven Hasselbach

Durch die Verwendung des <xp:hidden>-Elements lässt sich ein verstecktes Feld auf der XPage anlegen.

Hier ein Beispiel mit einem statischen Wert:

<xp:inputHidden id="inputHidden1" value="abc" />

Die XPages-Engine rendert daraus diesen HTML-Code:

<input type="hidden" id="view:_id1:inputHidden1"
   name="view:_id1:inputHidden1" value="abc">

Soweit so gut, doch wenn man den Wert dynamisch zuweisen will, rendert die XPages-Engine nicht mehr ein referenzierbares Feld,…

<xp:inputHidden id="inputHidden1">
   <xp:this.value>
      <![CDATA[#{javascript:"abc"}]]>
   </xp:this.value>
</xp:inputHidden>

… sondern einen <span>-Tag, der natürlich auch den Wert nicht enthält:

<span id:"view:_id1:inputHidden1"></span>

Will man trotzdem den Wert des Feldes berechnen, gibt es zwei Möglichkeiten:

1. Die Berechnung wird auf Compute on page load geändert:

<xp:inputHidden id="inputHidden1">
   <xp:this.value>
      <![CDATA[${javascript:"abc"}]]>
   </xp:this.value>
</xp:inputHidden>

2. Dem Feld wird eine Scope-Variable oder einem Dokumentenfeld via EL zugewiesen

<xp:inputHidden id="inputHidden1" value="#{viewScope.hiddenField}">
<xp:inputHidden id="inputHidden1" value="#{document1.hiddenField}">

Dann wird wie in der statischen Variante ein verstecktes Feld generiert, was sich sowohl mit CSJS als auch mit SSJS verarbeiten lässt.