Custom HTML Dropdown control (part 1)
My problem stated with the fact that if you set a width to a dropdown (select) control, IE clips the contents of a drop down list when expanded.
IE 7 |
Firefox 3 |
To solve this, I decided to create my own custom dropdown control and as a bonus be able to add some more custom functionality like ComboBox features, more visual effects, etc.
The basic principle is to have two text boxes (one for the selected text and another one hidden for the selected value), a down arrow button to show the list of options and a div that shows the options. All this peppered with some javascript (using jquery to ease things up) to control the behavior.
The Text boxes & button
1: <div>
2: <span class="textBoxWrapper">
3: <input type="text" id="ddText" class="ddTextBox" readonly>
4: <img src="gfx/downArrow.gif" id="btn" align="texttop" class="arrowImg">
5: </span>
6: </div>
7: <input id="ddValue" type="text" style="display:none;">
The first textbox (ddText) will hold the selected text, the second textbox (ddValue) will hold the value attribute of the selected item (if any) so it can be retrieved from the server side; and all these is wrapped in a span and a div. We need outer div to be able to make the span floated and having the whole control work as expected.
The list of options
1: <div id="listBox" class="listBox" style="display:none;" tabindex="0">
2: <ul>
3: <li value="1">Item 1 with a lot of stuff to make it bigger</li>
4: <li value="2">Item 2 a little shorter</li>
5: <li value="3">Item 3</li>
6: <li value="4">Item 4</li>
7: <li value="5">Item 5</li>
8: <li value="6">Item 6</li>
9: <li value="7">Item 7</li>
10: <li value="8">Item 8</li>
11: </ul>
12: </div>
The list of options is a regular div with an unorder list to hold the options. The only two special things about this code are the tabindex="0" property of the div that will help us workaround a problem with the OnBlur event on firefox not firing, and the non-standard "value" attribute of the list items.
With all these code we'll have the following control:
Styling the control
The wrapper and text boxes
1: .textBoxWrapper {
2: border:solid 1px #7F9DB9;
3: padding:0;
4: white-space:nowrap;
5: height:1em;
6: }
7: .ddTextBox {
8: border:solid 0px white;
9: width:120px;
10: padding-left:.2em;
11: cursor:arrow;
12: }
13: .arrowImg {
14: margin:1px;
15: }
The wrapper simple has a solid border with no padding and the white-space:nowrap will prevent the img from wrapping below the textbox.
Nothing special in the ddTextBox, we just remove the border (since we added the border to the span wrapped around the textbox and down arrow), set the width as desired and add some padding to make it look good, and we end up with this:
The styles for the list of options look like this:
1: .listBox {
2: height:100px;
3: overflow-y:auto;
4: overflow-x:hidden;
5: border:solid 1px black;
6: background-color:white;
7: white-space:nowrap;
8: float:left;
9: position:absolute;
10: }
11: .listBox ul {
12: margin:0px;
13: padding:0px;
14: list-style-type:none;
15: }
16: .listBox ul li {
17: cursor:arrow;
18: padding-left:.2em;
19: padding-right:1.3em;
20: }
21: .highLight {
22: background-color:#316ac5;
23: color:white;
24: }
The special things here are the overflow-y:auto to show the scroll bar if the content is greater than the defined height, and the float:left and position:absolute so it shows on top of the other elements on the page as expected. The rest of the classes are to format the <ul> and <li> elements and to highLight the options when we hover over them.
And that's all its needed to format our drop down for now
The script
Now all the magic happens in a set of javascript functions to handle the different events for our control:
1: <script src="js/jquery-1.2.6.min.js" type="text/javascript"></script>
2: <script type="text/javascript">
3: $(document).ready(function() {
4: var $items = $('#listBox ul li');
5: var $listBox = $('#listBox');
6:
7: $('#btn').click(function() {
8: var minWidth = $('#ddText').outerWidth() + $('#btn').outerWidth();
9: $listBox.css("left",$('#ddText').eq(0).offset().left).css("min-width",minWidth);
10: if($listBox.css("display")=="none") {
11: $listBox.show("fast", function() {
12: $listBox.scrollTop(0).eq(0).focus();
13: });
14: }
15: else {
16: $listBox.hide("fast");
17: }
18: });
19:
20: $items.click(function(e) {
21: $('#ddText').val($(this).text());
22: $('#ddValue').val($(this).attr("value"));
23: $listBox.hide("fast");
24: });
25:
26: $('#ddText').click(function() {
27: $('#btn').click();
28: });
29:
30: $items.hover(function() {
31: $(this).addClass("highLight");
32: }, function() {
33: $(this).removeClass("highLight");
34: }
35: );
36:
37: $listBox.blur(function(e) {
38: $listBox.hide("fast");
39: });
40: });
41: </script>
When the btn.click event fires (the user click on the down arrow) we need to adjust the position of the listbox to be be below the textbox and show the div
When the user selects and item, we'll get the selected text and selected value and set them to the corresponding textBoxes, and hide the list box
When the hover event happens on the options we simple swap in and out the highLight class.
When the the user clicks on anything else on the page outside the list box (onblur) we need to hide the listbox without making a selection.
And that's it! we have a fully functional drop down that corrects the IE clipping problem and is ready to extend as we want (ie. combobox functionality, finer control over style, autocomplete, etc.).
In the next part we'll convert this into a server control to bind it to some server data and maybe we can add some combo box functionality.
For the complete source click here
EDIT: Next part is The Server Control