Pic 1: Page
Pic 2: Popup 1
Pic 3: Popup 2
Pic 4: Popup 3
Introduction
This article describes an improved PopupControlExtender
control for the AJAX Control Toolkit and how to use it to create nested popup controls.
Background
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"
ReadOnly="false" runat="server"></asp:TextBox>
<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;">
Popup1 (yyyy-MM-dd)</div>
<br />
<asp:UpdatePanel ID="UpdatePanel1" UpdateMode="Conditional" runat="server">
<ContentTemplate>
<asp:TextBox ID="TextBox2" runat="server"></asp:TextBox>
<span style="color: White;">(Please select Year and Month)</span>
<asp:Panel ID="Panel2" runat="server">
</asp:Panel>
<asp:PopupControlExtender ID="PopupControlExtender2"
PopupControlID="Panel2" TargetControlID="TextBox2"
Position="Bottom" runat="server">
</asp:PopupControlExtender>
<br />
<br />
<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" />
<br />
<br />
<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:Panel>
<asp:PopupControlExtender ID="PopupControlExtender2_2"
PopupControlID="Panel2_2" TargetControlID="TextBox2_2"
Position="Bottom" runat="server">
</asp:PopupControlExtender>
</ContentTemplate>
</asp:UpdatePanel>
</div>
</asp:Panel>
<asp:PopupControlExtender ID="PopupControlExtender1" PopupControlID="Panel1"
TargetControlID="TextBox1" Position="Bottom" runat="server">
</asp:PopupControlExtender>
Here is the code-behind:
#region PopupControlExtender1
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 + "-" +
this.TextBox2_Other1.Text);
}
protected void ButtonCancel1_Click(object sender, EventArgs e)
{
this.PopupControlExtender1.Cancel();
}
#endregion
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.
Placing an 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 PopupControlExtender
s to work correctly, we need to use an Upd
atePanel following these rules:
- The value of the
UpdateMode
property of all UpdatePanel
s should be set as "Conditional
". - The value of the
UseSubmitBehavior
property of all Button
s 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.