Monday, January 17, 2011

MVC3 unobtrusive AJAX and default loading image

With MVC3's new unobtrusive AJAX and jquery.unobtrusive-ajax.js, all AJAX calls are mapped through jquery. You end up with lots of "data-ajax-xxxxxxx" attributes on your Form tag. Overall this works pretty well.

However, one issue is that _generally_ I want a 'loading' image to show on the start of each ajax call and hide at the end. In order to do that, you must provide the loadingElementId and optional loadingElementDuration to the ajax calls. Those get translated into attributes on each form call like this:

data-ajax-loading="myloadingpanel" data-ajax-loading-duration="3"


Of course I would be repeating that same information all over my code and that same information is output into each form element. You could of course create your own AjaxOptions class that provided these defaults to help with DRY. However, there are other features - what if you wanted to using one of the easing functions when showing and hiding your image? What if you wanted to center it on the screen?


JQuery has a function called ajaxSetup which sets defaults for all ajax calls. This turns out to be an easy place to attach default beforeSend and complete event that handles showing and hiding your loading image. This does not help in this scenario, because jquery.unobtrusive-ajax.js provides its own beforeSend and complete events and the default ones above will not be called.

So, I propose a new solution. Add two new entries to the ajaxSetup defaults. A showLoading and hideLoading event. You can then assign those to do whatever you want. If you do not pass in a 'loadingElementId' to the AjaxOptions then these events will be used - otherwise your will get the current behavior implemented today. (That way, this should not break any existing code). This would be something you setup and initialize in your master page/layout master.

jQuery.ajaxSetup({
   showLoading: function (XMLHttpRequest) {
    centerElement($('#loadingImage'));
    $('#loadingImage').show();
   },
   hideLoading: function (XMLHttpRequest) {
    $('#loadingImage').hide();
   }

Now you have complete flexibility in what happens when ajax calls start and stop with only one place to change it should you need to. Of course this requires a change to the jquery.unobtrusive-ajax.js code. The code addition is quite small and is shown here on lines 86 - 100. The lines with //wb are the ones I added.

Your feedback is welcomed!

Sunday, January 16, 2011

MVC3 CompareAttribute displays wrong default error message

I am glad to see new compare attribute in MVC3.  If you do not specify the optional error message for the compare attribute, it will display everything correctly, except for the name of the field you are comparing to.  It does not respect the DisplayName attribute of the compared field.  For example, if you have this:



The error message displayed will be:


Notice it does not use the DisplayName of 'New Password', but instead uses the property name.

As a workaround to this, you will just have to specify an error message to the Compare attribute. I will report this as a bug to the MVC team, but it may not be something they can easily fix.

Saturday, January 15, 2011

MVC3 requires unobtrusive validation if you want jquery validation

Reading all the new things about MVC3, it appeared to me they had all these options to 'not' enable Unobtrusive javascript.   Turns out you really have two choices.  You can use msajax style validation (regular or unobtrusive) or you can use jquery validation - unobtrusive style.

If you are like me, you have been doing jquery validation all along, using the alternative script MicrosoftMvcJQueryValidation.js provided by Microsoft futures library.   Well, there does not appear to be a 'new version' of that script and that no longer works.

So, if you currently use MVC2 and jquery validation - the first thing you need to do is turn on unobtrusive validation in your web.config:

<appSettings>
  <add key="ClientValidationEnabled" value="true"/> 
  <add key="UnobtrusiveJavaScriptEnabled" value="true"/> 
</appSettings>

And then replace your included script of MicrosoftMvcJqueryValidation.js with jquery.validate.unobtrusive.min.js.

MVC3 breaks any manual use of jquery validate

If MVC client side validation using model attributes just would not do what you needed it to, you could always turn of client side validation and call validate yourself:

$('#myForm').validate({
            rules: {
                myField: {
                    minlength: 5, maxlength: 5, required: true, digits: true
                }
            },
            submitHandler: function (form) {
                $.post($(form).attr("action"), $(form).serializeArray(), function (data) {
                    $("#myPostResults").html(data);
                });
                return false;
            }
        });

Well, not anymore! MVC3 and jquery unobtrusive (which is become VERY obtrusive) validation breaks this, even if you disable client validation on this form!

Why? The jquery.validate.unobtrusive.js code calls form.validate() even if no rules were found or created - destroying whatever you had done! So, here is another fix to that code.  Simply add the one line, shown here on line 100.  Note this has my other MVC3 validates hidden fields using jquery validation fix


MVC3 validates hidden fields using jquery validation

I am not sure about you, but I rarely, if ever, want to validate a form field that is hidden from view!

Consider the case where you have a several required fields on the screen and the user can take some action that causes some fields to then be hidden.  Well, using the default unobtrusive jquery validation, it will still validate those fields.   If you do not have a 'SummaryValidation', this means the form just will not submit - no errors or anything because the error is displayed on a hidden field!

How do we get it to stop doing this?   For now, I know of only one way.  You need to add one line of code into the jquery.validate.unobtrusive.js file.

Add line 97 as shown below (and of course the comma at the end of line 96):