Monday, February 28, 2011

Unobstrusive Javascript in MVC 3 helps clean up your forms

A great new feature in MVC 3 is unobstrusive javascript. This uses the magic of jQuery to hook attributes on your page without injecting a bunch of javascript into your forms as was done before.

To enable it, simply add this to your web.config
<configuration>
<appSettings>
<add key="UnobtrusiveJavaScriptEnabled" value="true"/>
</appSettings>
</configuration>


This makes ajax form submission code extremely easy. If you've ever tried to debug why a form wasn't submitted because of the emitted javascript from Ajax.BeginForm, you'll be happy to know your forms now are so much more readable.

We'll discuss the various options available shortly, but let's explain this from the beginning. If you want this all to happen automagically, you don't need to do anything but include the scripts (mentioned below), use mvc 3, and continue to use Ajax.BeginForm. The 'javascript-less' form tag will be emitted. If you want to do this manually, you enable your forms by including data-ajax="true" such as:
<form data-ajax="true" ...>

The inclusion of data-ajax="true" is where the magic comes into play.

In the unobtrusive jQuery library (jQuery.unobtrusive-ajax.js), we see this piece of code below that finds certain elements with data-ajax="true" and hooks them using the .live() method of jQuery. This methods hooks anything that exists in the DOM now and anything that is dynamically created in the DOM later via code.
//A HREF links hooked
$("a[data-ajax=true]").live("click", function (evt) {
evt.preventDefault();
asyncRequest(this, {
url: this.href,
type: "GET",
data: []
});
});

//images hooked inside a form
$("form[data-ajax=true] input[type=image]").live("click", function (evt) {
var name = evt.target.name,
$target = $(evt.target),
form = $target.parents("form")[0],
offset = $target.offset();

$(form).data(data_click, [
{ name: name + ".x", value: Math.round(evt.pageX - offset.left) },
{ name: name + ".y", value: Math.round(evt.pageY - offset.top) }
]);

setTimeout(function () {
$(form).removeData(data_click);
}, 0);
});

//Form submit function hooked
$("form[data-ajax=true] :submit").live("click", function (evt) {
var name = evt.target.name,
form = $(evt.target).parents("form")[0];

$(form).data(data_click, name ? [{ name: name, value: evt.target.value }] : []);

setTimeout(function () {
$(form).removeData(data_click);
}, 0);
});

//Form hooked
$("form[data-ajax=true]").live("submit", function (evt) {
var clickInfo = $(this).data(data_click) || [];
evt.preventDefault();
if (!validate(this)) {
return;
}
asyncRequest(this, {
url: this.action,
type: this.method || "GET",
data: clickInfo.concat($(this).serializeArray())
});
});



So, using this we no longer have to use code like such (but WE CAN still use this syntax if you want, as long as unobtrusive javascript is enabled in the web.config, the new form syntax will automatically be emitted):
<% using (Ajax.BeginForm("Index", "Customer", new AjaxOptions { UpdateTargetId = "customerArea", LoadingElementId = "customerWaitImage", OnFailure="ajaxCustomerFailed", OnSuccess="ajaxCustomerSuccess" } )) { %>
..
..
<%}%>




We can now simply
<form data-ajax="true" data-ajax-failure="ajaxCustomerFailed" data-ajax-success="ajaxCustomerSuccess" data-ajax-update="customerArea" data-ajax-loading="customerWaitImage" data-ajax-method="Post" >
...
..
</form>


Its very readable, requires no inline code, and renders with no javascript emitted to the page. You must make sure though you
1. Set the mentioned flag in the web.config: <add key="UnobtrusiveJavaScriptEnabled" value="true"/>
2. Include a reference to the jQuery library ~/Scripts/jquery-1.4.4.js
3. Include a reference to the library that hooks this magic at ~/Scripts/jquery.unobtrusive-ajax.js

If you do not have the script reference, note - the form will do a full post as opposed to an ajax post.

The options that are supported map directly from what was available in Ajax.BeginForm's AjaxOptions. The only reference I could find was the table I included below from Brad Wilson's blog, it doesn't seem as though Microsoft has the documentation available at the moment of this writing.

AjaxOptions HTML attribute
Confirm data-ajax-confirm
HttpMethod data-ajax-method
InsertionMode data-ajax-mode *
LoadingElementDuration data-ajax-loading-duration **
LoadingElementId data-ajax-loading
OnBegin data-ajax-begin
OnComplete data-ajax-complete
OnFailure data-ajax-failure
OnSuccess data-ajax-success
UpdateTargetId data-ajax-update
Url data-ajax-url


Again, remember - you can get the benefit of this by doing this manually in your form element, or simply continue using Ajax.BeginForm with unobtrusive javascsript enabled in the web.config and you will get the benefits of this new form syntax.

@using (Ajax.BeginForm("ControllerMethod", "ControllerName", new AjaxOptions() { LoadingElementId="loadingImage", OnSuccess = "success", OnFailure = "error", UpdateTargetId = "testDialog", InsertionMode = InsertionMode.Replace })){
<div id="testDialog">
</div>
}



A couple of extras - if you want to disable validation for a particular button you need to call

$("#myinput").rules("remove");

See
jQuery rules remove
for more details

Secondly, if you want to check via script if the form is valid you can check onsubmit (or other functions) easily as follows:
$(function () 
{
  $('#yourForm').submit(
        function () {
                 if(!$(this).valid()) {
                                       //form is not valid         
                                      }     
                     }); 
});

Enjoy - I love this new feature especially when trying to debug ajax partial views as it makes my life easier : )

No comments:

Post a Comment