Tuesday, 2 February 2010

Applying CSS Styling via Metadata

In answer to a post on the ASP.Net Dynamic Data Forum from Luigi here I though I should post my solution. The basic issue was dealt with here by Rick Anderson I decided to implement a version that used Attributes and a simple extension method to apply the attribute values:

Firstly here is the attribute, it just handles one class name.

/// <summary>
/// Used to apply css classes to the cell 
/// that encloses a field template.
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class CssAttribute : Attribute
{
    public String Class { get; private set; }

    public CssAttribute()
    {
    }

    public CssAttribute(String css)
    {
        Class = css;
    }
}

Listing 1 – CSS Attribute

There are two extension methods I use to implement the CssAttribute, the first Listing 2 is used in the Field Template  is a helper that I use in many other places; I use it to get the first parent control of a Type DetailsView GridView etc. here I’m use it to get the parent DataControlFieldCell or TD/cell of the table, obviously if you are using FormView or ListView you will need to specify the relevant container control.

/// <summary>
/// Gets the container control.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="control">The control.</param>
/// <returns></returns>
public static T GetContainerControl<T>(this Control control) where T : Control
{
    // get current parent
    var parentControl = control.Parent;
    while (parentControl != null)
    {
        var p = parentControl as T;
        if (p != null)
        {
            // return found control
            return p;
        }
        else
        {
            // get next parent
            parentControl = parentControl.Parent;
        }
    }
    // if no control found return null
    return null;
}

Listing 2 – GetContainerControl

The next Listing 3 actually applies the attribute by first calling the GetParentControl helper to get the containing cell/TD then extracts the class attribute and applies the new class either by appending or assigning.

/// <summary>
/// Applies the CSS.
/// </summary>
/// <param name="control">The control.</param>
/// <param name="Column">The column.</param>
public static void ApplyCss(this FieldTemplateUserControl control, MetaColumn Column)
{
    // get the attribute
    var css = Column.GetAttribute<CssAttribute>();
    // get parent control
    var parentCell = control.GetContainerControl<DataControlFieldCell>();

    // make sure we have the attribute and parent control
    if (css != null && parentCell != null)
    {
        // test for the presence of a class attribute on the cell
        var cssClass = parentCell.Attributes["class"];
        if (cssClass != null)
        {
            // add the extra class
            cssClass += " " + css.Class;
        }
        else
        {
            // assign the class
            parentCell.Attributes.Add("class", css.Class);
        }
    }
}

Listing 3 – ApplyCss method

You will also notice the GetAttribute extension method which I wont go into as you can find details on this site here Writing Attributes and Extension Methods for Dynamic Data

Some sample Metadata showing adding cantering to a single column:

[MetadataType(typeof(CategoryMetadata))]
public partial class Category
{
    internal class CategoryMetadata
    {
        public Object CategoryID { get; set; }
        [Css("CenterCell")]
        public Object CategoryName { get; set; }
        public Object Description { get; set; }
        public Object Picture { get; set; }
        // Entity Set 
        public Object Products { get; set; }
    }
}

Listing 4 – CssAttribute applied

Appled Css attribute

Figure 1 – applied CSS attribute

Download

Happy coding Big Grin

2 comments:

Anonymous said...

Why would you ever want to mix up presentation logic with your domain logic?

Stephen J. Naughton said...

Simply put because people ask for it and it keeps the model DRY as you are not putting this setting in many different places, you could concivable have two buddy classes one for domain logic and one for presentation logic, but the benfit it you are not adding this to many pages/templates.

Steve