Multiple WidgetZones in BE.NET

April 18, 2009 at 2:35 PMBen

One of the commonly asked for features in BlogEngine.NET has just made it into the codebase.  This feature is Multiple WidgetZones.

To use multiple widget zones in your BE.NET blog, there's not too much you need to do.  I'll explain how it works and a couple of minor changes you may need to make to your blog -- even if you won't be using more than one widgetzone.

Stylesheet Changes

When there was only one widgetzone, the widgetzone <div> and the widget selector dropdown list had a fixed ID on their HTML elements.  So, there was these two elements:

<div id="widgetzone">
<select id="widgetselector">

Because there can now be multiple widget zones and multiple widget selectors, and because there cannot legally be more than one element with the same ID, the widgetzone <div> now uses "widgetzone" for its class, and the selector uses "widgetselector" for its class.  Both the widgetzone and the selector also have a unique ID based on the new ZoneName property (see below).  So, example markup of what the <div> and <select> elements now look like are:

<div class="widgetzone" id="widgetzone_PageBottom">
<select class="widgetselector" id="widgetselector_PageBottom">

There's a good chance you have #widgetzone or #widgetselector styles defined in your theme's CSS file.  Since there no longer are elements with those IDs, you'll want to do a search-and-replace in your CSS file.  Replacing #widgetzone with .widgetzone and #widgetselector with .widgetselector is all that is required.  Fortunately, because each widgetzone <div> and <select> element now have their own unique IDs, you can use those IDs to style individual widgetzones if they need to be styled differently in your blog.

ZoneName

Each widgetzone you add to your blog needs to have it's own unique ZoneName.  The ZoneName can be added as a simple property to the widgetzone's control markup.  For example:

<blog:WidgetZone runat="server" ZoneName="PageBottom" />

Although the ZoneName property is new to BE.NET, the existing widgetzone you've had in your blog up till now will start off with a ZoneName of "be_WIDGET_ZONE".  This was the name used to store widgetzone data in either the App_Data folder, or in a database.  Although not required, it wouldn't hurt to add that ZoneName to your existing widgetzone for clarity:

<blog:WidgetZone runat="server" ZoneName="be_WIDGET_ZONE" />

If you don't explicitly state a ZoneName, that is okay for one widgetzone, as BE.NET will default to using "be_WIDGET_ZONE" when a ZoneName is not provided.  But, you MUST give each widgetzone its own unique ZoneName if you will use more than one widgetzone in your blog.

App_Data and Database - Both OK

Sometimes designing a flexible, well structured application pays off.  It's great that BE.NET was designed as such.  There was very little change required to enable multiple widget zones to be saved in either the App_Data folder or in a database.  No database changes are needed either since the data for all the widgetzones will be stored in the existing be_DataStoreSettings table.  Whether you're using XML storage in the App_Data folder or a DB backend, multiple widget zones work in both scenarios.

Moving Widgets

Drag-and-drop is out, and dropdown is in!  Rearranging widgets within a widgetzone has always been done by dragging and dropping a widget to a new location within the zone.  While initially implementing multiple widgetzones, I had a zone on the left side of the page, and another on the right side.  While trying to drag-and-drop a widget on the right side to a new location within the same right side zone, I was having troubles with the drag-and-drop library trying to drop the widget into the zone on the left side of the page!  Instead of spending time trying to weed through the logic of the drag-and-drop library, it occurred to me a simpler interface would be the better route to go.  Drag-and-drop may be sexy, but getting widgets to move without any hassles or confusion is a better goal.

There is now a 'Move' link for each widget, next to the Edit and X (remove) links.

New Move Link

Clicking the Move link will display a dropdown list and Move button above the Move link.

Move Dropdown List and Button

In this example, I have 3 widget zones in my blog.  Opening the dropdown list shows all 3 zones and the widgets within each zone.

Move Widget Dropdown List Contents

The 3 zones appear in the list with brackets around them.  They are the HeaderZone, be_WIDGET_ZONE and the PageBottom zone.  Under each zone name is the list of widgets within that zone.  You can either select a widget to move the current widget in front of, or you can select a zone which will move the current widget to the bottom of that zone.

As you can tell, moving a widget from one zone to another zone is fully supported.  There's lots of possibilities with this capability.

Few More Stylesheet Changes

In addition to the search-and-replace stylesheet modification mentioned above, there's a couple of other additions you may want to make to your stylesheet in support of the new Move link and dropdown list.  These changes are already in the stylesheets of the themes included with BE.NET.  So you know what they are, I've listed them below.  The first style below was a modification of an existing style.

div.widget a.edit, div.widget a.move{
	font-size: 10px;
	font-weight: normal;
	float: right;
	z-index: 1;
	margin-left: 5px;
}

.widgetzone div#moveWidgetToContainer {
	text-align: right;
	margin: 3px;
}

Credits

Thanks to McZosch who posted the initial code changes for multiple widgetzones in the Issue Tracker at CodePlex.  I was able to use mostly everything he contributed.  Integration of his code went smoothly.  Most of the time I spent with adding this feature was with moving widgets.  Switching from drag-and-drop to dropdown and enabling widgets to move from one zone to another took some time altogether.

Give it a Try!

Since it's of course not required to fill up a widgetzone with many widgets in it, multiple widgetzones will allow you to add a zone anywhere in your blog with just a single widget in it.  There's a lot of possibilities with this, and for many BE.NET bloggers out there, I think multiple widgetzones are going to be very useful.

Everything seems to work well after testing out the new feature in a few scenarios.  You can download the latest build of BE.NET in the Source Code section at CodePlex.  If you run into any issues, please post them in the Discussions area at CodePlex.

Posted in: Development

Tags: ,

BlogEngine.NET Widget: About Me

March 10, 2009 at 12:09 PMBen

Bloggers often have a few words they like to say about themselves.  With BlogEngine.NET, there are a few ways to achieve this.  In the default BE.NET installation, a TextBox widget titled "About the author" is already in the blog's sidebar.  The author can just click 'Edit' and quickly describe themselves using the WYSIWYG editor.  BE.NET users will find the TextBox widget is a very powerful widget because it's generic enough to display virtually anything.

Another option for About Me is to create a Page in BE.NET, and add a link on your blog to the About Me page.  The main difference with this 'Page' approach is the About Me content is only seen on the About Me page, rather than the widget approach where the About Me content is shown on every blog page in the sidebar.  With the Page approach, you also get a lot more room horizontally which is nice if you have some pictures or other space consuming content.  I'm currently using the Page approach for the About Me page on this blog.

A third approach which is very similar to using the TextBox widget described above is to use an "About Me" widget.  BE.NET allows each editor/administrator to create their own profile on the Profiles tab in the control panel.  On the Profile page, each editor/administrator can enter details such as their Name, Phone Number, Address, a Photo image, and last but not least, About Me!  The About Me box is a WYSIWYG editor.  Currently, profile information entered is hardly used anywhere within BE.NET.  The information is available, however, through the AuthorProfile class in BlogEngine.

The AboutMe widget displays the About Me text entered for a user on the Profiles tab in the control panel.

If multiple editors/administrators have been created on the Users tab in the control panel, a separate profile can be created for each one of these users on the Profiles tab.  Because multiple profiles may exist, the AboutMe widget needs to know which profile's About Me content it should display.  The edit control in the AboutMe widget allows the user to choose the profile to display the About Me content for.  Because the settings for each widget are stored independently of one another, multiple AboutMe widgets may be added to the blog -- one AboutMe widget for each profile.

The AboutMe widget consists of the 2 required files you find for widgets -- widget.ascx, widget.ascx.cs.  The edit control is made up of edit.ascx and edit.ascx.cs.  A download link for the entire widget is available at the bottom of this post if you are interesting in trying it out in your own BlogEngine.NET installation.

<%@ Control Language="C#" AutoEventWireup="true" CodeFile="widget.ascx.cs"
 Inherits="widgets_AboutMe_widget" %>

<asp:PlaceHolder runat="Server" ID="phAboutMe" />
#region Using

using System;
using System.Web;
using System.Web.UI;
using System.Collections.Specialized;
using BlogEngine.Core;

#endregion

public partial class widgets_AboutMe_widget : WidgetBase
{
    public override void LoadWidget()
    {
        string CacheKey = "widget_aboutme_" + this.WidgetID.ToString();
        string ProfileUserName = (string)HttpRuntime.Cache[CacheKey];

        if (ProfileUserName == null)
        {
            StringDictionary settings = GetSettings();

            if (settings.ContainsKey("UserName"))
                ProfileUserName = settings["UserName"];

            if (ProfileUserName == null)
                ProfileUserName = string.Empty;

            HttpRuntime.Cache[CacheKey] = ProfileUserName;
        }

        if (string.IsNullOrEmpty(ProfileUserName))
        {
            // Find the first profile with About Me content.
            foreach (AuthorProfile profile in AuthorProfile.Profiles)
            {
                if (!string.IsNullOrEmpty(profile.AboutMe))
                { 
                    ProfileUserName = profile.UserName;
                    HttpRuntime.Cache[CacheKey] = ProfileUserName;
                    break;
                }
            }
        }

        if (!string.IsNullOrEmpty(ProfileUserName))
        {
            AuthorProfile profile = AuthorProfile.GetProfile(ProfileUserName);
            if (profile != null && !string.IsNullOrEmpty(profile.AboutMe))
            {
                phAboutMe.Controls.Add(new LiteralControl(profile.AboutMe));
            }
        }        
    }

    public override string Name
    {
        get { return "AboutMe"; }
    }

    public override bool IsEditable
    {
        get { return true; }
    }
}
<%@ Control Language="C#" AutoEventWireup="true" CodeFile="edit.ascx.cs"
Inherits="widgets_AboutMe_edit" %>

<div>

    <h3>Select the Profile to show About Me for</h3>
    
    <div id="noProfilesAvailable" runat="server">
        No profiles have yet been created.
    </div>
    
    <asp:RadioButtonList ID="rblProfileToDisplay"
     runat="server"
     DataTextField="UserName"
     DataValueField="UserName">
    </asp:RadioButtonList>
    
    <br />
    
    <div>
        Note: Make sure you have entered About Me content for
        the selected user above.  This content should be
        entered on the Profiles tab in the Control Panel.
    </div>

</div>
#region Using

using System;
using System.Collections.Specialized;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
using BlogEngine.Core;

#endregion

public partial class widgets_AboutMe_edit : WidgetEditBase
{
    protected void Page_Load(object sender, EventArgs e)
    {
        if (!Page.IsPostBack)
        {
            string SelectedUserName = null;

            StringDictionary settings = GetSettings();
            if (settings.ContainsKey("UserName"))
                SelectedUserName = settings["UserName"];

            if (!string.IsNullOrEmpty(SelectedUserName))
            {
                if (AuthorProfile.GetProfile(SelectedUserName) != null)
                    rblProfileToDisplay.SelectedValue = SelectedUserName;
            }

            rblProfileToDisplay.DataSource = AuthorProfile.Profiles;
            rblProfileToDisplay.DataBind();

            noProfilesAvailable.Visible = rblProfileToDisplay.Items.Count == 0;
        }
    }

    public override void Save()
    {
        StringDictionary settings = GetSettings();
        settings["UserName"] = rblProfileToDisplay.SelectedValue;
        SaveSettings(settings);
        HttpRuntime.Cache.Remove("widget_aboutme_" + this.WidgetID.ToString());
    }
}


AboutMe Widget Download (2.4 kb)

Posted in: Development

Tags: ,