Tuesday 8 March 2011

Procedurally generated culture sensitive validation for ExtJS.

So here's a quick update of what I've been doing in the last week.  My estimate for this was way of the mark but I encountered a couple of bugs in ExtJS concerning at which point ExtJS receives its validation data, and at which point we can hook in to use its nifty features, but now I think I've got the best part of a pattern down for how to go about building custom controls with culturally sensitive validation.





My thought when starting this, is that I don't want to worry about any validation what so ever when it comes to client-side validation.  I don't want to write any manual JavaScript to do it, I just want ExtJS to hanlde it for us.  Obviously we would have to supply it with correct data on how to validate, but we shouldn't have to write basic validation methods, just RegEx formula's that ExtJS' validation in-built functionality can utilize as it has some quite nifty means of doing validating, specifically, by using the properties 'MaskRe' and 'RegEx'.  The former uses a regular expression as an Input Mask to filter keystrokes, the latter, a regular expression that pattern matches the value of the input on change.  I also wanted to have my validation to be culturally sensitive, so at runtime it would need to collect this information and base any regular expressions from the NumberFormat provided by CultureInfo. I also wanted the component to be reusable so I would need to create a custom c# component called 'Currency' that inherited from TextField.  I would then be able to call a custom method on any constructor to populate it with any extra validation information automatically that I might need injected in to the JSON. I also registered a new structure called 'Currency' with ExtJS which inhereted from the ExtJS textfield control and associated a new xtype with this structure; coincidentally named 'currency'.  So I got to it.

I set the Thread culture in our base controller.  I overrode the Initialize method, which as a parameter passes a RequestContext.  From this derive the UserLanguage from the client browser and set the thread culture.  Later (Much later, not in this blog) we can override this behaviour so that the culture can be retrieved elsewhere like from a database or a URL even...


protected override void Initialize(RequestContext requestContext)
{
base.Initialize(requestContext);
string browserClientCulture = requestContext.HttpContext.Request.UserLanguages[0];
var culture = new CultureInfo(browserClientCulture);
Thread.CurrentThread.CurrentCulture =
Thread.CurrentThread.CurrentUICulture = culture;
}


Doing this, sets the culture for the current user thread, and allows the 'Currency' component to use the current thread culture as the basis from which to derive a regular expression.

Next I created my c# Currency class that inherits from TextField and added extra properties for 'MaskRe' and 'RegEx'.  I created a private method on this class which could be called from all constructors to populate these properties with the culturally sensitive regex's.  I wont include the code as it's quite long so you'll have to hunt it out for yourselves, but know that it creates RegEx's from a cultures number format information and includes the ability to detect the use of positive and negative numbers, currency symbols, group seperators, decimal seperators, and all of various lengths.  The use of various symbols can be overridden by named parameters as can the culture.

The first problem I encountered was that due to our building an ExtJS textfield in C# and then creating a JSON interpretation of the class on the fly serialized the properties, it inevitably output 'MaskRe' and 'RegEx' as strings.  This wasn't good enough as JavaScript regular expressions were expected and the default behaviour threw an exception when the string regular expressions were tested.  I needed to work around this and scanning the ExtJS API discovered the 'VType' (ValidationType) property.

The ExtJS.form.vtype class is simply a singleton collection of validation types (There are some built in Vtypes check the ExtJS documentation).  I applied a new vtype, again remarkably called 'currency'.  Each vtype has a partner called [originalName]Mask (i.e. currencyMask).  Together these did the same work as 'MaskRe', and 'RegEx' but gives you the ability to do more advanced validation if you need to, which is awesome for the validation of composite controls.  On our C# Currency class I renamed the MaskRe and RegEx properties so the default behaviour would not activate, but the information is retained still in the JSON when the class is serialized.  A default (non-mask) vtype is a method that is called passing the value and field that is being validated.  A masking vtype is simply a regular expression.  The latter is not half as useful because at runtime we cannot get the mask regular expression from the field parameter like we can for the default VType.  With the Default 'currency' vtype we can get the regex from the field JSON definition and eval it like follows.


Ext.apply(Ext.form.VTypes, {
    //
    // Currency validation
    "currency": function (val, field) {
        var regEx = eval(field.InputPattern);
        return regEx.test(val);
    },
    "currencyMask" : null //populated at runtime
}


So I needed to find a way to populate this at runtime.  The first place that I thought of was our JavaScript method that applied the currency control to ExtJS and extended the Textfield control.  I created a onRender method that checked if this method had an input mask and if so applied it to the VType at runtime.


Ext.Custom.MoneyField = Ext.extend(Ext.IQP.TextField, {
    initComponent: function () {
        Ext.form.TextField.superclass.initComponent.call(this);
    },
    onRender: function () {
        Ext.IQP.TextField.superclass.onRender.apply(this, arguments);
        if (this.InputMask != null) {
            Ext.form.VTypes["currencyMask"] = eval(this.InputMask.Pattern);
        }
    }
}


Initially I thought this wouldn't work because if you had several Currency Fields with different overridden cultures, then this would be applied time and time again each time a TextField was rendered.  However this was not the case, when I created a page that did just that, with cultures from Aramaic to Hebrew, to Russian, to Zimbabwean.  Win.

There you have it and there you have the basic jist of how to localize validation for ExtJS controls in our framework.

No comments:

Post a Comment