Contents
WebModal
is an easy to use, ASP.NET server control which encapsulates the complexities of opening and passing information to/from a modal window. WebModal
does not require writing any JavaScript, and can be implemented with very little code. Design-time support also makes integrating with other server controls simple.
An intranet environment commonly requires modal window support. In this environment, a form may require multiple master-detail relationships which must be completed entirely before saving it. WebModal
makes this simple by allowing a detail record to be passed to a modal window, where it is edited, and returned back to the main page. This can be done without storing the information in a relational database until all processing is complete. As another example, a user wants to send an email to another employee, so they click a "TO" button which opens a modal window, the user selects an employee from a list, and the employee information is returned to the main page. In general, designs that allows non-sequential manipulation of data are good candidates for modal dialogs.
ASP.NET has given web application developers many robust tools for creating advanced user interfaces, but falls surprisingly short when it comes to modal windows. The following are some of the challenges when trying to implement modal windows:
- No server control implementation � requires extensive use of JavaScript.
- If a modal window attempts to post back to itself, a new window is inadvertently opened.
- As a result of #2, a modal window must load an IFrame to contain the actual page.
- The complexity of managing the flow of functionality and information from the main form to the window and back again, which is illustrated below in the table.
- The data must be encoded for JavaScript, and for XML to transport correctly.
- Solutions commonly using the query string to transport data to the window are limited by query string length constraints.
- It is difficult to reuse a non-server control version, which results in multiple copies of the JavaScript with subtle variations.
Several sample pages are provided to illustrate various implementations of the WebModal
. They do not do anything particularly useful or represent best coding standards of any kind, but they attempt to show off the variety of ways that a developer might choose to implement the WebModal
. They have also been helpful to test the WebModal
during development. Index.aspx is the starting point for all the samples.
As the name indicates, this is the simplest and most straightforward way to implement the WebModal
. No custom processing or coding of any kind is being implemented, except for one property which is added at runtime. Basically, the WebModalAnchor
is passing a set of Properties
and a query string parameter to a Modal window, which then lists them out.
When ClientSideSupport
is set to true
for the WebModalAnchor
, a complete object model of the WebModalAnchor
is constructed in JavaScript, which enables the developer to make client-side changes to the WebModalAnchor
before opening the modal window. For example, the value in a form field could be added to the Properties
collection, without requiring a round-trip to the server.
The datagrid example shows how to uses the WebModalAnchor
control inside of a templated databound control. It shows how to pass a field from the bound datasource as a Property
, and also how to bind the WebModal.OnWindowClose
event.
The most complex implementation is constructing the entire WebModalAnchor
at runtime. This may seem similar to binding it to a datagrid, but it brings unique challenges of its own. In this implementation, the grid is rendered twice. The first rendering wires the events and the second time renders the anchor after processing has occurred. There may be better practices for constructing dynamic pages, but the WebModalAnchor
needed to take this into consideration, because it is a common implementation.
This is the crown jewel of the .NET framework, and the WebModalAnchor
implementation. All the complexities of constructing a modal window are encapsulated inside of a server control, allowing it to be sub-classed as a specific implementation. Developers will inevitably need to use the same dialog in multiple places, which can be rolled into specialized versions of the WebModalAnchor
. In this sample, an EmployeeSearchAnchor
is derived from the WebModalAnchor
, hiding many of the non-changing details required to open an employee search. In addition to hiding, it also extends the WebModalAnchor
with an EmployeeType
enum, an EmployeeID
property, and a custom event, which returns the EmployeeID
back to the main page.
One of the primary benefits of using the WebModalAnchor
is the encapsulation of the complex execution flow.
# |
Execution Location |
Main/Modal Window |
Description of Processing |
1 |
Server |
Main |
Instantiate WebModalAnchor . |
2 |
Server |
Main |
Set Properties and Attributes. |
3 |
Client |
Main |
If ClientSideSupport is false , wire the JavaScript event (usually a button click) to open the modal window.
If ClientSideSupport is true , renders an object modal of the WebModalAnchor which can be manually controlled in JavaScript. |
4 |
Server |
Modal |
IFrame and hidden from are constructed and rendered. |
5 |
Client |
Modal |
Once the window is done loading, JavaScript submits the anchor contained in the form to the the IFrame. |
6 |
Server |
Modal |
Instantiate WebModalWindowHelper . |
7 |
Server |
Modal |
WebModalWindowHelper processes the data from the Main page and makes it available to the modal window. |
8 |
Server/Client |
Modal |
Your Standard page processing occurs. |
9 |
Server |
Modal |
CloseForm is set to true , usually in a button onclick event, and the WebModalWindowHelper renders the JavaScript required to close the window and return the OutputData .
|
10 |
Client |
Modal |
JavaScript pushes OutputData back to the Main window, and closes the window. |
11 |
Client |
Main |
JavaScript rendered in step 3 takes the OutputData send from the modal window and inserts it into a hidden field.
If ClientSideSupport is true , the JavaScript anchor's OutputData property is updated with the returned value.
If CausePostBack was set to true in Modal window, the form is submitted and sent back to the server. |
12 |
Server |
Main |
WebModalAnchor processes the data from the modal window and makes it available to the main page. If an OnWindowClose event is wired, it is fired during the Page_Load timeframe. |
The table below describes the simple and intuitive interface. This interface documentation combined with the many samples provided will make implementing the WebModalAnchor
an easy task.
Codesummit.WebModalAnchor |
Property |
Description |
Type |
ClientSideSupport
|
If true , builds an object model of the anchor on the client for manual processing. |
bool
|
HandledEvent
|
The JavaScript event that the anchor is wired to, such as "onclick ". |
CodeSummit.JSEvent Enum
|
LinkedControl
|
A read-only reference to the object identified by LinkedControlID . |
System.Web.UI.Control |
LinkedControlID
|
ID of the control that the anchor is bound to. The anchor will resolve the ClientID at runtime. |
string
|
OutputData
|
Contains the data returned from the modal window. |
string |
Properties
|
Collection of Property(Key, Value). Objects which will be sent to the modal window. |
CodeSummit.Properties(Collection) |
Scrolling
|
If true , modal window will have scrollbars. |
bool
|
ShowVersion
|
If true , the assembly version will appear in the designer. |
bool
|
Title
|
Modal window title. |
string
|
URL
|
Path loaded into modal window. |
string
|
WindowHeight
|
Height of modal window. |
System.Web.UI.WebControls.Unit
|
WindowWidth
|
Width of modal window. |
System.Web.UI.WebControls.Unit
|
Event |
Description |
Type |
OnWindowClose
|
Gives the page hosting the anchor an opportunity to take action on the OutputData returned from the modal window |
CodeSummit.OnWindowCloseEventHandler(WebModalAnchor sender) |
Codesummit.WebModalWindowHelper |
Property |
Description |
Type |
CausePostBack
|
If true , the main page will postback to the server when the window closes. |
bool
|
CloseForm
|
Typically used in a button on click event, used to notify that the form is to close. |
bool
|
OutputData
|
Contains the data being returned to the main form. |
string |
Properties
|
Collection of Property(Key, Value) Objects which is sent from the main page. |
CodeSummit.Properties(Collection) |
The WebModalAnchor
and the WebModalWindowHelper
have complete, robust design-time support. All of the designer details aren't covered in this article, but it may be worth noting the implementation of the designer for the Properties
collection. This is a standard implementation of a custom collection object (Properties
) deriving from a CollectionBase
, which contains a collection of custom objects (Property
). Therefore, it may be useful to implement it in other solutions. The WebModalAnchor
class contains the following attributes:
ParseChildren(true, "Properties"),
PersistChildren(false)
The ParseChildren
attribute allows the designer to get the contents of the WebModalAnchor
tag and treat them as a Properties
collection. Note that the "Properties" tag itself doesn't appear in the WebModalAnchor
tag. The PersistChildren
is false
, which means that any sub controls of the Control
don't appear in the designer. In this case, there aren't any.
[
Editor(typeof(PropertyCollectionEditor), typeof(UITypeEditor)),
DesignerSerializationVisibility(
DesignerSerializationVisibility.Content),
PersistenceMode(PersistenceMode.InnerDefaultProperty),
Description("Collection of Property(string Key,
string Value) objects sent to Modal Window.")
]
public Properties Properties
{
...
}
The Properties
property has several attributes which modify the Designer's behavior. DesignerSerializationVisibility(DesignerSerializationVisibility.Content)
tells the Designer to store the property inside the control's tag, and PersistenceMode(PersistenceMode.InnerDefaultProperty)
tells the Designer that this will be the only content inside the control's tag. A custom CollectionEditor
(PropertyCollectionEditor
) is defined, which tells the designer to use this when editing the property in the PropertyGrid. It sets the object type that it will edit to "Property". This is what the PropertyCollectionEditor
looks like:
internal class PropertyCollectionEditor : CollectionEditor
{
public PropertyCollectionEditor(Type type) : base(type)
{
}
protected override Type CreateCollectionItemType()
{
return typeof(Property);
}
}
This is all you need to support simple custom business collections and their objects in the designer.
The control's Onload
event is responsible for setting up the hidden fields used by the JavaScript, pulling the OutputData
back from the modal window, and firing the OnWindowClose
event:
protected override void OnLoad(System.EventArgs e)
{
RegisterJavascript("WebModalJavascript", "Javascript.Common.js");
this.Page.RegisterHiddenField("__WebModalData" , string.Empty);
this.Page.RegisterHiddenField("__CausePostBack" , string.Empty);
this.Page.RegisterHiddenField("__AnchorID" , string.Empty);
if (this.Page.IsPostBack)
{
if((this.Page.Request.Form["__CausePostBack"] != string.Empty) &&
(this.Page.Request.Form["__AnchorID"] == this.ClientID))
{
this.OutputData = HttpUtility.UrlDecode(
this.Page.Request.Form["__WebModalData"]);
if (OnWindowClose != null)
{
if (this.Context.Items["OnWindowCloseFired"] == null)
{
this.Context.Items["OnWindowCloseFired"] = "true";
OnWindowClose(this);
}
}
Clear();
}
}
this.RegisterClientEvent();
}
The control's PreRender
event is responsible for writing out the JavaScript that attaches a control to the window it's going to open. If ClientSideSupport
is set to true
for the WebModalAnchor
, a complete object model of the WebModalAnchor
is constructed in JavaScript, which enables the developer to make client-side changes to the WebModalAnchor
before opening the modal window. Only code from one WebModalAnchor
's PreRender
event gets executed, however. This is an optimization that renders all the controls to a single script block. The WebModalAnchors
are stored in the HttpContext.Items
collection, and retrieved at this time:
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
Hashtable anchors;
StringBuilder sb = new StringBuilder();
if (this.Context.Items["WebModalAnchor"] != null)
{
anchors = ((Hashtable)this.Context.Items["WebModalAnchor"]);
sb.Append("<script type=
\"text/javascript\">//<![CDATA[\n");
sb.Append("WireClientSideEvent(window,
\"onload\", wireAnchors);\n");
foreach (WebModalAnchor anchor in anchors.Values)
{
if (anchor.ClientSideSupport)
{
sb.AppendFormat("var {0} = new WebModalAnchor();\n" , anchor.ClientID);
}
}
sb.Append("function wireAnchors(){\n");
foreach (WebModalAnchor anchor in anchors.Values)
{
if (anchor.NamingContainer.FindControl(anchor.ID) != null)
{
...
if (anchor.ClientSideSupport)
{
}
else
{
sb.AppendFormat("document.getElementById(\"{0}\").{1}=function()",
GetClientID(anchor, anchor.LinkedControlID),
anchor.HandledEvent.ToString("f"));
sb.Append("{");
sb.AppendFormat("return WebModal ( null, \"{0}\", \"{1}\", \"{2}\", \"{3}\"
, \"{4}\", \"{5}\", \"{6}\" ) ", anchor.ClientID, anchor.Title, anchor.URL
, anchor.WindowHeight, anchor.WindowWidth, anchor.Scrolling.ToString()
, HttpUtility.UrlEncode(anchor.Properties.Xml) );
sb.Append("};\n");
}
}
}
sb.Append("}\n//]]></script>");
this.Context.Items["WebModalAnchor"] = null;
this.Page.RegisterClientScriptBlock("WebModalAnchors", sb.ToString());
}
}
The WebModalWindowHelper
is added to the modal window. This class takes care of the processing on the window's side. The WebModalWindowHelper
class has two distinct phases. During the first phase, the WebModalWindowHelper
renders an IFrame which is necessary to enable postbacks to work correctly. The JavaScript it renders also takes care of the window's title and scrollbars. When the page is loaded, JavaScript is executed which posts the data into the IFrame, and causes the second phase to occur. During the second phase, the requested URL is actually loaded in the IFrame, and the Properties
collection is loaded in the WebModalWindowHelper
class. This two phase process superseded a previous version of the control, which used an HttpHandler
to listen for a request to the IFrame. This added additional complexity to the setup and maintenance of the control. Incidentally, this two phase process could be used for any custom control that needs to load something from the server, such as another aspx page or an image.
A function is wired to the hosting Page.Init
event, so that no further processing of the page occurs unnecessarily when it's only purpose is to render the IFrame, during the first pass. This should be taken into consideration when implementing this class on any page that also attaches to the Page.Init
event, as they are not fired necessarily in any order:
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
this.Page.Init += new System.EventHandler(this.Page_Init);
}
protected void Page_Init(object sender, EventArgs e)
{
if (!this.Page.IsPostBack)
{
if (this.Page.Request.QueryString["__LoadIFrame"] != null)
{
string Scrolling;
if (((!(this.Page.Request.QueryString["__Scrolling"] == null)) &&
(this.Page.Request.QueryString["__Scrolling"] == "True")))
{
Scrolling = "yes";
}
else
{
Scrolling = "no";
}
string url = this.Page.Request.Url.ToString();
int internalParameters = url.IndexOf("__LoadIFrame");
url = url.Substring(0, internalParameters-1);
this.Page.Response.Clear();
this.Page.Response.Write(iFrameHtml.Replace("ScrollingParam", Scrolling)
.Replace("ActionParam", url));
this.Page.Response.End();
}
else
{
this.Properties.Xml = System.Web.HttpUtility.
UrlDecode(this.Page.Request.Form["__Properties"]);
}
}
}
At this time, WebModalAnchor
does not currently support non-Internet Explorer browsers. This was not a requirement for this implementation, because it was implemented on an intranet with a known audience. The primary reason it does not work, is window.showModalDialog
is an Internet Explorer only extension, so a workaround would be required.
Different implementations may require further control of the window itself, such as centering, etc.
Many thanks to Tim Lange from http://www.netgolfleague.com/ for some of the initial JavaScript work, and for help testing.
Presented in this article was a turn-key solution for using modal windows in an application. The samples provide a clear example of how to implement it in different situations. This article does not take a position on using or not using modal windows on the web, but provides the tools when they become necessary. End users tend to dislike them because they are restrictive, and developers tend to like them because they are restrictive! So use them judiciously. Constructive feedback is always welcome, and useful changes and fixes will be implemented as soon as time permits.
History
- 0.5.2 (05 Apr 2005)
CausePostback
failed when anchors existed in modal window; fixed conflict with hidden fields.
- Modified DataGrid sample to use odd number paging
- 0.5.1 (01 Apr 2005)
- Modified DataGridSample to use DataGrid Paging, and fixed the size of the modal window.
- 0.5.0 (31 Mar 2005)