Showing posts with label Visual Studio 2010. Show all posts
Showing posts with label Visual Studio 2010. Show all posts

Saturday, 17 July 2010

Autocomplete Filter from the old Futures project working in Dynamic Data 4

I was sorry that the Autocomplete filter from the old Futures project was not added to Dynamic Data 4 CryingSo I thought I would do it, so here it is. I also thought I would make it work with the Entity Framework as well.

First of all if you don’t have a copy of the old futures project then go get it from here Dynamic Data Futures VS2008 SP1 RTM this has many useful samples that are worth adapting to DD4.

Copying The Necessary Files

First we need to copy some filed from the old Futures project to the new project in Visual Studio 2010, see Figure 1 for the files we need and Figure 2 for the where we need to copy them

Autocomplete files LinqExpressionHelper.cs

 Figure 1 - Required files from old Futures project

Files Added to Custom Filter project

Figure 2 - Files Added to Custom Filters project

The Files will now need updating to work with this project.

Change namespace in each file

For this I usually just do a global replace in entire project

Ctrl+H  “Find and Replace”

Figure 3 - Ctrl+H  “Find and Replace”

Remove redundant using

In the Autocomplete.ascx.cs file remove the redundant using statement for Microsoft.Web.DynamicData.

Fix up the AutocompleteFilter.asmx.cs service.

First we need to add some references:

Download and add a reference to Ajax Control Toolkit,

Next we need is to add the using for generic collections;

using System.Collections.Generic;

Then we need to change this;

 return queryable.Cast<object>().Select(row => CreateAutoCompleteItem(table, row)).ToArray();

for this;

var values = new List<String>();
foreach (var row in queryable)
{
    values.Add( CreateAutoCompleteItem(table, row));
}

return values.ToArray(); 

This also makes it compatible with the Entity Framework

Fix up the Autocomplete filter it’s self.

The very first thing is the filter needs to inherit from “System.Web.DynamicData.QueryableFilterUserControl”

public partial class Autocomplete_Filter : System.Web.DynamicData.QueryableFilterUserControl

Add the following Usings:

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

Then we need to add some properties;

private new MetaForeignKeyColumn Column
{
    get { return (MetaForeignKeyColumn)base.Column; }
}

public override Control FilterControl
{
    get { return AutocompleteTextBox; }
}

Next replace ALL  occurrences of InitialValue with DefaultValue

In the CleantButton_Click event add OnFilterChanged() call

public void ClearButton_Click(object sender, EventArgs e)
{
    // this would probably be better handled using client JavaScirpt
    AutocompleteValue.Value = String.Empty;
    AutocompleteTextBox.Text = String.Empty;
    OnFilterChanged();
}

Remove the SelectedValue property and add the following event handler and method.

protected void AutocompleteValue_ValueChanged(object sender, EventArgs e)
{
    OnFilterChanged();
}

public override IQueryable GetQueryable(IQueryable source)
{
    string selectedValue = String.IsNullOrEmpty(AutocompleteValue.Value) ? null : AutocompleteValue.Value
    if (String.IsNullOrEmpty(selectedValue))
        return source;

    IDictionary dict = new Hashtable();
    Column.ExtractForeignKey(dict, selectedValue);
    foreach (DictionaryEntry entry in dict)
    {
        string key = (string)entry.Key;
        if (DefaultValues != null)
            DefaultValues[key] = entry.Value;

        source = ApplyEqualityFilter(source, Column.GetFilterExpression(key), entry.Value);
    }
    return source;
}

Listing 1 – New Methods for Autocomplete filter.

Next we need to hook-up the HiddenField’s OnValueChanged event handler to point to AutocompleteValue_ValueChanged method we just added, so it looks like this.

<asp:HiddenField
    runat="server" 
    ID="AutocompleteValue" 
    OnValueChanged="AutocompleteValue_ValueChanged"/>

Add a link to the Autocomplete css

Open site.master and drag the AutocompleteStyle.css to the Head section

<link href="AutocompleteStyle.css" rel="stylesheet" type="text/css" />

Now we need to add some styling to the Autocomplete.ascx filter, I’m just adding

CssClass="DDControl"

to the TextBox and Button controls.

Add some Metadata

In Listing 1 we have added a FilterUIHint setting the Autocomplete filter as the the filter for the Orders Customer property.

[MetadataTypeAttribute(typeof(Order.OrderMetadata))]
public partial class Order
{
    internal sealed class OrderMetadata
    {
        [FilterUIHint("Autocomplete")]
        public Customer Customer { get; set; }
    }
}

Listing 3 -  sample metadata

You should now have a working sample run it up and go to the Orders table list page:

Autocomplete filter in action

Figure 4 - Autocomplete filter in action

Downloads

Happy Coding Happy Wizzard

Sunday, 6 June 2010

Part 3 – A Cascading Hierarchical Field Template & Filter for Dynamic Data

This would have been the final part of the series of articles, however I realised that we will need a final article that covers the more complex action of the CascadingHierarchicalFilter, this where at each level of selection we filter the list.

Cascading Hierarchical Filter

Figure 1 – Cascading Hierarchical Filter.

e.g. in Figure 1 the CascadingHierarchicalFilter (the basic version) no selection occurs until we select a value from the final list control, however in the complex CascadingHierarchicalFilter we will filter at each level and so we will hold that over until we have completed the basic version of the CascadingHierarchicalFilter.

And so to the basic version, first we will need a starting place so copy the ForeignKey filter and rename it to CascadingHierarchical and the class name to CascadingHierarchicalFilter. Next we remove the content of the aspx page so it looks like Listing 1.

<%@ Control 
    Language="C#" 
    CodeBehind="CascadeHierarchical.ascx.cs" 
    Inherits="CascadeHierarchicalFieldTemplate.CascadeHierarchicalFilter" %>

Listing 1 – CascadingHierarchical.aspx page

Then we start on the code behind by adding our member variables, adjusting the two properties and replacing the default Page_Init with ours, which is very similar to the Edit templates Page_Init see Listing 2.

#region member variables
private const string NullValueString = "[null]";
// hold the current data context to access the model
private object context;
// hold the list of filters
private SortedList<int, HierachicalListControl> filters = new SortedList<int, HierachicalListControl>();
// hold the attribute
private CascadeHierarchicalAttribute cascadeHierarchicalAttribute;
#endregion

private new MetaForeignKeyColumn Column
{
    get { return (MetaForeignKeyColumn)base.Column; }
}

public override Control FilterControl
{
    get
    {
        if (filters[0] != null && filters[0].ListControl != null)
            return filters[0].ListControl;
        else
            return null;
    }
}

protected void Page_Init(object sender, EventArgs e)
{
    //if (!Column.IsRequired)
    // check we have a cascade hierarchical attribute if not throw error
    cascadeHierarchicalAttribute = Column.GetAttribute<CascadeHierarchicalAttribute>();
    if (cascadeHierarchicalAttribute == null)
        throw new InvalidOperationException("Was expecting a CascadeFilterAttribute.");

    // check we have correct column type if not throw error
    if (!(Column is MetaForeignKeyColumn))
        throw new InvalidOperationException(String.Format("Column {0} must be a foreign key column navigation property", Column.Name));

    // get current context
    context = Column.Table.CreateContext();

    // get hierarchical cascade columns
    var parentColumns = new SortedList<int, String>();
    for (int i = 0; i < cascadeHierarchicalAttribute.Parameters.Length; i++)
        parentColumns.Add(i, cascadeHierarchicalAttribute.Parameters[i]);

    // add extra column to represent this column itself
    parentColumns.Add(cascadeHierarchicalAttribute.Parameters.Length, String.Empty);

    //get current column into a local variable
    MetaForeignKeyColumn currentColumn = Column;

    // setup list of filter definitions
    for (int i = 0; i < parentColumns.Count; i++)
    {
        // get parent column name
        var parentColumnName = parentColumns[i];

        // create dropdown list
        var ddl = new DropDownList()
        {
            ID = String.Format("ListControl{0}", i),
            Enabled = false,
            AutoPostBack = true
        };

        // create filter
        var filter = new HierachicalListControl(ddl) { Column = currentColumn };

        // check for last parent filter
        if (!String.IsNullOrEmpty(parentColumnName))
        {
            // set parent column from parent table
            filter.ParentColumn = (MetaForeignKeyColumn)currentColumn.ParentTable.GetColumn(parentColumnName);

            // set current column to parent column
            currentColumn = filter.ParentColumn;
        }
        else
        {
            // this is the last parent and has
            // no parent itself so set to null
            filter.ParentColumn = null;
            currentColumn = null;
        }
        // add filter to list of filters
        filters.Add(i, filter);
    }

    // add dropdown list to page in correct order 2, 1, 0
    // last parent, parent<N>, child
    for (int i = parentColumns.Count - 1; i >= 0; i--)
    {
        // setup dropdown list
        filters[i].ListControl.Items.Clear();
        filters[i].ListControl.Items.Add(new ListItem("------", String.Empty));

        // add parent list controls event handler
        if (i > 0)
            filters[i].ListControl.SelectedIndexChanged += ListControls_SelectedIndexChanged;
        else
            filters[i].ListControl.SelectedIndexChanged += ListControl0_SelectedIndexChanged;

        // add control to place holder
        this.Controls.Add(filters[i].ListControl);
    }

    if (DefaultValue != null)
        PopulateAllListControls(DefaultValue);
    else
    {
        // fill last parent filter
        var lastParentIndex = filters.Count - 1;
        var parentTable = filters[lastParentIndex].Column.ParentTable;
        var parentQuery = parentTable.GetQuery(context);

        // set next descendant list control
        PopulateListControl(lastParentIndex, parentQuery);
    }
}

Listing 2 – Member variables and Page_Init

Now we add the following Listings 3 & 4 these are virtually the same as from the Edit template and so I am thinking in the next revision they may all be removed to the class library.

/// <summary>
/// Sets the default values.
/// </summary>
/// <param name="fieldValue">The value.</param>
private void PopulateAllListControls(object fieldValue)
{
    var displayStrings = new SortedList<int, String>();

    #region Get list of propert values
    // get property values
    var propertyValues = new SortedList<int, Object>();
    propertyValues.Add(0, fieldValue);
    for (int i = 0; i < filters.Count - 1; i++)
    {
        var parentName = filters[i].ParentColumn.Name;
        object pv = propertyValues[i].GetPropertyValue(parentName);
        propertyValues.Add(i + 1, pv);
    }
    #endregion

    // stating at the first filter and work way up to the last filter
    for (int i = 0; i < filters.Count; i++)
    {
        var parentTable = filters[i].Column.ParentTable;
        var parentQuery = parentTable.GetQuery(context);
        IQueryable listItemsQuery;
        if (i == cascadeHierarchicalAttribute.Parameters.Length)
        {
            listItemsQuery = parentQuery.GetQueryOrdered(parentTable);
        }
        else
        {
            var pcol = filters[i + 1].Column;
            var selectedValue = filters[i].ParentColumn.GetForeignKeyString(propertyValues[i]);
            listItemsQuery = parentQuery.GetQueryFilteredFkColumn(pcol, selectedValue);
        }

        // set next descendant list control
        PopulateListControl(i, listItemsQuery);

        // set initial values
        var selectedValueString = filters[i].Column.Table.GetPrimaryKeyString(propertyValues[i]);
        ListItem item = filters[i].ListControl.Items.FindByValue(selectedValueString);
        if (item != null)
            filters[i].ListControl.SelectedValue = selectedValueString;
    }
}

Listing 3 – PopulateAllListControls

/// <summary>
/// Handles the SelectedIndexChanged event of the parentListControls control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">
/// The <see cref="System.EventArgs"/> instance containing the event data.
/// </param>
/// <summary>
/// Setups the parent list control.
/// </summary>
/// <param name="table">The table.</param>
/// <param name="filterIndex">The parent id.</param>
/// <param name="items">The items.</param>
public void PopulateListControl(int filterIndex, IQueryable items)
{
    // clear the list controls list property
    filters[filterIndex].ListControl.Items.Clear();
    // enable list control
    filters[filterIndex].ListControl.Enabled = true;

    // add unselected value showing the column name
    // [Styles]
    filters[filterIndex].ListControl.Items.Add(
        new ListItem(String.Format("[{0}]",
            filters[filterIndex].Column.DisplayName), NullValueString));

    foreach (var row in items)
    {
        // populate each item with the display string and key value
        filters[filterIndex].ListControl.Items.Add(
            new ListItem(filters[filterIndex].Column.ParentTable.GetDisplayString(row),
            filters[filterIndex].Column.ParentTable.GetPrimaryKeyString(row)));
    }
}

/// <summary>
/// Resets all descendant list controls.
/// </summary>
/// <param name="startFrom">The start from.</param>
private void ResetAllDescendantListControls(int startFrom)
{
    for (int i = startFrom - 1; i >= 0; i--)
    {
        filters[i].ListControl.Items.Clear();
        filters[i].ListControl.Items.Add(new ListItem("----", String.Empty));
        filters[i].ListControl.Enabled = false;
    }
}

Listing 4 – PopulateListControl and ResetAllDescendantListControls

Listing 5 is again virtually the same as the Edit templates but I kept it separate as it is an event handler and may make more sense to keep it in the template.

/// <summary>
/// Handles the SelectedIndexChanged event for each List control, 
/// and populates the next list control in the hierarchy.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">
/// The <see cref="System.EventArgs"/> instance containing the event data.
/// </param>
void ListControls_SelectedIndexChanged(object sender, EventArgs e)
{
    // get list control
    var listControl = (ListControl)sender;

    // get the sending list controls id as an int
    var id = ((Control)sender).ID;

    // use regular expression to find list control index
    var regEx = new Regex(@"\d+");
    var parentIndex = int.Parse(regEx.Match(id).Value);

    if (listControl.SelectedValue != NullValueString)
    {
        if (parentIndex > 0)
        {
            // set child index
            var childIndex = parentIndex - 1;

            // get parent table
            var parentTable = filters[childIndex].Column.ParentTable;

            // get query from table
            var query = parentTable.GetQuery(context);

            // get items for list control
            var itemQuery = query.GetQueryFilteredFkColumn(
                filters[parentIndex].Column,
                listControl.SelectedValue);

            // populate list control
            PopulateListControl(childIndex, itemQuery);

            // reset all descendant list controls
            ResetAllDescendantListControls(childIndex);
        }
    }
    else
    {
        // reset all descendant list controls
        ResetAllDescendantListControls(parentIndex);
    }
}

Listing 5 – ListControls_SelectedIndexChanged

Next we replace the DropDownList1_SelectedIndexChanged with ListControl0_SelectedIndexChanged Listing 6 really only a name change to make the code read clearly.

protected void ListControl0_SelectedIndexChanged(object sender, EventArgs e)
{
    OnFilterChanged();
}

Listing 6 – ListControl0_SelectedIndexChanged

Finally we need to change the GetQueryable method to replace DropDownList1 with filters[0].ListControl.

Download

Happy coding

Note: With a few changes around the last two listings you could adapt this to either the old Dynamic Data Futures VS2008 SP1 RTM filters on CodePlex or Josh Heyes’s Dynamic Data Filtering.

Wednesday, 17 February 2010

DisplayAttribute “Prompt” or Watermark in Dynamic Data 4 (Visual Studio 2010 & .Net 4.0)

In Figure 1 there is another property called “Prompt” and the online help says:

Prompt: Gets or sets a value that will be used to set the watermark for prompts in the UI.

Figure 1 – Display attribute properties

So I set the value and looked at the output UI in Insert mode and there was no water mark so again I just had to “make it so”. So I decided to use the Ajax Control Toolkit’s TextBoxWatermark to achieve this.

Note: This example applies this to the Text and DateTime fields template, but it could also be applied to other templates that are based on Textboxes (e.g. Integer, Decimal etc.)

I’ve created a watermarked CSS class and added it to the site.css file.

.watermarked
{
     color: Silver;
}

Listing 1 – watermarked CSS class

<asp:TextBox 
    ID="TextBox1" 
    runat="server" 
    CssClass="DDTextBox"
    Text='<%# FieldValueEditString %>'> 
</asp:TextBox>
<asp:TextBoxWatermarkExtender 
    ID="TextBoxWatermarkExtender1" 
    runat="server" 
    TargetControlID="TextBox1"
    WatermarkCssClass="watermarked">
</asp:TextBoxWatermarkExtender>

Listing 2 – TextBoxWarterMarkExtender

I’ve also added the TextBoxWarterMarkExtender to both the Text_Edit.ascx and DateTime_Edit.ascx files see Listing 2 then we need to make the changes to the code behind files.

Note:  The code for the TextBoxWarterMarkExtender is the same across both field templates but there are some minor differences in the TextBox.

Here there will be major differences as we can leverage the System.Globalization namespace in the DateTime_Edit.

#region Watermarked
var display = Column.GetAttribute<DisplayAttribute>();
if (display != null && !String.IsNullOrEmpty(display.Prompt))
    TextBoxWatermarkExtender1.WatermarkText = display.Prompt;
else
    TextBoxWatermarkExtender1.Enabled = false;
#endregion

Listing 3 – Watermarked region of the Text_Edit.ascx.cs Page_Load event.

In Listing 3 in the Text_Edit field template we are just looking for the Display attribute and it’s Prompt property and if we have a prompt value then we apply it to the watermark otherwise we disable the extender.

#region Watermarked
String defaultFormat = String.Empty;
if (Column.DataTypeAttribute != null)
{
    // get format string depending on the data type
    switch (Column.DataTypeAttribute.DataType)
    {
        case DataType.Date:
            defaultFormat = DateTimeFormatInfo.CurrentInfo.ShortDatePattern;
            break;
        case DataType.DateTime:
            defaultFormat = DateTimeFormatInfo.CurrentInfo.FullDateTimePattern;
            break;
        case DataType.Time:
            defaultFormat = DateTimeFormatInfo.CurrentInfo.LongTimePattern;
            break;
    }
}
else
    defaultFormat = DateTimeFormatInfo.CurrentInfo.ShortDatePattern;

// if we have a Prompt value then override the default format string
var display = Column.GetAttribute<DisplayAttribute>();
if (display != null && !String.IsNullOrEmpty(display.Prompt))
    TextBoxWatermarkExtender1.WatermarkText = display.Prompt;
else
    TextBoxWatermarkExtender1.WatermarkText = defaultFormat;
#endregion

Listing 3 – Watermarked region of the DateTime_Edit.ascx.cs Page_Load event.

So then for the DateTime field template we could have a default watermark depending on the DateType set for the DateTime field (see Listing 3). Here in Listing 3 we are using System.Globalization to get the format string based on culture and date type (DateTime, Date or Time) but allowing the Prompt to be overridden.

Watermarked fields

Figure 1 – Watermarked fields

Obviously this could be applied to other fields that are based on TextBox, but this will do for now.

Download

Note: This sample uses Ajax Control Toolkit

Monday, 15 February 2010

Grouping Fields on Details, Edit and Insert with Dynamic Data 4, VS2010 and .Net 4.0 RC1

Whilst writing my last article over the week end I noticed the new Display attribute see Figure 1 the first one that intrigued me was the GroupName parameter, so the first thing I did was add some GroupName to some metadata on a Northwind table.

Display attribute parameters

Figure 1 – Display attribute parameters

[MetadataType(typeof(OrderMetadata))]
public partial class Order
{
    internal partial class OrderMetadata
    {
        public Object OrderID { get; set; }
        public Object CustomerID { get; set; }
        public Object EmployeeID { get; set; }
        [Display(Order = 0,GroupName = "Dates")]
        public Object OrderDate { get; set; }
        [Display(Order = 1,GroupName = "Dates")]
        public Object RequiredDate { get; set; }
        [Display(Order = 2,GroupName = "Dates")]
        public Object ShippedDate { get; set; }
        [Display(Order = 4,GroupName = "Ship Info")]
        public Object ShipVia { get; set; }
        [Display(Order = 5,GroupName = "Ship Info")]
        public Object Freight { get; set; }
        [Display(Order = 3,GroupName = "Ship Info")]
        public Object ShipName { get; set; }
        [Display(Order = 6,GroupName = "Ship Info")]
        public Object ShipAddress { get; set; }
        [Display(Order = 7,GroupName = "Ship Info")]
        public Object ShipCity { get; set; }
        [Display(Order = 8,GroupName = "Ship Info")]
        public Object ShipRegion { get; set; }
        [Display(Order = 9,GroupName = "Ship Info")]
        public Object ShipPostalCode { get; set; }
        [Display(Order = 10,GroupName = "Ship Info")]
        public Object ShipCountry { get; set; }
        // Entity Ref 
        [Display(Order = 12,GroupName = "Other Info")]
        public Object Customer { get; set; }
        // Entity Ref 
        [Display(Order = 13,GroupName = "Other Info")]
        public Object Employee { get; set; }
        // Entity Set 
        [Display(Order = 14,GroupName = "Other Info")]
        public Object Order_Details { get; set; }
        // Entity Ref 
        [Display(Order = 11,GroupName = "Ship Info")]
        public Object Shipper { get; set; }
    }
}

Listing 1 – GroupName metadata.

Well when I ran the app and get Figure 2 I was a little disappointed, I’d expected that at least the field would be grouped together by group name (maybe this was not the intended use but I was determined to make it work) but better still would have been with a separator containing the group name.

GroupingBefore

Figure 2 – Orders table with GroupName metadata

So I set about “making it so” (to quote Captain Picard) the first step was to group the fields so I looked at the new EntityTemplates.

<asp:EntityTemplate runat="server" ID="EntityTemplate1">
    <ItemTemplate>
        <tr class="td">
            <td class="DDLightHeader">
                <asp:Label 
                    runat="server"
                    OnInit="Label_Init" />
            </td>
            <td>
                <asp:DynamicControl 
                    runat="server"
                    OnInit="DynamicControl_Init" />
            </td>
        </tr>
    </ItemTemplate>
</asp:EntityTemplate>

Listing 2 – Default.ascx entity template

public partial class DefaultEntityTemplate : EntityTemplateUserControl
{
    private MetaColumn currentColumn;

    protected override void OnLoad(EventArgs e)
    {
        foreach (MetaColumn column in Table.GetScaffoldColumns(Mode, ContainerType))
        {
            currentColumn = column;
            Control item = new _NamingContainer();
            EntityTemplate1.ItemTemplate.InstantiateIn(item);
            EntityTemplate1.Controls.Add(item);
        }
    }

    protected void Label_Init(object sender, EventArgs e)
    {
        Label label = (Label)sender;
        label.Text = currentColumn.DisplayName;
    }

    protected void DynamicControl_Init(object sender, EventArgs e)
    {
        DynamicControl dynamicControl = (DynamicControl)sender;
        dynamicControl.DataField = currentColumn.Name;
    }

    public class _NamingContainer : Control, INamingContainer { }
}

Listing 3 – Default.ascx.cs entity template code behind.

If you look at my old Custom PageTemplates Part 4 - Dynamic/Templated FromView sample, I implemented the ITemplate interface for generating FormView and also ListView templates dynamically, and I remember at the time David Ebbo commenting on this and how he was working on something a little more flexible and then Entity Templates were unveiled in one of the early previews; but this is considerably more flexible than my sample was. I think we will be able to extend this greatly in the future but for now I’ll be happy with making grouping work.

So the first thing I did was tweak the default entity template to order by groups.

protected override void OnLoad(EventArgs e)
{
    // get a list of groups ordered by group name
    var groupings = from t in Table.GetScaffoldColumns(Mode, ContainerType)
                    group t by t.GetAttributeOrDefault<DisplayAttribute>().GroupName into menu
                    orderby menu.Key
                    select menu.Key;

    // loop through the groups
    foreach (var groupId in groupings)
    {
        // get columns for this group
        var columns = from c in Table.GetScaffoldColumns(Mode, ContainerType)
                      where c.GetAttributeOrDefault<DisplayAttribute>().GroupName == groupId
                      orderby c.GetAttributeOrDefault<DisplayAttribute>().GetOrder()
                      select c;

        // add fields
        foreach (MetaColumn column in columns)
        {
            currentColumn = column;
            Control item = new _NamingContainer();
            EntityTemplate1.ItemTemplate.InstantiateIn(item);
            EntityTemplate1.Controls.Add(item);
        }
    }
}

Listing 4 – extended default entity template stage 1

So what I did in listing 4 was get a list of all the groups sorted by group name, and then loop through the groups getting the column for each group; then generate the groups fields. Visually this does not produce much of a difference than the initial display.

Grouping with Sort

Figure 3 – Grouping with Sort.

Now we can see the groups coming together, next we need to add the visual aspect.

A little surgery on the ascx part of the entity template is required to get this to work. In Listing 5 you can see that I have added some runat=”server” properties to the TD’s of the template.

<asp:EntityTemplate runat="server" ID="EntityTemplate1">
    <ItemTemplate>
        <tr class="td">
            <td class="DDLightHeader" runat="server">
                <asp:Label 
                    runat="server" 
                    OnInit="Label_Init" />
            </td>
            <td runat="server">
                <asp:DynamicControl 
                    runat="server" 
                    OnInit="DynamicControl_Init" />
            </td>
        </tr>
    </ItemTemplate>
</asp:EntityTemplate>

Listing 5 – modified default.ascx Entity Template.

Moving to the Default entity templates code behind in Listing 6 I have added the code to add a separator, but it will need some modification as at the moment is just a repeat of one of the columns.

protected override void OnLoad(EventArgs e)
{
    // get a list of groups ordered by group name
    var groupings = from t in Table.GetScaffoldColumns(Mode, ContainerType)
                    group t by t.GetAttributeOrDefault<DisplayAttribute>().GroupName into menu
                    orderby menu.Key
                    select menu.Key;

    // loop through the groups
    foreach (var groupId in groupings)
    {
        // get columns for this group
        var columns = from c in Table.GetScaffoldColumns(Mode, ContainerType)
                      where c.GetAttributeOrDefault<DisplayAttribute>().GroupName == groupId
                      orderby c.GetAttributeOrDefault<DisplayAttribute>().GetOrder()
                      select c;

        // add group separator
        if (!String.IsNullOrEmpty(groupId))
        {
            groupHeading = true;
            currentColumn = columns.First();
            groupName = groupId;
            Control item = new _NamingContainer();
            EntityTemplate1.ItemTemplate.InstantiateIn(item);
            EntityTemplate1.Controls.Add(item);
        }

        // add fields
        foreach (MetaColumn column in columns)
        {
            groupHeading = false;
            currentColumn = column;
            Control item = new _NamingContainer();
            EntityTemplate1.ItemTemplate.InstantiateIn(item);
            EntityTemplate1.Controls.Add(item);
        }
    }
}

Listing 6 – final version of the OnLoad handler.

For the final tweaks of the visual of the separator we will done some extra manipulation in the two Init handlers of the Label and the DynamicControl.

For the Label wee need to change the text so I have added a class field groupHeading as a Boolean which if you look in Listing 6 I’m setting to true when it is a group and false when a field.

protected void Label_Init(object sender, EventArgs e)
{
    if (!groupHeading)
    {
        Label label = (Label)sender;
        label.Text = currentColumn.DisplayName;
    }
    else
    {
        Label label = (Label)sender;
        label.Text = groupName;
        var parentCell = label.GetParentControl<HtmlTableCell>();
        parentCell.ColSpan = 2;
        parentCell.Attributes.Add("class", "DDGroupHeader");
    }
}

Listing 7 – Label_Init handler

So in Listing 7 you can see that we do the standard thing if it is a filed, but do some custom stuff if it is a group heading. I first get the parent control (see Listing 8 for source) of type HtmlTableCell (we can get this because we set it to runat=”server”). Once we have the parent cell we can manipulate it; first of all we set it’s colspan attribute to 2 and change the CSS class to "DDGroupHeader" to make it stand out.

/// <summary>
/// Gets the parent control.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="control">The control.</param>
/// <returns></returns>
public static T GetParentControl<T>(this Control control) where T : Control
{
    var parentControl = control.Parent;
    // step up through the parents till you find a control of type T
    while (parentControl != null)
    {
        var p = parentControl as T;
        if (p != null)
            return p;
        else
            parentControl = parentControl.Parent;
    }
    return null;
}

Listing 8 – GetParentControl extension method.

.DDGroupHeader
{
	font-weight: bold;
	font-style: italic;
	background-color: Silver;
}

Listing 9 – DDGroupHeader CSS snippet

The next thing is to hide the DynamicControl and it’s HtmlTableCell so the label can span both columns.  Now if we are in a group header then we hide the DynamicControl, get the parent cell and also hide it, letting the label’s cell span both rows.

protected void DynamicControl_Init(object sender, EventArgs e)
{
    DynamicControl dynamicControl = (DynamicControl)sender;
    dynamicControl.DataField = currentColumn.Name;
    if (groupHeading)
    {
        // hide Dynamic Control maybe overkill
        dynamicControl.Visible = false;
        // get the parent cell
        var parentCell = dynamicControl.GetParentControl<HtmlTableCell>();
        // hide the cell
        parentCell.Visible = false;
    }
}

Listing 10 – DynamicControl_Init handler

Note: The DynamicControl must have it’s DataField set otherwise it will throw an error.

Grouping with visual separators

Figure 4 – Grouping with visual separators.

Now we have separators working, “made so” I would think, well only partially with this version you have to repeat all the above with a few minor changed for Edit and Insert EntityTemplates, but that is in the sample.

Note: Remember this sample is with Visual Studio 2010 and .Net 4.0 RC1

Download

As always have fun coding

Sunday, 14 February 2010

A New Way To Do Column Generation in Dynamic Data 4 (UPDATED)

There have been several questions on the Dynamic Data Forum saying things like IAutoFieldGenerator does not work with Details, Edit and Insert pages. This is because these page template have now moved to FormView which allows for us to have the nice new Entity Templates and this is cool; but leaves us with the issue of having to do custom column generation in two ways one for form view and one for GridView in List and ListDetails pages. So harking back to this post A Great Buried Sample in Dynamic Data Preview 4 – Dynamic Data Futures long ago in a year far far awayBig Grin

So what I am planning to do is add our own MetaModel that we can pass in a delegate to produce a custom list of columns. I am going to implement the Hide column based on page template (HideColumnInAttribute) for now.

The Custom MetaModel


So first things first lets build out custom MetaModel, the only two classes we will need to implement for our custom MetaModel are:


  MetaModel
  MetaTable

We need MetaModel because it goes away and get the other classes.

public class CustomMetaModel : MetaModel
{
    /// <summary>
    /// Delegate to allow custom column generator to be passed in.
    /// </summary>
    public delegate IEnumerable<MetaColumn> GetVisibleColumns(IEnumerable<MetaColumn> columns);

    private GetVisibleColumns _getVisdibleColumns;

    public CustomMetaModel() { }

    public CustomMetaModel(GetVisibleColumns getVisdibleColumns)
    {
        _getVisdibleColumns = getVisdibleColumns;
    }

    protected override MetaTable CreateTable(TableProvider provider)
    {
        if (_getVisdibleColumns == null)
            return new CustomMetaTable(this, provider);
        else
            return new CustomMetaTable(this, provider, _getVisdibleColumns);
    }
}

Listing 1 – Custom MetaModel class

So what are we doing here, firstly we have a delegate so we can pass in a methods to do the column generation and we are passing this in through our custom constructor. Then in the only method we are overriding we are returning the CustomMetaTable class, and passing in the delegate if it has been set.

public class CustomMetaTable : MetaTable
{
    private  CustomMetaModel.GetVisibleColumns _getVisdibleColumns;

    /// <summary>
    /// Initializes a new instance of the <see cref="CustomMetaTable"/> class.
    /// </summary>
    /// <param name="metaModel">The entity meta model.</param>
    /// <param name="tableProvider">The entity model provider.</param>
    public CustomMetaTable(MetaModel metaModel, TableProvider tableProvider) :
        base(metaModel, tableProvider) { }

    /// <summary>
    /// Initializes a new instance of the <see cref="CustomMetaTable"/> class.
    /// </summary>
    /// <param name="metaModel">The meta model.</param>
    /// <param name="tableProvider">The table provider.</param>
    /// <param name="getVisibleColumns">Delegate to get the visible columns.</param>
    public CustomMetaTable(
        MetaModel metaModel, 
        TableProvider tableProvider, 
        CustomMetaModel.GetVisibleColumns getVisibleColumns) :
        base(metaModel, tableProvider)
    {
        _getVisdibleColumns = getVisibleColumns;
    }

    protected override void Initialize()
    {
        base.Initialize();
    }

    public override IEnumerable<MetaColumn> GetScaffoldColumns(
        DataBoundControlMode mode, 
        ContainerType containerType)
    {
        if (_getVisdibleColumns == null)
            return base.GetScaffoldColumns(mode, containerType);
        else
            return _getVisdibleColumns(base.GetScaffoldColumns(mode, containerType));
    }
}

Listing 2 – Custom MetaTable class

In CustomMetaTable we have the default constructor and a custom constructor again we passing the delegate into the custom constructor. Now in the only method we are overriding we either call the base GetScaffoldColumns or our delegate if is has been set. And that’s it as far as the Meta Classes are concerned.

The Attribute

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class HideColumnInAttribute : Attribute
{
    public PageTemplate PageTemplate { get; private set; }

    public HideColumnInAttribute() { }

    public HideColumnInAttribute(PageTemplate lookupTable)
    {
        PageTemplate = lookupTable;
    }
}

Listing 3 – HideColumnInAttribute

Listing 3 is the HideColumnIn attribute see Dynamic Data - Hiding Columns in selected PageTemplates for details on this attribute.

public static class ControlExtensionMethods
{
    // "~/DynamicData/PageTemplates/List.aspx"
    private const String extension = ".aspx";

    /// <summary>
    /// Gets the page template from the page.
    /// </summary>
    /// <param name="page">The page.</param>
    /// <returns></returns>
    public static PageTemplate GetPageTemplate(this Page page)
    {
        try
        {
            return (PageTemplate)Enum.Parse(typeof(PageTemplate),
                page.RouteData.Values["action"].ToString());
        }
        catch (ArgumentException)
        {
            return PageTemplate.Unknown;
        }
    }
}

Listing 4 – GetPageTemplate extension method.

Updated: Here the GetPageTemplate extension method has been updated thanks to beeps4848 from the DD forum see this thread How to cast integer values as an array of enum values? where he makes this cool suggestion of using the RoutData to get the action name, so maybe the attribute should now be HideColumnInAction ot the Enum ActionName (PageActions has been used).
[Flags]
public enum PageTemplate
{
    // standard page templates
    Details         = 0x01,
    Edit            = 0x02,
    Insert          = 0x04,
    List            = 0x08,
    ListDetails     = 0x10,
    // unknown page templates
    Unknown         = 0xff,
}

Listing 5 – PageTemplate enum.

In Listing 4 we have the new improved GetPageTemplate extension method now you don’t have to change each page to inherit DynamicPage you can just call the Page.GetPageTemplate() to find out which page you are on. it required the PageTemplate enum in listing 5.

The Delegate Methods

public static IEnumerable<MetaColumn> GetVisibleColumns(IEnumerable<MetaColumn> columns)
{
    var visibleColumns = from c in columns
                         where IsShown(c)
                         select c;
    return visibleColumns;
}

public static Boolean IsShown(MetaColumn column)
{
    // need to get the current page template
    var page = (System.Web.UI.Page)System.Web.HttpContext.Current.CurrentHandler;
    var pageTemplate = page.GetPageTemplate();

    var hideIn = column.GetAttribute<HideColumnInAttribute>();
    if (hideIn != null)
        return !((hideIn.PageTemplate & pageTemplate) == pageTemplate);

    return true;
} 

Listing 6 – Column generator methods

Now we need to supply our own column generator methods, in Listing 6 we have two methods the first GetVisibleColumns (and the name does not need to be the same as the Delegate) is the one we pass into the MetaModel, and the second IsHidden is where we test to see if the column should be hidden or not.

Adding To Web Application

Now we need to put these into our sample web application.

 public class Global : System.Web.HttpApplication
 {
     private static MetaModel s_defaultModel = new CustomMetaModel(GetVisibleColumns);
     public static MetaModel DefaultModel
     {
         get { return s_defaultModel; }
     }
    // other code ...
}

Listing 7 – Adding to Global.asax

So all we have to do is change the default value in Global.asax from

private static MetaModel s_defaultModel = new MetaModel();

to

private static MetaModel s_defaultModel = new CustomMetaModel(GetVisibleColumns);

Now all column generation throughout the site is handled by the GetVisibleColumns method from Listing 6.

[MetadataType(typeof(OrderMetadata))]
public partial class Order
{
    internal partial class OrderMetadata
    {
        public Object OrderID { get; set; }
        public Object CustomerID { get; set; }
        public Object EmployeeID { get; set; }
        public Object OrderDate { get; set; }
        [HideColumnIn(PageTemplate.List)]
        public Object RequiredDate { get; set; }
        public Object ShippedDate { get; set; }
        public Object ShipVia { get; set; }
        [HideColumnIn(PageTemplate.List)]
        public Object Freight { get; set; }
        public Object ShipName { get; set; }
        [HideColumnIn(PageTemplate.List)]
        public Object ShipAddress { get; set; }
        [HideColumnIn(PageTemplate.List)]
        public Object ShipCity { get; set; }
        [HideColumnIn(PageTemplate.List)]
        public Object ShipRegion { get; set; }
        [HideColumnIn(PageTemplate.List)]
        public Object ShipPostalCode { get; set; }
        [HideColumnIn(PageTemplate.List)]
        public Object ShipCountry { get; set; }
        // Entity Ref 
        public Object Customer { get; set; }
        // Entity Ref 
        public Object Employee { get; set; }
        // Entity Set 
        public Object Order_Details { get; set; }
        // Entity Ref 
        public Object Shipper { get; set; }
    }
}

Listing 8 – sample metadata.

Download

Note: The sample is for Visual Studio 2010 RC

Happy Coding

Monday, 25 January 2010

Conditional UIHint for Dynamic Data v2 and .Net 4.0

Still not sure if I should call the .Net 4.0 v2 Big Grin

For the original article see Conditional UIHint from back in October last year, what we want to achieve is change the Field Template in use by page i.e. lets say we have Foreign Key column which would use the default ForeignKey field template but I wanted to use a custom field template during edit currently I would need to create two custom field templates one for edit and one for read only modes. With the ConditionalUIHint I can just apply the attribute like so:

[ConditionalUIHint("MyForeignKey", PageTemplate.Edit)]

And then in Edit mode it would use my custom field template.

As I said in my previous post on this for DDv1 in .Net 4.0 we can create our own custom Meta Classes (MetaModel, MetaTable and MetaColumn etc.); now I said if we could override the UIHint property of the MetaColumn, MetaChildrenColumnand MetaForeignKeyColumn’s then this sample could be made cooler i.e. to get this in DD all we would have to do is change this

private static MetaModel s_defaultModel = new MetaModel();
public static MetaModel DefaultModel
{
    get { return s_defaultModel; }
}

Listing 1 – getting metamodel in Global.asax.cs

to this

// get the custom metamodel
private static MetaModel s_defaultModel = new CustomMetaModel();
public static MetaModel DefaultModel
{
    get { return s_defaultModel; }
}

Listing 2 – getting custom metamodel in Global.asax.cs

so all we have to do is use our own custom metamodel in place of the default Dynamic Data MetaModel cool and if you read A Great Buried Sample in Dynamic Data Preview 4 – Dynamic Data Futures you can use this to control all sorts of things centrally in Dynamic Data without touching each page. Applause

If you look back to the A Great Buried Sample in Dynamic Data Preview 4 – Dynamic Data Futures article you will see that there were five files that made up the Custom Meta classes:

Meta Classes

Image 1 - Meta Classes

Due to low budget on imagination this post they will all be prefixed with CustomBig Grin

We will look at the three extension methods first. the first extension method replaces my old method of finding out which page template we are in:

/// <summary>
/// page extension
/// </summary>
private const String EXTENSION = ".aspx";

/// <summary>
/// Gets the page template from the page.
/// </summary>
/// <param name="page">The page.</param>
/// <returns></returns>
public static PageTemplate GetPageTemplate(this Page page)
{
    // get pages path
    var path = page.AppRelativeVirtualPath;

    // trim it so we just have the page name
    var pageName = path.Substring(path.LastIndexOf("/") + 1, 
        path.Length - path.LastIndexOf("/") - EXTENSION.Length - 1);

    PageTemplate pageTemplate;

    // extract the page template from the page name
    if (Enum.TryParse<PageTemplate>(pageName, out pageTemplate))
        return pageTemplate;
    else
        return PageTemplate.Unknown;
}

Listing 3 – GetPageTemplate extension method

here I’m getting the page template by extracting the page name excluding extension and then matching it to my PageTemplate enum Listing 4 and returning PageTemplate.Unkown if no match is found (after all I may be using some PageTemplates other that the default ones).

[Flags]
public enum PageTemplate
{
    // standard page templates
    Details         = 0x01,
    Edit            = 0x02,
    Insert          = 0x04,
    List            = 0x08,
    ListDetails     = 0x10,
    // default if unknown
    Unknown         = 0xff
}

Listing 4 – PageTemplate enum

And then we have two more extension methods that are used in each Meta Column type to return the ConditionalUIHint:

/// <summary>
/// Gets the Conditional UIHint.
/// </summary>
/// <param name="column">The column.</param>
/// <param name="baseUIHint">The base UIHint.</param>
/// <returns>a UIHint string</returns>
public static String GetUIHintConditionally(this MetaColumn column, String baseUIHint)
{
    // need to get the current page template
    var page = (System.Web.UI.Page)System.Web.HttpContext.Current.CurrentHandler;
    var pageTemplate = page.GetPageTemplate();
    
    var conditionalUIHint = column.GetAttribute<ConditionalUIHintAttribute>();
    if (conditionalUIHint != null && 
        conditionalUIHint.HasConditionalUIHint(pageTemplate))
        return conditionalUIHint.UIHint;
    else
        return baseUIHint;
}

/// <summary>
/// Determines whether [has conditional UI hint] 
/// [the specified conditional UI hint].
/// </summary>
/// <param name="conditionalUIHint">The conditional UI hint.</param>
/// <param name="currentPage">The current page.</param>
/// <returns>
/// <c>true</c> if [has conditional UI hint] 
/// [the specified conditional UI hint]; otherwise, <c>false</c>.
/// </returns>
private static Boolean HasConditionalUIHint(
    this ConditionalUIHintAttribute conditionalUIHint, 
    PageTemplate currentPage)
{
    return (conditionalUIHint.PageTemplates & currentPage) == currentPage;
}

Listing 5 – GetUIHintConditionally and HasConditionalUIHint

In Listing 5 we have GetUIHintConditionally and HasConditionalUIHint the first get the UIHint and if the current page template matches one in the ConditionalUIHint the ConditionalUIHint is returned. The HasConditionalUIHint is used to determine if there is a page template match.

public class CustomMetaColumn : MetaColumn
{
    public CustomMetaColumn(
        MetaTable table, 
        ColumnProvider 
        columnProvider) : 
        base(table, columnProvider) { }

    protected override void Initialize() { base.Initialize(); }

    /// <summary>
    /// Gets the name of the field template 
    /// specified for the data field.
    /// </summary>
    /// <value></value>
    /// <returns>
    /// The name of the field template 
    /// specified for the data field.
    /// </returns>
    public override string UIHint
    {
        get
        {
            return this.GetUIHintConditionally(base.UIHint);
        }
    }
}

Listing 6 – CustomMetaColumn (Children and ForeignKey are basically the same)

Now all we need in each Meta Column type is to call the GetUIHintConditionally with the current UIHint and if the conditions are met then the ConditionalUIHint will replace the default one.

Each of the other  Meta classes is there just to facilitate this and are very basic overrides of the base classes.

Also my standard Attribute extension methods are in there.

Downloads

Happy coding