Monday, 28 July 2008

Dynamic Data and Field Templates - An Advanced FieldTemplate

  1. The Anatomy of a FieldTemplate.
  2. Your First FieldTemplate.
  3. An Advanced FieldTemplate.
  4. A Second Advanced FieldTemplate.
  5. An Advanced FieldTemplate with a GridView.
  6. An Advanced FieldTemplate with a DetailsView.
  7. An Advanced FieldTemplate with a GridView/DetailsView Project

Well I say advanced... well here we go we are going to use the AutoCompleteFilter web service from Dynamic Data Futures example to make an AutoCompleteText_Edit FieldTemplate. What this will do is look up values in the column that we are editing for matches i.e. if we had a column for category names and we typed in con we may get list like Condiments, Confections, Containers etc.

Note: This does NOT work with foreign key fields it’s a text field only thing

I’ll be working with file based website project so there will be changes to make when we use the bits from the Dynamic Data Futures.

Step 1 – adding the AutocompleteFilter.asmx files and AutocompleteStyle.css

Copy the AutocompleteFilter.asmx and AutocompleteStyle.css to the root of the website and copy the AutocompleteFilter.asmx.cs to the App_Code folder.

Now we need to edit the AutocompleteFilter.asmx file:

<%@ WebService 
    Language="C#" 
    CodeBehind="~/AutocompleteFilterService.cs" 
    Class="DynamicDataFuturesSample.AutocompleteFilterService" %>

Listing 1 – the AutocompleteFilter.asmx

We need to remove the DynamicDataFuturesSample. namespace from the Class="DynamicDataFuturesSample.AutocompleteFilterService" so it reads Class="AutocompleteFilterService".

namespace DynamicDataFuturesSample
{

Listing 2 – namespace from the AutocompleteFilter.asmx.cs

Now remove the surrounding namespace from the AutocompleteFilter.asmx.cs file including the last “}” brace.

You can save and close both files.

Now to add the AutocompleteFilter style sheet to the master page and add the AutocompleteStyle.css style sheet to the head section of the page

<head runat="server">
    <title>Dynamic Data Site</title>
    <link href="~/Site.css" rel="stylesheet" type="text/css" />
    <link href="~/AutocompleteStyle.css" rel="stylesheet" type="text/css" />
</head>

Listing 3 – head section of the Site.master page with the AutocompleteStyle.css style sheet added

Step 2 – Adding a reference to the Dynamic Data Futures

You can achieve this in one of two ways

  1. Add a reference to the DLL created when the Dynamic Data Futures was compiled. Right mouse click on the root of the website and choose Add Reference and then click the browse tab and browse to the DLL and click OK.
  2. From the File menu add –> Existing Project and choose the Dynamic Data Futures project. When added, choose add reference from the root of the website and select the projects tab, choose Dynamic Data Futures project and click OK (useful when debugging).

Step 3 – Converting the Autocomplete.ascx from Daynamic Data Futures

Copy the Autocomplete.ascx and Autocomplete.ascx.cs files from the ~/DynamicData/Filter folder in the DynamicDataFuturesSample website to the ~/DynamicData/FieldTemplates folder in our website and then rename to TextAutocomplete_Edit.

Now as in step 1 we need to remove the namespace from the copied files:

<%@ Control 
    Language="C#" 
    AutoEventWireup="true"
    Inherits="DynamicDataFuturesSample.Autocomplete_Filter" 
    Codebehind="Autocomplete.ascx.cs" %>

Listing 4 – Control  tag of Autocomplete.ascx file

So remove the DynamicDataFuturesSample. from the Inherits property but also this time rename the Codebehind to CodeFile and change the name of the class and CodeFile/Codebehind see below

<%@ Control 
    Language="C#" 
    AutoEventWireup="true"
    Inherits="TextAutocomplete_EditField" 
    CodeFile="TextAutocomplete_Edit.ascx.cs" %>

Listing 5 – Control  tag of TextAutocomplete_Edit.ascx file

namespace DynamicDataFuturesSample
{
    public partial class Autocomplete_Filter : FilterUserControlBase
    {

Listing 6 - TextAutocomplete_Edit.ascx.cs file

Change Listing 6 to look like Listing 7 remembering to remove the closing brace “}” of the namespace.

public partial class TextAutocomplete_EditField : FieldTemplateUserControl
{

Listing 7 – Altered TextAutocomplete_Edit.ascx.cs file

Note the change of FilterUserControlBase to FieldTemplateUserControl.

Now go to the bottom of the TextAutocomplete_Edit.ascx file.

<script type="text/javascript">
    // Work around browser behavior of "auto-submitting" simple forms
    var frm = document.getElementById("aspnetForm");
    if (frm) {
        frm.onsubmit = function() { return false; };
    }
</script>
<%-- Prevent enter in textbox from causing the collapsible panel from operating --%>
<input type="submit" style="display:none;" />

Listing 8 – End of the TextAutocomplete_Edit.ascx

You will need to remove script altogether as it stops the LinkButtons on the DetailsView from posting back.

<%-- Prevent enter in textbox from causing the collapsible panel from operating –%>
<input type="submit" style="display:none;" />

Listing 9 – Modified end of the TextAutocomplete_Edit.ascx

Remove the Clear button from the user control

<asp:TextBox runat="server" ID="AutocompleteTextBox" Width="300" autocomplete="off"  />
<asp:HiddenField runat="server" ID="AutocompleteValue" />
<asp:Button runat="server" ID="ClearButton" OnClick="ClearButton_Click" Text="Clear" />
<ajaxToolkit:AutoCompleteExtender

Listing 10 – Remove the Clear Button
<asp:TextBox runat="server" ID="AutocompleteTextBox" Width="300" autocomplete="off"  />
<asp:HiddenField runat="server" ID="AutocompleteValue" />
<ajaxToolkit:AutoCompleteExtender

Listing 11 – Clear Button Removed

You can save and close the TextAutocomplete_Edit.ascx file

Now to hack the rest of the code in the code behind file.

Delete the following from the end of the control class:

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

public override string SelectedValue
{
    get
    {
        return String.IsNullOrEmpty(AutocompleteValue.Value) ? null : AutocompleteValue.Value;
    }
}

Listing 12 – bits to remove from the end of the code behind

Now to finish the hacking :D

Remove the following from the Page_Init event handler

if (!Page.IsPostBack && !String.IsNullOrEmpty(InitialValue))
{
    // set the initial value of the filter if it's present in the request URL

    MetaTable parentTable = fkColumn.ParentTable;
    IQueryable query = parentTable.GetQuery();
    // multi-column PK values are seperated by commas
    var singleCall = LinqExpressionHelper.BuildSingleItemQuery(query, parentTable, InitialValue.Split(','));
    var row = query.Provider.Execute(singleCall);
    string display = parentTable.GetDisplayString(row);

    AutocompleteTextBox.Text = display;
    AutocompleteValue.Value = InitialValue;
}

Listing 13 – delete from Page_Init

Edit the remaining code to look like this adding the ExtractValues and DataControl methods and the OnDataBinding event handler:

public partial class TextAutocomplete_EditField : FieldTemplateUserControl
{
    protected void Page_Init(object sender, EventArgs e)
    {
        autoComplete1.ContextKey = AutocompleteFilterService.GetContextKey(Table);

        // modify behaviorID so it does not clash with other autocomplete extenders on the page
        autoComplete1.Animations = autoComplete1.Animations.Replace(autoComplete1.BehaviorID, AutocompleteTextBox.UniqueID);
        autoComplete1.BehaviorID = AutocompleteTextBox.UniqueID;
    }

    protected override void OnDataBinding(EventArgs e)
    {
        base.OnDataBinding(e);

        if (!String.IsNullOrEmpty(FieldValueString))
        {
            // set the initial value 
            AutocompleteTextBox.Text = FieldValueString;
        }
    }

    protected override void ExtractValues(System.Collections.Specialized.IOrderedDictionary dictionary)
    {
        dictionary[Column.Name] = AutocompleteTextBox.Text;
    }

    public override System.Web.UI.Control DataControl
    {
        get
        {
            return AutocompleteTextBox;
        }
    }
}

Listing 14 – Completed TextAutocomplete_Edit.ascx.cs

Use UIHint attribute to tell Dynamic Data to use TextAutocomplete for that field and run it hers what it should look like:

TextAutocomplete_Edit in action

Figure 1 – TextAutocomplete_Edit in action

Step 4 – Adding an Attribute

Here we will add an attribute to set the minimum number of characters typed before the auto complete is started (the default is 1 see MinimumPrefixLength in the TextAutocomplete_Edit.ascx file). Here’s the code for the attribute:

using System;

/// <summary>
/// Summary description for EnumerationTypeAttribute
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class TextAutocompleteAttribute : Attribute
{
    public TextAutocompleteAttribute(int prefixLength)
    {
        AutocompletePrefixLength = prefixLength;
    }

    public int AutocompletePrefixLength { get; set; }
}

Listing 15 – TextAutoCompleteAttribute

The code in Listing 15 has only one property AutocompletePrefixLength which we will use to set the MinimumPrefixLength property of the Ajax Toolkit AutoCompleteExtender.

Edit the TextAutocomplete_Edit.ascx.cs’s Page_Init event handler so it looks like this:

 

protected void Page_Init(object sender, EventArgs e)
{
    // get the AutocompletePrefixLength if present
    var prefixLength = Column.Attributes.OfType<TextAutocompleteAttribute>().SingleOrDefault();

    // if present set MinimumPrefixLength
    if (prefixLength != null)
        autoComplete1.MinimumPrefixLength = prefixLength.AutocompletePrefixLength;

    autoComplete1.ContextKey = AutocompleteFilterService.GetContextKey(Table);

    // modify behaviorID so it does not clash with other autocomplete extenders on the page
    autoComplete1.Animations = autoComplete1.Animations.Replace(autoComplete1.BehaviorID, AutocompleteTextBox.UniqueID);
    autoComplete1.BehaviorID = AutocompleteTextBox.UniqueID;
}

Listing 16 – adding the code to read the attribute

The first line of code gets the attribute and then the IF statement tests to se if the value is not null and if so sets the AutoCompleteExtenders MinimumPrefixLength to it’s value.

That’s it on this one, until I get the time to write a FieldTemplate to handle SQL Server 2008’s Geography data type via Virtual Earth.

4 comments:

Anonymous said...

Hi Steve,
I thought I'd ask your advise on an isssue with Dynamic data that I think may be related to Field templates. Bear with me while I try to explain........
I would like to be able to select a subset of a table and only at that stage let dynamic data look after the display and CRUD - which it does very well. I can half do this by amending the linqdatasource with a where clause (although I hate doing this). The problem is that when I go to update or insert a record in this dataset that it's foreign keys display all available values found in the primary table. This causes me a huge problem as I'm potentially showing options that could be sensitive in nature to everyone who can edit a record in that table. The solution as I see it is to create a new field Template that populates itself from (and only from) the foreign key data from the referencing table. I know that has obvious problems if the table were empty i.e. the drop down's would be also be empty. But in spite of that it's a workable solution for me. I had a look at the ForeignKey_Edit.ascx and I saw the call PopulateListControl(DropDownList1) which is the end of the raod before the dropdown gets magically populated. Do you know of any smarts that I could use to intercept the population of the dropdown. Or even suggest a completely diffent workaround.
Cheers,
Cormac

Steve said...

Hi Cormac
try this post here Custom Pages Part 5 - I18N? Internationalisation Custom Page here http://csharpbits.notaclue.net/2008/07/dynamic-data-custom-pages-part-5-i18n.html
In this post I have a FieldTemplate that is populated from CultureInfo and I populate the DropDownList in the OnDataBaound event handler, anyway have a look a the post :D
Steve

Anonymous said...

Hello Steve
I have a problem with step 2 of your guide.

1. Add a reference to the DLL created when the Dynamic Data Futures was compiled. Right mouse click on the root of the website and choose Add Reference and then click the browse tab and browse to the DLL and click OK.
I can´t compile Dynamic Data Futures for some reason. VS 2008 complains about the global.asax file...

2. From the File menu add –> Existing Project and choose the Dynamic Data Futures project. When added, choose add reference from the root of the website and select the projects tab, choose Dynamic Data Futures project and click OK (useful when debugging).
Dosent work for me either...

Do you have any suggestions on a workaround?

Best regards

Johnny

Steve said...

This is a little out of date now as there is ASP.Net 4.0 Preview 2 up on codeplex here: http://www.codeplex.com/aspnet/Release/ProjectReleases.aspx?ReleaseId=20628 you should look at this newer version of the Futures project.