Wednesday 8 July 2009

Dynamic Data Custom Field Template – Values List

Here I have created a simple FieldTemplate for a recent project to allow you to give the user a predefined set of text values to choose from without a FK field relationship. An example of this could be a field with optional values of ‘Y’, ‘N’, ‘N/A’ or ‘Y’, ‘N’, ‘Other’ etc.

Requirements

  • Custom Attribute
  • Custom FieldTemplate

I have cover attributes many time on this blog so I’ll just post the code here:

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class ValuesListAttribute : Attribute
{
    public ValuesListAttribute() { }

    /// <summary>
    /// Construvtor take a list of values
    /// to show in the ListControl
    /// </summary>
    /// <param name="values">
    /// A ',' or ';' seperated list of values in the order
    /// that they should appear in the list control.
    /// </param>
    public ValuesListAttribute(String values)
    {
        var items = values.Split(new char[] { ',', ';' });
        Values = (from item in items
                  select new ListItem()
                  {
                      Text = item,
                      Value = item
                  }).ToArray();
    }

    public ListItem[] Values { get; set; }
}

Listing 1 – ValuesListAttribute

[MetadataType(typeof(Value.Metadata))]
partial class Value
{
    internal class Metadata
    {
        public object Id { get; set; }
        [UIHint("ValuesList")]
        [ValuesList("Y,N,N/A")]
        public object TestR { get; set; }
        [UIHint("ValuesList")]
        [ValuesList("Y,N,Other")]
        public object Test { get; set; }
    }
}

Listing 2 – sample metadata

So now to the FieldTemplate we don’t need to have two versions ValuesList and ValuesList_Edit as the data type we will be using this on is always text so the ValuesList version will automatically use the standard Text FieldTemplate.

<%@ Control 
    Language="C#" 
    CodeBehind="ValuesList_Edit.ascx.cs" 
    Inherits="DD_ValuesListFieldTemplate.ValuesList_EditField" %>

<asp:DropDownList 
    ID="DropDownList1" 
    runat="server" 
    CssClass="droplist">
</asp:DropDownList>

<asp:RequiredFieldValidator 
    runat="server" 
    ID="RequiredFieldValidator1" 
    CssClass="droplist" 
    ControlToValidate="DropDownList1" 
    Display="Dynamic" 
    Enabled="false" />

Listing 3 – ValuesList_Edit.ascx

public partial class ValuesList_EditField : System.Web.DynamicData.FieldTemplateUserControl
{
    protected void Page_Load(object sender, EventArgs e)
    {
        SetUpValidator(RequiredFieldValidator1);

        if (DropDownList1.Items.Count == 0)
        {
            // add [Not Set] in insert mode as the field would
            // otherwise just take on the first value in the list
            // if the user clicks save without changing it.
            if (!Column.IsRequired || Mode == DataBoundControlMode.Insert)
            {
                DropDownList1.Items.Add(new ListItem("[Not Set]", ""));
            }

            // fill the dropdown list with the 
            // values in the ValuesList attribute
            PopulateListControl(DropDownList1);
        }
    }

    /// <summary>
    /// Populates a list control with all the values from a parent table.
    /// </summary>
    /// <param name="listControl">The list control to populate.</param>
    protected new void PopulateListControl(ListControl listControl)
    {
        var uiHint = Column.GetAttribute<UIHintAttribute>();
        if (uiHint == null || uiHint.ControlParameters.Count == 0 || !uiHint.ControlParameters.Keys.Contains("ValueList"))
            throw new InvalidOperationException(String.Format(
                "ValuesList for FieldTemplate {0} field is missing from UIHint",
                Column.Name));

        var valuesList = ValuesListAttribute(uiHint.ControlParameters["ValueList"].ToString());
        if (valuesList.Count() == 0)
            throw new InvalidOperationException(String.Format(
                "ValuesList for FieldTemplate {0} field, is empty",
                Column.Name));

        listControl.Items.AddRange(valuesList);
    }

    public ListItem[] ValuesListAttribute(String values)
    {
        var items = values.Split(new char[] { ',', ';' });
        var listItems = (from item in items
                         select new ListItem()
                         {
                             Text = item,
                             Value = item
                         }).ToArray();
        return listItems;
    }

    // added page init to hookup the event handler
    protected override void OnDataBinding(EventArgs e)
    {
        if (Mode == DataBoundControlMode.Edit)
        {
            ListItem item = DropDownList1.Items.FindByValue(FieldValueString);
            if (item != null)
                DropDownList1.SelectedValue = FieldValueString;
        }

        if (Mode == DataBoundControlMode.Insert)
            DropDownList1.SelectedIndex = 0;

        base.OnDataBinding(e);
    }

    /// <summary>
    /// Provides dictionary access to all data in the current row.
    /// </summary>
    /// <param name="dictionary">The dictionary that contains all the new values.</param>
    protected override void ExtractValues(IOrderedDictionary dictionary)
    {
        // If it's an empty string, change it to null
        string val = DropDownList1.SelectedValue;
        if (val == String.Empty)
            val = null;

        dictionary[Column.Name] = ConvertEditedValue(val);
    }

    /// <summary>
    /// Gets the data control that handles the data field in a field template.
    /// </summary>
    /// <value></value>
    /// <returns>
    /// A data control that handles the data field in a field template.
    /// </returns>
    public override Control DataControl
    {
        get
        {
            return DropDownList1;
        }
    }
}

Listing 4 – ListValues_Edit.ascx.cs FieldTemplate

The FieldTemplate has a RequiredFieldValidator on it in case the field is required but as the user will be choosing from a list non of the other valuators make sense. Also note in the extract values method the value is replaced with null if an empty string is selected (the default in insert so no value will be added if the user does not explicitly select one.

In PopulateListControl we have replaced the base class FieldTemplateUserControl’s PopulateListControl with our own that will use the values passed into via the ValuesListAttribute (which is required for the FieldTemplate to function).

So there you have it a simple custom FieldTemplate for those situations where the database either uses a field with the values ‘Y’ and ‘N’ instead of a bit field or where you just want the user to choose from a simple list of possible cases.

Download

10 comments:

Unknown said...

Hello, this is a greate code I'm looking for.
But would you change a litte to handle to have compobox with value and display value. The Value will be save to database and The Display value will be showed to user.

Stephen J. Naughton said...

Hi Binh, have a look at
Custom Field Template – Values List, Take 2 which has an example of what you want.

Steve :D

apostolos said...

thanks for this code..

Stephen J. Naughton said...

Your Welcome :D

tricia said...
This comment has been removed by a blog administrator.
Anonymous said...

I have a DynamicData page that requires the functionality of three cascading dropdownlist boxes. I would like to use the AJAX Toolkit's CascadingDropDown control to extent these but the toolkit control only extends a standard asp:DropDownList control and not a asp:DynamicControl. Would really appreciate any suggestions on where some sample code for doing this might be found.

Thanks

Stephen J. Naughton said...

Hi there, all DynamicControl actually use a FieldTemplate fromt hefolder ~/DynamicData/FieldTemplates so you could extend on of these with the extender that you mentioned.

Steve :D

Anonymous said...

Thank you, very nice indeed! I'll definitely try it out, although thats seems to be a whole lot of code just to avoid using a foreign key.

Stephen J. Naughton said...

The idea was for all those time when a client needs Yes, No, N/A type fields and they want 5, 10 or even 20 on a form, with this your not having to create all those relationships just a UIHint.

Steve :D

PapRoch said...

hey, i tried your code in DD EF 4 and it is working fine but i don't want the value to store in the database as the selected option but rather the code it references. Not sure if i am using the right thing here. Basically i have a column called doc type which has String enum eg- BL means Bill of Lading. I want to show the used Bill of Lading in a drop down box but store as BL in the database. I also want the gridview to show Bill of lading. So basically it works like a foreignkey template but it isn't. Any help will be greatly appreciated