blog

Articles about web development from the consultants at sourcemotion limited.

Monday, October 27, 2008

Using the ASP.NET CompareValidator control to validate date input

Stumbled across this tip the other day:

http://dotnettipoftheday.org/tips/validate-format-of-integer-double-date-currency.aspx

It turns out that you can validate date inputs with a CompareValidator by setting the Operator property to DataTypeCheck and ValidationDataType to Date (or Integer, Double, Currency, etc.).

This was a new one on me, I had always assumed that the CompareValidator was just used for comparing the value of a control with another control (or a constant value) which is what the name (and MSDN) would suggest. I don't know why Microsoft didn't create a separate control called a DataTypeValidator with this functionality rather than hide it within the CompareValidator.

Labels: , ,

Wednesday, June 18, 2008

Customizing the Subsonic scaffold control

The Subsonic data access layer comes with a control called a Scaffold (the Scaffold concept comes from Ruby on Rails apparently) which allows you to quickly build a simple administration interface for your database tables.

The basic syntax is as follows:

<cc1:Scaffold ID="Scaffold1" runat="server" TableName="Products"></cc1:Scaffold>

Where SubsonicProvider is the name of the SubSonicService Provider from your web.config and TableName is the table you want to edit. The control handles rendering a gridview and a form for editing the fields in your table. By default the control will display all fields from the table in both the grid view and detail view. If you have a big table this quite quickly becomes unwieldy, particularly in the case of the grid view. The control provides two properties HiddenGridColumns and HiddenEditorColumns which allow you to specify which columns to hide. Place a comma-delimited list of the fields you want to hide in each property.

If you are providing an administration interface that allows your users to maintain content on their website then it is quite likely you will want to be able to present them with a XHTML editor for the appopriate fields. To achieve this I customized the original Scaffold control to include another property RichTextColumns. Again this is a comma-separated list that contains the fields in the table that you would like to present with a rich text editor.

My open source XHTML editor of choice is FCKEditor which is easy to configure and install and seems to create decent XHTML.

In the fields declaration I've added the following beneath the declaration of the other internal string lists:


private readonly List _richTextColumnList = new List();
In the BindEditor method between:




if(ctrlType == typeof(TextBox))
{
...
}
and




else if(ctrlType == typeof(CheckBox))
{
...
}
I've added the following:





else if (ctrlType == typeof(FredCK.FCKeditorV2.FCKeditor))
{
FredCK.FCKeditorV2.FCKeditor editor = (FredCK.FCKeditorV2.FCKeditor)control2;
editor.Value = reader[column.ColumnName].ToString();
}




Then in the GetEditorControl method in the section that deals with string fields I've added the part that creates the XHTML editor:



switch (col.DataType)
{
case DbType.Guid:
case DbType.AnsiString:
case DbType.String:
case DbType.StringFixedLength:
case DbType.Xml:
case DbType.Object:
case DbType.AnsiStringFixedLength:
if (Utility.IsMatch(colName, ReservedColumnName.CREATED_BY) Utility.IsMatch(colName, ReservedColumnName.MODIFIED_BY))
{
cOut = new Label();
}
else
{
if (_richTextColumnList.Contains(colName))
{
FredCK.FCKeditorV2.FCKeditor editor = new FredCK.FCKeditorV2.FCKeditor();
editor.ToolbarStartExpanded = false;
editor.ForcePasteAsPlainText = true;
editor.Height = 400;
editor.UseBROnCarriageReturn = true;
cOut = editor;
}
else
{

TextBox t = new TextBox();
if (Utility.GetEffectiveMaxLength(col) > 250)
{
t.TextMode = TextBoxMode.MultiLine;
t.Columns = 60;
t.Rows = 4;
}
else
{
t.Width = Unit.Pixel(250);
if (colName.EndsWith("guid"))
{
t.Text = Guid.NewGuid().ToString();
t.Enabled = false;
}
}
cOut = t;
}
}
break;

Then finally I've added the RichTextColumns property which works in a similar way to the other HiddenEditorColumns and HiddenGridColumns properties:



[DefaultValue(""), Description("A comma delimited list of column names which are displayed in the Editor using an XHTML editor."), Bindable(true), Category("Data")]
public string RichTextColumns
{
set
{
this._richTextColumnList.Clear();
foreach (string str in Utility.Split(value))
{
this._richTextColumnList.Add(str.ToLower());
}
}
}




And that's it! This has enabled us to quickly produce simple database administration interfaces for tables that store XHTML content.

Labels: , , , ,

Friday, March 21, 2008

Validating a CheckBoxList with an ASP.NET validation control

If you try to use the standard ASP.NET validation controls to validate a CheckBoxList control you may receive an error message that looks like this:

Control 'CheckBoxList1' referenced by the ControlToValidate property of 'RequiredFieldValidator1' cannot be validated

Unfortunately out of the box the RequiredFieldValidator will not work on the CheckBoxList control. In a recent project I had a requirement to validate a number of checkbox lists. I needed to validate that at least one item was checked and in some cases make sure that no more than a certain number of options were selected. I created a new validator control specifically for validating CheckBoxLists to handle this.

Here is a demo of the control in action:



http://www.sourcemotion.com/CheckBoxListTest.aspx


Here is the source code for the validator control:


using System;
using System.Data;
using System.Configuration;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;

///
/// Validation control for use with the CheckBoxList control
///
namespace Validators
{
public class CheckBoxListValidator : CustomValidator
{
private const string CheckBoxListValidatorScriptKey = "CheckBoxListValidator_GlobalScript";
private bool _isRequired;
private int _maximumChecked;
private CheckBoxList _listctrl;

public CheckBoxListValidator()
{
_isRequired = true;
_maximumChecked = -1;
}

public int MaximumChecked
{
get
{
return _maximumChecked;
}
set
{
_maximumChecked = value;
}
}

public bool IsRequired
{
get
{
return _isRequired;
}
set
{
_isRequired = value;
}
}

protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
if (this.EnableClientScript && !this.Page.ClientScript.IsClientScriptBlockRegistered(this.Page.GetType(), CheckBoxListValidatorScriptKey))
{
StringBuilder script = new StringBuilder();
script.AppendLine("function fixCheckboxValidationEvent(id)");
script.AppendLine("{");
script.AppendLine(" if (document.getElementById(id))");
script.AppendLine(" {");
script.AppendLine(" var checkboxList = document.getElementById(id);");
script.AppendLine(" if (checkboxList.getElementsByTagName)");
script.AppendLine(" {");
script.AppendLine(" var inputs = checkboxList.getElementsByTagName(\"input\");");
script.AppendLine(" for (j=0;j -1) && (selectedCount > maxCount)) ");
script.AppendLine(" {");
script.AppendLine(" args.IsValid = false;");
script.AppendLine(" } ");
script.AppendLine(" else ");
script.AppendLine(" {");
script.AppendLine(" args.IsValid = true;");
script.AppendLine(" }");
script.AppendLine(" }");
script.AppendLine("}");
// This script should only be included once...
this.Page.ClientScript.RegisterClientScriptBlock(Page.GetType(), CheckBoxListValidatorScriptKey, script.ToString(), true);
}
if (this.EnableClientScript && !this.Page.ClientScript.IsStartupScriptRegistered(this.GetType(), this.ClientID))
{
this.Page.ClientScript.RegisterStartupScript(this.GetType(), this.ClientID, GetStartupScript(), true);
}
}

protected string GetScript()
{
return String.Format("var {0}_maxChecked = {1};\r\nvar {0}_isRequired = {2};\r\n",
_listctrl.ClientID,
_maximumChecked,
_isRequired.ToString().ToLower());
}

protected string GetStartupScript()
{
return String.Format("fixCheckboxValidationEvent('{0}');\r\n", _listctrl.ClientID);
}

protected override bool ControlPropertiesValid()
{
Control ctrl = FindControl(ControlToValidate);
if (ctrl != null)
{
if (ctrl is CheckBoxList)
{
_listctrl = (CheckBoxList)ctrl;
if (this.EnableClientScript)
{
this.Page.ClientScript.RegisterClientScriptBlock(this.GetType(), this.ClientID, GetScript(), true);
this.ClientValidationFunction = "validateCheckBoxList";
}
return true;
}
else
{
return false; // raise exception
}
}
else
return false; // raise exception
}

protected override bool EvaluateIsValid()
{
int selectedCount = 0;
foreach (ListItem item in _listctrl.Items)
{
if (item.Selected)
{
selectedCount++;
}
}
if (selectedCount == 0 && _isRequired)
{
return false;
}
else if (_maximumChecked != -1 && selectedCount > _maximumChecked)
{
return false;
}
return true;
}
}
}





Notes:


  1. In the CheckBoxListValidator class I override the ControlPropertiesValid method. This is the method that normally throws an exception if you try to assign a CheckboxList control to the ControlToValidate property of a RequiredFieldValidator control. This method now checks to ensure that the ControlToValidate property is pointing at a CheckboxList control.
  2. The Javascript function fixCheckboxValidationEvent gets called for each CheckBoxList control and swaps the standard client validation function from the onchange event to the onclick event. By default validation is called on change but for the checkbox you actually need it on click.

Labels: , ,