Pic 1: Page
Pic 2: Popup 1
Pic 3: Popup 2
Pic 4: Popup 3
This article describes an improved
PopupControlExtender control for the AJAX Control Toolkit and how to use it to create nested popup controls.
PopupControlExtender is a useful User Interface to improve the user experience of web applications. Using
PopupControlExtender, you can click other parts of a page to cancel an operation and close the opened popup control, and even open another popup control with the same click. Popup controls are widely used to implement data picker controls, or for example, custom comboboxes, date pickers, username pickers, and so on.
PopupControlExtender itself is powerful enough for the most scenarios except that it can only open one popup at a time, so you can not create nested popup controls. If you try to open a popup control within an opened popup control, the opened popup control will be closed and the new popup control will not show.
To resolve this problem, I made some improvements to the
PopupControlExtender control to make it possible to open more than one popup control at the same time.
Using the code
Download the demo project, try it first, and you can use AjaxControlToolkit.dll in the demo project's bin directory directly in your project. The debug version AjaxControlToolkit.dll for .NET 3.5 was created based on the source code that was released in April 1 2011. If you need a release version DLL or a DLL for .NET 4.0, download the source code of the AJAX Control Toolkit, search and replace the file PopupControlBehavior.pre.js with the modified version in the attachment of this article, then build it.
Here is a code snap of the demo:
Date:<asp:TextBox ID="TextBox1" Width="100px" autocomplete="off"
<asp:Panel ID="Panel1" Style="display: none;" runat="server">
<div style="background-color: #7777cc; border: solid 2px #234389;
width: 400px; padding: 10px;">
<div style="padding: 2px; background-color: #234389;
color: White; font-weight: bold;">
<asp:UpdatePanel ID="UpdatePanel1" UpdateMode="Conditional" runat="server">
<asp:TextBox ID="TextBox2" runat="server"></asp:TextBox>
<span style="color: White;">(Please select Year and Month)</span>
<asp:Panel ID="Panel2" runat="server">
<span style="color: White;">Day:</span>
<asp:TextBox ID="TextBox2_Other1" runat="server"></asp:TextBox>
<asp:Button ID="Button2" OnClick="Button2_Click"
runat="server" UseSubmitBehavior="false" Text="Button2" />
<asp:Button ID="ButtonOk1" runat="server" UseSubmitBehavior="false"
Text="Ok1" OnClick="ButtonOk1_Click" />
<asp:Button ID="ButtonCancel1" runat="server" UseSubmitBehavior="false"
Text="Cancel1" OnClick="ButtonCancel1_Click" />
<div style="width:10px; height:90px;"></div>
<asp:TextBox ID="TextBox2_2" runat="server"></asp:TextBox>
<span style="color: White;">
(Anthor popup in the same nested level.)</span>
<asp:Panel ID="Panel2_2" runat="server">
<div style="width:100px; height:60px;background-color: orange;
border: solid 2px #884322;" ></div>
<asp:PopupControlExtender ID="PopupControlExtender1" PopupControlID="Panel1"
TargetControlID="TextBox1" Position="Bottom" runat="server">
Here is the code-behind:
protected void Button2_Click(object sender, EventArgs e)
this.TextBox2_Other1.Text = DateTime.Now.Day.ToString("D2");
protected void ButtonOk1_Click(object sender, EventArgs e)
this.PopupControlExtender1.Commit(this.TextBox2.Text + "-" +
protected void ButtonCancel1_Click(object sender, EventArgs e)
In this demo, we created five
PopupControlExtender control instances that constitute a three level nested structure. We describe the popup controls by levels. For example, in the demo, the body of document is level 0 and the popup control "Popup1" is level 1, and the popup control "Popup2" is level 2, and so on. The popup rules are:
- If you click on the target control of a
PopupControlExtender, the popup control that is in the current level will be closed and the popup control of the
PopupControlExtender will be opened.
- If you click on a popup control, all the opened popup controls in the current level will be closed.
- If you click on the body of the document, all the opened popup controls will be closed.
- In the same level, at most one popup control can be opened at the same time.
UpdatePanel inside a popup control is the most common style to use the
PopupControlExtender; otherwise, you cannot take advantage of the Microsoft AJAX Framework and must implement your own client and server side logic to update the content of the popup control. In this article, we just talk about the situation that uses
UpdatePanel. In the demo, all buttons are server side controls, clicking on these buttons will cause a partial postback. The values of year, month, and day are all generated by the server. In order to make
PopupControlExtenders to work correctly, we need to use an Up
datePanel following these rules:
- The value of the
UpdateMode property of all
UpdatePanels should be set as "
- The value of the
UseSubmitBehavior property of all
Buttons should be set as "false".
In fact, there are no differences between using the original
PopupControlExtender and the improved one, except that with the improved
PopupControlExtender, you can put the
PopupControlExtender target on a popup control and open two or more nested popup controls at the same time.
By the way, in a real project, you should split these popup controls into separate UserControls to make the code readable, maintainable, and reusable.
Improvements made to the PopupControlExtender control
All controls in the AJAX Control Toolkit have server control and client behavior. To make the
PopupControlBehavior control support nested popups, we need to modify the client behavior of the
PopupControlExtender control which is defined in PopupControlBehavior.pre.js.
If you have read the following code in PopupControlBehavior.pre.js, you will understand why the
PopupControlExtender can only show at most one popup control at a time.
Sys.Extended.UI.PopupControlBehavior.__VisiblePopup = null;
Yes, just how it was designed.
In a page that contains nested popup controls, the page and all popup controls constitute a popup tree where the document body is the root node. For example, in the demo project, the popup tree looks like this:
We do not need to created a data structure to contain info of this popup tree, we just need to maintain a stack to record all of the
PopupControlBehavior objects of the opened popup controls in the opening order. So we create a static field like this:
Sys.Extended.UI.PopupControlBehavior.__VisiblePopups = ;
For example, when Popup3 is opened, the value of the
__VisiblePopups array is like this: [Popup1, Popup2, Popup3].
When a click event occurs: if a popup control catches the event, it stops the event from bubbling up and finds out its
PopupControlBehavior object's position in the
__VisiblePopups array, pops all the
PopupControlBehavior objects that are above it, and calls the
hidePopup method of these objects. If body catches the click event, call
hidePopup of all
__VisiblePopups's elements, and empty it.
When the target control of the
PopupControlExtender catches a click or focus event, find out the related
PopupControlBehavior's position in the
__VisiblePopups array, pop all the
PopupControlBehavior objects that are above it, and call the
hidePopup method of these objects, and show the newly opened popup control.
You can look at the source code for more details.