Introduction
When writing accessible websites, we are told that frames are wrong. But with web-applications for the local intranet of your company or your customers, frames can be very useful. But why is it that we ASP.NET developers rarely use frames for our web-applications? Not because we don't like frames, but because ASP.NET limits us to the page we are currently building, disallowing us to reach out of our 'frame' and altering other frames, without refreshing them constantly.
This article is part one of a trilogy, in which I present the AnywherePlaceHolder
. A control that functions like a normal PlaceHolder
(with children), but that enables you to render the child controls in any frame of your web-application. The control is built in the new ASP.NET 2.0 and uses generics and is deeply coupled with the ASP.NET 2.0 client side JavaScript functions.
In this first article I'll present the BaseAnywherePlaceHolder
and the SimpleAnywherePlaceHolder
. The SimpleAnywherePlaceHolder
is only used to explain the basics of the real AnywherePlaceHolder
that will be explained in part two. The SimpleAnywherePlaceHolder
can write normal controls like Label
s, Table
s, GridView
s to any desired frame.
In part two I will explain the working of the AnywherePlaceHolder
that enables you to write basically any content, including submit buttons, to any other frame, with respect to their functionality. Even JavaScript code will still work.
In part three I will explain the working of the AnywhereValidationSummaryPlaceHolder
. This is a BaseAnywherePlaceHolder
with the functionality of a normal ValidationSummary
. This special PlaceHolder
allows you to write your custom messages and validation error messages into the frame of choice.
Using the code
In this part we'll be examining the BaseAnywherePlaceHolder
and SimpleAnywherePlaceHolder
classes. Below is the class diagram.
The BaseAnywherePlaceHolder
is an abstract class that inherits from System.Web.UI.WebControls.Panel
rather than System.Web.UI.WebControls.PlaceHolder
, what its name does expect. A PlaceHolder
does not render itself, but only its children and we need the control to render itself. The name PlaceHolder
however, tells more intuitively what the control does. Here are the basic methods and properties of the control:
DestinationControlId
Gets or sets the ID of the element in what the child controls should be inserted. When this property is specified the child elements will be inserted into the parent frame through JavaScript innerHTML
function. Otherwise the child controls are normally rendered.
DestinationFrame
Gets or sets the name of the frame in what the child controls should be inserted.
SourceControl
Gets or sets the control in what the child controls should be inserted, when the DestinationControlId
could not be found during execution on the client. The default is the current object (so the this
pointer is returned).
SourceFrame
Gets or sets the client side reference to the frame in what the current aspx page is that renders this control. Needed when child controls need a reference back, i.e. when a child control uses JavaScript or submits to the form.
JavaScriptCode
The JavaScript code that should be registered on the client. This code inserts the child controls into another frame. A descendant could override this property to implement a different JavaScript function. The ScriptName
should then also be overridden to return a different name.
ScriptName
The JavaScript function that should be rendered on the client. The name of the script is defined by the JavaScriptCode
property.
RenderMethod
Enumeration with what the rendering method can be set. Three options:
CreateScriptCall
Creates and returns some JavaScript code that calls the JavaScript function defined in JavaScriptCode
and ScriptName
, using a HTML string as input and all the other properties listed above.
PreRenderCheck
Checks if all properties are set correctly and throws an exception otherwise. This method should be called from within the Render
method, when it's overridden by a descendant.
OnPreRender
Overridden from Control.OnPreRender
. This method registers the JavaScript function to the page, using the Page.ClientScript.RegisterClientScriptBlock
method.
The SimpleAnywherePlaceHolder
control inherits from BaseAnywherePlaceHolder
and only implements the Render
method. Most of the work has been done by its base class. The Render
method looks like this:
protected override void Render(System.Web.UI.HtmlTextWriter writer)
{
this.PreRenderCheck();
base.Render(writer);
if (this.RenderMethod == AnywhereRenderMethodType.RenderNormal) return;
HtmlTextWriter buffer = new HtmlTextWriter(
new StringWriter(CultureInfo.InvariantCulture));
base.RenderChildren(buffer);
string renderedChildren = buffer.InnerWriter.ToString();
this.Page.ClientScript.RegisterStartupScript(
this.GetType(),
this.DestinationControlId,
this.CreateScriptCall(renderedChildren),
true
);
}
The Render
method just calls the base.Render()
method, so that the control is rendered normally. That seems strange, when you know that the AnywherePlaceHolder
should inject the HTML code into another frame. It is the JavaScript function however, that hides or shows the HTML code using the stylesheet 'display
' property. Still it doesn't really seem useful to write that HTML code, when it will be hidden. Sometimes however, that HTML code is really needed. This will be shown in part 2 of the article, when explaining the complete version of the AnywherePlaceHolder
.
After calling the base.Render()
method, the method will return when the RenderMode
is set to RenderNormal
. Otherwise a HtmlTextWriter
object is created to buffer the output from the RenderChildren
method. This code will be used to generate some JavaScript to inject HTML into the destination element. That code could look like this:
AnywherePlaceHolderWriter(
'Title', 'TitlePlaceHolder', parent, true,
'<img src="myimage.gif" src="" />Function page dynamic title'
);
As you can see the call to the AnywherePlaceHolderWriter
function holds some raw HTML code, packed in a JavaScript string. This HTML code has been rendered by the base.RenderChildren(buffer);
line. The code for the AnywherePlaceHolderWriter
is given below:
function AnywherePlaceHolderWriter(destid, sourceid, destframe,
hideSource, destHtml)
{
var dest = document.all ? destframe.document.all[destid] :
destframe.document.getElementById(destid);
var source = document.all ? document.all[sourceid] :
document.getElementById(sourceid);
if (dest) {
dest.innerHTML = destHtml;
if (source && hideSource)
source.style.display = 'none';
}
else {
if (!source) alert('Can not find ' + sourceid + '.');
}
}
The AnywherePlaceHolderWriter
code is fairly straightforward. The destid
and sourceid
parameters are strings that represent the ID of an HTML element and the destframe
is a reference to another frame (so not a string). The destHtml
contains the HTML code that should be injected into the destination element. That is done with this code: dest.innerHTML = destHtml;
.
Using the SimpleAnywherePlaceHolder
Using the SimpleAnywherePlaceHolder
is rather simple, but you must take care of both frames during design. Look at the following examples:
Destination.aspx
<html>
<body>
<div id="DestElement" />
<iframe name="sourceframe" src="source.aspx" />
</body>
</html>
Source.aspx
<%@ Register Namespace="DeKale.WebControls" TagPrefix="kale" %>
<html>
<body>
<form runat="server">
<kale:SimpleAnywherePlaceHolder runat="server"
ID="TitlePlaceHolder" DestinationControlId="DestElement"
DestinationFrame="parent" SourceFrame="FunctionFrame"
RenderMethod="RenderInDestination">
<asp:image runat="server" src="images/myimage.gif" />
Function page dynamic title
</kale:AnywherePlaceHolder>
</form>
</body>
</html>
As you can see, the HTML of the source.aspx contains a SimpleAnywherePlaceHolder
control with the properties DestinationControlId
and DestinationFrame
. In this case, the destination.aspx can be reached using the browsers 'parent
' property. But it is also possible that the destination.aspx is not a parent but a brother frame within a frameset. Then the DestinationFrame
property should be something like: 'parent.destination
'. The frameset must then use the name
property to define the destination. In other words, we can always reach that other frame.
To wrap things up
As you saw, using fairly simply HTML injection through the innerHTML
property, we are able to render a control into another frame. "But does this always work well?", you may ask: Well, no, not exactly. When you use the SimpleAnywherePlaceHolder
to write buttons, links or something that contains JavaScript, this method will break. Is there a solution for this? Yes, behold the AnywherePlaceHolder
in The AnywherePlaceHolder. Part 2: The Advanced AnywherePlaceHolder.!
History
- 22 August, 2005 - Version 1.0. Initial version.