XPages: Inject CSJS code at page top

Sometimes it is required to add 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:

Veröffentlicht unter Dojo Toolkit, HTML, Java Script, Web | Verschlagwortet mit , , , , , , , | 13 Kommentare

Notes 9: Some interesting xsp.properties

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

  • xsp.client.resources.uncompressed

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

  • xsp.client.script.dojo.html5attr

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

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

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

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

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

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

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

  • xsp.maximum.mime.tree.scanLevel
  • com.ibm.ws.webcontainer.HTTPOnlyCookies
  • xsp.client.script.xspClient.preventLayer
  • xsp.client.script.radioCheckbox.ie.onchange.trigger
  • xsp.repeat.parseSingleStringAsInt
  • xsp.client.script.dojo.loader
Veröffentlicht unter Dojo Toolkit, HTML5, Server, XPages, XSP | Verschlagwortet mit , , , , , , | 11 Kommentare

Notes 9: No Comment!

Veröffentlicht unter Allgemein | Verschlagwortet mit , | 5 Kommentare

Quick-n-Dirty: How to add HTML 5 events

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.

Veröffentlicht unter HTML, HTML5, Java Script, ServerSide JavaScript, Web, XPages | Verschlagwortet mit , , , , , , , , , , | 3 Kommentare

In Ya Facet!

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 The elements added to the callback have 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:

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.

Veröffentlicht unter Allgemein, JSF, XPages | Verschlagwortet mit , , | 4 Kommentare

A performance bottleneck?

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.).

Veröffentlicht unter Allgemein, Java, JSF, Performance, ServerSide JavaScript, XPages | Verschlagwortet mit , , , , , | Schreib einen Kommentar

XPages: The Problem with DataContext Variables

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.

Veröffentlicht unter Allgemein, Performance, XPages | Verschlagwortet mit , , , , , , | 6 Kommentare

Quick-n-Dirty: Disable all validators at once

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!

Veröffentlicht unter Allgemein, Errorhandling, Java Script, ServerSide JavaScript, XPages | Verschlagwortet mit , , , , , , | Schreib einen Kommentar

XPages: compositeData is undefined

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>
Veröffentlicht unter Java, JSF, ServerSide JavaScript, XPages | Verschlagwortet mit , , , , , , , | Schreib einen Kommentar

Quick-n-Dirty: Import SSJS libraries with DXL

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.

Veröffentlicht unter Allgemein, Java, ServerSide JavaScript, XPages | Verschlagwortet mit , , , , , , | Schreib einen Kommentar