jQuery MVC Form Helper – Revisited

Well, I decided to try a different approach.  The goal here is to minimize and abstract the ajax form submit calls with jQuery and ASP.NET MVC using HtmlHelpers.

 

To do this, I decided to try a ‘2 helper’ approach:  one to define the ajax form options, the second to define the form itself.  Secondly, the code must support my current using of jQuery form validation.

 

An example of what jQuery form validation looks like is here:

 

    var newEmployeeOptions = {
        target:        '#status',   // target element
        beforeSubmit:  showNewEmployeeRequest,  // pre-submit callback
        success:       showNewEmployeeResponse,
        error: showNewEmployeeError,
        resetForm: false,
        dataType:  'json'
    };

    $("#addEmployeeForm").validate({
       submitHandler: function(form) {
        $(form).ajaxSubmit(newEmployeeOptions);
       }
    }).form();
 
(see end of post for an example of the javascript callbacks here)

The form itself is a standard form tag (or can use Html.Form) :

<form id="addEmployeeForm" action="/BistroWeb/Company.mvc/AddNewEmployee/<%= ViewData.Model.CompanyId %>" method="post">

Basically, when you click the submit button, the javascript ‘intercepts’ the submit, validates the form, and if success validation, executes the action.

Ok, that is general idea here.

Now, a look at how it works with my new Html Helpers:

<% Html.jQueryAjaxOptions("addEmployeeForm", AjaxDataType.json, new Hash(
   target=>"'#status'", beforeSubmit=>"showNewEmployeeRequest",
   success=>"showNewEmployeeResponse", Error=>"showNewEmployeeError", resetForm=>"false")); %>

This code produces the above javascript.  I use a Hash (from MVCContrib) to pass my options as above.

Next is my form itself:

<% Html.JQueryAjaxForm<string, CompanyController>("addEmployeeForm",
       c => c.AddNewEmployee(ViewData.Model.CompanyId.ToString()), null,
       s => {
       %>
(inside here is the form... ie...<%= Html.TextBox("HireDate", "", new { @class = "required" })%>)

<% });   %> 
How does this work?
As in my preview post, much cudos to Seth, as he got me started, as well as some examination of the MVCContrib source code.
My 2 Html Helpers are:
public static void JQueryAjaxForm<T, TController>(this HtmlHelper helper,
            string name, Expression<Action<TController>> formAction,
            T data,
            Action<T> block)
            where T : class
            where TController : Controller
        {
            AjaxFormBuilder form = new AjaxFormBuilder(helper.ViewContext.HttpContext, name);
            form.RenderStartForm(helper.BuildUrlFromExpression<TController>(formAction));
            block.Invoke(data);
            form.RenderCloseForm();
        }

        public static void jQueryAjaxOptions(this HtmlHelper helper, string name, AjaxDataType ajaxDataType, 
IDictionary ajaxOptions) { if (ajaxOptions == null) ajaxOptions = new Hash(); AjaxFormBuilder form = new AjaxFormBuilder(helper.ViewContext.HttpContext, name); form.BuildAjaxOptions(ajaxOptions, ajaxDataType); }
public enum AjaxDataType
    {
        html,
        json
    }

(I decided to create an enumerator for the dataTypes as well)

The key here is that the ‘Action<T> block, ‘block.Invoke(data)’, creates the output in the s => above.  I could use a strongly typed object in the Html.JQueryAjaxForm<string, CompanyController>  (ie. Html.JQueryAjaxForm<EmployeeModel, CompanyController>)

Before moving on to the ‘AjaxFormBuilder’, I do find the helper.BuildUrlFromExpression<TController>(formAction) call to be interesting, it’s a built in feature of the HtmlHelper – it will return the corrected action url (in this case “Company.mvc/AddNewEmployee/id”

The next part is the ‘AjaxFormBuilder’.  This needs to generate the form tag in the jQueryAjaxForm, and the BuildAjaxOptions in the jQueryAjaxOptions helper:

public class AjaxFormBuilder
    {
        private string form;
        private string formid;
        private HttpContextBase context;

        public AjaxFormBuilder(HttpContextBase context, string formid)
        {
            this.context = context;
            this.formid = formid;

        }

        public void BuildAjaxOptions(IDictionary ajaxOptions, AjaxDataType ajaxDataType)
        {
            string options = BuildOptions(ajaxOptions, ajaxDataType);
            context.Response.Write("<script type='text/javascript'>");
            context.Response.Write("$(function(){");
            context.Response.Write(options);
            context.Response.Write("});");
            context.Response.Write("</script>");
        }

        public void RenderStartForm(string action)
        {
            form = string.Format("<form name=\"{0}\" id=\"{0}\" action=\"{1}\" method=\"POST\">", formid, action);
            context.Response.Write(form);
        }

        public void RenderCloseForm()
        {
            context.Response.Write("</form>");
        }

        private string BuildOptions(IDictionary ajaxOptions, AjaxDataType ajaxDataType)
        {
            StringBuilder sb = new StringBuilder();
            sb.AppendFormat("var {0}Options =", formid);
            sb.Append("{");
            //sb.Append("contentType:'application/x-www-form-urlencoded',");
            //sb.Append("data:\"{}\",");
            int counter = 0;
            sb.AppendFormat("dataType:'{0}'", ajaxDataType);
            if (ajaxOptions == null || ajaxOptions.Count > 0)
                sb.Append(",");
            foreach (DictionaryEntry option in ajaxOptions)
            {
                counter++;
                sb.AppendFormat("{0}:{1}", option.Key, option.Value);
                if (ajaxOptions.Count != counter)
                    sb.Append(",");

            }

            sb.Append("};"); //end options

            sb.AppendFormat("$(\"#{0}\")", formid);
            sb.Append(".validate({");
            sb.Append("submitHandler: function(form) {");
            sb.AppendFormat("$(form).ajaxSubmit({0}Options);", formid);
            sb.Append("}});");

            return sb.ToString();
        }
    }

One of the key reasons I decided to separate the ‘options’ from the ‘form’ is for more flexibility, but also to allow the emitted javascript from the helpers to be controllable of ‘where’ it’s emitted by the developer.  ie. the head tag.  It uses the jQuery onReady  $(function(){});

I use the following jQuery libraries in this code (all can be found on the jQuery plugin repository)

jquery-1.2.6.min.js
jQuery.Delegate.js   (jQuery delegate plug-in v1.0 – Copyright (c) 2007 Jörn Zaefferer)
jQuery.Validation.js  (Copyright (c) 2006 – 2008 Jörn Zaefferer – see links below)
jquery.form.js    (jQuery Form Plugin version: 2.07 (03/04/2008)  @requires jQuery v1.2.2 or later)

http://bassistance.de/jquery-plugins/jquery-plugin-validation/
http://docs.jquery.com/Plugins/Validation

 

callback example:

Here is an example of the 'showNewEmployeeRequest':
 
function showNewEmployeeRequest(formData, jqForm, options){
        var loading = "<img src='/BistroWeb/Content/Images/spinner.gif'>";
        $("#status").html(loading).show("slow");
    };
This basically have a spinner gif that is displayed in the 'status' div on the page,
I also like to use BlockUI as well to prevent 'double' submits
 
Advertisements

3 thoughts on “jQuery MVC Form Helper – Revisited

  1. Ideally, you should combine the two in one and have something similar to the Ajax helper method “BeginForm” (in System.Web.Mvc)
    I would use exactly the same syntax so people can esaly move from to another
    I honestly prefer the JQuery Ajax to the Microsoft Ajax included in MVC
    If you managed to to it, I would easly rewrite my code

    to

  2. I made this decision to not combine the two, I can easily pull out of using ajax or not. ie. create your form without ajax. If you choose to ‘opt in’ to ajax, you add this helper without touching your form.

    But I guess you could go either way, and just create a new form helper that implements the same basic logic.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s