Adding a Blog Category to Blog4Umbraco

Recently, I've been working on an Umbraco website for a client that required several blogs, but each blog needed to have it's own set of tags and the Tag Cloud needed to be compartmentalised.

 

NOTE: The code described below didn't quite work out as I'd hoped with further testing, so I went back to the drawing board.  Take a look at the post Blog Categories in Blog4Umbraco - Take Two for a much cleaner and more transparent solution to the problem with flexible Tag Groups.

Problem Number 1: Tag Clouds in Umbraco are non-discriminatory

If you have more than one blog in Umbraco, go take a look at your Tag Cloud.  You will notice that all tags are listed, not just those for a specific blog.  Worse, even Documents that have a Tag Data-typed attribute will contribute to the Tag Cloud, regardless of whether they are a Blog Post document or not.  As a result, clicking on one of those tags will not necessarily result in listing blog posts.

Adding a Tag Category to an blog

Adding a category is as simple as adding another attribute to the Blog Document Type.  Here's my settings:

Adding a Category attribute to the Blog Document TypeAdding a Category attribute to the Blog Document Type

To apply the category, go to the root document of your blog and update the field, save and publish.  Done.  Now what?

It's all about the Tags

First, some background:  The Umbraco Tag datatype allows the administrator to set up tags for document types, which can then be used in various ways - as a Tag Cloud, to loosely associate related documents (allowing the developer to list those documents automatically on an Article page, for example), etc.

By default, the Tags Data Type uses a Tag Group named, surprisingly enough, "Default".  However it's possible to create a new Data Type - call it say "Event Tags" - and specify a new Group - in this case, "Events":

Event Tags Data Type

Creating a new Event Tags Datatype - Notice the Tag group that can be specified once you choose the Tags control.

The advantages of doing this mean that you could have a tag cloud for a specific group of articles - essentially creating an article Category... Exactly what we want for our multiple blogs scenario.

Note: For this solution, creating a new Tag Data Type is not actually necessary.  This step is one of the paths I originally took trying to resolve this issue, and serves as background information to what we are trying to do. However I quickly realised that this wasn't going to serve my needs at all.

Using a non-standard Tag Group in Blog4Umbraco

Ok, so now we have a category associated with a blog, and we have a grouping for a set of tags.  But how do we make sure that tagging an article in a Blog Entry uses the correct Tag Group?

As it turns out, this isn't so straightforward.  We can't just change the Blog Post Document Type to use our new Event Tags Data Type, as all blog entries, on all blogs, must by necessity use the Blog Post Document Type.  If you try to create a new Document Type based on the Blog Post one, Blog 4 Umbraco simply refuses to work with it.

After some investigation, I decided that the best approach was to implement some Event Handling Code.  The beauty of this approach is that you are actually able to roll your own Library - there's no need to modify any existing source code.  So here goes:

Writing an Event Handler

We want to write an Event Handler.  In order to make sure our event handler is registered, we need to derive from the Umbraco ApplicationBase class, and register our handlers in the Constructor:

    public class SetTags : ApplicationBase
    {
        public SetTags()
        {
            Document.AfterSave += Document_AfterSave;
        }

        void Document_AfterSave(Document sender, umbraco.cms.businesslogic.SaveEventArgs e)
        {
            FixTags(sender);
        }
    }

Handling the Document.AfterSave Event

The core of this solution is to examine the Blog Post document, retrieve a list of Tags, and any tags that don't currently have a Group that matches the category attrbute we applied to the Blog earlier need to be removed and replaced with new ones associated with the desired group.  For this I have the following Method:

private void FixTags(Document sender)
        {
            if (sender.ContentType.Alias == "BlogPost")
            {
                if (sender.Parent != null)  //If top of tree, something is wrong.  Skip.
                {// Get the Blog category.
                    // We want to remove any tags on the document that aren't associated with the Blog category.

                    string category = BlogLibrary.GetValueRecursively(sender.Id, "category").ToString();
                    if (string.IsNullOrEmpty(category))
                        // No point continuing - we'll use the default tag group.
                        return;

                    StringBuilder tags = new StringBuilder();
                    foreach (var t in umbraco.editorControls.tags.library.GetTagsFromNodeAsITags(sender.Id))
                    {
                        if (t.Group != category)
                        {
                            if (tags.Length > 0)
                                tags.Append(",");
                            tags.Append(t.TagCaption);

                            umbraco.editorControls.tags.library.RemoveTagFromNode(sender.Id, t.TagCaption, t.Group);
                        }
                    }
                    umbraco.editorControls.tags.library.addTagsToNode(sender.Id, tags.ToString(), category);
                }
            }
        }

Some notes on this code:

  • Line #9: The BlogLibrary.GetValueRecursively method is a refactoring of the private method in the Autoping Event Handler class in the Blog4Umbraco dll.  All I've done is extract it into a public static method in the BlogLibrary class, and switch the parameters (mainly because it makes more sense to me to have the Node Id as the first parameter).  I've also changed the return value to an object type instead of string.  For reference, I include the code for that method here:

        public static object GetValueRecursively(int nodeId, string alias)
        {
            Document n = new Document(nodeId);
            Property p = n.getProperty(alias);

            if (p != null && !string.IsNullOrEmpty(p.Value.ToString()))
                return p.Value;
            else if (n.Level > 1)
                return GetValueRecursively(n.Parent.Id, alias);

            return string.Empty;
        }
  • Lines 23 & 26: The umbraco.editorControls.tags.library exposes all the tag manipulation methods we need. We retrieve the list of tags associated with this node, and any node that doesn't have a group matching the Blog Category gets removed and added to the new Tag list.  Then after going through all the tags, we add back the new Tags with the Blog category as their Tag Group.

Compile your dll, copy it to the bin directory of your Umbraco installation, and try it out.  Whenever you tag a Blog Post and save the document, all tags will re-added with the new Tag Group:

Tags Database Table

The Tags Database Table after the test Tag has been added with the Events Group.

Of course, you'll be wanting to go and tweak your Tag Cloud xslt code to take advantage of the Blog Category and Tag Groups next... I'll cover that in another Blog Entry...

Post a comment