Posts Tagged: ‘Java Script’

XPages: Inject CSJS code at page top

9. April 2013 Posted by Sven Hasselbach

Sometimes it is required to add a some CSJS code at the beginning of the XPage before the Dojo libaries are loaded and are executed. This can be easily achieved by using the resource aggregation feature and including a CSJS library with the <xp:headTag>.

First you have to create a CSJS library which contains the code you want to execute:

Then you have to add the <xp:headTag> which loads the CSJS library:

<?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:headTag tagName="script">
             <xp:this.attributes>
                 <xp:parameter name="type" value="text/javascript" />
                 <xp:parameter name="src" value="Before.js" />
             </xp:this.attributes>
         </xp:headTag>
    </xp:this.resources>

</xp:view>

[The xp:parameter can ignored if resource aggregation is enabled for the whole application]

The generated HTML source code will look like this, with the CSJS library at the top of the page:

It works ONLY if resource aggregation is enabled. If it is disabled, the CSJS library is added after the Dojo libraries and style sheets:

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.

Quick-n-Dirty: Disable all validators at once

10. Februar 2013 Posted by Sven Hasselbach

In a larger project there are a lot of forms to fill in, each form has many fields with validators and converters. During the development of the workflow it was really helpful to disable all validators at once by using the setDisableValidators() method of the facesContext.

To control the behaviour I have added an URL parameter which is checked in the afterRestoreView event. If you open the XPage in the format

http://hostname/db.nsf/xPage.xsp?noValidators=true

all validators will be disabled. Here is a small example XPage:

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

    <xp:this.afterRestoreView>
    <![CDATA[#{javascript:
        if( param.containsKey("noValidators") ){
            facesContext.setDisableValidators(true);
        }else{
            facesContext.setDisableValidators(false);
        }
    }]]>
    </xp:this.afterRestoreView>
    <xp:inputText id="inputText1" required="true" />
    <xp:br />
    <xp:button value="Send" id="buttonSend">
        <xp:eventHandler event="onclick" submit="true" 
           refreshMode="complete" immediate="false" save="true" />
    </xp:button>
    <xp:br />
    <xp:messages id="messages1" />

</xp:view>

This makes developers life easier!

XPages: compositeData is undefined

10. Februar 2013 Posted by Sven Hasselbach

An interesting question was asked on StackOverflow.com: The compositeData of custom control is undefined in beforeRenderResponse event. I have never noticed this before, but if you are accessing the compositeData object in the before-, afterRenderResponse or the afterRestoreView event, the object is undefined.

Here is a simple demonstration CC which just prints the type of the compositeData object to the console:

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

    <xp:this.beforePageLoad>
        <![CDATA[#{javascript:print( "beforePageLoad: " + typeof( compositeData )) }]]>
    </xp:this.beforePageLoad>

    <xp:this.afterPageLoad>
        <![CDATA[#{javascript:print( "afterPageLoad: " + typeof( compositeData )) }]]>
    </xp:this.afterPageLoad>

    <xp:this.afterRestoreView>
        <![CDATA[#{javascript:print( "afterRestoreView: "  + typeof( compositeData )) }]]>
    </xp:this.afterRestoreView>

    <xp:this.beforeRenderResponse>
        <![CDATA[#{javascript: print( "beforeRenderResponse: "  + typeof( compositeData )) }]]>
    </xp:this.beforeRenderResponse>

    <xp:this.afterRenderResponse>
        <![CDATA[#{javascript: print( "afterRenderResponse: "  + typeof( compositeData )) }]]>
    </xp:this.afterRenderResponse>

</xp:view>

And here is a screenshot of the console, including a partial refresh of the page:

Only in the PageLoad events the compositeData object is available. But why?

The answer is rather simple: The events are executed in a different context. The PageLoad events are running “inside” of the UIInlcudeComposite (the Custom Control), the other events are running in the UIViewRoot:

That’s why the compositeData is undefined in these events. To use the object in the events, you have to do the following:

First you have to add an ID to your custom control:

<xc:ccWithId test="foo" id="ccWithId" />

This allows you to access the CC as a regular component with getComponent(). Now you can access the com.ibm.xsp.binding.PropertyMap of the component in the custom control’s event which holds the variable you want:

<xp:this.beforeRenderResponse>
   <![CDATA[#{javascript:
      var cmp:com.ibm.xsp.component.UIIncludeComposite = getComponent("ccWithId");
      print("Value of 'test' -> " + cmp.getPropertyMap().getString("test") )
   }]]>
</xp:this.beforeRenderResponse>

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: Disable Validation for FileDownload control

24. Oktober 2012 Posted by Sven Hasselbach

If you are using the file download control,  you sooner or later want to allow your users to delete an existing file. But if you have some required fields on your XPage, a deletion is not possible because the validation is always fired before the attachment is removed.

To disable to validation you can use this little SSJS snippet which sets the “disableValidators” property for all events of the UIFileDownload control. Just add the following code to the beforeRenderResponse event of your XPage and change the id of the component to fit your requirements.

<xp:this.beforeRenderResponse>
   <![CDATA[#{javascript:
      /***
       * disable validation for UIFileDownload control
       * 
       * @param UIFileDownload component
       * @author Sven Hasselbach
       * @category SSJS
       * @category UI
       * @version 0.2
       */
      function disableFileDownloadValidation( fDownload:com.ibm.xsp.component.UIFileDownload ){
         if( fDownload === null )
            return;

         rekDisableFileDownloadValidation( fDownload );
      }

      function rekDisableFileDownloadValidation( component:javax.faces.component.UIOutput ){
         try{
            var children:java.util.List = component.getChildren();
            var it:java.util.Iterator = children.iterator();
            var curChild:javax.faces.component.UIOutput;

            while( it.hasNext() ){
               curChild = it.next();
               if( typeof( curChild ) === 'com.ibm.xsp.component.xp.XspEventHandler' )
                  curChild.setDisableValidators( true );

               rekDisableFileDownloadValidation( curChild );
            }
         }catch(e){}    
      }

      disableFileDownloadValidation( getComponent( 'fileDownload1' ) );
   }]]>
</xp:this.beforeRenderResponse>

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

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.

XPages: Capture Signatures with the jQuery-Plugin ‘jSignature’

26. September 2012 Posted by Sven Hasselbach

In one of my current projects it is one of the goals that the members of the field staff have the possibility to sign a report directly on their iPad. After some research I found the very cool jQuery plugin jSignature, which easily allows to add a signature capture field to a XPage.

The plugin is very easy to use: Just add a <div> to your XPage and initialize the plugin with the .jSignature() constructor and that’s it! The API is very simple, but provides everything needed: The captured signatures can be stored in different formats like PNG or SVG or as native Vector. Additionally they can be encoded as BASE64 or BASE30. The data can restored as easy as they can be saved: Just one API call and it’s done.

To save the signatures to a Notes document, the resulting data can be copied to a hidden input field. In the provied example above I additionally added the format of the signature. For a better handling of the generated data I decided to store the data in the SVG format, because this allows to save it directly in standard notes text field (without having problems because of the 32 K limit). This works well and the data can be displayed in standard browsers without any problems. Only in XPiNC this will not work, because the SVG format is not supported. PDFs doesn’t support SVG too, that’s why I created a converted agent using the Apache Batik framework.

I will add a demo database asap. A description of parameters to customize the plugin can be found here.

P.S. The compressed Version is not running on teamstudio Unplugged. You have to use the uncompressed Version. The example uses the x$ function of Mark Roden.

 XPage example

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

    <xp:this.resources>
        <xp:script src="/x$.js" clientSide="true"></xp:script>
        <xp:script src="/jSignatureHandler.js" clientSide="true" />
        <xp:styleSheet href="/jSignature.css"></xp:styleSheet>
    </xp:this.resources>
    <xp:this.data>
        <xp:dominoDocument var="documentSig" 
           formName="frmSignature" />
    </xp:this.data>

    <script src="js/jquery-1.8.1.min.js" />
    <script src="js/jSignature/jSignature.min.js" />

    <xp:div id="jSignature" style="width:400.0px"></xp:div>
    <xp:scriptBlock id="scriptBlockSignature">
        <xp:this.value><![CDATA[
        $(document).ready(function() { 
            sigHandler.init( "#{id:jSignature}", 
               "#{id:inputHiddenSignatureData}", 
               "#{id:inputHiddenSignatureFormat}" );
        }
        )]]></xp:this.value>
    </xp:scriptBlock>

    <xp:button id="button1" value="Reset">
        <xp:eventHandler event="onclick" submit="false">
            <xp:this.script><![CDATA[
                sigHandler.reset();
            ]]></xp:this.script>
        </xp:eventHandler>
    </xp:button>

    <xp:button id="button2" value="Save">
        <xp:eventHandler event="onclick" submit="true"
           refreshMode="complete">
            <xp:this.script><![CDATA[ 
                return sigHandler.save();
            ]]></xp:this.script>
            <xp:this.action>
                <xp:saveDocument var="documentSig" />
            </xp:this.action>
        </xp:eventHandler>
    </xp:button>

    <xp:inputHidden id="inputHiddenSignatureData" 
       value="#{documentSignature.SignatureData}" />
    <xp:inputHidden id="inputHiddenSignatureFormat"
       value="#{documentSignature.SignatureFormat}" />

</xp:view>

CSJS library “jSignatureHandler.js”

/**
 * signatureHandler
 * JS Object for handling the signature data
 * Requires jSignature jQuery plugin & special 
 * function "x$" from Mark Roden
 * 
 * @author Sven Hasselbach
 * @category JavaScript
 * @category jQuery
 * @category UI
 * @version 1.0
 */
var signatureHandler = function() {
    _module = "signatureHandler";

    _idJSignature: null; // DOM id of JSignatue DIV
    _idItemData: null; // DOM id of INPUT for signature data
    _idItemFormat: null; // DOM id of INPUT for signature format
    _objDOMJSignature: null; // handle to DOM object
    _objDOMItemData: null; // handle to DOM object
    _objDOMItemFormat: null; // handle to DOM object
    _maxSize = 32000; // max characters to store (32K limit!)
    _format = "svg"; // format used

    /**
     * set DOM id of JSignature DIV
     * @param String id
     */
    this.setJSignatureId = function( id ){
        this._idJSignature = id;
    }
    /**
     * get DOM id of JSignature DIV
     * @return String id
     */
    this.getJSignatureId = function(){
        return this._idJSignature;
    }
    /**
     * set DOM id of data item
     * @param String id
     */
    this.setItemDataId = function( id ){
        this._idItemData = id;
    } 
    /**
     * get DOM id of data item
     * @return String id
     */
    this.getItemDataId = function(){
        return this._idItemData;
    }
    /**
     * set DOM id of format item
     * @param String id
     */
    this.setItemFormatId = function( id ){
        this._idItemFormat = id;
    } 
    /**
     * get DOM id of format item
     * @return String id
     */
    this.getItemFormatId = function(){
        return this._idItemFormat;
    }

    /**
     * get handle to DOM object of JSignature DIV
     * @return Object
     */
    this.getJSignatureDOMObj = function(){
        return this._objDOMJSignature;
    }
    /**
     * set handle to DOM object of JSignature DIV
     * @param Object
     */
    this.setJSignatureDOMObj = function( obj ){
        this._objDOMSignature = obj;
    }
    /**
     * get handle to DOM object of data item
     * @return Object
     */
    this.getItemDataDOMObj = function(){
        return this._objDOMItemData;
    }
    /**
     * set handle to DOM object of data item
     * @param Object
     */
    this.setItemDataDOMObj = function( obj ){
        this._objDOMItemData = obj;
    }
    /**
     * get handle to DOM object of format item
     * @return Object
     */
    this.getItemFormatDOMObj = function(){
        return this._objDOMItemFormat;
    }
    /**
     * set handle to DOM object of format item
     * @param Object
     */
    this.setItemFormatDOMObj = function( obj ){
        this._objDOMItemFormat = obj;
    }

    /**
     * initialize object
     * 
     * @param String id of jSignature DIV
     * @param String id of data item INPUT
     * @param String id of format item INPUT
     * 
     */
    this.init = function( idJSig, idItemData, idItemFormat ){
        try{

            // init jSignature
            this._idJSignature = idJSig;
            this._objDOMSignature = x$( this._idJSignature ) ;
            this._objDOMSignature.jSignature();
            // init data item
            this._idItemData = idItemData;
            this._objDOMItemData = x$( this._idItemData );

            // init format item
            this._idItemFormat = idItemFormat;
            this._objDOMItemFormat = x$( this._idItemFormat );

            return true;
        }catch(e){
            var errMsg = _module + "::" + arguments.callee.name + "\n";
            for( p in e ){
                errMsg += p + ": '"  + e[p] + "'\n";
            }
            console.error( "Error!\n\n" + errMsg );
            return false;
        }
    }

    /**
     * reset jSignature
     */
    this.reset = function(){
        try{
            this._objDOMSignature.jSignature("reset");
            return true;
        }catch(e){
            var errMsg = _module + "::" + arguments.callee.name + "\n";
            for( p in e ){
                errMsg += p + ": '"  + e[p] + "'\n";
            }
            console.error( "Error!\n\n" + errMsg );
            return false;
        }
    }
    /**
     * saves the data from jSignature
     * 
     */
    this.save = function(){
        try{
            var datapair =  this._objDOMSignature.jSignature( "getData", _format );
            var format = "data:" + datapair[0];
            var data = datapair[1];
            // check max size!
            if( data.length >  _maxSize){
                alert( "The size of the signature is too large. Please retry!" );
                return false;
            }

            this._objDOMItemData.val( data );

            this._objDOMItemFormat.val( format )
            return true;

        }catch(e){
            var errMsg = _module + "::" + arguments.callee.name + "\n";
            for( p in e ){
                errMsg += p + ": '"  + e[p] + "'\n";
            }
            console.error( "Error!\n\n" + errMsg );
            return false;
        }
    }
}

// init JS instance
sigHandler = new signatureHandler();

Quick-n-Dirty: Development Helper for Unplugged

21. September 2012 Posted by Sven Hasselbach

I started today testing/evaluating Teamstudio Unplugged for a customer project. The first thing I missed during developement was a button to sync the current page directly to see my changes on the fly. That’s why I created this small custom control:

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
   <table border="1" width="100%" cellpadding="2">
      <tr>
         <td colspan="3" align="middle">
            <b>Unplugged Development Helper</b>
         </td>
     </tr>
     <tr>
        <td align="middle">
           <input type="button"
              onclick="window.location='/unpws.unp/'" 
              name="Workspace" value="Workspace" />
         </td>
         <td>&#160;</td>
         <td align="middle">
            <input type="button"
              onclick="$.get('/unpws.unp/ajaxreplicate.xsp', function(data){ location.reload(); });"
              name="Sync" value="Sync!" />
         </td>
      </tr>
   </table>   
</xp:view>

The Workspace Button brings you to the workspace, the Sync button starts the replication and reloads the current page.

XPages: High Performance Applications

18. September 2012 Posted by Sven Hasselbach

During the last months I worked on a high performance XPages application used by a lot of end users.  To get a better data throughput, I decided to use a reverse proxy for load balancing, caching of ressources, SSL connections etc.

For static resources I am using some “special” domains: This means that the browser is allowed to do more HTTP requests at once to the same NSF. If you have for example some images in your database which are reachable from outside via http://www.example.com/mydb.nsf/image.gif, this can be changed to http://static.example.com/mydb.nsf/image.gif (Here you can find a list of best practices).

I solved the problem of multiple execution during the JSF lifecycle  by checking if the request has already actual data (details can be found here – German only), but there was still a problem: Everytime a XPage wants some data to display, a query is sent to the domino server. This is nice if your application requires data in real-time, but not for a normal application – it kills the user experience.

This is why I searched a way which easily allows to implement memory cached database queries for domino databases.  The main idea is that the database query is no longer send directly to the backend . Instead, the request is made against a JSON wrapper, and the request to this wrapper (an agent residing in the same NSF) is done via a proxy. This allows a full control of the requested data.

The browser sends the HTTP request to the XPage and receives the response. The XPage queries the MemCache for data; if the data is not in the cache, the proxy queries the data storage (the domino database) and caches the result. The XPage has to parse the JSON data only, and this boosts the performance in my test environment for about 250-300%.

By “stealing” the session cookie, the database query to the backend database will be done in the context of the user; the security for domino databases is not influenced.

In my solution, I am using Apache 2.2 as reverse proxy. The following modules are additionally enabled for this solution:

  • cache_module
  • deflate_module
  • expires_module
  • headers_module
  • mem_cache_module
  • proxy_module
  • proxy_http_module
  • rewrite_module
  • setenvif_module

The virtual host configuration looks like this:

<VirtualHost *:8080>
 ServerName localhost

 # Enable reverseproxy
 ProxyRequests Off
 ProxyPreserveHost On
 <Proxy *>
  AddDefaultCharset off
  Order allow,deny
  Allow from all
 </Proxy>

 # Proxy config for Domino server
 # 
 ProxyPass / http://localhost:80/
 ProxyPassReverse / http://localhost:80/

  # prevent max-age calculation from Last-Modified
  # prevent If-Modified-Since requests
  # reduces the number of requests that hit the server
 <LocationMatch "/.*$">
  Header unset Last-Modified
  Header unset ETag
  Header unset HTTP_CACHE_CONTROL
 </LocationMatch>

 # MemCache Config
 # 
 CacheEnable mem /
 CacheEnable mem http://

 # Cache-Size 80 MB
 MCacheSize 81920
 MCacheMaxObjectCount 8192

 # Min Obj. Size 1 Byte
 MCacheMinObjectSize 1

 # Max Obj. Size 1 MB
 MCacheMaxObjectSize 1000000

 # cache for 60 seconds by default
 CacheDefaultExpire 60

 # FORCE caching for all documents (without Cache-Control: no-cache)
 CacheIgnoreNoLastMod On

 # force caching for all requests
 # ignore client side Cache-Control header
 CacheIgnoreCacheControl On
 # don't add Set-Cookie header to cache
 CacheIgnoreHeaders Set-Cookie

 # Add expires headers for images, css & js files
 # reduces the number of requests that hit the server
 ExpiresActive On
 ExpiresByType domino/json A600

</VirtualHost>

As you can see, the proxy runs on port 8080, and I have added a special content type “domino/json“. This makes it easier to identify the relevant data.

This is the XPage the user accesses:

<?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">

    <xp:pager layout="Previous Group Next" partialRefresh="true"
        id="pager1" for="repeat1">
    </xp:pager>

    <xp:inputText id="inputSearch" value="#{sessionScope.searchFor}" />

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

    <xp:repeat id="repeat1" rows="30" var="rowData">
        <xp:this.value><![CDATA[#{javascript:
            importPackage( ch.hasselba.xpages.util );

            var sessionId = null;
            try{
                 sessionId = cookie.get("DomAuthSessId").getValue();
            }catch(e){}

            var url = "http://localhost:8080/Data.nsf/DoSearch?OpenAgent";
            url += "&sessionId=" + sessionId;

            if( sessionScope.get("searchFor") !== null ){
                if( sessionScope.get("searchFor") !== "" )
                    url += "&search="; 
                    url += java.net.URLEncoder.encode(sessionScope.get("searchFor"),"UTF-8");
            }

            var data = ch.hasselba.xpages.util.URLReader.read( url, sessionId );
            var parsed = null;
            try{
                 parsed = fromJson(data).data;
            }catch(e){}
            parsed
            }]]>
        </xp:this.value>
        <xp:text escape="true" id="computedField1" value="#{javascript:rowData.LastName}">
        </xp:text>
        &#160;
        <xp:text escape="true" id="computedField2" value="#{javascript:rowData.FirstName}">
        </xp:text>
        <xp:br />
    </xp:repeat>

</xp:view>

The Java class used is really simple, I know there are better ways to do a Http request, but this is a proof of concept.

package ch.hasselba.xpages.util;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;

/**
 * URLReader
 * performs a HTTP request
 * 
 * @author Sven Hasselbach
 * @category URL
 * @category Proxy
 * @version 0.2
 */
public class URLReader {

    // Constants
    private final static String PROPERTY_COOKIE_NAME = "Cookie";
    private final static String PROPERTY_DOMAUTHSESSID_VALUE = "DomAuthSessId=";

    /**
     * reads data from a given URL
     * 
     * @param pURL URL to load data from
     * @param pSessionId session data for doing a request in the current user context
     * @return String containg the result of the http request
     * @author Sven Hasselbach
     * @category URL
     * @category Proxy
     * @version 0.2
     */
    public static String read( final String pURL, final String pSessionId ){
        String data = null;

        try{
            // init the URL connection
            URL url = new URL( pURL );
            URLConnection uc = url.openConnection();

            // "steal" the original user session cookie
            if( !("".equals(pSessionId)))
                    uc.setRequestProperty ( PROPERTY_COOKIE_NAME ,
                       PROPERTY_DOMAUTHSESSID_VALUE + pSessionId);

            // do the HTTP request
            BufferedReader in = new BufferedReader( 
               new InputStreamReader( uc.getInputStream() ));

            // process the data returned 
            StringBuffer strBuf = new StringBuffer();
            String tmpStr = "";
            while((tmpStr = in.readLine()) != null ){
                strBuf.append( tmpStr );
            }
            data = strBuf.toString();

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

        return data;
    }
}

And here comes the JSON handler, a simple Lotus Script agent:

Sub Initialize
    Dim session As New NotesSession
    Dim db As NotesDatabase
    Dim dc As NotesDocumentCollection
    Dim doc As NotesDocument
    Dim isFirst As Boolean
    Dim contextDoc As NotesDocument
    Dim hlp
    Set contextDoc = session.Documentcontext
    Set db = session.Currentdatabase

    ' get the search string from the URL or use the default search
    hlp = Split( contextDoc.QUERY_STRING_DECODED(0), "search=" )
    If UBound( hlp ) = 0 Then
        Set dc = db.Ftsearch("[FirstNAME] CONTAINS AARON", 0)
    Else
        Set dc = db.Ftsearch(hlp(1), 0)
    End If

    ' create the JSON output    
    isFirst = true
    Set doc = dc.Getfirstdocument()

    ' special content type domino/json
    Print |Content-type: domino/json|
    Print
    Print |{"data":[|
    While Not doc Is Nothing
        If Not isFirst Then Print ","
        Print |{"LastName":"| & doc.LastName(0) & _
        |","FirstName":"| & doc.FirstName(0) & |"}|
        isFirst = False
        Set doc = dc.Getnextdocument( doc )
    Wend

    Print |]}|

End Sub

In the next days I will provide a sample database and add more details. A database with 300k test datasets will be added too.

XPages: Lost in Translation

18. September 2012 Posted by Sven Hasselbach

The localization feature is really nice and helps a lot, but you also can have some trouble using it. The first problem is that the language codes which are used in XPages are different from the language codes in java.util.Locale.

This SSJS code for example will not work:

var locale = new java.util.Locale( "fr_BE" );
context.setLocale( locale );
context.reloadPage();

It will not work because the java.util.Locale object returns “fr_be“, but the PageDetails are set to “fr_BE“, and that’s why the setting the language does not work.

<xp:text escape="true" id="computedFieldLocale">
   <xp:this.value>
      <![CDATA[#{javascript:
         var locale = new java.util.Locale( "fr_BE" );
         locale.getLanguage()}]]>
      </xp:this.value>
</xp:text>

[This SSJS Code returns "fr_be"]

[In PageDetails "fr_BE" is used]

But if you set the locale string manually with the setLocaleString() method of the context object, it works as designed.

The second problem I ran into is the aggressive transformation of all texts on a XPage. I copied a simple CSS Style definition from another database, but after copy/pasting it I was unable to see any changes in the browser, as soon I switched to another than the default language. The CSS style didn’t work anymore:

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
   <style>
      .red { color:rgb(255,0,0 ) }
   </style>
   <p>TEXT</p>
</xp:view>

[Default Language, it worked]

[Other Language, not working anymore]

The CSS style was transformed by the localization feature and was now “translated”. In the generated HTML code, the tag looked like this:

<style>[en| .red { color:rgb(255,0,0 ) } ]</style>

Same result for directly copied JavaScript-Code, Text etc. (as shown in the screenshot above).

But I still love this feature. It makes developers life a lot easier.

Security: Another XSS Vulnerability in Domino

11. September 2012 Posted by Sven Hasselbach

Stephan Wissel wrote about a XSS vulnerabilty for Domino servers (< 8.5.4) and in his post you will get an advise how to protect your domino server against this attack. Thanks for this! Works great!

But there is still a problem with another URL pattern:

*/xsp/.ibmmodres/*

This resolves resources from databases, that’s why it only works in a database URL. But normally domcgf.nsf is reachable from outside.

Update:

The blog post was updated on wissel.net. Please update your server configuration!

SSJS: Execute remote SSJS Code

24. August 2012 Posted by Sven Hasselbach

I have created a small helper class to run SSJS code from a remote server. The basic idea behind this class is a question on stackoverflow: http://stackoverflow.com/questions/12054733/include-jss-file-from-notes-document-as-resource

As far as I know there is no way to add a SSJS resource via the src attribute, this won’t work:

<xp:this.resources>
   <xp:script src="http://localhost:8080/test.jss"
      clientSide="false" />
</xp:this.resources>

It will always fail, even if the file is available, has the correct file extension etc.

That’s why I wrote the code, it’s only a proof of concept. There are no security features to protect against manipulations, no caching for better performance and whatever.

Here is a demo XPage to demonstrate how to use it:

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
   <xp:label id="label1">
      <xp:this.value>
         <![CDATA[#{javascript:
            importPackage( ch.hasselba.xpages.util.ssjs );
            SSJSUtil.executeSSJSFromURL("http://localhost:8080/test.jss");
            test();
         }]]>
      </xp:this.value>
   </xp:label>
</xp:view>

The method executeSSJSFromURL loads a text file from the given URL, creates a method binding with the content and invokes it. Then, the SSJS code is executed directly – all functions, objects and variables defined in the remote code are ready to use from now on. As you can see above, the method test() is called which is defined in the remote SSJS file.

And here is the Java code:

package ch.hasselba.xpages.util.ssjs;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import com.ibm.xsp.util.StreamUtil;
import com.ibm.xsp.page.compiled.ExpressionEvaluatorImpl;
import javax.faces.context.FacesContext;
import javax.faces.el.ValueBinding;

/**
 * SSJSUtil
 * 
 * helper class for SSJS operations
 * 
 * @author Sven Hasselbach
 * @version 1.0.2
 * @category SSJS
 * @category Utility
 */

public class SSJSUtil {

    private static final String NEWLINE = "\n";
    private static final String SSJS_EXPRESSION_BEGIN = "#{javascript:";
    private static final String SSJS_EXPRESSION_END = "}";

    /**
     * Loads SSJS code from a given URL and executes it
     * Declared methods and objects are reachable for other SSJS code
     * 
     * @param url of the SSJS code
     * @return Object resulting object from SSJS execution
     * @author Sven Hasselbach
     * @version 1.0.1
     * @category Utility
     */
    public static Object executeSSJSFromURL( final String url ){
        return execute( loadFromURL( url ) );
    }

    /**
     * loads a URL stream and converts it to a string
     * @param url of the resource
     * @return String containing the data loaded from given url
     * @author Sven Hasselbach
     * @version 1.0.1
     * @category Utility
     */
    public static String loadFromURL( final String url ){
        String ret = null;
        try{
            FacesContext fc = FacesContext.getCurrentInstance();
            InputStream in = StreamUtil.getInputStream(fc, url);
            ret = inputStreamToString( in );
        }catch(Exception e){
            e.printStackTrace();
        }
        return ret;
    }

    /**
     * executes given SSJS code and returns the result (if any)
     * functions / libraries are added to runtime 
     * 
     * @param ssjsCode code to execute
     * @return resulting object
     * @author Sven Hasselbach
     * @version 1.0.2
     * @category SSJS
     */
    public static Object execute( final String ssjsCode ){
        Object ret = null;

        try{
            String valueExpr = SSJS_EXPRESSION_BEGIN + ssjsCode + SSJS_EXPRESSION_END;
            FacesContext fc = FacesContext.getCurrentInstance();
            ExpressionEvaluatorImpl evaluator = new ExpressionEvaluatorImpl( fc );
            ValueBinding vb = evaluator.createValueBinding( fc.getViewRoot(), valueExpr, null, null);
            ret = vb.getValue(fc);
        }catch(Exception e){
            e.printStackTrace();
        }
        return ret;
    }

    /**
     * converts the data from a given inputstream to a string
     * 
     * @param in InputStream to convert
     * @return String containing data from input stream
     * @throws IOException
     * @author Sven Hasselbach
     * @version 1.0.1
     * @category Utility
     */
    public static String inputStreamToString(final InputStream inStream) throws IOException {
        BufferedReader bufReader = new BufferedReader( new InputStreamReader(inStream) );
        StringBuilder strBuilder = new StringBuilder();
        String line = null;

        while ((line = bufReader.readLine()) != null) {
            strBuilder.append(line);
            strBuilder.append( NEWLINE );
        }
        bufReader.close();

        return strBuilder.toString();
     }
}

By the way: You cannot use the import method in the remote code.

SSJS: What’s “this”?

24. August 2012 Posted by Sven Hasselbach

In Serverside JavaScript the keyword this always refers to the “owner” of the function which is executing,  or rather, to the object that a function is a method of.

This means f.e. that this refers to the UIComponent which contains the SSJS code. If you add a label to a XPage and compute the value…

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
   <xp:label id="label1">
      <xp:this.value>
         <![CDATA[#{javascript:return this;}]]>
      </xp:this.value>
   </xp:label>
</xp:view>

this will always return the current corresponding instance of the XspOutputLabel:

But if you are inside a function this will return the current JavaScript object instead:

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

   <xp:label id="label1">
      <xp:this.value>
         <![CDATA[#{javascript:
            var arr = [1, 2];

            function callMe(){
               var obj = this;
               for( var p in obj ){
                  print( p + " -> " + obj[p] + " [" + typeof(obj[p]) + "]" );
               }    
            }
            callMe()
         }]]>
      </xp:this.value>
   </xp:label>

</xp:view>

The result on the console looks like this:

If you are using call or apply, you can overwrite the this object:

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

   <xp:label id="label1">
      <xp:this.value>
         <![CDATA[#{javascript:
            function callMe(){
               println(this);

               for( p in arguments ){
                  println( arguments[p] );
               } 
            }

            callMe.call("1","2","3");
         }]]>
      </xp:this.value>
   </xp:label>
</xp:view>

Now this is set to “1“. The arguments are “2” and “3“, as you can see on the server console:

XPages: Access a datasource from one custom control in another one

5. August 2012 Posted by Sven Hasselbach

On stackoverflow.com, a very interesting question was asked: How can you access a document datasource from one custom control in another custom control. And here comes my solution in a small SSJS function.

First you have to add an id to the custom control which contains the datasource. This gives you a handle for other custom controls:

<xc:ccDS1 id="idDSComponent"></xc:ccDS1>

Now it is possible to get the custom control by using the SSJS function getComponent(). The datasources are stored in the data attribute of the custom control. You just have to iterate through them and check every entry for the name you are looking for.

And here is the code snippet to access the datasource in another custom control:

/***
 * getDatasource
 * Resolves a datasource from a custom control
 * 
 * @param componentId    id of the component containing the datasource
 * @param dsName    name of the datasource in the component
 * @author Sven Hasselbach
 */
 function getDatasource( componentId:String, dataSourceName:String ):com.ibm.xsp.model.domino.DominoDocumentData {
    try{
       var data:java.util.ArrayList = getComponent( componentId ).getAttributes().get("data");
       if( data == null )
          return null;
                    
       var it:java.util.Iterator = data.iterator();
       var obj = null;
       while( it.hasNext() ){
          obj = it.next();
          if( obj.getVar() == dataSourceName )
             return obj;
       }
    }catch(e){
       print( e );
    }
}

To use the function you have to use it like this:

getDatasource( "idDSComponent", "document1" )

Here is the link to the original question: How to get document datasource in another Custom Control?