Showing posts with label Custom Field Templates. Show all posts
Showing posts with label Custom Field Templates. Show all posts

Tuesday, 17 August 2010

A Boolean Date Time Field Template for Dynamic Data 4

Here’s the idea, you want to mark a status of a row with a checkbox and you also need the date time setting one the row to record when the status changed. You normally end up with two fields in your table for this. I decided that in many cases I would only need the date time i.e. if the date time is set then the status has changed is null then not, e.g. to set Order Status to complete set the Complete column to DateTime.Now.

Another reason you may want to use a checkbox to enter a date is to set the date without letting the user enter a custom date.

This turned out to be a little more complicated than I first though and I needed to consult David Ebbo at Microsoft to get the answer. First we’ll deal with the issue I came across that at first was a road block. This is the issue of setting the field to null when the user unchecks the check box.

<%@ Control
    Language="C#" 
    CodeBehind="DateTimeBoolean_Edit.ascx.cs" 
    Inherits="CustomFieldTemplates.DateTimeBoolean_EditField" %>

<asp:CheckBox runat="server" ID="CheckBox1" />
<asp:HiddenField ID="HiddenField1" runat="server" />

Listing 1 – DateTimeBoolean_Edit.ascx

So here in Listing 2 we see the first ExtractValues attempt

protected override void ExtractValues(IOrderedDictionary dictionary)
{
    if (!CheckBox1.Checked)
    {
        // only set to null if unchecked
        dictionary[Column.Name] = String.Empty;
    }
    else if (String.IsNullOrEmpty(HiddenField1.Value))
    {
        // only set a value if there is no current value
        dictionary[Column.Name] = DateTime.Now;
    }
}

Listing 2 – ExtractValues method v1

As you can see if the CheckBox1 is unchecked I set the return value to String.Empty this you would think would do the trick but it doesn't what happens is the value is retained. What confused the matter for me was is if I have a new row or a row with a null value and check it, it sets the value Surprise

It turns out that the reason is that the ExtractValues method is called twice the first time when the field template is initialised and the second time when the updated value is returned. But if look at my logic it returns no value the first time the method is called, but a value is required for the system to know that it is to be set to null the second time it is called Big Grin

So here the full code behind of the Edit template, you will see now that the hidden field is set in the OnDataBound event to make sure we return the correct value the first time ExtractValues is called.

public partial class DateTimeBoolean_EditField : System.Web.DynamicData.FieldTemplateUserControl
{
    protected override void OnDataBinding(EventArgs e)
    {
        base.OnDataBinding(e);

        // keep current value
        HiddenField1.Value = FieldValueString;

        if (FieldValue != null)
            CheckBox1.Checked = true;
        else
            CheckBox1.Checked = false;
    }

    protected override void ExtractValues(IOrderedDictionary dictionary)
    {
        if (!CheckBox1.Checked)
        {
            // only set to null if unchecked
            dictionary[Column.Name] = String.Empty;
        }
        else if (String.IsNullOrEmpty(HiddenField1.Value))
        {
            // only set a value if there is no current value
            dictionary[Column.Name] = DateTime.Now;
        }
        //else
        //    dictionary[Column.Name] = ConvertEditedValue(HiddenField1.Value);
    }

    public override Control DataControl
    {
        get { return CheckBox1; }
    }
}

Listing 3 – DateTimeBoolean_Edit.ascx.cs

And for completeness the read-only version of the field template.

<%@ Control
    Language="C#" 
    CodeBehind="DateTimeBoolean.ascx.cs" 
    Inherits="CustomFieldTemplates.DateTimeBooleanField" %>

<asp:CheckBox ID="CheckBox1" runat="server" Enabled="false" />

Listing 4 – DateTimeBoolean.ascx

public partial class DateTimeBooleanField : System.Web.DynamicData.FieldTemplateUserControl
{
    protected override void OnDataBinding(EventArgs e)
    {
        base.OnDataBinding(e);
        CheckBox1.Checked = !String.IsNullOrEmpty(FieldValueString);
        if (FieldValue != null)
            CheckBox1.ToolTip = FieldValueString;
    }

    public override Control DataControl
    {
        get { return CheckBox1; }
    }
}

Listing 5 – DateTimeBoolean.ascx.cs

You may want to show a date time value instead of the checkbox if so you can just use a copy of the default read-only Date Time field template.

And remember to add a UIHint to set the column to use the new field template

[UIHint("DateTimeBoolean")]
public Nullable<DateTime> ShippedDate { get; set; }

As always happy coding

Tuesday, 28 July 2009

Dynamic Data Custom Field Template – Values List, Take 2

An alternative way of doing the Dynamic Data Custom Field Template – Values List this version uses the UIHint’s ControlParameters property to pass in values to the control.

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

Listing 1 – Metadata using the UIHint’s ControlParameters

See the example of using UIHint’s ControlParameters in Listing 1. In the above example you can see that I am passing in the ValueList in the UIHint’s ControlParameters as a key value pair.

/// <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);
}

Listing 2 – PopulateListControl

In Listing 2 you can see the extraction of the ValuesList from the UIHint’s ControlParameters collection.

So Listings 1 & 2 will get you the same as the previous article but without the new attribute.

Take 2

In take two of this control binh said “But would you change a little to handle to have combobox with value and display value. The Value will be save to database and The Display value will be showed to user.” so I thought I would add this version here.

My personal view is this should be done either via an FK relationship or a Enum value. (Both my Your First FieldTemplate and Dynamic Data Preview 4 have an Enum FieldTemplate) but here goes.

In the Edit FieldTemplate this is all you need to do is modify the ValuesList method.

public ListItem[] GetValuesList(String values)
{
    var items = values.Split(new char[] { ',', ';' });
    var listItems = (from item in items
                     select new ListItem()
                     {
                         Text = item.Split(new char[] { '|' })[1],
                         Value = item.Split(new char[] { '|' })[0]
                     }).ToArray();
    return listItems;
}
Listing 3 – updated ValuesListAttribute
[MetadataType(typeof(Value.Metadata))]
partial class Value
{
    internal class Metadata
    {
        public object Id { get; set; }
        [UIHint("ValuesList", null, "ValueList", "1|Y,2|N,3|N/A")]
        public object TestR { get; set; }
        [UIHint("ValuesList", null, "ValueList", "1|Y,2|N,3|Other")]
        public object Test { get; set; }
    }
}

Listing 4 – sample metadata

Here you can see that the values and text are seperated by the pipe symbol ‘|’. However having done this you would now need to create a ValuesList (ReadOnly) FieldTemplate to get the ValuesList and the match the value in the field and then display the correct value, which in my opinion would be quite messy, so here I will stick to using Enums.

Download

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

Tuesday, 2 June 2009

HTML Editor FieldTemplate for Dynamic Data Using AJAX Control Toolkit Editor

I decided to create an HTML Edit FieldTemplate after the AJAX Control Toolkit HTML Editor was released and a post (Edit.aspx page hanging on text entry) on the ASP.Net Dynamic Data Forum was posted.

For this we will need two FieldTemplates:

  1. Html.ascx
  2. Html_Edit.ascx

The first of the two Html.ascx will display the HTML rendered correctly and the second will use the AJAX Control Toolkit HTML Editor.

The Normal FieldTemplate

<%@ Control 
    Language="C#" 
    Inherits="DD_HTMLEditor.Html_Field" 
    Codebehind="Html.ascx.cs" %>

<asp:Literal 
    runat="server" 
    ID="Literal1" 
    Text="<%# FieldValueEditString %>" />

Listing 1 – Html.ascx

using System;
using System.Web.UI;

namespace DD_HTMLEditor
{
    public partial class Html_Field : System.Web.DynamicData.FieldTemplateUserControl
    {
        public override Control DataControl
        {
            get
            {
                return Literal1;
            }
        }
    }
}

Listing 2 – Html.ascx.cs

As you can see this is a direct copy of the Text.ascx FieldTemplate the only change is to Literal1 in the ascx file instead of FieldValueString we are using FieldValueEditString.

Description Field Showing HTML

Figure 1 – Description Field Showing HTML

The Edit FieldTemplate

<%@ Control 
    Language="C#" 
    Inherits="DD_HTMLEditor.Html_EditField" 
    Codebehind="Html_Edit.ascx.cs" %>
    
<%@ Register 
    Assembly="AjaxControlToolkit" 
    Namespace="AjaxControlToolkit.HTMLEditor"
    TagPrefix="cc1" %>

<cc1:Editor 
    ID="Editor1" 
    Content='<%# FieldValueEditString %>'
    runat="server" />
    
<asp:RequiredFieldValidator 
    runat="server" 
    ID="RequiredFieldValidator1" 
    CssClass="droplist" 
    ControlToValidate="Editor1" 
    Display="Dynamic" 
    Enabled="false" />
    
<asp:RegularExpressionValidator 
    runat="server" 
    ID="RegularExpressionValidator1" 
    CssClass="droplist" 
    ControlToValidate="Editor1" 
    Display="Dynamic" 
    Enabled="false" />
    
<asp:DynamicValidator 
    runat="server" 
    ID="DynamicValidator1" 
    CssClass="droplist" 
    ControlToValidate="Editor1" 
    Display="Dynamic" />

Listing 3 – Html_Edit.ascx file

using System;
using System.Collections.Specialized;
using System.Web.UI;

namespace DD_HTMLEditor
{
    public partial class Html_EditField : System.Web.DynamicData.FieldTemplateUserControl
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            SetUpValidator(RequiredFieldValidator1);
            RequiredFieldValidator1.Text = "*";
            SetUpValidator(RegularExpressionValidator1);
            RegularExpressionValidator1.Text = "*";
            SetUpValidator(DynamicValidator1);
            DynamicValidator1.Text = "*";
        }

        protected override void ExtractValues(IOrderedDictionary dictionary)
        {
            dictionary[Column.Name] = ConvertEditedValue(Editor1.Content);
        }

        public override Control DataControl
        {
            get
            {
                return Editor1;
            }
        }
    }
}

Listing 4 – Html_Edit.ascx.cs file

Here again it’s just the Text_Edit FieldTemplate with a few minor changes (shown in BOLD ITALIC). First we added the AJAC Control Toolkit HTML Editor control to replace the TextBox control and added a reference to the FieldTemplate.

Note:

You could also add a reference for the AJAX Control Toolkit to the web.config

<pages>
    <controls>
        <add tagPrefix="ajaxToolkit"
            namespace="AjaxControlToolkit"
            assembly="AjaxControlToolkit"/>
    </controls>
</pages>

Description Field in Edit mode

Figure 2 – Description Field in Edit mode

Next Steps

Next we need to add some of the configurability of the HTML Editor via a custom Attribute.
using System;
using System.Linq;
using System.Web.DynamicData;
using AjaxControlToolkit.HTMLEditor;

namespace DD_HTMLEditor
{
    /// <summary>
    /// Attribute to identify which column to use as a 
    /// parent column for the child column to depend upon
    /// </summary>
    [AttributeUsage(AttributeTargets.Property)]
    public class HtmlEditorAttribute : Attribute
    {
        /// <summary>
        /// Default Contructor
        /// </summary>
        public HtmlEditorAttribute()
        {
        }

        /// <summary>
        /// Active editing panel (Design, Html, Preview) on control loaded 
        /// </summary>
        public AjaxControlToolkit.HTMLEditor.ActiveModeType ActiveMode { get; set; }

        /// <summary>
        /// If true, editing panel is focused and cursor is set 
        /// inside it ("Design" or "HTML text") on initial load 
        /// or editing panel change
        /// </summary>
        public Boolean AutoFocus { get; set; }

        /// <summary>
        /// A css class override used to define a custom look 
        /// and feel for the HTMLEditor. See the HTMLEditor 
        /// Theming section for more details
        /// </summary>
        public String CssClass { get; set; }

        /// <summary>
        /// Sets the path of additional CSS file used for 
        /// HTMLEditor's content rendering in "Design" panel. 
        /// If not set, the default CSS file is used which is 
        /// embedded as a WebResource and is a 
        /// part of the Toolkit assembly
        /// </summary>
        public String DesignPanelCssPath { get; set; }

        /// <summary>
        /// Sets the path of CSS file used for HTMLEditor's 
        /// content rendering in "Design" and "Preview" panels. 
        /// If not set, the default CSS file is used which is 
        /// embedded as a WebResource and is a part of the 
        /// Toolkit assembly
        /// </summary>
        public String DocumentCssPath  { get; set; }

        /// <summary>
        /// A css class override used to define a custom look for the 
        /// HTMLEditor's "HTML text" mode panel. See the HTMLEditor 
        /// Theming section for more details
        /// </summary>
        public String HtmlPanelCssClass  { get; set; }

        /// <summary>
        /// If true, Tab key navigation is suppressed inside 
        /// HTMLEditor control 
        /// </summary>
        public Boolean IgnoreTab  { get; set; }

        /// <summary>
        /// If true, HTMLEditor's content is cleaned up on 
        /// initial load. MS Word specific tags are removed 
        /// </summary>
        public Boolean InitialCleanUp  { get; set; }

        /// <summary>
        /// If true, JavaScript code is suppressed 
        /// in HTMLEditor's content 
        /// </summary>
        public Boolean NoScript  { get; set; }

        /// <summary>
        ///  If true, all Unicode characters in 
        ///  HTML content are replaced with &#code;
        /// </summary>
        public Boolean NoUnicode { get; set; }

        /// <summary>
        /// The client-side script that executes after 
        /// active mode (editing panel) changed
        /// </summary>
        public String OnClientActiveModeChanged { get; set; }

        /// <summary>
        /// The client-side script that executes before 
        /// active mode (editing panel) changed
        /// </summary>
        public String OnClientBeforeActiveModeChanged { get; set; }

        /// <summary>
        /// If true, no white spaces inserted on Tab key 
        /// press in Design mode. Default Tab key navigation 
        /// is processing in this case 
        /// </summary>
        public Boolean SuppressTabInDesignMode { get; set; }

        /// <summary>
        /// Sets the height of the body of the HTMLEditor 
        /// </summary>
        public int Height { get; set; }

        /// <summary>
        /// Sets the width of the body of the HTMLEditor 
        /// </summary>
        public int Width { get; set; }
    }

    public static partial class HelperExtansionMethods
    {
        /// <summary>
        /// Get the attribute or a default instance of the attribute
        /// if the Column attribute do not contain the attribute
        /// </summary>
        /// <typeparam name="T">
        /// Attribute type
        /// </typeparam>
        /// <param name="table">
        /// Column to search for the attribute on.
        /// </param>
        /// <returns>
        /// The found attribute or a default 
        /// instance of the attribute of type T
        /// </returns>
        public static T GetAttributeOrDefault<T>(this MetaColumn column) where T : Attribute, new()
        {
            return column.Attributes.OfType<T>().DefaultIfEmpty(new T()).FirstOrDefault();
        }
    }
}

Listing 5 – HtmlEditorAttribute

var htmlAttr = Column.GetAttributeOrDefault<HtmlEditorAttribute>();
if (htmlAttr.ActiveMode != ActiveModeType.Design)
    Editor1.ActiveMode = htmlAttr.ActiveMode;
if (htmlAttr.AutoFocus)
    Editor1.AutoFocus = true;
if (htmlAttr.CssClass != null)
    Editor1.CssClass = htmlAttr.CssClass;
if (htmlAttr.DesignPanelCssPath != null)
    Editor1.DesignPanelCssPath = htmlAttr.DesignPanelCssPath;
if (htmlAttr.DocumentCssPath != null)
    Editor1.DocumentCssPath = htmlAttr.DocumentCssPath;
if (htmlAttr.Height > 0)
    Editor1.Height = htmlAttr.Height;
if (htmlAttr.HtmlPanelCssClass != null)
    Editor1.HtmlPanelCssClass = htmlAttr.HtmlPanelCssClass;
if (htmlAttr.IgnoreTab)
    Editor1.IgnoreTab = true;
if (htmlAttr.InitialCleanUp)
    Editor1.InitialCleanUp = true;
if (htmlAttr.NoScript)
    Editor1.NoScript = true;
if (htmlAttr.NoUnicode)
    Editor1.NoUnicode = true;
if (htmlAttr.OnClientActiveModeChanged != null)
    Editor1.OnClientActiveModeChanged = htmlAttr.OnClientActiveModeChanged;
if (htmlAttr.OnClientBeforeActiveModeChanged != null)
    Editor1.OnClientBeforeActiveModeChanged = htmlAttr.OnClientBeforeActiveModeChanged;
if (htmlAttr.SuppressTabInDesignMode)
    Editor1.SuppressTabInDesignMode = true;
if (htmlAttr.Width > 0)
    Editor1.Width = htmlAttr.Width;

Listing 6 – Attribute setting for Html_Edit.ascx

using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using AjaxControlToolkit.HTMLEditor;

namespace DD_HTMLEditor.Models
{
    [MetadataType(typeof(CategoryMD))]
    public partial class Category 
    {
        public partial class CategoryMD
        {
            public object CategoryID {get;set;}
            public object CategoryName {get;set;}

            [UIHint("Html")]
            [HtmlEditor(
                ActiveMode=ActiveModeType.Preview,
                Width=200,
                Height=100
                )]
            public object Description {get;set;}
            public object Picture {get;set;}
            public object Products {get;set;}
        }
    }
}

Listing 7 – Metadata

Above we have the attribute Listing 5 and the code Listing 6 to set the properties of the Html Editor in the Html_Edit.ascx FieldTemplate’s Page_Load event, and finally Listing 7 sample metadata.

Attribute from the Metadata applied

Figure 3 – Attribute from the Metadata applied

Not Rocket Science but a cool Html Editor (Thanks AJAX Toolkit Guys).

download

Wednesday, 20 May 2009

Password FieldTemplates for Dynamic Data (UPDATED)

See this thread on the Dynamic Data Forum where I suggested you could use [DataType(DataType.Password)] attribute to mark the field as a password field and alternative would be to create a custom FieldTemplate and use [UIHint("Password")] to set it.

This just means the extra logic I implement for the password is only used on password fields and not all Text fields.

<%@ Control 
    Language="C#" 
    CodeBehind="Password.ascx.cs" 
    Inherits="DD_EF4_PasswordField.PasswordField" %>

<asp:Literal runat="server" ID="Literal1" />

Listing 1 – Password.ascx

using System;
using System.Web.UI;

namespace DD_EF4_PasswordField
{
    public partial class PasswordField : System.Web.DynamicData.FieldTemplateUserControl
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            // add a string of '*' the max length of the field
            var length = Column.MaxLength > 20 ? 20 : Column.MaxLength;
            Literal1.Text = new String('*', length);
        }

        public override Control DataControl
        {
            get
            {
                return Literal1;
            }
        }
    }
}

Listing 2 – Password.ascx.cs

Listing 1 & 2 represent the Password (Normal) FieldTemplate.

This is FieldTemplate just displays a list of '*' the max length of the field instead of the actual text.

<%@ Control 
    Language="C#" 
    CodeBehind="Password_Edit.ascx.cs" 
    Inherits="DD_EF4_PasswordField.Password_EditField" %>

<asp:TextBox 
    ID="TextBox1" 
    runat="server" 
    TextMode="Password"
    CssClass="DDTextBox" >
</asp:TextBox>

<asp:RequiredFieldValidator 
    runat="server" 
    ID="RequiredFieldValidator1"
    CssClass="DDControl" 
    ControlToValidate="TextBox1" 
    Display="Dynamic" 
    Enabled="false" />
<asp:RegularExpressionValidator 
    runat="server" 
    ID="RegularExpressionValidator1" 
    CssClass="DDControl" 
    ControlToValidate="TextBox1" 
    Display="Dynamic" 
    Enabled="false" />
<asp:DynamicValidator 
    runat="server" 
    ID="DynamicValidator1" 
    CssClass="DDControl" 
    ControlToValidate="TextBox1" 
    Display="Dynamic" />

Listing 3 – Password_Edit.ascx

using System;
using System.Collections.Specialized;
using System.Web.DynamicData;
using System.Web.UI;

namespace DD_EF4_PasswordField
{
    public partial class Password_EditField : FieldTemplateUserControl
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            if (Column.MaxLength < 20)
            {
                TextBox1.Columns = Column.MaxLength;
            }
            TextBox1.ToolTip = Column.Description;

            SetUpValidator(RequiredFieldValidator1);
            SetUpValidator(RegularExpressionValidator1);
            SetUpValidator(DynamicValidator1);
        }

        protected override void OnDataBinding(EventArgs e)
        {
            base.OnDataBinding(e);
            TextBox1.MaxLength = Math.Max(FieldValueEditString.Length, Column.MaxLength);
        }

        protected override void ExtractValues(IOrderedDictionary dictionary)
        {
            if (TextBox1.Text.Length > 0)
                dictionary[Column.Name] = ConvertEditedValue(TextBox1.Text);
        }

        public override Control DataControl
        {
            get
            {
                return TextBox1;
            }
        }
    }
}

Listing 4 – Password_Edit.ascx.cs

Listings 3 & 4 are for the Edit version Password_Edit FieldTemplate.

Updated: Listing 4 has been updated with the line if (TextBox1.Text.Length > 0) so that in edit mode if you do not enter a value it will not return an empty string.

Here the TextBox’s TextMode is set to Password so that any password you type in is hidden.

Note: The TextBox does not show any text in the TextBox.Text property when in Password mode this is normal behaviour.

Friday, 8 May 2009

Communicating Between FieldTemplates in Dynamic Data (UPDATED)

A question that is asked a lot on the Dynamic Data Forum is how can I get a reference to a FieldTemplate, the reason people ask this is because they are used to doing things this was from classic ASP.Net code; the problem with this is that it leads to specialised code in the page, which means custom page and I always go for custom FieldTemplate over custom page.

The problem with most of the custom FieldTemplates I’ve written for production code is that they are not generic which can be ok, but I tend to find myself writing the same sort of things again and again. So with question on the Dynamic Data Forum and on this blog I thought I’d tackle one of these types of problem in a more generic reusable way. This solution come from the previous cascading articles I’ve culminating with Cascading Filters and Fields – Dynamic Data Entity Framework Version which allows fields and filters to cascade. Here I’m going to use the same event model so one control can alert other controls to a change in it’s state thus facilitating say a checkbox hiding or disabling other field on the form depending upon its state. In this article I’m going to look at Checkbox as parent controls enabling other controls to change there status.

What we will need to Build This.

  • Event Interface
  • Event Delegate
  • EventArgs
  • Implementations
    • Parent
    • Child

The Code

Here I will quickly layout the code (each listing is fully commented) we are going to use it is not majorly different form the Cascading FieldTemplate mentioned  here

/// <summary>
/// The interface for parent controls to implement.
/// </summary>
public interface IChangeNotifyingFieldTemplate
{
    /// <summary>
    /// Gets the parent column.
    /// </summary>
    /// <value>The parent column.</value>
    MetaColumn ParentColumn { get;}

    /// <summary>
    /// Gets the state.
    /// </summary>
    /// <value>The state.</value>
    String State { get; }

    /// <summary>
    /// Occurs when [state changed].
    /// </summary>
    event ChangingAwareEventHandler StateChanged;
}

Listing 1 – the IChangingAware event interface

In Listing 1 we have our interface which has an event and three properties we will need top implement in our FieldTemplates. Now we will need a way of sending the current status of the parent control to the child control for this will will use an EventArgs class.

/// <summary>
/// Event Arguments for Changing Aware Event
/// </summary>
public class ChangingAwareEventArgs : EventArgs
{
    /// <summary>
    /// Custom event arguments for SelectionChanged 
    /// event of the ParentChangingAwareFieldTemplate control
    /// </summary>
    /// <param name="value">
    /// The value of the currently selected 
    /// value of the parent control
    /// </param>
    public ChangingAwareEventArgs(String state)
    {
        State = state;
    }
    /// <summary>
    /// The values from the control of the parent control
    /// </summary>
    public String State { get; set; }
}

Listing 2 – Changing Aware EventArgs

As you can see in Listing 2 Changing Aware EventArgs has only one property which is a string for simplicity. We will use Value to pass the current value of the parent control to the child.

/// <summary>
/// Delegate for the changing aware Interface
/// </summary>
/// <param name="sender">Parent Control</param>
/// <param name="e">An instance of the ChangingAwareEventArgs</param>
public delegate void ChangingAwareEventHandler(
    object sender,
    ChangingAwareEventArgs e);

Listing 3 – The delegate for our parent and child controls

In Listing 3 you can see the delegate for our controls event.

public class ParentChangeNotifyingFieldTemplate 
: FieldTemplateUserControl, IChangeNotifyingFieldTemplate { /// <summary> /// Gets or sets the value. /// </summary> /// <value>The state.</value> public virtual String State { get; private set; } /// <summary> /// Gets or sets the parent column. /// </summary> /// <value>The parent column.</value> public MetaColumn ParentColumn { get; private set; } /// <summary> /// publish event. /// </summary> public event ChangingAwareEventHandler StateChanged; /// <summary> /// Raises the <see cref="E:System.Web.UI.Control.Init"/> event. /// </summary> /// <param name="e"> /// An <see cref="T:System.EventArgs"/> /// object that contains the event data. /// </param> protected override void OnInit(EventArgs e) { ParentColumn = Column; base.OnInit(e); } /// <summary> /// Raises the event checking first that an event if hooked up /// </summary> /// <param name="value">The value of the currently selected item</param> public void RaiseStatusChanged(String value) { // make sure we have a handler attached if (StateChanged != null) { //raise event StateChanged(this, new ChangingAwareEventArgs(value)); } } }

Listing 4 – Parent Change Notifying FieldTemplate

In Listing 4 the control that parent FieldTemplate will inherit so that they can generate events for the child control to subscribe to.

public class ChildChangingAwareFieldTemplate : FieldTemplateUserControl
{
    /// <summary>
    /// Gets or sets the parent column.
    /// </summary>
    /// <value>The parent column.</value>
    public MetaColumn ParentColumn { get; private set; }

    /// <summary>
    /// Gets or sets the parent control.
    /// </summary>
    /// <value>The parent control.</value>
    public IChangeNotifyingFieldTemplate ParentControl { get; set; }

    /// <summary>
    /// Raises the <see cref="E:System.Web.UI.Control.Init"/> event.
    /// </summary>
    /// <param name="e">An <see cref="T:System.EventArgs"/> object that contains the event data.</param>
    protected override void OnInit(EventArgs e)
    {
        // get the parent column
        var parentColumn = Column.GetAttributeOrDefault<ChangingAwareAttribute>().ParentColumn;
        if (!String.IsNullOrEmpty(parentColumn))
            ParentColumn = Column.Table.GetColumn(parentColumn) as MetaColumn;

        // get parent field (note you must specify the container control type in
        // DetailsView and FormView = CompositeDataBoundControl : DataBoundControl
        // ListView = DataBoundControl
        if (ParentColumn != null)
            ParentControl = GetParentControl();

        // finally call base
        base.OnInit(e);
    }

    /// <summary>
    /// Gets the Parent control in a cascade of controls
    /// </summary>
    /// <param name="column"></param>
    /// <returns></returns>
    private IChangeNotifyingFieldTemplate GetParentControl()
    {
        if (ParentColumn != null)
        {
            // get value of dev ddl (Community)
            var parentDataControl = this.GetContainerControl<DataBoundControl>();

            // Get Parent FieldTemplate
            var parentDynamicControl = parentDataControl
                .FindDynamicControlRecursive(ParentColumn.Name)
                as DynamicControl;

            // extract the parent control from the DynamicControl
            IChangeNotifyingFieldTemplate parentControl = null;
            if (parentDynamicControl != null)
                parentControl = parentDynamicControl.Controls[0] as IChangeNotifyingFieldTemplate;

            return parentControl;
        }
        return null;
    }
}

Listing 5 – Child Changing Aware FieldTemplate

And Listing 5 is the control that child FieldTemplates will inherit, so it can subscribe to events from the parent control. It contains the logic to find the parent control and a couple of properties to hold the parent column and controls in.

/// <summary>
/// Get the attribute or a default instance of the attribute
/// if the Column attribute do not contain the attribute
/// </summary>
/// <typeparam name="T">
/// Attribute type
/// </typeparam>
/// <param name="table">
/// Column to search for the attribute on.
/// </param>
/// <returns>
/// The found attribute or a default 
/// instance of the attribute of type T
/// </returns>
public static T GetAttributeOrDefault<T>(this MetaColumn column) where T : Attribute, new()
{
    return column.Attributes.OfType<T>().DefaultIfEmpty(new T()).FirstOrDefault();
}

Listing 6 – Get attribute extension method

The extension method in Listing 6 is there to simplify the code for getting an attribute which we do a lot in Dynamic Data.

/// <summary>
/// Get the DynamicControl by searching recursively for it by DataField.
/// </summary>
/// <param name="Root">The control to start the search at.</param>
/// <param name="Id">The DataField of the control to find</param>
/// <returns>The found control or NULL if not found</returns>
/// public static Control FindDynamicControlRecursive<T>(this Control root, string dataField) where T : Control
public static Control FindDynamicControlRecursive(this Control root, string dataField)
{
    var dc = root as DynamicControl; //Category
    if (dc != null)
    {
        if (String.Compare(dc.DataField, dataField, true) == 0)
            return dc;
    }

    foreach (Control Ctl in root.Controls)
    {
        Control FoundCtl = FindDynamicControlRecursive(Ctl, dataField);

        if (FoundCtl != null)
            return FoundCtl;
    }
    return null;
}

/// <summary>
/// Get the Data Control containing the FiledTemplate
/// usually a DetailsView or FormView
/// </summary>
/// <param name="control">
/// Use the current field template as a starting point
/// </param>
/// <returns>
/// A FilterRepeater the control that 
/// contains the current control
/// </returns>
public static T GetContainerControl<T>(this Control control) where T : Control
{
    var parentControl = control.Parent;
    while (parentControl != null)
    {
        var p = parentControl as T;
        if (p != null)
            return p;
        else
            parentControl = parentControl.Parent;
    }
    return null;
}

Listing 7 – A group of extension methods to get the parent control

Listing 7 is the two extension methods used by the child control to find the parent, by first using GetContainerControl to find the DetailsView, FormView or GridView etc.

!Important:

ALL previous Cascading examples have a minor flaw/bug/feature. The issue occurs when the parent control appears in the list of controls after the child control, which means in the controls OnInit event all following controls are not in the list. There are two options here

  1. Force the order of columns shown in the data control
  2. let each child control capture the OnDataBound event of the container DataControl and then find the parent control there, which may be too late to hookup the event
In this article we are going to use the first method and so I will introduce a field generator and an attribute to set the column order.
/// <summary>
/// Allows to specify the ordering of columns. Columns are will
///
be sorted in increasing order based on the Order value. Columns without /// this attribute have a default Order of 0. Negative values are /// allowed and can be used to place a column before all other columns. /// unashamedly nicked from the DD Futures project :D /// </summary> [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field,
Inherited = true,
AllowMultiple = false)] public class ColumnOrderAttribute : Attribute, IComparable { public static ColumnOrderAttribute Default = new ColumnOrderAttribute(0); public ColumnOrderAttribute(int order) { Order = order; } /// <summary> /// The ordering of a column. Can be negative. /// </summary> public int Order { get; private set; } public int ListOrder { get; set; } #region IComparable Members
public int CompareTo(object obj) { return Order - ((ColumnOrderAttribute)obj).Order; } #endregion } public static partial class HelperExtansionMethods { public static ColumnOrderAttribute GetColumnOrdering(this MetaColumn column) { return column.Attributes.OfType<ColumnOrderAttribute>()
.DefaultIfEmpty(ColumnOrderAttribute.Default).First(); } }

Listing 8 – Column Order attribute

/// <summary>
/// Implements the IAutoFieldGenerator interface and 
/// supports advanced scenarios such as declarative 
/// column ordering, workaround for attribute 
/// localization issues.
/// Again mostly swiped from DD Futures
/// </summary>
public class AdvancedFieldGenerator : IAutoFieldGenerator
{

    private MetaTable _table;
    private bool _multiItemMode;

    /// <summary>
    /// Allows to explicitly declare which columns should be skipped
    /// </summary>
    public List<MetaColumn> SkipList
    {
        get;
        set;
    }

    /// <summary>
    /// Creates a new AdvancedFieldGenerator.
    /// </summary>
    /// <param name="table">The table this class generates fields for.</param>
    /// <param name="multiItemMode"><value>true</value> to indicate a multi-item control such as GridView, <value>false</value> for a single-item control such as DetailsView.</param>
    public AdvancedFieldGenerator(MetaTable table, bool multiItemMode)
    {
        if (table == null)
        {
            throw new ArgumentNullException("table");
        }

        _table = table;
        _multiItemMode = multiItemMode;
        SkipList = new List<MetaColumn>();
    }

    private bool IncludeField(MetaColumn column)
    {
        // Skip columns that should not be scaffolded
        if (!column.GetScaffold())
            return false;

        // Don't display long strings in controls that show multiple items
        if (column.IsLongString && _multiItemMode)
            return false;

        // Skip columns that are on the skip list
        if (SkipList.Contains(column))
            return false;

        return true;
    }

    private ColumnOrderAttribute ColumnOrdering(MetaColumn column)
    {
        return column.Attributes.OfType<ColumnOrderAttribute>().DefaultIfEmpty(ColumnOrderAttribute.Default).First();
    }

    #region IAutoFieldGenerator Members

    public ICollection GenerateFields(Control control)
    {
        // Get all of table's columns, take only the ones that should be automatically included in a fields collection,
        // sort the result by the ColumnOrderAttribute, and for each column create a DynamicField
        var fields = from column in _table.Columns
                     where IncludeField(column)
                     orderby ColumnOrdering(column)
                     select new DynamicField()
                     {
                         DataField = column.Name,
                         HeaderText = column.DisplayName
                     };

        return fields.ToList();
    }

    #endregion
}

public static partial class HelperExtansionMethods
{
    /// <summary>
    /// Gets a value indicating if the column should be scaffolded. This honors the
    /// ScaffoldColumnAttribute as well as returns true if the column is an enumerated type.
    /// </summary>
    /// <param name="column"></param>
    /// <returns></returns>
    public static bool GetScaffold(this MetaColumn column)
    {
        // make sure we honor the ScaffoldColumnAttribute. The framework already does this
        // but we want to do this again as the first thing.
        var scaffoldAttribute = column.GetAttribute<ScaffoldColumnAttribute>();
        if (scaffoldAttribute != null)
            return scaffoldAttribute.Scaffold;

        // always return true for enumerated types
        return column.ColumnType.IsEnum || column.Scaffold;
    }
}

Listing 9 – The IAutoFieldGenerator

I have included Listing 8 & 9 for completeness they can both be found in the ASP.NET July 2007 Futures Source Code project on Codeplex and all I’m going to do is add [ColumnOrder(-1)] to the Discontinued column of the Products table (-1 is before zero and the default value is zero).

So now we are ready to setup some FieldTemplates to act as parents and children. Here we will create on parent control by modifying the default Boolean FieldTemplate Boolean_Edit.ascx.

Implementing the above classes in the FieldTemplates

Here we have a class for parent FieldTemplates to inherit and one for children, the parent exposes two properties and an event and the child class does the dirty business of finding the parent control.

Here we are going to use the Boolean_Edit.ascx for as out parent, you could use any theoretically but I thought Boolean made for a good sample.

#region Changing Aware code
/// <summary>
/// override the Value property and 
/// return the controls curretn state
/// </summary>
public override string State
{
    get
    {
        return CheckBox1.Checked.ToString();
    }
}

public MetaColumn ChildColumn { get { return Column; } }

protected void CheckBox1_CheckedChanged(object sender, EventArgs e)
{
    RaiseStatusChanged(this.CheckBox1.Checked.ToString());
}
#endregion

Listing 10 – code to add to the parent control (Boolean_Edit.ascx)

You just need to add the code from Listing 10 to the Boolean_Edit.ascx.cs file and then change the classes inheritance to ParentChangeNotifyingFieldTemplate now Boolean_Edit FieldTemplate is publishing its ChangingAware event.

#region Changing Aware Control
// added page init to hookup the event handler
protected override void OnDataBinding(EventArgs e)
{
    // get the parent column
    var parentColumn = Column.GetAttributeOrDefault<ChangingAwareAttribute>().ParentColumn;

    if (!String.IsNullOrEmpty(parentColumn))
    {
        //TODO: get the value from Row of the ParentColumn
        Object value = DataBinder.GetPropertyValue(Row, parentColumn);
        if (String.Compare(value.ToString(), "true", true) == 0)
            this.Visible = false;
        else
            this.Visible = true;
    }

    base.OnDataBinding(e);
}
#endregion

Listing 11 – this is the code for the Text.ascx.cs file

All you need to do is add the above code Listing 11 the to ReadOnly FieldTemplates that you want to hide in response to the parent in our case its just the Text.ascx.cs file.

#region Event
// added page init to hook-up the event handler
protected void Page_Init(object sender, EventArgs e)
{
    if (ParentColumn != null && ParentControl != null)
    {
        // regiter for the event
        ParentControl.StateChanged += StateChanged;
    }
}

// consume event
protected void StateChanged(object sender, ChangingAwareEventArgs e)
{
    if (ParentColumn != null && ParentControl != null)
    {
        // show or hide depending on current state of parent
        if (String.Compare(e.State, "true", true) == 0)
            this.Visible = false;
        else
            this.Visible = true;
    }
}

// added data binding to allow field to be hidden on load
protected override void OnDataBinding(EventArgs e)
{
    if (ParentControl != null)
        this.Visible = ParentControl.State == "True" ? false : true;
    base.OnDataBinding(e);
}

Listing 12 – this is the code for the Text_Edit.ascx and Integer_Edit.ascx files (UPDATED)

Listing 12 code is added to both the Text_Edit.ascx.cs and Integer_Edit.ascx.cs files

Updated: I’ve update the code in the OnDataBinding event handler to fix a bug during insert where there would be no value in the parent filed.
[MetadataType(typeof(ProductMD))]
public partial class Product
{
    public class ProductMD
    {
        public object ProductID {get;set;}
        public object ProductName {get;set;}
        public object SupplierID {get;set;}
        public object CategoryID {get;set;}

        [ChangingAware("Discontinued")]
        public object QuantityPerUnit {get;set;}

        public object UnitPrice {get;set;}
        public object UnitsInStock {get;set;}

        [ChangingAware("Discontinued")]
        public object UnitsOnOrder {get;set;}

        [ChangingAware("Discontinued")]
        public object ReorderLevel {get;set;}

        [ColumnOrder(-1)]
        public object Discontinued {get;set;}
        // EntitySet
        public object Order_Details {get;set;}
        // EntityRef
        public object Category {get;set;}
        public object Supplier {get;set;}

    }
}

Listing 13 – the Metadata

As you can see in Listing 13 of the metadata I’ve added the ChangeAware attribute to several columns these will be hidden id the row is discontinued. And to make sure that the children can see the parent when looking for it I’ve added a ColumnOrder attribute to the Discontinued column with a value of –1 for force it to be the first field in the row see Figure 1, 2 and 3 .

Figure 1 – As you can see the bottom row is discontinued and some field are hidden appropriately

Figure 1 – As you can see the bottom row is discontinued and some field are hidden appropriately.

 

Figure 2 - normal Figure 3 - Discontinued
Figure 2 - normal Figure 3 - Discontinued

 

So there we have it

Download (UPDATED)

Happy coding HappyWizard