Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Full-Featured ASP.NET Modal Window Server Control

0.00/5 (No votes)
31 Mar 2005 1  
WebModal is an easy to use, ASP.NET server control which encapsulates the complexities of opening and passing information to/from a modal window.

Screen Shot 1

Screen Shot 2

Contents

Introduction

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.

Background

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:

  1. No server control implementation � requires extensive use of JavaScript.
  2. If a modal window attempts to post back to itself, a new window is inadvertently opened.
  3. As a result of #2, a modal window must load an IFrame to contain the actual page.
  4. 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.
  5. The data must be encoded for JavaScript, and for XML to transport correctly.
  6. Solutions commonly using the query string to transport data to the window are limited by query string length constraints.
  7. It is difficult to reuse a non-server control version, which results in multiple copies of the JavaScript with subtle variations.

Samples

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.

BasicSample.aspx

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.

ClientSideSupportSample.aspx

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.

DataGridSample.aspx

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.

DynamicSample.aspx

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.

SubclassSample.aspx

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.

Execution Flow

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.

Interface

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)

Control Design

Design-Time Support

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"), //needed by the page parser

PersistChildren(false) // Needed by the designer

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.

WebModalAnchor Design

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);

  //Move the data returned from the window back into the anchor.

  if (this.Page.IsPostBack) 
  { 
    //Every anchor calls its OnLoad function, so only the anchor that 

    //opened the window should take action here

    if((this.Page.Request.Form["__CausePostBack"] != string.Empty) && 
      (this.Page.Request.Form["__AnchorID"] == this.ClientID))
    { 
      this.OutputData = HttpUtility.UrlDecode(
                     this.Page.Request.Form["__WebModalData"]); 
     
      //Allow the anchor host to take action on the return data

      if (OnWindowClose != null) 
      { 
        //Prevent this event from firing more than once. 

        //Occurs in a dynamic scenario, where the anchor is created twice.

        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(); 
  //This will only be called once for all anchors, to reduce page output size

  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)
    {
      //Add a global reference to the client-side anchor object

      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)
      {
        ...  //Code Removed for clarity


        //render a javascript object model mimicking the 

        //server-side one ClientSideSupport is chosen

        if (anchor.ClientSideSupport)
        {
          //code removed for clarity

          //Renders the client side support object

        }
        else
        {
          //Doesn't seem possible to escape the curly braces, 

          //so they are broken out

          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>");  
    //clear it out, so it doesn't get called again

    this.Context.Items["WebModalAnchor"] = null;

    //write the javascript out to the client

    this.Page.RegisterClientScriptBlock("WebModalAnchors", sb.ToString());
  }

}

WebModalWindowHelper Design

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);
  //Attach to the helper's Page Class.

  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)
    {
      //Page is being loaded into the modalwindow.  Will create an iframe, 

      //set the scrolling property, 

      string Scrolling; 
      if (((!(this.Page.Request.QueryString["__Scrolling"] == null)) && 
        (this.Page.Request.QueryString["__Scrolling"] == "True")))
      { 
        Scrolling = "yes"; 
      }
      else 
      { 
        Scrolling = "no"; 
      } 
      //strip off internal parameters

      string url = this.Page.Request.Url.ToString();
      int internalParameters = url.IndexOf("__LoadIFrame");
      url = url.Substring(0, internalParameters-1);

      //Render the IFrame with the adjusted url

      this.Page.Response.Clear();
      this.Page.Response.Write(iFrameHtml.Replace("ScrollingParam", Scrolling)
        .Replace("ActionParam", url)); 
      this.Page.Response.End();
    }
    else
    {
      //This time, the page is being loaded into the iframe

      //Load the Properties

      this.Properties.Xml = System.Web.HttpUtility.
        UrlDecode(this.Page.Request.Form["__Properties"]);
    }
  }
}

Points of Interest

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.

Conclusion

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)
    • Created.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here