Posts Tagged: ‘Partial Refresh’

XPages: Optimized Partial Refreshs (2)

7. März 2014 Posted by Sven Hasselbach

With the Optimized Partial Refresh you can do a lot of nice things: If only a part of the form is sent to the server, only this part of the components in the JSF component tree will be processed. This means that only submitted values are applied, coverted and validated, which can result in less server usage and a better performance.

Normally, if you have two required fields on you XPage and do a Partial Refresh, both fields will have a validation error:

But with this technique, you can choose which component should be refreshed. When clicking of Button Refresh 1, only the first field is marked as a required field:

When filling in the first field, but clicking on Refresh 2

… the value of the first field gets lost, because it was not sent to the server (if a value was already stored there, it won’t get lost). And the second field is marked as required:

This is the example XPage, the CSJS code is moved to the Custom Control ccOptimizedPR.

<?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:ccOptimizedPR />

    <xp:div id="refreshMe">
        <xp:messages id="messagesError" />
        <xp:inputText
            id="inputText1"
            value="#{sessionScope.inputText1}">
            <xp:this.validators>
                <xp:validateRequired message="Field 1 is empty!" />
            </xp:this.validators>
        </xp:inputText>
        <xp:inputText
            id="inputText2"
            value="#{sessionScope.inputText2}">
            <xp:this.validators>
                <xp:validateRequired message="Field 2 is empty!" />
            </xp:this.validators>
        </xp:inputText>
    </xp:div>

    <xp:button
        value="Normal Refresh"
        id="buttonNormal">
        <xp:eventHandler
            event="onclick"
            submit="true"
            refreshMode="partial"
            refreshId="refreshMe">
        </xp:eventHandler>
    </xp:button>

    <xp:button
        value="Refresh 1"
        id="buttonCleaned1">
        <xp:eventHandler
            event="onclick"
            submit="false">
            <xp:this.script>
                <![CDATA[
                    XSP.partialRefreshPost(
                        '#{id:refreshMe}',{
                            clearForm: true,
                            additionalFields: ['#{id:inputText1}' ]
                        }
                    );]]>
            </xp:this.script>
        </xp:eventHandler>
    </xp:button>
    <xp:button
        value="Refresh 2"
        id="buttonCleaned2">
        <xp:eventHandler
            event="onclick"
            submit="false">
            <xp:this.script>
                <![CDATA[
                    XSP.partialRefreshPost(
                        '#{id:refreshMe}',{
                            clearForm: true,
                            additionalFields: ['#{id:inputText2}' ]
                        }
                    );]]>
            </xp:this.script>
        </xp:eventHandler>
    </xp:button>

</xp:view>

XPages: Optimized Partial Refreshs

7. März 2014 Posted by Sven Hasselbach

Inspired by the last post of Mark, I have created a small CSJS snippet which allows to optimize the behaviour of a Partial Refresh. Normally, if you execute a Partial Refresh, all elements of a form are sent to the server. Take a look at this XPage:

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

    <xp:inputText
        id="inputText01"
        value="#{sessionScope.inputText01}" />
    <xp:inputText
        id="inputText02"
        value="#{sessionScope.inputText02}" />
    <xp:inputText
        id="inputText03"
        value="#{sessionScope.inputText03}" />
    <xp:inputText
        id="inputText04"
        value="#{sessionScope.inputText04}" />
    <xp:inputText
        id="inputText05"
        value="#{sessionScope.inputText05}" />
    <xp:inputText
        id="inputText06"
        value="#{sessionScope.inputText06}" />
    <xp:inputText
        id="inputText07"
        value="#{sessionScope.inputText07}" />
    <xp:inputText
        id="inputText08"
        value="#{sessionScope.inputText08}" />
    <xp:inputText
        id="inputText09"
        value="#{sessionScope.inputText09}" />
    <xp:inputText
        id="inputText10"
        value="#{sessionScope.inputText10}" >
    </xp:inputText>

    <xp:div id="refreshMe">
        <xp:label
            value="#{javascript:java.lang.System.currentTimeMillis()}"
            id="labelNow" />
    </xp:div>

    <xp:button
        value="Normal Refresh"
        id="buttonNormal">
        <xp:eventHandler
            event="onclick"
            submit="true"
            refreshMode="partial" refreshId="refreshMe">
        </xp:eventHandler>
    </xp:button>

</xp:view>

The button refreshes only a small portion of the XPage, the DIV element refreshMe. It does not require any of the posted field values, it just refreshes the DOM element in the frontend. But when clicking the button, the posted data to the server contain all the fields and their values, which is not necessary in this case.

This is how the request looks like in Firebug (I have prefilled all fields with the values 1..9):

The response of the server is – as expected – the actual HTML code for the refreshed DOM element:

The optimized  version adds the option clearForm to partial refreshs. When using this option, only the XPages internal fields are sent to the server, but DOM will be refreshed correctly:

<xp:button
        value="Cleaned Refresh"
        id="buttonCleaned">
        <xp:eventHandler
            event="onclick"
            submit="false">
            <xp:this.script>
                <![CDATA[
                    XSP.partialRefreshPost(
                        '#{id:refreshMe}',{
                            clearForm: true,
                        }
                    );]]>
            </xp:this.script>
        </xp:eventHandler>
    </xp:button>

Now, the POST looks like this:

But wait, this is useless, you can do a XSP.partialRefreshGet instead!

That’s why there is the second option additionalFields. This allows to define all fields you want to update during the refresh:

<xp:button
        value="Cleaned Refresh"
        id="buttonCleaned">
        <xp:eventHandler
            event="onclick"
            submit="false">
            <xp:this.script>
                <![CDATA[
                    XSP.partialRefreshPost(
                        '#{id:refreshMe}',{
                            clearForm: true,
                            additionalFields: ['#{id:inputText01}',
                                              '#{id:inputText02}' ],
                        }
                    );]]>
            </xp:this.script>
        </xp:eventHandler>
    </xp:button>

When clicking the button now, the specified fields are added to the POST request (and will be updated in the JSF tree:

Here is the snippet (Tested on IE 11, Chrome 33 & FF 27 with ND9 & ND 8.5.3 )

<xp:scriptBlock id="scriptBlockPROptimized">
        <xp:this.value><![CDATA[
XSP.addOnLoad(function(){

    // hijack the existing partial refresh method
    if( !XSP.__partialRefresh ){
        XSP.__partialRefresh = XSP._partialRefresh;
    }

    // add the new one to the XSP object
    XSP._partialRefresh = function x_prfh(method, form, refreshId, options){

        // clear the form?
        if( options.clearForm ){

            // create a new HTML form...
            var newForm = document.createElement( "form" );
            newForm.setAttribute( "method", form.method );
            newForm.setAttribute( "action", form.action );

            // ... and loop all existing fields
            for( var i = 0; i<form.length; i++ ){
                var field = form[i];
                var fieldName = field.name;
                var includeField = false;

                try{

                    // check for addition fields
                    if( options.additionalFields ){
                        includeField = dojo.indexOf(options.additionalFields, fieldName)!=(-1)?true:false;
                    }

                    // only add XPages relevant fields and addtional fields
                    if( fieldName == form.id || fieldName.substr(0,2) == '$$' || includeField ){

                        var newField = null;
                        if( field.options ){
                            // special handling for fields with options
                            for( var j=0; j<field.length; j++ ){
                                if( field.options[j].selected ){
                                    newField = document.createElement( "input" );
                                    newField.setAttribute( "type", "hidden" );
                                    newField.setAttribute( "name", fieldName );
                                    newField.setAttribute( "value", field.options[j].value );
                                    newForm.appendChild( newField );
                                }
                            }
                        }else{
                            // default field handling: just clone the DOM element
                            newField = field.cloneNode( true );
                            newForm.appendChild( newField );
                        }
                    }
                }catch(e){
                    console.log(e);
                }
            }

            // call the original refresh method with the new form
            return XSP.__partialRefresh(method, newForm, refreshId, options);
        }

        XSP.__partialRefresh(method, form, refreshId, options);
    };
});]]></xp:this.value>
    </xp:scriptBlock>

Just add the script block above to your XPage, or move it into a CSJS library. But keep in mind that you have to think twice when removing data from the request. It can lead in an inconsistence between the data in the client and the data stored in the component tree on the server.

Quick-n-Dirty: Ajax Fileuploads

9. September 2013 Posted by Sven Hasselbach

Here is an example how to upload a file with Ajax to a XPage. It is a simple Javascript, and adds the required fields to the xhr request. This example works in FireFox and Chrome and should work on Domino >=8.5.2.

<?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="RTItem"
            action="editDocument"
            concurrencyMode="force">
        </xp:dominoDocument>
    </xp:this.data>

    <xp:fileUpload onchange="handleFiles(this.files)"
        id="fileUpload1"
        value="#{document1.Body}">
    </xp:fileUpload>

    <xp:eventHandler event="onsaveDoc" submit="false" refreshMode="norefresh" immediate="false" save="true" id="saveDoc" />

    <xp:button
        value="ajaxSave"
        id="buttonAjax">
        <xp:eventHandler
            event="onclick"
            submit="false">
            <xp:this.script><![CDATA[sendFiles();]]></xp:this.script>
        </xp:eventHandler>
    </xp:button>

    <xp:div id="fileData" />

      <xp:scriptBlock id="scriptBlockAjax">
          <xp:this.value>
          <![CDATA[

          function handleFiles(files) {
            var i = 0;
            var dataDiv = dojo.byId('#{id:fileData}');
             var fileList = files;

            for(i = 0; i < fileList.length; i++)
            {
                    var img = document.createElement("img");
                    img.file = fileList[i];
                    img.name = 'no_'+ i;
                    img.classList.add("fileData");

                    var reader = new FileReader();
                    reader.onload = (function(aImg) { return function(e) { aImg.src = e.target.result; }; })(img);
                    reader.readAsDataURL(fileList[i]);

                    dataDiv.appendChild(img);    
            }
        }

        function FileUpload(img, file) {
             var xhr = new XMLHttpRequest();
            this.xhr = xhr;

            var frm = new FormData;
            frm.append( "#{id:fileUpload1}", file);
            frm.append( "$$viewid", dojo.byId('view:_id1__VUID').value);
            frm.append( "$$xspsubmitid", "#{id:saveDoc}");
            frm.append( "view:_id1", "view:_id1");

            xhr.open("POST", "#{javascript:facesContext.getExternalContext().getRequest().getRequestURI()}", true);
            xhr.send(frm);
        }

        function sendFiles(){
          var files = document.querySelectorAll(".fileData");

          for(var i = 0; i < files.length; i++)
          {
              new FileUpload(files[i], files[i].file);
          }

        }
        ]]>
        </xp:this.value>
    </xp:scriptBlock>

</xp:view>

The fileupload control executes the javascript method handleFiles when a new file is selected. This method “converts” the file to a DOM image and attaches it to the DIV fileData.

When clicking the button ajaxSend, the sendFiles method is fired and sends all selected files with xhr Requests to the server. The value of the $$submitid identifies a server side event which saves the document with the received attachment as new document.

Keep in mind: There is NO full refresh.

If you selecting some file in the browser…

.. and click on “ajaxSave“, the files are posted to the server:

The server side event saves the uploaded files to new documents:

XPages: File downloads and blocked UI

24. Juli 2013 Posted by Sven Hasselbach

Yesterday Christian asked a very interesting question: He had observed that the browser UI is blocked when clicking a button which generates a PDF on the server and sends the result. For about 30 seconds no button is working, no events are fired. My first thought was that this behaviour is caused by the submit locking during partial refreshs, and after testing a XSP.allowSubmit() in the debug console I could prove myself that I was right.

It looked first like an easy solution, but after thinking a little bit about it, I had no idea how to execute this little piece of CSJS code. Let’s have a look at the PDF generation:

public void exportPdfOutputStream(TemplateData pdfData, String templateFile, String filename) {

   ExternalContext con = facescontext.getExternalContext();
   XspHttpServletResponse response = (XspHttpServletResponse) con.getResponse();
   try {
     ServletOutputStream writer = response.getOutputStream();
     // setting response headers for browser
     response.setContentType(“application/pdf”);
     response.setHeader(“Cache-Control”, “no-cache”);
     response.setDateHeader(“Expires”, -1);
     response.setHeader(“Content-Disposition”, “attachment; filename=\”" + filename + “\”");

     PdfReader pdfTemplate = getPdfReader(templateFile);
     PdfStamper stamper = new PdfStamper(pdfTemplate, writer);
     stamper.setFormFlattening(true);
     setDataToPdfDocument(pdfData, stamper);
     stamper.close();
     pdfTemplate.close();
     writer.flush();
     writer.close();
     facescontext.responseComplete();
    } catch (Exception e) {
      String errorMessage = “PDF Exporter: An error occureed: ” + e.toString();
      try {
         response.sendError(500, errorMessage);
      } catch (Exception e2) {
        e2.printStackTrace();
      }
    }
 }

A full refresh is submitted to the server, the code generates a PDF, adds the result to the output stream as attachment, and then closes the response. The browser receives a file to download and that’s it. Works fine. But how to execute some CSJS code? You cannot use view.postScript nor you cannot send an additional HTML element (a JS script block) and there is no onComplete event.

While I was musing I had the idea to use a repeating interval and check the server response in Javascript. But how can you access this information? When performing a XHR Call (a partial refresh) you can access the HTTP header of the response, but this is not possible when performing a full refresh.

I googled for an answer an found this article about a solution to block the UI: http://geekswithblogs.net/GruffCode/archive/2010/10/28/detecting-the-file-download-dialog-in-the-browser.aspx

It uses a cookie to send the information back to the browser, which is a really amazing idea. Here is my implementation for XPages and Dojo 1.8:

The button to download the PDF first executes a CSJS script which fills in the field fileDownloadToken with the current timestamp. This timestamp is sent to the server and then added to the cookie in the response.

The client checks every 500ms if the cookie exists and if the value equals the original token sent to the server. If the cookie is set correctly, the finishDownload() method is executed which calls XSP.allowSubmit() and removes the cookie.

This is the code of the XPage:

<?xml version="1.0" encoding="UTF-8"?>
<xp:view
        xmlns:xp="http://www.ibm.com/xsp/core">
        <xp:button
                id="buttonDownload"
                value="Download">
                <xp:eventHandler
                        event="onclick"
                        submit="true">

                        <xp:this.action>
                                <![CDATA[#{javascript:
                                  importPackage( ch.hasselba.tools );        
                                  new ch.hasselba.tools.PDFUtil().exportPdfOutputStream("output.pdf");}]]>
                        </xp:this.action>
                        <xp:this.script><![CDATA[
                                require(["dojo/cookie"], function(cookie){
                                          var fileDownloadCheckTimer;
                                          var tokenName = 'fileDownloadToken';

                                        function finishDownload() {
                                                 window.clearInterval(fileDownloadCheckTimer);
                                                 cookie('fileDownloadToken', null, {expire: -1});
                                                 XSP.allowSubmit();                        
                                        }

                                        function startDownload(){
                                                setToken();
                                                fileDownloadCheckTimer = window.setInterval(function () {
                                                      var cookieValue = cookie( tokenName );

                                                      if ( cookieValue == getToken() )
                                                               finishDownload();
                                            }, 500);

                                        }

                                        function getToken(){
                                                return XSP.getElementById( tokenField ).value;
                                        }
                                        function setToken(){
                                                 XSP.getElementById( tokenField ).value = Date.now();
                                        }

                                        startDownload();
                                  })
                        ]]></xp:this.script>
                </xp:eventHandler>
        </xp:button>
        <input type="hidden" id="fileDownloadToken" name="fileDownloadToken" />

</xp:view>

And this is the modified Java code:

package ch.hasselba.tools;

import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.Cookie;

import com.ibm.xsp.webapp.XspHttpServletResponse;

public class PDFUtil {
        public void exportPdfOutputStream(String filename) {

                FacesContext fc = FacesContext.getCurrentInstance();
                ExternalContext ex = fc.getExternalContext();

                XspHttpServletResponse response = (XspHttpServletResponse) ex
                                .getResponse();

                // get the token from the request
                String token = (String) ex.getRequestParameterMap().get(
                                "fileDownloadToken");

                try {
                        ServletOutputStream writer = response.getOutputStream();

                        // setting response headers for browser
                        response.setContentType("application/pdf");
                        response.setHeader("Cache-Control", "no-cache");
                        response.setDateHeader("Expires", -1);
                        response.setHeader("Content-Disposition", "attachment; filename=\""
                                        + filename + "\"");

                        // set the cookie to the response
                        response.addCookie(new Cookie("fileDownloadToken", token));

                        //
                        // generate the output
                        //

                        // close the writer and mark response as completed
                        writer.flush();
                        writer.close();
                        fc.responseComplete();

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

XPages: Dojo 1.8.1 & jQuery Mobile 1.3.1

25. April 2013 Posted by Sven Hasselbach

As David Leedy got into trouble with Dojo 1.8.1 and jQuery Mobile 1.3.1  and after reading the follow up from Ulrich Krause with the analysis of the problem, I thought that this problem is caused from the AMD loader of Dojo. That’s why I changed the loading order (by forcing the jQuery libraries to get loaded before Dojo), and this seems to work.

I have tested it in IE 8, FF 20.0 and Chrome 26.0.

Here is my test XPage:

<?xml version="1.0" encoding="UTF-8"?>
<xp:view
    xmlns:xp="http://www.ibm.com/xsp/core">
    <xp:this.properties>
        <xp:parameter
            name="xsp.resources.aggregate"
            value="true" />
    </xp:this.properties>
    <xp:this.resources>
        <xp:styleSheet
            href="http://code.jquery.com/mobile/1.3.1/jquery.mobile-1.3.1.min.css" />
        <xp:headTag
            tagName="script">
            <xp:this.attributes>
                <xp:parameter
                    name="type"
                    value="text/javascript" />
                <xp:parameter
                    name="src"
                    value="http://code.jquery.com/jquery-1.9.1.min.js" />
            </xp:this.attributes>
        </xp:headTag>
        <xp:headTag
            tagName="script">
            <xp:this.attributes>
                <xp:parameter
                    name="type"
                    value="text/javascript" />
                <xp:parameter
                    name="src"
                    value="http://code.jquery.com/mobile/1.3.1/jquery.mobile-1.3.1.min.js" />
            </xp:this.attributes>
        </xp:headTag>
    </xp:this.resources>

    <div
        data-role="page"
        class="jqm-demos"
        data-quicklinks="true">

        <div
            data-role="content"
            class="jqm-content"
            id="contentRefresh">
            <h2
                id="accordion-markup">
                <script>document.write(dojo.version)</script>
            </h2>

            <div>
                <div
                    data-role="collapsible-set"
                    data-theme="c"
                    data-content-theme="d">

                    <div
                        data-role="collapsible">
                        <h3>Section</h3>
                        <p>
                            <xp:label
                                value="#{javascript:java.lang.System.currentTimeMillis()}"
                                id="labelRefresh" />
                        </p>
                    </div>
                </div>
            </div>
            <a
                href="#"
                onclick="XSP.partialRefreshGet('#{id:labelRefresh}')"
                data-role="button">Refresh Label</a>
        </div>
    </div>

</xp:view>

And this is the result:

The XSP object is loaded correctly and Partial Refreshs are executed correctly:

It is required that the resource aggregation is enabled. More details can be found here.

Quick-n-Dirty: How to add HTML 5 events

26. März 2013 Posted by Sven Hasselbach

One way for adding unsupported events to an XPage or a component is the trick from Keith. But this is limited to CSJS only. If you need to execute a server side event, you just need change the name of the event to a new HTML 5 event name which does not exist in the DDE.

Here is an example for the new onSearch event:

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

    <xp:inputText id="inputTextSearch" type="search">
        <xp:eventHandler event="onsearch" submit="true"
            refreshMode="partial" refreshId="labelSearchFor">
            <xp:this.action>
               <![CDATA[#{javascript:print("onSearch");}]]>
            </xp:this.action>
        </xp:eventHandler>
    </xp:inputText>

    <xp:br />
    <xp:br />

    <xp:label id="labelSearchFor">
        <xp:this.value>
           <![CDATA[#{javascript:getComponent("inputTextSearch").value}]]>
        </xp:this.value>
    </xp:label>

</xp:view>

After entering a value and hitting enter…

… you can see on the server console that the event occurred:

This technique allows to add new events to the whole XPage, for example the onContextMenu event:

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

        <xp:eventHandler event="oncontextmenu" submit="true"
            refreshMode="norefresh">
            <xp:this.action>
                <![CDATA[#{javascript:print("onContextMenu");}]]>
            </xp:this.action>
        </xp:eventHandler>

</xp:view>

Every time if the user opens the context menu in the browser, the event is triggered and executed on the server:

The name of the event must be lowercased. A list of HTML 5 events can be found here: http://www.tutorialspoint.com/html5/html5_events.htm. Not all browsers support every HTML 5 event in the list.

P.S. Keep in mind that this article has been posted in the “Quick-n-Dirty” category.

A performance bottleneck?

17. Februar 2013 Posted by Sven Hasselbach

Paul Withers wrote an very interesting article about the difference between a passthrough UIComponents and the corresponding XPages elements. This means the use of <br> instead a <xp:br>, or a <div> instead of a <xp:div>. A while ago I have tested if this would affect the performance, and as far as I know it makes no difference.

Here is the code I used to test the performance:

<?xml version="1.0" encoding="UTF-8"?>
 <xp:view xmlns:xp="http://www.ibm.com/xsp/core">
    <h1>Name of the Element</h1>
    <xp:repeat id="repeatFull" rows="9999999">
       <xp:this.value><![CDATA[#{javascript:
          var arr:java.util.Vector = new java.util.Vector();
          for( var i=0; i<10;i++){
             arr.add( i );
          }
          var start = java.lang.System.currentTimeMillis();

          arr}]]>
       </xp:this.value>

       <xp:repeat id="repeat1" rows="9999999">
          <xp:this.value><![CDATA[#{javascript:
             var arr:java.util.Vector = new java.util.Vector();
             for( var i=0; i<100000;i++){
                arr.add( i );
             }
             var start = java.lang.System.currentTimeMillis();
             arr}]]>
          </xp:this.value>

          <!-- HERE COMES THE REPEATING ELEMENT //-->

       </xp:repeat>
       <xp:label id="labelTimer">
          <xp:this.value>
             <![CDATA[#{javascript:
                var end = java.lang.System.currentTimeMillis();
                "Total " + (end - start) + " ms."
             }]]>
          </xp:this.value>
       </xp:label>
    </xp:repeat>
    <xp:br />
    <xp:button value="Refresh" id="buttonRefresh">
       <xp:eventHandler event="onclick" submit="true"
          refreshMode="partial" refreshId="repeatFull">
       </xp:eventHandler>
   </xp:button>

</xp:view>
  • Test with a <xp:div>

  • Open the page

  • Partial Refresh

  • Test with a <div>

  • Opening the page

  • Partial Refresh

[Similar results with the other tags]

As you can see there is nothing to worry about. Yes, a UIPassthroughComponent is faster then the corresponding XPages UIComponent. But keep in mind that these test are for 100.000 elements at once. If you have an XPage with 100.000 components on it, you will have other problems (f.e. memory usage etc.).

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.

XSnippets: viewPanelHelper

28. November 2012 Posted by Sven Hasselbach

I have added a new XSnippet, the viewPanelHelper. The code helps to keep the selection of the selected documents in a view panel even if pager is used or categories are expand or collapsed.

It is not required to modify the view: Only the script and the hidden field must be added to the facets section of the panel and the CSJS class “viewPanelHelper” must be added to the XPage (the class is loaded only once and can be stored in a separate CSJS resource file).

The selected documents are stored in the hidden field “viewPanelSelectedIds”.
Tested in IE 9 and FF 15 – 17.

Quick-n-Dirty: HTML5 UIComponents without effort

17. Mai 2012 Posted by Sven Hasselbach

Obviously it was Chris Toohey who first has discovered the way of manipulation UIComponents with the tagName attribute: http://www.dominoguru.com/pages/xpage_xptext_tagName_options.html

 

The xp:text – element can easily manipulated to add HTML5 functionality to a XPages. By overriding the property tagName, the component can be accessed like every other UI component in the component tree.

Normally the designer only allows a small list of choices for the tagName property:

But this can be easily overwritten by manually editing the tagName attribute in the source and allows to change the generated HTML element to whatever you want.

Here is a small example for a HTML5 progessbar which can be accessed like every other UIComponent:

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

   <xp:text escape="false" id="pBar" disableTheme="true">
      <xp:this.value><![CDATA[#{javascript:""}]]></xp:this.value>
      <xp:this.tagName>
         <![CDATA[#{javascript:"progress"}]]>
      </xp:this.tagName>
      <xp:this.attrs>
         <xp:attr name="value">
            <xp:this.value><![CDATA[#{javascript:
               if( !sessionScope.containsKey("pBar") )
                  sessionScope.put("pBar", 0 );

                var value = sessionScope.get("pBar");

                if( value > 100 ) value = 0;

                sessionScope.put("pBar", (value+10) );

                value
               }]]></xp:this.value>
         </xp:attr>
         <xp:attr value="100" name="max"></xp:attr>
      </xp:this.attrs>
   </xp:text>

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

</xp:view>

The generated HTML Tag looks like this and can for example be refreshed with a partial refresh etc.

<progress id="view:_id1:pBar" value="40" max="100"></progress>

XSnippets: Cancel a partial refresh via SSJS

3. Mai 2012 Posted by Sven Hasselbach

With the assistance of Philippe Riand I was able to shorten the original idea of canceling a partial refresh to a single SSJS function.  By setting the HTTP header “X-XspRefreshId” to “@none” it is possible to get the same result as in the posting before, but there is no “Dojo hack” required.

function cancelPartialRefresh(){
   var response = facesContext.getExternalContext()
      .getResponse();
   response.setHeader("X-XspRefreshId", "@none");
   response.reset();
   response.commitResponse();
   facesContext.responseComplete();
}

To use this function you just have to call it.  Here is the same example like in the previous posting:

<?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="label1">
      </xp:eventHandler>
   </xp:button>
   <xp:br/><xp:br/>
   <xp:label id="label1">
      <xp:this.value>
      <![CDATA[#{javascript:
      function cancelPartialRefresh(){
         var response = facesContext.getExternalContext()
            .getResponse();
         response.setHeader("X-XspRefreshId", "@none");
         response.reset();
         response.commitResponse();
         facesContext.responseComplete();
      }

      var ajax = new com.ibm.xsp.ajax.AjaxUtil();
      if( ajax.isAjaxPartialRefresh(facesContext) == true){
         cancelPartialRefresh();
         return; // stop execution of SSJS code
      }
      java.lang.System.currentTimeMillis();}]]>
      </xp:this.value>
   </xp:label>
</xp:view>

I have added the code to the XSnippet Contest. Here is the link to the XSnippet: http://openntf.org/XSnippets.nsf/snippet.xsp?id=cancel-partial-refresh

If you want to read more information about the HTTP header  you can read an earlier posting (Sorry, on german only).

Cancel a partial refresh via SSJS

1. Mai 2012 Posted by Sven Hasselbach

After reading Tim Tripcony’s blog post , I thought about a way how to cancel a partial refresh via server side javascript. To bring this to life, there are just three things to do:

  1. Abort the processing of the request on the server
  2. Give feedback to the client that request is canceled
  3. Stop Dojo to process the XHR request

To stop the processing of a request on the server and to send an empty response to the client, this SSJS code can be used:

var response = facesContext.getExternalContext().getResponse();
response.reset();
response.commitResponse();

The client will receive an empty HTTP response body:

A HTTP Header has to be added to the response to inform Dojo that the request was canceled. In this example it is “X-Partial-Refresh-Cancel“:

response.setHeader("X-Partial-Refresh-Cancel", "1");

The header is now added to the HTTP response and sent back to the client:

The XHR response must be hijacked before it is processed by Dojo. To do this, a new XHR function has to be injected between the existing one. Here is a basic code snippet:

var xhrLoad = null; // placeholder for the original function

if( !dojo._xhr )
   dojo._xhr = dojo.xhr;

dojo.xhr = function(){
   var args = arguments[1];
   xhrLoad = args["load"]; // "save" the original load function
                           // and overwrite with a new one
   args["load"] = function( a, ioArgs ){
      // execute custom code
      // call the original load function:
      xhrLoad( a, ioArgs );
   }

   // do XHR request
   dojo._xhr( arguments[0], arguments[1], arguments[2] );
}

The load function of a Dojo XHR request has a parameter ioArgs. This parameter gives a handle to an object that allows to access the HTTP response headers, so the response can be identified as canceled:

var canceled = ioArgs.xhr &&
   ioArgs.xhr.getResponseHeader("X-Partial-Refresh-Cancel");

if( canceled ){
   XSP.allowSubmit();
   return;
}

If the HTTP header is set, the processing of the request can be stopped. After stopping the request, the XSP object is not allowed to fire the next event. To allow this again, the function XSP.allowSubmit() must be called.

Here is a demonstration XPage with a button and a label. The partial refresh will never be processed, instead a “Canceled” message will occur.

<?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="label1">
      </xp:eventHandler>
   </xp:button>
   <xp:br/><xp:br/>
   <xp:label id="label1">
      <xp:this.value>
      <![CDATA[#{javascript:
      var response = facesContext.getExternalContext().getResponse();
      var ajax = new com.ibm.xsp.ajax.AjaxUtil();
      if( ajax.isAjaxPartialRefresh(facesContext) == true){
         response.setHeader("X-Partial-Refresh-Cancel", "1");
         response.reset();
         response.commitResponse();
         return;
      }
      java.lang.System.currentTimeMillis();}]]>
      </xp:this.value>
   </xp:label>
   <xp:br/>
   <xp:br/>
   <xp:scriptBlock id="scriptBlockXHRHandler">
   <xp:this.value><![CDATA[
      var xhrLoad = null;
      dojo.addOnLoad( function(){
         if( !dojo._xhr )
            dojo._xhr = dojo.xhr;

        dojo.xhr = function(){
           try{
              var args = arguments[1];
              xhrLoad = args["load"];
              args["load"] = function( a, ioArgs ){
                 var canceled = ioArgs.xhr &&
                    ioArgs.xhr.getResponseHeader("X-Partial-Refresh-Cancel");
                 if( canceled ){
                    alert("Canceled!");
                    XSP.allowSubmit();
                    return;
                 }
                 xhrLoad( a, ioArgs );
               }
            }catch(e){}
            dojo._xhr( arguments[0], arguments[1], arguments[2] );
         }
      });]]>
      </xp:this.value>
   </xp:scriptBlock>
</xp:view>

Bug: facesContext.getRenderResponse()

11. März 2012 Posted by Sven Hasselbach

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

Zur Erläuterung verwende ich folgende XPage:

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

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

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

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

Bei einem Partial Refresh hingegen wird die Phase korrekt erkannt:

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

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