Magnus Rahl
Apr 21, 2009
visibility 7907
star star star star star
(1 votes)

Generic Property Value Selection Tree

Relate+ uses the Club and Forum association property types to associate pages with Community entities. This is a great way to integrate community and editorial content. However, the edit control for the property uses a flat list of all available clubs/forums. As a community grows this list may grow out of bounds for efficient editing, which is why I saw the need to be able to browse say Clubs in a tree like with the Page property type.

 

The idea

I didn’t want to limit my property type to a certain structure or otherwise limit the source for the tree where the property value is selected. For that reason I created a generic control and an interface which should be implemented to feed the tree with data.

The class implementing the interface decides how the tree should be built. This way Clubs, Forums (or really anything) can be put in a tree sorted by Site, Category, first letter in name, or a combination of several of these, or anything else. The property edit control, inheriting an abstract class associated with the tree control, is still responsible for saving the value, so invalid selections (like selecting the “first letter in name” parent rather than a Club) can be disregarded.

 

Example

The property types that can be used with the tree control are not limited, they can be based on PropertyNumber, PropertyString or anything else. The representation (as strings) is controlled by your implementation. Internally in the tree control the values are treated as strings (by calling the ToString method of your PropertyData.Value). This example uses a property type derived from PropertyNumber to represent a Category in EPiServer Community.

This is the code implementing the data feeding interface, IHierarchialdata, in the class CommunityCategoryData:

   1: /// <summary>
   2:     /// Represents the categories in the current EPiServer Community installation,
   3:     /// disregarding Site.
   4:     /// </summary>
   5:     public class CommunityCategoryData : IHierarchialData
   6:     {
   7:         #region IHierarchialData Members
   8:  
   9:         public Dictionary<string, string> GetChildren(string parentKey)
  10:         {
  11:             Dictionary<string, string> children = new Dictionary<string, string>();
  12:  
  13:             CategoryCollection childCategories = null;
  14:  
  15:             if (parentKey != null)
  16:             {
  17:                 // Return the child categories
  18:                 ICategory parentCategory = GetCategory(parentKey);
  19:                 if (parentCategory != null)
  20:                 {
  21:                     childCategories = CategoryHandler.GetCategories(parentCategory);
  22:                 }
  23:             }
  24:             else
  25:             {
  26:                 // Return the root categories
  27:                 childCategories = CategoryHandler.GetCategories();
  28:             }
  29:  
  30:             if (childCategories != null)
  31:             {
  32:                 foreach (ICategory child in childCategories)
  33:                 {
  34:                     children.Add(child.ID.ToString(), child.Name);
  35:                 }
  36:             }
  37:             return children;
  38:         }
  39:  
  40:         public string GetDisplayString(string key)
  41:         {
  42:             // Return a string consisting of the category name and ID
  43:             ICategory cat = GetCategory(key);
  44:             if (cat != null)
  45:             {
  46:                 return String.Format("{0} [{1}]", cat.Name, cat.ID);
  47:             }
  48:             else
  49:             {
  50:                 return String.Empty;
  51:             }
  52:         }
  53:  
  54:         public string DialogTitle
  55:         {
  56:             get
  57:             {
  58:                 //TBD: Could return a language path
  59:                 return "Select Category";
  60:             }
  61:         }
  62:  
  63:         public string DialogCssPath
  64:         {
  65:             get
  66:             {
  67:                 // Use default style
  68:                 return null;
  69:             }
  70:         }
  71:  
  72:         #endregion
  73:  
  74:         /// <summary>
  75:         /// Helper method which gets the ICategory object corresponding to an ID as a string
  76:         /// </summary>
  77:         /// <param name="key">The ID as a string</param>
  78:         /// <returns></returns>
  79:         protected ICategory GetCategory(string key)
  80:         {
  81:             ICategory cat = null;
  82:             int id;
  83:             if (int.TryParse(key, out id))
  84:             {
  85:                 cat = CategoryHandler.GetCategory(id);
  86:             }
  87:             return cat;
  88:         }
  89:     }

As you can see all it does is basically to feed data according to the category tree from the Community database, as well as implement some required settings for the dialog and user friendly names for the categories when displayed in edit mode.

Needed is also the property control (and of course the property, but that only overrides the CreatePropertyControl method), which is based on the abstract HierarchialSelectionControlBase class which is responsible for rendering the controls:

   1: /// <summary>
   2:     /// Implementation of the HierarchicalSelectionControlBase used by 
   3:     /// PropertyCommunityCategory
   4:     /// </summary>
   5:     public class PropertyCommunityCategoryControl : HierarchicalSelectionControlBase
   6:     {
   7:         protected override Type DataFactoryType
   8:         {
   9:             get
  10:             {
  11:                 // Return the type of the class responsible for returning the
  12:                 // hierarchial data to the HierarchialBrowser control
  13:                 return typeof(CommunityCategoryData);
  14:             }
  15:         }
  16:  
  17:         /// <summary>
  18:         /// Overridden method which sets the height and width of the
  19:         /// dialog.
  20:         /// </summary>
  21:         public override void CreateEditControls()
  22:         {
  23:             base.CreateEditControls();
  24:             base.DialogWidth = 480;
  25:             base.DialogHeight = 640;
  26:         }
  27:  
  28:         /// <summary>
  29:         /// Overridden method which sets the value selected in the dialog,
  30:         /// taking into consideration that the dialog uses a string representation
  31:         /// and that the property used is based on PropertyNumber
  32:         /// </summary>
  33:         public override void ApplyEditChanges()
  34:         {
  35:             // Parse the string value, default to 0
  36:             int id = 0;
  37:             int.TryParse(this.EditControl.Value, out id);
  38:             base.SetValue(id);
  39:         }        
  40:     }

As you can see, this is were we provide information where the control should get it’s tree data from (the CommunityCategoryData class) and make sure the value is saved in correct form (remember it’s represented by a string by calling your PropertyData.Value.ToString() when loaded).

 

Result

The community category property type looks like this:

hiearchial_editcontrol

And the dialog when launched looks like this:

hiearchial_dialog

Source code

The source code for the base classes and interfaces, the page used in the dialog as well as the Community category example implementation can be downloaded from http://www.mediafire.com/src (the file HierarchialBrowser-src-20090421.zip)

Apr 21, 2009

Comments

Sep 21, 2010 10:32 AM

Great post but I have another question regarding blogging at world.episerver.com.
How do you post inline code-snippets as you do in this post?

Im using Live Writer but I cant get it to look like that. What sort of plugin do you use?

Thanks
Peter

Magnus Rahl
Magnus Rahl Sep 21, 2010 10:32 AM

I have a "Code snippet" plugin for live writer. I think there is one for attaching files as well but I haven't installed it yet.

Sep 21, 2010 10:32 AM

Thanks, I used another one but it didn't work that well.

error Please login to comment.
Latest blogs
Add more scheduled job settings from the Optimizely CMS 12 admin UI -- with OptiScheduledJob.ExtraParameters

  Optimizely (EPiServer) CMS 12 ships a great scheduled-jobs framework, but it has one frustrating gap: a job has nowhere to store its own...

Binh Nguyen Thi | Jun 25, 2026

Automated Search & Navigation to Graph Migration with Claude Code

A Claude Code plugin that scans your S&N codebase, applies Graph SDK transformations, and validates the result. Install once, run one command. CMS ...

Connor Fortin | Jun 24, 2026

Migrating from Find to Graph: Lessons Learned from a Real CMS 13 Project

While migrating a search solution from Optimizely Search & Navigation (Find) to Optimizely Graph in CMS 13, I encountered several issues that were...

Binh Nguyen Thi | Jun 24, 2026

Optimizely: Upgrade Opti-ID and .NET 10 in CMS 12

Many Optimizely customers are planning their roadmap around a future migration to Optimizely CMS 13. As a result, upgrades such as Opti ID adoption...

Madhu | Jun 23, 2026 |

Understanding Optimizely Graph: Caching, Webhooks & Avoiding Stale Content (Optimizely SaaS CMS)

📌 Scope: This post covers Optimizely CMS (SaaS) only — using the official @optimizely/cms-sdk and @optimizely/cms-cli packages with Next.js 15. If...

Kiran Patil | Jun 23, 2026 |

Optimizely Content APIs: the Setup the Docs Don't Walk You Through

CMS 13 is pushing things firmly in the direction of Optimizely Graph, but plenty of teams are still running on older CMS versions, or have good...

Andre | Jun 22, 2026