Archive | September 2015

Fixing the Width of a Select 2 with a Long Value in a Bootstrap Form Group or Input Group

If you have a Select2 control within a form-group or input-group div in a Bootstrap UI, a long value can cause the Select2 to grow to become wider than its container. In this post, I’ll show how to fix the issue.

The Problem with Large Values

In this demo, I have a form with two columns of fields, each within a well (which makes the container size very clear).

Select2 Long Values - A

In one field, there is a very long value. This usually isn’t an issue when you have explicit control over the options, but if you have an application where a drop-down box’s choices come from a plain text field on other documents, it’s possible.

Select2 Long Values - B

When I select the long value, it limits the amount of text displayed in the box to what will fit on one line, but it expands the size of the Select2 outside of its container.

Select2 Long Values - C

If I remove the second column of fields and re-test, it looks like the Select2 was enlarged to be the same the size as the parent container, which is too big for the area that it’s supposed to be in.

Select2 Long Values - C2

Fixing with CSS

This is a known issue with Select2 inside of a form-group or input-group within Bootstrap.

Fortunately, there’s a simple CSS fix, adapted from this post on github

.form-group .select2-container {
  position: relative;
  z-index: 2;
  float: left;
  width: 100%;
  margin-bottom: 0;
  display: table;
  table-layout: fixed;
}

If you’re using an input-group instead of a form-group, then change the first line to this:

.input-group .select2-container {

Select2 Long Values - D

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.

XPages Tip: Reading Custom Properties and Modifying a Component Before a Page Loads

If you need to read a property on a custom control and do something to any component on the page before a page loads, it can get tricky because the compositeData object is not available in the beforeRenderResponse event and getComponent() does not work in the beforePageLoad event.

I have an application where I need to dynamically inject components before a page loads. However, the injection depends on custom properties on a custom control, so it needs to read the properties and then get a handle to a component and modify it. Since the compositeData object isn’t available in the beforeRenderResponse event and getComponent() doesn’t work in the beforePageLoad event, there’s no single place to put the code that I need.

Fortunately, there’s a simple workaround. Code in the beforePageLoad event can read custom properties from the compositeData object and store them in viewScope variables. Then, code in the beforeRenderResponse event can read those values from viewScope and use getComponent() to get the handle to the component that I need to modify.