Posts Tagged: ‘9.0’

XPages: Bootstrap File Input

26. März 2014 Posted by Sven Hasselbach

When using the default file upload control in a Bootstrap application, the default file upload button does not fit anymore to the design:

To fix this issue, you can use a small jQuery plugin named Twitter Bootstrap File Input. When this plugin is added to your XPage, the button will look like this:

 

To initialize the jQuery plugin, you have to call it with a selector which selects all DOM elements of type file:

<xp:scriptBlock
    id="scriptBlockInitFile">
    <xp:this.value>
        <![CDATA[
           $(document).ready( 
               function() {
                   $('input[type=file]').bootstrapFileInput();
               }
           );
        ]]>
    </xp:this.value>
</xp:scriptBlock>

The description of the button can be changed by setting the title attribute. Additionally, you can choose if the file name will be displayed inside or outside of the button:

To place it inside, you need to add the attribute data-filename-placement to the file upload control:

<xp:fileUpload
    id="fileUploadControl"
    value="#{document.Body}"
    title="Datei auswählen">
    <xp:this.attrs>
        <xp:attr
            name="data-filename-placement"
            value="inside" />
    </xp:this.attrs>
</xp:fileUpload>

Because I have added it to a Custom Control and use it multiple times on a XPage, I have changed the original code and added a flag to prevent multiple calls, otherwise all file elements are modified over and over again:

Here is the modified code:

/*
  Bootstrap - File Input
  ======================

  This is meant to convert all file input tags into a set of elements that displays consistently in all browsers.

  Converts all
  <input type="file">
  into Bootstrap buttons
  <a>Browse</a>

  Sven Hasselbach, 26.03.2014:
  Added a fix to prevent multiple wrapping 

*/
(function($) {

$.fn.bootstrapFileInput = function() {

  this.each(function(i,elem){

    var $elem = $(elem);

    // Maybe some fields don't need to be standardized.
    if (typeof $elem.attr('data-bfi-disabled') != 'undefined') {
      return;
    }

    // --- Fix to prevent multiple wrapping 
    // Sven Hasselbach, 26.03.2014

    // check for an existing 'wrapped' attribute'
    if(!!$elem.attr('wrapped'))
        return;

    // add the 'wrapped' attribute
    $elem.attr('wrapped', 'true');
    // --- End of Fix    
    // Set the word to be displayed on the button
    var buttonWord = 'Browse';

    if (typeof $elem.attr('title') != 'undefined') {
      buttonWord = $elem.attr('title');
    }

    var className = '';

    if (!!$elem.attr('class')) {
      className = ' ' + $elem.attr('class');
    }

    // Now we're going to wrap that input field with a Bootstrap button.
    // The input will actually still be there, it will just be float above and transparent (done with the CSS).
    $elem.wrap('<a></a>').parent().prepend($('<span></span>').html(buttonWord));
  })

  // After we have found all of the file inputs let's apply a listener for tracking the mouse movement.
  // This is important because the in order to give the illusion that this is a button in FF we actually need to move the button from the file input under the cursor. Ugh.
  .promise().done( function(){

    // As the cursor moves over our new Bootstrap button we need to adjust the position of the invisible file input Browse button to be under the cursor.
    // This gives us the pointer cursor that FF denies us
    $('.file-input-wrapper').mousemove(function(cursor) {

      var input, wrapper,
        wrapperX, wrapperY,
        inputWidth, inputHeight,
        cursorX, cursorY;

      // This wrapper element (the button surround this file input)
      wrapper = $(this);
      // The invisible file input element
      input = wrapper.find("input");
      // The left-most position of the wrapper
      wrapperX = wrapper.offset().left;
      // The top-most position of the wrapper
      wrapperY = wrapper.offset().top;
      // The with of the browsers input field
      inputWidth= input.width();
      // The height of the browsers input field
      inputHeight= input.height();
      //The position of the cursor in the wrapper
      cursorX = cursor.pageX;
      cursorY = cursor.pageY;

      //The positions we are to move the invisible file input
      // The 20 at the end is an arbitrary number of pixels that we can shift the input such that cursor is not pointing at the end of the Browse button but somewhere nearer the middle
      moveInputX = cursorX - wrapperX - inputWidth + 20;
      // Slides the invisible input Browse button to be positioned middle under the cursor
      moveInputY = cursorY- wrapperY - (inputHeight/2);

      // Apply the positioning styles to actually move the invisible file input
      input.css({
        left:moveInputX,
        top:moveInputY
      });
    });

    $('body').on('change', '.file-input-wrapper input[type=file]', function(){

      var fileName;
      fileName = $(this).val();

      // Remove any previous file names
      $(this).parent().next('.file-input-name').remove();
      if (!!$(this).prop('files') && $(this).prop('files').length > 1) {
        fileName = $(this)[0].files.length+' files';
      }
      else {
        fileName = fileName.substring(fileName.lastIndexOf('\\') + 1, fileName.length);
      }

      // Don't try to show the name if there is none
      if (!fileName) {
        return;
      }

      var selectedFileNamePlacement = $(this).data('filename-placement');
      if (selectedFileNamePlacement === 'inside') {
        // Print the fileName inside
        $(this).siblings('span').html(fileName);
        $(this).attr('title', fileName);
      } else {
        // Print the fileName aside (right after the the button)
        $(this).parent().after('<span>'+fileName+'</span>');
      }
    });

  });

};

// Add the styles before the first stylesheet
// This ensures they can be easily overridden with developer styles
var cssHtml = '<style>'+
  '.file-input-wrapper { overflow: hidden; position: relative; cursor: pointer; z-index: 1; }'+
  '.file-input-wrapper input[type=file], .file-input-wrapper input[type=file]:focus, .file-input-wrapper input[type=file]:hover { position: absolute; top: 0; left: 0; cursor: pointer; opacity: 0; filter: alpha(opacity=0); z-index: 99; outline: 0; }'+
  '.file-input-name { margin-left: 8px; }'+
  '</style>';
$('link[rel=stylesheet]').eq(0).before(cssHtml);

})(jQuery);

Thanks to Gregory Pike for his good work. The jQuery plugin is distributed under Apache License.

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>

Quick-n-Dirty: Use “isDocEditable” in an old school Java Agent

14. März 2014 Posted by Sven Hasselbach

If you want to check if a document is editable, you can do this in an old school Java agent with the NAPI function isDocEditable provided by the XSPNative class.

First you have to add the required JARs to your agent. Then, you have to call XSPNative.isDocEditable with the document you want to test:

import lotus.domino.AgentBase;
import lotus.domino.Database;
import lotus.domino.Document;
import lotus.domino.Session;

import com.ibm.domino.napi.c.xsp.XSPNative;

public class JavaAgent extends AgentBase {

    public void NotesMain() {

      try {
          Session session = getSession();
          Database db = session.getCurrentDatabase();
          Document doc = db.getAllDocuments().getFirstDocument();
          System.out.println( "Is Editable: " + XSPNative.isDocEditable( doc ) );
      } catch(Exception e) {
          e.printStackTrace();
       }
   }
}

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

XPages: Set a Theme for a single XPage

14. März 2014 Posted by Sven Hasselbach

… or how you can use your own FacesContext implementation.

What we need first is our own FacesContext implementation with new methods to set the StlyeKitId (which is the name of the Theme) for initializing the StyleKit instance:

package ch.hasselba.xpages;

import javax.faces.context.FacesContext;
import com.ibm.xsp.application.ApplicationExImpl;
import com.ibm.xsp.context.FacesContextExImpl;
import com.ibm.xsp.stylekit.StyleKit;

/**
 * ThemeSwitcherFacesContext
 * allows to switch the theme during runtime
 * 
 * @author Sven Hasselbach
 * @version 0.1
 */
public class ThemeSwitcherFacesContext extends FacesContextExImpl {

    private StyleKit styleKit;
    private String styleKitId;
    private FacesContext delegated;
    /**
     * constructor
     * 
     * @param fc
     *     delegated javax.faces.context.FacesContext
     */
    public ThemeSwitcherFacesContext(FacesContext fc) {
        super(fc);
        this.delegated = fc;
    }

    /**
     * returns current StyleKit
     * 
     * @return com.ibm.xsp.stylekit.StyleKit
     *      the StyleKit used for rendering the view
     */
    public StyleKit getStyleKit() {
        if (this.styleKit == null) {
            this.styleKit = super.getStyleKit();
        }
        return this.styleKit;
    }

    /**
     * sets the StyleKit
     * 
     * @param stlyeKit
     *     the com.ibm.xsp.stylekit.StyleKit to use 
     */
    public void setStyleKit(final StyleKit styleKit) {
        this.styleKit = styleKit;
    }

    /**
     * returns the current StyleKitId (aka Theme name)
     * 
     * @return String
     *     the id of the StyleKit
     */
    @Override
    public String getStyleKitId() {
        if (this.styleKitId == null) {
            this.styleKitId = super.getStyleKitId();
        }
        return styleKitId;
    }

    /**
     * sets the StyleKitId
     * 
     * @param styleKitId
     *     the id of the StyleKit
     */
    public void setStyleKitId(final String styleKitId) {
        this.styleKitId = styleKitId;
    }

    /**
     * initializes the StyleKit for the current view
     */
    public void loadStyleKit() {
        this.styleKit = ((ApplicationExImpl) getApplication())
                .getStyleKit(this.styleKitId);
    }
}

But you cannot register the FacesContext implementation directly. That’s why we have to build our own FacesContextFactory to inject our FacesContext instance this:

package ch.hasselba.xpages;

import javax.faces.FacesException;
import javax.faces.context.FacesContext;
import javax.faces.context.FacesContextFactory;
import javax.faces.lifecycle.Lifecycle;
import com.ibm.xsp.FacesExceptionEx;
import com.ibm.xsp.context.FacesContextFactoryImpl;

/**
 * ThemeSwitcherFacesContextFactory
 * 
 * @author Sven Hasselbach
 * @version 0.1
 */
public class ThemeSwitcherFacesContextFactory extends FacesContextFactory {

    private FacesContextFactory delegate;

    @SuppressWarnings("unchecked")
    public ThemeSwitcherFacesContextFactory(){
        try{
            Class clazz = Class.forName( "com.sun.faces.context.FacesContextFactoryImpl" );
            this.delegate = (FacesContextFactory) clazz.newInstance();
        }
        catch (Exception e){
          throw new FacesExceptionEx(e);
        }
    }

    public ThemeSwitcherFacesContextFactory(FacesContextFactory fcFactory){
        this.delegate = fcFactory;
        if ((this.delegate instanceof FacesContextFactoryImpl)) {
            this.delegate = ((FacesContextFactoryImpl)this.delegate).getDelegate();
        }
    }

    public FacesContext getFacesContext(Object param1, Object param2, 
        Object param3, Lifecycle paramLC) throws FacesException {
        FacesContext fc = this.delegate.getFacesContext(param1, param2, param3, paramLC);
        return new ThemeSwitcherFacesContext(fc);
    }
}

Now, you have to add the FacesContextFactory to the faces-config.xml to activate it:

<?xml version="1.0" encoding="UTF-8"?>
<faces-config>
  <factory>
    <faces-context-factory>
        ch.hasselba.xpages.ThemeSwitcherFacesContextFactory
    </faces-context-factory>
  </factory>
</faces-config>

If you open the XPage, an error will occur:

This happens because there are some java security restrictions with the class loader of the delegated classes. You have to modify the java security in the java.policy file (you can limit the the grant to specific database if you want).

grant {
    permission java.security.AllPermission;
};

Then you can add a Theme to your XPage, f.e. this one:

<theme
    extends="webstandard" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:noNamespaceSchemaLocation="platform:/plugin/com.ibm.designer.domino.stylekits/schema/stylekit.xsd" >
    <control>
        <name>ViewRoot</name>
        <property>
            <name>style</name>
            <value>background-color:rgb(255,255,0)</value>
        </property>
    </control>
</theme>

To use the ThemeSwitcher, you have to add some code in the beforePageLoad event:

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

    <xp:this.beforePageLoad>
        <![CDATA[#{javascript:
            importPackage( ch.hasselba.xpages );
            var fc:ch.hasselba.xpages.ThemeSwitcherFacesContext = facesContext;
            fc.setStyleKitId( "ThemeA");
            fc.loadStyleKit();}
        ]]>
    </xp:this.beforePageLoad>

    Switched Theme

</xp:view>

When the XPage is opened in the browser, the Theme is changed:

All other pages in the NSF are stil using the default Theme:

Quick-n-Dirty: A Hotfix for CKEditor 4

10. März 2014 Posted by Sven Hasselbach

Russell Maher wrote a very interesting article about using CKEditor 4 in XPages, but the current solution requires to change to HTML files directly on the server.

But with this little Hotfix you can use CKEditor form a NSF an don’t need to change the HTML files on the domino server:

1. Switch to package explorer perspective
2. Open the file ckeditor.js

3. Search for the variable timestamp…

4. …and remove the 4 characters

5. Save it. That’s it!

Now, CKEditor 4 can be loaded directly from the NSF.

XPages: Use async / defer option for external CSJS Script Libraries

10. März 2014 Posted by Sven Hasselbach

When adding CSJS libraries to your XPage, try to use the defer or the async option for a better user experience. When not using this options, the Page might be blocked during page load.

Have a look at this example XPage which contains two external CSJS scripts (for demonstration purposes they are computed to get a remote script out of nowhere):

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
    Foo!
    <xp:this.resources>
        <xp:script clientSide="true">
            <xp:this.src>
               <![CDATA[#{javascript:"http://" + @Unique() + ".null";}]]>
            </xp:this.src>
        </xp:script>
    </xp:this.resources>
    <xp:scriptBlock id="scriptBlock1">
        <xp:this.src>
           <![CDATA[#{javascript:"http://" + @Unique() + ".null";}]]>
        </xp:this.src>
    </xp:scriptBlock>
    Bar!
</xp:view>

When opening the XPage, the DOM is blocked, until the operation times out:

The best you can do is to use the async or the defer option of external CSJS scripts. For script blocks, there is an option in the DDE available:

The async option can be set with an attribute:

<xp:this.attrs>
    <xp:attr name="async" value="async" minimized="true" />
</xp:this.attrs>

To use the option for a resource, you must add them as an attribute for both options:

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
    Foo!
    <xp:this.resources>
        <xp:script clientSide="true">
            <xp:this.src>
               <![CDATA[#{javascript:"http://" + @Unique() + ".null";}]]>
            </xp:this.src>
            <xp:this.attrs>
                <xp:attr name="async" value="async" minimized="true" />
            </xp:this.attrs>
        </xp:script>
    </xp:this.resources>
    <xp:scriptBlock id="scriptBlock1" defer="true">
        <xp:this.src>
           <![CDATA[#{javascript:"http://" + @Unique() + ".null";}]]>
        </xp:this.src>
    </xp:scriptBlock>
    Bar!
</xp:view>

There are some other techniques, but this is the simplest way and supported in most browsers:

XPages: Add an attribute to the BODY-Element

9. März 2014 Posted by Sven Hasselbach

Today I wanted to add an attribute to the <BODY> element of my XPage. My goal was to generate HTML code like this:

<body role="document">

After some testing I found a solution by overwriting the method encodeHtmlBodyStart. To do this, you have to extend the class ViewRootRendererEx2:

package ch.hasselba.xpages;

import java.io.IOException;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import com.ibm.xsp.component.UIViewRootEx;
import com.ibm.xsp.renderkit.html_basic.ViewRootRendererEx2;

public class MyViewRootRenderer extends ViewRootRendererEx2 {

    @Override
    protected void encodeHtmlBodyStart(FacesContext fc, UIViewRootEx uiRoot,
            ResponseWriter writer) throws IOException {

        writer.startElement("body", uiRoot);
        writer.writeAttribute("role", "document", "role");
        writeln(writer);
    }

}

To activate it you have to change the renderer class in the faces-config.xml:

<faces-config>  
  <render-kit>
    <renderer>
      <component-family>javax.faces.ViewRoot</component-family>
      <renderer-type>com.ibm.xsp.ViewRootEx</renderer-type>
      <renderer-class>ch.hasselba.xpages.MyViewRootRenderer</renderer-class>
    </renderer>
  </render-kit>
</faces-config>

Now, the attribute is added correctly to my HTML code:

XPages: Use a Method Binding as Converter

8. März 2014 Posted by Sven Hasselbach

I accidentally found a way to add a method binding as a converter to a component, because I have added a managed bean as a converter directly in the source code. The DDE does not support this.

If you go to the converter property of a component, you can only add one of the predefined converters:

But you can go to the source and add a method binding to the option, in this case my bean which implements my converter functionality.

<xp:inputText
    id="inputText1"
    value="#{sessionScope.inputText1}"
    converter="#{myConverterBean}" />

If you now reopen the saved XPage, the converter property is filled in, but cannot edited / changed anymore.

You must remove the property in the source code to get the old behaviour back.

Tested in 8.5.2, 8.5.3 & ND 9

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.

XPages: SSJS & How you can have fun at the office

4. März 2014 Posted by Sven Hasselbach

This article is a demonstration of what harmful things you can do when using the SSJS & prototyping wrong( decribed here by the great Mark Roden). Don’t do that! Especially not on a productive server!
 

Wanna have a lot of fun in the office with the other developers? Just overwrite some global SSJS functionality! They will never find out what happend to their applications!

 

This is the application we destroy

Our demonstration application uses a small Java class which has only a single method:

package ch.hasselba.xpages;

public class Demo {

    public String foo() {
       return "bar";
    }
 }

This class is used for a label on our XPage and is loaded with the importPackage functionality of SSJS:

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

    <xp:label
        id="labelDemo">
        <xp:this.value>    
            <![CDATA[#{javascript:
                importPackage( ch.hasselba.xpages );
                return new ch.hasselba.xpages.Demo().foo();}]]>
        </xp:this.value>
    </xp:label>

</xp:view>

When opening the XPage, nothing special happens, the label is filled with the value “bar”:

Now let’s destroy it!

Add a button on the XPage, with containing a single line of SSJS code…

<xp:button
        value="Click Me"
        id="button1">
        <xp:eventHandler
            event="onclick"
            submit="true"
            refreshMode="complete">
            <xp:this.action>
                <![CDATA[#{javascript:prototype.importPackage = null;}]]>
            </xp:this.action>
        </xp:eventHandler>
    </xp:button>

… and reopen the XPage:

Question: What will happen if you click the button?

Answer:

Exactly: The method is destroyed on the whole server! Every XPage which imports a Java package with SSJS will not work anymore. Only a complete server restart helps now. The error message you will receive won’t help you, because it looks like the Java classes could not be found. Now “help” the other developers with tips like “Maybe a build problem? Have you tried to clean the application?”.

P.S. You can execute the code on other ways, f.e, with an eval statement, or a method binding of your choice.

Tested on ND 8.5.3 & ND 9.0

XPages: Events, ActionListeners & Parameters

2. März 2014 Posted by Sven Hasselbach

To access the event parameters of an event within your actionListener, you have to access the source object of your actionEvent object:

package ch.hasselba.xpages;

import java.util.List;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.ActionEvent;
import com.ibm.xsp.complex.Parameter;
import com.ibm.xsp.component.xp.XspEventHandler;

public class MyActionListener implements javax.faces.event.ActionListener {

    public void processAction(ActionEvent event)
            throws AbortProcessingException {
        XspEventHandler eventHandler = (XspEventHandler) event.getSource();
        List<Parameter> params = eventHandler.getParameters();
        for (Parameter p : params) {
            System.out.println(p.getName() + " -> " + p.getValue());
        }
    }
}

Here is an example XPage:

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

    <xp:button
        value="Label"
            id="buttonAction">
        <xp:eventHandler
            event="onclick"
            submit="true"
            immediate="true" refreshMode="norefresh">
            <xp:this.parameters>
                <xp:parameter
                    name="param"
                    value="#{javascript:java.lang.System.currentTimeMillis()}">
                </xp:parameter>
            </xp:this.parameters>

                <xp:this.actionListeners>
                    <xp:actionListener type="ch.hasselba.xpages.MyActionListener" />
                </xp:this.actionListeners>
            </xp:eventHandler>

    </xp:button>

</xp:view>

You can see the result on the console when the button is clicked:

When doing the same for an action, you have to access the UIComponent from the actionEvent:

package ch.hasselba.xpages;
import java.util.List;
import javax.faces.event.ActionEvent;
import com.ibm.xsp.complex.Parameter;
import com.ibm.xsp.component.xp.XspEventHandler;

public class MyActionListener {
public void execute(ActionEvent event) {
        XspEventHandler eventHandler = (XspEventHandler) event.getComponent();
        List<Parameter> params = eventHandler.getParameters();
        for (Parameter p : params) {
            System.out.println(p.getName() + " -> " + p.getValue());
        }
     }
}

Here is an example XPage, the method execute is contained by the managed bean myAction:

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

    <xp:button
        value="Label"
            id="buttonAction">
        <xp:eventHandler
            event="onclick"
            submit="true"
            immediate="true"
            refreshMode="complete"
            actionListener="#{myAction.execute}">
            <xp:this.parameters>
                <xp:parameter
                    name="param"
                    value="#{javascript:java.lang.System.currentTimeMillis()}">
                </xp:parameter>
            </xp:this.parameters>
        
        </xp:eventHandler>
            
    </xp:button>
    
</xp:view>

Quick-n-Dirty: Disable Validation for FileDownload control (2)

19. Februar 2014 Posted by Sven Hasselbach

Because my old snippet does not work anymore for ND9 (the IBM changed the internal methods / objects) I had to create a new way to disable the validation of the FileDownload control. Now I have switched to a PhaseListener which waits for to any XspEventHandler and checks if this event handler is a child of the FileDownload control. If so, it skips the validation.

Here is the code for the PhaseListener:

package ch.hasselba.xpages;

import java.util.Iterator;
import java.util.Map;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.event.PhaseEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.PhaseListener;

import com.ibm.xsp.component.UIViewRootEx;
import com.ibm.xsp.component.xp.XspEventHandler;
import com.ibm.xsp.component.xp.XspFileDownload;
import com.ibm.xsp.context.FacesContextExImpl;
import com.ibm.xsp.util.FacesUtil;

public class FileDownloadPhaseListener implements PhaseListener {

    /**
     * FileDownloadPhaseListener
     * 
     * by Sven Hasselbach, 19.12.2014
     */
    private static final long serialVersionUID = 1L;

    public void afterPhase(PhaseEvent arg0) {}

    public void beforePhase(PhaseEvent arg0) {
        try{
            FacesContextExImpl fc = (FacesContextExImpl) FacesContext.getCurrentInstance();

            // get client id
            String clientId = FacesUtil.getHiddenFieldValue(fc); 

            // extract last id 
            String[] ids = clientId.split(":");
            String id = ids[ ids.length  - 1 ];

            boolean found = false;

            // search for the component
            UIViewRootEx uiRoot = (UIViewRootEx) fc.getViewRoot();
            UIComponent cmp = FacesUtil.getComponentFor(uiRoot,  id );

            if( cmp instanceof XspEventHandler ){

                while( cmp != null || found == false ){

                    // found the download control?
                    if( cmp instanceof XspFileDownload ){
                        // disable validation & quit the process
                        fc.setDisableValidators( true );
                        found = true;
                    }
                    // climb up the component tree    
                    cmp = cmp.getParent();
                }
            }

        }catch(Exception e){}
    }

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

When using it on a XPage…

… you can now delete a File …

… but cannot submit a form with the required field:

This is the required faces-config.xml:

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

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

Quick-n-Dirty: Use “Old-School” Javacode in your XPages-Application

13. Februar 2014 Posted by Sven Hasselbach

Because I often have read that it is not possible to access “Old School” Java elements in XPages, but never understood the reason why, I have written my own class loader for fun to demonstrate how to do this:

1. I have created a simple Java library:

2. The library contains a single class “LoadedClass“:

The class is very simple. When it is instantiated, it just prompts the current timestamp to the server console:

package ch.hasselba.test;

public class LoadedClass { 

    public LoadedClass(){
        System.out.println("Time: " + System.currentTimeMillis() );
    }
}

3. If you inspecting the Java design element, you will notice a attachment with the name %%object.jar%%. That’s the compiled library:

And here is the UNID of the design element for accessing it

4. Now it is time for the class loader (the modified code originally came from Laurentiu Cristofor):

package ch.hasselba.demo;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Hashtable;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;

/**
 * 
 * This class implements a simple class loader that can be used to load at
 * runtime classes contained in a JAR file.
 * 
 * (P)2000 Laurentiu Cristofor
 * 
 * Modified 2014 by Sven Hasselbach
 * 
 */
public class JarClassLoader extends ClassLoader {
    private Hashtable jarContents;

    /**
     * Creates a new JarClassLoader that will allow the loading of classes
     * stored in a jar file.
     * 
     * @param bos
     *            ByteArrayOutputStream containing a Jar
     * @exception IOException
     *                an error happened while reading the contents of the jar
     *                file
     */
    public JarClassLoader(ByteArrayOutputStream bos) throws IOException {

        // second get contents of the jar file and place each
        // entry in a hashtable for later use
        jarContents = new Hashtable();

        JarInputStream jis = new JarInputStream(new ByteArrayInputStream(bos
                .toByteArray()));

        JarEntry je;
        while ((je = jis.getNextJarEntry()) != null) {
            // make sure we have only slashes, we prefer unix, not windows
            String name = je.getName().replace('\\', '/');

            // get entry size from the entry or from our hashtable
            int size = bos.size();
            // read the entry
            byte[] ba = new byte[size];
            int bytes_read = 0;
            while (bytes_read != size) {
                int r = jis.read(ba, bytes_read, size - bytes_read);
                if (r < 0)
                    break;
                bytes_read += r;
            }

            jarContents.put(name, ba);
        }

        jis.close();
    }

    /**
     * Looks among the contents of the jar file (cached in memory) and tries to
     * find and define a class, given its name.
     * 
     * @param className
     *            the name of the class
     * @return a Class object representing our class
     * @exception ClassNotFoundException
     *                the jar file did not contain a class named
     *                <code>className</code>
     */
    public Class findClass(String className) throws ClassNotFoundException {
        // transform the name to a path
        String classPath = className.replace('.', '/') + ".class";

        byte[] classBytes = (byte[]) jarContents.get(classPath);

        if (classBytes == null)
            throw new ClassNotFoundException();

        return defineClass(className, classBytes, 0, classBytes.length);
    }
}

The trick is to ignore the size of the JarEntries and use the size of the whole ByteArrayOutputStream.

5. Now we need to load the design element, extract the Jar and load our test class:

package ch.hasselba.demo;

import ch.hasselba.demo.JarClassLoader;

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import lotus.domino.Database;
import lotus.domino.Document;
import lotus.domino.EmbeddedObject;
import lotus.domino.Session;

public class JavaLoader {

    public JavaLoader(Session session) {
        try {

            Database db = session.getCurrentDatabase();

            // get the design document
            Document doc = db
                    .getDocumentByUNID("6783F550459C81B1C1257C7E007A5D44");

            // get the attachment
            EmbeddedObject embObj = doc.getAttachment("%%object%%.jar");
            FileInputStream fis = (FileInputStream) embObj.getInputStream();

            // convert the FileInputStream to a ByteArrayOutputStream
            ByteArrayOutputStream bos = new ByteArrayOutputStream();

            int data;
            while ((data = fis.read()) != (-1)) {
                bos.write(data);
            }
            bos.flush();

            // load the Jar with the class loader
            JarClassLoader jcl = new JarClassLoader(bos);

            // get the class
            Class loadedClass = jcl.findClass("ch.hasselba.test.LoadedClass");

            // and instantiate it
            Object obj = loadedClass.newInstance();

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

6. Last but not least, a XPage for demonstrating how to use the code:

<?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="complete">
            <xp:this.action>
                <![CDATA[#{javascript:
                    importPackage( ch.hasselba.demo );
                    new ch.hasselba.demo.JavaLoader(session);}]]>
            </xp:this.action>
        </xp:eventHandler>
    </xp:button>

</xp:view>

7. If you open the XPage and click the button…

… you can see the result on the server console:

If you want to access the methods and/or properties of the loaded object, you have do do this with reflection.

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

XPages: Modify the File Selection Button

24. Januar 2014 Posted by Sven Hasselbach

With Dojo, you can easily customize the file upload button, f.e. to change the label, add additional style sheets or use HTML5 or Flash plugin for uploading.

Here is basic example which adds a label and a blue border around the button:

<?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="File" />
    </xp:this.data>

    <xp:scriptBlock id="scriptBlockFileUpload">
        <xp:this.value>
            <![CDATA[
                require(['dojo/parser',
                         'dojox/form/Uploader',
                         'dojo/domReady!'], 
                         function(parser, ready){
                             parser.parse().then(function(){
                                uploader = new dojox.form.Uploader({
                                    name:'#{id:fileUpload1}', 
                                    label:'Select Some Files',
                                    multiple:false, 
                                    style: 'border:5px solid blue;'
                                }, '#{id:fileUpload1}');

                                uploader.startup();
                             });
                         });
            ]]>
        </xp:this.value>
    </xp:scriptBlock>

    <xp:fileUpload id="fileUpload1"
        value="#{document1.Body}">
    </xp:fileUpload>

    <xp:button value="Submit" id="buttonSubmit" >
        <xp:eventHandler event="onclick" submit="true"
            refreshMode="complete" immediate="false" save="true" />
    </xp:button>

</xp:view>

Have a look at dojox.form.Uploader for more details.