Monday, June 11, 2012

Dynamic Data Custom PageTemplates - A variation of Part 1 with the Details and SubGrid in Tabs

This article is the same as Part 1 in that it’s all the same code but with just a few changes. Here I’m going to include the DetailsView from Part 1 as the first tab of the tabbed sub grids.

The main changes are:

  • The Details and the SubGrids are all generated in the ITemplate class DetailsSubGridsTemplate
  • A new PageTemplate TabbedEditWithSubGrids.aspx now only has a FormView
  • New FieldTemplates have been added, TableDetail.ascx and TableForm.ascx
  • A new Template folder structure has been added to separate all the different template types

The Design

I thought I'd explain what I wanted to achieve and then show the code, so here goes.

What I wanted was the ability to have a compact form for dealing with related entities (i.e. a customer with all it’s related entities Orders, Addresses, Contacts etc) but I also wanted the main Form/Details to be customisable change the column and also be able to change the layout (to which end I’ve added two new FieldTemplates TableDetail.ascx and TableForm.ascx. TableDetail.ascx is just what you’d expect and is the same in appearance as the Edit.aspx page, TableForm.ascx on the other hand is fully customisable via templates (User Controls as mentioned in the previous article Part 1).

So we should end up with something like this:

Figure 1 - TableDetail.ascx

Figure 2 - TableForm.ascx

As you can see Figure 1 is based on the standard Edit.aspx page template and Figure 2 is based on a custom template defined for each table. At runtime the ITemplate class DetailsSubGridsTemplate checks to see if a template exists for the current table and then gives the DynamicControl a UIHint for either TableDetail of TableForm.

Hope that makes sense.

The Implementation of ITemplate

using System;

using System.Collections.Generic;

using System.Linq;

using System.Web;

using System.Web.UI;

using System.Web.DynamicData;

using System.Web.UI.WebControls;

using AjaxControlToolkit;

using System.IO;

 

/// <summary>

/// Creates an item template that renders any children columns in the passed in table as GridViews

/// </summary>

public class DetailsSubGridsTemplate : ITemplate

{

    private MetaTable _table;

    private Page _page;

 

    public DetailsSubGridsTemplate(MetaTable table, Page page)

    {

        _table = table;

        _page = page;

    }

 

    public void InstantiateIn(Control container)

    {

        IParserAccessor acessor = container;

        // get all the children columns

        var subGridTables = from c in _table.Columns.OfType<MetaChildrenColumn>()

                            select new SubDetails()

                            {

                                Column = c,

                                SubGridMetaData = c.Attributes.OfType<SubGridViewsAttribute>().FirstOrDefault(),

                                Order = c.Attributes.OfType<SubGridViewsAttribute>().FirstOrDefault() != null

                                && c.Attributes.OfType<SubGridViewsAttribute>().FirstOrDefault().Order > 0

                                ? c.Attributes.OfType<SubGridViewsAttribute>().FirstOrDefault().Order

                                : int.MaxValue,

                            };

 

        // sort the according to Order first and column name second

        // note if SubGridViewsAttribute is not allied or the attrivute

        // has no value for Order then just sort but column name

        subGridTables = from sg in subGridTables

                        orderby sg.Order, sg.Column.Name

                        select sg;

 

        // make sure there are some children columns

        if (subGridTables.Count() > 0)

        {

            // create tab container to hold each children column

            var tabContainer = new TabContainer();

            tabContainer.ID = "tabContainer";

 

            // enable auto poastback so selected tab is remembered

            //tabContainer.AutoPostBack = true;

            tabContainer.EnableViewState = true;

 

            // add the tab container to the page

            acessor.AddParsedSubObject(tabContainer);

 

            // Add DetailsView =======================================================================

            // add tab pannel to hold the parent table

            var tabPanelParent = new TabPanel();

            tabPanelParent.ID = "tpParent" + _table.Name;

 

            // add the tab panel

            tabContainer.Tabs.Add(tabPanelParent);

 

            // set the tab header maybe later add an attribute?

            tabPanelParent.HeaderText = _table.DisplayName;

 

            //Instantiate a DynamicControl for this Children Column

            var parentDetails = new DynamicControl(DataBoundControlMode.Edit)

            {

                ID = "tpParent",

 

                // set data field to the first column name

                // any column will do not used

                DataField = _table.Columns[0].Name

            };

 

            // if a template exists then use the TableForm control

            String itemTemplate = _table.Model.DynamicDataFolderVirtualPath + "Templates/FormViewEdit/" + _table.Name + ".ascx";

            String path = _page.Server.MapPath(itemTemplate);

            if (File.Exists(path))

                parentDetails.UIHint = "TableForm";

            else

                // else use TableDetail

                parentDetails.UIHint = "TableDetail";

 

            // add the DynamicControl to the tab panel

            tabPanelParent.Controls.Add(parentDetails);

            // End DetailsView =======================================================================

 

            // add a tab panel for each children table

            foreach (SubDetails SubGridDetails in subGridTables)

            {

                var tabPanel = new AjaxControlToolkit.TabPanel();

                tabPanel.ID = "tp" + SubGridDetails.Column.Name;

 

                // add the tab panel

                tabContainer.Tabs.Add(tabPanel);

 

                var subGridAttributes = SubGridDetails.Column.Attributes.OfType<SubGridViewsAttribute>().SingleOrDefault();

                // set the Tab's name to be the tables display name

                // or table Name if no attribute is present

                if (subGridAttributes != null && subGridAttributes.TabName.Length > 0)

                    tabPanel.HeaderText = subGridAttributes.TabName;

                else

                    tabPanel.HeaderText = SubGridDetails.Column.ChildTable.DisplayName;

 

                //Instantiate a DynamicControl for this Children Column

                var childrenGrid = new DynamicControl(DataBoundControlMode.Edit)

                {

                    ID = SubGridDetails.Column.Name,

 

                    // set UIHint

                    UIHint = "ChildrenGrid",

 

                    // set data field to column name

                    DataField = SubGridDetails.Column.Name

                };

 

                // add the DynamicControl to the tab panel

                tabPanel.Controls.Add(childrenGrid);

            }

            // set the tab pannels index to 0 which

            // forces the first tab to be selected

            if (!_page.IsPostBack)

                tabContainer.ActiveTabIndex = 0;

        }

        else

        {

            // Add DetailsView =======================================================================

            //Instantiate a DynamicControl for this Children Column

            var parentDetails = new DynamicControl(DataBoundControlMode.Edit)

            {

                ID = "tpParent",

 

                // set data field to the first column name

                // any column will do not used

                DataField = _table.Columns[0].Name

            };

 

            // if a template exists then use the TableForm control

            String itemTemplate = _table.Model.DynamicDataFolderVirtualPath + "Templates/FormViewEdit/" + _table.Name + ".ascx";

            String path = _page.Server.MapPath(itemTemplate);

            if (File.Exists(path))

                parentDetails.UIHint = "TableForm";

            else

                // else use TableDetail

                parentDetails.UIHint = "TableDetail";

 

            // add the DynamicControl to the tab panel

            acessor.AddParsedSubObject(parentDetails);

            // End DetailsView =======================================================================

 

            // if no children columns

            // add label to show no grids

            var label = new Label();

            label.Text = "There are no SubGrids";

            label.CssClass = "droplist";

 

            // add the label to the page

            acessor.AddParsedSubObject(label);

        }

    }

 

    private class SubDetails

    {

        /// <summary>

        /// Column to display

        /// </summary>

        public MetaChildrenColumn Column { get; set; }

 

        /// <summary>

        /// MetaData if any from the original column

        /// </summary>

        public SubGridViewsAttribute SubGridMetaData { get; set; }

 

        /// <summary>

        /// Holds the sort order value

        /// </summary>

        public int Order { get; set; }

    }

}

Listing 1 – DetailsSubGridsTemplate ITemplate class

As you can see the bulk of the class is the same as the ITemplate class from the previous article (Part 6) the two main changes are, one is the tests for the number of records in the DetailsSubGridsTemplate, this is because there are always tabs if there is one or more children tables. And the second change is the addition of the code to add the Details/Form of the parent table to the first tab or if not children tables are present to the page.

Field Templates

<%@ Control

    Language="C#"

    CodeFile="TableDetail.ascx.cs"

    Inherits="TableDetailField" %>

 

<asp:DynamicDataManager

    ID="DynamicDataManager1"

    runat="server"

    AutoLoadForeignKeys="true" />

   

<asp:ValidationSummary ID="ValidationSummary1"

    D="ValidationSummary1"

    runat="server"

    EnableClientScript="true"

    HeaderText="List of validation errors" />

   

<asp:DynamicValidator

    runat="server"

    ID="DetailsViewValidator"

    ControlToValidate="DetailsView1"

    Display="None" />

   

<asp:DetailsView

    ID="DetailsView1"

    runat="server"

    DataSourceID="DetailsDataSource"

    DefaultMode="Edit"

    AutoGenerateEditButton="True"

    OnItemCommand="DetailsView1_ItemCommand"

    OnItemUpdated="DetailsView1_ItemUpdated"

    CssClass="detailstable"

    FieldHeaderStyle-CssClass="bold">

</asp:DetailsView>

 

<asp:LinqDataSource

    ID="DetailsDataSource"

    runat="server"

    EnableUpdate="true">

    <WhereParameters>

        <asp:DynamicQueryStringParameter />

    </WhereParameters>

</asp:LinqDataSource>

Listing 2 - TableDetail.ascx

using System;

using System.Linq;

using System.Web.DynamicData;

using System.Web.UI.WebControls;

 

public partial class TableDetailField : FieldTemplateUserControl

{

    protected MetaTable table;

 

    public String[] DisplayColumns { get; set; }

 

    protected void Page_Init(object sender, EventArgs e)

    {

        DynamicDataManager1.RegisterControl(DetailsView1);

        table = DetailsDataSource.GetTable();

 

        var attribute = Table.Attributes.OfType<ShowColumnsAttribute>().SingleOrDefault();

 

        if (attribute != null)

            DisplayColumns = attribute.DisplayColumns;

 

        DetailsView1.RowsGenerator = new FieldTemplateRowGenerator(table, DisplayColumns);

    }

 

    protected void DetailsView1_ItemCommand(object sender, DetailsViewCommandEventArgs e)

    {

        if (e.CommandName == DataControlCommands.CancelCommandName)

        {

            Response.Redirect(table.ListActionPath);

        }

    }

 

    protected void DetailsView1_ItemUpdated(object sender, DetailsViewUpdatedEventArgs e)

    {

        if (e.Exception == null || e.ExceptionHandled)

        {

            Response.Redirect(table.ListActionPath);

        }

    }

}

Listing 3 - TableDetail.ascx.cs

Listings 2 and 3 comprise the TableDetail FieltTempalte which uses the DisplayColumns property of the ShowColumnsAttribute to determin which columns to show.

<%@ Control

    Language="C#"

    CodeFile="TableForm.ascx.cs"

    Inherits="TableDetailField" %>

 

<asp:DynamicDataManager

    ID="DynamicDataManager1"

    runat="server"

    AutoLoadForeignKeys="true" />

   

<asp:ValidationSummary

    ID="ValidationSummary1"

    runat="server"

    EnableClientScript="true"

    HeaderText="List of validation errors" />

   

<asp:DynamicValidator

    runat="server"

    ID="DetailsViewValidator"

    ControlToValidate="FormView1"

    Display="None" />

 

<asp:FormView

    ID="FormView1"

    DefaultMode="Edit"

    DataSourceID="FormDataSource"

    OnItemCommand="Edit_ItemCommand"

    OnItemUpdated="Edit_ItemUpdated"

    runat="server">

    <ItemTemplate>

    </ItemTemplate>

    <EditItemTemplate>

    </EditItemTemplate>

</asp:FormView>

 

<asp:LinqDataSource

    ID="FormDataSource"

    runat="server"

    EnableDelete="true">

</asp:LinqDataSource>

Listing 4 – TableForm.ascx

using System;

using System.IO;

using System.Linq;

using System.Web.DynamicData;

using System.Web.UI.WebControls;

 

public partial class TableDetailField : FieldTemplateUserControl

{

    protected MetaTable table;

 

    public String[] DisplayColumns { get; set; }

 

    protected void Page_Init(object sender, EventArgs e)

    {

        DynamicDataManager1.RegisterControl(FormView1);

 

        table = FormDataSource.GetTable();

 

        var attribute = Column.Attributes.OfType<ShowColumnsAttribute>().SingleOrDefault();

 

        if (attribute != null)

            DisplayColumns = attribute.DisplayColumns;

 

        // load item template

        table = FormDataSource.GetTable();

        String itemTemplate = table.Model.DynamicDataFolderVirtualPath + "Templates/FormViewEdit/" + table.Name + ".ascx";

        if (File.Exists(Server.MapPath(itemTemplate)))

        {

            FormView1.EditItemTemplate = LoadTemplate(itemTemplate);

        }

        else

        {

            throw new InvalidOperationException("The TableForm FieldTemplate requires an EditItemTamplate in the " + table.Model.DynamicDataFolderVirtualPath + "Template/FormViewEdit folder");

        }

    }

 

    protected void Edit_ItemUpdated(object sender, FormViewUpdatedEventArgs e)

    {

        if ((e.Exception == null))

        {

            Edit_RedirectToList();

        }

    }

 

    protected void Edit_ItemCommand(object sender, FormViewCommandEventArgs e)

    {

        if ((e.CommandName == DataControlCommands.CancelCommandName))

        {

            Edit_RedirectToList();

        }

    }

 

    protected void Edit_RedirectToList()

    {

        MetaTable table = FormDataSource.GetTable();

        String returnUrl = Request.QueryString["returnUrl"];

 

        if ((returnUrl == null))

        {

            Response.Redirect(table.ListActionPath);

        }

        else

        {

            Response.Redirect(returnUrl);

        }

    }

}

Listing 5 – TableForm.ascx.cs

Listings 4 and 5 makeup the TableForm FieldTemplate which requires a template in the ~/DynamicData/Template/FormViewEdit folder otherwise it throws an InvalidOperationException.

Both the above FieldTemplates only require access to the MetaTable table to generate their output. The most notable feature is that they are both configured using the DynamicDataManager just like a PageTemplate.

A couple of TableForm template examples

<%@ Control

    Language="C#"

    AutoEventWireup="true"

    CodeFile="Employees.ascx.cs"

    Inherits="Employees" %>

   

<div style="width: 400px;">

    <p class="droplist" style="border: dotted 1px cyan;">

        <span style="display:inline-block; height: 16px; text-align: right; vertical-align: middle; width: 80px;">

            Title:&nbsp;

        </span>

        <span style="height: 16px; text-align: left; vertical-align: middle;">

            <asp:DynamicControl

                ID="Title"

                DataField="Title"

                Mode="Edit"

                runat="server">

            </asp:DynamicControl>

        </span>

    </p>

    <p class="droplist" style="border: dotted 1px cyan;">

        <span style="display:inline-block; height: 16px; text-align: right; vertical-align: middle; width: 80px;">

            First Name:&nbsp;

        </span>

        <span style="height: 16px; text-align: left; vertical-align: middle;">

            <asp:DynamicControl

                ID="FirstName"

                DataField="FirstName"

                Mode="Edit"

                runat="server">

            </asp:DynamicControl>

        </span>

    </p>

    <p class="droplist" style="border: dotted 1px cyan;">

        <span style="display:inline-block; height: 16px; text-align: right; vertical-align: middle; width: 80px;">

            Last Name:&nbsp;

        </span>

        <span style="height: 16px; text-align: left; vertical-align: middle;">

            <asp:DynamicControl

                ID="LastName"

                DataField="LastName"

                Mode="Edit"

                runat="server">

            </asp:DynamicControl>

        </span>

    </p>

    <div class="droplist">

    <asp:LinkButton

        ID="UpdateLinkButton"

        CommandName="Update"

        runat="server">

        Update

    </asp:LinkButton>&nbsp;

    <asp:LinkButton

        ID="CancelLinkButton"

        CausesValidation="false"

        CommandName="Cancel"

        runat="server">

        Cancel

    </asp:LinkButton>

    </div>

</div>

Listing 6 – Example 1 of a template for the Employees table

<%@ Control

    Language="C#"

    AutoEventWireup="true"

    CodeFile="Copy of Employees.ascx.cs"

    Inherits="Employees" %>

 

<table class="detailstable">

    <tr>

        <th>Title</th>

        <td><asp:DynamicControl

                ID="Title"

                DataField="Title"

                Mode="Edit"

                runat="server">

            </asp:DynamicControl></td>

    </tr>

    <tr>

        <th>First Name</th>

        <td><asp:DynamicControl

                ID="FirstName"

                DataField="FirstName"

                Mode="Edit"

                runat="server">

            </asp:DynamicControl></td>

    </tr>

    <tr>

        <th>Last Name</th>

        <td><asp:DynamicControl

            ID="LastName"

            DataField="LastName"

            Mode="Edit"

            runat="server">

        </asp:DynamicControl></td>

    </tr>

    <tr>

        <td colspan="2">

            <asp:LinkButton

                ID="UpdateLinkButton"

                CommandName="Update"

                runat="server">

                Update

            </asp:LinkButton>

            <asp:LinkButton

                ID="CancelLinkButton"

                CausesValidation="false"

                CommandName="Cancel"

                runat="server">

                Cancel

            </asp:LinkButton>

        </td>

    </tr>

</table>

Listing 7 – Example 2 of a template for the Employees table

Listing 6 and 7 are the FieldTemplates used in Figures 1 and 2.

 

No comments:

Post a Comment