Reputation: 1319
I have the following models
public class A
{
public string Ecuation { get; set; }
...
public B B { get; set; }
}
public class B // validation attributes omitted
{
public int Number1 { get; set; }
public int Number2 { get; set; }
public int Number3 { get; set; }
}
and an EditorTemplate
for B
(its reused in other views)
@model B
<div id="EcuationGenerator">
@Html.EditorFor(x => x.Number1)
@Html.ValidationMessageForBootstrap(x => x.Number1)
.... // ditto for other properties
<button id="sendEcuation">Fill Ecuation</button>
</div>
<script>
$('#sentEcuation').on('click', function (e) {
e.preventDefault();
$.ajax({
cache: false,
type: "POST",
url: '@Url.Action("ValidateB")',
data: $('#EcuationGenerator :input').serialize(),
dataType: "json"
})
.done(function (data) {
if (data.Valid) {
// working here about how can i get the 'Ecuation' to fill the input
return;
}
$.each(data.Errors, function (key, value) {
// The following is not working to update the error messages
if (value != null) {
key = key.replace('.', '\\.');
$(":input[name='" + key + "']").addClass("is-invalid");
key = key.replace('[', '\\['); key = key.replace(']', '\\]');
$("#Err_" + key).text(value[value.length - 1].ErrorMessage);
}
});
})
.fail(function (xhr) {
alert(xhr.responseText);
alert("Critical Error!. Failed to call the server.");
});
});
</script>
Where the ValidationMessageForBootstrap()
extension method is
public static MvcHtmlString ValidationMessageForBootstrap<TModel, TValue>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TValue>> expression, object htmlAttributes = null)
{
if (expression == null) { throw new ArgumentNullException("Campo vacío"); }
string result = string.Empty;
try
{
Func<TModel, TValue> deleg = expression.Compile();
result = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(ExpressionHelper.GetExpressionText(expression));
}
catch { }
var attributes = HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);
attributes["class"] += " invalid-feedback";
attributes["id"] += "Err_" + result;
TagBuilder generador = new TagBuilder("div");
generador.MergeAttributes(new RouteValueDictionary(attributes));
return new MvcHtmlString(generador.ToString());
}
and the main view is
@model A
....
@Html.EditorFor(x => x.Ecuation, new {htmlAttributes = new { @readonly = "readonly" }})
<button class="btn btn-outline-dark rounded-right" data-keyboard="false" data-toggle="modal" data-target="#modal" id="abrirConexionado" type="button">Fill Ecuation!</i></button>
<div class="modal fade" id="modal" tabindex="-1" role="dialog" aria-labelledby="myLargeModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-body">
@Html.EditorFor(m => m.B })
</div>
</div>
</div>
</div>
When I click the button in the template, I make an ajax call to post the value of the inputs in the template to a controller method that perform some server side calculations. If the value of the inputs are not valid I get the ModelState
errors and return then in a JsonResult
, which I then want to use to update the message in the element generated by my ValidationMessageForBootstrap()
methods.
The controller method is
public JsonResult ValidateB(B b)
{
var valid = TryUpdateModel(b)
if(valid)
{
error = false;
//More Logic
}
return Json(new
{
EcuationResult = //logic,
Valid = valid,
Errors = getErrorsForModel()
});
}
The getErrorsForModel()
method returns a IDictionary<string, object>
with the ModelState errors.
My problem is that the names of the properties returned in the JsonResult
are just Number1
etc and do not include the full name (i.e. B.Number1
) which is generated in the view, and my code in the ajax .done()
function is not updataing the messages.
How can I find the correct elements in the view in order to update the error messages?
Upvotes: 0
Views: 766
Reputation:
The first thing you need to do is delete that ValidationMessageForBootstrap()
extension method and use the inbuilt @Html.ValidationMessageFor()
which correctly generates the placeholder for client and server side validation messages. If you inspect the <span>
that it generates, it has a data-valmsg-for
attribute whose value is the full name of the property which you can use to find the the message placeholder associated with a property.
Next delete the scripts from your template - scripts go in the main view or its layout, not in partial views.
Your EditorTemplate
should be
@model B
<div id="EcuationGenerator">
@Html.EditorFor(x => x.Number1)
@Html.ValidationMessageFor(m => m.x.Number1) // add
... // ditto for other properties
<button type="button" id="sendEcuation">Fill Ecuation</button> // add type="button"
</div>
Then change the code in the server method to get a collection of the validation messages using
var errors = ModelState.Keys.Where(k => ModelState[k].Errors.Count > 0)
.Select(k => new { propertyName = k, errorMessage = ModelState[k].Errors[0].ErrorMessage });
and assign that to the Errors
property of your JsonResult
.
You can then determine the 'prefix' of the property name using the javascript .split()
, pop()
and .join()
methods, and append the propertyName
returned in the errors collection to find the associated input and its message placeholder, then update the message text and class names of those elements
And then the script (in the main view) will be
$('#sentEcuation').click(function() {
// Determine the prefix of the property names
var fullName = $('#EcuationGenerator').find('input').first().attr('name'); // returns "B.Number1"
var parts = fullName.split('.'); // returns [ "B", "Number1" ]
parts.pop(); // returns [ "B" ]
var prefix = parts.join('.') + '.'; returns "B."
$.ajax({
....
})
.done(function (data) {
if (data.Valid) {
// Update input with the result
$('#Ecuation').val(data.EcuationResult);
return;
}
$.each(data.Errors, function (index, item) {
var fullName = prefix + item.propertyName;
// Get the validation message placeholder
var element = $('[data-valmsg-for="' + fullName + '"]');
// Update message
element.append($('<span></span>').text(item.errorMessage));
// Update class names
element.removeClass('field-validation-valid').addClass('field-validation-error');
$('[name="' + fullName + '"]').removeClass('valid').addClass('input-validation-error');
});
})
.fail(function (xhr) {
....
});
});
To avoid unnecessary ajax calls, you should also validate the inputs in your EditorTemplate
and if not valid, then cancel the ajax call. For an example of validating just the form controls in your template, refer MVC Force jQuery validation on group of elements
Upvotes: 1