Thursday, January 7, 2010

XPages: Hijacking/publishing partial refreshes globally

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

Update 07.05.2010 Fixed a bug where the onStart/onError/onComplete event were overwritten.

This is a modification to Jeremy Hodges Adding the Ability to Watch for ANY Partial Refresh in an XPage.

The difference between this and Jeremy's code is that this lets you have multiple listeners to all stages of the partial refresh (init, start, complete and error).

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();
}
}
};
});
}

Run the above function at the top of your application script to hijack and publish the partial refresh event.

Example usage:
dojo.subscribe( 'partialrefresh-init', null, function( method, form, refreshId ){
alert('Partial refresh for ' + refreshId + ' initiated.' );
} );

dojo.subscribe( 'partialrefresh-start', null, function( method, form, refreshId ){
alert('Partial refresh for ' + refreshId + ' started.' );
} );

dojo.subscribe( 'partialrefresh-complete', null, function( method, form, refreshId ){
alert('Partial refresh for ' + refreshId + ' complete.' );
} );

dojo.subscribe( 'partialrefresh-error', null, function( method, form, refreshId ){
alert('An error occured during partial refresh of ' + refreshId + '.' );
} );

Documentation for dojo.subscribe

16 comments:

Mark Hughes said...

Can you give a use case for doing this? I use the event handler interface in X-Pages 8.5.1 to do the same thing. It is kind of hidden though.

Tommy Valand said...

Sure.. In a "general" script library for an application our company is developing, we have a script that attaches event listeners to all date fields (using dojo.query to find the fields). This listener "fixes" the input of the user (disallows letters++).

Several of the XPages have date fields with computed visibility. By listening to the partial refreshes, we can make sure that all date fields have the aforementioned listener.

In a page that I'm developing, a search page, the user can select full text searching, or to search on a field value. Depending on the user's choice, the search field turns into a text field with/without type ahead or a date field. This transition is partial. When the transition is complete, the focus is set to the input field.

The search page has its own client side library, but also uses the "general" script library. Therefore we need the possibility to attach multiple subscribers to partial updates.

Anonymous said...

very useful article. I would love to follow you on twitter.

Tommy Valand said...

I don't think I'll ever join the twitterers. Not my thing.

Mark Hughes said...

Tommy what license is this code under?

Tommy Valand said...

Under the "Share and enjoy!" license ;)

IT-bonden Odde said...
This comment has been removed by the author.
IT-bonden Odde said...

Hi! First I want to thank you for contributing. I've read several of your articles - good stuff!

Now I have an issue with partial refresh involving datatable.

I reconstructed my problem in a simple XPage containing 3 xp:div with ids div1, div2 and div3. div2 contains a datatable with a simple javascript building an array of strings. It has one column which just return the string.

in each xp:div I have a button:
div1: partial refresh div1
div2: partial refresh div2
div3: partial refresh div2

Then I use th hijack function to listen for refresh of div2. This triggers a refresh of div1.

Now I'm getting to the point :-)
Each time div2 is refreshed (button in div2 and div3) div1 is correctly refreshed, BUT the array in div2's datatable is built/refreshed twice. Looks ok in the browser, but with large data-sets this will be a problem regarding server-performance. Here is a dump from the console (comments):
04.10.2011 11:06:34 HTTP JVM: build array
(initial loading page)

04.10.2011 11:06:37 HTTP JVM: build array
(div1-button)

04.10.2011 11:06:39 HTTP JVM: build array
(div2-button)
04.10.2011 11:06:39 HTTP JVM: build array
(div2-button, partial refresh of div1)

04.10.2011 11:06:54 HTTP JVM: build array
(div3-button)
04.10.2011 11:06:54 HTTP JVM: build array
(div3-button, partial refresh of div1)

(the 'build array' is printed from the datatable in div 2 and should only be printed once for each click)

I have tested with various settings, but so far without any luck.

Any ideas?

Tommy Valand said...

I've just accepted this. Not sure who is at fault, the designers of XPages, or the designers of JSF.

I work around this by storing results of "expensive" operations in scoped variables.

If the scoped variable is set, return scoped object. If not, build object/array/whatever, store in scope, return generated object.

Which scope I store it in depends on what's calculated.

Anonymous said...

ilthesHello, I have used this great code on 8.5.2, and it worked well. Now we upgraded the Domino serverto 8.5.3, and it raises an error:

too much recursion

dojo.publish( 'partialrefresh-init', [ method, form, refreshId, options ]);

Does anybody konw the reason and the possible fix?

Thank you in advance.
JLehocz

Tommy Valand said...

Do you have the hijacker in multiple places on the same page?

I haven't had any problem with it on 8.5.3

Anonymous said...

Thanx for the tip. Simple, but I have not thought about it.
There is one hijacker on a custom control, so it should not be the problem.
However, on another custom control included on the same XPage I have found this at the bottom:


I have removed this eventHandler and hijacking started working immediately on 8.5.3 as well.
Funny, this worked on 8.5.2 without any error???
Thanx again. JLehocz

Anonymous said...

xp:eventHandler event="onClientLoad" submit="true"
refreshMode="partial"
/xp:eventHandler

Unknown said...


Hi,

We get the below error from an xpage after one hour of execution (say button click and its running an excel report which takes more than 65 minutes to run)

An error occurred while updating some of the page.
Unable to load /xxx/xxx/xxx/xxxx.nsf/xxxx.xsp?$$ajaxid=view%3A_id1%3A_id2%3A_id4%3xxxPanel status:12002

Kindly help!

Tommy Valand said...

When you google status 12002 it seems to be the error code related to timeout for Internet Explorer.

I would suggest either trying to optimize the code, or rewrite the code so that the report is generated as a backend job.

E.g. when the user clicks the button to create the report, a document is created. Redirect the user to the document, so that he can check the status of the report generation. Maybe have a checkbox that lets the user select getting an email when the report is finished.

Have an agent run on created or modified documents with that form. You could update the "report order" document with information about progress. When the report is completed, update the information in the document/attach the report and/or send mail to the user that the report is finished.

Wayne said...

Hi Tommy,

Appreciate this is an older thread but....I have a repeat control within which I am doing inline editing of records. I want the user to tab out of one cell into another in order to populate values almost excel style; e.g. I am in column A and I enter a value, I tab out and some SSJS which runs onkeypress to save the currentDocument and update 2 viewScope variables; one to set the next row as the selected document within the panel datasource and another to show which field should be shown as editable. A partial refresh is called for the panel containing the repeat. All of this works fine. I then have an onComplete event which tries to set the focus to the editable field in the next row:

dojo.query("input[id$='Field1']")[0].focus();

If I have an alert ahead of this it works fine. Without an alert the focus is never set.

I implemented the hijack for the partial refresh and put the focus script within the complete event:

dojo.subscribe( 'partialrefresh-complete', null, function( method, form, refreshId ){
//alert('Partial refresh for ' + refreshId + ' complete.' );
dojo.query("input[id$='Field1']")[0].focus();
} );

...but same problem unless I enable the alert.

I saw in your example you were setting focus following partial refresh without any issue so guess it can be achieved. Any thoughts on where to look next?