Showing posts with label Validation. Show all posts
Showing posts with label Validation. Show all posts

Sunday, 18 August 2013

Adding Validation Using Bootstrap Popover

I noticed an article by Damien Edwards called Updating the ASP.NET validator controls to change invalid control’s CSS class on non-JavaScript clients this got me thinking could I create an Control Adapter that would work for ALL Validators and would take advantage of the Bootstrap Tooltip or Popover JavaScript effects, Figure 1 is what I came up with.

Bootstrap Tooltip in action

Figure 1- Bootstrap Popover in action

So how did I do it, well I’m not entirely sure this is good practice, I’m using a Control Adapter to get the the Pre-Render event and then I get the the control as a BaseValidator get access to the ControlToValidate the about 30 lines of code and we have the above for any Validator.

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

var validator = Control as BaseValidator;
var controlType = Control.GetType().ToString();
var stringType = controlType.Substring(controlType.LastIndexOf(".") + 1, controlType.Length - controlType.LastIndexOf(".") - 1);

// build unique CCS class per validator type
var validatorError = String.Format("{0}-{1}", stringType, ERROR_CLASS);
var controlToValidate = Control.NamingContainer.FindControl(validator.ControlToValidate) as WebControl;
if (controlToValidate != null)
{
if (validator.IsValid)
{
// remove validator class
var className = String.Join(" ", controlToValidate.CssClass.Split(' ').Where(c => !c.Equals(validatorError)).ToArray());
controlToValidate.CssClass = className;
}
else
{
// add validator class
if (controlToValidate.CssClass.Split(' ').FirstOrDefault(c => c.Equals(validatorError)) == null)
controlToValidate.CssClass = String.IsNullOrEmpty(controlToValidate.CssClass)
? validatorError
: String.Format("{0} {1}", controlToValidate.CssClass, validatorError);

// add tooltip
controlToValidate.Attributes.Add("data-placement", "right");
controlToValidate.Attributes.Add("data-toggle", "popover");
controlToValidate.Attributes.Add("data-trigger", "hover");
controlToValidate.Attributes.Add("data-delay", "500");

// add title
controlToValidate.Attributes.Add("data-original-title", "Error!");
//TODO: add append errors to tooltip
controlToValidate.Attributes.Add("data-content", validator.ErrorMessage);

//$(document).ready(function () { $("#test").tooltip(); });
var datePickerScript = String.Format("$(document).ready(function () {{ $('#{0}').popover(); }});\n", controlToValidate.ClientID);
Control.Page.AddStartupClientScript(controlToValidate.ClientID, datePickerScript);
}
}
}

Listing 1 – Control Adapter OnPreRender

This piece of code does it all it finds the control we are validating and then add the CSS class or removes it depending on it being valid or not.

.inner-glow (@radius: 3px, @color: #f3ffa7)
{
-webkit-box-shadow: 0 0 @radius @radius @color inset !important;
box-shadow: 0 0 @radius @radius @color inset !important;
}

.required
{
.inner-glow;
}

.RequiredFieldValidator-validation-error,
.RegularExpressionValidator-validation-error,
.CompareValidator-validation-error,
.CustomValidator-validation-error,
.RangeValidator-validation-error,
.DynamicValidator-validation-error
{
.inner-glow(2px, fadeout(#ff0000, 50%));
border: 1px solid fadeout(#ff0000, 40%) !important;
}

Listing 2 – LESS Classes

I did the the styling using LESS as it’s available readily now with Mads Kristensen’s Web Essentials for Visual Studios 2012 and 2013.

Note: I think in the long term I want to do what Damien Edwards said inn his post and create an updated Validator for each of the main validators and also do a custom Popover that can be styles independently of the Bootstrap one.

Download

As usual you can get the code from the Project on Bootstrap Friendly Control Adaptors and on my SkyDrive

Monday, 11 January 2010

Required Field Highlighting in Dynamic Data Field Templates

I’ve been using this for some time and thought I should post a small article on it.

RequiredField RequiredFKField

 Figure 1 – Highlighted fields

The idea is to give the user some prior knowledge the some field are required and these are have their backgrounds tinted yellow, so lets look at the code for this:

protected void Page_Load(object sender, EventArgs e)
{
    TextBox1.ToolTip = Column.Description;

    SetUpValidator(RequiredFieldValidator1);
    SetUpValidator(RegularExpressionValidator1);
    SetUpValidator(DynamicValidator1);

    // add 'validationRequired' class to the TextBox
    if (Column.IsRequired)
        TextBox1.CssClass += " validationRequired";
}

Listing 1 – the highlighter code.

It’s the last if statement that does the work if the the column is required then the “validationRequired” class is added to the fields class.

Note: All you need to do is add this code to each Edit field template that has a required field validator.
/* required field class */
.validationRequired
{
    background-color: #FFFEBD;
}

Listing 2 – required field css class

Making ForeignKey Field Template have Required Validation

The next thing I like is to add validation the FK field templates so there is a select [type] as the first entry in the FK field if the field is required and validation is enabled.

<asp:DropDownList 
    ID="DropDownList1" 
    runat="server" 
    CssClass="droplist">
</asp:DropDownList>
<asp:RequiredFieldValidator 
    runat="server" 
    ID="RequiredFieldValidator1" 
    CssClass="droplist" 
    ControlToValidate="DropDownList1" 
    Display="Dynamic" 
    Enabled="false" />
<asp:DynamicValidator 
    runat="server" 
    ID="DynamicValidator1" 
    CssClass="droplist" 
    ControlToValidate="DropDownList1" 
    Display="Dynamic" />

Listing 3 – ForeignKey_Edit.aspx

protected void Page_Load(object sender, EventArgs e)
{
    if (DropDownList1.Items.Count == 0)
    {
        if (!Column.IsRequired || 
            (Column.IsRequired && Mode == DataBoundControlMode.Insert))
        {
            DropDownList1.Items.Add(new ListItem("[Not Set]", ""));
        }

        PopulateListControl(DropDownList1);
    }
    DropDownList1.ToolTip = Column.Description;

    SetUpValidator(RequiredFieldValidator1);
    SetUpValidator(DynamicValidator1);

    // add 'validationRequired' class to the TextBox
    if (Column.IsRequired)
        DropDownList1.CssClass += " validationRequired";
}

Listing 4 – ForeignKey_Edit.aspx.cs

In Listing 3 I have added the standard RequiredFieldValidator and DynamicValidator to the ForeignKet_Edit field template and in Listing 4 I have added the setup for both validators and also changed the if statement that adds the “[Not Set]” entry to the dropdown list; this is make sure the user picks a value when the column is required and it is in insert mode as opposed to just accepting the default value.

Like I said only a short article but neatOpen-mouthed

Sunday, 13 July 2008

Part 4 – A DetailsView and a GridView with seperate Validation *** UPDATED ***

As far as I can see there are three (oops! four then) types of Custom Page:

  1. Custom Pages Part 1 - Standard Custom Page based on an existing PageTemplate and customised in the DynamicData\CustomPages folder.
  2. Custom Pages Part 2 - A completely Custom Page again in the DynamicData\CustomPages folder.
  3. Custom Pages Part 3 - Standard ASP.Net Page with Dynamic Data features added to take advantage of the FieldTemplates.
  4. Custom Pages Part 4 - A DetailsView and a GridView using Validation Groups
  5. Custom Pages Part 5 - I18N? Internationalisation Custom Page

This page is a standard Edit.aspx PageTamplate with a few extra controls (GridView, LinqDataSource, DynamicValidator and ValidationSummary)

<asp:ValidationSummary 
    runat="server" 
    ID="OrdersValidationSummary" 
    EnableClientScript="true"
    ValidationGroup="Orders" 
    HeaderText="List of validation errors for Orders" />
    
<asp:DynamicValidator 
    runat="server" 
    ID="OrdersValidator" 
    ControlToValidate="dvOrders"
    ValidationGroup="Orders" 
    Display="None" />
    
<asp:DetailsView 
    runat="server" 
    ID="dvOrders" 
    DataSourceID="ldsOrders" 
    DefaultMode="Edit"
    OnItemCommand="dvOrders_ItemCommand" 
    OnItemUpdated="dvOrders_ItemUpdated" 
    CssClass="detailstable"
    FieldHeaderStyle-CssClass="bold" 
    AutoGenerateRows="True" 
    DataKeyNames="OrderID">
    <FieldHeaderStyle CssClass="bold" />
    <Fields>
        <asp:TemplateField ShowHeader="False">
            <EditItemTemplate>
                <asp:LinkButton 
                    ID="LinkButton1" 
                    runat="server" 
                    CausesValidation="True" 
                    ValidationGroup="Orders"
                    CommandName="Update" 
                    Text="Update">
                </asp:LinkButton>
                &nbsp;
                <asp:LinkButton 
                    ID="LinkButton2" 
                    runat="server" 
                    CausesValidation="False" 
                    CommandName="Cancel"
                    Text="Cancel">
                </asp:LinkButton>
            </EditItemTemplate>
        </asp:TemplateField>
    </Fields>
</asp:DetailsView>

<asp:LinqDataSource 
    runat="server" 
    ID="ldsOrders" 
    EnableUpdate="True" 
    ContextTypeName="NorthwindDataContext"
    TableName="Orders">
    <WhereParameters>
        <asp:Parameter />
    </WhereParameters>
</asp:LinqDataSource>

<br />

<asp:ValidationSummary 
    runat="server" 
    ID="Order_DetailsSummary" 
    EnableClientScript="true"
    ValidationGroup="Order_Details" 
    HeaderText="List of validation errors for Order_Details" />
    
<asp:DynamicValidator 
    runat="server" 
    ID="dyvOrder_Details" 
    ControlToValidate="gvOrder_Details"
    ValidationGroup="Order_Details" 
    Display="None" />
    
<asp:GridView 
    ID="gvOrder_Details" 
    runat="server" 
    DataKeyNames="OrderID,ProductID"
    CssClass="gridview"
    DataSourceID="ldsOrder_Details">
    <Columns>
        <asp:TemplateField ShowHeader="False">
            <EditItemTemplate>
                <asp:LinkButton 
                    ID="LinkButton1" 
                    runat="server" 
                    CausesValidation="True" 
                    ValidationGroup="Order_Details"
                    CommandName="Update" 
                    Text="Update">
                </asp:LinkButton>
                &nbsp;
                <asp:LinkButton 
                    ID="LinkButton2" 
                    runat="server" 
                    CausesValidation="False" 
                    CommandName="Cancel"
                    Text="Cancel">
                </asp:LinkButton>
            </EditItemTemplate>
            <ItemTemplate>
                <asp:LinkButton 
                    ID="LinkButton1" 
                    runat="server" 
                    CausesValidation="False" 
                    CommandName="Edit"
                    Text="Edit">
                </asp:LinkButton>
            </ItemTemplate>
        </asp:TemplateField>
    </Columns>
</asp:GridView>

<asp:LinqDataSource 
    ID="ldsOrder_Details" 
    runat="server" 
    ContextTypeName="NorthwindDataContext"
    TableName="Order_Details" 
    Where="OrderID == @OrderID" 
    EnableUpdate="True">
    <WhereParameters>
        <asp:ControlParameter 
            ControlID="dvOrders" 
            Name="OrderID" 
            PropertyName="SelectedValue"
            Type="Int32" />
    </WhereParameters>
</asp:LinqDataSource>

Listing 1 – Custom Edit page for Orders

In the above listing you can see that the validation controls and link buttons in the DetailsView and GridView have been decorated with ValidationGroup="Orders" and ValidationGroup="Order_Details" appropriately.

Note: You must set the ValidationGroup of the DetailsView/GridView data controls command buttons that update the data so that they are in the correct validation group otherwise the error will drop through the validation net and appear on the page as an un-handled exception.
Note: ValidationGroups allow you to assign validation controls on a page to a specific category. Each validation group can be validated independently of other validation groups on the page.

The problem you have with a DetailsView or GridView with AutoGenerateRows set to true is how do you set the item controls ValidationGroup.

public class ValidationGroupFieldsManager : IAutoFieldGenerator
{
    protected MetaTable _table;
    protected String _validationGroup;

    public ValidationGroupFieldsManager(MetaTable table, String validationGroup)
    {
        _table = table;
        _validationGroup = validationGroup;
    }

    public ICollection GenerateFields(Control control)
    {
        List<DynamicField> oFields = new List<DynamicField>();

        foreach (MetaColumn column in _table.Columns)
        {

            // carry on the loop at the next column  
            // if scaffold table is set to false or DenyRead
            if (!column.Scaffold)
                continue;

            // create a new field with its validation group set
            DynamicField f = new ValidationGroupDynamicField(_validationGroup);

            f.DataField = column.Name;
            oFields.Add(f);
        }
        return oFields;
    }
}

// special thanks to David Ebbo for this
public class ValidationGroupDynamicField : DynamicField
{
    protected String _validationGroup;

    public ValidationGroupDynamicField(String validationGroup)
    {
        _validationGroup = validationGroup;
    }

    public override void InitializeCell(
        DataControlFieldCell cell,
        DataControlCellType cellType,
        DataControlRowState rowState,
        int rowIndex)
    {
        if (cellType == DataControlCellType.DataCell)
        {
            var control = new DynamicControl()
                {
                    DataField = DataField,
                    // Copy various properties into the control
                    UIHint = UIHint,
                    HtmlEncode = HtmlEncode,
                    NullDisplayText = NullDisplayText,
                    // set ValidationGroup
                    ValidationGroup = _validationGroup
                };

            if ((rowState & DataControlRowState.Edit) != 0)
                control.Mode = DataBoundControlMode.Edit;

            if ((rowState & DataControlRowState.Insert) != 0)
                control.Mode = DataBoundControlMode.Insert;

            cell.Controls.Add(control);
        }
        else
        {
            base.InitializeCell(cell, cellType, rowState, rowIndex);
        }
    }
}

Listing 2 – IAutoFieldGenrator for setting the Mode and ValidationGroup

There are two classes in Listing 2 the first an example of an implementation of IAutoFieldGenrator. In this implementation all we do is create a DynamicField if there should be a column or row.

The clever bit (smile_embaressed if you can call it that) is in the my DynamicField implementation. In here we create a DynamicField and add a DynamicControl to it, set various properties including ValidationGroup (which was passed in via the constructor), then we set the Mode of the control by testing the rowState.

public partial class Edit : System.Web.UI.Page
{
    protected MetaTable mtOrders;
    protected MetaTable mtOrder_Details;

    protected void Page_Init(object sender, EventArgs e)
    {
        DynamicDataManager1.RegisterControl(dvOrders);

DynamicDataManager1.RegisterControl(gvOrder_Details);





// the following four lines were moved form Page_Load (cut and paste error on my part) mtOrders = ldsOrders.GetTable(); mtOrder_Details = ldsOrder_Details.GetTable(); dvOrders.RowsGenerator = new ValidationGroupFieldsManager(mtOrders, "Orders"); gvOrder_Details.ColumnsGenerator = new ValidationGroupFieldsManager(mtOrder_Details, "Order_Details"); } protected void Page_Load(object sender, EventArgs e) { mtOrders = ldsOrders.GetTable(); Title = mtOrders.DisplayName; } protected void dvOrders_ItemCommand(object sender, DetailsViewCommandEventArgs e) { if (e.CommandName == DataControlCommands.CancelCommandName) { Response.Redirect(mtOrders.ListActionPath); } } protected void dvOrders_ItemUpdated(object sender, DetailsViewUpdatedEventArgs e) { if (e.Exception == null || e.ExceptionHandled) { Response.Redirect(mtOrders.ListActionPath); } } }

Listing 3 – Edit.apsx.cs code behind file

In here we add the plumbing for the extra GridView and then add the RowGenrator and ColumnGenrator to the DetailsView and GridView to set the ValidationGroup for the individual fields in the both the DetailsView and GridView.

This give us the ability to have multiple DynamicData Lists, DetailsViews etc on a page and validate each one separately, which again put the validation where is should be in a central place OAOO (Once And Once Only) that I like so much smile_teeth.

Note: There appears to be a problem with the above at the moment see this thread Setting ValidationGroup on DynamicValidator and DynamicControl has... and also my post to this thread Re: How to submit and validate a dynamic data page programatically? I’m pretty sure that there will be a resolution to the problem soonsmile_teeth This was erroneous I was feeling a bit DUMB smile_embaressed but I’m better now. The issue I’m trying to fix is where you have customised the page’s columns using DynamicField which you cannot set the ValidationGroup declaratively, I’m trying to do this at runtime on the OnDataBound event. I’ll post an update when I’ve figured it out.
It would seem my Dumbness continued! It’s too late in the page creation cycle to try and set the ValidationGroup, this is because Dynamic Data uses FieldTemplates which are actually User Controls which means that they are made up of several controls. So you would have to drill down into the DynamicControl and set the ValidationGroup of each of the separate controls that make up the FieldTemplate user control. Yes you could do but it’s a lot of messing about better to just use DynamicControl over DynamicField in those situations even if the markup is more fiddly. smile_teeth