Pages

Tuesday, March 24, 2015

List Enum Types and Values in C# - Ektron Sample

Sometimes when you are working with an API you run across a scenario where the documentation doesn't outline exactly what you need, but you know it's there in the code. I have come across this scenario in several cases while working with Enumerations, specifically, where I knew I needed one specific value, maybe two, but I was uncertain exactly which ones they were.

Thankfully, with Visual Studio you can type a part of the Namespace, Class, or Enumeration, and then scroll through the list to see your choices for Enum types or values in Intellisense. Sometimes, though, that's a bit slow for finding exactly what's needed; especially when dealing with a list of over 150 options. This isn't always the case, but in those instances where I have a large list housed in a Namespace, Class, or two, I would prefer an easier method to visualize them, and maybe look through them.

My Solution

Lately I have been working with two CMS APIs that have allowed me to use an Enum to filter objects in a query, but I haven't always had a clear picture of what my choices were. To make this easier on myself, I decided to put together a simple WebForm page with a ListView databound to a DataTable of Enums and their values. I say simple, but I did take some extra steps to add a little filtering option to the UI.

The code sample below is from my use of this with the Ektron CMS API, the latest situation where I encountered this need. Thankfully, in this API most of the Enumerations I was working with are housed in a class named Ektron.Cms.Common.EkEnumeration, which made this easier. I didn't have to combine a bunch of lists from various classes, though this is just one use case and you might need to do so to get a complete picture depending on your needs.

This solution uses reflection to get the Enum types in a specified class, along with the Values of each type while enumerating through the Type Array. Additionally, the System.Enum class in .NET provides a GetUnderlyingType method that allows us to get the underlying non-string value of the Enum value. By default, the underlying type of any Enum is int, but it could be defined as one of several supported types. Read more here. In the case of Ektron, the underlying values are int or long.
[pre class="brush:csharp"]
// define my DataTable and columns - very simple structure
DataTable dt = new DataTable();
dt.Columns.Add("Name");
dt.Columns.Add("Values");
// get types and filter to only Enumerations
var group = typeof(Ektron.Cms.Common.EkEnumeration).GetNestedTypes();
var filteredItems = group.Where(gi => gi.IsEnum);
// loop through Enum types returned, in alphabetical order
foreach (var item in filteredItems.OrderBy(i => i.Name)) {
    StringBuilder values = new StringBuilder();
    foreach (var subitem in Enum.GetValues(item)) {
        // add a string for each value - this makes styling easier
        values.AppendFormat("<span class=\"enumValue\">{0} ({1})</span>", subitem.ToString(),
            Convert.ChangeType(subitem, Enum.GetUnderlyingType(item)).ToString());    // this returns the non-string value of the enum value
    }
    dt.Rows.Add(item.Name, values.ToString());    // add my row to the DataTable
}
// bind table to listview
lvEnums.DataSource = dt;
lvEnums.DataBind();
[/pre]
In my sample you can see my enumeration of the Array of Types, filteredItems, is sorted alphabetically for easier reading. Contained within that foreach loop is another foreach to enumerate the Enum values. I wrapped each value in a span for the final output to be styled easier.

Also, for this to work for Enums defined in a namespace, some decent changes have to be implemented in the way the types are retrieved, so this code only works for Enums defined in a class.
 

A Step Further

As mentioned above, this DataTable is being bound to a ListView. This has a ton of benefits over the DataGrid, the most important to me being control of the markup. If you're developing using WebForms and are not using ListView yet over DataList, GridView, or Repeater, please read up on it.

Now, with the Ektron API there are 182 Enums defined just in the EkEnumeration class. This creates a long list to scroll through, and my goal is to make this easier. So, instead of stopping at the DataBind, I decided to employ a bit of jQuery to create a sort-of search filter. With a little help from a keyup event handler, I toggle the visibility of any rows matching what was typed in the box. Additionally, a JavaScript RegEx helps me highlight the matching text within the name column and the "enumValue" spans in the value column.
[pre class="brush:jscript" title="toggleRows Function"]
function toggleRows(str) {
    // this removes any existing match wrappers
    rows.find('.strMatch').replaceWith(function () {
        return $(this).text();
    });
    // only go through all this if we have a value
    if (str != '') {
        var trs = rows.filter(':icontains("' + str + '")');
        trs.children('td:first-child').add(trs.find('.enumValue')).each(function () {
            var $t = $(this);
            var regX = new RegExp('(' + str + ')', 'gi');
            var repStr = $t.text().replace(regX, '&lt;span class="strMatch"&gt;$1&lt;/span&gt;');
            $t.html(repStr);
        });
        rows.hide();
        trs.show();
    }
        // otherwise they all show
    else {
        rows.show();
    }
    // maintain my selected letter filter
    toggleLetters($('.letter-container .active'));
}
[/pre]
I also took it a step further than that, and added a filter by starting letter. You will see a call to this method above on the last line of the script.
[pre class="brush:jscript" title="toggleLetters Function"]
function toggleLetters($ele) {
    // only do something if we have an element ref
    if ($ele.length &gt; 0) {
        var $t = $ele;
        // this helps preserve matched items
        var matched = false;
        if (rows.find('.strMatch').length &gt; 0) {
            matched = true;
        }
        // remove active from other letters possibly selected
        $('.letter-container .letter.active').not($t).removeClass('active');
        // if this letter selected
        if ($t.hasClass('active')) {
            rows.hide();    // reset rows
            // filter rows to those matching letter
            var matchNames = names.filter(function () {
                return $(this).data('letter') == $t.text();
            });

            if (matched) {
                // this should only show matches rows matching letter
                var ps = matchNames.parent().has('.strMatch');
                ps.show();  
            }
            else {
                matchNames.parent().show();    // show all matching letter
            }
        }
        else {    // no letter selected
            if (matched) {
                rows.has('.strMatch').show();    // only show matches
            }
            else {
                rows.show();    // show all
            }
        }
    }
}
[/pre]
This toggleLetters function allows me to filter the table by Enum types that start with the selected letter. It's bound to a click handler on my letters, which are added based on the names loaded in my table using a jQuery.ready function. The function also takes into account any matches that are highlighted, so I keep my search filter in place, and my matches highlighted. This makes it very efficient for me to type in something to search for, then filter by a letter I know the Enum I need starts with.

The final look, searched and filtered:

You can check out the working example of this via the attached download. Again, this example is listing the Enums for an Ektron project I was working with, but you can easily change the class referenced to one you need to list from.

Download Example

No comments:

Post a Comment