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.

6 comments:

Anonymous said...

Hi Tommy,

I'm using this method to do dynamic binding in a custom control, which has the datasource as parameter using the now well known DocumentDataSource class binding in the property. Since I have a datasource already, I'm attempting to mod your code to check if it has a datasource or a string, and simply return the datasource if it has one.
The issue is inside getDataSource, typeof says the compositeData.model property is a NotesXspDocument, and it won't bind to that. Now I'm thinking I need to get the datasource ID somehow, but I would prefer to do that without passing in a parm to the custom control specifically to give to the Events object, when compositeData.model already should represent the correct datasource.
Do you have any thoughts on managing this?

Thanks for your time...

- David Gilmore

Tommy Valand said...

Dynamic event binding works on a DocumentDataSource.

It's probably possible to do what you want by looping the XPage view tree/looking at data objects/comparing unid or something like that, but I would imagine it's a lot of trial and error. :)

newbs said...

Tommy:

Tried using this but it could not find the data Source since it was not at the top view level of the XPage. I added a method called getAllDatasources which finds any data source anywhere on the page.

Once I did that the event addition results in the error:

java.io.NotSerializableException: com.ibm.xsp.binding.javascript.JavaScriptMethodBinding
com.ibm.xsp.binding.javascript.JavaScriptMethodBinding

Trying to figure this out now..

-Newbs

Tommy Valand said...

Looking at the code, it's possible that I ran it with "Keep all pages in memory" persistence/it won't run on any other persistence setting.

It would probably be better to create a view scoped bean that takes care of the event queueing. Add statements as string ( "#{javascript:someCode}" ) or something like that. Fetch existing, if any, event binding, add to queue (ArrayList<String>). Change to run a method in the view scoped bean. This method evaluates all statements.

Something like that.. :P

Naveen said...

Hi Tommy,
Your code in "EventsSSJS.txt" would be available under which license for use?
Rgds/Naveen

Tommy Valand said...

It's under apache license, so use the code however you like :)