Quantcast
Channel: XPages – Xcellerant
Viewing all articles
Browse latest Browse all 216

Fixing a Bug with Dropdown Buttons in 9.0.1 FP3 and FP4

$
0
0

If you use Dropdown Buttons in XPages on Domino 9.0.1 and a OneUI 2.x theme, there’s a bug with two recent fixpacks (FP3 and FP4) that breaks them. In this post, I’ll show what happens and share code I’ve used to fix them.

Version Disclaimer

The configuration where I’ve seen this is a test server with Domino 9.0.1 and extension library version 9.0.1.v00_02_20131212-1115. I have not set up other configurations (such as new extension libraries or a non-extension library server) and tested them at this point.

Extension Library Fix

Note: The latest relase of the extension library (Aug 31, 2015) says that it includes a fix for this issue. SPR PEDS9ZKCZU – Fix dropdown button regresssion in OneUI 2.X

Dropdown Button Issue

The dropdown button control should generally look like this when clicked:

DropdownButton_A

However, an issue was introduced with 9.0.1 FP3 (and continued with FP4) that caused it to be generated as a link instead.

DropdownButton_B

The good news is that it still works.

Generated Source

Inspecting the source of the button in both versions, it’s clear to see the difference. The original version generates a button tag.

<div id="view:_id1:dropDownButton1" class="lotusBtnContainer">
  <button aria-owns="view:_id1:dropDownButton1_ab_0_mn" aria-haspopup="true" role="button" id="view:_id1:dropDownButton1_ab_0" class="lotusBtn">
    Update Status 
    <img src="/oneuiv2/images/btnDropDown2.png" aria-label="Show Menu" alt="Show Menu">
    <span class="lotusAltText">
      ▼
    </span>
  </button>
</div>

The version in FP3 and FP4 generates a link.

<div id="view:_id1:dropDownButton2" class="lotusBtnContainer">
  <span>
    <span aria-owns="view:_id1:dropDownButton1_ab_0_mn" aria-haspopup="true" role="button">
      <a id="view:_id1:dropDownButton1_ab_0" href="javascript:;" class="lotusBtn">
        Update Status 
        <img src="/oneuiv2/images/btnDropDown2.png" aria-label="Show Menu" alt="Show Menu">
        <span class="lotusAltText">
          ▼
        </span>
      </a>
    </span>
  </span>
</div>

In both cases, the same JavaScript is generated in a script tag at the end of the page

function view__id1_dropDownButton1_ab_0_mn_ctor(){
var m=new dijit.Menu({"title":"Drop Down Menu"});
var ch0=(new dijit.MenuItem({label:"Draft",onClick:function(){XSP.setSubmitValue("Draft");XSP.fireEvent(arguments[0], "view:_id1:_id4", "view:_id1:dropDownButton1", null, true, 2, null);}}));
m.addChild(ch0);
var ch1=(new dijit.MenuItem({label:"Submitted",onClick:function(){XSP.setSubmitValue("Submitted");XSP.fireEvent(arguments[0], "view:_id1:_id4", "view:_id1:dropDownButton1", null, true, 2, null);}}));
m.addChild(ch1);
var ch2=(new dijit.MenuItem({label:"Approved",onClick:function(){XSP.setSubmitValue("Approved");XSP.fireEvent(arguments[0], "view:_id1:_id4", "view:_id1:dropDownButton1", null, true, 2, null);}}));
m.addChild(ch2);
var ch3=(new dijit.MenuItem({label:"Rejected",onClick:function(){XSP.setSubmitValue("Rejected");XSP.fireEvent(arguments[0], "view:_id1:_id4", "view:_id1:dropDownButton1", null, true, 2, null);}}));
m.addChild(ch3);
var ch4=(new dijit.MenuItem({label:"Cancelled",onClick:function(){XSP.setSubmitValue("Cancelled");XSP.fireEvent(arguments[0], "view:_id1:_id4", "view:_id1:dropDownButton1", null, true, 2, null);}}));
m.addChild(ch4);
return m;
}

The Fix

To fix it, I wrote a JavaScript function that takes the generated output and changes it back to the original output. It also re-attaches the event handling functions to each menu option. I added the function to a client-side JavaScript library on the page and then called it with the client-side ID of a drop-down button control to fix it.

Note: Testing has been limited, but there are no known issues at this time. Please test thorougly before putting into a production environment.

Here’s the library function:

function fixDropDownButtons(clientID) {
  try {
  
  var dropdownDiv = dojo.byId(clientID);
  var html = dropdownDiv.innerHTML;
  
  // Remove opening and both closing span tags
  html = html.substring(13, html.length-16);
  
  // Get the attributes from the next span tag (parse from after <span to the next left angle bracket -1
  var idx = html.indexOf('>');
  var attributes = html.substr(0, idx);

  // Add the attributes to the next tag
  html = html.substr(idx + 4);
  html = '<button ' + attributes + ' ' + html;

  // Remove the href="javascript:;" attribute
  idx = html.indexOf('href=');
  
  // NOTE: in IE11, the href="javascript:;" attribute is the last attribute, so it goes up to the '>'
  // In Firefox, it's not the last attribute, so it can be located by the next space
  // Solution: Find the next space and the next closing bracket and use the lower of the two numbers
  var idxSpace = html.indexOf(' ', idx+1) + 1;
  var idxBracket = html.indexOf('>', idx+1);
  var idx2 = idxSpace < idxBracket ? idxSpace : idxBracket;
  html = html.substr(0, idx) + html.substr(idx2);
  
  html = html.replace('</a>', '</button>');
  console.info(html);
  
  dropdownDiv.innerHTML = html;
  
  // Re-connect the event handler that the server already generated.
  // This assumes that it's part of the global window object
  var clientID_noColon = clientID.replace(/:/g, '_');
  dojo.connect(dojo.byId(clientID + "_ab_0"),"onclick",function(thisEvent) {
    XSP.openMenu(thisEvent,eval(clientID_noColon + "_ab_0_mn_ctor"));
  });
  } catch (e) {
    console.warn('Error in fixDropDownButtons(' + clientID + ') - ' + e.toString());    
  }

}

It’s largely text parsing and rearranging once it gets the HTML generated for the component. (Read code comments for more details.)

Toward the end, it re-connects event handlers for the dropdown button options. Fortunately, there’s a predicatable naming pattern, so it can consistently determine how to find the correct functions to re-attach.

The code also accounts for the fact that the HTML attributes are generated in differnet ordres in different browsers, which affects the text parsing.

Calling the Function

Here’s how to call it from the onClientLoad event of the page:

fixDropDownButtons("#{id:dropDownButton1}");

This code uses pass-thru logic to determine the button’s client-side JavaScript ID and passes it to the function.

Potential Improvements

This is my initial solution, but there are a few ways that it could be quickly enhanced.

First of all, it could be enhanced to work based on a class or just automatically finding drop-down buttons rather than having to determine the client-side ID and pass it to the function.

It could also be updated to only fix buttons as needed, in order to prevent it from causing problems when you update to a fixpack or extension library version that solves the original bug. A simple check for whether it has a button tag could determine this.



Viewing all articles
Browse latest Browse all 216

Trending Articles