3

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).

gxclarke
  • 1,953
  • 3
  • 21
  • 42
  • This is more of a presentation issue, can you handle it with css? – Rob Allen May 29 '12 at 19:43
  • Will showing one error at a time frustrate the users? – VJAI May 30 '12 at 09:20
  • @Rob A: The only feasible CSS solution would be via first/nth-of-type, but this is not supported in IE8 and below. – gxclarke May 30 '12 at 19:17
  • @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 May 30 '12 at 19:18

4 Answers4

2

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>
gxclarke
  • 1,953
  • 3
  • 21
  • 42
0

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.

Blast_dan
  • 1,135
  • 9
  • 18
  • I just want to clarify that it is the first error in the form that I want to show, rather than the first error per field. I did adapt your approach to test this, but the problem is that it doesn't address client side validations. – gxclarke May 29 '12 at 20:17
0

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);

Community
  • 1
  • 1
smdrager
  • 7,327
  • 6
  • 39
  • 49
0

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.

VJAI
  • 32,167
  • 23
  • 102
  • 164