Thursday, December 9, 2010

My first request to the XPages Extension library - userScope

I was planning on making a caching mechanism on a user by user basis in SSJS (think userScope)/post it on my blog, but while I sketched the API, I ran into some issues.

In SSJS you have no way to bind callbacks (think event handlers) to the XPages life cycle. This can make the API quite IO heavy on servers with many users.

I've implemented something similar in Domino applications, and have gotten a big boost in performance. The current applicationScope makes it a little cumbersome (not a lot.. you have to write a wrapper for gets/sets that includes username in the scoped field) to cache lookups in applications with Readers fields. Another thing that I miss in the scopes as a caching mechanism is that the scoped variables only live as long as the application is active. In regards to performance/scalability, it would be nice to have caching mechanisms that lives beyond the applicationScope.

I posted a feature request on the XPages Extension Library's project page on OpenNTF.

If this is something that you would like/you have comments, please leave a response on the request, and maybe IBM will implement it :)

Wednesday, December 8, 2010

Spending the holidays in Singapore

I decided I wanted to do something different this christmas/new years. Ever since I went to Korea in 2007, I've wanted to travel to Asia again. Most of my (single) friends travel to some place in Europe when they go on vacation, so I seldom have the opportunity to travel to Asia, unless I travel alone.

A couple of friends recently moved to Singapore. I contacted them and asked if they wanted company in the holidays. Luckily they did, so this holiday season I'll be halfway around the world in a very different climate than I'm used to this time of year :)

Saturday, December 4, 2010

Favorite Applications repository v0.1 Alpha

I've started working on an application heavily inspired by Tim Tripconys PIM Slap application.

I've made it document based, so that you can easily add more applications (in PIM Slap, you had to add them in Domino Designer).

In the config, you can specify the servers you have your apps on:


In the application documents, you select the server, and write the path to the application.
Save the document, and the other "required" fields are set through code. Once the document is saved. Click refresh on the framesets. Some apps require you to select a frameset, or they'll open in a separate tab. If there aren't any framesets in the app, leave it empty.

If you have an app spread across several servers, and want the possibility to open the app on those servers, click refresh below the "Servers with.." table. The code will then look through all your configured servers for the application, both by path and by replica.


Once you've configured the applications, you can click in the left menu, on the little icon to the left of the application title. This should open the app in the main frame. On the top of the app there's a dialog list that lets you switch servers for the app.

My motivation for making this app is that I have several OpenLog implementations spread across several servers, and I'm tired of opening them manually. The reason I don't have any other screenshots is that they would show the server names of my company, and I've heard that's a no no.

I decided to post this very early version of the app so that I might get feedback on wanted improvements early on (apart from poor usability, it does what I needed). One of the first things I'll implement next is a Database Browser-button, so that you don't have to remember the path of every app you want to configure.

Hopefully I'll get around to comment the code the next time I get my hands dirty with the app again.

My brain is now fried after trying to get around all the quirks of Notes development, so no more development tonight. :)

>> Download Demoapp

Share and enjoy!

Thursday, December 2, 2010

New and improved EventDelegator with InViewEdit demo

In may 2010, I posted about server side event delegation. The demoapp was from my (current) point of view very messy and hard to use for any other purpose than the demo.

I've improved the EventDelegator code at work, and made it more general purpose. The client side script can be used for event delegation in the browser, or if you want to, use it in combination with server side event delegation.

On the client side, you should be able to delegate most events (I haven't implemented onChange).

This demoapp implements the same functionality as the previous, with a couple of additions. There's an edit icon on every editable area. When you click to edit a name, the field gets focus. If you press escape while in the field, the edit is cancelled, same as if you click the cancel button. The code is also a lot better documented through comments.

Here's the video from the previous app:





A screenshot from the new demoapp:


>> Download Demoapp

Debugging traditional WebQueryOpen/WebQuerySave code

I just had a eureka moment when it comes to debugging WQO/WQS code. Once upon a time I decided it was impossible to debug code that required NotesSession.DocumentContext, through the (local) Domino Debugger. It's not :)

You need a global NotesDocument variable that you can bind the DocumentContext to. The reason that it has to be global is that it has probably has to be available across multiple methods/scopes. It should be put inside a script library that doesn't use other script libraries. In most of my apps, I have a script library stack, where the most general code is put inside a script library that most other script libraries use (either through "inheritance" or through Use). This is where I put the global declarations.

Then you need a function that returns either NotesSession.DocumentContext, or a temporary document. This should be put inside the same script library as the aforementioned global declaration.
Function getDocumentContext() As NotesDocument
On Error GoTo returnEmptyDocument
'// Returns NotesSession.DocumentContext if availabe, else a temporary document
Dim s As New NotesSession()
Set getDocumentContext = s.documentContext
If getDocumentContext Is Nothing Then GoTo returnEmptyDocument

Exit Function
returnEmptyDocument:
If contextDoc Is Nothing Then Set contextDoc = s.currentDatabase.createDocument()
Set getDocumentContext = contextDoc
Exit Function
End Function
contextDoc is declared as a global variable.

Wherever in your code where you fetch NotesDocument.DocumentContext, you have to change that to call the above function, getDocumentContext().

To test code, all you have to do is to create an agent, bind getDocumentContext to a NotesDocument variable (or modify the global var), set the needed fields, and you're ready to debug the code.

E.g.
Call getDocumentContext()
Call contextDoc.replaceItemValue( "someNeededField", "someTestValue" )
Call methodThatsDesignedForWQOOrWQS()
Share and enjoy!

Monday, November 29, 2010

Recommended video - The Top 5 Mistakes of Massive CSS

I rarely post links, but if you're working with CSS, you really need to watch this video. It contains several great tips that help you write more maintanable CSS.

Monday, November 22, 2010

XPages - Adding non-xpages attributes to input fields

I got a question today from a reader of my blog, regarding input fields, and attributes not available in XPages. He was working with web applications for Apple devices, and needed to add some attributes to input fields that aren't allowed by Domino Designer/XPages.

My workaround kind of reminds me of "old school" domino development, where I often have fields that are HTML, and use hidden fields (hide from web) to capture the value from the browser.

Basically it works like this. The HTML for the input fields is hand typed in the source code. I have a hidden field (xp:inputHidden) for each of the custom fields. In the hidden fields, I've added a custom converter that fetches the value posted from the HTML field using the param map.

To simplify the process of adding custom fields, I've made a custom control with two parameters, fieldName and attributes. The field is bound to currentDocument[ compositeData.fieldName ], and the html field is rendered in a computed field (xp:text), where the attributes are added. I'm terrible at explaining code, so take a look at the provided demoapp to see what I mean :)

In the demoapp, I've created a simple form with HTML5 controls. You need Opera or Safari to view the controls properly.

Screenshots from Opera:



I don't recommend this technique unless you really need it. If possible, use JavaScript in the browser to set the attributes instead. This way, you can still take advantage of the power of XPages.

>> Download demoapp

Tuesday, November 16, 2010

XPages Demoapp - Adding controls to a pager

I got a question from a reader regarding posting a demoapp of Adding controls inside a pager

I mocked up a simple demoapp with search controls inside the pager area.


>> Download demoapp

Wednesday, November 10, 2010

NotesDocumentCollection.stampAllMulti and readers fields

Update 11.11.10: I did a little test, and it looks like the bug(?) affects Readers, Authors and Names fields.

Readers fields (isReaders=True) in the "template" document seems to be set as Text fields in the stamped documents.

It wasn't a critical bug(?) for me, but I thought I should share it.

Only tested on Domino 8.5.2

Monday, November 8, 2010

Enable enhanced HTML generation via LotusScript

I have about 850 applications that now needs enhanced HTML generation enabled. As far as I know, the only way to enable this in an application is via LS or manually.

Here's example code for enabling it with LS for one db:
Dim s As New NotesSession, db As NotesDatabase
Set db = s.currentDatabase

'// Fetch the db icon document
Dim col As NotesNoteCollection, doc As NotesDocument
Set col = db.createNoteCollection( False )
col.selectIcon = True
Call col.buildCollection()

'// Enable enhanced HTML
Set doc = db.getDocumentById( col.getFirstNoteId() )
Call doc.replaceItemValue( "$AllowPost8HTML", "1" )
Call doc.save( True, False )
I found the solution here.

Friday, November 5, 2010

New blog design

I've decided to ditch the standard green template, and go for a cleaner look.
I modified the template I chose slightly to fit my requirements. I was expecting several hours to find/create a suitable look, but the process took about an hour total.

Hopefully it will make the content more readable.

Thursday, November 4, 2010

XPages: Obscure error message for undeclared variable

I've had an error message that popped up regularly in our logs.
com.ibm.jscript.InterpretException: Script interpreter error, line=524, col=25: Interpret exception
at com.ibm.xsp.javascript.AbstractLocalObject.put(AbstractLocalObject.java:95)
The line it refers to has a for .. in loop on a scoped variable. I believed it had to do with the object being in a scoped variable, and tried to make a by-value copy. That didn't help.

I finally (!!) discovered the reason for the error message. I had forgotten to declare the "key" variable.
// Results in error
for( key in object ){ .. }
// OK (key can also be declared outside loop)
for( var key in object ){ .. }

Friday, October 29, 2010

LotusScript: Fast sorting of arrays using Java

If you want to sort large string arrays (>1000 items), LotusScript can be horrendously slow. Back in the day, I tried to use Java via LS2J. Unfortunately LS2J was so unstable that I went back to pure LS sorting.

The reason I'm so interested in efficiently sorting arrays, is that I use a token string array as a base when rendering various content in a CMS I maintain at work. Sorting big arrays in LS is, as said, slow. Sorting token string arrays, based on a specific token in LS is s l o w!

Then came Domino 8.5.2, and NotesAgent.runWithDocumentContext :D

Basically, it lets you send an in-memory NotesDocument to an agent (LS or Java), and modify it.

To the demoapp..

In the demoapp, I've created a couple of helpers that lets you sort arrays with the Java API (via a Java Agent). It supports (locale aware) sorting of "regular" string arrays, and token string arrays, based on a token

With 5-600 items in the array, Java beats LS, even when sorting by token. The more items, the bigger the difference.

With 10 000 items, running on my computer:
Java sort: 0.9 seconds
LS Sort (algorithm): 46 seconds
Java token sort: 1.5 seconds

Open the demoapp on the web to run the test on your system. Modify the (SortDemo) agent to test different array sizes.

>> Download DemoApp (demoapp is around 6MB, due to 40k+ documents)

Share and enjoy!

Wednesday, October 27, 2010

XPages: New methods to the Debug class

I added couple of new methods to the Debug class.

Debug.messageToPage( message:String ): Adds a message to the bottom of the page.
Example usage:
..
Debug.messageToPage( viewScope.someValue )
..
Debug.exceptionToPage( exception:String ): Adds exception message to the bottom of the page
Example usage:
function someFunction(){
try {
..
} catch( exception ){ Debug.exceptionToPage( exception ) }
}
Each call to the function results in a line being added to the "message box". The code for the Debug class can be found here.

The messages are displayed in a "validation error" type box (xspMessage).

I would think messageToPage is the most useful, as an exception may crash the entire page/the message box won't be rendered.

Share and enjoy!

Friday, October 22, 2010

XPages: Make categorized views behave

Update 22.05.14: Updated with improved code.

The current implementation of categorized views gives a table column to each column. In most cases, you probably don't want this. The code below makes the categories look more like nested sections.

The code is requires Dojo 1.4, which is bundled with Domino 8.5.2. To make it compatible with previous versions, you'd have to exchange categoryButtons.closest( 'tr' ) with code that walks up the DOM tree until it finds a tr-node.

function transformCategorizedViews(){
 // Needed for dojoj.NodeList.closest()
 dojo.require("dojo.NodeList-traverse");
  
 var dataTables = dojo.query( '.xspDataTable' );
 dataTables.forEach( function( dataTable ){
  var numColumnsTotal = dojo.query( 'thead th', dataTable ).length;
   
  var categoryButtons = dojo.query( '.xspDataTable button[title~=collapsed], .xspDataTable button[title~=expanded]' );
  
  // Get parent row/find all the empty cells before expand/collapse button
  var categoryButtonRows = categoryButtons.closest( 'tr' );
  categoryButtonRows.forEach( function( categoryButtonRow ){
   var numCellsWithContent = 0;
   var numEmptyCellsBeforeButton = 0;
   var cells = categoryButtonRow.cells;
   if( cells.length > 1 ){
    var categoryButtonCell = null;
    for( var i = 0, numCells = cells.length; i < numCells; i++ ){
     var cell = cells[i];
     var cellIsEmpty = ( cell.innerHTML === '' );
     
     // Hide empty cells/set colspan to the category equal to hidden cells
     if( !categoryButtonCell ){
      if( cellIsEmpty ){
       numEmptyCellsBeforeButton += 1;        
      } else {
       categoryButtonCell = cell;
      }
     }
     
     if( cellIsEmpty ){
      cell.style.display = 'none';       
     } else {
      numCellsWithContent += 1;
     }
    }
     
    // colspan = column with expand/collapse + column count - num cells with content 
    categoryButtonCell.setAttribute( 'colspan', 1 + numColumnsTotal - numCellsWithContent );
    categoryButtonCell.style.paddingLeft = (30 * numEmptyCellsBeforeButton ) + 'px';     
   }
  });
 });
}

Since categorized views use partial refresh, you'll need something like my partial refresh hijacker.

Here's how to use the above code with the hijacker:
dojo.addOnLoad(function(){
 // Run on load
 transformCategorizedViews();
 // Run on partial refreshes
 dojo.subscribe( 'partialrefresh-complete', transformCategorizedViews );
});
Share and enjoy!

Tuesday, October 19, 2010

XPages: Show validation errors for multiple/specified components

There's a lot of great stuff in XPages, but there's also a few things I miss. One of those things is message-controls that you can connect to multiple fields. It's quite easy to emulate this using a little bit of SSJS, and a xp:text.

SSJS:
// Fetch messages for specified components
function getFacesMessages( components ){
try {
if( typeof components !== 'array' && typeof components != 'object' ){ components = [ components ]; }
var clientId, component, messages = [], msgIterator;
for( var i = 0; i < components.length; i++ ){
component = components[i];
if( typeof component === 'string' ){
clientId = getClientId( component );
} else {
clientId = component.getClientId( facesContext );
}

msgIterator = facesContext.getMessages( clientId );
if( !msgIterator ){ continue; }
while( msgIterator.hasNext() ){
messages.push( msgIterator.next().getSummary() );
}
}
return messages;
} catch( e ){ /*Debug.logException( e );*/ }
}

XPages source code example:
<xp:text styleClass="xspMessage" 
escape="false" rendered="#{javascript:return ( this.value );}">
<xp:this.value>
<![CDATA[#{javascript:return getFacesMessages( [ 'field1', 'field2' ] ).join( '<br />' );}]]>
</xp:this.value>
</xp:text>
Share and enjoy!

Monday, October 11, 2010

XPages: Another bugfix for the partial refresh hijacker

When the onStart/-Complete/-Error code for an event was served to the browser as a string, it wouldn't run. This bug has now been fixed.

Original post with code

Thursday, October 7, 2010

XPages: Add global/field message

If you want to set a message in a xp:message/xp:messages control, here's a little code snippet for you.

// Sets a global message/message for a field
function addFacesMessage( message, component ){
try {
if( typeof component === 'string' ){
component = getComponent( component );
}

var clientId = null;
if( component ){
clientId = component.getClientId( facesContext );
}

facesContext.addMessage( clientId,
new javax.faces.application.FacesMessage( message ) );
} catch(e){ /*Debug.logException(e);*/ }
}


If the second parameter isn't specified, the message becomes global (visible in all rendered xp:messages controls on the page).

If you specify the second parameter, it has to be the component id of a field which has a xp:message control, or the field component. The message then shows up in the message control for the field.

Code stolen/ported from this page.

Share and enjoy!

Wednesday, October 6, 2010

XPages: Bug in fromJson (with fix)

Update 07.10.10: I added a by-value copy method

toJson can convert most JavaScript objects to JSON. fromJson can not convert back all JSON strings that toJson creates.

Example:
var arrayJson = toJson( [1,2,3] ); // "[1,2,3]"
fromJson( arrayJson ) // fails
If you try the same in Firefox, which has implemented the JSON API, everything works:
var arrayJson = JSON.stringify( [1,2,3] ); // "[1,2,3]"
JSON.parse( arrayJson ) // [1,2,3]
I've created a simple wrapper-class that works around this bug:
var JSON = {
// Makes a by-value copy of the object
copy: function( object ){
try {
// Faster way to copy arrays
if( object && typeof object.concat === 'function' ){ return object.concat(); }

return this.parse( this.stringify( object ) );
} catch( e ){ /*Debug.logException( e );*/ }
},

// Converts object to JSON string
stringify: function( object ){
try {
return toJson( object );
} catch( e ){ /*Debug.exception( e );*/ }
},

// Parses JSON to JS object
parse: function( JSON ){
try {
return fromJson( '{"values":' + JSON + '}' ).values;
} catch( e ){ /*Debug.exception( e );*/ }
}
}
It saddens me that there are so many "simple" bugs in the XPages API. :\

Share and enjoy!

Code snippet - Array.splice according to ECMA

I've really missed the proper Array.splice in SSJS. To avoid writing a loop for every time I want to do a "splice operation", I've made a function that should work according to spec.

// $splice -> Array.splice according to ECMA standards
function $splice( array, startIndex, numItems ){
try {
var endIndex = startIndex + numItems;
var itemsBeforeSplice = [], splicedItems = [], itemsAfterSplice = [];
for( var i = 0; i < array.length; i++ ){
if( i < startIndex ){ itemsBeforeSplice.push( array[i] ); }
if( i >= startIndex && i < endIndex ){ splicedItems.push( array[i] ); }
if( i >= endIndex ){ itemsAfterSplice.push( array[i] ); }
}

// Insert all arguments/parameters after numItems
for( i = 3; i < arguments.length; i ++ ){
itemsBeforeSplice.push( arguments[ ''+i ] );
}

// Combine before/after arrays
var remainingItems = itemsBeforeSplice.concat( itemsAfterSplice );

// Rewrite array. Arrays can't be overwritten directly in SSJS
for( i = 0, len=Math.max( array.length, remainingItems.length ); i < len; i++ ){
if( remainingItems.length > i ){
array[i] = remainingItems[i];
} else {
array.pop();
}
}

return splicedItems;
} catch(e){ /*Debug.logException( e );*/ }
}
Note! If you want to do a splice on a scoped field that's an array, you first have to copy it, or your script might crash. E.g.
viewScope.put( 'someArray', [1,2,3,4] ); 
...
var array = viewScope.someArray.concat(); // concat copies the array
$splice( array, 1, 2, 12, 22 ); // modifies the array, returns the removed items [2,3]
viewScope.put( 'someArray', array );// viewScope.someArray -> [1,12,22,4]
I really hope IBM fixes the SSJS API/makes it follow ECMA-262 Edition 3 (or newer editions), but only time will tell.

Wednesday, September 29, 2010

Storing data as JSON in a field VS multiple fields

I've started to convert a CMS I'm maintaining at work to XPages. I'll probably do it over a longer time span, when I'm not doing other more important work. In the process, I'm brainstorming a little bit on how to improve some of the things in the application.

One of the things our customers request is more fields for links/the ability to sort them (they are displayed in "categorized" sections on the right side of the page in read mode). The form currently have 20 rows of links. There are five fields for each link (hidden?, category, title, url, target). This takes up quite a bit of real estate on the form.

In the XPages version, I'm considering changing the way links are stored. I'm thinking about storing them in a single field as JSON. I've made a list of pros/cons. There's no traditional Notes interface for the app, so I don't have the need for the values to be "notes view compatible".
Pros
Less fields to deal with
Takes up a lot less real estate on the form/xpage
Easy to change the order of the link values
Cons
Adds processing overhead(?), as the JSON has to be converted from/to JS objects when storing/retrieving
More complex
Needs a widget/logic to add/edit/remove
Currently, I'm in favor of implementing the change. I might implement this for other "repeating" fields as well if it works out well. If you can see any more pros/cons, please feel free to leave a comment :)

XPages: SSJS code snippet that lets you parse/stringify JS<->JSON

Update: I got word from Philippe Riand that fromJson/toJson/isJson is implemented in the XPages runtime. Apparently it has been available from 8.5.1.

Example usage:
var json = toJson( {a:[1,2,3]} ) //-> '{"a":[1,2,3]}'
var jsObj = fromJson( '{"a":[1,2,3]}' ) //-> {a:[1,2,3]}
isJson( '{"a":[1,2,3]}' ) //-> true
---

A while back, I ported a script written for Rhino to SSJS that lets you parse JSON to JS objects/serialize JS objects to a JSON string. I've found it quite useful, so I thought I should share it with you.

I can't remember where I found the Rhino script, but the original source code that the Rhino script is based on is linked to at the bottom of this page: http://www.json.org/js.html

The original script was written/maintained by Douglas Crockford, and should prevent script injection, e.g. database.getAllDocuments().removeAll(true).

The script


Share and enjoy!

Monday, September 20, 2010

If you're getting errors on dijit.Dialog.hide() on Domino 8.5.2 - workaround

Update: I found the reason. Opening a dialog from another dialog (which is then destroyed). Unless refocus=false, dojo (1.4.3) tries to put back the focus to the "destroyed" dialog.

There is a bug in the current code stream (dojo 1.4.2) when hiding/closing dialogs. I'm not a 100% sure what triggers the bug in our applications. It happens on a couple of dialogs.

The workaround for the bug is to add refocus: false in the options when creating the dialog. E.g.
var dialog = new dijit.Dialog({
refocus: false,
title: 'Dialog title'
});

Tuesday, September 14, 2010

XPages: Workaround for fields losing focus on partial refresh

Update 13.08.13: I found a bug when a link triggered partial refresh. Fixed.

Update 20.03.13: I updated the code so that it doesn't try to focus the field if the partial refresh hasn't influenced the active field.

Update 02.05.11: By request, I made a simple demoapp.

Update 22.11.10: Ajit Rathore found a weakness in the code. On partial refresh error -> unsubscribe to the event. I've added his patch to the code.

The code snippet below, in conjunction with my hijackAndPublishPartialRefresh function should fix the problem of fields losing focus when elements containing them are refreshed.
dojo.addOnLoad(function(){
dojo.subscribe( 'partialrefresh-init', function(){
 // setTimeout needed to make it work in Firefox
 setTimeout(function(){
  var activeElementId = document.activeElement.id;  

  var focusSubscription = dojo.subscribe( 'partialrefresh-complete', function(){
 // Only set focus if field hasn't been overwritten/lost focus
 if( document.activeElement.id !== activeElementId ){
  var activeElement = dojo.byId(activeElementId);

  if( activeElement && /INPUT|SELECT|TEXTAREA/.test( activeElement.nodeName ) ){
   // Set focus to element/select text
   activeElement.focus();
   if( activeElement.nodeName !== 'SELECT' ){
    activeElement.select();
   }
  }
 }
  
   // Unsubscribe after focus attempt is done
   dojo.unsubscribe( focusSubscription );
  });

  // In case of error -> remove subscription
  var errorSubscription = dojo.subscribe( 'partialrefresh-error', function(){
     dojo.unsubscribe( focusSubscription );
  });
 }, 0 );
} );
});
When a partial refresh is initiated, the browser is queried for the active element/the id of the element is stored in a variable. When the refresh is complete, focus is set to the element that had focus before the refresh.

I've only tested it in a small testpage, so there may be bugs that I'm not aware of. Please let me know if you encounter any.

I've tested it/it should work in IE7/8, latest version of Firefox and Opera.

Share and enjoy!

Monday, September 6, 2010

XPages: Helper function for inconsistent API methods - always get an array

Update: I added functionality to convert collections (ArrayList/Vector/etc.) to arrays as well.

// Helper for inconsistent API
// Wrap around @DbLookup/@DbColumn/@Trim/@Unique calls to have an array returned
function $A( object ){
// undefined/null -> empty array
if( typeof object === 'undefined' || object === null ){ return []; }
if( typeof object === 'string' ){ return [ object ]; }

// Collections (Vector/ArrayList/etc) -> convert to Array
if( typeof object.toArray !== 'undefined' ){
return object.toArray();
}

// Array -> return object unharmed
if( object.constructor === Array ){ return object; }

// Return array with object as first item
return [ object ];
}
E.g.
@Unique( 1, 1, 1 ) -> 1
$A( @Unique( 1, 1, 1 ) -> [ 1 ]

@Trim( '', '', 'a', '' ) -> 'a'
$A( @Trim( '', '', 'a', '' ) ) -> ['a']

If IBM fix this inconcistency, your code will not break if you're using the above function. Call it whatever you like. I called it $A from the $A function in MooTools.

Share and enjoy!

Friday, September 3, 2010

If you want to run XPages API Inspector in the Client

Add this line to your java.policy file (in the notes directory):
grant {
permission java.lang.RuntimePermission "accessDeclaredMembers";
};
Thanks to F. Kranenburg for the tip. :)

Need help.. ECL Exception when using Java Reflection API

Update: Workaround

I got a feature request for my XPages API Inspector. When running the inspector on an XPage in the Notes Client:
Exception:ECL Permission Denied (java.lang.RuntimePermission accessDeclaredMembers)

I think it has something to do with accessing declared members of a class. Have any of you encountered this, and if so, is there a way to work around it in code, or is this something that you have to do on the client/server?

Thursday, September 2, 2010

XPages API Inspector V3

Changes:
From feature requests (thanks, Mark Leusink, F. Kranenburg):
* Classes that don't have a constructor can now be inspected (e.g. lotus.domino.local.Item)
* The API Inspector now has partial refresh. No more full reload of page
* Possibility to filter hidden _components (e.g. eventHandlers)

My own tweaks:
* Removed the clear-button
* If you select something other than Custom, the input field/inspect button is hidden.
To reset, select custom
* The expression field is now an auto-growing text-area.
This allows you to test more advanced expressions in a more user friendly way.
>> Download V3 from OpenNTF.org

Let me know if there are any bugs :)

Wednesday, August 25, 2010

XPiNC: Prevent tabs being closed on ESC

A reader of my blog asked me how to prevent tabs being closed when pressing escape in XPages running in the Notes Client.

This client side code seems to work in Notes 8.5.2 at least:
dojo.addOnLoad( function(){ 
dojo.connect( document, 'keydown', function(e){
// 27 -> Key code for ESC -> stop event
if(e.keyCode === 27){ dojo.stopEvent(e); }
});
});

Thursday, June 24, 2010

XPages: Simple function to clear scoped variables

All scope objects (applicationScope, sessionScope, etc.) are maps, so it's quite easy to clear them. This might come in handy during development, if you want to clear the applicationScope.

I've tested it on both applicationScope and sessionScope, and it doesn't seem to do any harm. After the maps are cleared, the server automatically loads the "internal" values used by the system.

function clearMap( map:Map ){
// Get iterator for the keys
var iterator = map.keySet().iterator();

// Remove all items
while( iterator.hasNext() ){
map.remove( iterator.next() );
}
}
Usage:
clearMap( applicationScope )

Share and enjoy!

Tuesday, June 22, 2010

XPages: Adding default value for empty view columns

Go to source mode, and find the column you want to add the default text. Add a computed field that's rendered when:
!( this.getParent().getValue() )

E.g.
<xp:viewColumn columnName="notes" id="colNotes">
<xp:viewColumnHeader value="Notes"></xp:viewColumnHeader>
<xp:text value="No notes"
rendered="#{javascript:return !( this.getParent().getValue() );}" />
</xp:viewColumn>

this.getParent() returns the cell. If the cell has no value, the computed field is visible.

You can achieve the same in the Notes column for the view (@If...), but I don't like mixing data and presentation.

Thursday, June 17, 2010

XPages: Code snippet for Multi Value Custom Converter

I got a question regarding how to work around the buggy multi-value implementation in XPages. I use a custom converter for my multi value fields.

var Converters = {  
multivalue: {
// separator: String or RegExp
getAsObject: function( valuesString, separator ){
try {
separator = separator || ',';
var values = valuesString.split( separator );

// Trims empty values
var trimmedValue, trimmedValues = [];
for( var i = 0; i < values.length; i++ ){
trimmedValue = values[i];
// Removes leading and trailing white-space
if( trimmedValue ){
trimmedValues.push( trimmedValue.replace( /^\s+|\s+$/g, '' ) );
}
}
return trimmedValues;
} catch( e ){ /* Exception handling */ }
},
getAsString: function( values, separator ){
try {
if( values.constructor !== Array ){ values = [ values ]; }
separator = separator || '\n';

return values.join( separator );
} catch( e ){ /* Exception handling */ }
}
}
}
Put the above code snippet inside a script library. Add a custom converter to the multi value field.

In getAsObject -> Converters.multivalue.getAsObject( value, separator );
In getAsString -> Converters.multivalue.getAsString( value, separator );

getAsObject is the conversion of the submitted value to the stored value.
getAsString is the conversion of the stored value to a displayable string value.

Examples:
// Split on comma, semi colon and any white space 
Converters.multivalue.getAsObject( value, /,|;|\s/ );

// Show values comma separated
Converters.multivalue.getAsObject( value, "," );
value is a global variable that's available in the conversion. For getAsObject it's the submitted string. For getAsString, it's the stored value.

Wednesday, June 16, 2010

XPages: Bug? View XPage Source code in the browser

Edit: This bug has been confirmed fixed in Domino 8.5.1 FP1.

I just read this in my daily RSS roundup.

Add a + after .xsp in the address field of your browser results in the source code of the current XPage being printed to the browser (click above link for screenshot or try for yourself). It's not sure how I feel about this. In most cases it won't be a security issue, but still..

Wednesday, June 2, 2010

XPages: Avoid saving duplicate documents on browser refresh

Update 22.06.10: Parameters are carried over when the redirect happens.
Update 02.09.10: Parameters starting with _ are stripped when redirect occurs

A reader wanted to know if there was some way to avoid duplicate documents when saving a document, refreshing the browser, and pressing ok on the resulting dialog. I wasn't able to recreate the problem of duplicate documents, but here's a workaround for the "POST" dialog. It should also take care of any duplicate documents on browser refresh.

In JSF lingo, this is known as the POST-Redirect-GET pattern. This is quite easy to accomplish in XPages.

On the save button, put the save action, and an Execute Script action. In the script part, call on the function below (put it in a SSJS Script Library/add it to the page). This makes the server redirect to the document in a get request, and you avoid the double posting problem.

function redirectToCurrentDocument( switchMode:boolean ){
try {
if( typeof currentDocument === 'undefined' || viewScope.stopRedirect ){ return; }
// Gets the name of the XPage. E.g. /Person.xsp
var page = view.getPageName();

// Finds extra parameters, strips away XPages parameters/trims leading &
var parameters = context.getUrl().getQueryString().substring(1);
var extraParameters = parameters.replace(
/([&\?^]*_[a-z0-9=]+[&]{0,1})|(action=\w{4}Document[\&]{0,1})|(documentId=[\w]{32}[\&]{0,1})/ig, '').replace( /\&$/, '' );

// Gets the unid of the current document
var unid = currentDocument.getDocument().getUniversalID();

// Sets/changes the action according according to the mode the document is in
var isEditable = currentDocument.isEditable();
if( switchMode ){ isEditable = !isEditable; } // false -> true / true -> false

var action = ( isEditable ) ? 'editDocument' : 'openDocument';

// Open the current document via a get request
var redirectUrl = page + '?documentId=' + unid + '&action=' + action;
if( extraParameters ){ redirectUrl += '&' + extraParameters; }
context.redirectToPage( redirectUrl );
} catch(e){ /* Exception logging */ }
}

Tuesday, May 18, 2010

DateConverter tip - get weeknumber in one line

If you want to get the week number from a date:
DateConverter.dateToString( date, 'w' )
The DateConverter script

Friday, May 7, 2010

XPages: Severe bug in the partial event hijacker

Update 11.10.2010 Found another bug. onComplete can sometimes be a string. Fixed.

For the life of me, I couldn't understand why the onStart/onError/onComplete events didn't fire for partial refreshes. Today it suddenly dawned on me. It was because of my partial event hijacker.

The previous version overwrote the onStart/onError/onComplete events. Below is the fixed code. Sorry if you've used the hijacker and suffered the same fate as me.

function hijackAndPublishPartialRefresh(){
// Hijack the partial refresh
XSP._inheritedPartialRefresh = XSP._partialRefresh;
XSP._partialRefresh = function( method, form, refreshId, options ){
// Publish init
dojo.publish( 'partialrefresh-init', [ method, form, refreshId, options ]);
this._inheritedPartialRefresh( method, form, refreshId, options );
}

// Publish start, complete and error states
dojo.subscribe( 'partialrefresh-init', function( method, form, refreshId, options ){
if( options ){ // Store original event handlers
var eventOnStart = options.onStart;
var eventOnComplete = options.onComplete;
var eventOnError = options.onError;
}

options = options || {};
options.onStart = function(){
dojo.publish( 'partialrefresh-start', [ method, form, refreshId, options ]);
if( eventOnStart ){
if( typeof eventOnStart === 'string' ){
eval( eventOnStart );
} else {
eventOnStart();
}
}
};

options.onComplete = function(){
dojo.publish( 'partialrefresh-complete', [ method, form, refreshId, options ]);
if( eventOnComplete ){
if( typeof eventOnComplete === 'string' ){
eval( eventOnComplete );
} else {
eventOnComplete();
}
}
};

options.onError = function(){
dojo.publish( 'partialrefresh-error', [ method, form, refreshId, options ]);
if( eventOnError ){
if( typeof eventOnError === 'string' ){
eval( eventOnError );
} else {
eventOnError();
}
}
};
});
}

Thursday, May 6, 2010

XPages: Simple string encrypter/decrypter

I needed a simple encryption routine today. I found this article with accompanying code.

Here's the routine ported to Server Side JavaScript:
function StringEncrypter( encryptionScheme:String, encryptionKey:String ){
try{
encryptionScheme = encryptionScheme || this.DESEDE_ENCRYPTION_SCHEME;
encryptionKey = encryptionKey || this.DEFAULT_ENCRYPTION_KEY;
if( encryptionKey.length < 24 ){
throw new java.lang.Exception(
'Encryption key was less than 24 characters.' );
}
var keyAsBytes = encryptionKey.getBytes( this.UNICODE_FORMAT );

switch( encryptionScheme ){
case this.DESEDE_ENCRYPTION_SCHEME:
this.keySpec = new javax.crypto.spec.DESedeKeySpec( keyAsBytes ); break;
case this.DES_ENCRYPTION_SCHEME:
this.keySpec = new javax.crypto.spec.DESKeySpec( keyAsBytes ); break;
default:
throw new java.lang.Exception(
"Encryption scheme not supported: " + encryptionScheme );
}

this.keyFactory = javax.crypto.SecretKeyFactory.getInstance( encryptionScheme );
this.cipher = javax.crypto.Cipher.getInstance( encryptionScheme );
} catch( e ){ /* your exception handling */ }
}

StringEncrypter.prototype = {
DESEDE_ENCRYPTION_SCHEME: "DESede",
DES_ENCRYPTION_SCHEME: "DES",
DEFAULT_ENCRYPTION_KEY:
"The ships hung in the sky in much the same way that bricks don't.",
UNICODE_FORMAT: "UTF8",

keySpec: null,
keyFactory: null,
cipher: null,

encrypt: function( unencryptedString ){
if( !unencryptedString ){ return ''; }

try {
var key = this.keyFactory.generateSecret( this.keySpec );
this.cipher.init( javax.crypto.Cipher.ENCRYPT_MODE, key );
var clearText = unencryptedString.getBytes( this.UNICODE_FORMAT );
var cipherText = this.cipher.doFinal( clearText );

var base64encoder = new sun.misc.BASE64Encoder();
return base64encoder.encode( cipherText );
} catch( e ){ /* your exception handling */ }
},
decrypt: function( encryptedString ){
if( !encryptedString ){ return ''; }

try {
var key = this.keyFactory.generateSecret( this.keySpec );
this.cipher.init( javax.crypto.Cipher.DECRYPT_MODE, key );
var base64decoder = new sun.misc.BASE64Decoder();
var clearText = base64decoder.decodeBuffer( encryptedString );
var cipherText = this.cipher.doFinal( clearText );

return new java.lang.String( cipherText );
} catch( e ){ /* your exception handling */ }
}
}
Example usage:

Encryption
var encrypter = new StringEncrypter();
encrypter.encrypt( unencryptedString );
Decryption
var encrypter = new StringEncrypter();
encrypter.decrypt( encryptedString );
As you can see from the constructor, you can specify your own encryption key/choose from two encryption methods.

I simply ported the code, so if you want more details, read the article from which I snagged the code.

Monday, May 3, 2010

Using the Rhino JavaScript engine in XPages

Not sure how useful this is yet, but if you want to use the Rhino engine with XPages, here's how:
var rhinoEngine = new javax.script.ScriptEngineManager().getEngineByName(
"JavaScript");
// to eval JavaScript string: rhinoEngine.eval( jsString );

Oracle Tutorial

General API for javax.script.ScriptEngine

Java 6 uses Rhino v1.6R2 (documentation/etc)


One thing that just came to mind is maybe to use Rhino where IBM JScript fails.

E.g.
String.prototype.rhinoMatch = function( regExpString, modifiers ){
var rhino = new javax.script.ScriptEngineManager().getEngineByName("JavaScript");
rhino.eval(
'var result = "' + this + '".match( ' + regExpString + modifiers + ' )' );
return rhino.get( 'result' )
}

'{lastName}, {firstName}'.rhinoMatch( '/\\{(\\w+)\\}/', 'g' );
// returns array, ["{lastName}", "{firstName}"]

Saturday, May 1, 2010

XPages: Dynamically binding and unbinding server side events

Back in my early XPages career, I asked the readers if it was possible to Dynamically bind server side events.

The only answer I got was this:
The short answer: NO.
The long: it is somewhere on the list.


I don't like those kinds of answers.

After todays experiment, I can firmly say: Yes, it's possible!
This is what you'll be able to do with the simple framework provided in the demo:

  • Dynamically bind/unbind custom events

  • Bind multiple event handlers to one event

  • Run the event in a specified scope

  • Use regular HTML elements as event triggers


This started out as an experiment into Server Side Event Delegation. A couple of nights later, I further improved on the functionality.

In the slumbering stage this morning, I discovered that I was on the path for something, but not what I initially thought I had started out for. The two first experiments lead to what would have been a complex unusable blob of functionality.

Today, I did some cleanup. I removed all the class attributes, and rewrote the framework code to be more flexible. The previous two version relied on an eventHandler node in the top of the XPage, which results in a generated script tag. This implementation doesn't add any script tags to the generated html.

The server side part of the code lets you specify what to do for certain events. The client side part of the code specifies what server side event to run, what data to send to the event handler (optional), and which part of the page should be updated after the event has run.

I did a little screen capture of the demoapp in action (not visible in Google Reader):





Simple Server Side Event Delegation:
The two first buttons toggles (when active) visibility of a couple of computed fields. The third button binds/unbinds the event handler for the two preceding buttons.

Multiple Event Handlers:
The first button triggers a custom event on the server. The second button adds an event handler to the event. As you can see from the demo, for each event handler added, the addition increases by 1.

Event handler running in a specified scope:
The event handler for the event runs in the scope of an object on the server. Then the event is triggered, a viewScope variable is created from a property fetched from the object ( viewScope.put( 'theAnswer', this.theAnswer ) )

In View Edit:
When the name of the person is clicked, an "edit" event is triggered. When the ok button is clicked, a "save" event is triggered.

If you add the event handlers afterRestoreView or before-/afterRendreResponse, an event handler is added for every partial refresh. Adding them in an event handler or in the afterPageLoad event should be safe.

>> Download Demoapp

If you don't like a lot of generated server side script tags for events/embedding the event code in control after control, this is definitely worth looking at.

If you find any bugs/want more info about something, leave me a comment.

Share and enjoy!

Wednesday, April 28, 2010

XPages: Code snippet for number/string conversion

The DecimalFormat object is cached in the application scope due to performance/memory concerns.

The default locale for the conversion seems to be the locale of the OS on the server, so if you want to support multiple locales, you'd have to extend the functionality.

Example usage (Norwegian locale):
String to number
NumberConverter.stringToNumber( '15,55', '0.00' ) -> java.lang.Double

Number to string
NumberConverter.numberToString( 15.926, '0.00' ) -> "15,93"

var NumberConverter = {
stringToNumber : function(numberString, patternString) {
try {
if( !numberString ){ return null; }

var formatter = NumberConverter.getFormatter(patternString);
return formatter.parse(numberString);
} catch( e ){ Debug.logException( e ); }
},
numberToString : function(number, patternString) {
try {
if( !number ){ return null; }

number = new java.lang.Double(number);
var formatter = NumberConverter.getFormatter(patternString);
return formatter.format(number);
} catch( e ){ Debug.logException( e ); }
},
getFormatter : function(patternString) {
try {
var cacheKey = 'numberFormatter' + patternString;
var numberFormatter = applicationScope[cacheKey];
if(!numberFormatter) {
numberFormatter = java.text.DecimalFormat(patternString);
applicationScope[cacheKey] = numberFormatter;
}

return numberFormatter;
} catch( e ){ Debug.logException( e ); }
}
}

Tuesday, April 27, 2010

XPages: Server Side Event Delegation - In view edit

This is a continuation of the previous investigation into the possibilities of Server Side Event Delegation (SSED).

As I mentioned at the end of the previous blogpost, the demoapp didn't take advantage of Client Side Event Delegation (CSED). This resulted in having to create the same amount of client side event listeners as was generated by the server. If you have repeat controls/view events, this can amount to quite a bit of script tags (html to download). The event listeners themselves can take up quite a bit of memory, and may make the page act a little sluggish.

Here's the generated script tags for events in this demoapp:
XSP.addOnLoad(function() {
  XSP.attachPartial("view:_id1:eventDelegator", "view:_id1", null, "foo", ...
  XSP.attachPartial("view:_id1:peopleView:pager1__Group__lnk__1", null, ...
  XSP.attachPartial("view:_id1:peopleView:pager1__Group__lnk__2", null, ...
  XSP.attachPartial("view:_id1:peopleView:pager1__Group__lnk__3", null, ...
  XSP.attachPartial("view:_id1:peopleView:pager1__Group__lnk__4", null, ...
  XSP.attachPartial("view:_id1:peopleView:pager1__Next", ...
});

One for the global event delegator event handler, and one per link in the view pager. The view has thirty "click to edit" cells, and a couple of buttons from the previous version. Not bad. :)

The experiments into SSED is partially due to the aforementioned reasons, curiosity, and my eternal struggle to find smarter ways to work. Only time will show if it fulfills all of the above.

Since the previous experiment, the demoapp has grown a bit. I've added a couple of more class attributes:
  • delegate-click-parent: makes a component a clickable area
  • click-trigger: when parent has delegate-click-parent class/styleClass, elements with this class, when clicked, triggers the server side event delegator
  • refresh-[refreshId]: previously refresh-target-[refreshId], the target of the partial refresh
  • action-[name]: used by the server side event delegator to determine what action to take
  • data-[value]: can be used on the server side for various things. E.g. unid of a row in a view
The editable columns work like this:
The view has the delegate-click-parent class. This makes all the child elements with a class attribute of click-trigger activate the server side event delegator.

I created a custom column. This lets me put other controls into the column.

Inside the column, I put a panel, a field and a button. A cell is editable when a scoped variable containing unid is the same as the unid of the row that the cell belongs to.

The panel has a computed styleClass attribute. When the cell isn't editable, the panel acts as a click trigger. One of the class attributes is the previously mentioned data-class. This contains the unid of the row the cell belongs to. This unid is put into the previously mentioned scoped variable, making the cell editable.

The field has value="" and defaultValue="#{row.columnName}". This makes it possible to edit the field/have it take the value from the appropriate row/column. The field has a readonly attribute that's computed based upon if the cell is editable.

The button is hidden when the cell isn't editable. When clicked, it triggers a save and resets the editable row. The client side id of the button is passed along to the server. This makes it possible to get the name of the field/get the value from the params-map (contains all submitted values).

E.g.
component id of field/button -> field/button
client side id of button: view:_id1:button
client side id of field: view:_id1:field

No more than a simple replace to find the correct field name :)

That's more or less it. If this sounds like something interesting, take a look at the demoapp/code, and leave a comment if you're curious about something.

>> Download Demoapp

Share and enjoy!

Thursday, April 22, 2010

XPages: Server Side Event Delegation

Update 27.04.10: I've updated the demoapp with In View Edit functionality

While working on a view with in view edit functionality today, I was appalled over how much client side code was generated.

I guess my subconsciousness was working on the problem, as I came up with a hack that lets you delegate events, while eating dinner (almost forgot the comma). It's somewhat similar to the event delegation that you would do in a browser. The current implementation has its definite limits, but I think it's a nice proof of concept.

Without the Programmatically Triggering an XPages Server Side Event Handler from a Client Side Script article by Jeremy Hodge, I don't think I would have come up with this hack, so thank you for sharing, Jeremy!

The short and simple explanation of the hack:
A global event handler is added to the XPage:
<xp:eventHandler id="eventDelegator" event="foo"
  action="#{javascript:delegateEvent()}" submit="true" refreshMode="partial"
  refreshId="#{javascript:param.$$xspsubmitvalue}" />

I use foo as the event name, so that the generated client side event handler has no possibility of being triggered.

I've added some client side JavaScript that acts upon elements with certain class attributes.
delegate-click: Clicks on an element with this class triggers the server event handler.
refresh-target-componentId: The component id of the control that should be updated.

The code could easily be extended to handle other events. Out of curiosity, I tested onmouseover and onfocus. Both work great.

When an event is triggered, param.$$xspsubmitvalue is set to the id of the component I want to update. As you can see in the above XPages source code snippet, the refreshId of the event is computed to this value. This means that only the requested component is updated. The eventDelegator-function that is triggered by the event uses also uses this value to determine what server side action should be executed.

In the attached demoapp, a viewScope value is altered depending on the refreshId. This toggles a couple of panels, which have computed their visibility based upon the scoped variable.

As for the limitations..

The component that is the target of the refresh has to be visible, as its generated id (clientId) is necessary to get the partial update to work.

This version of the hack doesn't do much about the problem of a lot of active event listeners for my "in view edit" problem. It only results in less generated code. To fix this, I'd have to make the client side code smarter. E.g. add client side event delegation. The container could have a delegate-child-clicks class. The children that should fire the events could have maybe have a click-trigger class. Anyways, that's for a future proof of concept.. :)

The reason I decided to post this experiment is that I hope that it can inspire other XPages developers to make something cool, as I feel Jeremy's post did for me.

Here's the demoapp for you to probe and inspect

Share and enjoy!

Thursday, April 8, 2010

XPages: Code snippet for date/string conversion

The SimpleDateFormat object is cached in the application scope due to performance/memory concerns (I have data tables generated from JSON with up to several thousand conversions).

Example usage:
String to date
DateConverter.stringToDate( '12.12.2001', 'dd.MM.yyyy' ) -> java.util.Date

Date to string
DateConverter.dateToString( @Now(), 'dd.MM.yyyy hh:mm' ) -> 08.04.2010 12:01

var DateConverter = {
 dateToString: function( date:java.util.Date, pattern:String ){
  try {
   if( !date ){ return ''; }
  
   var formatter = DateConverter.getFormatter( pattern );
   return formatter.format( date );
  } catch( e ){
   // ErrorHandling
  }
 },
 
 stringToDate: function( dateString:String, pattern:String ){
  try {
   if( !dateString ){ return null; }
  
   var formatter = DateConverter.getFormatter( pattern );
   return formatter.parse( dateString );
  } catch( e ){
   // ErrorHandling
  }
 },
 
 getFormatter: function( pattern:String ){
  try {
   var cacheKey = 'dateFormatter' + pattern;
   var dateFormatter = applicationScope[ cacheKey ];
   if( !dateFormatter ){
    dateFormatter = new java.text.SimpleDateFormat( pattern );
    applicationScope[ cacheKey ] = dateFormatter;
   }
   
   return dateFormatter;
  } catch( e ){
   // ErrorHandling
  }
 } 
}

Wednesday, April 7, 2010

XPages: Adding controls inside a pager

I haven't read anyone else writing about this, so I thought I should share this technique with you.

While working on a custom search data table, I wanted to show the number of entries in the result. I wanted the pagination links to be on the left of the pager bar, and the number of entries on the right side.

At first I started mucking about with CSS, but that ended up being a maintenance nightmare as the search control I was developing was to be used in a number of pages. I noticed that the pager control had the xp:key attribute. This attribute is also used by the editable area control that you can put inside a custom control. I noticed when adding multiple controls to the editable area, that the generated source code wrapped the controls inside a panel with an xp:key attribute.

Tried the same in the data table control and the view control, and it works. If you want to have your own controls in the header pager area, simply add an xp:panel-tag in the source code, with the attribute, xp:key="headerPager". If you want a custom footer, create an xp:panel-tag with the attribute xp:key="footerPager". If you already have a pager there, simply wrap it in the aforementioned panel, and remove the xp:key attribute from it, and it should work without the need for further modification.

Example of use:

Unfiltered view


Filtered view

Renaming fields with formula agents

Put the code below in a formula agent. Set the old field names in the _fieldNamesFrom-list, and the new field names in the _fieldNamesTo-list. Not sure if it works correctly for NotesRichTextItems, but it should work on the other field types.

If there isn't a field with the old fieldname, a new one isn't created (in case you need to run the agent multiple times -> doesn't blank the new fields).

_fieldNamesFrom := "silly_fieldname" : "tpyo_fieldname";
_fieldNamesTo := "crazy_fieldname" : "correct_fieldname";

@For( i := 1 ; i <= @Elements( _fieldNamesFrom ) ; i := i + 1 ;
 _valueFrom := @GetField( _fieldNamesFrom[i] );
 @If( _valueFrom = "" ; "" ; @SetField( _fieldNamesTo[i] ; _valueFrom ) );
 @SetField( _fieldNamesFrom[i] ; @Unavailable )
);
SELECT @All

I post this as much for myself as for you, as I tend to delete the renaming agents after they've done their duty.

Tuesday, April 6, 2010

I've added a comments feed

Just wanted to let you know that I've added an RSS feed for comments.

The feed

Friday, March 26, 2010

XPages: Computing the category label in categorized columns

I stumbled on this one while helping a colleague.

He had a categorized totals view that was categorized on id's referencing documents in another database. He wanted the category label to show the a value from the document the id came from. That's easy I told him (I hadn't worked with categorized views in XPages), just use @DbLookup..

Turns out it's not that straightforward. If you edit the value in the Data panel under properties, the column loses it's "connection" (the columnName attribute) to the categorized column in the underlying Notes View.

Edit property


Results in columnName disappearing


When columnName disappears, the column becomes a custom column, and the categorization (twisties/etc) disappears.

To be able to compute the label of the category, you have to go into the source and add the columnName property after you've computed the value.

What it should look like


That's all there's to it..

Share and enjoy!

Do you want -real- Server Side JavaScript? Vote!

The current implementation isn't according to spec (as I've ranted about a couple of times). If you want to change that, it's time to go to the voting booth.

Make Server Side JS standards compliant

Wednesday, March 17, 2010

I'll be appearing on The XPages Blog

Bruce Elgort offered me a space on The XPages Blog a little while back. I happily accepted the offer, so I'll soon start cross posting relevant XPages content on this blog and on The XPages Blog.

Thanks for the opportunity Bruce!

Clean up excessive white space in XPages source code

Use Find/Replace

Find: \>\s*\n\s*\n+
Replace: >\n
Check Regular expressions and Wrap search

After you're removed the excessive line breaks, press Ctr+Shift+F to format the source code.

XPages: Design Definition and Editable Area-controls

If you want to have an include control/use a design definition, simply copy/paste the source code from the include control into the design definition.

+1 point to IBM :)

Monday, March 8, 2010

XPages: Making validation behave properly

Update 06.10.10 Slimmed the code

Validation in XPages is sometimes extremely hard to work with. Especially when you have partial updates on the page.

While looking for a way to make it easier to work with partial updates on a page with validation, I stumbled onto this blogpost (JSF). It describes calculating the required property to determine when the validation should execute.

While we can't apply the technique discussed in the above blogpost, we have another tool at our hands, $$xspsubmitid. This field contains the id of the event handler that triggered the update.

I wrote a function that lets you test if a specific component triggered an update.
// Used to check which if a component triggered an update
function submittedBy( componentId ){
try {
var eventHandlerClientId = param.get( '$$xspsubmitid' );
var eventHandlerId = @RightBack( eventHandlerClientId, ':' );
var eventHandler = getComponent( eventHandlerId );
if( !eventHandler ){ return false; }

var parentComponent = eventHandler.getParent();
if( !parentComponent ){ return false; }

return ( parentComponent.getId() === componentId );
} catch( e ){ /*Debug.logException( e );*/ }
}


If you only want the validation to run when the user clicks a specific button, write this in all the required-attributes:
return submittedBy( 'id-of-save-button' )

id-of-save-button is the id of the component that triggers a save.

The above code results in the validation only executing when the document is saved. No more broken partial updates.

The downside to this technique is that you have to compute all required-attributes, but I personally think that's a small price to pay to have the XPage behave as expected.

Update:
Julian Buss posted a very valid question. What about the other validators? I did a little test, and here's what I came up with.

For the other kinds of validators, you should be able to use an if-statement to conditionally execute the validator. I took a look at the generated java code for the validators, and from what I can tell, you can put JavaScript statements inside all of them.

E.g. for constraint validators:
if( submittedBy( 'id-of-save-button' ) ){ return /\d+/; }

For big expression statements, it's probably better to do something like this at the top of the script:
if( !submittedBy( 'id-of-save-button' ) ){ return true; }

Share and enjoy!

Friday, March 5, 2010

XPages: Tip regarding private scoped variables inside a custom control

One sure way to have a dynamic "private" variable inside a custom control is to name it using the clientId of from one of its components.

Example:
Create a panel that wraps the content of the custom control. Set an id on the panel to wrapper.

To set the scoped variable:
viewScope.put( getClientId( 'wrapper' ), 'someValue' )

To get the scoped variable:
viewScope.get( getClientId( 'wrapper' ) )

This way, you can use the control multiple times in an XPages without having to worry about one custom control overwriting another controls scoped variables. If you need more "private" scoped variables, simply use the clientId as the base. E.g. 'title-' + getClientId( 'wrapper' ).

If you want to use private scoped variables as data source for fields, you need to bind the variables dynamically.

Wednesday, March 3, 2010

XPages: Dynamically binding document events without breaking existing

Update 19.03.10: I rewrote the script library so that it executes the dynamically added event handlers in a queue, instead of trying to modify the expression string of the embedded event handler.

Update 29.04.10: Fixed a bug in the script. var dataSource = this.getDataSource( datasourceId );. dataSourceId was missing, resulting in only the first document data source being returned.


One of many things I miss in XPages is the possibility to dynamically bind multiple event listeners per document event (e.g. querySaveDocument). You can dynamically bind a single document event quite easy.

In beforePageLoad:
var application = facesContext.getApplication();
var methodBinding = application.createMethodBinding( '#{javascript:yourCode}', null);

// get(0) -> The first data source in the page
view.getData().get(0).setQuerySaveDocument( methodBinding );

The problem with the above code, is that if someone puts querySaveDocument code in the XPage you're setting the above event, that code is overwritten/ignored.

Here's some server side javascript that lets you add document event code without breaking existing code. I suggest running this function beforePageLoad.

Example usage (beforePageLoad):
Events.addToDocumentEvent( 'querySaveDocument', 'xspTestDoc.setValue( "description", "Overridden" )' );

If you're wondering why I'd want to bind events dynamically..


In an XPages application I'm working on, I've made a custom control that's similar to the "prevent save conflicts" custom control I wrote about a little while back.

The difference between the one I posted, and the one in the application is that it uses a custom field on the document. The field is used to test if the document has been modified by a user. When a user saves a document, the field value is set to @Now().

The custom control I wrote about earlier uses the "@Modified-value" that's set whenever a document is modified. I only want the validation to trigger when a user has modified the document/another user has the document open for editing.

The custom control in the application I'm working on dynamically adds a little querySaveDocument code so that the modified-by-user field is set when the document is saved. This code has to work alongside existing code in the querySaveDocument-event.

--

I probably couldn't have written the above function without my API Inspector. If you want to hack around the limitations of Domino Designer, I suggest you take a look at it.

If you're having a hard time debugging XPages, I suggest you take a look at the Medusa project written by three wizards from Lotus911.

Tuesday, March 2, 2010

Small cleanup of the blog layout

I removed some of the less used tags from the tag list/changed it into a tag cloud.

I also removed the blogroll, as it's very outdated. I try to follow most of the technical Domino blogs. I think I have between 70 and 90 Notes/Domino blogs that I subscribe to.

XPages: Full featured CKEditor integration

So I finally cracked the CKEditor+XPages nut. It may be considered a little hacky, but it works.. :)

The following technique lets you edit RichText fields using a regular text area controls.

Add two components per RichText field. One for edit-mode (Multiline Edit Box), and one for read mode (Computed field/String/HTML). Bind both to a scoped variable, and compute visibility. In the example below, I've called the scoped variable ckEditorBody. body is the name of the RichText field being edited.

In the beforeRenderResponse-event, fetch the value from the RT-field, and put the value in the scoped variable set to the field.
if( !currentDocument.isNewNote() ){
var doc = currentDocument.getDocument();
var rtBody = doc.getFirstItem( 'body' );
viewScope.put( 'ckEditorBody', rtBody.getUnformattedText() );
}
In the querySaveDocument-event for the document data source, take the scoped variable value, and put it inside a new (delete the old one) NotesRichTextItem on the domino document.
function updateBody( doc:NotesDocument, fieldName:String, value:String ){
var rtStyle:NotesRichTextStyle = session.createRichTextStyle();
rtStyle.setPassThruHTML( 1 );

doc.removeItem( fieldName );
var rtField:NotesRichTextItem = doc.createRichTextItem( fieldName );
rtField.appendStyle( rtStyle );
rtField.appendText( value );
}

var doc:NotesDocument = currentDocument.getDocument();
updateBody( doc, 'body', viewScope.ckEditorBody );
The reason I've created an updateBody-function is for re-usability. This code would normally lie inside a Server Side JavaScript library.

I've updated the previous CKEditor demoapp using the aforementioned technique, so that it's no longer 32k limited.

Prerequisites for running the demo

>> Download demoapp

Tested on Opera 10.50, Firefox 3.6 and IE 7/8.

Saturday, February 27, 2010

XPages: Use converters to emulate domino computed fields

I don't have much time to write this (people to meet, stuff to do), so I'll keep it short.

One of the things I really missed going from standard Domino development to XPages development is the (Domino) computed fields.

When I woke up this morning, I thought "what about converters?". Tested it out, and it works! :)

In the getAsObject-code (all properties), you write the code that computes the field.
var numItems = getComponent( 'numItems' ).value;
var price = getComponent( 'price' ).value;
return (numItems && price) ? numItems * price : 0;
The above example stores the value as number, so in getAsString, you have to convert the number to string (value is a local variable containing the value of the field):
value.toString();
I didn't have much time to test, so I don't know all the weaknesses with this technique. One thing I found. If the field is set to read only, the converter is ignored/the value is static.

The technique should work great for hidden fields that have dynamic values. It lets you have all the computing code relevant to the field in the field (instead of setting the field form other fields' events, or in XPages events).

If you can think of other ways to use this technique, please leave a comment.

Monday, February 22, 2010

XPages: Using an array of objects in property definitions

If you want to use a complex JavaScript object in property definition for a custom control, write object in Type.

Example:

Wednesday, February 17, 2010

XPages API Inspector V2

The changes from the previous version are quite big (as you can see from the screenshots below).

From what I can remember:
* Methods are now sorted alfabetically

* If the returned class/the class of the (inherited or local) method is
from the JSF 1.0 or Java 6 API, I've added a direct link
into Sun's APIs. Google search link always added.

* Declared Methods, Methods, Declared Fields and Fields are organized in
their own section.

* Each section has the number of contained items next to the title

* Private methods/fields have been removed from the report

(Google idea robbed and deployed from Nathan T. Freeman google)

Overview:


Methods:


The best thing? I only needed an evening to make the changes. That's RAD!

>> Download from OpenNTF
No login is required for download. If you don't like version 2 (let me know why), version 1 is also available.

As with the previous version, all code is contained in the custom control. This is sometimes a headache to work with (for me), but it makes it extremely easy to implement the control.

Let me know if there are any bugs.

Share and enjoy!

Update: dijit.Dialog custom control fixup

Just wanted to let you know that I've updated and improved the demoapp from yesterdays post.

The custom control is self-contained, but I recommend moving the client side script at the bottom of the source code into a javascript library.

I've implemented Mark Hughes code (thanks for the tip, Julian), which is a lot more general than my hacks.

>> Download demoapp

Let me know if you find any bugs..

XPages: Use MooTools in Server Side JS

I wanted to filter/extract values from an array of objects today. Before I started reinventing the wheel, I took a look at MooTools' server side library. It contains a lot of syntactical sugar.

The code needed to extract email addresses from an array of JS-objects, using MooTools (data is an array of objects):
var emailAddresses = data.map(function(item){
return item.email || null;
}).clean();

Download here.
General MooTools documentation here.

Tuesday, February 16, 2010

XPages: Making dojo dialogs works with server-side events

Julian gave me a link to a cleaner way of making dialogs work. I recommend using that method instead of what's below.

As I've mentioned several times, I'm currently working on a larger project that relies heavily on XPages technology.

One of the widgets I felt the need for in this project is the modal dialog box. Dojo has two inbuilt widgets that provides this functionality, dijit.Dialog, and dojox.widget.Dialog.

As Julian Buss points out, you'll stumble onto a couple of big problems when using these widgets in XPages (out of the box). Server side events aren't triggered, and field values aren't posted to the server. I've used more time than I want to admit, trying to find out why server side events doesn't work. The answer is quite simple.

XSP.getForm

Actions that triggers server side code (partial-/full refresh) need to know which <form> to post. When an event is triggered, the DOM tree is traversed upwards until a form is found/the top of the tree is reached. If a form isn't found, the event is ignored. The dialog widgets in Dojo are moved to the bottom of <body>, outside any form tags.

Client side JS to "fix" XSP.findForm:
/*
Modifies XSP.findForm so server side events are fired correctly when
dojox/dijit dialogs are used.
- First try the native findForm-method to find the correct form
- Then try to find a parent element with the attribute form_id (used specifically in ccModalDialogWeb in this demoapp)
- If everything else fails, return the first form on the page.
*/
dojo.addOnLoad(function( el ){
var oldFn = XSP.findForm;
XSP.findForm = function( el ){
var form = oldFn.apply( this, arguments );

if( !form ){ // Look for element with form_id attribute
el = (typeof el === 'string' ) ? dojo.byId( el ) : el;
if( el && el.nodeName ){
while( el && el.nodeName !== 'BODY' ){
var formId = el.getAttribute( 'form_id' );
if( formId ){ // form_id found -> get form by id
form = dojo.byId( formId );
break;
}

el = el.parentNode;
}
}
}

return form || document.forms[0];
}
});

Only server side events from the the dojo dialog (/any other elements triggering server side events outside forms) are affected by this code. First the original XSP.getForm is tested (so that "standard" events arent't broken). If no form is found, the DOM tree is traversed upwards looking for an attribute, form_id, which can be added to one of the parent elements of the dialog content. If form_id is found, it's used to locate the correct form. If that also fails, the first form on the page is used. I use form_id in the demoapp. This is not a native attribute, so you have to generate this yourself. getForm.getClientId(facesContext) returns the browser-id of the parent form.

The above code doesn't fix the problem with fields being moved outside the form. In the demoapp, I've made a simple workaround for this (only for input-fields). Basically you have to clone the fields in the dialog, and copy them onto the correct form. Add an onChange event to the dialog fields, so that the clones get the correct value.

If you decide to implement the custom control in your own projects, and need workaround for other field types, let me know.

The demoapp contains a custom control that let's you open another custom control/xpage inside another XPage. If you use a custom control for the dialog, the custom control should have full access to the XPage that contains the dialog.

The interactive part of the demoapp is extremely simple. The "gold" is the custom control ccModalDialog, and the client side script, ccModalDialog.js.

Hopefully IBM implements a standard control for modal dialogs. It's not impossible to make perfect. It only requires time, knowledge and patience.

>> Download demoapp

Share and enjoy!

Wednesday, February 10, 2010

IBM Support Assistant

Just discovered the IBM Support Assistant search. It seems to be a mashup of different search engines.

Available in Domino Designer from
Help -> Support -> IBM Support Assistant


Monday, February 1, 2010

Another small update on the XPages API Inspector

I've added a clear-button, and a subheader with the toString-value that's visible if the expression corresponds with a server side object.

@Now()


>> Download tool

>> Original post with description

Thursday, January 28, 2010

XPages: Using the powerful Design Definition in custom controls

I've recently started using the Design Definition in my custom controls to save space on the XPages they are included in.

When I first encountered this new (since Domino 8.5.1) feature, I believed it was a static feature. It turns out it's highly dynamic, and quite powerful.

Immediately, when I read the "Providing illustration for your XPage custom controls" article from the Lotus Notes and Domino Application Development wiki, I thought of JSP. What reminded me of JSP was this little snippet:
<%=this.viewName %>

I've only tested if/else statements, as I know very little JSP/haven't had the need for anything else. If you know JSP/have a blog, please test out this feature, and share your findings.

Included in todays demoapp, there is a custom control that lets you open up a "remote" Dojo dialog (dijit.Dialog). The developer can choose between using a link and a button as the "launcher". This is controlled by the property definition in the custom control.

The design definition for the control:
<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
<% if(this.activationControl==='link' ){ %>
<a href="#"><%=this.showDialogLabel || 'DojoDialogLink' %></a>
<% } else { %>
<button>
<%if( this.btnImage ){%><img src="<%=this.btnImage %>" /><%}%>
<%=this.showDialogLabel || 'DojoDialogButton' %></button><%
}%>
</xp:view>


this is (amongst other things?) the compositeData-object that contains all the properties from the property definition. this.btnImage in the design definition is the same as compositeData.btnImage in the custom control.

If you set the custom control to use a link to launch the dialog, the custom control shows up as a link in the XPage it's included in. If you change that to a button, immediately the representation of the custom control changes into a button. If you haven't typed a label for the link/button, it has the label DojoDialogLink/DojoDialogButton, depending of the action type you've chosen. When you type in a label, the link text/button label changes correspondingly. If you've chosen an image for the button, the image shows up in the XPage.

Flash video of the design definition in action:



>> Small demoapp

Share and enjoy!

Small bugfix for the XPages API tool

When you inspected a class, using the class name, e.g. java.util.ArrayList, you got the API for java.lang.Class every time. This is now fixed.

Also, when you write the name of a global object, e.g. view, the class name of the object is now the header for the table containing the methods/properties.

>> Original post with API tool

>> Download tool

Tuesday, January 26, 2010

XPages: Custom Control that can help prevent save conflicts

Today, I share with you a demoapp with a basic "has document been modified validation" (in lack of a better description) custom control, ccValidateModified.

Just pop it into an XPage with a document data source, and it should in theory work out of the box. Plug and play.

This is by no means an absolute protection against save conflicts. For applications that it's critical to avoid save conflicts, you need better protection.

Below is a movie of the control in action. A document is open in Opera, Firefox and Internet Explorer. When the document is saved in one of the browsers, the "other browsers" gets a dialog saying that the document has been modified.


(Click the play-icon to start. Broadband recommended.)

The way it works is that a scoped variable with the modified-time from the stored doc is set on page load (editmode).

When the window gets focus/every two seconds (setInterval), a partial refresh is initiated. If the doc has been modified since the document was opened for editing, a hidden image is loaded. The image has an onload event (easiest way to make it cross browser) -> show a modal dojo dialog.

>> Download demoapp with custom control

Share and enjoy!

Office 2007 MIME headers fixed in Domino 8.5.2

Just got word that IBM finally have implemented the Office 2007 MIME headers.

Monday, January 25, 2010

Small update on the Xpages search demo

The xpages search demo I posted last week is still messy, but I've cleaned it up a tiny bit.

I've added another parameter in the config for the search-method. You can now specify fieldName separately from the formula. I actually documented it, but forgot to implement it in the demo.

This should make it easier to refer to fields in the columns.
E.g.
TemplateSearch.search( [{ 
fieldName: 'name',
formula: 'LastName + ", " + FirstName',
dataType: 'string'
},{
fieldName: 'modified',
formula: '@Modified',
dataType: 'date'
}]);

If you want to have a demo of something more in regards search-results/data tables, leave a comment, and I'll take a look at it. If it's something I can do in a relatively short amount of time, I'll make/post a demoapp with a simple description.

Saturday, January 23, 2010

Comments are now open

I've got a few comments over the time of my blog that people can't comment because they don't have a google account.

I've now opened for anonymous comments, so if you want to ask me about something in regards to any of my blogposts, now's your chance.

The reason I restricted comments back in the day was because I was afraid of dealing with spam all day. Let's see how it turns out.

Wednesday, January 20, 2010

XPages: Three ways to build a search interface with "on the fly" sortable columns

While copy/paste programmers and XPages novices might get something out of this demoapp/blogpost, it's directed towards the people that want to push XPages beyond the constraints of the drag and drop interface in Domino Designer.

I detest the "need" to have a Notes view for every kind view in XPages. Recently, at work, I've been occupied mostly by creating customizable search/filtering views in XPages.

Until today, I've used a DataTable with a SSJS function as data source (returns JS-objects). The function does an FTSearch on a NotesView, and fetch precalculated JSON strings from a view column. The DataTable can be designed to use one/all fields from the json-objects. One DataTable column can have one or more values from the JSON object (e.g. dataItem.fieldName + ' ' + dataItem.fieldName2). This has the advantage of only needing a one (unsorted) column view to generate a wide array of reports.

Julian Buss seems to have been working on/pondering solutions for "on the fly" sortable searches as well. I've implemented what I think he meant in the demoapp as well. I didn't take the time to write caching algorithms for his way, using NotesViewEntryCollection. I also didn't take the time to implement descending sort. The reason I was so lazy was that I didn't like my implementation of his idea.

The last way (default choice in demoapp), and the best way I've found so far is to do FTSearch on the db. In the demoapp, I search the entire db without restricting to a form/etc. This is because I didn't find the need for restricting the search as there are only one kind of documents in the demoapp. The result from the search is a NotesDocumentCollection. The datasource is a function that accepts what I call an array of field definitions.

E.g.
var fieldDefinitons = [{
fieldName: 'someFieldName',
dataType: 'string'
},{
fieldName: 'someFormulaString',
dataType: 'date'
}]


Supported datatypes in the demoapp are string, multi-value string, date, multi-value date, number and multi-value numbers. The field definitions are parsed into a string that can be evaluated against a document (using session.evaluate). Using session.evaluate is the fastest way I know of to get multiple values out of a document. Compared to fetching precalculated JSON strings from a view, there is no discernible difference in speed when evaluating against documents in the NotesDocumentCollection.

Regarding caching, the JSON object is stored in a viewScope-variable. When the user navigates to another page, the JSON object should be recycled from what I understand of XPages/JSF. The values are only fetched from the server when the query string is changed. When the used wants to sort the view, the cached object is resorted. This results in speedy pagination/sort.

On my local computer, with 5000 items in the result-object, re-sort is done in about 100ms on the server. It probably takes 500-1000ms before the user gets what he ordered. The initial search takes 2-4 seconds (server time).

I could probably write a tiny book on the "technology"/thought/history behind this demoapp. Instead, I'll tease you with an animated gif, and let you download a demoapp (here be messy code). The demoapp is a biggie. It's a mutilated version of Jake's FakeNames application. I chose this because I wanted to test against a "big" database. This has 40k NAB Person documents.

Screenshots (animated gif):


>> Demoapp (6 MB zip)

Share and enjoy!