Posts Tagged: ‘Partial Refresh’

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.

Der neue HTTP Header ‘X-XspRefreshId’

5. Dezember 2011 Posted by Sven Hasselbach

Mit Domino 8.5.3 ist der neue HTTP Header ‘X-XspRefreshId’ eingeführt worden, mit dem sich die refreshId eines Partial Refreshs vom Server aus verändern lässt. Dadurch ist es möglich, ein Element zu refreshen, dass Ergebnis dieser Operation jedoch auf ein anderes Element im Client anzuwenden.

Hier ein kleines Beispiel anhand einer XPage, die vor dem Partial Refresh wie folgt aussieht:

Screenshot: Vor dem Partial Refresh

Der Code der XPage ist ebenfalls simpel, ausser das bei einem Partial Refresh der XPage ein Header an den Request angefügt wird. Dazu wird das Event afterRestoreView genutzt.

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

    <xp:this.afterRestoreView><![CDATA[#{javascript:
        var exCon = facesContext.getExternalContext();
        var writer = facesContext.getResponseWriter();
        var response = exCon.getResponse();
        response.setHeader("X-XspRefreshId",  getClientId('label2') ); }]]>
    </xp:this.afterRestoreView>

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

Der Button löst einen Partial Refresh auf das Label label1 aus, der dazugehörige Request, der an den Server gesendet wird liefert auch den zu erwartenden HTML-Code zurück:

<span id="view:_id1:label1" class="xspTextLabel">Label1</span>

Doch nun kommt der zusätzliche Header ins Spiel. Er bewirkt, dass nicht das label1 ersetzt wird, sondern das Element label2:

Durch den in der HTTP Header in der Antwort des Servers wurde das XSP-Objekt dazu veranlasst, den HTML-Code im DOM-Baum an einer anderen Stelle zu ersetzen.

In diesem kleinen Beispiel tritt ein kleiner Seiteneffekt auf: Das Label mit der id label2 verschwindet komplett aus dem DOM-Baum. Betätigt man den Button ein zweites Mal, funktioniert der Refresh nicht mehr, und folgende Fehlermeldung erscheint im Browser:

Abbrechen eines Partial Refresh im Client

4. Dezember 2011 Posted by Sven Hasselbach

Leider bietet das XSP-Objekt keine Möglichkeit, einen Partial Refresh via CSJS vorzeitig zu beenden. Zwar basiert der Partial Refresh-Mechanismus auf dojo.xhr-Requests, die diese Funktionalität bieten, doch das XSP-Objekt stellt keine Möglichkeit zur Verfügung, auf die darunter liegenden Dojo-Objekte zuzugreifen.

Um dennoch Zugriff auf die Requests zu erhalten, müssen die Aufrufe von dojo.xhrGet und dojo.xhrPost daher direkt abgefangen und umgebogen werden. Dadurch kann auf das  zurück gelieferte dojo.Deferred-Objekt zugegriffen werden und es lassen sich dessen Methoden verwenden.

Hier ein kleines Beispielskript, dass diese Aufgabe verrichtet. Es muss in einen CSJS-Scriptblock eingebettet werden:

var xhrCall = null;

dojo.addOnLoad( function(){
   /*** hijack dojo's xhrRequest ***/
   dojo._xhrPost = dojo.xhrPost;
   dojo._xhrGet = dojo.xhrGet;

   dojo.xhrPost = function( args ){
      xhrCall = dojo._xhrPost( args );
   }

   dojo.xhrGet = function( args ){
      xhrCall = dojo._xhrGet( args );
   }
});

Will man nun im Client einen Partial Refresh abbrechen, muss nur die cancel-Methode des dojo.Deferred-Objektes aufgerufen werden, und der Partial Refresh wird beendet*:

xhrCall.cancel();

[Die Methode cancel ist unter Dojo 1.3 dokumentiert; in höheren Versionen ist sie aber aus Kompatibilitätsgründen weiterhin vorhanden.]

Das Abbrechen eines Requestes wird allerdings als Partial Refresh-Fehler betrachtet. Um die Popup-Meldung des XSP-Objekts zu unterbinden, muss dem Partial Refresh noch ein eigener Error-Handler mitgegeben werden. Dieser darf ruhig leer sein, er sorgt nur dafür, daß die Fehlermeldung unterdrückt wird:

XSP.partialRefreshGet('id', { onError: function(){} } );

*: Der eigentliche AJAX-Request wird nicht abgebrochen, sondern die weitere Verarbeitung im CSJS unterbunden.

Art des Refreshs programmatisch ermitteln

13. November 2011 Posted by Sven Hasselbach

Um festzustellen, ob das Berechnen eines Elementes von einem Full Refresh oder einem Partial Refresh ausgelöst wird, kann die Klasse com.ibm.xsp.ajax.AjaxUtil verwendet werden. Die Klasse stellt die Methode isAjaxPartialRefresh() bereit, die das nötige Ergebnis zurückliefert.

Hier eine Beispiel-XPage, die die Verwendung demonstriert:

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

   <xp:label id="lblRefreshMe">
      <xp:this.value><![CDATA[#{javascript:
         var ajax = new com.ibm.xsp.ajax.AjaxUtil();
          if( ajax.isAjaxPartialRefresh(facesContext) == true)
             return "AJAX" ;

          return "NOT AJAX!"}]]>
      </xp:this.value>
   </xp:label>
   <xp:br></xp:br>
   <xp:br></xp:br>
   <xp:button value="Do Full Refresh" id="buttonFullRefresh">
      <xp:eventHandler event="onclick" submit="true"
         refreshMode="complete">
      </xp:eventHandler>
   </xp:button>
   <xp:br></xp:br>
   <xp:button value="Do Partial Refresh" id="buttonPartialRefresh">
      <xp:eventHandler event="onclick" submit="true"
         refreshMode="partial" refreshId="lblRefreshMe">
      </xp:eventHandler>
   </xp:button>
</xp:view>

Hier ein Screenshot der XPage beim Aufruf der XPage.

Durch Klick auf die Buttons läßt sich die Art des Refreshs bestimmen. Wird ein Partial Refresh durchgeführt, ändert sich das Label wie folgt:

Quick-n-Dirty: Manipulation von UI Komponenten via SSJS (2)

3. November 2011 Posted by Sven Hasselbach

Ist einer der Items durch einen Mehrfachwert definiert worden,  muss das entsprechende Item anders behandelt werden. Eine Definition wie folgt…

<xp:comboBox id="comboBox1">
   <xp:selectItems>
      <xp:this.value><![CDATA[#{javascript:
         var vItem = new java.util.Vector();
         vItem.add("A|1");
         vItem.add("B|2");
         vItem.add("C|3");
         vItem
      }]]></xp:this.value>
   </xp:selectItems>
</xp:comboBox>

…ist auch intern als Array zu behandeln. Eine Funktion, die mit beiden Item-Typen umgehen kann (auch im Mix), kann dann wie folgt aufgebaut sein:

function getSelectableValues( id ) {
   var ComboBox = getComponent( id );
   var ChildrenList:java.util.ListIterator;
   ChildrenList = ComboBox.getChildren().listIterator()
   while (ChildrenList.hasNext()) {
      var Child = ChildrenList.next();     
      if( typeof( Child ) == 'com.ibm.xsp.component.UISelectItemsEx' ){
         var hlp = Child.getValue();
         for( var i=0; i< hlp.length; i++ ){
            print( hlp[i].getLabel() + "|" + hlp[i].getValue() );
            hlp[i].setLabel("ABC" + i );
            hlp[i].setValue("123" + i );
            hlp[i].setDisabled(true);
         }
         Child.setValue(hlp);
      }
      if( typeof( Child ) == 'com.ibm.xsp.component.UISelectItemEx' ){
         print( Child.getItemLabel() + "|" + Child.getItemValue() );
      }
   }
}

getSelectableValues( 'comboBox1' );

[Fett: Wenn Änderungen am Mehrfachwert-Item vorgenommen werden, muss das Item nach der Bearbeitung mit "setValue()" erneuert werden]

Die Namen der Methoden sind ohne “Item” zu verwenden, z.B. setItem() anstelle von setItemValue().

Quick-n-Dirty: Manipulation von UI Komponenten via SSJS

3. November 2011 Posted by Sven Hasselbach

Serverseitig lassen sich die die Items einer Auswahlbox (z.B. eine Combobox, Listboxen, usw.) auslesen und ggf. manipulieren. Hier eine Combobox mit drei Items:

<xp:comboBox id="comboBox1">
   <xp:selectItem itemLabel="A" itemValue="1"></xp:selectItem>
   <xp:selectItem itemLabel="B" itemValue="2"></xp:selectItem>
   <xp:selectItem itemLabel="C" itemValue="3"></xp:selectItem>
</xp:comboBox>

Um via SSJS die Items der UI Komponente und deren Eigenschaften zu erhalten, kann man wie folgt darauf zu greifen:

function listSelectableValues( id ) {
   var cBox = getComponent( id );
   var cList:java.util.ListIterator;
   cList = ComboBox.getChildren().listIterator();
   while (ChildrenList.hasNext()) {
      var c = ChildrenList.next()
      print ("Label: " + c.getItemLabel() );
      print ("Value: " + c.getItemValue() );
      print ("IsDisabled: " + c.isDisabled() );
   }
}

listSelectableValues( 'comboBox1' );

Auf der Serverkonsole werden nun die Eigenschaften der drei Items ausgegeben.

Natürlich lassen sich die Items auch manipulieren:

<xp:button value="Label" id="button1">
   <xp:eventHandler event="onclick" submit="true"
      refreshMode="partial" refreshId="comboBox1">
      <xp:this.action><![CDATA[#{javascript:
         var cBox = getComponent( 'comboBox1' );
         var cBoxChildren = cBox.getChildren();
         var cBoxChild = cBoxChildren.get(0);

         // --- disablen
         cBoxChild.setItemDisabled(true);

         // --- das Label ändern
         cBoxChild.setItemLabel("DISABLED");

         // --- den Value ändern
         cBoxChild.setItemValue("disabled");
      }]]></xp:this.action>
      </xp:eventHandler>
</xp:button>

Der Button führt einen PartialRefresh auf die ComboBox aus, deaktiviert das erste Item (es ist nicht mehr selektierbar) und ändert sowohl das Label als auch den Wert des ersten Items.