Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / ASP.NET

Modify and Use PopupControlExtender to Create Nested Popups

4.50/5 (4 votes)
20 Apr 2011CPOL5 min read 75.7K   3.7K  
An improved PopupControlExtender control for the AJAX Control Toolkit and how to use it to create nested popup controls.

Popup 1 of nested popups

Pic 1: Page

Popup 1 of nested popups

Pic 2: Popup 1

Popup 2 of nested popups

Pic 3: Popup 2

Popup 2 of nested popups

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:

ASP.NET
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">
                    <!-- content of Panel2 was omitted -->
                </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:

C#
#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:

  1. 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.
  2. If you click on a popup control, all the opened popup controls in the current level will be closed.
  3. If you click on the body of the document, all the opened popup controls will be closed.
  4. 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 PopupControlExtenders to work correctly, we need to use an UpdatePanel following these rules:

  1. The value of the UpdateMode property of all UpdatePanels should be set as "Conditional".
  2. 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.

JavaScript
// This global variable tracks the currently visible popup. Automatically
// hiding the popup when focus is lost does not work with our mechanism to
// hide the popup when something else is clicked... So we will instead go for
// the weaker strategy of letting at most one popup be visible 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:

Popup 2 of nested popups

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:

JavaScript
// @@@@@
// Tracks all the opened popups. 
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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)