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>
—————-
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? ;)
Hi there!!!.
just try your code and seems i still get a table wrapping the webparts. something at the
// Render the part body
base.RenderBody(writer);
…. i think.
output:
Plain Paragraph
Please edit this webpart.
how did you get it to work??, actually i checked your demo, but i just seems plain html… is this just a theoretical approach???
Problem solved!!!!:
protected override void RenderBody(HtmlTextWriter writer)
{
WebPartCollection wpColl = new WebPartCollection(this.WebParts);
WebPartChrome chrome = CreateWebPartChrome();
foreach (WebPart wp in wpColl)
{
chrome.RenderWebPart(writer, wp);
}
}
Hi David, thanks for the fix.
Yes, this was just theoretical, but I was hoping someone else could improve it.
Thanks!
David, I just tried your modification and it seems the table is still there.
Yeah, i know.
at the end i fixed it!!, let me post some code.
namespace corecms.framework
{
public class MyChrome : WebPartChrome
{
WebPartManager _wpm;
PortalWebPartZone _pzn;
public MyChrome(PortalWebPartZone myZone, WebPartManager WPM)
: base(myZone, WPM)
{
_wpm = WPM;
_pzn = myZone;
}
public override void RenderWebPart(HtmlTextWriter writer, WebPart webPart)
{
bool showChrome = false;
if (HttpContext.Current.User.Identity.IsAuthenticated && _wpm.DisplayMode == PortalWebPartManager.CMSDesignDisplayMode)
showChrome = true;
// Begin wrapper
writer.AddAttribute(HtmlTextWriterAttribute.Id,webPart.ID);
writer.AddAttribute(HtmlTextWriterAttribute.Class,string.Format(“part {0}”,webPart.CssClass));
writer.RenderBeginTag(HtmlTextWriterTag.Div);
if (showChrome)
{
// Begin ToolBar
writer.AddAttribute(HtmlTextWriterAttribute.Class, “toolbar”);
writer.RenderBeginTag(HtmlTextWriterTag.Div);
// Begin TitleBar, which also allows to drag and drop
writer.AddAttribute(HtmlTextWriterAttribute.Class, “titlebar”);
writer.RenderBeginTag(HtmlTextWriterTag.Div);
//Start verbs
writer.AddAttribute(HtmlTextWriterAttribute.Class, “actions”);
writer.RenderBeginTag(HtmlTextWriterTag.Div);
RenderVerbs(webPart, writer);
writer.RenderEndTag();
//End verbs
//If there’s an image url, print that
if (!String.IsNullOrEmpty(webPart.TitleIconImageUrl))
{
writer.AddAttribute(HtmlTextWriterAttribute.Class, “wpicon”);
writer.AddAttribute(HtmlTextWriterAttribute.Alt, webPart.Description);
writer.AddAttribute(HtmlTextWriterAttribute.Src, webPart.TitleIconImageUrl);
writer.RenderBeginTag(HtmlTextWriterTag.Img);
writer.RenderEndTag();//img
}
//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”);
}
writer.RenderEndTag();//Ends TitleBar
writer.RenderEndTag();//Ends Toolbar
}
// Begin body
writer.AddAttribute(HtmlTextWriterAttribute.Class,”outer”);
writer.RenderBeginTag(HtmlTextWriterTag.Div);
writer.AddAttribute(HtmlTextWriterAttribute.Class,”inner”);
writer.RenderBeginTag(HtmlTextWriterTag.Div);
// Print the contents
webPart.RenderControl(writer);
// Close Inner
writer.RenderEndTag();
// Close outer
writer.RenderEndTag();
// Close part
writer.RenderEndTag();
}
private void RenderVerbs(WebPart webPart,HtmlTextWriter writer)
{
typeof(WebPartChrome).GetMethod(
“RenderVerbsInTitleBar”,
System.Reflection.BindingFlags.NonPublic |
System.Reflection.BindingFlags.Instance).Invoke(this, new object[] { writer, webPart, 1 });
}
}
public class PortalWebPartZone : WebPartZone
{
protected override bool HasHeader
{
get { return false; }
}
protected override WebPartChrome CreateWebPartChrome()
{
return new MyChrome(this, base.WebPartManager);
}
protected override void OnCreateVerbs(WebPartVerbsEventArgs e)
{
base.OnCreateVerbs(e);
this.ExportVerb.ImageUrl = “/images/chrome/icon_export.gif”;
this.EditVerb.ImageUrl = “/images/chrome/icon_edit.gif”;
this.MinimizeVerb.ImageUrl = “/images/chrome/icon_minimise.gif”;
this.RestoreVerb.ImageUrl = “/images/chrome/icon_restore.gif”;
this.CloseVerb.ImageUrl = “/images/chrome/icon_close.gif”;
this.DeleteVerb.ImageUrl = “/images/chrome/icon_delete.gif”;
this.DeleteVerb.Visible = false;
this.EditVerb.Visible = false;
// this.CloseVerb.Visible = false;
Collection verbs = new Collection();
HttpContext ctx = HttpContext.Current;
if (ctx.Request.IsAuthenticated)
{
WebPartVerb editVerb = new WebPartVerb(
“editVerb”,
new WebPartEventHandler(HandleEditClick)
);
verbs.Add(editVerb);
WebPartVerb deleteVerb = new WebPartVerb(
“deleteVerb”,
new WebPartEventHandler(HandleDeleteClick)
);
verbs.Add(deleteVerb);
if (this.WebPartManager.Personalization.Scope == PersonalizationScope.Shared)
{
editVerb.Text = “Edit2 Web Part”;
deleteVerb.Text = “Delete2 Web Part”;
editVerb.ImageUrl = “/images/chrome/icon_edit.gif”;
deleteVerb.ImageUrl = “/images/chrome/icon_delete.gif”;
}
else
{
editVerb.Text = “Edit Web Part”;
deleteVerb.Text = “Delete Web Part”;
}
}
e.Verbs = new WebPartVerbCollection(verbs);
}
protected override void RenderContents(HtmlTextWriter writer)
{
// Get the current webpartzone
PortalWebPartZone wpz = this;
if (wpz != null)
{
// Print the current zone ID and css class
writer.AddAttribute(HtmlTextWriterAttribute.Id,wpz.ID);
writer.AddAttribute(HtmlTextWriterAttribute.Class,string.Format(“wpzone {0}”,wpz.CssClass));
writer.RenderBeginTag(HtmlTextWriterTag.Div);
// Render the part body
RenderBody(writer);
writer.RenderEndTag();
}
else
{
base.RenderContents(writer);
}
}
protected override void RenderBody(HtmlTextWriter writer)
{
WebPartCollection wpColl = new WebPartCollection(this.WebParts);
WebPartChrome chrome = CreateWebPartChrome();
//chrome.PerformPreRender();
foreach (WebPart wp in wpColl)
{
chrome.RenderWebPart(writer, wp);
}
}
protected override void Render(HtmlTextWriter writer)
{
RenderContents(writer);
}
protected override void RaisePostBackEvent(string eventArgument)
{
WebPartManager wpm = WebPartManager.GetCurrentWebPartManager(this.Page);
if (eventArgument.StartsWith(“catalog:”))
{
try
{
string[] argParts = eventArgument.Split(new char[] { ‘:’ }, StringSplitOptions.RemoveEmptyEntries);
Type t = BuildManager.GetType(argParts[argParts.Length – 1], true, true);
WebPart wp1 = (WebPart)Activator.CreateInstance(t);
wpm.AddWebPart(wp1, this, this.WebParts.Count);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
else
{
base.RaisePostBackEvent(eventArgument);
}
}
void HandleDeleteClick(object sender, WebPartEventArgs e)
{
HttpContext.Current.Trace.Warn(“DELETING WEBPART”);
WebPart wp = e.WebPart;
CoreCMS.DataAccess.SPs.UDeletewebpartref(wp.ID).Execute();
this.WebPartManager.DeleteWebPart(wp);
}
public void HandleEditClick(object sender, WebPartEventArgs e)
{
CMSWebpart wp =(CMSWebpart)e.WebPart;
if ((this.Page as PageBasePage).portalwp.SelectedPart != wp.ID)
(this.Page as PageBasePage).portalwp.SelectedPart = wp.ID;
}
void HandleConnectClick(object sender, WebPartEventArgs e)
{
WebPart wp = e.WebPart;
if (wp != this.WebPartManager.SelectedWebPart)
{
}
}
}
}
Thanks David, I’ll give this a go.
But I think you left a few bits of your code out ;)
I’ll see if this works when I get home.
Pingback: Switchable stylesheets « This page intentionally left ugly
Greate Code, thanks
but I can’t find a way that Webpart’s Drag to Another Zone..
Can U help me more?….
Hi bj Park. Thanks!
I’m working on server side drag n’ drops ;)
Actually, you can use jQuery to drag and drop it right now.
I have an example of the HTML (not the server side code) running on a template, but the server side “save” functions need to be implemented.
I think the best way to do this is to trigger an AJAX call to another page which does the saving.
I.E. When you drag and drop it to another webpartzone, the AJAX call sends the location of the webpart back to the control page. This way, you don’t have to refresh the page to save a webpart location.
Hope that helps somewhat.
I plan to add a fully functioning version with server side code for this later on.
Hi eksith..
thanks!
I’ll waiting :)
sorry.. My English is not good…
Hello,
Great job, but I ask you: Why don’t you publish it on the cssfriendly adapter?
There a group of developers doing a good job changing those tableful asp.net components like asp:menu, asp:sitemap in tableless components.
Check it out: http://www.codeplex.com/cssfriendly
See ya!
Hi Roberto.
This is why I didn’t include it on the css friendly adapters ;)
Any news on the ‘fully functioning version with server side code’?
I’m just missing the essential ability drag and drop.
I’m using the original posted code that moment as when I tried the version pasted in the ‘Comments’ section the compiler kept complaining about the PortalWebPartManager. I figured it was a namespace thing but couldn’t sort it out. Was I just being thick?
Regards, Matt
Hi Matt.
The “PortalWebPartManager” that David posted was part of his project. From that code, I’d say there’s a significant portion of his own code we’re not seeing.
The best I can do for now is to suggest using the original code I posted and work from there. There’s a little update where I managed to fix the remaining tables.
As for a fully functioning and perhaps downlodable version…
I’m afraid you will have to wait a bit longer. I had virtually none written when I posted this article and it was only theorhetical. However, as far as I know the code does work as advertised. Right now I’m literally up to my eyeballs in another project. Sorry.
Until I can write a complete version, you may want to consider using a jQuery callback when the module is dropped into its container.
I.E. An AJAX call to a handler that gives the ID of the WebPart, the ID of the WebPartZone and the Sort order. Then, these can be loaded dynamically when the visitor refreshes the page.
All the raw material is given. Only a few steps need to be taken to make it all fit together.
Hope that helps.
Hi All. I would be very interested in you opinion about our tableless webpart implementation. We are working on an open source Portal/ECMS for .NET, which uses Webparts, modified to be tableless. You can download the source from our webpage.
Hello! Thanks a lot!! I was having serious problems with the default table based WebParts layout, and with the help of this post i was be able to solve all my problems. Thanks, again!!
Glad to have helped, Rafael! :D
Nice article I have added drag and drop support take a look here on my web site http://www.coderesearchcenter.com/post/2009/03/14/TFRPortal-CSS-friendly-web-parts.aspx the project is still in beta but thought you might be interested.
Thanks for dropping by Jeff!
And thanks for the link, I’ll definitely check it out.
Hi, eksith.
Why don’t you try building this into a control adapter ?
So you wouldn’t have to make changes into every page.
Have you tried this into Sharepoint?
Thanks
Gabriel
Hi Gabriel.
I haven’t written up a control adapter due to my dissatisfaction with them ;)
In fact this whole approach, I feel, is now obsolete due to the introduction of the MVC framework. Only the client side HTML is the critical portion now. The placements can then be assigned via a custom controller.
I’m hoping to write something about that as well.
let me know when you do, i’d love to read it.
Pingback: Tableless WebParts and WebPartZones « A Place for C Sharpers/.Netters
Nicely done David Martin… well done, with a few tweeks now my XHTML successfully validated.
What I would like to know is how you update the page state – save the personalization blob, when user drops a webpart from one zone to another via a callback?
chaars
Hello eksith ,
I m using your code for one of my site,
web parts are rendring as shown by you but design mode is not working i.e i m not able to move a web part from one zone to another
it will be better if you provide me with source code files you have
Pingback: Building Completely Tabeless WebPart Control + Drag and Drop | Colour Blend Creative
Pingback: [RESOLVED]How to create rounded corner webparts in asp.net 3.5?? | ASP Questions & Answers
Pingback: Table Less Asp.Net webparts with SkinID | ASP Questions & Answers
Hi Eksith,
Your link below is not good anymore. I really need that to add the drag and drop ability to webparts without tables. Can you please provide me a new link.
http://www.coderesearchcenter.com/post/2009/03/14/TFRPortal-CSS-friendly-web-parts.aspx