XPages: Create a Database without Template

On stackoverflow.com, an interessting topic was asked about how to create a notes database programmatically without using a template. The problem is, that it will not contain a Icon document. But in this document are all database properties stored. So the question is: How can you create this document?

Jesse Gallagher came up with the idea to use the DXL import and create the Icon document this way, which works fine. But the next problem is, that there is no ACL note in the database and no default view.

That’s why I modified his idea and created a Java Utility class. This class creates a new database which includes all of the required design elements.

package ch.hasselba.core;

import lotus.domino.ACL;
import lotus.domino.Base;
import lotus.domino.Database;
import lotus.domino.DbDirectory;
import lotus.domino.DxlImporter;
import lotus.domino.Session;

/**
 * DB Utilities
 * 
 * @author Sven Hasselbach
 * @version 1.1
 */
public class DBUtil {

    /**
     * creates a new database 
     * the database is identically to a database created by the -blank- template in designer.
     * The default access is set to Manager
     *  
     * @param session
     *             the session used to create the database
     * @param dbTitle
     *             the database title
     * @param dbPath
     *             the path of the database
     * @param dbServer
     *             the server 
     * 
     * @version 1.1
     */
    public static void createDatabase( Session session,  final String dbTitle, final String dbPath, final String dbServer ) {
        DbDirectory dbDir = null;
        Database db = null;
        DxlImporter importer = null;
        ACL acl = null;

        try{
            // create a new database
            dbDir = session.getDbDirectory( dbServer );
            db = dbDir.createDatabase( dbPath );

            // initialize dxl importer
            importer = session.createDxlImporter();
            importer.setDesignImportOption( DxlImporter.DXLIMPORTOPTION_REPLACE_ELSE_CREATE );

            // generate DXL
            String dxl = generateDXL( dbTitle, dbPath, db.getReplicaID() );

            // import DXL
            importer.importDxl(dxl, db);

            // set ACL: Default to Manager
            acl = db.getACL();
            acl.getFirstEntry().setLevel(ACL.LEVEL_MANAGER);
            acl.save();

        }catch(Exception e){
            e.printStackTrace();
        }finally{
            recycleObj( acl );
            recycleObj( importer );
            recycleObj( db );
            recycleObj( dbDir );
        }

    }

    /**
     * generates the DXL for a blank database
     * 
     * @param dbTitle
     *            the title of the database
     * @param dbPath
     *             the path of the database
     * @param dbReplicaId
     *             the replica of the database
     * @return    String with DXL
     * 
     * @version 1.1
     *          
     */
    private static String generateDXL( final String dbTitle, final String dbPath , final String dbReplicaId ){

        StringBuilder str = new StringBuilder();

        str.append("<?xml version='1.0' encoding='utf-8'?>");
        str.append("<!DOCTYPE database SYSTEM 'xmlschemas/domino_8_5_3.dtd'>");
        str.append("<database xmlns='http://www.lotus.com/dxl' version='8.5' maintenanceversion='3.0' ");
        str.append("replicaid='");
        str.append( dbReplicaId );
        str.append("' path='");
        str.append( dbPath );
        str.append("' title='");
        str.append( dbTitle );
        str.append("' allowstoredforms='false' ");
        str.append("usejavascriptinpages='false' increasemaxfields='true' showinopendialog='false'>");
        str.append("<databaseinfo dbid='");
        str.append( dbReplicaId );
        str.append( "' odsversion='51' ");
        str.append("numberofdocuments='0'></databaseinfo>");
        str.append("<note default='true' class='icon'>");
        str.append("<noteinfo noteid='11e'>");
        str.append("</noteinfo>");
        str.append("<item name='IconBitmap' summary='true'>");
        str.append("<rawitemdata type='6'>");
        str.append("AiAgAQAA///////wD///gAH//gAAf/wAAD/4AAAf8AAAD+AAAAfgAAAHwAAAA8AAAAPAAAADgAAA");
        str.append("AYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAHAAAADwAAAA8AAAAPgAAAH4AAAB/AAAA/4AAAf");
        str.append("/AAAP/4AAH//gAH///AP//////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAiIiIiAAAAAAAAAAAAAAI");
        str.append("jPZmZm/IgAAAAAAAAAAIjGZmZmZmZsiAAAAAAAAAjGZmZmZmZmZmyAAAAAAACMZmZmZmZmZmZmyA");
        str.append("AAAAAIxmZmZmZmZmZmZmyAAAAAjGZmZmZmZmZmZmZmyAAAAIZmbyL2byL2byL2ZmgAAAjGZmIiJm");
        str.append("IiJmIiJmZsgAAIZmZiIiZiIiZiIiZmZoAADGZmYiImYiImYiImZmbAAI9mZmIiJmIiJmIiJmZm+A");
        str.append("CGZmZiIiZiIiZiIiZmZmgAhmZmYiImYiImYiImZmZoAIZmZmIiJmIiJmIiJmZmaACGZvIiIiZiIi");
        str.append("ZiIiIvZmgAhmYiIiImYiImYiIiImZoAIZm8iIi9m8i9m8iIi9maACPZmZmZmZmZmZmZmZmZvgADG");
        str.append("ZmbyL2byL2byL2ZmbAAAj2ZmIiJmIiJmIiJmZvgAAIxmZiIiZiIiZiIiZmbIAAAI9mbyL2byL2by");
        str.append("L2ZvgAAACMZmZmZmZmZmZmZmbIAAAACMZmZmZmZmZmZmZsgAAAAACMZmZmZmZmZmZmyAAAAAAACM");
        str.append("9mZmZmZmZm/IAAAAAAAACIz2ZmZmZm/IgAAAAAAAAAAIiMZmZmyIgAAAAAAAAAAAAACIiIiIAAAA");
        str.append("AAAAUEECICABAAD/////+A4DgA==");
        str.append("</rawitemdata></item>");
        str.append("<item name='$Daos'><text>0</text></item>");
        str.append("<item name='$TITLE'><text>");
        str.append( dbTitle );
        str.append("</text></item>");
        str.append("<item name='$Flags'><text>7f</text></item>");
        str.append("<item name='$FlagsNoRefresh'><text/></item></note>");
        str.append("<view xmlns='http://www.lotus.com/dxl' version='8.5' maintenanceversion='3.0' ");
        str.append("replicaid='");
        str.append( dbReplicaId );
        str.append("' showinmenu='true' publicaccess='false' default='true' noviewformat='true'>");
        str.append("<noteinfo noteid='11a' sequence='1'></noteinfo>");
        str.append("<code event='selection'><formula>SELECT @All</formula></code>");
        str.append("<item name='$FormulaClass'><text>1</text></item></view>");
        str.append("</database>");

        return str.toString();

    }

    /**
     * recycles notes objects
     * 
     * @param obj
     *             the notes object to recycle
     */
    private static void recycleObj( Base obj ){
        try{
            if( obj != null )
                obj.recycle();
        }catch( Exception e ){}
    }
}

Here is an example XPage how to use the class:

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

    DB Path:<xp:inputText id="inputTextDBPath" value="#{viewScope.dbPath}" /><xp:br />
    DB Title:<xp:inputText id="inputText1" value="#{viewScope.dbTitle}" /><xp:br />

    <xp:button value="Create DB" id="buttonCreateDB">
        <xp:eventHandler event="onclick" submit="true" refreshMode="complete">
            <xp:this.action>
                <![CDATA[#{javascript:
                    importPackage( ch.hasselba.core );
                    var dbUtil = new ch.hasselba.core.DBUtil();
                    dbUtil.createDatabase(session, viewScope.dbTitle, viewScope.dbPath, "");
                }]]>
            </xp:this.action>
        </xp:eventHandler>
    </xp:button>

</xp:view>

You can enter the database Title and the database path. After clicking the button „Create DB“, the database is created on the server (assuming you have the right to do that).

Veröffentlicht unter Java, XPages | Verschlagwortet mit , , , , , | 2 Kommentare

You might not need jQuery

I have found a very interesting website: You might not need jQuery. It contains a lot of usefull solutions for the different IE versions.

Veröffentlicht unter Java Script, jQuery | Verschlagwortet mit , , | Schreib einen Kommentar

Problems with Handles: When the same document is not the same

Disclaimer: This will work in Java, SSJS and Lotus Script.

When opening the same document from the same database in different instances, and then recycle one of them, the other documents will be recycled too, because the handle to the underlying C object are the same.

This SSJS example…

<?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:
                var dbCur:NotesDatabase = session.getCurrentDatabase();
                var dbOther:NotesDatabase =  session.getCurrentDatabase();

                var docCur:NotesDocument = dbCur.getDocumentByUNID( "E5CA138B7F5A21E5C1257C190068DBA9" );
                var docOther:NotesDocument = dbOther.getDocumentByUNID( "E5CA138B7F5A21E5C1257C190068DBA9" );

                docCur.recycle();
                return docOther.getUniversalID();
            }]]>
        </xp:this.value>
    </xp:label>

</xp:view>

… fails, because docOther is recycled too.

But if you open the database dbOther after initializing the database object, the handles are not the same. Then, the recycling of the document won’t affect the other instance of the same object:

<?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:
                var dbCur:NotesDatabase = session.getCurrentDatabase();
                var dbOther:NotesDatabase = session.getDatabase( "", "" );
                dbOther.openByReplicaID( dbCur.getServer(), dbCur.getReplicaID() );

                var docCur:NotesDocument = dbCur.getDocumentByUNID( "E5CA138B7F5A21E5C1257C190068DBA9" );
                var docOther:NotesDocument = dbOther.getDocumentByUNID( "E5CA138B7F5A21E5C1257C190068DBA9" );

                docCur.recycle();
                return docOther.getUniversalID();
            }]]>
        </xp:this.value>
    </xp:label>

</xp:view>

In Lotus Script, you can fall into the trap when doing something like this:

Dim session As New NotesSession
Dim dbCur As NotesDatabase
Dim dcCur As NotesDocumentCollection
Dim dbOther As NotesDatabase
Dim docOther As NotesDocument

' open current database & create a document collection
Set dbCur = session.Currentdatabase
Set dcCur = dbCur.CreatedocumentCollection()

' open the current database again  
Set dbOther = New NotesDatabase( "","" )
dbOther.Open dbCur.Server, dbCur.Filepath

' get a document...
Set docOther = dbOther.Alldocuments.Getfirstdocument()

' ... and add it to the collection
dcCur.Adddocument docOther

This will result in a „Document is from a different database“ error:

Veröffentlicht unter Java, Java Script, Lotus Script | Verschlagwortet mit , , , | Schreib einen Kommentar

XPages: Bootstrap File Input

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.

Veröffentlicht unter Java Script, Web, XPages | Verschlagwortet mit , , , , , , , , | 2 Kommentare

XPages: A Bootstrap Skin for CKEditor

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>

EDIT:
Oliver Busse found a problem when using the style in an XPiNC application and added a hotfix for the issue on his blog: http://mardou.dyndns.org/hp.nsf/blogpost.xsp?documentId=BC2

Veröffentlicht unter Java Script, Web, XSP | Verschlagwortet mit , , , | 4 Kommentare

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

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.

Veröffentlicht unter Agenten, Java | Verschlagwortet mit , , , , | Schreib einen Kommentar

XPages: Set a Theme for a single XPage

… 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:

Veröffentlicht unter Java, JSF, Web, XPages | Verschlagwortet mit , , , | 3 Kommentare

Quick-n-Dirty: A Hotfix for CKEditor 4

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.

Veröffentlicht unter Java Script, XPages | Verschlagwortet mit , , , | 3 Kommentare

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

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:

Veröffentlicht unter HTML5, Java Script, Performance, Web, XPages | Verschlagwortet mit , , , , , , , , , | Ein Kommentar

XPages: Add an attribute to the BODY-Element

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:

Veröffentlicht unter Java, JSF, XPages | Verschlagwortet mit , , , , , | Schreib einen Kommentar