MVC 4.0: Autocomplete with jQuery UI

mvc

Autocomplete is a very nice feature that makes your website more responsive to user inputs. I like this feature very much 🙂

Before you use autocomplete, you can set up the application to include the base theme style sheet by adding it to the layout view:


<link href="~/Content/themes/base/jquery-ui.css" rel="stylesheet" type="text/css" />

You’ll need to fi nd the input element from JavaScript and attach the jQuery autocomplete behavior. One approach to do this is to borrow an idea from the MVC framework and use a data dash attribute:


<input type="text" name="q" data-autocomplete-source="@Url.Action("QuickSearch", "Home")" />

Note that you’ve pointed the source to a controller action.

You can use the following code during the ready event to attach autocomplete to all inputs with the data-autocomplete-source attribute:


$("input[data-autocomplete-source]").each(function () {
var target = $(this);
target.autocomplete({ source: target.attr("data-autocomplete-source") });
});

Autocomplete expects to call a data source and receive a collection of objects it can use to build a list for the user. The QuickSearch action of the HomeController needs to return data in a format autocomplete will understand.

Autocomplete expects to call a data source and receive objects in JSON format. Fortunately, it’s easy to generate JSON from an MVC controller action, as you’ll see soon. The objects must have a property called label, or a property called value, or both a label and a value. Autocomplete uses the label property in the text it shows the user. When the user selects an item from the autocomplete list, the widget will place the value of the selected item into the associated input. If you don’t provide a label, or don’t provide a value, autocomplete will use whichever property is available as both the value and the label.

To return the proper JSON, you’ll implement QuickSearch with the following code:


public ActionResult QuickSearch(string term)
{
var artists = GetArtists(term).Select(a => new {value = a.Name});
return Json(artists, JsonRequestBehavior.AllowGet);
}
private List<Artist> GetArtists(string searchString)
{
return storeDB.Artists
.Where(a => a.Name.Contains(searchString))
.ToList();
}

You are ready to run your application now, you should have something like the following:

autocomplete

Thank you 🙂

Keep It Simple & Straightforward.

~:H}{H:~

 

Advertisements

MVC 4.0: How to support client-side custom validation

mvcI talked about CUSTOM VALIDATION LOGIC  and we saw how to implement custom validation logic to our model classes.

Here we will see how to support client-side validation for our custom validation attribute.

I will start with the solution then I will explain it in details.

    public class MaxWordsAttribute : ValidationAttribute,
                                     IClientValidatable
    {
        public MaxWordsAttribute(int wordCount)
            :base("Too many words in {0}")
        {
            WordCount = wordCount;
        }

        public int WordCount { get; set; }

        protected override ValidationResult IsValid(
            object value,
            ValidationContext validationContext)
        {
            if (value != null)
            {
                var wordCount = value.ToString().Split(' ').Length;
                if (wordCount > WordCount)
                {
                    return new ValidationResult(
                        FormatErrorMessage(validationContext.DisplayName)
                    );
                }
            }
            return ValidationResult.Success;
        }

        public IEnumerable<ModelClientValidationRule> GetClientValidationRules(
            ModelMetadata metadata, ControllerContext context)
        {
            var rule = new ModelClientValidationRule();
            rule.ErrorMessage = FormatErrorMessage(metadata.GetDisplayName());
            rule.ValidationParameters.Add("wordcount", WordCount);
            rule.ValidationType = "maxwords";
            yield return rule;
        }
    }

To support client-side validation, you need your attribute to implement an interface.
The IClientValidatable interface defines a single method: GetClientValidationRules.
When the MVC framework finds a validation object with this interface present, it invokes GetClientValidationRules to retrieve — you guessed it — a sequence of ModelClientValidationRule objects. These objects carry the metadata, or the rules, the framework sends to the client.

If you think about the scenario, there are a few pieces of information you’d need on the client to run the validation:

  • What error message to display if the validation fails (Error Message)
  • How many words are allowed (Validation Parameters)
  • An identifi er for a piece of JavaScript code that can count the words (ValidationType)

The MVC framework takes the rules given back from the GetClientValidationRules method and serializes the information into data dash attributes on the client:

<input
data-val="true"
data-val-length="The field Title must be a string with a maximum length of 160."
data-val-length-max="160"

data-val-maxwords="Too many words in Title"
data-val-maxwords-wordcount="10"

data-val-required="An Album Title is required" id="Title" name="Title"
type="text" value="For Those About To Rock We Salute You" />

Now you have metadata on the client, but you still need to write some script code to execute the validation logic.

You’ll need two pieces of script in place for validation to work:

  • The adapter: The adapter works with the unobtrusive MVC extensions to identify the required metadata. The unobtrusive extensions then take care of retrieving the values from data dash attributes and adapting the data to a format jQuery validation can understand.
  • The validation rule itself: This is called a validator in jQuery parlance.
/// <reference path="jquery-1.6.2.js" />
/// <reference path="jquery.validate.js" />
/// <reference path="jquery.validate.unobtrusive.js" />

if ($.validator && $.validator.unobtrusive) {

    $.validator.unobtrusive.adapters.addSingleVal("maxwords", "wordcount");

    $.validator.addMethod("maxwords", function (value, element, maxwords) {
        if (value) {
            if (value.split(' ').length > maxwords) {
                return false;
            }
        }
        return true;
    });

}

You have four kinds of Adapter Methods (addBool, addSingleValue, addMinMax, add). For the maximum words scenario, you could use either addSingleVal or addMinMax (or add, because it can do anything).

The validator method takes two parameters:

  • The name of the validator, which by convention matches the name of the adapter (which matches the ValidationType property on the server).
  • A function to invoke when validation occurs.

The validator function accepts three parameters and can return true (validation passed) or false (validation failed):

  • The fi rst parameter to the function will contain the input value (like the title of an album).
  • The second parameter is the input element containing the value to validate (in case the value itself doesn’t provide enough information).
  • The third parameter will contain all the validation parameters in an array, or in this case, the single validation parameter (the maximum number of words).

Thank You,
Keep It Simple & Straightforward 🙂
~:H}{H:~

MVC 4.0: CUSTOM VALIDATION LOGIC

The extensibility of the ASP.NET MVC framework means an infinite number of possibilities exist for implementing custom validation logic. However, this post focuses on two core scenarios:

  • Packaging validation logic into a custom data annotation
  • Packaging validation logic into a model object itself

Custom Annotations

Imagine you want to restrict the last name value of a customer to a limited number of words. For example, you might say that 10 words are too many for a last name.

    public class MaxWordsAttribute : ValidationAttribute
    {
        public MaxWordsAttribute(int maxWords)
            :base("{0} has too many words.")
        {
            _maxWords = maxWords;
        }

        protected override ValidationResult IsValid(
            object value, ValidationContext validationContext)
        {
            if (value != null)
            {
                var valueAsString = value.ToString();
                if (valueAsString.Split(' ').Length > _maxWords)
                {
                    var errorMessage = FormatErrorMessage(validationContext.DisplayName);
                    return new ValidationResult(errorMessage);
                }
            }
            return ValidationResult.Success;
        }

        private readonly int _maxWords;
    }

With the validation logic in place, you can apply the attribute to any model property:

    [Required]
    [StringLength(160)]
    [MaxWords(10, ErrorMessage="There are too many words in {0}")]
    public string LastName { get; set; }

Now if the customer types in too many words, he’ll see the message in the following figure in the view.
maxwords validation

IValidatableObject

A self-validating model is a model object that knows how to validate itself. A model object can announce this capability by implementing the IValidatableObject interface.

    public class Order : IValidatableObject
    {
        public IEnumerable<ValidationResult> Validate(
        ValidationContext validationContext)
        {
            if (LastName != null &&
            LastName.Split(' ').Length > 10)
            {
                yield return new ValidationResult("The last name has too many words!",
                new[] { "LastName" });
            }
        }
        // rest of Order implementation and properties
        // ...
    }

This has a few notable differences from the attribute version.

  • The method the MVC run time calls to perform validation is named Validate instead of IsValid, but more important, the return type and parameters are different.
  • The return type for Validate is an IEnumerable instead of a single ValidationResult, because the logic inside is ostensibly validating the entire model and might need to return more than a single validation error.
  • There is no value parameter passed to Validate because you are inside an instance method of the model and can refer to the property values directly.

Many validation scenarios are easier to implement using the IValidatableObject approach, particularly scenarios where the code needs to compare multiple properties on the model to make a validation decision.

Thank you,
Keep It Simple and Straightforward 🙂
~:H}{H:~