| If you haven't
followed the evolution of my DHTML Dropdown XML/XSL menu in its first
incarnation as an ASCX User Control, you may wish to view that in my
first article
here.
After completing the menu as a User Control, I decided to put in a little
extra time and create a full-fledged ServerControl from it. To summarize,
what I did with the original is take the "guts" of the old MSDN - style
menubar with the drop-down menu lists, doctor it up to my liking, and
port it to an ASP.NET ASCX User Control. The advantage of this is that
you can have a different XML menu contents file for each instance of
the User Control and therefore have a menu that is customized to the
context
of
each individual
page. The XSL, CSS and Javascript files that handle the transform, styling
and menu actions will typically remain the same.
As discussed previously, User Controls are the easiest to author, being
basically a reusable portion of ASP.NET UI code that has been saved with
the the extension ASCX. However, User Controls are somwhat limited in
scope, being only available for a single application at a time, and any
properties we wish to set for our control cannot be done through Property
Sheets. We can also use the @OutputCache directive so that output from
our User Controls doesn't have to be recreated or loaded from the database
on every view. By enabling caching in the User Control and allowing the
host page to remain dynamic, we are accomplishing what is referred to
as fragment caching.
ServerControls, on the other hand, are more complex
and powerful than User Controls, having been precompiled beforehand and
supporting Property Sheets, Design - time HTML, and the Toolbox. With
ServerControls we can handle postback data, events, and the gamut of ASP.NET
strongly - typed classes and actions. In addition, where appropriate,
a ServerControl can be added to the GAC, giving all applications on the
machine access to it. Finally, ServerControls are more performant because
they are strongly - typed compiled classes and don't have to be compiled
along with the code in the ASPX page that they are rendered on.
In the case of this Menu Control, I wanted to provide
for the user to be able to supply their own completely different CSS,
Javascript, XML and XSL files for each separate instance of the control through
easily set Property Sheet entries. In addition, I wanted to provide a default
file name for each of the above so that a "Stock" set of files can be used without
having to set anything; simply drag and drop the control from the Toolbox
onto your WebForm and you "got your menu"! I didn't add Caching, because
the control renders so quickly, but you could certainly add this if desired.
First, let's take a look at the code in the main PAB.Web.MenuCtrl
class:
using System;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.ComponentModel;
using System.Xml;
using System.Xml.Xsl;
using System.Xml.XPath;
using System.IO;
using System.Text;
namespace PAB.Web
{
/// <summary>
/// Generic DHTML Menu Web Custom Control
/// </summary>
[DefaultProperty("Text"),
Designer(typeof(MenuCtrlDesigner)),
ToolboxData("<{0}:MenuCtrl runat=server></{0}:MenuCtrl>")]
public class MenuCtrl : System.Web.UI.WebControls.WebControl
{
private string stylesheet;
private string scriptname;
private string xmlfilename;
private string xslfilename;
[Bindable(true),
Category("Appearance"),
DefaultValue("menu.css")]
public string StylesheetName
{
get
{
return stylesheet;
}
set
{
stylesheet=value;
}
}
[Bindable(true),
Category("Appearance"),
DefaultValue("menu.js")]
public string ScriptName
{
get
{
return scriptname;
}
set
{
scriptname=value;
}
}
[Bindable(true),
Category("Appearance"),
DefaultValue("menu.xml")]
public string XmlFileName
{
get
{
return xmlfilename;
}
set
{
xmlfilename=value;
}
}
[Bindable(true),
Category("Appearance"),
DefaultValue("menu.xsl")]
public string XslFileName
{
get
{
return xslfilename;
}
set
{
xslfilename=value;
}
}
public MenuCtrl()
{
if(XmlFileName==null) XmlFileName="menu.xml";
if(XslFileName==null) XslFileName="menu.xsl";
if(ScriptName==null) ScriptName="menu.js";
if(StylesheetName==null) StylesheetName="menu.css";
}
/// <summary>
/// Render the control
/// </summary>
/// <param name="output"> The HTML writer to write out to </param>
protected override void Render(HtmlTextWriter output)
{
//Test to see if the developer forgot to fill in or
// has accidentally removed the default names ---
if(XmlFileName.Length <5) XmlFileName="menu.xml";
if(XslFileName.Length <5) XslFileName="menu.xsl";
if(ScriptName.Length <4) ScriptName="menu.js";
if(StylesheetName.Length <5) StylesheetName="menu.css";
string XmlSystemFileName = System.Web.HttpContext.Current.Server.MapPath(XmlFileName);
string XslSystemFileName = System.Web.HttpContext.Current.Server.MapPath(XslFileName);
string ScriptSystemFileName = System.Web.HttpContext.Current.Server.MapPath(ScriptName);
string StylesheetSystemFileName = System.Web.HttpContext.Current.Server.MapPath(StylesheetName);
string strContent="<link rel=\"stylesheet\" type=\"text/css\" href=\"" + StylesheetSystemFileName + "\">";
strContent+="<script language=\"javascript\" src=\"" + ScriptSystemFileName +"\"></script> ";
XslTransform xslt = new XslTransform();
xslt.Load(XslSystemFileName);
XPathDocument xpathdocument = new
XPathDocument(XmlSystemFileName);
StringBuilder sb = new StringBuilder();
StringWriter sw = new StringWriter(sb);
xslt.Transform(xpathdocument, null, sw, null);
strContent+= sb.ToString();
output.Write(strContent);
}
}
} |
You can see above that I have created four public properties, one each
for the menu.xml, menu.xsl, menu.css and menu.js respectively, each with
a default name, and that they are bindable and will appear in the Appearance
portion of the property sheet for the control. Note also that I make
reference to my designer class, which in this case simply provides a
nice representation of the control on the design surface of the WebForm, and
that I've specified my Toolbox attributes as "MenuCtrl". Finally, in
the "meat" of the control's guts, the Render method is overriden to grab
each of the files we need, spit out the CSS stylesheet and javascript
references, and finally add the results of our XSL Transform and render
the entire DHTML dropdown menu to the page. You can see that I also ensure
that if the developer has messed with the control to blow away the default
property names or forgotten to put them in, we recreate the default names.
In this manner, as long as you have your four required files with the
default names as above, you will get your default menu and no errors.
You can't see it here, but I also have a Toolbox bitmap (our signature
Eggheadcafe.com "Fried egg" logo). If this has the same namespace
and class name as your control (in this case, "PAB.Web.MenuCtrl.bmp")
and it is added to the project and set as "embedded resource", you'll
get your nice custom Toolbox icon when you add the control to any of
your Toolbox tabs, instead of the crummy "gear" icon provided by default.
At this point, we only need to add the most simple of designer classes:
using System;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Reflection;
using System.Web;
using System.Web.UI.WebControls;
using System.Web.UI;
using System.Web.UI.Design;
namespace PAB.Web
{
public class MenuCtrlDesigner : ControlDesigner
{
private MenuCtrl menuctrl ;
public MenuCtrlDesigner()
{
}
public override string GetDesignTimeHtml()
{
return "<div id=\'div1\' style=\'align: center; COLOR:Blue; valign: middle; background-color:FFCC66; border-width:2px;\' >Menu Table Here</div>"; }
public override void Initialize(IComponent component)
{
menuctrl = (MenuCtrl)component;
base.Initialize(component);
return;
}
}
} |
And that's it! This control doesn't need to be that smart because it's
purpose
is to be a "Generic DHTML XML Transformer" - it simply takes the pieces
you give it - the XML, XSL, Javascript and CSS - and does its thing. The
beauty of this control is, it
really doesn't matter what kind of DHTML menu you want to put together
- as
long as you can come up with a way to encapsulate it in the
four
files (Xml, Xsl, Css and Js),
my
control
will load them and faithfully render it on your favorite ASP.NET Page!
Please refer
to the
first article referenced above for a more complete description of these files.
They can be completely different for every instance of the control; they can
even be set programmatically "on the fly". The downloadable solution
below is in Visual Studio.NET 2003. If you don't have 2003, either back- convert
it using the utility from
a recent
article here, or simply start a new WebControl Library project and bring in
the files.
Download the code that accompanies this article
| Peter Bromberg is a C# MVP, MCP, and .NET consultant who has worked in the banking and financial industry for 20 years. He has architected and developed web - based corporate distributed application solutions since 1995, and focuses exclusively on the .NET Platform. Pete's samples at GotDotNet.com have been downloaded over 41,000 times. You can read Peter's UnBlog Here. --><-- NOTE: Post QUESTIONS on FORUMS! |  |
Do you have a question or comment about this article? Have a programming problem you need to solve? Post it at eggheadcafe.com forums and receive immediate email notification of responses.
|