Shredding, coding, arguing etc.

Asp .NET Form Item Suite Tutorial

Creating a CheckBoxListFormItem

26 September 2008

When I was creating my Form Item Suite, I wanted it to be super simple to add new controls to the suite. For example, my company started using the fantastic Obout WYSIWYG editor on a lot of CMS pages. It took me just 10 minutes to integrate the WYSIWYG into my suite.

For those of you interested in the suite, I thought that walking through some of the code would help you get your head around how it works.

Developing a CheckBoxListFormItem control

The first step is to create a new class called CheckBoxListFormItem and inherit the base FormItem class:

public class CheckBoxListFormItem : FormItem
{

The FormItem has a about seven abstract methods that must be overriden by the inheriting class.

I have Resharper installed, so I hit Alt-Insert, and select "Implement Missing Members". I then check all the abstract methods and click Finish.

This is what I have:

public class CheckBoxListFormItem : FormItem
{
   protected override void CreateInputControls()
   {
      throw new System.NotImplementedException();
   }

   protected override void PreRenderActions()
   {
      throw new System.NotImplementedException();
   }

   protected override void LoadAndPreRenderActions()
   {
      throw new System.NotImplementedException();
   }

   protected override Control RequiredControlToValidate
   {
      get { throw new System.NotImplementedException(); }
   }

   protected override Control AssociateLabelsWithControl
   {
      get { throw new System.NotImplementedException(); }
   }

   protected override string DefineClientSideClass()
   {
      throw new System.NotImplementedException();
   }

   public override string GetNewClientSideObject()
   {
      throw new System.NotImplementedException();
   }
}

Adding the CheckBoxList input control

The CheckBoxListFormItem class is basically just a wrapper around a regular old CheckBoxList class out of the standard .Net suite. Of course, I could use some fancier implementation from a third-party provider if I wanted, but let's just stick with the vanilla framework for now.

I'll make the CheckBoxList a private member of the class:

public class CheckBoxListFormItem : FormItem
{
   private CheckBoxList _CheckBoxList;

CreateInputControls

The CreateInputControls method is kind of like the CreateChildControls method from the CompositeControl class. In fact, the base FormItem class calls CreateInputControls during CreateChildControls.

I use this method to create an instance of _CheckBoxList and add it to the Panel that contains the input controls. At this stage I can also set any properties that I don't plan on allowing developers to change once the Init stage is complete. In this case I want to set the RepeatLayout to Flow so that there are no horrible tables rendered by the control.

protected override void CreateInputControls() {    _CheckBoxList = new CheckBoxList();    _CheckBoxList.ID = "cbl";    _CheckBoxList.RepeatLayout = RepeatLayout.Flow;    InputControlsContainer.Controls.Add(_CheckBoxList); }

PreRenderActions

This method is executed during the PreRender phase. At this point I like to set any properties that are purely to do with rendering the control, rather than properties that affect the way the control behaves on the server side:
protected override void PreRenderActions()
{
   _CheckBoxList.CssClass = "checkboxList";
   _CheckBoxList.AutoPostBack = AutoPostBack;
}

LoadAndPreRenderActions

This method is executed twice! It executes during the Load phase and then again during the PreRender phase.

The reason I execute it twice is to allow for properties to be changed or accessed at any time prior to the PreRender phase. The most common reason this needs to occur is when the Required property is changed. During server-side page validation, which occur before the PreRender phase, the Required property needs to be accessible, so it must be set during the Load phase. However, an event handler may execute after the Load phase and modify the Required property, so I want to set it again during the PreRender phase.

In general, the performance overhead is negligible, but obviously you want to avoid any long running tasks in this method.

For our CheckBoxListFormItem, nothing needs to happen during this method, so I can leave it empty:

protected override void LoadAndPreRenderActions()
{
}

RequiredControlToValidate

This property tells the base FormItem class which control I want the RequiredFieldValidator to validate (the base FormItem class always creates a RequiredFieldValidator).

Because the CheckBoxList cannot be validated by a RequiredFieldValidator (which makes sense given the nature of a CheckBoxList), I have to return null. In the future I could add a CustomValidator to my CheckBoxListFormItem that could perform some sort of validation - such as ensuring at least one item is checked.

protected override Control RequiredControlToValidate
{
   get { return null; }
}

AssociateLabelsWithControl

This property works in the same way as the RequiredControlToValidate : it tells the base FormItem class which control to associate the label with. For most browsers, this means that when the user clicks on the label, the cursor will automatically jump to the associated control. It also improves accessibility for blind users.

protected override Control AssociateLabelsWithControl
{
   get { return _CheckBoxList; }
}

DefineClientSideClass

OK, Javascript doesn't actually have classes, but usually I work with a Javascript framework such as Prototype, MooTools or JQuery which provide a way of creating what are essentially classes in Javascript.

To simplify things I have chosen not to use any special framework and just use normal Javascript for this suite. Perhaps in the future I will release a patch that uses a more sophisticated framework for client-side operations.

The DefineClientSideClass method gets the Javascript to render on the page that will create a simple wrapper for each of the controls within CheckBoxListFormItem. It looks like this:

protected override string DefineClientSideClass()
{
   return string.Concat(
      "function clsCheckBoxListFormItem(checkBoxList, requiredVal) {",
      "this.checkBoxList = $(checkBoxList); ",
      "this.requiredVal = $(requiredVal); }");
}

What this means, is that on the client-side I can now render Javascript that looks something like this:

<my:CheckBoxListFormItem ID="ctl00_someControl_cbl" runat="server" />

<script type="text/javascript">

var objCBL = new clsCheckBoxListFormItem('ctl00_someControl_cbl','ctl00_someControl_rval');
alert(objCBL.checkBoxList.id);
alert(objCBL.requiredVal.innerHTML);

</script>

Note that this method is only called if the EnableClientSide property is set to true. This prevents unnecessary javascript from being rendered on the client side.

GetNewClientSideObject

As just mentioned, once EnableClientSide, a javascript 'class' will be defined that allows easy access to the controls wrapped up within the FormItem. The GetNewClientSideObject method returns the javascript that creates a new instance of this 'class':

public override string GetNewClientSideObject()
{
   return string.Format("new clsCheckBoxListFormItem('{0}','{1}')",
      _CheckBoxList.ClientID, RequiredValidator.ClientID);
}

So if we wanted to rewrite the Javascript example above on the aspx page we could write:

<my:CheckBoxListFormItem ID="myCheckBoxListFormItem" runat="server" />

<script type="text/javascript">
var objCBL = <%# myCheckBoxList.GetNewClientSideObject() %>;
alert(objCBL.checkBoxList.id);
alert(objCBL.requiredVal.innerHTML);
</script>

Exposing the CheckBoxList items

Next we want to expose the items of the underlying CheckBoxList:

[PersistenceMode(PersistenceMode.InnerProperty)]
public ListItemCollection Items
{
   get
   {
      EnsureChildControls();
      return _CheckBoxList.Items;
   }
}

Setting up databinding

Finally, I want a way to databind the control.

For my FormItems I have developed a new technique that can prevent unnecessary data from being stored in ViewState. To read up on the problem I am trying to solve, please read this article.

The idea is that if the ListItem are added to the controls Item collection before the control is added to CompositeControl's control collection, then the ListItems won't be stored in ViewState. This is some very quirky behaviour on the part of .Net.

The code I have used in the FormItem Suite exploits this behaviour by attempting to performing DataBind prior to CreateChildControls finishing. Normally this means that if the developer calls DataBind very early in the page lifecycle, for example during Page_Init, then the FormItem control won't store the ListItems in ViewState. In almost all cases I find this behaviour preferable.

Here's the relevant parts of the completed code, with DataBinding enabled:

public class CheckBoxListFormItem : FormItem
{

   private CheckBoxList _CheckBoxList;
   private DataBindingOptions _DataBindingOptions;

   [PersistenceMode(PersistenceMode.InnerProperty)]
   public ListItemCollection Items
   {
      get
      {
         EnsureChildControls();
         return _CheckBoxList.Items;
      }
   }

   public object DataSource
   {
      set
      {
         if (_DataBindingOptions == null)
         {
            _DataBindingOptions = new DataBindingOptions(value);
         }
         else
         {
            _DataBindingOptions.DataSource = value;
         }
      }
   }

   public string DataTextField
   {
      set
      {
         if (_DataBindingOptions == null)
         {
            _DataBindingOptions = new DataBindingOptions();
         }

         _DataBindingOptions.DataTextField = value;
      }
   }

   public string DataValueField
   {
      set
      {
         if (_DataBindingOptions == null)
         {
            _DataBindingOptions = new DataBindingOptions();
         }

         _DataBindingOptions.DataValueField = value;
      }
   }

   protected override void CreateInputControls()
   {
      _CheckBoxList = new CheckBoxList();
      _CheckBoxList.ID = "cbl";
      _CheckBoxList.RepeatLayout = RepeatLayout.Flow;

      if (_DataBindingOptions != null)
      {
         DataBindListControl(_CheckBoxList, _DataBindingOptions);
      }

      InputControlsContainer.Controls.Add(_CheckBoxList);
   }

   public override void DataBind()
   {
      if (_DataBindingOptions != null && _CheckBoxList != null)
      {
         DataBindListControl(_CheckBoxList, _DataBindingOptions);
      }
   }

   public void DataBind(DataBindingOptions options)
   {
      _DataBindingOptions = options;
      DataBind();
   }
}

Comments

 
   
Stupidity filter

Unfortunately, the internet is rife with automaton zombies that fill up blog threads with rubbish. There are also computer scripts that do this too. Please answer the following question.

Waht is the nmae of the plnaet clsoset to the cneter of the sloar ssyetm?    
  •  
  • del.icio.us  del.icio.us
  • reddit  reddit
  • StumbleUpon  StumbleUpon