Looking at ASP.NET MVC 5.1 and Web API 2.1 - Part 3 - Bootstrap and JavaScript enhancements - Jon Galloway

Looking at ASP.NET MVC 5.1 and Web API 2.1 - Part 3 - Bootstrap and JavaScript enhancements

This is part 3 of a 4 part series covering some of the new features in the ASP.NET MVC 5.1 and Web API 2.1 releases.

In this post, we'll be focusing on some client-side improvements to ASP.NET MVC 5.1.

As a reminder if you haven't read the first post, these updates are currently delivered via a NuGet update to your existing ASP.NET MVC 5 / Web API 2 applications. They'll be part of the File / New Project templates included in an upcoming Visual Studio update.

EditorFor now supports passing HTML attributes - Great for Bootstrap

The new ASP.NET project templates all include Bootstrap themes. Bootstrap uses custom class names for everything - styling, components, layout, behavior. That made it frustrating that you couldn't pass classes down to the Html.EditorFor HTML helper: you either had to use specific HTML Helpers like Html.TextBoxFor (which do allow you to pass HTML attributes, but don't benefit from some of the other nice features in HTML.EditorFor, like Data Attribute support for display and input validation) or give up on using the Bootstrap classes and style things yourself.

In the 5.1 release, you can now pass HTML attributes as an additional parameter to Html.EditorFor, allowing you to get the best of both. Here's an example of why that's useful.

In the first post in the series, we scaffolded a simple create controller and associated views. The Create view ended up looking like this:

2014-01-28_10h51_55

That's okay, but it's not taking advantage of any of the Bootstrap form styling (e.g. focus indication, element sizing, groups, etc.) and it won't do anything special with custom Bootstrap themes. A great start would be just to add the "form-control" class to the form elements. That just involves changing from this:

@Html.EditorFor(model => model.FirstName)

to this:

@Html.EditorFor(model => model.FirstName, new { htmlAttributes = new { @class = "form-control" }, })

When I make that update to the textboxes, I get this view:

2014-01-28_00h51_07

You'll notice some subtle improvements, like the focus highlight on the FirstName field, nicer textbox size and validation layout for Age, etc. These are just really simple things with a  very basic model, but they give a quick idea of the improvement here.

Also nice is that I can pass the attributes on Html.EditorFor when displaying the entire model. Here I've updated the entire form section to just use one EditorFor call, passing in the model:

@using (Html.BeginForm()) 
{
    @Html.AntiForgeryToken()
    
    <div class="form-horizontal">
        <h4>Person</h4>
        <hr />
        @Html.ValidationSummary(true)
        @Html.EditorFor(model => model, new { htmlAttributes = new { @class = "form-control" }, })
        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Create" class="btn btn-default" />
            </div>
        </div>
    </div>
}

Note that to make sure the Id property didn't display and to use the custom radio enum display template (as explained in the first post in the series), I added two annotations to my model. Here's how the model and associated Enum look:

public class Person
{
    [ScaffoldColumn(false)]
    public int Id { get; set; }
    [UIHint("Enum-radio")]
    public Salutation Salutation { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }
}
//I guess technically these are called honorifics
public enum Salutation : byte
{
    [Display(Name = "Mr.")]   Mr,
    [Display(Name = "Mrs.")]  Mrs,
    [Display(Name = "Ms.")]   Ms,
    [Display(Name = "Dr.")]   Doctor,
    [Display(Name = "Prof.")] Professor,
    Sir, 
    Lady, 
    Lord
}

That gives me the exact same output as shown above (the second, nicer screenshot). What's cool there is that the EditorFor method passed that form-control class to each element in the form, so each input tag got the form-control class. That means that I could apply additional Bootstrap classes, as well as my own custom classes, in that same call:

        @Html.EditorFor(model => model, new { htmlAttributes = new { @class = "form-control input-sm my-custom-class" }, })

You can see the code changes and associated tests for this EditorFor change on this commit on CodePlex.

Client-side validation for MinLength and MaxLength

This is a pretty straightforward one: we had client-side validation for StringLength before, but not for MinLength and MaxLength. Personally, I feel like it's a tossup on which to use - StringLength lets you set both min and max and is more widely supported, but MinLength and MaxLength allow you to specify them separately and give different validation messages for each. Regardless, the good news is that whichever you use, they're both supported on both server and client.

To test that out, we'll add some MinLength and MaxLength attributes to our Person class.

public class Person
{
    [ScaffoldColumn(false)]
    public int Id { get; set; }
    [UIHint("Enum-radio")]
    public Salutation Salutation { get; set; }
    [Display(Name = "First Name")]
    [MinLength(3, ErrorMessage = "Your {0} must be at least {1} characters long")]
    [MaxLength(100, ErrorMessage = "Your {0} must be no more than {1} characters")]
    public string FirstName { get; set; }
    [Display(Name = "Last Name")]
    [MinLength(3, ErrorMessage = "Your {0} must be at least {1} characters long")]
    [MaxLength(100, ErrorMessage = "Your {0} must be no more than {1} characters")]
    public string LastName { get; set; }
    public int Age { get; set; }
}

I get immediate feedback on what the website thinks of a potential stage name I've been considering:

2014-01-28_14h45_16

Here's the link to the work item, and here's the code diff for the commit.

Three small but useful fixes to Unobtrusive Ajax

There are a few fixes to Unobtrusive Ajax:

I thought the first fix was pretty interesting: a question came up on StackOverflow, someone posted a suggested one line fix on a CodePlex issue, and it got fixed in this commit.

This issue allows callbacks from Unobtrusive Ajax to have access to the initiating element.  That's pretty handy when you've got multiple potential callers, e.g. a list of items which contain Ajax.ActionLink calls. In the past, I've had to write unnecessarily complicated JavaScript to wire things up manually because I couldn't take advantage of the OnBegin, OnComplete, OnFailure and OnSuccess options, e.g.

<script type="text/javascript">
    $(function () {
        // Document.ready -> link up remove event handler
        $(".RemoveLink").click(function () {
            // Get the id from the link
            var recordToDelete = $(this).attr("data-id");
            if (recordToDelete != '') {
                // Perform the ajax post
                $.post("/ShoppingCart/RemoveFromCart", {"id": recordToDelete },
                    function (data) {
                        // Successful requests get here
                        // Update the page elements
                        if (data.ItemCount == 0) {
                            $('#row-' + data.DeleteId).fadeOut('slow');
                        } else {
                            $('#item-count-' + data.DeleteId).text(data.ItemCount);
                        }
                        $('#cart-total').text(data.CartTotal);
                        $('#update-message').text(data.Message);
                        $('#cart-status').text('Cart (' + data.CartCount + ')');
                    });
            }
        });
    });
</script>

Now with this change, I'd have the option of wiring up the Ajax call and success callbacks separately and tersely, since they'd have access to the calling element for the ID.

That's it for this post, in the next (and definitely last) post of this series I'll look at some ASP.NET Web API 2.1 improvements.

Published Tuesday, January 28, 2014 3:17 PM by Jon Galloway
Filed under: ,

Comments

No Comments