Archive | Extension Library RSS for this section

Fixing a Bug with Dropdown Buttons in 9.0.1 FP3 and FP4

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.

Advertisements

URL @Functions in the Extension Library

There are a few @Functions for working with URLs available in the Extension library. They can come in handy if you need to work with URLs related to any element in your database.

Availability

These functions are part of the extension library. They’re available if you’ve installed the Extension Library, or if you have Upgrade Pack 1 for 8.5.3. They’re also part of the extension library features built directly into Notes9.

@FunctionsEx

The functions are available on the Reference tab in the SSJS Script Editor. Under Libraries, select @FunctionsEx to see the additional functions.

ExtLib URL Functions - 1

The following URL functions are available:

  • @EncodeUrl() – Encodes spaces and symbols in the URL
  • @FullUrl() – Displays the full path to a resource in the database (relative to the server)
  • @AbsoluteUrl() – Displays the absolute path to a resource in the database*
  • @IsAbsoluteUrl() – Boolean value for whether a given URL is absolute

Examples

Here are some examples of how they can be used in an application:

Code Result
@FullUrl(view.getPageName()) /BlogTesting.nsf/ExtLibURLFormulas.xsp
@AbsoluteUrl(view.getPageName()) http://127.0.0.2/ExtLibURLFormulas.xsp
@EncodeUrl(context.getUrl().toString());

Where the url is: http://127.0.0.2/BlogTesting.nsf/ExtLibURLFormulas.xsp?my_Parameter=spaces and $ymbol$
http://127.0.0.2/BlogTesting.nsf/ExtLibURLFormulas.xsp?my_Parameter=spaces+and+%24ymbol%24
@IsAbsoluteUrl(view.getPageName()) false
@IsAbsoluteUrl(context.getUrl().toString()) true

*@AbsoluteUrl

I generally use context.getUrl() and work with that to parse and build URLs, so I haven’t run into this before, but the output of @AbsoluteUrl() wasn’t what I expected. At first glance, it appeared to give the full url to the specified design element, but it actually just gives the protocol, server, and design element name that was supplied.

It looks like I can get the real absolute url to the design element by combining @FullUrl() with @AbsoluteUrl()

Code Result
@AbsoluteURL(@FullUrl(view.getPageName())) http://127.0.0.2/BlogTesting.nsf/ExtLibURLFormulas.xsp

Creating a Dojo Filtering Select Control in XPages

If you’d like to provide users the ability to type directly into an XPages combo box (like Dialog List field in the Notes client), you can easily convert combo boxes to dojo filtering select controls in XPages.

Combo Box

Let’s start with this straightforward combo box:

FilteringSelect_2

<xp:comboBox id="comboBox1">
  <xp:selectItem itemLabel="Acai"></xp:selectItem>
  <xp:selectItem itemLabel="Apple"></xp:selectItem>
  <xp:selectItem itemLabel="Apricot"></xp:selectItem>
  <xp:selectItem itemLabel="Artichoke"></xp:selectItem>
  <xp:selectItem itemLabel="Asparagus"></xp:selectItem>
  <xp:selectItem itemLabel="Avocado"></xp:selectItem>
</xp:comboBox>

Dojo Filtering Select

If you have the extension library, 8.5.3 with Upgrade Pack 1, or Notes 9, the Dojo Filtering Select control is available to you in the Dojo Form section of the Controls palette. When you drag it onto a page, it looks the same as the standard Combo Box on the Design tab.

FilteringSelect_1

The primary difference between the two is that you can type directly into the filtering select in order to narrow down the choices.

Easily Creating a Dojo Filtering Select

There’s also a major difference in Domino Designer when creating a filtering select — it doesn’t have the ‘Values’ subtab in its properties, so there isn’t a similar method of building the options for the list.

There are two simple ways to convert this to a dojo filtering select.

1) Manually change the xp:comboBox tag to xe:dojoFilteringSelect (along with the corresponding closing tag)
2) Drag a Dojo Filtering Select control onto the page and copy all of the xp:selectItem tags into it (then remove the combo box)

In fact, this is the best way that I know of to create a dojo filtering select easily. They can be enhanced to work with a different data store, but if your list of options is hard-coded or computed with SSJS, this is an easy way to go.

<xe:djFilteringSelect id="djFilteringSelect1">
  <xp:selectItem itemLabel="Acai"></xp:selectItem>
  <xp:selectItem itemLabel="Apple"></xp:selectItem>
  <xp:selectItem itemLabel="Apricot"></xp:selectItem>
  <xp:selectItem itemLabel="Artichoke"></xp:selectItem>
  <xp:selectItem itemLabel="Asparagus"></xp:selectItem>
  <xp:selectItem itemLabel="Avocado"></xp:selectItem>
</xe:djFilteringSelect>

UI Features

Even though it’s a combo box at its core, the filtering select control functions quite differently than the standard combo box.

1) The UI is a little different, as the filtering select picks up dojo styling. This is definitely something to keep in mind; you may need to add CSS to restyle it to keep it consistent with the rest of the page.

FilteringSelect_3

2) As you start typing, it will filter the list to only include the values that start with the characters that have been typed.

FilteringSelect_4

3) If the user types a value that is not valid in the list, an icon displays in the field and an error message pops up next to the field.
FilteringSelect_5

When this happens, no server-side code will execute, because it’s failing client-side validation that’s built into the control.