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!

11 comments:

Ajit said...

Brilliant, Thank you so much for this wonderful solution. I used your approach with some minor modifications in one of my applications. Basically the problem that I faced was that if let’s say the currently activeElement is a <TD> (this can happen if you have a partial refresh on a field’s onChange event and which normally executes when the value is changed and user clicks on another page elements which can be a <TD> or a <DIV>). Once this condition is achieved this approach would stop working for all the fields on the page. This is because there is an error thrown here and dojo.unsubscribe( focusSubscription ) is not called after that – I just added an onError entry to your approach to solve the problem:


var errorSubscription = dojo.subscribe( 'partialrefresh-error', function(){

dojo.unsubscribe( focusSubscription );
});
I have added added this change with your solution with all the details and a demo application in my blog @ bleedyellow:

http://www.bleedyellow.com/blogs/AjitRathore/entry/xpages_partial_refresh_field_focus_problem_and_fix?lang=en_us

Tommy Valand said...

Thanks! I've added your patch to the code.

leon ta said...

Hi Tommy,

Do you have a sample database you can share?

my email address
leonta2010@hotmail.com

thank you
leon

Sjef Bosman said...

Small changes to accommodate radio buttons and checkboxes. Other advantage: the focused field is scrolled to the top of a scrollable area (if any). Note: not tested for fool-proof behaviour.

dojo.addOnLoad(function(){
dojo.subscribe( 'partialrefresh-init', function(){
// setTimeout needed to make it work in Firefox
setTimeout(function(){
var activeElementId = document.activeElement.id;
var activeElementName = document.activeElement.name;

var focusSubscription = dojo.subscribe( 'partialrefresh-complete', function(){
var activeElement = dojo.byId( activeElementId || activeElementName );

if( activeElement && 'INPUT|SELECT|TEXTAREA|FIELDSET'.indexOf( activeElement.nodeName ) > -1 ){
// Set focus to element/select text
activeElement.scrollIntoView();
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 );
} );
});

Ryan said...

Thanks for the post Tommy, this is extremely useful.

I'm using Dojo Radio Buttons (from the Extension Library) and Sjef's solution works if you click on the Label of the radio button, but not if you click on the radio button itself. If you click directly on the radio button it still loses focus after a partial refresh.

I know there are some weird issues with the radio button control and partial refreshes...maybe this just has to do with those quirks. Anyone have thoughts on this?

Tommy Valand said...

I'm not sure how the Dojo radio buttons work. If they work like a lot of other Dojo stuff, they might modify the DOM.

It could be that the "original" radio buttons have focus, but after Dojo has done it's thing, a new DOM element has replaced the original element.

One way to find out is to look at the response from the server on a partial refresh, and compare with the HTML in the live page. If the HTML in the live page is different from the HTML that's served, my theory is probably correct.

If you do the test/let me know how it goes, I can probably try to work around the issue.

Ryan said...

It looks like the dojo radio buttons work in IE9. It is only in IE8 that it does not work if you click on the radio button itself.

Unknown said...

Hi,

I am unable to figure out where to put this code of partial refresh and field focus problem. I am facing this issue and looking to find the solution.

Please guide me where to put this code to resolve the issue.

Tommy Valand said...

Hi!

Add the snippet from this blogpost and the snippet from the Hijacking/publishing partial refreshes globally into a (client side) JavaScript library. Add the JavaScript library to the xpage you want the functionality in.

Use Firebug in Firefox to look for errors if the code doesn't work. Any error on the page may result in something not working.

Pipalia said...

Hi Tommy,

Many thanks for sharing this work-around.

How would it work if I am refreshing a div inside a repeat control and want to set focus to a field within that div which is within a repeat control?

Thank you,

Samir

Tommy Valand said...

If the field exists in the browser before the refresh, it might work. If it doesn't, I'm not sure :)