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

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"> <asp:Label runat="server" OnInit="Label_Init" /> <asp:DynamicControl runat="server" OnInit="DynamicControl_Init" />

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().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"> <asp:Label runat="server" OnInit="Label_Init" /> <asp:DynamicControl runat="server" OnInit="DynamicControl_Init" />

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().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(); 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.

///

/// Gets the parent control. /// /// /// The control. /// public static T GetParentControl(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(); // 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