IE8 clips the text from a max-widthed drop-down box – fix!

OK, Windows XP only has about 6 months to live from the date of this post, and so IE8 (which is XP only) will die out sooner rather than later, but if you are in the unfortunate position of having to try and support ancient browsers then you will have almost certainly come across the problem with fixed-width or max-width drop-down lists. If the width of your options exceeds the width (or max width) of the drop-down box then IE8 just clips the text, which may well mean that your users can’t read it. Another useful feature from IE.

There is a suggested solution here for fixed width drop-downs: http://jquerybyexample.blogspot.com/2012/05/fix-for-ie-select-dropdown-with-fixed.html, which is useful however does not quite address the problem of drop-down boxes with no fixed width defined, but which do have a max-width defined.

The solution is a modification of the above link. Firstly wrap your drop down in a div with a width the same as the max-width of the drop-down, and an overflow value of hidden. The example below shows a Razor ASP.NET drop down, but this will work with HTML too.

  <div style="width:400px;overflow:hidden;">
            @Html.DropDownList("Selection", Model.ChoiceList, new { id = "ddlChoose", @class="ddlClass", style = "max-width:400px; text-overflow: ellipses; " }) 
  </div>

Then in the jQuery $(document).ready() method put the following code on the change, blur, mousedown and mouseup events for your drop-down. In this example I’ve done the element selection by class, but obviously you can use ID if you want. I have used the jQuery ‘live’ method as my drop-downs were not necessarily generated on page load, however you can just use the mousedown() etc methods if you are not doing anything fancy.

$(document).ready(function () {

    /* Begin functions for IE8 drop-down boxes */

    $(".ddlClass").live("mousedown", function () {
        SetToAutoWidth($(this), true, 400);
    });

    $(".ddlClass").live("change", function () {
       ResetToFixedWidth($(this), true, 400);
    });
    $(".ddlClass").live("blur", function () {
       ResetToFixedWidth($(this), true, 400);
    });

    $(".ddlClass").live("mouseup", function () {
       ResetToFixedWidth($(this), true, 400);
   });

  
    /* End functions for IE8 drop-down boxes */

});

This final bit of jQuery contains the methods that actually do the work. Note that you could probably make the FindTextWidth() method more efficient by only running it on page load, and persisting the div (as mentioned in the stackoverflow reply I took this from).

// Return the width (in pixels) of the passed string
// From here: http://stackoverflow.com/questions/118241/calculate-text-width-with-javascript and slightly modified
function FindTextWidth(textToMeasure, fontFamily, fontSize){
    var o = $('<div>' + textToMeasure + '</div>').css({ 'position': 'absolute', 'float': 'left', 'white-space': 'nowrap', 'visibility': 'hidden', 'font-family': fontFamily, 'font-size': fontSize }).appendTo($('body'));
    var w = o.width();

    o.remove();
    return w;
}

// Look through all the options on the drop-down. If any are longer than the passed width value then expand the box.
// Params: 
// ddl - the dropdown list object
// maxWidthFlag - if true then set the max-width, else set the width
// width - the option width value over which the functionality is invoked.
function SetToAutoWidth(ddl, maxWidthFlag, width) {
    if ($.browser.msie && $.browser.version.substring(0, 2) === "8.") { // IE8 only

        var ID = $(ddl).attr("id");
        var ff = $(ddl).css("font-family");
        var fs = $(ddl).css("font-size");

        $("#" + ID + " > option").each(function () { // For each option in the list...

            var t = $(this).text(); // Get the text
            var w = FindTextWidth(t, ff, fs); // Find the width in pixels

            if (w >= width) { // If the width is greater than the passed value
                if (maxWidthFlag === true) {
                    $(this).parent().css("max-width", ""); // Remove the max-width style
                }
                else {
                    $(this).parent().css("width", "auto"); // Set the width style to auto
                }

            }
        });
    }
}

// Params: 
// ddl - the dropdown list object
// maxWidthFlag - if true then set the max-width, else set the width
// width - the option width value over which the functionality is invoked.
function ResetToFixedWidth(ddl, maxWidthFlag, width) {
    if ($.browser.msie && $.browser.version.substring(0, 2) === "8.") { // IE8 only

        if (maxWidthFlag === true){
             $(ddl).css("max-width", width + "px");
        }
        else{
            $(ddl).css("width", width + "px");
        }
    }
}

Note that this still does not get around the problem of the greater-than-the-max-width selected option overflowing the main drop-down box and overwriting the down-arrow. If anyone has a solution for that that doesn’t involve adding an image to it please say!

Advertisements

Executing an ASP.NET HttpModule for specific pages only

By default an ASP.NET HttpModule runs for all webpages on a website. If you use an HttpHandler instead then it is possible to specify the path it executes for in the web.config, but using a handler may not always be possible/desirable in your circumstances.

A module can read the web.config though, so you can create an appSetting entry containing the pages that you want to show, like this:

<appSettings>
    <add key="ModulePages" value="Wibble.aspx,Wobble.aspx"/>
</appSettings>

And then your module sourcecode can be adjusted similar to the following (the key code is in CheckPath()):

   public class HumMasterPage:IHttpModule
    {
        private HttpApplication _current = null;

        #region IHttpModule Members

        public void Init(HttpApplication context)
        {
            _current = context;
            _current.BeginRequest += new EventHandler(context_BeginRequest);
        }

        #endregion


        /// <summary>
        /// DynamicMasterPath appsetting tells us which path the module should run for
        /// </summary>
        /// <returns>true if the current path is in the allowed paths, or allowed paths are not specified (i.e. all are allowed)</returns>
        private bool CheckPath()
        {
            if (ConfigurationManager.AppSettings["ModulePages"] == null)
                return true;

            string[] allowedPaths = ConfigurationManager.AppSettings["ModulePages"].ToString().Split(',');
            
            foreach(string allowedPath in allowedPaths)
            {
                if (_current.Context.Request.Url.PathAndQuery.Contains(allowedPath))
                    return true;
            }

            return false;
        }

        void context_BeginRequest(object sender, EventArgs e)
        {
            if (CheckPath() == false)
                return;
 
            // Do your module stuff here...
        }

    }

You obviously may need to modify it if you have several pages named the same in your site, and I have to admit that I’ve only tested with Webforms, not MVC (I can’t remember off the top of my head if MVC routing is done before or after the module processing). It works well with Webforms however, and there are often better ways to process the stream using MVC anyway.