3
votes

I am using ASP.NET MVC3 for a form that has both server and client validations. I'm showing error messages as balloons above the inputs. Due to the presentation of the errors, I need to only show one error at a time, otherwise the balloons tend to obscure other fields that may also be in error.

How can I customize the validation behavior to only render the first error message?

Edit: Please notice that the form has both server and client validations, and that I only want to show the first error message for the entire form (not per field).

4
This is more of a presentation issue, can you handle it with css? - Rob Allen
Will showing one error at a time frustrate the users? - VJAI
@Rob A: The only feasible CSS solution would be via first/nth-of-type, but this is not supported in IE8 and below. - gxclarke
@Mark: I don't think so. Errors are the exception when filling out a form, so there will typically only be one or two. Btw... this is exactly how it works in Apple's checkout process, which I think remains quite user friendly while being fairly sophisticated. - gxclarke

4 Answers

2
votes

In case anyone needs it, the solution I came up with is to add the following script towards the bottom of the page. This hooks into the existing javascript validation to dynamically hide all but the first error in the form.

<script>
    $(function() {
        var form = $('form')[0];
        var settings = $.data(form, 'validator').settings;
        var errorPlacementFunction = settings.errorPlacement;
        var successFunction = settings.success;

        settings.errorPlacement = function(error, inputElement) {
            errorPlacementFunction(error, inputElement);
            showOneError();
        }
        settings.success = function (error) {
            successFunction(error);
            showOneError();
        }
        function showOneError() {
            var $errors = $(form).find(".field-validation-error");
            $errors.slice(1).hide();
            $errors.filter(":first:not(:visible)").show();
        }
    });
</script>
0
votes

Could give this a shot on your controller action

var goodErrors = ModelState.GroupBy(MS => MS.Key).Select(ms => ms.First()).ToDictionary(ms => ms.Key, ms => ms.Value);
ModelState.Clear();

foreach (var item in goodErrors)
{
    ModelState.Add(item.Key, item.Value);
}

I'm just selecting only one of each property error, clearing all errors then adding the individual ones back.

this is completely untested but should work.

0
votes

You could create a custom validation summary which would display only the first error. This could be done either by creating an extension for the HtmlHelper class, or by writing your own HtmlHelper. The former is the more straightforward.

public static class HtmlHelperExtensions
{
    static string SingleMessageValidationSummary(this HtmlHelper helper, string validationMessage="") 
    {
        string retVal = "";
        if (helper.ViewData.ModelState.IsValid)
            return "";
        retVal += @"<div class=""notification-warnings""><span>";
        if (!String.IsNullOrEmpty(validationMessage))
            retVal += validationMessage;
        retVal += "</span>";
        retVal += @"<div class=""text"">";
        foreach (var key in helper.ViewData.ModelState.Keys) 
        {
            foreach(var err in helper.ViewData.ModelState[key].Errors)
            retVal += "<p>" + err.ErrorMessage + "</p>";
            break;
        }
        retVal += "</div></div>";
        return retVal.ToString();
    }
}

This is for the ValidationSummary, but the same can be done for ValidationMessageFor.

See: Custom ValidationSummary template Asp.net MVC 3

Edit: Client Side...

Update jquery.validate.unobstrusive.js. In particular the onError function, where it says error.removeClass("input-validation-error").appendTo(container);

Untested, but change that line to: error.removeClass("input-validation-error").eq(0).appendTo(container);

0
votes

Create a html helper extension that renders only one message.

public static MvcHtmlString ValidationError(this HtmlHelper helper)
{
  var result = new StringBuilder();
  var tag = new TagBuilder("div");
  tag.AddCssClass("validation-summary-errors");

  var firstError = helper.ViewData.ModelState.SelectMany(k => k.Value.Errors).FirstOrDefault();

  if (firstError != null)
  {
    tag.InnerHtml = firstError.ErrorMessage;
  }

  result.Append(tag.ToString());
  return MvcHtmlString.Create(result.ToString());
}

Update the jquery.validate.unobtrusive.js OnErrors function as below,

function onErrors(form, validator) {  // 'this' is the form element
   // newly added condition 
   if ($(form.currentTarget).hasClass("one-error")) {
      var container = $(this).find(".validation-summary-errors");
      var firstError = validator.errorList[0];

      $(container).html(firstError.message);
    }
    else {
      var container = $(this).find("[data-valmsg-summary=true]"),
            list = container.find("ul");

      if (list && list.length && validator.errorList.length) {
        list.empty();
        container.addClass("validation-summary-errors").removeClass("validation-summary-valid");

        $.each(validator.errorList, function () {
          $("<li />").html(this.message).appendTo(list);
        });
      }
    }
  }

Basically we have added a condition in the OnError to check whether the form contains a css-class named one-error and if yes then displays a single error else display all.