Stupid Skinning Trick

I meant to post about this, oh, three months ago? I guess things got away from me.

You know those lists that Community Server offers for blogs? The ones I've got on the right side of my skin for blogs that I read and sites that I find myself at more than I should? Were you aware that those aren't sorted? This can be a bit misleading because they're sorted in your Control Panel, but on the page itself, they appear in whatever order they were originally entered, just like that image to the right.

Thankfully, we can address this with a very small amount a C#. The skin file that contains the UI for CS's lists is Skin-LinkList.ascx. The general structure of this file is a pair of nested Repeater tags. The outer Repeater iterates over your link categories (ID="Categories"), while the inner Repeater iterates over the links themselves (ID="Links"). Using a couple of strategically-placed ASP.NET event handlers, we can sort the contents of one or both of these Repeaters before they begin iterating. I only sort the inner Repeater in my skin, so that's the example I'll give.

The first thing you need to do is to hook the DataBinding event on the repeater. Locate the Repeater with the ID "Links" and add a reference to your event handler:

<asp:Repeater ID="Links" OnDataBinding="Links_DataBinding" runat="server">

Now you just need to add the Links_DataBinding method itself:

<script runat="server">
 
    protected void Links_DataBinding( object sender, EventArgs e )
    {
        Repeater links = sender as Repeater;
        if ( links == null )
            return;
        
        // The list of links is provided as an ArrayList. Note that is is extremely 
        // possible that this will change in future revisions of CS. This becomes a
        // possible breaking point for this skin.
        ArrayList items = links.DataSource as ArrayList;
        if ( items == null )
            return;
        
        // Sort the items using their default comparer. They're all strings, so 
        // this is a straightforward sort.
        items.Sort();
    }
    
</script>

This is really, really simple. The data source being passed to the Repeater is an ArrayList of strings, which are sorted easily enough. We get access to the data source (using the necessary safeguards to ensure that we don't blow anything up in the name of something so trivially cosmetic) and simply call ArrayList.Sort(). That's it!

Interestingly enough, if you look in the CS database schema, you'll notice that the table that contains links (cs_Links) also has a column called "SortOrder". I did some digging around in the source code, and I don't believe that this field is currently referenced anywhere in the application. I assume that it could be used as the basis for a control panel modification that would allow you to reorder your links on a whim, should anyone feel like going that far.

NFCS Updated

Seeing Jaxon's post about fixing the search on his blog prompted me to do a search on my own blog and see what happened. I was happy to discover that it works, but the EntryDate control that I had put together was not producing any dates. This confused me.

The search results page for your blog is generated by Skin-IndividualSearchContainer.ascx. This page contains an instance of the <Blog:EntryList> control. This is where my confusion arose from, as I was certain that I had covered the EntryList case, as that is what the front page of my blog uses (verily, the front page of the site still displays dates correctly, as expected).

After rooting around a bit, I discovered the problem. The EntryList control, at its heart, relies on a Repeater to display its content. When the EntryList occurs inside of EntryListContainer, it is populated with instances of WeblogPost. However, when it occurs in IndividualSearchContainer, it is populated with instances of IndexPost. Both of these contain the post date, but I was only checking for the presence of the former. By adding support for the latter, everything is once again sunshine and rainbows.

An updated version of the assembly can be found here, complete with source code.

NinjaFish.CommunityServer

[Update] Due to something else I'm working on, I've shortened the name of the control to just "EntryDate".

Something that I'd been meaning to do was to take that code I came up with for parsing out the Community Server post dates and distill it down to a web custom control. Well, I've finally gotten around to it.

The control is called EntryDate, and inherits from the CS WeblogThemedControl. This means that it uses the same skinning motif as the rest of the built-in CS blog controls, so you drop a reference to EntryDate where you want it and fill out the UI details on Skin-EntryDate.ascx. Here is an example pulled from my own blog skin:

Skin-EntryView.ascx

<%@ Control Language="C#" %>
<%@ Import Namespace="CommunityServer.Components" %>
<%@ Register TagPrefix="CS" Namespace="CommunityServer.Controls" Assembly="CommunityServer.Controls" %>
<%@ Register TagPrefix="Blog" Namespace="CommunityServer.Blogs.Controls" Assembly="CommunityServer.Blogs" %>
<%@ Register TagPrefix="nf" Namespace="NinjaFish.CommunityServer.Controls" Assembly="NinjaFish.CommunityServer" %>

<div class="entry-view">
<nf:EntryDate runat="server" />
<h1 class="entry-title"><asp:Literal ID="EntryTitle" runat="server" /></h1>
<asp:Literal ID="EntryBody" runat="server" />
<div class="entry-footer">
<div class="entry-published-date">
<CS:ResourceControl ResourceName="Feedback_FilterPublished" runat="server" /> <asp:Literal ID="EntryDesc" Text="Perma Link" runat="server" />
</div>

<asp:HyperLink ID="EditLink" Visible="False" runat="server" /> <Blog:CategoryTagControl ID="Tags" runat="server" />
<Blog:DownloadAttachmentLink ID="Attachment" runat="server" />
</div>
</div>

The tag that does the fun bit is <nf:TemplatedEntryDate runat="server" />, while the contents of the skin file can be found below:

Skin-EntryDate.ascx

<%@ Control Language="C#" %>

<span class="entry-date">
<asp:Label ID="EntryDay" CssClass="entry-day" runat="server" />
<asp:Label ID="EntryMonth" CssClass="entry-month" runat="server" />
<asp:Label ID="EntryYear" CssClass="entry-year" runat="server" />
</span>

That's it! The look and feel of the date is up to you; you don't even have to use my markup, and the control supports attributes to render different date formats for each part of the date. More details in the readme file.

Also, because I was too lazy to strip it out and I guess it works as intended, the <CS:Head> replacement I spoke about in this post is included, as well.

You can download the assembly and source here.

Breaking Down the CS Post Date

While I don't think there's anything terribly fancy about the particular skin I've put together, there is one part of it that required some research, and I've had a couple of questions about it already: the post dates. I've seen this particular style of date (the big one, to the left of this paragraph) on a few other blogs, and wanted it for myself. The problem is that Community Server doesn't give you a particularly handy way to get at this information, so you have to do a bit of extra work to get it.

If you open up Skin-EntryView.ascx in the default skin, you will see the following:

<asp:Literal id="EntryDesc" Text="Perma Link" Runat="server" />

This is the control into which CS embeds the post date, and it is the easiest place from which to extract that information. For this skin, I’ve created the following markup to hold the date information:

<span class="entry-date">
    <asp:Label ID="entryDay" CssClass="entry-day" runat="server" />
    <asp:Label ID="entryMonth" CssClass="entry-month" runat="server" />
    <asp:Label ID="entryYear" CssClass="entry-year" runat="server" />
</span>

The Label entryDay will hold the day, entryMonth the month, and entryYear the year. To populate these controls, I handle the PreRender event on the page. In this method, I parse the date into its constituent parts and use that information to fill the Labels. Once done, you can hide the EntryDesc Literal if you no longer need it.

<script runat="server">
    protected void Page_PreRender( object sender, EventArgs e )
    {
        DateTime entryDate = DateTime.Parse( EntryDesc.Text );
        entryDay.Text = entryDate.Day.ToString();
        entryMonth.Text = entryDate.ToString( "MMM" );
        entryYear.Text = entryDate.Year.ToString();
    }
</script>

Note: To be safe, you’d probably want to wrap the DateTime.Parse() in a try-catch block, or use DateTime.TryParse() if you are on ASP.NET 2.0. I was on ASP.NET 1.1 when I wrote this code and I have a fair amount of faith that CS will actually provide me with a valid date.

This works quite well for Skin-EntryView.ascx. Unfortunately, It’s not quite as simple for Skin-EntryList.ascx. Because you’re handling multiple date controls now, sorting out all of the dates in PreRender isn’t really a viable option. In this case, the simplest and most straightforward approach is to handle the ItemDataBound event of the Repeater control that generates the entries. We use the same markup as before, but this C# code instead:

<script runat="server">
protected void entryItems_MyItemDataBound( object sender, RepeaterItemEventArgs e )
{
    WeblogPost entry = e.Item.DataItem as WeblogPost;
    if ( entry != null )
    {
        Label entryDay = e.Item.FindControl( "entryDay" ) as Label;
        if ( entryDay != null )
            entryDay.Text = entry.UserTime.Day.ToString();

        Label entryMonth = e.Item.FindControl( "entryMonth" ) as Label;
        if ( entryMonth != null )
            entryMonth.Text = entry.UserTime.ToString( "MMM" );

        Label entryYear = e.Item.FindControl( "entryYear" ) as Label;
        if ( entryYear != null )
            entryYear.Text = entry.UserTime.Year.ToString();
    }
}
</script>

The line to note here is WeblogPost entry = e.Item.DataItem as WeblogPost. This gets you the WeblogPost instance that corresponds to the entry currently being rendered. WeblogPost represents a single blog post and contains all of the relevant information that belongs to it. You can learn more about this class by perusing the CS SDK. The WeblogPost’s UserTime property contains the date and time at which the entry was posted, and we parse this DateTime in the same way that we did on Skin-EntryView.ascx. Also note that, because we are assigning values to controls inside of a Repeater, we must use the FindControl() method to get references to them instead of referencing them directly by ID.

New Skin, New CS

I’ve finally got that new Community Server skin more or less completed, as should be painfully obvious if you’d seen the old one. I did things a little bit differently this time (but not much), and did a few things that would not have been possible without some digging around in the CS SDK. If you’re not familiar, the SDK is simply the CS source code. Being a relatively new ASP.NET developer myself, I found it to be really quite interesting the way that they went about developing their framework. Perhaps later I’ll give a tour of that and some of the more complicated things that went into this skin.

I’ve also gone ahead and upgraded to the Community Server 2.1 beta, which was released while I was still working on the new skin. I stuck with the ASP.NET 1.1 version this time, because I’m not 100% certain that the medium trust issue has been resolved, and ASPnix would rather that we keep off of the ASP.NET 2.0 version until it has been. It’s not a big deal, except that I do want to slip some .NET 2.0 code in behind the scenes.

[Update]
Holy busted layout, Batman! I should really check this thing in Internet Explorer before I upload it. I guess that's what I get for sticking to the better browser.

Custom Blog Links

So it’s 11:15 and I’m sitting here hammering away at a new blog skin for this site. One of the challenges I’m facing with Community Server is creating a custom format for the links at the top of the blog. For the current skin, I don’t use <CS:NavigationMenu> to generate the navigation links. Instead, I make direct calls to the individual links that I want, such as <Blog:HomeLink> and <Blog:AboutLink>. These links render as list items by default, and you can pass in an “IsListItem” attribute to override this behavior. But what if you want even more control than that?

For the new skin, what I need is to generate the following markup for the links:

<ul>
    <li><a href=”#”><span>Link Text</span></a></li>
</ul>

The only difference, in terms of the link itself, is that there is a span tag inside the anchor tag. As far as I’ve been able to tell, the built in blog controls do not provide an easy way to do this. However, after some sleuthing in the CS source code, I’ve managed to hack out a way to generate this markup. It’s not at all pretty, but it seems to work. Here’s how it goes:

The first thing you’ll need to do is to add the following namespace import directives to the top of your page.

<%@ Import Namespace=”CommunityServer.Components” %>
<%@ Import Namespace=”CommunityServer.Blogs.Components” %>

These provide you access to the two utility classes that you’ll need to use to make this happen: CommunityServer.Blogs.Components.BlogUrls and CommunityServer.Components.CSContext. The former provides methods to retrieve the URLs related to your blog. These methods require you to pass in your blog’s application key as a parameter, however; CSContext provides you with access to your application key, among other things.

How you put all of this together is pretty simple, actually (assuming you’re familiar with the basics of ASP.NET). The code to generate the link is as follows:

<a href="<%= BlogUrls.Instance().HomePage( CSContext.Current.ApplicationKey ) %>">
    <span>
        <CS:ResourceControl ResourceName="Weblog_BlogLinks_Home" runat="server" />
    </span>
</a>

That’s a lot of text! Let’s pick it apart.

CSContext.Current.ApplicationKey gives you the application key for the current blog. The application key provides a unique identifier for each blog.

BlogUrls.Instance().HomePage() is the method that gets you the link to a blog’s home page. Which blog? Whichever blog corresponds to the application key you feed it. BlogUrls contains identical methods for the other links on your blog, such as About() and ContactForm().

The <CS:ResourceControl> control simply retrieves text from your Resources.xml file. You pass in a resource name via the ResourceName attribute and the control gets that text from the XML file.

And, of course, you can see my span tag inside of the anchor, which was my goal in the first place. You can substitute whatever markup you please, of course, and I’m sure that there are ways to turn this into a user control, though I’m not sure how that would integrate with CS. If anyone has a better (read: cleaner) way of achieving the same effect, I’d love to hear it.

Changing the Community Server Contact Link Text

I believe this was the second or third stupid question that I asked on the Community Server forums. The default text for the link to the contact form is actually “Email”. I wanted this to read “Contact” (as it does now). Easy enough, right? Community Server stores its text strings in a file called Resources.xml. This bad boy is hiding in the \Languages\en-US folder under your CS root. All you need to do is find the resource tag that controls the text you’re looking for any you’re all set!

Yeah, right. If only it were that easy! The logical text to search for here is “>Email<”. Unfortunately, this turns up about thirty bajillion hits, none of which particularly screams “I am the link you are looking for,” especially if you’re a relative CS newb and not yet familiar with every feature of the software.

So I will save you the trouble: the resource key for the contact link is “Weblog_SubscriptionList_Contact”. This resource is rendered by the tag <Blog:ContactLink ID="ContactLink" runat="server" />.

Skinning This Beast

I've finally gotten enough of the blog's skin done so that I have something to show for it. The skin is based loosely on Jaxon Rice's Developer skin. It's heavily modified, but that's the point, isn't it? It's been a very long time since I did any sort of web development for myself, and it turns out that I'm a bit out of date on the latest development trends. I think I've nailed most of them in terms of layout and such, but there's some tweaking to do. There's also several parts that I just haven't gotten around to yet, but I feel that there's enough that updating the site is worthwhile.

[Update]
I've been making incremental changes to the blog's skin all day, and I have to say, I don't know where I'd be without Jaxon's Developer skin. While the layout provided can be used as a nice starting point, the real value is that Jaxon has exposed (I believe) all of the available Community Server skinning elements, whether they are necessary or not. This makes it very easy to change the look and feel of any of the blog's elements without having to figure out what they are and where they go. For those of us who prefer hitting buttons and seeing the result to poring over documentation and rooting through whitepapers, this is tremendously useful.

It also speaks volumes to the quality of Community Server itself. I haven't yet run into something I wanted to implement that I could not do with a minimum of effort. While Jaxon's skin exposes all of that information to me, it was CS itself that provided the functionality. So far, the product seems to be extremely useable.
More Posts