Custom Form Control To Select A Folder In Media Library

I had a need to have the ability to select a folder from within the site's media library. Not a file. A folder. The idea behind this requirement was to allow the site administrator to upload a bunch of images to a single directory in the media library, so that the contents (in this case images) could be output to the page.

Unfortunately, after contacting Kentico support, I was told that such a folder selector control does not exist and I would need to create one myself. So I did exactly that!

Step 1: Create A New User Control

I have created a user control in "/CMSFormControls/Surinder/" of my Kentico installation. I have named the user control: FolderSelector.ascx.

HTML

<table>
        <tr>
            <td class="EditingFormValueCell">
                <asp:TreeView ID="MediaLibraryTree" SelectedNodeStyle-BackColor="LightGray" ExpandDepth="0" ImageSet="Arrows" runat="server"></asp:TreeView>
            </td>
        </tr>
</table>

Code-behind

using CMS.CMSHelper;
using CMS.FormControls;
using CMS.GlobalHelper;
using CMS.MediaLibrary;
using CMS.SettingsProvider;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.Script.Serialization;
using System.Web.UI;
using System.Web.UI.WebControls;

public partial class CMSFormControls_iSurinder_FolderSelector : FormEngineUserControl
{
    private string _Value;

    public override Object Value
    {
        get
        {
            return MediaLibraryTree.SelectedValue;
        }
        set
        {
            _Value = System.Convert.ToString(value);
        }
    }

    public string MediaLibraryPath
    {
        get
        {
            //Get filter where condition format or default format
            return DataHelper.GetNotEmpty(GetValue("MediaLibraryPath"), String.Empty);
        }
        set
        {
            SetValue("MediaLibraryPath", value);
        }
    }

    public override bool IsValid()
    {
        bool isControlValid = true;

        if ((FieldInfo != null) && !FieldInfo.AllowEmpty)
        {
            this.ValidationError = "Please select an Image Gallery directory";
            isControlValid = false;
        }

        return isControlValid;
    }

    protected void EnsureItems()
    {
        if (MediaLibraryPath != String.Empty)
        {
            string fullPath = Server.MapPath(String.Format("/{0}", MediaLibraryPath));

            if (Directory.Exists(fullPath))
            {
                DirectoryInfo rootDir = new DirectoryInfo(fullPath);

                TreeNode treeNodes = OutputDirectories(rootDir, null);

                MediaLibraryTree.Nodes.Add(treeNodes);
            }
            else
            {
                this.ValidationError = "Directory path does not exist.";
            }
        }
        else
        {
            this.ValidationError = "Properties for this control have not been set.";
        }
    }

    protected void Page_Load(object sender, EventArgs e)
    {
        if (!Page.IsPostBack)
            EnsureItems();
    }
    
    TreeNode OutputDirectories(DirectoryInfo directory, TreeNode parentNode)
    {
        if (directory == null)
            return null;

        //Create a node for directory
        TreeNode dirNode = new TreeNode(directory.Name, directory.FullName);

        //Get subdirectories of the current directory
        DirectoryInfo[] subDirectories = directory.GetDirectories();

        //Mark node as selected
        if (dirNode.Value == _Value.ToString())
            dirNode.Selected = true;

        //Get all subdirectories
        for (int d = 0; d < subDirectories.Length; d++)
            OutputDirectories(subDirectories[d], dirNode);
                
        //If the parent node is null, return this node
        //otherwise add this node to the parent and return the parent
        if (parentNode == null)
        {
            return dirNode;
        }
        else
        {
            parentNode.ChildNodes.Add(dirNode);

            return parentNode;
        }
    }
}

I'm hoping that the code I've shown above is quite self-explanatory. But the only thing you need to be aware of is the "MediaLibraryPath" public property. You will see in the next steps that we will be using this property to contain a link to where our Media Library resides.

Step 2: Add New Control To Kentico

When creating a custom form control in Kentico, ensure we have the following form control settings:

Kentico Folder Selector Settings

Step 3: Add Form Control Property

Remember, from Step 1, we had a property called "MediaLibraryPath". Now we just need to create this property in our custom control settings.

Kentico Folder Selector Settings

Now, when our Folder Selector control is added to a document, we will need enter a map path to the location of our Media Library. For example, "/Surinder/media/Surinder/".

If this custom control has been implemented successfully, you should see something like this when creating a new page based on a document type:

Kentico Folder Selector Tree

ASP.NET CheckBoxList Control With Value Attribute

ASP.NET server controls is a great way to quickly build a page with dynamic functionality. Even though we do not have much of direct control over the way these controls are rendered, they do a pretty good job and its not very often I get annoyed with them.

Until now.

Generally, I find myself using the .Attributes.Add() method when needing to add additional attributes to certain server controls. No problem! In this case, I wanted to add a “value” attribute that will contain the record ID for that checkbox. I can then use this value within my JavaScript. I would have thought a value attribute would already be there. Its perfectly valid HTML mark-up:

<form>
    <input type="checkbox" name="vehicle" value="Volvo" />
    <input type="checkbox" name="vehicle" value="Volkswagen" />
</form> 

For some reason, when I tried to add my custom attributes after my CheckBoxList was databound (as shown below), the attribute was simply ignored.

NewsCheckList.Items[0].Attributes["value"] = "1";
NewsCheckList.Items[1].Attributes["value"] = "2";
NewsCheckList.Items[2].Attributes["value"] = "3";

So I decided the best way forward would be to create a custom CheckBoxList control that would contain a value attribute. I based my code from an old (but very useful) article that can be found here.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.UI.WebControls;
using System.IO;
using System.Web.UI;
using System.Collections;
using System.ComponentModel;

namespace Site.WebControls
{
    [DefaultProperty("Text"),
    ToolboxData("<{0}:CheckBoxValueList runat=server></{0}:CheckBoxValueList>")]
    public class CheckBoxValueList : CheckBoxList
    {
        protected override void Render(HtmlTextWriter writer)
        {
            StringBuilder sb = new StringBuilder();
            TextWriter tw = new StringWriter(sb);
            
            HtmlTextWriter originalStream = new HtmlTextWriter(tw);
            base.Render(originalStream);
            string renderedText = sb.ToString();

            int start = 0;
            int labelStart = 0;
            int end = renderedText.Length;

            for (int i = 0; i < this.Items.Count; i++)
            {
                StringBuilder itemAttributeBuilder = new StringBuilder();

                end = renderedText.Length;
                start = renderedText.IndexOf("<input", start, end - start);
                labelStart = renderedText.IndexOf("<label", start, end - start);

                this.Items[i].Attributes.Render(new HtmlTextWriter(new StringWriter(itemAttributeBuilder)));

                renderedText = renderedText.Insert(labelStart + 7, itemAttributeBuilder.ToString() + " ");
                renderedText = renderedText.Insert(start + 7, String.Format("{0} value=\"{1}\" ", itemAttributeBuilder.ToString(), this.Items[i].Value));
                start = renderedText.IndexOf("/>", start, renderedText.Length - start);
            }
            
            writer.Write(renderedText);
        }
    }
}