Click here to monitor SSC
  • Av rating:
  • Total votes: 29
  • Total comments: 7
Dino Esposito

Modal Input Forms in ASP.NET MVC

08 May 2013

Forms in websites have, for a long time, languished in their classic clunky pattern of browser-led 'submit' of content, using the FORM tag. As websites grow nearer to applications in their user-experience, so better is required. Dino show how to get a more sophisticated modal input form based on Twitter Bootstrap, jQuery Validate, and XmlHttpRequest (XHR).

The world is over-populated with articles and blog posts that emphasize the importance of user-experience (UX) beyond imagination. So often, this describes an ideal world; in reality, too many web sites fall short of providing even a decent, let alone a great, experience to their users. Whereas designers are putting much effort in improving certain aspects of UX such as the plain presentation of data, other important aspects such as Data entry is not yet receiving the design attention that it deserves. Hopefully, it’s only a matter of time.

For a long time, web input forms have been built around the FORM tag and a browser-led content submission. This originates an HTTP POST call to the target URL; data is received, massaged into easy-to-use data structures and becomes ready for further use. In this regard, the model binding layer of ASP.NET MVC does really a fantastic job.

The problem is that this fantastic job takes place over a full page refresh and in some cases over two HTTP requests—original POST and redirect to GET the same page. This is the popular PRG pattern (Post-Redirect-Get) introduced so that, out of a form post, the last operation tracked by the browser is a GET. If the user hits F5 (or selects ‘Refresh’) the browser repeats the last operation which is now an innocuous GET rather than a potentially-unsafe POST.

Is there a way to implement input forms without a full page refresh? There a few ways; but all of them have quirks and therefore are subject to personal preferences. In general, I use two possible approaches for building my own forms: plain old form posts over PRG and modal input forms via Ajax. In this article, I’ll discuss how I build modal input forms. Technologies involved are Twitter Bootstrap for the user interface (and modal popup infrastructure), jQuery Validate for client-side checks, and XmlHttpRequest (XHR) for posting.

Preliminaries

Bootstrap is a UI framework developed by Twitter that you can download from http://twitter.github.io/bootstrap. It consists of CSS and JavaScript files and allows you to style HTML elements with predefined CSS classes. The overall idea is similar to jQuery UI and jQuery Mobile; the results are more compact markup and, more importantly, support for responsive design. All you need to do is linking the bootstrap CSS file and also the JavaScript file(s) if you plan to use some of the styles that are bound to script code. Bootstrap works on top of jQuery and uses some jQuery plugins for more advanced tasks. Bootstrap has its own default gray-ish theme; you can create your own theme following the Customize link from the Bootstrap home page or go to http://bootswatch.com for a few free options.

Step 1: Displaying the Modal Popup

In the layout page(s) of your ASP.NET MVC application, you link the bootstrap files, as below:

<link rel="stylesheet" 
      href="@Url.Content("~/Content/Styles/bootstrap.min.css")"></link>   
<link rel="stylesheet" 
      href="@Url.Content("~/Content/Styles/bootstrap-responsive.min.css")"></link>   

<script src="@Url.Content("~/Content/Scripts/jquery-1.8.3.min.js")" 
        type="text/javascript"></script>      
<script src="@Url.Content("~/Content/Scripts/bootstrap.min.js")" 
        type="text/javascript"></script>

At some point in your web page you should have a trigger for the modal popup. This is likely a button or an anchor the user clicks on. For example, you can have the following:

<a type="button" 
   class="btn" 
   href="#article-editor" 
   data-toggle="modal">Click me</a>

The attributes type and data-toggle are meaningful to Bootstrap and are blissfully ignored by browsers. The class attribute is just the standard attribute used to attach a CSS style to the element. Finally, the href attribute points to the next markup as expected.

In particular, the btn name refers to one of the predefined Bootstrap classes that render out anchors as buttons. The most significant attribute here is data-toggle. Set to “modal” it tells the framework that the markup referenced by href should be displayed as a modal popup when the anchor is clicked. In the example, “article-editor” is the ID of a plain DIV element within the page that will be popped up. Here’s some possible content for the modal popup:

<div class="modal fade" id="article-editor">
    <div class="modal-header">
       <a class="close" data-dismiss="modal">&times;</a>
       <h3>Title of the form</h3>
    </div>
    <div class="modal-body">
       <p>Body of the form</p>
    </div>
    <div class="modal-footer">
       <a href="#" class="btn" data-dismiss="modal">Close</a>
       <a href="#" class="btn btn-primary">Save Changes</a>
    </div>
</div>

The combined effect of anchor and DIV is shown by the figure below. (As a side note, I’m using here the Slate theme for Bootstrap from the aforementioned http://bootswatch.com site.)

The combined effect of anchor and DIV

There are a few things to notice in the markup for the DIV. The DIV must be marked with the modalhide CSS classes to avoid it is displayed upon page loading. You can replace hide with fade to enable slide (yes, right) transitions. By default, the popup slides from the top. You can change that by editing some of the Bootstrap classes in your app. Here’s how to force the popup to kick in from the left.

<style>
    #container .modal.fade {
         left: -25%;
          -webkit-transition: opacity 0.3s linear, left 0.3s ease-out;
             -moz-transition: opacity 0.3s linear, left 0.3s ease-out;
               -o-transition: opacity 0.3s linear, left 0.3s ease-out;
                  transition: opacity 0.3s linear, left 0.3s ease-out;
    }
    #container .modal.fade.in {
        left: 50%;
    }
</style>

Note that #container here refers to the DIV that contains the modal markup. The markup below, instead, sets a custom maximum height for the body of the modal popup. For the real height, you should consider also header and footer.

<style>
    #container .modal-body {
        max-height: 50px;
    }
</style>

Should the required height exceed the maximum set programmatically, scrollbars will be used. To customize the width you also need to fix the margins and not just the width. In addition, to control width you should work at the modal DIV level:

#article-editor {
    width: 600px;
    margin-left: -300px;
    margin-right: -300px;
}

The modal-header and modal-footer classes just add some predefined graphics to header and footer. The data-dismiss attribute, instead, runs no action on a button click and just dismisses the popup.

Step 2: Defining the Input Form

It is no big deal to create the HTML form to be hosted within the modal popup. It boils down to wrapping the body of the popup with the usual BeginForm call:

@using (Html.BeginForm("articles", "editor", FormMethod.Post, new { id="articleForm" }))
{
   ...
}

Bootstrap, however, may help a bit in laying out input fields. At the worst, though, you might want to use the ASP.NET built-in Editor-for-Model. It’s quick and often it’s good enough but in doing so it should be clear that you’re missing entirely the whole point of a good UX. The skeleton of the form is presented below. Layout is defined using Bootstrap predefined classes.

<div class="modal-body">
    <div class="row-fluid">
        <div class="controls span6">
            <label class="control-label" for="Title">Title and summary</label>
            <input type="text" 
                   class="input-xlarge" id="Title" name="Title" 
                   placeholder="Title of the article" />
            <textarea class="input-xlarge" id="Summary"
                      name="Summary" rows="5" cols="0" 
                      placeholder="Summary"></textarea>
        </div> 
        <div class="controls span6">
            <label class="control-label" for="Title">Date of publication</label>
            <input type="month" class="input-xlarge" id="PubMonth" name="PubMonth" />
            <label class="control-label" for="Magazine">Publication</label>
            <input type="text" class="input-xlarge" id="Magazine" name="Magazine" 
                   placeholder="Magazine" />
            <input type="text" class="input-xlarge" id="Url" name="Url" 
                   placeholder="Url" />
        </div> 
    </div>
</div>
<div id="validationSummary" class="validation-summary">
    <ul></ul>
</div>

In particular, the class controls groups controls together and is ideal for label and input to go hand in hand. A few predefined classes exist for input field sizing such as input-xlarge and input-mini. Unless you have special needs for width you might want to go with these.

A bit more of attention deserves the span6 class. Bootstrap defines horizontal fluid rows split in up to 12 regions. The spanX class indicates how large each should be. By using span6 on two DIV elements you indicate you want them to be placed side by side on the same logical row as two TD cells in a TABLE. All in all, I think this is the best way to explain the concept to most developers. (And this is the way I learned it myself.) Note also the use of the HTML5 month input field to pick up only month and year. The resulting form is in the figure below.

#

Step 3: Initializing the Input Form

When displaying a form within a regular HTML page, you can leverage the Razor facilities to initialize fields. If you intend to use a modal popup, you likely need to perform dynamic initialization of the fields as you’ll be using the same form to edit multiple records. In addition, you probably want to set up things for client-side validation. In this example I’m writing my own jQuery Validate code. To do both things, you need to run some initialization code in the document’s ready event of the page that hosts the modal popup:

<script type="text/javascript">
    $(document).ready(function () {
        gladiatorApp.initArticleModal();
    });
</script>

In the initialization code you need to do two basic things. First, you set up validation rules; second, you initialize input fields to the values of the record to edit. Assuming you’re using jQuery Validate, here’s some code you can use with the form of the figure above:

gladiatorApp.initArticleModal = function () {
    $("#articleForm").validate({
        rules: {
            Title: { required: true, minlength: 4 },
            Magazine: { required: true, minlength: 4 },
            PubMonth: { required: true, date: true }
        },
        messages: {
            Title: "Article title is required (at least 4 chars).",
            Magazine: "Magazine name is required is required (at least 4 chars)."
        },
        errorContainer: "#validationSummary",
        errorLabelContainer: "#validationSummary ul",
        wrapper: "li",
        submitHandler: function (form) {
            gladiatorApp.addArticle($(form));
        }
    });

    // More here ...
}

Title and magazine are required fields along with the month of publication. Errors should be displayed in the given and wrapped in a UL list.

A very common scenario for using a modal popup is to edit a record you select from a list in a drill-down mode. You can place a remote call to a JSON endpoint to retrieve the details to edit. However, this requires you know at least the ID of the element to retrieve. Here’s a way to rewrite the trigger of the modal:

<table style="width:50%">
@foreach (var a in Model.Articles)
{
   <tr>
     <td>
       <a type="button" class="btn mylink" 
          href="#article-editor" 
          data-toggle="modal" 
          data-id="@a.Id">@a.Title</a>
     </td>
   </tr>
}
</table>

The data-id element stores the unique identifier to be used to download details of the data element—the article in this case. You also noticed that I added an extra class to the anchors—the mylink class. The class isn't defined anywhere and just serves the purpose of quickly and reliably identifying all and only the anchors of the current list. I’m using this class in the rest of the initArticleModal script function.

$(".btn.mylink").on("click", function () {
        var articleId = $(this).data('id');
        $.ajax({
            url: "/article/get/" + articleId,
            cache: false
        }).done(function (data) {
            $("#Title").val(data.Title);
            $("#Summary").val(data.Summary);
            $("#Magazine").val(data.Magazine);
            $("#Url").val(data.Url);
            $("#PubMonth").val(data.Year.toString() + "-" + 
                               ("00"+data.Month.toString()).slice(-2));
        });
    });

The function attaches a handler for the click event of all article buttons. When the button is clicked, JSON for the article matching the ID is downloaded and input fields in the modal popup are updated. This happens for every article you click to edit through the popup. To serve JSON, you need an ad hoc controller method:

public class ArticleController : Controller
{
    private readonly ArticleRepository _repository;

    public ArticleController()
    {
        _repository = new ArticleRepository();
    }

    public JsonResult Get(Int32 id)
    {
        return Json(_repository.Get(id), JsonRequestBehavior.AllowGet);
    }
}

We’re almost done. Before we attack the final step let’s have a look at the modal popup when users try to submit incomplete data and client validation fails.

The error shown when validation fails

Step 4: Posting the Form

The JavaScript code shown earlier contains in the section where jQuery Validate was set up an assignment to submitHandler. This is a property of jQuery Validate that sets a function of yours as the handler which gets control of the form submission.

submitHandler: function (form) {
    gladiatorApp.addArticle($(form));
}

In light of this, the addArticle JavaScript function is where the upload takes place. By using Ajax to post the form, you gain total control of the process.

gladiatorApp.addArticle = function (form) {
    var url = "/editor/articles";
    var postData = form.serialize();

    $.post(url, postData, function (data) {
        if (data.toLowerCase() == "ok") {
            alert("Data saved!");
            return;
        }

        alert("Something went wrong. Please retry!");
    });
};

The screenshot below shows that the code posts correctly to the ASP.NET MVC backend and breakpoints are hit and model binding works as expected.

The code posting correctly to the MVC backend

Note that the controller method returns a plain string; this can be whatever you want, from a plain OK to a complex list of error messages. Messages are received as a string and JavaScript code is in charge of displaying that to the user.

The Bottom Line

Modal dialog boxes are cool and useful because they contribute to reduce requests pollution and save the HTTP planet. However, we are still lacking consolidated workflows and patterns to code modal input forms around ASP.NET MVC. This approach that relies on Bootstrap and underlying jQuery plugins is my favorite at the moment. Your thoughts?

Dino Esposito

Author profile:

A long-time trainer and consultant, Dino is the author of many popular books for Microsoft Press for .NET developers.including “Architecting Mobile Solutions for the Enterprise“ and “Programming ASP.NET MVC” both for Microsoft Press. CTO of Crionet, a firm specializing in Web-based and mobile solutions for sport events across Europe (http://www.crionet.com), at the moment Dino is also technical evangelist for JetBrains, where he focuses on Android and Kotlin development, and member of the team that manages WURFL—the database of mobile devices used by organizations such as Google and Facebook. Follow Dino through his blog at http://software2cents.wordpress.com or at http://twitter.com/despos

Search for other articles by Dino Esposito

Rate this article:   Avg rating: from a total of 29 votes.


Poor

OK

Good

Great

Must read
Have Your Say
Do you have an opinion on this article? Then add your comment below:
You must be logged in to post to this forum

Click here to log in.


Subject: thanks
Posted by: Kourosh (not signed in)
Posted on: Sunday, May 12, 2013 at 2:39 AM
Message: thank you for this useful post,

Subject: issue
Posted by: chinthana (not signed in)
Posted on: Monday, May 13, 2013 at 12:38 AM
Message: nice post
i've one concern its not directly to this demo.
when we are using jquery ajax method ,it can be
viewed by the user and who also has chance to change the id(hidden filed).
how do we face overcome of this issue

Chinthana [sri lanka]

Subject: Awesome post
Posted by: Laxmeesh Joshi (not signed in)
Posted on: Thursday, May 23, 2013 at 10:29 PM
Message: Awesome post Sir.. Thanks for sharing.

Subject: Article on Web Data and quickening the pulse of business
Posted by: William Tucker in Sprigfield, MO (not signed in)
Posted on: Wednesday, May 29, 2013 at 7:41 AM
Message: What most people don't realize is that a good interface is invisible to the user. Getting the work done is what is most important. "The work" is data, sending or retrieving data....everything else is superfluous....unless you're just selling the exterior/body and ignoring the engine/wheels/transmission....and hopes no one catches on that it looks great but runs like molassess.

Great article.

Subject: Validation Popovers
Posted by: PKG (not signed in)
Posted on: Monday, June 03, 2013 at 3:16 AM
Message: Nice Post.

I would recommend the use of the Validation Popover plugin to make the validation messages pop up over the relevant inputs. Works very nicely.

https://github.com/mdobie/TWBootstrapJQValPopover/blob/master/index.html


Subject: some issues
Posted by: judo132 (view profile)
Posted on: Friday, July 12, 2013 at 9:19 PM
Message: Its a nice article but following it through there are a few issues.

The jquery validate bundle needs to be added as this is a separate library.

I'm not sure how the Save Changes link/button is wired up to submit the form - as far as I can see it is just a hyperlink with a class applied to it and won't cause any form submission.

Subject: source files
Posted by: vorikua (view profile)
Posted on: Thursday, October 10, 2013 at 10:02 AM
Message: Dino, thank you for your article. Have you made the source files of this project available? For someone that is starting to develop in this environment having the source files of the working example would be great.

If you have made them available, could you let me know the URL?

Thanks

 

Top Rated

Acceptance Testing with FitNesse: Multiplicities and Comparisons
 FitNesse is one of the most popular tools for unit testing since it is designed with a Wiki-style... Read more...

Acceptance Testing with FitNesse: Symbols, Variables and Code-behind Styles
 Although FitNesse can be used as a generic automated testing tool for both applications and databases,... Read more...

Acceptance Testing with FitNesse: Documentation and Infrastructure
 FitNesse is a popular general-purpose wiki-based framework for writing acceptance tests for software... Read more...

TortoiseSVN and Subversion Cookbook Part 11: Subversion and Oracle
 It is only recently that the tools have existed to make source-control easy for database developers.... Read more...

TortoiseSVN and Subversion Cookbook Part 10: Extending the reach of Subversion
 Subversion provides a good way of source-controlling a database, but many operations are best done from... Read more...

Most Viewed

A Complete URL Rewriting Solution for ASP.NET 2.0
 Ever wondered whether it's possible to create neater URLS, free of bulky Query String parameters?... Read more...

Visual Studio Setup - projects and custom actions
 This article describes the kinds of custom actions that can be used in your Visual Studio setup project. Read more...

.NET Application Architecture: the Data Access Layer
 Find out how to design a robust data access layer for your .NET applications. Read more...

Calling Cross Domain Web Services in AJAX
 The latest craze for mashups involves making cross-domain calls to Web Services from APIs made publicly... Read more...

Web Parts in ASP.NET 2.0
 Most Web Parts implementations allow users to create a single portal page where they can personalize... Read more...

Why Join

Over 400,000 Microsoft professionals subscribe to the Simple-Talk technical journal. Join today, it's fast, simple, free and secure.