Introduction
This article describes an encapsulation of an existing JavaScript popup library using ASP.NET custom controls with the script being provided to the page directly from an assembly using a WebResource (.axd) stream.
Some background on the script library will be given, along with how to use the WebResource technique to eliminate having to manually deploy the script on your website or to dynamically modify the web.config and site structure at design time to accomplish the same end.
Lastly some examples of extending standard ASP.NET controls such as the button, link button and image will be demonstrated, illustrating how easy it is to use the scripts, in conjunction with a script manager control which is placed on the page at design time.
The demonstration code uses VS 2005 SP1 to take advantage of Web Applications, and the sample code is written with C#, so make sure you have both.
Background
While working on a client project, I had decided to provide extra information about a grid item using an icon, with the text embedded as a tooltip. This worked fine, however ToolTips disappear quickly. The client wanted time to read the text, particularly as there might be a few paragraphs. There are quite a few alternatives to show extra information including:
- JavaScript popup window using the
window.open(...)
method - Use a select command on
Gridview
, requiring a postback - Have an information panel next to the grid, requiring a command handler (already present)
- Div popup like some of the clever AJAX popups, or those that show ads on many commerical web-sites
I have tried all of the methods in the past with success, but settled on the div-popup because it eliminated the postback and could show the information right away. The trouble was writing the JavaScript to do it and ending up with a polished result. After searching and finding some pretty cool techniques, the Overlib
JavaScript library by Erik Bosrup et al. seemed to offer a lot of flexibility, different types of windows and was a project still being managed.
The Overlib
library can be used without custom controls by linking the source in the HEAD
, adding a reusable DIV
in the body and adding event handlers like ONMOUSEOVER
, ONMOUSEOUT
to call the script. And there is nothing wrong with that technique. But as a professional developer, I have an aversion to JavaScript and to pages where manual calls to it are made. Besides, having learnt to write custom controls the hard way, I know how much easier it is to drag-an-drop controls on the page to automatically to do it. In short, I want to forget about the JavaScript itself and get back to writing code the .NET way.
Overlib
offers a lot of options to control the windows and there has been a solution posted as a pluggable C# control based library in C# 1.1 which works in 2.0 as-is. It is easy to see that a lot of hard work has gone into it as it can be configured to accept revisions to the JavaScript, and because popups can be configured and added dynamically.
This is all fine when your page can be built that way, but I wanted to use the Overlib
to extend the already existing controls that participate in binding like the asp:image
, asp:linkbutton
, asp:button
and also my own control suite that I make available and extend with each new client project. And I wanted to simplify the controls I extended so that I can just drag-and-drop from the toolbox controls that were already preconfigured to use Overlib
and to never configure them again.
The article is divided into three parts:
- Part 1: Demonstration Files for
Overlib
- Part 2: Encapsulation with WebResource.axd, and
- Part 3: A Custom
Overlib
Library.
Part 1 demonstrates the use of Overlib
without custom controls, and relies on manual connection of attributes, addition of a script reference to every page that uses the library and a script folder. This will be useful for comparison with the later parts.
Part 2 shows how to encapsulate the Overlib
JavaScript library in an assembly, how this is used to eliminate the need for a script folder, and how to extend some ASP.NET controls to use the Overlib
library automatically.
Finally, part 3 shows how to implement a simple library of controls so that they can be drag-and-dropped directly to the page without having to remember all of the Overlib
settings, with no code-behind required.
Part 1: Demonstration Files for Overlib
In order to appreciate what the code does, I need to show you what the code later in this article is going to replace. Begin by referring to the OverlibDemo
project attached. There are two key files for this part
- DemoWithScriptFolder
- OverlibDemo.Web
DemoWithScriptFolder is a Web application that has a single default page that shows how to use Overlib
as-is. The scripts are in a folder called scripts/overlib421 and are in their own folder to keep version 4.21 separate from future releases. No changes to the scripts have been made and they are as I extracted them.
If you run the demo after setting DemoWithScriptFolder as the StartUp project, you will see that the Overlib
popups appear over the first row of HTML controls as well as the second row of ASP.NET controls. The HTML controls are configured exactly as in the reference code for Overlib
but I have attached to the ASP.NET controls using helper methods from the class library OverlibDemo.Web.
The page works because of the script in the head and the div directly after the body tag in the following html snippet which you must manually add to all pages where the Overlib
library is to be used.
<head runat="server">
<title>Untitled Page</title>
<script type="text/javascript"
src="http://www.mydomain.com/scripts/overlib421/overlib.js">
<!-- overLIB (c) Erik Bosrup -->
</script>
</head>
<body>
<div id="overDiv" style="position:absolute;
visibility:hidden; z-index:1000;"></div>
<form id="form2" runat="server">
<div>
...
using System;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
using OverlibDemo.Web;
namespace DemoWithScriptFolder
{
public partial class Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
AttachControlAttributes();
}
}
private void AttachControlAttributes()
{
TextBox1.Attributes.Add("onmouseover",
"return overlib('This is an ordinary popup.');");
TextBox1.Attributes.Add("onmouseout", "return nd();");
Helper.AttachOverlibAttributes(LinkButton1, AttributeAttach.OnMouseOver,
"'This is what we call a sticky, since I stick around
(it goes away if you move the mouse OVER and then OFF
the overLIB popup--or mouseover another overLIB).',
STICKY, MOUSEOFF");
Helper.AttachOverlibAttributes(Label1, AttributeAttach.OnClick,
"'This is a sticky with a caption.
And it is centered under the mouse!', STICKY, CAPTION,
'Sticky!', CENTER");
}
}
}
Because the attributes need only be attached once, a call is made in the first page_load
. The onmouseover
and onmouseout
attributes are added for the TextBox
control in a manual way to show what the Helper class does. It would be possible this way to have helper class methods to produce predefined popup types but I have left that for the interested reader. There will be more on this in the next demonstration.
Part 2: Encapsulation with WebResource.axd
If you are happy to use the script folder method, then this section need not apply to you. However, I think this is a valuable method of accessing scripts and other resources in general.
The aim is to produce the following HTML code snippet, with each script representing a cached copy of each of the Overlib
scripts. If you don't like so many scripts for just one implementation, then consider concatenating the scripts into one script.
<script src="http://www.mydomain.com/WebResource.axd?d=
RA_RSv4e2a3_RCfGm3LhncSOtTvZxzDlgRvJ9DhK21R2FRY1x3xUfiRbjPuNJs0Tr6AoMVhJBSZnxk
NsyizEStKD-NGRQVn8MVZPOt-U4K-HtAl2XxxXO591bJekyiuI0&t=633118836619375000"
type="text/javascript"></script>
<script src="http://www.mydomain.com/WebResource.axd?d=
RA_RSv4e2a3_RCfGm3LhncSOtTvZxzDlgRvJ9DhK21R2FRY1x3xUfiRbjPuNJs0Tr6AoMVhJBSZnxk
NsyizEStKD-NGRQVn8MVZPOt-U4K97UyrpPXHxsyoT-BPfJ8hD0&t=633118836619375000"
type="text/javascript"></script>
<script src="http://www.mydomain.com/WebResource.axd?d=
RA_RSv4e2a3_RCfGm3LhncSOtTvZxzDlgRvJ9DhK21R2FRY1x3xUfiRbjPuNJs0Tr6AoMVhJBSZnxk
NsyizEStKD-NGRQVn8MVZPOt-U4K_5EclWnboZocXmXtsHPZzEXGg9-deQZ_JuBvTyRc6ubw2&t=633118836619375000"
type="text/javascript"></script>
<script src="http://www.mydomain.com/WebResource.axd?d=
RA_RSv4e2a3_RCfGm3LhncSOtTvZxzDlgRvJ9DhK21R2FRY1x3xUfiRbjPuNJs0Tr6AoMVhJBSZnxk
NsyizEStKD-NGRQVn8MVZPOt-U4K9IAQrv9eBK5iELJ9z_XugH0&t=633118836619375000"
type="text/javascript"></script>
<script src="http://www.mydomain.com/WebResource.axd?d=
RA_RSv4e2a3_RCfGm3LhncSOtTvZxzDlgRvJ9DhK21R2FRY1x3xUfiRbjPuNJs0Tr6AoMVhJBSZnxk
NsyizEStKD-NGRQVn8MVZPOt-U4K9lJFNkVYCC9HejPdW3brQN0&t=633118836619375000"
type="text/javascript"></script>
<script src="http://www.mydomain.com/WebResource.axd?d=
RA_RSv4e2a3_RCfGm3LhncSOtTvZxzDlgRvJ9DhK21R2FRY1x3xUfiRbjPuNJs0Tr6AoMVhJBSZnxk
NsyizEStKD-NGRQVn8MVZPOt-U4K9FDnGy2T1Ue2LUFVEy0A4x0&t=633118836619375000"
type="text/javascript"></script>
<script src="http://www.mydomain.com/WebResource.axd?d=
RA_RSv4e2a3_RCfGm3LhncSOtTvZxzDlgRvJ9DhK21R2FRY1x3xUfiRbjPuNJs0Tr6AoMVhJBSZnxk
NsyizEStKD-NGRQVn8MVZPOt-U4K-v-UsQI3RAmi58dICS1iLs0&t=633118836619375000"
type="text/javascript"></script>
<script src="http://www.mydomain.com/WebResource.axd?d=
RA_RSv4e2a3_RCfGm3LhncSOtTvZxzDlgRvJ9DhK21R2FRY1x3xUfiRbjPuNJs0Tr6AoMVhJBSZnxk
NsyizEStKD-NGRQVn8MVZPOt-U4K8tmjuGT_2t2VScXJFR9FJVXbaFiN5ojD_lJWbSwEpChw2&t=633118836619375000"
type="text/javascript"></script>
<script src="http://www.mydomain.com/WebResource.axd?d=
RA_RSv4e2a3_RCfGm3LhncSOtTvZxzDlgRvJ9DhK21R2FRY1x3xUfiRbjPuNJs0Tr6AoMVhJBSZnxk
NsyizEStKD-NGRQVn8MVZPOt-U4K8YbNQeEYKqJM9qqdnsXXFs0&t=633118836619375000"
type="text/javascript"></script>
<script src="http://www.mydomain.com/WebResource.axd?d=
RA_RSv4e2a3_RCfGm3LhncSOtTvZxzDlgRvJ9DhK21R2FRY1x3xUfiRbjPuNJs0Tr6AoMVhJBSZnxk
NsyizEStKD-NGRQVn8MVZPOt-U4K-B4jTGkO2pU5MA9ug0_qfX0&t=633118836619375000"
type="text/javascript"></script>
<script src="http://www.mydomain.com/WebResource.axd?d=
RA_RSv4e2a3_RCfGm3LhncSOtTvZxzDlgRvJ9DhK21R2FRY1x3xUfiRbjPuNJs0Tr6AoMVhJBSZnxk
NsyizEStKD-NGRQVn8MVZPOt-U4K8Q92ExUFMgFmDdkB-bhsRa0&t=633118836619375000"
type="text/javascript"></script>
Each WebResource.axd is simply a keyed reference to a unique resource, in this case a script file. For more information on WebResource files refer to Praveen Yerneni's article on MSDN.
To begin the demonstration, there are two project files in the OverlibDemo
that you should refer to :
- DemoWithScriptEmbedding
- OverlibDemo.Web.UI.WebControls
The first is the Web Application that has the demonstration pages for Part 2 and Part 3, and the second file is a class library that cleanly encapsulates the Overlib
scripts. Set the default StartUp project to DemoWithScriptEmbedding
and take a look at the Part 2 link to make sure it all works.
The technique described here is to embed the scripts as part of the class library assembly, and write custom controls that automate the registration of script files. The first step is to add the following line to the AssemblyInfo.cs file
[assembly: TagPrefix("OverlibDemo.Web.UI.WebControls", "OL")]
You can, of course, change the prefix to whatever you want, and it just makes it easier to identify your controls in a complex page. Note that if you make a typo in the assembly name, you will get cc1, cc2 etc. as the default tag prefix when you add the controls to a page.
This is what the assembly reference looks at the top of an aspx page (I chose the WebControls namespace to follow Microsoft's conventions):
<%@ Register Assembly="OverlibDemo.Web.UI.WebControls"
Namespace="OverlibDemo.Web.UI.WebControls" TagPrefix="OL" %>
Referring to the class library, there is a folder Overlib421 with all of the Overlib
scripts. Because these are to be embedded it is VERY IMPORTANT to alter the Build Action for each script to Embedded Resource. Failure to do this will result in the script links appearing on the page, but references to the JavaScript causing errors. The build action is set by single-clicking each JavaScript, and editing the Build Action property which is the first property in the Advanced section.
To make the WebResource work and be available on the page, I drop a single custom control on the page called OverLibPageControl
(to be discussed shortly) which performs all of the script registration. However a number of support classes are required to make this control work. The first class is a ScriptManager
class which registers the scripts as follows.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
[assembly: WebResource("OverlibDemo.Web.UI.WebControls.OverLib421.overlib.js",
"application/x-javascript", PerformSubstitution = false)]
[assembly: WebResource("OverlibDemo.Web.UI.WebControls.OverLib421.overlib_anchor.js",
"application/x-javascript", PerformSubstitution = false)]
[assembly: WebResource("OverlibDemo.Web.UI.WebControls.OverLib421.overlib_crossframe.js",
"application/x-javascript", PerformSubstitution = false)]
[assembly: WebResource("OverlibDemo.Web.UI.WebControls.OverLib421.overlib_cssstyle.js",
"application/x-javascript", PerformSubstitution = false)]
[assembly: WebResource("OverlibDemo.Web.UI.WebControls.OverLib421.overlib_debug.js",
"application/x-javascript", PerformSubstitution = false)]
[assembly: WebResource("OverlibDemo.Web.UI.WebControls.OverLib421.overlib_exclusive.js",
"application/x-javascript", PerformSubstitution = false)]
[assembly: WebResource("OverlibDemo.Web.UI.WebControls.OverLib421.overlib_followscroll.js",
"application/x-javascript", PerformSubstitution = false)]
[assembly: WebResource("OverlibDemo.Web.UI.WebControls.OverLib421.overlib_hideform.js",
"application/x-javascript", PerformSubstitution = false)]
[assembly: WebResource("OverlibDemo.Web.UI.WebControls.OverLib421.overlib_setonoff.js",
"application/x-javascript", PerformSubstitution = false)]
[assembly: WebResource("OverlibDemo.Web.UI.WebControls.OverLib421.overlib_shadow.js",
"application/x-javascript", PerformSubstitution = false)]
namespace OverlibDemo.Web.UI.WebControls.OverLib421
{
public class ScriptManager
{
private static string scriptNamespace = "OverlibDemo.Web.UI.WebControls.OverLib421";
private static string[] scriptNames = new string[] { "overlib",
"overlib_anchor",
"overlib_centerpopup",
"overlib_crossframe",
"overlib_cssstyle",
"overlib_debug",
"overlib_exclusive",
"overlib_followscroll",
"overlib_hideform",
"overlib_setononff",
"overlib_shadow" };
private static string scriptFmtFullName = "{0}.{1}.js";
public static void RenderOverlibScripts(Page page)
{
ClientScriptManager cs = page.ClientScript;
Type rstype = typeof(ScriptManager);
foreach (string scriptName in scriptNames)
{
string script = string.Format(scriptFmtFullName,
scriptNamespace, scriptName);
cs.RegisterClientScriptResource(rstype, script);
}
}
}
}
The first block of assembly meta-data marks the scripts as is available from the assembly for streaming with firstly their full namespace name, then their mime-type. It is very important to get the namespace correct as I found this quite tricky the first time round. Importantly, note that the OverLib421 folder is included as part of the namespace.
Another noteworthy part of the class is the RenderOverlibScripts
method which registers the scripts on the page by iterating over the array of script names. The ScriptManager
will be called from other controls which rely on the script library, which is why the Script Manager itself is not a custom control.
The next required class is ScriptPageControl
which will be a base class to simplify the design of the final page control, OverLibPageControl
.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace OverlibDemo.Web.UI.WebControls.OverLib421
{
[ToolboxData("<{0}:ScriptPageControl runat="server">")]
public class ScriptPageControl : WebControl, IRegisterOverLib
{
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
RegisterScripts(this.Page);
}
#region IRegisterOverLib Members
public void RegisterScripts(Page page)
{
ScriptManager.RenderOverlibScripts(page);
}
#endregion
}
}
ScriptPageControl
is a custom control, which simply calls the script manager to register the Overlib
scripts, once it has completed the OnPreRender
method. The control that you as a developer might drop on the page is OverLibPageControl
, as below:
using System.Web.UI;
namespace OverlibDemo.Web.UI.WebControls
{
[ToolboxData("<{0}:OverLibPageControl runat="server">")]
public class OverLibPageControl : OverLib421.ScriptPageControl
{
protected override void RenderContents(HtmlTextWriter writer)
{
if (DesignMode)
{
writer.Write("" + this.ClientID + "");
}
}
}
}
OverLibPageControl
is in a slightly different namespace, and has RenderContents
overridden to make the control visible on the Design Surface in VS2005.
There are two ASP.NET controls that have been extended to use the scripts, these being ImageExt
and LinkButtonExt
. Refer to the class files for more information. As far as their presence on the aspx page is concerned, all that is required to make a popup appear when mousing over is to add text to the PopupText
attribute.
<OL:ImageExt ID="ImageExt1" runat="server"
PopupText="'This is a sticky with a caption.
And it is centered under the mouse!', STICKY, CAPTION, 'Sticky!', CENTER" />
<OL:LinkButtonExt ID="LinkButtonExt1" runat="server" Text="here"
PopupText="'This is what we call a sticky, since I stick around
(it goes away if you move the mouse OVER and then OFF the overLIB popup--
or mouseover another overLIB).', STICKY, MOUSEOFF"/>
Both of these ASP.NET controls are extended by making use of AddAttributesToRender
and preventing empty popups appearing. The advantage of this encapsulation is that they can be drag-and-dropped straight onto the page which will keep your code clean.
protected override void AddAttributesToRender(HtmlTextWriter writer)
{
if (PopupText.Trim() != string.Empty)
{
writer.AddAttribute("onmouseover", "return overlib(" + PopupText + ");");
writer.AddAttribute("onmouseout", "return nd();");
}
base.AddAttributesToRender(writer);
}
Part 3: A Custom Overlib Library
This section builds on the classes in Part 2 by building three purpose-built popup controls that you can alter and extend as required. Open the Part 3 link from the previous demonstration, and then take a look at the OverlibCustomControls.aspx page. There are no code-behind methods on the page, and three independent custom controls on the design surface. There is no OverlibPageControl
required as these controls register the scripts themselves. If you also require access to the JavaScripts, then they are automatically available as long as at least one of the three controls is present. Deliberately adding an OverlibPageControl
won't cause any problems, it just isn't necessary. Also note that the special overlib
div isn't present on the page. If you require more control of the Z-Index of the div, then embed it as part of the Script Manager, otherwise overlib
adds it automatically. The three controls created for this demo are:
OrdinaryPopup
StickyCaption
StickyPopup
You will see that they are very similar and rely on a single base class called OverLib421Popup
, parts of which are shown below. The class has two key properties: Text
and PopupText
. The Text
is the link text, while PopupText
is the text that is shown in the popup. Because of the encapsulation and re-use, each of the three controls adds the overlib
formatting arguments so that unlike in the previous demonstration, the PopupText
doesn't contain any formatting information.
protected virtual string OverlibParameterString()
{
throw new Exception(ExOverlibParameterStringMustBeImplementedByInheritingControl);
}
protected virtual string OverlibActivationAttribute()
{
return StrOnMouseOver;
}
protected override void RenderContents(HtmlTextWriter output)
{
output.WriteBeginTag("a");
output.WriteAttribute("href", "javascript:void(0);");
if (PopupText.Trim() != string.Empty)
{
output.WriteAttribute(OverlibActivationAttribute(),
string.Format(FmtOverlibParam, OverlibParameterString()));
output.WriteAttribute("onmouseout", StrOverlibNd);
}
output.Write('>');
output.Write(Text);
output.WriteEndTag("a");
}
The important methods are shown above. OverlibParameterString
and OverlibActivationAttribute
are both virtual methods returning strings. OverlibParameterString
will be called by an inheriting class and will return a formatted JavaScript overlib
argument string, while OverlibActivationAttribute
is typically either 'onclick'
or 'onmouseover'
.
For an ordinary popup, the control OrdinaryPopup
inherits the popup control, and since there are no formatting arguments the PopupText
is returned as the only argument for overlib()
.
using System.Web.UI;
namespace OverlibDemo.Web.UI.WebControls
{
[ToolboxData("<{0}:OrdinaryPopup runat="server">")]
public class OrdinaryPopup : OverLib421.OverLib421Popup
{
const string FmtOrdinaryParam = "'{0}'";
protected override string OverlibParameterString()
{
return string.Format(FmtOrdinaryParam, PopupText);
}
}
}
The sticky caption is encapsulated with StickyCaption
as below:
using System.ComponentModel;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace OverlibDemo.Web.UI.WebControls
{
[ToolboxData("<{0}:StickyCaption runat="server">")]
public class StickyCaption : OverLib421.OverLib421Popup
{
const string FmtStickyCaptionParam = "'{0}', STICKY, CAPTION,
'Sticky!', CENTER";
protected override string OverlibParameterString()
{
return string.Format(FmtStickyCaptionParam, PopupText);
}
protected override string OverlibActivationAttribute()
{
return StrOnClick;
}
}
}
The Caption is activated by a mouse click so the activation attribute is overridden. There is a change to the formatting also. The implementation of StickyPopup
is similar, so please refer to the class for implementation details.
Summary
There is a fair amount of detail here, though I hope that with referring to the demonstration code and the text it will make sense. I believe the overlib
scripts are a great resource and are simple to implement and encapsulate. With some effort, the WebResource.axd approach, though new for me, will ease deployment of your own class libraries.
Note that whether you stay with script folders or use WebResource files, the beginnings of your own class library can be made to work with either.