2006/07/21

Emulating Disabled Options in IE with DHTML Behaviors

Summary

Internet Explorer 6 does not implement disabled OPTION's in a SELECT. "Select, Option, Disabled And The JavaScript Solution" talks about a solution to emulate them using JavaScript. In this article, I will show a cleaner solution using DHTML Behaviors.

Advantages

Advantages of my DHTML Behaviors solution over other JavaScript solutions are:
  • No mess with JavaScript
    You don't have to add JavaScript to every SELECT tag which may contain disabled options.
    If you already have some complicated JavaScript, just leave them as they are.
  • No mess with DOM operations
    If you already have some complicated DOM operations, just leave them as they are.
  • Only a small change in CSS
    To implement the solution, you only need to make a small change to existing CSS.
  • Works completely transparent
    The solution works as if disabled options are supported from the beginning.
    You can use this solution with JSF, with which it is difficult to change output HTML.

The Solution

To demonstrate the solution, look at the three files below:
  • sample.html
  • sample.css
  • sample.htc
The first two files are just to test the emulation. The last HTC file is the core of the solution. (Download is available at the bottom of this page.)
sample.html
<html>
<head>
<link rel="stylesheet" type="text/css" href="sample.css">
</head>
<body>
<form>
<select>
<option>Option 1</option>
<option>Option 2</option>
<option disabled>Option 3</option>
</select>
</form>
</body>
</html>
Above is a simple HTML file with disabled options. Normally, disabled options are displayed as non-disabled options in IE6. Note that external stylesheet sample.css is loaded. If you like, you can internalize it.
sample.css
select, option {
  behavior: url(sample.htc);
}
Above is a simple CSS file which will be loaded with the HTML file. DHTML Behaviors are attached to SELECT and OPTION. Actual behaviors are defined in sample.htc.
sample.htc
<?xml version="1.0" encoding="ISO-8859-1"?>
<PUBLIC:COMPONENT LIGHTWEIGHT="true">
<PUBLIC:ATTACH EVENT="ondocumentready" ONEVENT="onDocumentReady()" />
<PUBLIC:ATTACH EVENT="ondetach" ONEVENT="onDetach()" />

<SCRIPT type="text/javascript">
//<![CDATA[

var nLastSelectedIndex;
var fOnChangeOriginal;

// event handlers

function onDocumentReady() {
  var sTag = element.tagName.toLowerCase();
  if (sTag == "select") {
    attachEvent("onchange", onChangeSelect);
    attachEvent("onpropertychange", onPropertyChangeSelect);
    nLastSelectedIndex = element.selectedIndex;
    hackOnChange();
  } else if (sTag == "option") {
    attachEvent("onpropertychange", onPropertyChangeOption);
    emulateOption();
  }
}

function onDetach() {
  var sTag = element.tagName.toLowerCase();
  if (sTag == "select") {
    detachEvent("onchange", onChangeSelect);
    detachEvent("onpropertychange", onPropertyChangeSelect);
  } else if (sTag == "option") {
    detachEvent("onpropertychange", onPropertyChangeOption);
  }
}

//

function onChangeSelect() {
  if (element.options[element.selectedIndex].disabled) {
    element.selectedIndex = nLastSelectedIndex;
  } else {
    nLastSelectedIndex = element.selectedIndex;
    if (fOnChangeOriginal != undefined) {
      fOnChangeOriginal();
    }
  }
}

function onPropertyChangeSelect() {
  var sChangedPropertyName = event.propertyName.toLowerCase();
  if (sChangedPropertyName == "onchange") {
    hackOnChange();
  } else if (sChangedPropertyName == "selectedindex") { // contributed by Zecc
    nLastSelectedIndex = element.selectedIndex;
  }
}

function onPropertyChangeOption() {
  var sChangedPropertyName = event.propertyName.toLowerCase();
  if (sChangedPropertyName == "disabled") {
    emulateOption();
  }
}

// hack onChange attribute of select tag

function hackOnChange() {
  detachEvent("onpropertychange", onPropertyChangeSelect);
  fOnChangeOriginal = element.onchange;
  element.onchange = null;
  attachEvent("onpropertychange", onPropertyChangeSelect);
}

// emulate disabled option

function emulateOption() {
  if (element.disabled) {
    element.style.color = "graytext";
  } else {
    element.style.color = "menutext";
  }
}

//]]>
</SCRIPT>
</PUBLIC:COMPONENT>
Above is a core HTC file which defines the DHTML Behaviors for SELECT and OPTION. What it does are:
  • onDocumentReady, onDetach
    Initialization and cleaning up.
  • onChangeSelect
    When a selection is changed for a SELECT, check if the selected option is disabled. If it is disabled, revert the selection. If it is not disabled, save the selectedIndex for later use, and run the original onchange event handler set by user.
  • onPropertyChangeSelect
    When a onchange event handler is set or changed for a SELECT, save the event handler for later use and set the onchange to null. This ensures that onChangeSelect above is called, which then calls the onchange event handler set by the user. This mechanism is needed because, by the spec, a onchange event handler set in a HTML file is called before a onchange event handler in a HTC file. This makes it possible to work consistently when there is a onchange event handler in the HTML or there is JavaScript/DOM manipulations.
    Contributed by Zecc:
    When a selectedindex value is set or changed for a SELECT, save the value for later use. This makes it possible to work consistently when there is JavaScript/DOM manipulations.
  • onPropertyChangeOption
    When a disabled attribute is changed for a OPTION, change the text color of the OPTION, so it will look enabled or disabled. This makes it possible to work consistently when there is JavaScript/DOM manipulations.

Download

You can download zipped files from here.
Download includes sample-javascript.html which is a little more complicated version of sample.html, to demonstrate how the solution works when there are some JavaScript/DOM manipulations.
Note that if you open HTML files from a local directory, IE will display a alert, but this will not happen when you open the files from a web server.

Copyright?

This solution is dedicated to the public domain.
posted by apptaro at 11:32 | Comment(39) | TrackBack(0) | HTML
×

この広告は1年以上新しい記事の投稿がないブログに表示されております。