I think I established why MS web developers have tables tattooed to their foreheads in my previous post on the subject.
Well, here’s my, partial, solution to this. My ultimate goal was to remove as much of the table markup as I can. It doesn’t solve all the problems associated with the bloated markup, but for the most part it cleans it up significantly. You may want to do additional cleanup afterwards, but at least, this will get you on the way.
The following is the basic rendered code for each webpart…
<div id="zoneID" class="webpartzone">
<div id="partID" class="webpart">
<div class="webpart_chrome">
<div class="webpart_title">
<img src="icon.png" /> Title
</div>
<div class="webpart_body">
<div class="webpart_data">
Webpart stuff
</div>
</div>
</div>
</div>
</div>
You may notice that it’s identical to the plain HTML markup from my previous post.
As long as you give a title to your WebPart and give a value to the TitleImageUrl attribute (optional), you should look identical to that.
Now for the WebPartZone…
This is a basic inherited WebPartZone so there is still some table markup. For my project, this was acceptable, but if you can improve it, please do. Post a comment or send me an email with your modifications if you can. This way we can share and solve this mess.
namespace MyApp.UI.Controls
{
public class MyPartZone : WebPartZone
{
protected override void Render(HtmlTextWriter writer)
{
RenderContents(writer);
}
protected override void RenderContents(HtmlTextWriter writer)
{
// Get the current webpartzone
WebPartZone wpz = this;
if (wpz != null)
{
// Print the current zone ID and css class
writer.AddAttribute(HtmlTextWriterAttribute.Id,
wpz.ID);
writer.AddAttribute(HtmlTextWriterAttribute.Class,
"webpartzone");
writer.RenderBeginTag(HtmlTextWriterTag.Div);
// Render the part body
base.RenderBody(writer);
writer.RenderEndTag();
}
else
{
base.RenderContents(writer);
}
}
protected override WebPartChrome CreateWebPartChrome()
{
// Return a new derived class to build the chrome
return new MyPartChrome(this, base.WebPartManager);
}
}
}
Now for the MyPartChrome class, which is basically inherited from WebPartChrome. This will be the part of the code that styles the actual webpart itself. If you want rounded corners etc.. on your webparts, this is the code to modify.
namespace MyApp.UI.Controls
{
public class MyPartChrome : WebPartChrome
{
public MyPartChrome(MyPartZone zone, WebPartManager manager)
: base(zone, manager) { }
// In case you are rewriting URLs, you need to find the base URL
private string BaseUrl
{
get
{
string url = HttpContext.Current.Request.ApplicationPath;
if (url.EndsWith("/"))
return url;
else
return url + "/";
}
}
public override void RenderWebPart(HtmlTextWriter writer, WebPart webPart)
{
// Begin wrapper
writer.AddAttribute(HtmlTextWriterAttribute.Id,
webPart.ID);
writer.AddAttribute(HtmlTextWriterAttribute.Class,
"webpart");
writer.RenderBeginTag(HtmlTextWriterTag.Div);
// Begin outer border or chrome
writer.AddAttribute(HtmlTextWriterAttribute.Class,
"webpart_chrome");
writer.RenderBeginTag(HtmlTextWriterTag.Div);
// Begin title
writer.AddAttribute(HtmlTextWriterAttribute.Class,
"webpart_title");
writer.RenderBeginTag(HtmlTextWriterTag.Div);
//If there's an image url, print that
if (!String.IsNullOrEmpty(webPart.TitleIconImageUrl))
{
writer.AddAttribute(HtmlTextWriterAttribute.Src,
BaseUrl + webPart.TitleIconImageUrl);
writer.RenderBeginTag(HtmlTextWriterTag.Img);
writer.RenderEndTag();
}
//Print the title
if (!String.IsNullOrEmpty(webPart.Title))
{
writer.Write(webPart.Title);
}
else
{
// If your app is localized, use a definition instead
writer.Write("Untitled");
}
// Close title
writer.RenderEndTag();
// Begin body
writer.AddAttribute(HtmlTextWriterAttribute.Class,
"webpart_body");
writer.RenderBeginTag(HtmlTextWriterTag.Div);
//Write the subtitle if there is one
if (!String.IsNullOrEmpty(webPart.Subtitle))
{
writer.AddAttribute(HtmlTextWriterAttribute.Class,
"webpart_subtitle");
writer.RenderBeginTag(HtmlTextWriterTag.Div);
writer.Write(webPart.Subtitle);
writer.RenderEndTag();
}
// Begin data (I'm using this because some users may want to
// have fancy double borders or other custom styles)
writer.AddAttribute(HtmlTextWriterAttribute.Class,
"webpart_data");
writer.RenderBeginTag(HtmlTextWriterTag.Div);
// Print the contents
RenderPartContents(writer, webPart);
// Close data
writer.RenderEndTag();
// Close body
writer.RenderEndTag();
// Close chrome
writer.RenderEndTag();
// Close wrapper
writer.RenderEndTag();
}
}
}
You may see that part of the code is cut off toward the ends of some lines, but if you highlight the whole thing, you should still be able to copy the whole code. Don’t worry, the cutoff is in the stylesheet for this blog theme, not the HTML itself.
How to use it
Create two class files in your App_Code folder and drop these in. Be sure to include the following headers at a minimum on top of each class :
using System;
using System.Web;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
Now you have two choices on how to integrate it into your pages…
Either add the following to your web.config file :
<pages>
<controls>
<add tagPrefix="MyApp" namespace="MyApp.UI.Controls" assembly="__code"/>
</controls>
</pages>
And be sure to change “MyApp.UI.Controls” to reflect what program structure you have (be sure to change it in the code files as well).
This will allow you to call these classes on all pages.
Or
You can add the following to each page :
<%@ Register TagPrefix="MyApp" Namespace="MyApp.UI.Controls" Assembly="__code" %>
And then call the webpartzone on your aspx page :
<MyApp:MyPartZone id="MyWebPartZone1" runat="server">
<ZoneTemplate>
<%-- Insert web part here --%>
</ZoneTemplate>
</MyApp:MyPartZone>
—————-
Hope you will find this helpful.
I will post code at a future date showing how to use jQuery for drag n’ drop functionality and personalization options instead of the native ASP.NET code.
The code I used on my app runs just fine (with the described limitations). I had to modify it a bit before posting here.
Please let me know if there are any errors.
Update 07/02/08
Finally! No more tables!
The fix to the above code was so simple, I hit myself on the head.
Change the following line in the MyPartZone class :
base.RenderBody(writer);
To this:
this.RenderBody(writer);
Anyone else feel relieved? ;)