Dialog boxes are plain windows and can work with or without modality. A modal window
is a window that disables entirely any visual elements underneath it.
The user can’t take any action on the current application until he or
she dismisses the modal window. The most popular Web counterpart of
dialog boxes are popups.
The
internal object model of most Web browsers already provides a native
API to manage popups, and it supplies methods to open and close such
windows with and without modality. Largely abused by some Web sites,
popups are blocked by most client browsers and are no longer a valid
option to drill down information over the Web. However, this doesn’t
mean that modal dialog boxes are definitely banned from Web
applications.
It is
essential to come up with a different implementation of dialog boxes
that is as effective as modal popups and totally hassle-free for end
users. The problem with classic popups is that they are just additional
browser windows adorned with a different set of styles and fully
controlled via script. A truly modal Web window, instead, provides you
with the same modal effect of classic popups, but it leverages the page’s object model rather than the browser’s object model.
In the ACT, you find a number of extenders to provide popup functionalities. The most compelling is the ModalPopup extender.
The ModalPopup Extender
The ModalPopup
extender adds modality to a piece of markup—typically, a panel. Bound
to a button control, it pops up the specified panel and disables the
underlying page. Any clicking on anything other than the elements in
the topmost panel is lost and never reaches the intended target. ModalPopup performs a smart trick by adding an invisible <div>
tag to cover the entire browser window. This layer swallows any user
action and stops it from reaching underlying controls. With clever CSS
coding, you can add some nice effects, such as graying out anything
underneath the top-most panel, as shown in Figure 1.
The
modal popup extender just takes the markup generated by a server-side
ASP.NET panel and shows and hides it as the user clicks on a linked
HTML element. Initially styled to be hidden, the markup used for the
dialog box is downloaded on the client when the host page is loaded and
then shown and hidden on demand.
You
start by defining a panel to provide the user interface, and then you
add a button control to trigger the display of the dialog box:
<asp:Button runat="server" ID="btnViewMore" Text="More" />
<asp:Panel runat="server" ID="pnlViewCustomer">
<div style="margin:10px">
<h1>The service is not available in <span id="lblCountry"></span>.</h1>
<asp:Button runat="server" ID="viewBox_OK" Text="OK" />
</div>
</asp:Panel>
Next, you set up the extender and specify the target control ID and the popup control ID:
<act:ModalPopupExtender ID="ModalPopupExtender1" runat="server"
TargetControlID="btnViewMore"
PopupControlID="pnlViewCustomer"
BackgroundCssClass="modalBackground"
OkControlID="viewBox_OK"
OnOkScript="allSet()" />
The
target control ID of a modal popup extender is the ID of the server
control that, when clicked, causes the dialog box to pop up. The popup
control ID is the ID of the server control that provides the content
for the dialog box.
The OkControlID
property allows you to identify a button control in the popup panel to
be used to dismiss the panel with an OK answer. When the panel is
dismissed using the OK button, the associated OnOkScript JavaScript function, if any is present, is executed:
<script>
function allSet()
{
...
}
</script>
The
content of the popup panel can be initialized before display on both
the client and the server. On the client, you register a handler for
the showing event:
function pageLoad(sender, args)
{
$find("ModalPopupExtender1").add_showing(onModalShowing);
}
function onModalShowing(sender, args)
{
$get("pnlViewCustomer").style.backgroundColor = "yellow";
}
On the server, you do it in the code associated with the Click event of the trigger button. However, in this case the popup panel must be wrapped in an UpdatePanel and brought up programmatically from the server:
protected void btnEditText_Click(object sender, EventArgs e)
{
// Initialize the controls in the panel used as the UI of the dialog box
InitDialog();
// The panel markup has already been served to the page. To edit it,
// you need to wrap the panel's content in an UpdatePanel region and
// update the panel once you make any changes
popupPanel.Update();
// Inject the script to show the dialog as the page is loaded in the browser.
ModalPopupExtender1.Show();
}
There’s another trick that contributes to making this code work. The ModalPopup
extender is bound to an invisible button so that it can never be
brought up via the user interface. If you bind the popup to a visible
push button, the Click
server event will never be fired and you have no way to initialize the
control in the popup panel. For more information and details on Web
dialog boxes, you might want to read the March 2008 installment of my
“Cutting Edge” column in MSDN Magazine.
The PopupControl Extender
The PopupControl extender differs from ModalPopup because it can be dismissed by simply clicking outside. The PopupControl extender can be attached to any HTML element that fires the onclick, onfocus, or onkeydown
events. The ultimate goal of the extender is to display a pop-up window
that shows additional content, such as a calendar on a text box in
which the user is expected to enter a date. The contents of the pop-up
panel are expressed through a Panel control, and they can contain ASP.NET server controls as well as static text and HTML elements:
<asp:textbox runat="server" ID="txtInvoiceDate" />
<asp:panel runat="server" ID="Panel1">
...
</asp:panel>
<act:PopupControlExtender ID="PopupExtender1" runat="server"
TargetControlID="txtInvoiceDate"
PopupControlID="Panel1"
Position="Bottom" />
The TargetControlID property points to the control that triggers the popup, whereas PopupControlID indicates the panel to display. The Position
property sets the position of the panel—either at the top, left, right,
or bottom of the parent control. Additional properties are OffsetX and OffsetY, which indicate the number of pixels to offset the popup from its position, as well as CommitProperty and CommitScript, which can be used to assign values to the target control.
The
pop-up window will probably contain some interactive controls and post
back. For this reason, you might want to insert it within an UpdatePanel
control so that it can perform server-side tasks without refreshing the
whole page. Typically, the popup will be dismissed after a postback—for
example, after the user has selected a date in a Calendar control. The Calendar control in this case fires the SelectionChanged event on the server:
protected void Calendar1_SelectionChanged(object sender, EventArgs e)
{
PopupExtender1.Commit(Calendar1.SelectedDate.ToShortDateString());
}
The Commit
method sets the default property of the associated control to the
specified value. If you want to control which (nondefault) property is
set on the target when the popup is dismissed, use the CommitProperty property. Likewise, you use the CommitScript property to indicate the JavaScript function to execute on the client after setting the result of the popup.
Note
An extender can’t be placed in an UpdatePanel that is different than the control it extends. If the extended control is incorporated in an UpdatePanel, the extender should also be placed in the updatable panel. If you miss this, you get a runtime exception. |
The HoverMenu Extender
The HoverMenu extender is similar to the PopupControl
extender and can be associated with any ASP.NET control. Both extenders
display a pop-up panel to display additional content, but they do it
for different events. The HoverMenu,
in particular, pops up its panel when the user moves the mouse cursor
over the target control. The panel can be displayed at a position
specified by the developer. It can be at the left, right, top, or
bottom of the target control. In addition, the control can be given an
optional CSS style so that it looks like it is in a highlighted state.
(See Figure 2.)
The HoverMenu
extender is good for implementing an auto-display context menu for
virtually every ASP.NET control instance and for providing tips to fill
in some input fields. In Figure 2, for example, when the user hovers the cursor over the text box, a list of suggestions appears to simplify the work:
<asp:TextBox ID="TextBox1" runat="server" />
<asp:Panel ID="Panel1" runat="server" CssClass="popupMenu">
<asp:RadioButtonList ID="RadioButtonList1" runat="server" AutoPostBack="true"
OnSelectedIndexChanged="RadioButtonList1_SelectedIndexChanged">
<asp:ListItem Text="Dino Esposito"></asp:ListItem>
<asp:ListItem Text="Nancy Davolio"></asp:ListItem>
<asp:ListItem Text="Andrew Fuller"></asp:ListItem>
<asp:ListItem Value="" Text="None of the above"></asp:ListItem>
</asp:RadioButtonList>
</asp:Panel>
<act:HoverMenuExtender ID="HoverMenu1" runat="server"
TargetControlID="TextBox1"
HoverCssClass="hoverPopupMenu"
PopupControlID="Panel1"
PopupPosition="Right" />
The Panel1 control defines a list of radio buttons, each containing a suggestion for filling the text box. The HoverMenu extender targets the text box control and defines Panel1 as its dynamic pop-up panel. The PopupPosition
property indicates the position of the panel with respect to the target
control. Likewise, other properties not shown in the previous example
code, such as OffsetX and OffsetY, define the desired offset of the panel. The PopDelay sets the time (in milliseconds) to pass between the mouse movement and the display of the panel. The HoverCssClass
can optionally be used to give the text box a different style when the
hover menu is on. It is interesting to look at the CSS class associated
with the panel:
.popupMenu
{
position:absolute;
visibility:hidden;
background-color:#F5F7F8;
}
.hoverPopupMenu
{
background-color:yellow;
}
Just as for the PopupControl extender, to take full advantage of the HoverMenu extender you need to place extended controls inside of an UpdatePanel control. In this way, whenever the user clicks a radio button, the panel posts back asynchronously and fires the SelectedIndexChanged event on the server:
void RadioButtonList1_SelectedIndexChanged(object sender, EventArgs e)
{
TextBox1.Text = RadioButtonList1.SelectedValue;
}
The server-side event handler will then just update the text in the text box, as shown in Figure 19-14.
The TabContainer Control
Multiple
views are a common feature in most pages. They group information in
tabs and let users click to display only a portion of the information
available. In ASP.NET, the MultiView
control provides an effective shortcut to this feature. But it requires
a postback to update the page when the user selects a new tab. In the
ACT, the TabContainer control provides a free AJAX version of the multiview control.
The TabContainer control includes a list of child TabPanel objects you can access programmatically via the Tabs collection. You add one <TabPanel> tag for each desired tab and configure it at will. Here’s an example:
<act:TabContainer runat="server" ID="TabContainer1">
<act:TabPanel runat="server" ID="TabPanel1" HeaderText="Your Tab">
<ContentTemplate>
<h3>Some text here</h3>
</ContentTemplate>
</act:TabPanel>
...
</act:TabContainer>
All tabs are given the same size, and you can control the size designation through the Width and Height properties of the container. The height you set refers to the body of tags and doesn’t include the header.
You
can add some script code to run when the user selects a new tab. You
can wrap up all the code in a page-level JavaScript function and bind
the name of the function to the OnClientActiveTabChanged
property of the tab container. The following code writes the name of
the currently selected tab to a page element (originally, an ASP.NET Label control) named CurrentTab:
<script type="text/javascript">
function ActiveTabChanged(sender, e)
{
var tab = $get('<%=CurrentTab.ClientID%>');
tab.innerHTML = sender.get_activeTab().get_headerText();
}
</script>
Note
the usage of code blocks in JavaScript. In this way, the client ID of
the label is merged in the script regardless of whether the page is a
regular page or a content page (with a hierarchy of parent controls and
naming containers). Figure 3 shows the control in action.