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 13.01.23 Added Java code neeeded to check what component triggered submit in response to this question on Stack Overflow.

Java version:
@SuppressWarnings( "unchecked" )
public static String getParameter( String name ) {
	Map parameters = resolveVariable( "param", Map.class );
	return parameters.get( name );
}

@SuppressWarnings( "unchecked" )
public static UIComponent getChildComponent( UIComponent component, String id ) {
	if( id.equals( component.getId() ) ) {
		return component;
	}

	Iterator children = component.getFacetsAndChildren();
	while( children.hasNext() ) {
		UIComponent child = children.next();
		UIComponent found = getChildComponent( child, id );
		if( found != null ) {
			return found;
		}
	}

	return null;
}

public static  T resolveVariable( String name, Class typeClass ) {
	Object variable = ExtLibUtil.resolveVariable( FacesContext.getCurrentInstance(), name );
	return variable == null ? null : typeClass.cast( variable );
}

public static UIComponent getComponent( String id ) {
	return getChildComponent( (UIViewRootEx) FacesContext.getCurrentInstance().getViewRoot(), id );
}

public static boolean wasSubmittedByComponentId( String componentId ) {
	if( componentId == null ) {
		return false;
	}

	String eventHandlerClientId = getParameter( "$$xspsubmitid" );
	if( eventHandlerClientId == null ) {
		return false;
	}

	// Extract the component id for the event handler
	String eventHandlerComponentId = eventHandlerClientId.replaceFirst( "^.*\\:(.*)$", "$1" );
	UIComponent eventHandler = getComponent( eventHandlerComponentId );
	if( eventHandler == null ) {
		return false;
	}

	// Fetch the component the event handler belongs to
	UIComponent submissionComponent = eventHandler.getParent();
	if( submissionComponent == null ) {
		return false;
	}

	return ( componentId.equals( submissionComponent.getId() ) );
}
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.