Wednesday, December 9, 2009

Sloppy multi-value implementation in XPages

Update: Demoapp for testing the bug
Update 2: Code for Custom Converter that helps work around the problem

I reported this to IBM today (my first Service Request). I thought I should share the bugs, so others are aware of it.

Bug with multi-value fields and converters in XPages


By multi-value field, I mean a field that have the multi-value 
separator set.

When saving a multi-value field of type String, the value alternates
between being split into multi-value, and being a flat string.
First save -> multi-value
Second save -> flat string
Third save -> multi-value

When saving a multi-value field of type date or number, the field only saves
the first value.

I also tried using a custom converter to modify the field
(getAsObject -> split, getAsString -> join). This doesn't work at all.
It seems custom converters only can be result in string values.


Example:


How hard can it be to implement multi-value. It seems like someone slapped together a couple of lines of code, and forgot to test it. I'm surprised that there isn't any other bloggers haven't written about this. Maybe multi-value fields aren't that popular?

Thursday, December 3, 2009

Helper-functions for debugging XPages

Update: Modified the class to support HTML in the message
Update 2: Made the code compatible with Domino 8.5.2
Update 3: Added methods to display message/exception on page
Update 4: Modified to send to current user. If on localhost, the exception/message is thrown -> xpage shows stack trace.

Since there are no good tools for debugging XPages, I created a couple of helper-methods (Server Side Javascript) of my own.

Debug.message sends a (MIME/HTML) mail with the specified message

Debug.exception sends a stack trace of an exception (used in a try/catch). This is useful because not all exceptions crash the application (for instance AfterRenderResponse code).

// Helper-class for debugging
var Debug = {
// Send a stack trace of an exception
exception: function( exception ){
// If on localhost/public db - throw exception
if( this.getUserName() === 'Anonymous' ){ throw exception; }

this.message( this.getExceptionString( exception ), 'Exception!' );
},

// Add exception to page
exceptionToPage: function( exception ){
this.setPageDebugMessage( 'Exception: ' + this.getExceptionString( exception ) );
},

getExceptionString: function( exception ){
var errorMessage = exception.message;

if( typeof exception.printStackTrace !== 'undefined' ){
var stringWriter = new java.io.StringWriter();
exception.printStackTrace( new java.io.PrintWriter( stringWriter ) );
errorMessage = stringWriter.toString();
}

if( typeof exception === 'com.ibm.jscript.InterpretException' ){
errorMessage = exception.getNode().getTraceString() + '\n\n' + errorMessage;
}

return errorMessage;
},

getUserName: function(){
return @Name( '[CN]', @UserName() );
},

// Send a message (supports HTML)
message: function( message, subject ){
// If on localhost/public db - throw exception
if( this.getUserName() === 'Anonymous' ){ throw 'Not logged in. Could not send message: ' + message; }

session.setConvertMime( false );
var doc:NotesDocument = database.createDocument();
doc.replaceItemValue( 'Form', 'Memo' );
doc.replaceItemValue( 'Subject', subject || 'Debug..' );
doc.replaceItemValue( 'SendTo', this.getUserName() );

var body:NotesMIMEEntity = doc.createMIMEEntity();

var contentStream = session.createStream();
// Set preferred styling
contentStream.writeText( '' );

// Convert linefeeds to <br>s
contentStream.writeText( message.replace( '\n', '<br />' ) );
body.setContentFromText( contentStream, 'text/html;charset=ISO-8859-1',
lotus.domino.MIMEEntity.ENC_NONE );
doc.send();

session.setConvertMime( true );
},

// Add message to page
messageToPage: function( message ){
this.setPageDebugMessage( message );
},

// Adds message to the bottom of the page in a dynamically created xp:text
setPageDebugMessage: function( message ){
var messageControl = getComponent( 'global-debug-messages' );
if( !messageControl ){
messageControl = new com.ibm.xsp.component.xp.XspOutputText();
messageControl.setId( 'global-debug-messages' );
messageControl.setEscape( false );
messageControl.setStyleClass( 'xspMessage' );

var valueBinding = facesContext.getApplication().createValueBinding( '#{requestScope.debugMessages}' );
messageControl.setValueBinding( 'value', valueBinding );

view.getChildren().add( messageControl );
}

var currentMessages = requestScope.debugMessages;
if( typeof currentMessages !== 'string' ){ currentMessages = ''; }
requestScope.put( 'debugMessages', message + '<br />' + currentMessages );
}
}

Tuesday, December 1, 2009

Server side event handling - get value without having to use getComponent

If you type this.getParent().getValue(), you will get the value of the component that holds the event handler.

this.getParent() returns the component that holds the event handler.

this is the EventHandler object.