Posts Tagged: ‘8.5.3’

XPages: Add inline CSJS with a Theme

18. April 2013 Posted by Sven Hasselbach

I needed a way to add some inline CSJS to an existing application which affects any XPage, that’s why I did this them with a theme.

Et voilà:

<theme extends="webstandard" >

    <resources mode="concat">
        <script>
            <contents>alert('THEME JS!')</contents>
            <clientSide>true</clientSide>
        </script>
    </resources>

</theme>

The javascript code is added and executed as expected:

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.

In Ya Facet!

20. Februar 2013 Posted by Sven Hasselbach

Using Facets in XPages is very nice, because it gives you an easy way for a global design of your application (and much more). It can be used in all XPages and their custom controls. The xp:callback element defines a section, and this section can be filled in by the parent component with the xp:key attribute.

Take a look at this custom control:

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core" style="margin: 16px;">
   <xp:text escape="true" id="computedFieldTitle"
        value="#{javascript:compositeData.title}" tagName="h1" />
   <xp:callback facetName="TabAreaCallback" id="callbackTabArea"
        xp:key="TabAreaCallback" />
</xp:view>

It has a single callback area (I will never understand why it is called “Editable Area” in the DDE), and a xp:text which is filled in with the value of the property title of the custom control.

This CustomControl (ccFacet) can now be used in another custom control (ccMain) by using the xp:key attribute:

<?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:ccFacet title="Hello World!" >
        <xp:this.facets>
            <xp:div xp:key="TabAreaCallback">
                <p>Hello World!</p>
            </xp:div>
        </xp:this.facets>        
    </xc:ccFacet>

</xp:view>

The <xp:div> element calls the facet of the embedded custom control back (with the xp:key as the phone number), and the content of the calling component will be inserted in the defined placeholder. Added to a XPage the result looks like this:

If you change now the design in the ccFacet, the design will be changed on every XPage automatically. Or you can add new buttons, footers, etc. which will be automatically used in all other XPages which are using the custom control.

OK? OK!

But  what will happen if we are using the compositeData object?

Let’s add the property “foo” to the ccMain custom control:

<?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:ccFacet title="Hello World!" >
        <xp:this.facets>
            <xp:div xp:key="TabAreaCallback">
                <p>Hello World!</p>
                <xp:text escape="true" id="computedFieldFoo"
                                        value="#{compositeData.foo}" />
            </xp:div>
        </xp:this.facets>        
    </xc:ccFacet>

</xp:view>

The custom control definition in the XPage is changed to this…

<xc:ccMain foo="bar"></xc:ccMain>

… but in the browser you will see nothing:

If you remove the ccFacet definition from ccMain, you will see that the compositeData works as expected:

<?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:ccFacet title="Hello World!" >-->
<!--        <xp:this.facets>-->
            <xp:div xp:key="TabAreaCallback">
                <p>Hello World!</p>
                <xp:text escape="true" id="computedFieldFoo"
                                       value="#{compositeData.foo}" />
            </xp:div>
<!--        </xp:this.facets>-->        
<!--    </xc:ccFacet>-->

</xp:view>

What happened to the compositeData object? Is it broken? No, it is still working correctly and refers to the compositeData of the current custom control – but the custom control is no longer the same. It was moved to another node in the component tree, and that’s why the compositeData cannot be found anymore (see here for some details).

If you print the client id of the component and the variable in the beforePageLoad event to the console, you will see the id of the custom control before it is moved:

But if you take a look in the generated source of the XPage, you will see that the client ids of the elements have been moved a node deeper in the tree:

<span id="view:_id1:_id2:_id3:callbackTabArea:computedField1" class="xspTextComputedField"></span>

If you want to use the data from the compositeData object, you have to add all the properties to the ccFacet too (and create the property in the custom control):

<xc:ccFacet title="Hello World!" foo="#{compositeData.foo}">

That’s a really annoying and reduces the usability of compositeData – but it seems to be the only usable workaround for this problem.

A performance bottleneck?

17. Februar 2013 Posted by Sven Hasselbach

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

Here is the code I used to test the performance:

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

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

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

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

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

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

  • Open the page

  • Partial Refresh

  • Test with a <div>

  • Opening the page

  • Partial Refresh

[Similar results with the other tags]

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

XPages: The Problem with DataContext Variables

11. Februar 2013 Posted by Sven Hasselbach

There is a large problem with data context variables if they are bound dynamically.
They will be recomputed again and again, even when in Partial Execution mode and if they are not in use. Here is a small demo XPage:

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

   <xp:this.dataContexts>
      <xp:dataContext var="dataContext">
         <xp:this.value>
            <![CDATA[#{javascript:
               // Sleep for 100ms
               java.lang.Thread.currentThread().sleep( 100 );
               print('-=> Recalc: ' + java.lang.System.currentTimeMillis());
               return true;
            }]]>
          </xp:this.value>
       </xp:dataContext>
    </xp:this.dataContexts>

   <xp:label id="labelRefreshMe">
      <xp:this.value>
         <![CDATA[#{javascript:
            java.lang.System.currentTimeMillis()
         }]]>
      </xp:this.value>
   </xp:label>

   <xp:button value="Refresh" id="buttonRefresh" >
      <xp:eventHandler event="onclick" submit="false"
         refreshMode="partial"
         refreshId="labelRefreshMe"
         execId="labelRefreshMe"
         execMode="partial" />
   </xp:button>

</xp:view>

If you are open the XPage, a dataContext variable will be recalculated for three times:

Clicking on the button will recalculate them up to eleven times:

Keep in mind: Partial Refresh & Partial Execution is enabled. That makes them only useable if their values are computed on page load.

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>

LO: signification delays encountered when sending mass email with nested groups

29. Januar 2013 Posted by Noteshexe

Error description: Mass emailing are emailed out to to all approx 15000 users. The groups are nested. Customer sends to “NMGroup1″ that has 10 nested groups which have has about 2500 users in different sub groups. When the user hits SEND, the workstaion becomes  unrepsponsive for up to 4 hours. Steps to Reproduce:  This is [...]

Domino 8.5.3 – iNotes user can not send a mail with an attachment file when the size of database is over quota

11. Januar 2013 Posted by Noteshexe

Error description: iNotes user can not send a mail with an attachment file when the size of database is over quota. Domino administrator adds the following parameter to notes.ini. iNotes_wa_Ignore_Quota=1 However, following error message was displayed when iNotes user sent a mail with an attachment file. “Unable to write to database because database would exceed [...]

Quick-n-Dirty: Import SSJS libraries with DXL

10. Januar 2013 Posted by Sven Hasselbach

In the last time I have developed different techniques for manipulating the design elements of XPages applications. While I am still working on a way for manipulating the localization files, I was playing a little bit with DXL imports.

Here comes a Java class to import a SSJS library with DXL to a database. It allows to create a new library directly from the browser:

By clicking the Import button the new Library will be added to your database (perhaps you have to refresh the DDE):

To use the new imported code you have to sign the library first (or the complete database), otherwise you will receive a security error (This can be done in the XPage too, just alter the code of the button).

Here is the code of the example XPage:

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

    <xp:inputTextarea id="inputTextareaSSJSCode"
       value="#{sessionScope.SSJSCode}">
    </xp:inputTextarea>

    <xp:br />

    <xp:inputText id="inputTextSSJSLib" value="#{sessionScope.SSJSLib}" />

    <xp:br />

    <xp:button value="Import" id="buttonImport">
       <xp:eventHandler event="onclick" submit="true" refreshMode="complete">
       <xp:this.action>
          <![CDATA[#{javascript:
             importPackage( ch.hasselba.xpages.util );

             var dxl = SSJSLibImporter.convertToDXL(
             sessionScope.SSJSCode, sessionScope.SSJSLib,
                database.getReplicaID()
             );

             var dxlImp = sessionAsSignerWithFullAccess.createDxlImporter();
             dxlImp.setDesignImportOption( 6 );
             dxlImp.importDxl( dxl,
                sessionAsSignerWithFullAccess.getCurrentDatabase() );
          }]]>
       </xp:this.action>
     </xp:eventHandler>
   </xp:button>

</xp:view>

And here is the Java class:

package ch.hasselba.xpages.util;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import com.ibm.misc.BASE64Encoder;

/**
 * Helper Class for DXL Import
 * of a SSJS library
 *
 * @author Sven Hasselbach
 * @version 0.2
 * @category DXL
 * @category Util
 */
public class SSJSLibImporter {

    /**
     * converts a integer to a byte array
     * in little endian order
     *
     * @param i integer to convert
     * @return byte array
     */
    static byte[] toBytes(final int i)
    {
            ByteBuffer b = ByteBuffer.allocate(4);
            b.order( ByteOrder.LITTLE_ENDIAN );
            b.putInt(i);
                return b.array();
    }

    /**
     * encapsulates raw SSJS lib data to RT records
     *  
     * @param data array to encapsulate
     * @return byte array with RT records
     */
    public static byte[] generateSSJSBlock( byte[] data ){

            final int RECORDLENGTH_OVERALL = 50;
            final int RECORDLENGTH_BLOB = 18;

            int size = data.length;  // size of the data
            int roundedSize = (size % 2 == 0)?size:size+1; // rounded size

            byte[] record = new byte[ roundedSize + RECORDLENGTH_OVERALL ];
            byte[] hlp;

            // Record Type (Event)
            record[0] = -7;
            record[1] = -1;

            // Record Length
            record[2] = 32;
            record[3] = 0;

            // event type
            record[8] = 22;
            record[9] = 0;

            // action type
            record[10] = 4;
            record[11] = 0;

            // data size
            hlp = toBytes( size );
            record[12] = hlp[0];
            record[13] = hlp[1];

            // Record Type (Blob)
            record[32] = -36;
            record[33] = -1;

            // Rounded size + recordlength
            hlp = toBytes( roundedSize + RECORDLENGTH_BLOB );
            record[34] = hlp[0];
            record[35] = hlp[1];

            record[36] = -7;
            record[37] = -1;

            // Rounded size
            hlp = toBytes( roundedSize );
            record[38] = hlp[0];
            record[39] = hlp[1];

            record[40] = 32;
            record[41] = 78;

            // Add data to BLOB          
            for( int  i=0; i<data.length; i++ ){
                    record[i+RECORDLENGTH_OVERALL] = data[i];
            }
            return record;
    }

    /**
     * generates the DXL to import a new SSJS library
     *
     * @param data array of bytes 
     * @param libName Name of the SSJS library
     * @param dbReplicaId
     * @return DXL to import
     * 
     */
    public static String generateSSJSDXL( byte[] data, final String libName, final String dbReplicaId ){
            StringBuffer dxl = new StringBuffer();
            BASE64Encoder benc = new BASE64Encoder();
            dxl.append( "<?xml version='1.0'?><!DOCTYPE scriptlibrary SYSTEM 'xmlschemas/domino_8_5_3.dtd'>" );
            dxl.append( "<scriptlibrary name='" + libName + "' xmlns='http://www.lotus.com/dxl' version='8.5' " );
            dxl.append( "maintenanceversion='3.0' replicaid='" + dbReplicaId + "' hide='v3 v4strict' " );
            dxl.append( "designerversion='8.5.3'>" );
            dxl.append( "<item name='$Flags'><text>.5834Q</text></item>" );
            dxl.append( "<item name='$ServerJavaScriptLibrary' sign='true'>" );
            dxl.append( "<rawitemdata type='1'>" );
            dxl.append( benc.encode( data ) );
            dxl.append( "</rawitemdata></item></scriptlibrary>" );

            return dxl.toString();

    }

    /**
     * converts a String to a DXL for a SSJS library to import
     *
     * @param String to convert
     * @param Library name
     * @param ReplicaId
     * @return String with DXL
     */
    public static String convertToDXL( String raw, String libName, String dbReplicaId ){
            byte[] data = generateSSJSBlock( raw.getBytes() );
            return generateSSJSDXL( data, libName, dbReplicaId );          
    }

}

This class only imports a new SSJS library. If you want to update an existing library, you need to add a <note-info> tag to the DXL. And don’t forget to update the ACL of your database, otherwise the DXL import will fail!

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

XPages: UI for editing localization files (1)

17. Dezember 2012 Posted by Sven Hasselbach

For one of my customers I am developing an easy way for editing the localization/translation of existing XPages applications: Instead of importing and exporting the property files with the Domino Designer (which is not an option for non-developers) it would be more suitable, if the users would have the possibility to edit the files directly in the browser. I am currently  working on a XPage solution for this, and this is my current beta version.

This is a proof-of-concept with some known bugs, f.e. HTML special chars are not encoded correctly. But this can be fixied easily. First, it is required that the users can choose a property file to edit. This can be realized with a small Java class I have created (based on a stackoverflow.com answer):

package ch.hasselba.xpages.localization;

import java.util.Vector;
import javax.faces.context.FacesContext;
import lotus.domino.Database;
import lotus.domino.Document;
import lotus.domino.NoteCollection;
import lotus.domino.NotesException;

public class DesignElements {

    private final String EMPTY_STRING = "";
    private final String[] FLAG_PROPERTIES = { "gC~4K2P", "gC~4K2" };
    private final String FIELD_$FLAGS = "$Flags";
    private final String FIELD_TITLE = "$TITLE";

    /**
     * returns Vector containing all property files of a database
     * 
     * No error handling included!
     * 
     * @category Domino
     * @author Sven Hasselbach
     * @category Tools
     * @version 0.2
     */
    public Vector<String> getPropertFileList() {

        FacesContext fc = FacesContext.getCurrentInstance();

        Vector<String> data = new Vector<String>();
        try {

            // get DB
            Database db = (Database) fc.getApplication().getVariableResolver()
                    .resolveVariable(fc, "database");

            // get all design docs
            NoteCollection nc = db.createNoteCollection(false);
            nc.selectAllDesignElements(true);
            nc.buildCollection();

            // process all notes
            String noteId = "";
            noteId = nc.getFirstNoteID();

            Document doc = null;
            // 
            while (!(EMPTY_STRING.equals(noteId))) {

                // get design doc
                doc = db.getDocumentByID(noteId);

                // check if its a property file
                for (int i = 0; i < FLAG_PROPERTIES.length; i++) {
                    if (FLAG_PROPERTIES[i].equals(doc
                            .getItemValueString(FIELD_$FLAGS))) {
                        // add to Vector
                        data.add(doc.getItemValueString(FIELD_TITLE));
                    }
                }

                // next one
                noteId = nc.getNextNoteID(noteId);

                // recycle doc
                recycleObject(doc);
            }

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

        return data;

    }

    /**
     * recycles a domino document instance
     * 
     * @param lotus
     *            .domino.Base obj to recycle
     * @category Domino
     * @author Sven Hasselbach
     * @category Tools
     * @version 1.1
     */
    public static void recycleObject(lotus.domino.Base obj) {
        if (obj != null) {
            try {
                obj.recycle();
            } catch (Exception e) {
            }
        }
    }
}

The difference to the answer on stackoverflow.com is the additional property flag I have added to this class (I know it would be better to check the flags instead of comparing the strings, but it seems to work this way).

Next step is to load the selected property file. This can be done with a managed bean and a helper class for the entries itself. Here is the helper class:

package ch.hasselba.xpages.localization;

import java.util.UUID;

/**
 * Locale Entry of a locale file
 * 
 * @author Sven Hasselbach
 * @category Localization
 * @version 1.0
 * 
 */

public class LocaleEntry {

    private String id;
    private String name;
    private String value;

    /**
     * initializes the object and
     * sets an unique identifier
     */
    public LocaleEntry(){
        this.id = UUID.randomUUID().toString();
        this.name = "";
        this.value = "";
    }

    /**
     * returns unique identifier of the object
     * @return String unique id
     */
    public String getId() {
        return id;
    }

    /**
     * sets the unique identifier of the entry
     * @param id String unique id
     */
    public void setId(final String id) {
        this.id = id;
    }

    /**
     * returns the name of the entry
     * @return String
     */
    public String getName() {
        return name;
    }

    /**
     * sets the name of the entry
     * @param String
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * returns the value of the entry
     * @return String
     */
    public String getValue() {
        return value;
    }

    /**
     * sets the value of the entry
     * @param value
     */
    public void setValue(String value) {
        this.value = value;
    }

}

And here comes the “Loader” class:

package ch.hasselba.xpages.localization;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.Vector;
import com.ibm.xsp.context.FacesContextEx;

/**
 * "Loader" class to access the entries
 * of a .property file
 * 
 * @author Sven Hasselbach
 * @version 1.0
 */
public class Locale {

    private Vector<LocaleEntry> locales;

    /**
     * @return vector containing the LocalEntry objects
     */
    public Vector<LocaleEntry> getLocales() {
        return locales;
    }

    /**
     * sets vector containing the LocalEntry objects
     * @param Vector
     */
    public void setLocales(Vector<LocaleEntry> locales) {
        this.locales = locales;
    }

    /**
     * wrapper for the static method call
     * 
     * @param fileName name of the property file to load
     */
    public void loadFile(final String fileName) {
        Map<String, String> m = getPropertiesFromFile(fileName);
        this.locales = parseMap(m);
    }

    /**
     * loads a property file and parses it to a key/value map
     * 
     * @param fileName name of the property file to load
     * @return Map containing the key/values
     * 
     * The loading routine is shamelessly copied from Ulrich Krause:
     * http://openntf.org/XSnippets.nsf/snippet.xsp?id=access-.properties-files
     */
    public static Map<String, String> getPropertiesFromFile(String fileName) {
        Properties prop = new Properties();

        try {
            prop.load(FacesContextEx.getCurrentInstance().getExternalContext()
                    .getResourceAsStream(fileName));
            Map<String, String> map = new HashMap<String, String>((Map) prop);

            return map;

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

    }

    /**
     * parses a property map and create a vector
     * with LocalEntry objects
     * 
     * @param map to parse
     * @return Vector containing the LocalEntry objects
     */
    public static Vector<LocaleEntry> parseMap(final Map<String, String> map) {

        // init new vector
        Vector<LocaleEntry> localEntries = new Vector<LocaleEntry>();
        String key;
        String value;

        // get key set for iteration
        Iterator<?> it = map.keySet().iterator();

        while (it.hasNext()) {

            // extract key & value
            key = (String) it.next();
            value = (String) map.get(key);

            // create new entry and add to vector
            LocaleEntry lEntry = new LocaleEntry();
            lEntry.setName(key);
            lEntry.setValue(value);
            localEntries.add(lEntry);

        }

        // return vector
        return localEntries;

    }

    /**
     * dumps current object data to console
     * Just for debugging
     * 
     * @category Debug
     */
    public void dump() {
        for (int i = 0; i < this.locales.size(); i++) {
            LocaleEntry e = this.locales.get(i);
            System.out.println(e.getId() + " - " + e.getName() + " - "
                    + e.getValue());
        }

    }

}

Now the new managed bean can be added to the faces-config.xml:

<managed-bean>
  <managed-bean-name>locale</managed-bean-name>
  <managed-bean-class>ch.hasselba.xpages.localization.Locale</managed-bean-class>
  <managed-bean-scope>session</managed-bean-scope>
</managed-bean>

The resulting XPage can look like this:

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

    Property File:&#160;
    <xp:comboBox id="comboBoxFile" value="#{sessionScope.PropertyFile}">
        <xp:selectItems>
            <xp:this.value>
            <![CDATA[#{javascript:
               importPackage(ch.hasselba.xpages.localization);
               ch.hasselba.xpages.DesignElements().getPropertFileList();
            }]]>
            </xp:this.value>
        </xp:selectItems>
    </xp:comboBox>
    &#160;
    &#160;
    <xp:button value="Load" id="buttonLoadFile">
        <xp:eventHandler event="onclick" submit="true"
            refreshMode="complete">
            <xp:this.action>
               <![CDATA[#{javascript:
                  importPackage(ch.hasselba.xpages.localization);
                  var bean = facesContext.getApplication().getVariableResolver()
                     .resolveVariable(facesContext, "locale");
                  bean.loadFile(sessionScope.get("PropertyFile") )}]]>
            </xp:this.action>
        </xp:eventHandler>
    </xp:button>
    <hr />
    <xp:label id="labelSelecteFile">
        <xp:this.value>
           <![CDATA[#{javascript:sessionScope.get("PropertyFile")}]]>
        </xp:this.value>
    </xp:label>
    <xp:br />
    <xp:br />
    <xp:repeat id="repeat1" rows="9999" var="lMap" value="#{locale.locales}">
        <xp:label value="#{lMap.name}" id="labelMapName" />
        <xp:inputText id="inputTextValue" value="#{lMap.value}" />
        <xp:br />
    </xp:repeat>

</xp:view>

And this is how it looks in the browser:

The next article describes the procedure to save the properties back the database.

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.

XPiNC and Attachments: A better PDF handling

15. November 2012 Posted by Sven Hasselbach

Achtung: Sicherheitslücke in IBM Notes&Domino in IBM JRE (Java)/Security Bulletin: IBM Notes&Domino IBM JRE

14. November 2012 Posted by Noteshexe

Abstract: IBM Lotus Notes and IBM Lotus Domino are vulnerable to four Java exploits where malicious agents, applets, or XPages applications can escalate privileges. These vulnerabilities are in the IBM Java SDK. Content: ​For information about the impact of this vulnerability on other affected IBM products, ​refer to this post​ on the Product Security Incident [...]