Posts Tagged: ‘XSP’

XPages: WebContent Files (3) – Create a Minimizer Servlet

26. November 2014 Posted by Sven Hasselbach

XPages: Execute Events with HTTP Get

30. September 2014 Posted by Sven Hasselbach

To execute an event on the server, you normally have to send a POST request, because actions will be executed in the Invoke Application phase of the JSF lifecycle. A GET request will only process the Restore View and the Render Response phase, that why you can not execute an event with a GET request.

But with the help of a PhaseListener, the execution can be done earlier in the Restore View phase:

package ch.hasselba.xpages.util;

import javax.faces.event.PhaseEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.PhaseListener;
import com.ibm.xsp.component.xp.XspEventHandler;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import com.ibm.xsp.util.FacesUtil;

public class ExecuteOnServerPhaseListener implements PhaseListener {

    private static final long serialVersionUID = 1L;

    public void beforePhase(PhaseEvent event) {}

    public PhaseId getPhaseId() {
        return PhaseId.RESTORE_VIEW;
    }

    public void afterPhase(PhaseEvent event) {
        
        FacesContextExImpl = FacesContextExImpl.getCurrentInstance();
        ExternalContext ec = fc.getExternalContext();
        String url = ec.getRequestPathInfo();
        String[] pathes = url.split( "/" );
        
        try{
            if( pathes.length > 2 ){
                if( "executeOnServer".equals( pathes[pathes.length -2 ] ) ){
                    String[] fullId = pathes[ pathes.length - 1 ].split(":");
                    String actionId = fullId[ fullId.length - 1 ];
                    XspEventHandler eventHandler = (XspEventHandler)
                        FacesUtil.findComponentWithFullId( fc.getViewRoot(), actionId );
                    if( eventHandler != null ){
                        eventHandler.getAction().invoke( fc, null );
                        fc.responseComplete();
                    }
                }
            }
        }catch(Exception e){
            e.printStackTrace();
        }
        
    }

}

To activate the PhaseListener, it has to be enabled in the faces-config.xml:

<?xml version="1.0" encoding="UTF-8"?>
<faces-config>
   <lifecycle>
      <phase-listener>ch.hasselba.xpages.util.ExecuteOnServerPhaseListener</phase-listener>
   </lifecycle>
</faces-config>

The following Javascript snippet extends the XSP object and adds the new function executeOnServerGet to it. The parameter is the if of the event to invoke.

XSP.executeOnServerGet = function( eventId ){
    var viewId = dojo.query('[name="$$viewid"]')[0].value;
    var url = document.forms[0].action;
    url += "/executeOnServer/" + eventId;
    url += "?$$viewid=" + viewId;
    url += "&$$ajaxid=@none";
    dojo.xhrGet({url: url});
}

When calling the function, it sends a GET request and adds the current view id to the request. With the parameter $$ajaxId set to @none, the XPages Engine is forced to send no HTML code back to the client.

And here is an example XPage:

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

    <xp:eventHandler id="helloworld" event="helloworld" submit="false">
        <xp:this.action>
            <![CDATA[#{javascript:
               print("hello world " + java.lang.System.currentTimeMillis() );
            }]]>
        </xp:this.action>
    </xp:eventHandler>


    <xp:scriptBlock id="scriptBlock1">
        <xp:this.value><![CDATA[
            dojo.addOnLoad( function(){
                XSP.executeOnServerGet = function( eventId ){
                      var viewId = dojo.query('[name="$$viewid"]')[0].value;
                    var url = document.forms[0].action;
                    url += "/executeOnServer/" + eventId;
                    url += "?$$viewid=" + viewId;
                    url += "&$$ajaxid=@none";
                    dojo.xhrGet({url: url});
                  }
            });
        ]]></xp:this.value>
    </xp:scriptBlock>
    
    <xp:button value="Execute" id="button1">
        <xp:eventHandler event="onclick" submit="false">
            <xp:this.script>
                <![CDATA[XSP.executeOnServerGet( "#{id:helloworld}" );]]>
            </xp:this.script>
        </xp:eventHandler>
    </xp:button>
</xp:view>

When clicking the button, the following URL is opened in the background:

http://example.com/db.nsf/EventGet.xsp/executeOnServer/view:_id1:helloworld?$$viewid=!dwjldz64w0!&$$ajaxid=@none

A GET request was sent to the server:If you look on the server console, you will see that the Action was invoked:

XPages: A Bootstrap Skin for CKEditor

17. März 2014 Posted by Sven Hasselbach

I have found a very nice skin for CKEditor, the “BootstrapCK-Skin”. It gives a bootstrap look and feel to the Editor:

The dialogs are also skinned:

You can find and download the skin here http://kunstmaan.github.io/BootstrapCK-Skin/

To use the skin in one of your applications, you have to import the unzipped files into your NSF…

… and add your “own” declaration of a xspCKEditor instance:

<xp:scriptBlock id="scriptBlockCKEditor">
   <xp:this.value>
      <![CDATA[
         require( ['dojo/_base/declare', 'ibm/xsp/widget/layout/xspCKEditor'], function( declare, xspCKEditor ){
            return declare( 'ch.hasselba.xpages.CKEDITOR', xspCKEditor, {
               constructor: function ckew_ctor(/*Object*/options){
                  CKEDITOR.timestamp = '';
               }
            });    
         });
      ]]>
   </xp:this.value>
</xp:scriptBlock>

This is required to remove an URL parameter, which is added automatically and will break the references. Then you have to overwrite the dojoType of your RichText control and add a dojoAttribute for the Skin. The path has to be appended after the name of the skin.

<xp:inputRichText
   id="inputRichTextBody"
   value="#{documentInfo.Body}"
   dojoType="ch.hasselba.xpages.CKEDITOR">
   <xp:this.dojoAttributes>
      <xp:dojoAttribute
         name="skin"
         value="BootstrapCK-Skin,/path/to/your/db.nsf/BootstrapCK-Skin/">
      </xp:dojoAttribute>
   </xp:this.dojoAttributes>
</xp:inputRichText>

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.

Notes 9: Some interesting xsp.properties

3. April 2013 Posted by Sven Hasselbach

Some interesting new xsp.properties were introduced with Notes 9:

  • xsp.client.resources.uncompressed

When set to true, all Dojo libraries and CSS resources where delivered in the uncompressed version. The path changes f.e. to /xsp/.ibmxspres/dojoroot-1.8.1-u/dojo/dojo.js.

  • xsp.client.script.dojo.html5attr

When set to true, the Dojo HTML5 Data attribute is added to all Dojo component on the XPages. Here is an example for a Date/Time field:

<input type="text"
   id="view:_id1:inputText1"
   name="view:_id1:inputText1"
   class="xspInputFieldDateTimePicker"
   data-dojo-type="ibm.xsp.widget.layout.DateTextBox"
   iconStyleClass="xspInputFieldDatePickerIcon"
   constraints="{datePattern:&quot;dd.MM.yyyy&quot;,timePattern:&quot;HH:mm:ss&quot;,selector:&quot;date&quot;}">
  • xsp.radiobuttongroup.item.label.prefixSpace

When set to true, a blank is added before the label the label:

<xp:radioGroup id="radioGroup1">
   <xp:selectItem itemLabel="Untitled" />
</xp:radioGroup>

Resulting HTML code (There is a space before the red marked label):

<label for="view:_id1:radioGroup1:0">
   <input type="radio" id="view:_id1:radioGroup1:0" 
   name="view:_id1:radioGroup1" value="Untitled"> Untitled</label>

New properties which are described in the xsp.properties.sample file:

  • xsp.maximum.mime.tree.scanLevel
  • com.ibm.ws.webcontainer.HTTPOnlyCookies
  • xsp.client.script.xspClient.preventLayer
  • xsp.client.script.radioCheckbox.ie.onchange.trigger
  • xsp.repeat.parseSingleStringAsInt
  • xsp.client.script.dojo.loader

Quick-n-Dirty: Non Closable Dialog for ExtLib

19. Oktober 2012 Posted by Sven Hasselbach

I have created a dirty hack for dialogs which prevents users to close the dialog by pressing the escape key or the close button. In my first approach  (StackOverflow.com)the hack has overwritten all dialogs for the whole XPage. This version allows to enable / disable it per dialog.

To use this hack you have to add the CSJS library to your XPage and call the dialog with an additional parameter ‘nonclosable‘:

XSP.openDialog("#{id:dialogNonClosable}", {'nonClosable': true } );

The hack was tested with IE 9 and FF 16, ExtLib 8.5.3.20120605-0921. Here is the CSJS library you have to attach to your XPage:

/**
 * ExtLib Dialog Hack to enable NonClosable Dialogs
 * 
 * This function overrides the exsiting openDialog method of the XSP object. It
 * adds a new option named 'nonclosable'
 * 
 * To open a non closable dialog, you have to use it like this
 * 
 * XSP.dialog('#{id:dialog}', {'nonclosable': true } );
 * 
 * @category CSJS
 * @category ExtLib
 * @author Sven Hasselbach
 */

function injectExtLibHack() {
    XSP.openDialog = function xe_od(dialogId, options, params) {
        dojo.addOnLoad( function() {
            var created = false;
            var dlg = dijit.byId(dialogId);
            if (!dlg) {
                var type = XSP._dialog_type;
                try {
                    if (options['nonClosable'])
                        type = XSP._dialog_type_nonClosable;
                } catch (e) {
                }

                options = dojo.mixin( {
                    dojoType : type || "extlib.dijit.Dialog"
                }, options);

                dojo.parser.instantiate( [ dojo.byId(dialogId) ], options);
                dlg = dijit.byId(dialogId);
                created = true;
            } else {
                if (dlg.keepComponents) {
                    dlg.show();
                    return;
                }
            }

            var onComplete = function() {
                dlg.show();
            };
            var axOptions = {
                params : dojo.mixin( {
                    $$showdialog : true,
                    $$created : created
                }, params),
                onComplete : onComplete,
                formId : dialogId
            };
            dlg.attr("content", "<div id='" + dialogId + ":_content'></div>");
            XSP.partialRefreshGet(dialogId + ":_content", axOptions);
            if (dojo.isIE < 8) {
                dojo.query(".lotusDialogBorder").style("width", "500px");
            }
        });
    }

    dojo.provide("extlib.dijit.OneUIDialogNonCloseableDialog");
    dojo.require("extlib.dijit.Dialog");
    dojo.declare(
       "extlib.dijit.OneUIDialogNonCloseableDialog",
       extlib.dijit.Dialog,
       {
          baseClass: "",
          templateString: dojo.cache("extlib.dijit", "templates/OneUIDialog.html"),
          disableCloseButton: true,
          _onKey: function(evt){
          if(this.disableCloseButton &&
             evt.charOrCode == dojo.keys.ESCAPE) return;
             this.inherited(arguments);
          },
          _updateCloseButtonState: function(){
             dojo.style(this.closeButtonNode,
             "display",this.disableCloseButton ? "none" : "block");
          },
          postCreate: function(){
             this.inherited(arguments);
             this._updateCloseButtonState();
             dojo.query('form', dojo.body())[0].appendChild(this.domNode);
          },
          _setup: function() {
             this.inherited(arguments);
             if (this.domNode.parentNode.nodeName.toLowerCase() == 'body')
                dojo.query('form', dojo.body())[0].appendChild(this.domNode);               
          }        
       }
    );

    // This is used by the picker dialog to grab the correct UI
    XSP._dialog_type_nonClosable="extlib.dijit.OneUIDialogNonCloseableDialog";

}

XSP.addOnLoad( injectExtLibHack );

And here is an example XPage which demonstrates how to use the hack:

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core"
    xmlns:xe="http://www.ibm.com/xsp/coreex">
    <xp:this.resources>
        <xp:script src="/dialogHack.js" clientSide="true"></xp:script>
    </xp:this.resources>

    <xp:button value="Show Dialog Closable" id="button1">
        <xp:eventHandler event="onclick" submit="false">
            <xp:this.script><![CDATA[
            XSP.openDialog("#{id:dialogClosable}");]]></xp:this.script>
        </xp:eventHandler>
    </xp:button>
    <xp:button value="Show Dialog Non Closable" id="button2">
        <xp:eventHandler event="onclick" submit="false">
            <xp:this.script>
                <![CDATA[
                    XSP.openDialog("#{id:dialogNonClosable}", {'nonClosable': true } );
                ]]>
            </xp:this.script>
        </xp:eventHandler>
    </xp:button>

    <xe:dialog id="dialogClosable" title="This is a closable dialog">
        <xe:dialogContent>
            You can close me via Escape or the close button.
        </xe:dialogContent>
        <xe:dialogButtonBar>
            <xp:button value="Ok" id="button3"
                styleClass="lotusFormButton">
                <xp:eventHandler event="onclick" submit="false">
                    <xp:this.script><![CDATA[XSP.closeDialog('#{id:dialogClosable}')]]></xp:this.script>
                </xp:eventHandler>
            </xp:button>
        </xe:dialogButtonBar>
    </xe:dialog>

    <xe:dialog id="dialogNonClosable" title="This is a non-closable dialog">
        <xe:dialogContent>
            Try to close me via Escape or the close button ;-) 
        </xe:dialogContent>
        <xe:dialogButtonBar>
            <xp:button value="Ok" id="button4"
                styleClass="lotusFormButton">
                <xp:eventHandler event="onclick" submit="false">
                    <xp:this.script><![CDATA[XSP.closeDialog('#{id:dialogNonClosable}')]]></xp:this.script>
                </xp:eventHandler>
            </xp:button>
        </xe:dialogButtonBar>
    </xe:dialog>

</xp:view>

This is the “standard” ExtLib dialog:

And this is the “non-closable” version:

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

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>

XSnippets: Fire querySave / postSave – Events

22. April 2012 Posted by Sven Hasselbach

The second XSnippet I have added to the XSnippet Contest is a help to fire the querySave- and postSave-events from SSJS: Save Datasource & Fire querySave/postSave events

If you only do a simple document1.save() , the events of a datasource won’t be executed. To fix this, you have to use the save() method of com.ibm.xsp.model.domino.DominoDocumentData instead:

var dsName = "document1.DATASOURCE"; // change this to the name of
                                     // the datasource you want
                                     // to save
var app = facesContext.getApplication();
var ds = app.getVariableResolver()
           .resolveVariable(facesContext, dsName);
ds.save( facesContext, true);

The variable dsName contains the name of the datasource to save, followed by “.DATASOURCE“. To use it f.e. with the current document, you have to change the variable to “currentDocument.DATASOURCE“.

The difference here is the type of object that is used: If you save a document datasource, the save method of an object of type NotesXspDocument is called. The com.ibm.xsp.model.domino.DominoDocumentData object has another method for saving. The first object type is like a backend NotesDocument, the second object is like the NotesUIDocument class.

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.