ASP.Net

ASP.NET MVC: Show Busy Indicator on Form Submit using JQuery and Ajax


Image by Martin Abegglen | Some Rights Reserved

As we all know, users are impatient. We also know that if we don’t tell them our application is “doing something” they tend to do silly things like click the mouse repeatedly, seeking some sign that the requested action is indeed being performed.

For this reason, it is a good idea to throw up some sort of “busy” indicator when a user-initiated action may invoke a long-running process which requires them to wait.

In this article we are going to demonstrate a very basic way to achieve this which will be effective in most cases.

If you are new to ASP.NET MVC, you may want to check out my previous posts on Routing Basics and Route Customization as well. While not critical to understanding what we discuss here, some background on the basics of MVC routing will certainly help in understanding what’s going on in places.

The fully-functioning source code for the example used in this article is available from my Github Repo. You will need to use Nuget Package Restore to pull down all the package goodness so that the project will run. If you are unsure what that means, see Keep Nuget Packages Out of Source Control with Nuget Package Manager Restore.

What the Hell is Ajax?

Ajax is an acronym for Asynchronous JavaScript and XML. Ajax represents a broad range of technologies used to facilitate client-server communication without interfering with the current state of the page.

In the main, we most often use the term when we speak of making an Ajax Request to the server, usually in the context of posting or retrieving data, and updating only a portion of the page, as opposed to completely refreshing a page simply because a small sub-area needs to be updated.

Upon the introduction of the term circa 2005, XML represented what many believed would be the primary data interchange format for this type of client-server transaction. Since then, JavaScript Object Notation (JSON) has become more and more of a standard. While XML is not gone, you are as likely to send and receive JSON in the course of an Ajax request as you are XML.

At present, we often find Ajax used in conjunction with JQuery (more properly, one often uses JQuery to make an Ajax request) when we need to retain the current state of our page while a request is made to the server and then update the portion of the page affected by the information returned from the request.

Adding a Busy indicator to a Page with JQuery and Ajax

The common way to display a busy indicator on a page while we wait for a long-running request to return from the server is to throw up an animated Gif, to let users know something is in fact happening. Equally as often, we need to then remove or hide the animation when the process completes, and refresh the portion of the page affected by our new data.

There really is no good way to do this from the server side in an MVC application – we need to use Ajax. We do this with JavaScript. in this case, we are going to use the popular JQuery library, because it is ubiquitous, it ships with and in integrated into the core MVC project.

Let’s take a look at a quick example.

Basic Example – The View

First we will look at a basic view. We’ve kept it simple here – we will display a couple of data entry fields, and a button to submit the data entered by the user. The essential cshtml code for this is as follows:

A Basic View:
@model JqueryBusyExample.Models.DemoViewModel
@{
    ViewBag.Title = "Home Page";
}
  
<h3>Enter Your Name and Submit:</h3>
  
@using (Html.BeginForm("LongRunningDemoProcess", 
    "Home", FormMethod.Post, 
    new { encType = "multipart/form-data", id="myform", name = "myform" }))
{
    <div class="form-group">
        @Html.LabelFor(model => model.FirstName, 
            new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.EditorFor(model => model.FirstName)
            @Html.ValidationMessageFor(model => model.FirstName)
        </div>
    </div>
    <div class="form-group">
        @Html.LabelFor(model => model.LastName, 
            new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.EditorFor(model => model.LastName)
            @Html.ValidationMessageFor(model => model.LastName)
        </div>
    </div>
    
    <input type="submit" name="operation" id="process" value="process" />
}

As we can see, there is nothing unusual here. We use standard Razor syntax to create a form with a couple data entry fields for First and Last names, and a submit button. In the opening of the using statement, The form is wired to a method named LongRunningDemoProcess on our HomeController.

In the form declaration, the syntax is:

Html.BeginForm(<actionName>, <controllerName>, <Http Method Type>, <Html Attributes>)

The above basically determines what happens when the form is submitted, and specifies a specific method, and a specific controller as a target for the Http request. It also defines the request method type (GET, POST, PUT, DELETE).

In our example case, we want to send our model data in the POST body. On our controller, the LongRunningDemoProcess method will need to be decorated with the [HttpPost] attribute, and accept the POST body payload as an argument.

Now let’s take a look at our Home controller.

Basic Example – The Controller

For our purpose here, I have simply added a method named LongRunningDemoProcess to the stock ASP.NET MVC Home Controller which is part of the default MVC 5 project template:

A Basic Controller with Long-Running Process:
public class HomeController : Controller
{
    public ActionResult Index()
    {
        return View();
    }

    [HttpPost]
    public JsonResult LongRunningDemoProcess(DemoViewModel model)
    {
        Thread.Sleep(1000);
        return Json(model, "json");
    }

    public ActionResult About()
    {
        ViewBag.Message = "Your application description page.";
        return View();
    }
 
    public ActionResult Contact()
    {
        ViewBag.Message = "Your contact page.";
        return View();
    }
}

In the above, we use Thread.Sleep to mimic a long-running process on the server. You will need to add a reference to the System.Threading namespace in the using statement imports at the top of your file.

As we can see, LongRunningDemoProcess is the target of for our HTTP Post request from the Index view when form submission occurs. We want to use the Json data returned from the request to update our view, and let the user know their submit succeeded.

At the moment, though, things are not all they could be. We can load up our page, type some data into the form, and hit submit. What happens next, though, is that our page freezes up while the long-running process runs, and then our Json data is returned to a new tab in our browser (Chrome) or we are prompted to Save or Open (IE).

What we WANT to happen is to display a busy indicator, and then somehow indicate to the user that their submission was successful (or something!).

Get a Busy Indicator GIF

One of the more useful little sites I’ve found recently is AjaxLoad.Info, which presents a handy library of various customizable animated GIFs. You can specify some basic parameters (Size, Type, Foreground Color, etc.) and then download, ready to use.

Go get one, add it to your project in a location which makes sense (I am using ASP.NET MVC 5 project in VS 2013, so I placed mine at the root level of the Content folder).

Next, let’s modify our view, and add a div we can show and hide as needed, and which contains our gif:

The View, with Animated GIF and Placeholder for Submission Result

In the highlighted area below, we have added a div element to contain our Gif. While we’re at it, we also added another div, to hold the result when our long-running process returns:

Modified View with Animated Gif and Placeholder Div:
@model JqueryBusyExample.Models.DemoViewModel
@{
    ViewBag.Title = "Home Page";
}
  
<h3>Enter Your Name and Submit:</h3>
  
@using (Html.BeginForm("LongRunningDemoProcess", "Home", FormMethod.Post, 
    new { encType = "multipart/form-data", id="myform", name = "myform" }))
{
    <div class="form-group">
        @Html.LabelFor(model => model.FirstName, 
            new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.EditorFor(model => model.FirstName)
            @Html.ValidationMessageFor(model => model.FirstName)
        </div>
    </div>
    <div class="form-group">
        @Html.LabelFor(model => model.LastName, 
            new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.EditorFor(model => model.LastName)
            @Html.ValidationMessageFor(model => model.LastName)
        </div>
    </div>
    
    <input type="submit" name="operation" id="process" value="process" />
}
   
// We want to show this while the server process is running:
<div id="divProcessing">
    <p>Processing, please wait . . . <img src="../../Content/ajax-loader.gif"></p>
</div>
  
// We want to display the result from our submission in here:
<div id="divResult">
</div>

Now, in order to tie all this together, we need to add an Ajax request.

Adding the Ajax Request

The easiest way to achieve what we want is to use JavaScript on the client to show our animated gif, and then submit our data to the server. We also want to be able to respond when the Json is returned by the server by updating our page, without refreshing the whole thing.

For our example, I am going to add the JavaScript right on the view. This may or may not make sense in a production application, but we will do it here for simplicity.

I’ve added the JQuery code below our now-familiar view:

The View, with JQuery Added:
@model JqueryBusyExample.Models.DemoViewModel
@{
    ViewBag.Title = "Home Page";
}
  
<h3>Enter Your Name and Submit:</h3>
  
@using (Html.BeginForm("LongRunningDemoProcess", "Home", FormMethod.Post, 
    new { encType = "multipart/form-data", id="myform", name = "myform" }))
{
    <div class="form-group">
        @Html.LabelFor(model => model.FirstName, 
            new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.EditorFor(model => model.FirstName)
            @Html.ValidationMessageFor(model => model.FirstName)
        </div>
    </div>
    <div class="form-group">
        @Html.LabelFor(model => model.LastName, 
            new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.EditorFor(model => model.LastName)
            @Html.ValidationMessageFor(model => model.LastName)
        </div>
    </div>
    
    <input type="submit" name="operation" id="process" value="process" />
}
    
// We want to show this while the server process is running:
<div id="divProcessing">
    <p>Processing, please wait . . . <img src="../../Content/ajax-loader.gif"></p>
</div>
    
// We want to display the result from our submission in here:
<div id="divResult">
  
</div>
  
@section Scripts {
  @Scripts.Render("~/bundles/jqueryval")
  
  <script type="text/javascript">
  
    $(document).ready(function () {
  
      // Hide the "busy" Gif at load:
      $("#divProcessing").hide();
  
      // Attach click handler to the submit button:
      $('#process').click(function () {
          $('#myform').submit();
      });
  
      // Handle the form submit event, and make the Ajax request:
      $("#myform").on("submit", function (event) {
        event.preventDefault();
  
        // Show the "busy" Gif:
        $("#divProcessing").show();
        var url = $(this).attr("action");
        var formData = $(this).serialize();
        $.ajax({
          url: url,
          type: "POST",
          data: formData,
          dataType: "json",
          success: function (resp) {
  
            // Hide the "busy" gif:
            $("#divProcessing").hide();
  
            // Do something useful with the data:
            $("<h3>" + resp.FirstName + " " + resp.LastName + "</h3>").appendTo("#divResult");
          }
        })
      });
    });
  </script>
}

In the above, we start with the standard JQuery document.ready function, and hide the div containing our animated Gif.

We then attach the form submit event to the click event of the submit button we defined in our View (Isn’t it already attached, you ask? We’ll get to why we do this in a moment), and then we handle the form submit event.

Handling the Form Submit Event with an Ajax Request

This is where things get interesting. There are a few things occurring inside this function, in a rather specific order. Let’s walk through it.

First, we want to prevent the default action using (wait for it . . .) the JQuery preventDefault() method when the form submit event occurs, otherwise, form submission will proceed automatically, as before, and any code we place in our handler will not work properly.

Next, we show our animated Gif. Once shown, it will do its thing, giving the user the impression that something  useful is happening.

Finally, we collect what we need from our form in order to submit our Ajax request. First, we grab the Url and stow it in a variable by accessing the form’s action attribute using JQuery. The action attribute essentially pulls a Url which matches the route we specified in the form declaration (remember?  ActionMethod, ControllerName, HttpMethodType, etc?).

Next, we serialize the form data ( in this case, our model data) into another variable, again using JQuery.

Once we have collected these things, we can set up our Ajax request by making the property assignments shown. Note that we need to specify the data type as Json, so that when the request returns, JQuery will recognize it as valid Json. Then, we come to the all-important success property.

We have assigned a function here, which will execute when our long-running process returns a response. The resp argument will contain the Json returned from our controller method. In this very simple case, it is merely the data we already submitted. However, it could just as easily be the result of persisting some data in a database on the server. In any case, we know that when this function is called, our remote, long-running task has completed.

That being the case, the first thing we want to do is hide our animated Gif. Next, we push the returned data into the div we set up as a placeholder (after doing a little formatting, of course).

Fairly Simple, but Not Always Obvious

And there you have it. The example we have examined is pretty basic, but gives a good look at how to both display a busy indicator, and how to make Ajax requests from your ASP.NET MVC page.

There is a lot more to learn about Ajax, and it provides a foundation for much of the interactivity found in today’s modern websites. I welcome comments and constructive criticism in the comments section below (especially if I got something wrong here . . .)!

You can clone or download the example project from my Github Repo. Note that you will need to use Nuget Package Restore to pull in all the package dependencies. If you don’t know what that means, see Keep Nuget Packages Out of Source Control with Nuget Package Manager Restore.

Additional Resources and Items of Interest

ASP.Net
ASP.NET MVC: Keep Private Settings Out of Source Control
C#
Extending C# Listview with Collapsible Groups (Part I)
C#
Visual Studio: Use Conditional Compilation to Control Runtime Settings for Different Deployment Scenarios
  • XIV-Admin

    XIV-AdminXIV-Admin

    Author Reply

    Interesting. Have you considered using some sort of "endless scroll" strategy, where elements below the bottom of teh client window are rendered after scrolling?

    Scott Hanselman discusses this in a slightly different context here:

    http://www.hanselman.com/blog/InfiniteScrollWebSitesViaAutoPagerizeHackyButTheBeginningOfSomethingCool.aspx

    And there are no doubt other solutions.

    This would allow you to render only the visible portion of your data once the AJAX request returns, and then render bits as the user scrolls. In other words, "defer" the bulk of the rendering operation until the data is needed.

    Let me know if you get something working, and I will let you know if I figure something out. There is a possible article unto itself here. :-)


  • Tony

    TonyTony

    Author Reply

    Just like other samples, when you retrieve large data (my case is 2000 rows of data) from server and show all data in a single client page, the animated Gif will only work during data are transferred from server to client, it will be stuck when client renders until rendering finished. I don't know how to handle it, and seems nobody gave a good solution either, what I could do was using paging, but that was not I wanted. do you have any idea? Thanks! (sorry my email is fake)


  • XIV-Admin

    XIV-AdminXIV-Admin

    Author Reply

    @Anil –

    Indeed, I looked at that. However, I am trying to describe the basics, with minimal dependence on libraries at this point (JQuery is a noteable exception here).

    I am a firm believer in the notion that, once I understand the core way to do it, I will better understand what I am doing when using a 3rd-party library.

    Spin.js looked very cool, but wasn't what I was trying to learn how to do at the moment.

    Thanks for reading, and taking the time to comment!


  • Anil Jadhav

    How about using Spin.Js ?
    http://fgnass.github.io/spin.js/


  • XIV-Admin

    XIV-AdminXIV-Admin

    Author Reply

    Ack. Thought I fixed that!

    It was a copy/past error as I was putting the code together in the post. I will update shortly!


  • JimmiV

    JimmiVJimmiV

    Author Reply

    Why did you use $(document).ready() twice ?