Using jQuery to do Ajax Form posts in Asp.net MVC

It is now official from a post by great Scott Guthrie that jQuery is bundled with Asp.net MVC Beta. jQuery is a tiny 15K JavaScript library that contains features from UI tweaks, DOM manipulation to full Ajax control. In my last post, I have shown how to get going with Ajax.Form using Microsoft MVC Ajax library. In this post, I will show how to do Ajax form posts with jQuery but in Ajax.Form style.

To mark the marriage of jQuery, I have released a new version of FlickrXplorer that uses nothing but jQuery on the client. More info on the release can be found at the following URL.

http://www.codeplex.com/FlickrXplorer/Release/ProjectReleases.aspx?ReleaseId=18041

Ajax.Form gives a nice way of adding ajax features with no more tears. Just add a using block with necessary html controls and a submit button, everything else is taken care of on behalf. Being inspired by it, in FlickrXplorer project I have created a Html.JForm that works in a similar way using jQuery library.

To see how it works, it is to mention that Ajax.Form basically creates a html form with onsubmit hook where it injects few JavaScript from Microsoft MVC Ajax Library. To replicate, le's say to do an image list  paging with Html.JForm that gets the data, shows the loader and updates the container

image 

I first added the MVC JavaScript (Ex. Inside default.master) file with ajax stuffs that works with Html.JForm  and referenced the jQuery library.

<script type="text/javascript" src="<%= Page.ResolveClientUrl("~/Content/jquery-1.2.6.min.js") %>" ></script> 
<script src="<%= Page.ResolveClientUrl("~/Content/mvc-jquery.js") %>" type="text/javascript"></script>

Finally, In the actual ascx/aspx file, I wrote the following

 <% 
    using (Html.JForm(VirtualPathUtility.AppendTrailingSlash(HttpContext.Current.Request.Path), "POST", new JOptions
    { 
        TargetPanelId = "imgListContainer", 
        WaitPanelId = "imgListWait"
    }))
                        
    { %>

...
...
<% 
} %>

That's it , also it is to include that this will work with controller actions that return either ContentResult or ActionResult.

Basically, the signature of Html.JForm looks like

Overload 1 : Html.JForm(actionurl, methodType, JOptions);
Html.JForm("/controller/action", "GET/POST",
new JOptions 
{ 
    TargetPanelId = "update containter"
    WaitPanelId = "intermidiate visible panel"
});
Overload 2 : Html.JForm(actionurl, methodType, JOptions, htmlArrributes);
Html.JForm("/controller/action", "GET/POST",
new JOptions 
{ 
    TargetPanelId = "update containter"
    WaitPanelId = "intermidiate visible panel"
}, new { name = "myForm" );

Going deeper, these are actually HtmlHelper extenstion methods

public static IDisposable JForm(this HtmlHelper helper, string action, string method, JOptions options, object htmlAttribtues)
{
    return new JQueryForm(helper, action, method, options, htmlAttribtues);
}

Behind the scene they call a IDisposable class callled JQueryForm that generates the form with actual hookup scripts. The concept is to generate the starting form tag with all the attributes provided during initialization and the ending form tag on dispose call. This basically is done in the Ajax.From that can be found with a little help from reflector.net (or source from codeplex). Now, inside System.Web.Mvc there is a new public class called TagBuilder, which I found really handy for building up html scripts.

Therefore, here is what I have done during initialization of JQueryForm. I have added only code that generates the tag, other things you can find it by yourself in the code provided at the end.

TagBuilder builder = new TagBuilder("form");

builder.MergeAttribute("action", url);
builder.MergeAttribute("method", method);
builder.MergeAttributes<string, object>(new RouteValueDictionary(htmlAttributes));

if (options.CallBack == null)
{
    builder.MergeAttribute("onsubmit",
                           string.Format(jStringOverload, options.WaitPanelId, options.TargetPanelId));
}
else
{
    builder.MergeAttribute("onsubmit",
                           string.Format(jString, options.WaitPanelId, options.TargetPanelId, options.CallBack));
}

responseBase = helper.ViewContext.HttpContext.Response;
responseBase.Write(builder.ToString(TagRenderMode.StartTag));

 

To add attributes MergeAttribute is used that has few overloads and appends the attribute to the generating tag. Finally, to build the string you need to use the ToString overload with proper render mode.I have used TagRenderMode.StartTag which will generate the opening html form tag.Basically, JQueryForm has only a Constructor where I build starting tag and a Dispose method where I just have to close the tag.

responseBase.Write("</form>");

As you can see that I have hooked one method in onsubmit call, the purpose of this method is to prevent default form post, do an ajax callback and get the result to a html container. I named it jAjaxSubmit.

Before jumping to the analysis of the method, let's see how to do ajax calls using jQuery. It is very clean and simple. Therefore, really cool.

$("#" + waitElementId).show();

$.ajax({
    type: actionType,
    dataType: "html",
    url: url,
    data: params,
    success: function(result) {
        $("#" + elementId).html(result);
        $("#" + waitElementId).hide();

        if (typeof callback != 'undefined')
            callback();
    },
    error: function(error) {
        $("#" + waitElementId).hide();
        //TODO:// write your log here
    }
});

This is an example of Ajax callback where I can provide the type [GET|POST], dataType[html|xml|json](i have used "html"), data[serialized form params], success and a failure callback. Those who are new to jQuery, it is to mention that $(..) is equal to the $get in Microsoft Ajax and it accepts element either by id or name (# is used to specify get element by id).

During the submit button click under Html.JForm , we first need to stop the form post. For, Internet Explorer we can do this by sending a return false but for Mozilla based browsers the proper way to do is the stopPropagation() call.

 if (!$.browser.msie) {
        e.stopPropagation();
    }

Inside the using of Html.JForm as we specify  Html elements either by Html extension methods (<%= Html.Textbox("comment") %>) or by hand. we need to get the values of them and pass them as "&" separated way during the submit process. using JQuery, we can easily do that using $(form).serialize() and pass it in the callback. So the final script looks like

function jAjaxSubmit(form, e, waitPanelId, targetToUpdate, methodName) {

if (!$.browser.msie) {
    e.stopPropagation();
}
var isValid = true;

if (typeof methodName != 'undefined') {
    isValid = methodName(form, null);
}

if (isValid) {
    // create the form body
    var body = $(form).serialize();
    renderContent2(targetToUpdate, waitPanelId, form.action, body, form.method);
}
return false;
}

renderContent2 is just a wrap around of the callback script shown earlier. In running project, injected block looks like the following firebug snap.

image

Apart from the internals , all things are done automatically by Html.JForm call, this can be found running in FlickrXplorer project but for your convenience, I have added a sample project using the default MVC template which you can get here.

Of course, you can try browsing the live app at http://www.flickrmvc.net (This gives you a nice way to fast explore millions cool public photos from flickr).

Have Fun!!!

Updated with Asp.net MVC Beta on Oct 19, 2008

kick it on DotNetKicks.com

No Comments