Extending the framework - Overriding formatting, custom user-controls and Mvc.XForms.UI

by 8. December 2008 00:25

Available for download via the MVC XForms codeplex project (Thanks for everyone who has expressed an interest!). The project includes all source code and a test project that requires no database or external setup. Just download, open up visual studio and press play...

Planned posts in this series:

  1. Introduction to MVC XForms and basic form controls

  2. XForm container controls - Group and Repeat

  3. Automatic validation and deserialization

  4. Extending the framework - Overriding formatting, custom user-controls and Mvc.XForms.UI

  5. JavaScript API and Event Model

One of my goals for the MVC XForms framework is to enable clean, terse view code. This is not as easy as it seems. I think it's very important that any API is as simple and understandable as possible (In this respect, lambda expressions are the best thing to have happened to C# as a language. The expressive simplicity they provide is awesome). I have gone through several refactorings of the MVC XForms controls API to make the syntax "clean" and readable.

At the moment I'm working on the implementation of the XForm "bind", which will facilitate constraints on the data model in the form of attributes and a lambda-based API. I'm delaying post three in the series until this functionality is nailed down (hopefully soon).

Overriding formatting

One thing that bugs me with web controls is that there isn't a clean way to override the default HTML rendered by most of them. I've aimed to make MVC XForms easily extensible and pluggable (taking the lead from the MVC framework). To this end, I've decoupled all the rendering code from the control logic.

All the formatting is done via the IFormatter interface. This is instantiated via a factory, so to plug in your own formatter simply override the default formatter on application startup:

FormatterFactory.Current.Formatter = new ExampleFormatter();

The default formatter is called DefaultFormatter and all its methods are virtual. If there is one part of the output you don't like, simply inherit from this class and override the offending method:

public class ExampleFormatter : DefaultFormatter {    
    public const string requiredFormat = "<span>THIS IS REQUIRED</span>";    
    public override string Required(ICoreControl control) {    
        if (!control.Required)    
            return string.Empty;    
        return requiredFormat;    
    }    
}

I might add format overriding to individual form controls for even more granular control.

Custom User-Controls

In the last post I outlined a basic method for creating "user-controls". This was simply an extension method that created an XFormGroup, outputted the control based on the template within and returned void. What if you want to have settings on your user-control?

To be consistent with rest of the framework, we can provide a Settings object with our control that will give the user a fluent interface to work with. For example, we might want a TimeControl that has two drop-downs for hour and minute to bind to a Time object (with hour and minute int properties). This can be implemented like this:

public class TimeControl<TModel, TModelContext> : XFormGroup<TModel, TModelContext, Time> {    
    public TimeControlSettings<TModel, TModelContext> TimeControlSettings { get; set; }    
    public TimeControl(IContainerControl<TModel, TModelContext> parent, Expression<Func<TModelContext, Time>> bind)    
        : base(parent, bind) {    
        CssClass.Add("time");    
        TimeControlSettings = new TimeControlSettings<TModel, TModelContext>(this);    
    }    
}    
public class TimeControlSettings<TModel, TModelContext> {    
    public TimeControl<TModel, TModelContext> TimeControl { get; set; }    
    public TimeControlSettings(TimeControl<TModel, TModelContext> timeControl) {    
        TimeControl = timeControl;    
    }    
    public void Render() {    
        var resp = TimeControl.XForm.HttpContext.Response;    
        TimeControl.Settings.Template(con => {    
            resp.Output.Write("<label>{0}</label>", TimeControl.Label);    
            resp.Write(con.Select1(x => x.Hour, Enumerable.Range(0, 24), h => h, h => string.Format("{0:00}", h))    
                .Label("")    
                .CssClass("hour")    
                .Nullable("Hr")    
            );    
            resp.Write(con.Select1(x => x.Minute, Enumerable.Range(0, 12).Select(i => i * 5), m => m, m => string.Format("{0:00}", m))    
                .Label("")    
                .CssClass("minute")    
                .Nullable("Min")    
            );    
        });    
    }    
    public TimeControlSettings<TModel, TModelContext> CssClass(string cssClass) {    
        TimeControl.CssClass.Add(cssClass);    
        return this;    
    }    
    public TimeControlSettings<TModel, TModelContext> Label(string dateLabel) {    
        TimeControl.Label = dateLabel;    
        return this;    
    }    
} 

The TimeControlSettings object is used to hide the TimeControl properties and provide a clean fluent interface for a simple API. Now we just need to expose this control via an extension method:

public static TimeControlSettings<TModel, TModelContext> Time<TModel, TModelContext>(
    this IContainerControl<TModel, TModelContext> parent,
    Expression<Func<TModelContext, Time>> bind) {
    var time = new TimeControl<TModel, TModelContext>(parent, bind);
    return time.TimeControlSettings;
} 

Now we can use the control to bind to a time property on our model:

<% form.Time(x => x.StartTime).Label("Please enter a start time:").CssClass("startTime"); %>

One thing I would like to get working would be the use of ascx pages for user-controls. The use of generics means that I found no way to do it. It doesn't seem like it's possible to have the ascx page inherit from a generic type. Perhaps it is possible to inject this dynamically at runtime somehow. If anyone knows a technique to do this, let me know.

Exposing child control settings

Perhaps we might want to allow our API user to change the settings of a child control of our user-control. We can expose the child control settings by adding an HourSettings property to our control, then adding the following to the TimeControlSettings:

public TimeControlSettings<TModel, TModelContext> DateSettings(Action<Select1Settings> hourSettings) {
    hourSettings(TimeControl.HourSettings);
    return this;
} 

This would then make the following possible:

<% form.Time(x => x.StartTime).Label("Please enter a start time:")    
    .CssClass("startTime").HourSettings(hr =>  hr.Label("Hour:").CssClass("startTimeHour")); %>  

Mvc.XForms.UI

The UI project will be the place where common extensions of the MVC XForms framework will be kept. At the moment it just has a basic grid control used in the example project. Future enhancements will include:

  • Editing for the grid, hopefully matching the functionality of the web forms gridview (but with XForms controls and with no postbacks...)
  • Autocomplete
  • Date picker with Time picker

Currently rated 2.5 by 2 people

  • Currently 2.5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags:

Comments

Powered by BlogEngine.NET 1.4.5.0
Theme by Mads Kristensen