Fixing combobox from ajax control toolkit
I was using the ComboBox control of Microsoft’s Ajax Control Toolkit and ran into two issues.
- When its initially hidden (its inside something with a style of display:none) and you make it visible, the toggle button and actual dropdown list are incorrectly sized to be almost meaningless.
how it should look:
how it actually looks
Notice the toggle button has virtually disappeared. That grey underline is the dropdown list.
- When its in a ModalDialog created by the ModalPopupExtender, the dropdown lists appear far away from where they belong.
Here the Category dropdown toggle was clicked. The list should be flush with the bottom of the Category’s textbox.
I created the following class to solve both of these problems. It was developed with v3.0.30512.1 of the AjaxControlToolkit.dll. (Get it as a zip file here)
1: /// <summary>
2: /// These static methods fix bugs related to the AjaxControlToolkit's ComboBox.
3: /// </summary>
4: public class ComboBoxFixer
5: {
6: /// <summary>
7: /// Use this when adding ACT Comboboxes that may be initially hidden when loaded.
8: /// It registers all comboboxes into a client-side array. If the user writes
9: /// scripts that make the ACT Comboboxes visible, they should call
10: /// ActComboBoxMadeVisible_All(), a client-side method.
11: /// </summary>
12: public static void RegisterComboBox(AjaxControlToolkit.ComboBox pComboBox)
13: {
14: ClientScriptManager vClientScript = pComboBox.Page.ClientScript;
15: if (!vClientScript.IsClientScriptBlockRegistered("ActComboBoxFixer"))
16: {
17: string vScript =
18: "function ActComboBoxMadeVisible_All()\r\n" +
19: "{\r\n" +
20: " if (window.gActComboboxes)\r\n" +
21: " for (var vI = 0; vI < window.gActComboboxes.length; vI++)\r\n" +
22: " {\r\n" +
23: " var vCB = $find(window.gActComboboxes[vI]);\r\n" +
24: " if (vCB)\r\n" +
25: " {\r\n" +
26: " ActComboBoxMadeVisible(vCB);\r\n" +
27: " }\r\n" +
28: " }\r\n" +
29: "}\r\n" +
30: "function ActComboBoxMadeVisible(pCB)\r\n" +
31: "{\r\n" +
32: " if (pCB && !pCB._optionListItemHeight)\r\n" +
33: " {\r\n" +
34: " var vBtn = pCB.get_buttonControl();\r\n" +
35: " vBtn.style.width = '';\r\n" +
36: " vBtn.style.height = '';\r\n" +
37: " pCB.initializeButton();\r\n" +
38: " pCB._optionListHeight = null;\r\n" +
39: " pCB._optionListWidth = null;\r\n" +
40: " pCB._optionListItemHeight = 21;\r\n" +
41: " pCB._getOptionListWidth();\r\n" +
42: " pCB._getOptionListHeight();\r\n" +
43: " }\r\n" +
44: "}\r\n";
45: vClientScript.RegisterClientScriptBlock(typeof(Page), "ActComboBoxFixer", vScript, true);
46: }
47: pComboBox.Page.ClientScript.RegisterArrayDeclaration("gActComboboxes", "'" + pComboBox.ClientID + "'");
48: }
49:
50: /// <summary>
51: /// Helps ModalDialogExtenders work with AjaxControlToolkit.ComboBox. Call for each ModalDialogExtender
52: /// that may have ComboBoxes. If you have more than one and they may appear simulateously, add them
53: /// in a specific order where the topmost one is added before those below it.
54: /// </summary>
55: /// <remarks>
56: /// <para>Requires each ComboBox is passed to ComboBoxFixer.RegisterComboBox.</para>
57: /// </remarks>
58: /// <param name="pModalExtender"></param>
59: public static void RegisterModalPopupExtender(AjaxControlToolkit.ModalPopupExtender pModalExtender)
60: {
61: ClientScriptManager vClientScript = pModalExtender.Page.ClientScript;
62: vClientScript.RegisterArrayDeclaration("gACTModalDEIDs", "'" + pModalExtender.ClientID + "'");
63:
64: if (!vClientScript.IsClientScriptBlockRegistered("ActComboBoxInMDE"))
65: {
66: // The basic idea: Replace AjaxControlToolkit._popupShown with ActComboBoxInMDE_PopupShown.
67: // ActComboBoxInMDE_PopupShown is a clone of _popupShown, but inserts code to change x,y
68: // when a ModalDialogExtender is visible.
69: // MDEs are registerd in the client-side array gACTModalDEIDs.
70: // This allows multiple MDEs and will only evaluate the first whose content element (called _foregroundElement)
71: // is visible. So if there are nested MDEs, register the topmost one first and bottommost last.
72: string vScript =
73: "function ActComboBoxInMDE_Init()\r\n" +
74: "{\r\n" +
75: " if (window.gActComboboxes)\r\n" +
76: " for (var vI = 0; vI < gActComboboxes.length; vI++)\r\n" +
77: " {\r\n" +
78: " var vCB = $find(gActComboboxes[vI]);\r\n" +
79: " if (vCB.InitedMDE) continue;\r\n" +
80: " vCB._popupShown = ActComboBoxInMDE_PopupShown;\r\n" +
81: " vCB._popupShownHandlerFix = Function.createDelegate(vCB, ActComboBoxInMDE_PopupShown);\r\n" +
82: " vCB._popupBehavior.add_shown(vCB._popupShownHandlerFix);\r\n" +
83: " vCB.InitedMDE = 1;\r\n" +
84: " }\r\n" +
85: "}\r\n" +
86: "function ActComboBoxInMDE_PopupShown() {\r\n" +
87: "\r\n" +
88: " this.get_optionListControl().style.display = 'block';\r\n" +
89: "\r\n" +
90: " // check and enforce correct positioning.\r\n" +
91: " var tableBounds = Sys.UI.DomElement.getBounds(this.get_comboTableControl());\r\n" +
92: " var listBounds = Sys.UI.DomElement.getBounds(this.get_optionListControl());\r\n" +
93: " var textBoxBounds = Sys.UI.DomElement.getBounds(this.get_textBoxControl());\r\n" +
94: " var y = listBounds.y;\r\n" +
95: " var x;\r\n" +
96: "\r\n" +
97: " if (this._popupBehavior.get_positioningMode() === AjaxControlToolkit.PositioningMode.BottomLeft\r\n" +
98: " || this._popupBehavior.get_positioningMode() === AjaxControlToolkit.PositioningMode.TopLeft) {\r\n" +
99: " x = textBoxBounds.x;\r\n" +
100: " }\r\n" +
101: " else if (this._popupBehavior.get_positioningMode() === AjaxControlToolkit.PositioningMode.BottomRight\r\n" +
102: " || this._popupBehavior.get_positioningMode() === AjaxControlToolkit.PositioningMode.TopRight) {\r\n" +
103: " x = textBoxBounds.x - (listBounds.width - textBoxBounds.width);\r\n" +
104: " }\r\n" +
105: "\r\n" +
106: " if (window.gACTModalDEIDs)\r\n" +
107: " for (var vI = 0; vI < gACTModalDEIDs.length; vI++)\r\n" +
108: " {\r\n" +
109: " var vMDE = $find(gACTModalDEIDs[vI]);\r\n" +
110: " if (vMDE._foregroundElement.style.display == '')\r\n" +
111: " {\r\n" +
112: " var vMDBounds = Sys.UI.DomElement.getBounds(vMDE._foregroundElement);\r\n" +
113: " x = x - vMDBounds.x;\r\n" +
114: " y = (textBoxBounds.y + textBoxBounds.height) - vMDBounds.y;\r\n" +
115: " break;\r\n" +
116: " }\r\n" +
117: " }\r\n" +
118: " Sys.UI.DomElement.setLocation(this.get_optionListControl(), x, y);\r\n" +
119: "\r\n" +
120: " // enforce default scroll\r\n" +
121: " this._ensureHighlightedIndex();\r\n" +
122: " this._ensureScrollTop();\r\n" +
123: "\r\n" +
124: " // show the option list\r\n" +
125: " this.get_optionListControl().style.visibility = 'visible';\r\n" +
126: "\r\n" +
127: "}\r\n";
128: vClientScript.RegisterClientScriptBlock(typeof(Page), "ActComboBoxInMDE", vScript, true);
129:
130: vClientScript.RegisterStartupScript(typeof(Page), "ActComboBoxInMDEInit",
131: "Sys.Application.add_load(ActComboBoxInMDE_Init);\r\n", true);
132:
133: // on MDE popup, also fix comboboxes
134: vScript =
135: "function ActComboBoxInMDE_MDEPopupInit()\r\n" +
136: "{\r\n" +
137: " if (window.gACTModalDEIDs)\r\n" +
138: " for (var vI = 0; vI < gACTModalDEIDs.length; vI++)\r\n" +
139: " {\r\n" +
140: " var vMD = $find(gACTModalDEIDs[vI]);\r\n" +
141: " vMD.add_shown(ActComboBoxInMDE_MDEPopupShown);\r\n" +
142: " }\r\n" +
143: "}\r\n" +
144: "function ActComboBoxInMDE_MDEPopupShown(sender, args)\r\n" +
145: "{\r\n" +
146: " if (window.ActComboBoxMadeVisible_All)\r\n" +
147: " ActComboBoxMadeVisible_All();\r\n" +
148: "}\r\n";
149: vClientScript.RegisterClientScriptBlock(typeof(Page), "ActComboBoxInMDE_MDEPopupInitBlock", vScript, true);
150: vClientScript.RegisterStartupScript(typeof(Page), "ActComboBoxInMDE_MDEPopupInit",
151: "Sys.Application.add_load(ActComboBoxInMDE_MDEPopupInit);\r\n", true);
152: }
153: }
154: }
You can retrieve a zip file with C# and VB versions of this code here.
Here is how to use this class:
- Call ComboBoxFixer.RegisterComboBox(combobox) for each ComboBox that is initially hidden. Generally this is done in Page_Load or if inside of a Databound control like ListView or FormView, from their ItemCreated event.
- Call ComboBoxFixer.RegisterModalPopupExtender(modalpopupextender) for each ModalPopupExtender that contains ComboBoxes. If you have nested ModalDialogs, register the topmost first.
- If you have a button that makes the comboboxes visible, add this javascript to its client-side onclick event. It must be run after the comboboxes are visible.
ActComboBoxMadeVisible_All();
A little background on what these scripts do:
- The ComboBox initializes the sizes of its toggle button and list when the page is first loaded. If this happens when the control is hidden, its calculations do not get the correct sizing information as the clientWidth and clientHeight properties of DOM elements are usually 0 in this case. The ActComboBoxMadeVisible(cb) function resets properties that determine width. It explicitly calls a function to recalculate the button size and lets the ComboBox’s _popupShowing() method use the rest to know to recalculate.
- The ComboBox’s _popupShown() method calculates the x and y coordinates for the dropdownlist using the offset of its textbox from the upper left of the browser window. It uses style=”position:absolute;top:x;left:y;” to position it. This works well until the ModalPopupExtender gets involved. The ModalPopupExtender uses style=”position:fixed” and this appears to impact positioning. _popupShown() needs to position from the upper left of the <div> using that style=”position:fixed”.
To fix this, _popupShown() is replaced by a clone generated by ComboBoxFixer.RegisterModalPopupExtender(). This new function inserts code that detects if a ModalPopupExtender is visible and adjusts the x and y coordinates based on its location.