Я использую jquery.validate в MVC 2 с MicrosoftMvcJQueryValidation. У меня есть аннотации данных по моей модели, которые затем переводятся в валидаторы jquery. Я использую модификацию MicrosoftMvcJQueryValidation, обозначенную Soe Tun , чтобы мои сообщения об ошибках отображались в сводке проверки вместо элементов управления.
Когда страница загружается, все работает как положено. Проблема в том, что я использую формы ajax с режимом замены, чтобы переписать форму. Когда я делаю это, я теряю всю свою проверку на стороне клиента.
Проверка по-прежнему происходит на стороне сервера, и полям с ошибками корректно присваиваются классы css для изменения их стиля. Тем не менее, только последнее сообщение об ошибке отображается в моем резюме проверки.
Контроллер не является чем-то особенным. Если модель действительна, выполните работу, в противном случае верните ту же модель обратно в представление.
Вот образец моей формы Ajax
<% using (Ajax.BeginForm("AddCreditCard", "Dashboard",
new { },
new AjaxOptions() {
HttpMethod = "Post",
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "quickpay-wrapper",
OnSuccess = "newPaymentSetup",
LoadingElementId = "loading-pane"
}, new { id="new-credit-card-form" })) { %>
Вот модифицированный javascript.
jQuery.validator.addMethod("regex", function(value, element, params) {
if (this.optional(element)) {
return true;
}
var match = new RegExp(params).exec(value);
return (match && (match.index == 0) && (match[0].length == value.length));
});
// glue
function __MVC_ApplyValidator_Range(object, min, max) {
object["range"] = [min, max];
}
function __MVC_ApplyValidator_RegularExpression(object, pattern) {
object["regex"] = pattern;
}
function __MVC_ApplyValidator_Required(object) {
object["required"] = true;
}
function __MVC_ApplyValidator_StringLength(object, maxLength) {
object["maxlength"] = maxLength;
}
function __MVC_ApplyValidator_Unknown(object, validationType, validationParameters) {
object[validationType] = validationParameters;
}
function __MVC_CreateFieldToValidationMessageMapping(validationFields) {
var mapping = {};
for (var i = 0; i < validationFields.length; i++) {
var thisField = validationFields[i];
mapping[thisField.FieldName] = "#" + thisField.ValidationMessageId;
}
return mapping;
}
function __MVC_CreateErrorMessagesObject(validationFields) {
var messagesObj = {};
for (var i = 0; i < validationFields.length; i++) {
var thisField = validationFields[i];
var thisFieldMessages = {};
messagesObj[thisField.FieldName] = thisFieldMessages;
var validationRules = thisField.ValidationRules;
for (var j = 0; j < validationRules.length; j++) {
var thisRule = validationRules[j];
if (thisRule.ErrorMessage) {
var jQueryValidationType = thisRule.ValidationType;
switch (thisRule.ValidationType) {
case "regularExpression":
jQueryValidationType = "regex";
break;
case "stringLength":
jQueryValidationType = "maxlength";
break;
}
thisFieldMessages[jQueryValidationType] = thisRule.ErrorMessage;
}
}
}
return messagesObj;
}
function __MVC_CreateRulesForField(validationField) {
var validationRules = validationField.ValidationRules;
// hook each rule into jquery
var rulesObj = {};
for (var i = 0; i < validationRules.length; i++) {
var thisRule = validationRules[i];
switch (thisRule.ValidationType) {
case "range":
__MVC_ApplyValidator_Range(rulesObj,
thisRule.ValidationParameters["minimum"], thisRule.ValidationParameters["maximum"]);
break;
case "regularExpression":
__MVC_ApplyValidator_RegularExpression(rulesObj,
thisRule.ValidationParameters["pattern"]);
break;
case "required":
var fieldName = validationField.FieldName.replace(".", "_");
if ($("#" + fieldName).get(0).type !== 'checkbox') {
// only apply required if the input control is NOT a checkbox.
__MVC_ApplyValidator_Required(rulesObj);
}
break;
case "stringLength":
__MVC_ApplyValidator_StringLength(rulesObj,
thisRule.ValidationParameters["maximumLength"]);
break;
default:
__MVC_ApplyValidator_Unknown(rulesObj,
thisRule.ValidationType, thisRule.ValidationParameters);
break;
}
}
return rulesObj;
}
function __MVC_CreateValidationOptions(validationFields) {
var rulesObj = {};
for (var i = 0; i < validationFields.length; i++) {
var validationField = validationFields[i];
var fieldName = validationField.FieldName;
rulesObj[fieldName] = __MVC_CreateRulesForField(validationField);
}
return rulesObj;
}
function __MVC_EnableClientValidation(validationContext) {
// this represents the form containing elements to be validated
var theForm = $("#" + validationContext.FormId);
var fields = validationContext.Fields;
var rulesObj = __MVC_CreateValidationOptions(fields);
var fieldToMessageMappings = __MVC_CreateFieldToValidationMessageMapping(fields);
var errorMessagesObj = __MVC_CreateErrorMessagesObject(fields);
var options = {
errorClass: "input-validation-error",
errorElement: "span",
errorPlacement: function(error, element) {
var messageSpan = fieldToMessageMappings[element.attr("name")];
$(messageSpan).empty();
$(messageSpan).removeClass("field-validation-valid");
$(messageSpan).addClass("field-validation-error");
error.removeClass("input-validation-error");
error.attr("_for_validation_message", messageSpan);
error.appendTo(messageSpan);
},
messages: errorMessagesObj,
rules: rulesObj,
success: function(label) {
var messageSpan = $(label.attr("_for_validation_message"));
$(messageSpan).empty();
$(messageSpan).addClass("field-validation-valid");
$(messageSpan).removeClass("field-validation-error");
}
};
var validationSummaryId = validationContext.ValidationSummaryId;
if (validationSummaryId) {
// insert an empty <ul> into the validation summary <div> tag (as necessary)
$("<ul />").appendTo($("#" + validationSummaryId + ":not(:has(ul:first))"));
options = {
errorContainer: "#" + validationSummaryId,
errorLabelContainer: "#" + validationSummaryId + " ul:first",
wrapper: "li",
showErrors: function(errorMap, errorList) {
var errContainer = $(this.settings.errorContainer);
var errLabelContainer = $("ul:first", errContainer);
// Add error CSS class to user-input controls with errors
for (var i = 0; this.errorList[i]; i++) {
var element = this.errorList[i].element;
var messageSpan = $(fieldToMessageMappings[element.name]);
var msgSpanHtml = messageSpan.html();
if (!msgSpanHtml || msgSpanHtml.length == 0) {
// Don't override the existing Validation Message.
// Only if it is empty, set it to an asterisk.
//messageSpan.html("*");
}
messageSpan.removeClass("field-validation-valid").addClass("field-validation-error");
$("#" + element.id).addClass("input-validation-error");
}
for (var i = 0; this.successList[i]; i++) {
// Remove error CSS class from user-input controls with zero validation errors
var element = this.successList[i];
var messageSpan = fieldToMessageMappings[element.name];
$(messageSpan).addClass("field-validation-valid").removeClass("field-validation-error");
$("#" + element.id).removeClass("input-validation-error");
}
if (this.numberOfInvalids() > 0) {
errContainer.removeClass("validation-summary-valid").addClass("validation-summary-errors");
}
this.defaultShowErrors();
// when server-side errors still exist in the Validation Summary, don't hide it
var totalErrorCount = errLabelContainer.children("li:not(:has(label))").length + this.numberOfInvalids();
if (totalErrorCount > 0) {
$(this.settings.errorContainer).css("display", "block").addClass("validation-summary-errors").removeClass("validation-summary-valid");
$(this.settings.errorLabelContainer).css("display", "block");
}
},
messages: errorMessagesObj,
rules: rulesObj
};
}
// register callbacks with our AJAX system
var formElement = document.getElementById(validationContext.FormId);
var registeredValidatorCallbacks = formElement.validationCallbacks;
if (!registeredValidatorCallbacks) {
registeredValidatorCallbacks = [];
formElement.validationCallbacks = registeredValidatorCallbacks;
}
registeredValidatorCallbacks.push(function() {
theForm.validate();
return theForm.valid();
});
theForm.validate(options);
}
// need to wait for the document to signal that it is ready
$(document).ready(function() {
var allFormOptions = window.mvcClientValidationMetadata;
if (allFormOptions) {
while (allFormOptions.length > 0) {
var thisFormOptions = allFormOptions.pop();
__MVC_EnableClientValidation(thisFormOptions);
}
}
});
Я пытался переместить вызовы внизу документа, готовые в мой метод OnSuccess, но этого не произошло.
Итак, как мне получить подтверждение на стороне клиента для повторной инициализации, когда я делаю замену ajax, и как мне отобразить все мои ошибки в сводке проверки? Я надеюсь, что если я исправлю одну проблему, она исправит другую.
EDIT:
Вот немного больше информации о том, что я делаю
Вот обертка
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<QuickPayModel>" %>
<div id="quickpay-wrapper">
<% if (Model.NewPaymentMethod) { %>
<% Html.RenderAction<DashboardController>(x => x.QuickPayNewMethod()); %>
<% } else { %>
<% Html.RenderPartial("QuickPayMakePayment", Model); %>
<% } %>
</div>
Вот панель оплаты.
<%= Html.ClientValidationSummary(new { id = "valSumContainer" })%>
<% Html.EnableClientValidation(); %>
<% using (Ajax.BeginForm("QuickPay", "Dashboard",
new { },
new AjaxOptions() {
HttpMethod = "Post",
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "quickpay-wrapper",
OnSuccess = "updatePaymentHistory",
LoadingElementId = "loading-pane"
}, new { }))
{ %>
<div class="horizontalline"><%= Html.Spacer() %></div>
<% ViewContext.FormContext.ValidationSummaryId = "valSumContainer"; %>
<p>
<%: Html.LabelFor(x => x.PaymentMethods)%>
<% if (Model.HasOnePaymentMethod) { %>
<%: Html.DisplayFor(x => x.SelectedPaymentMethodName) %>
<%: Html.HiddenFor(x => x.SelectedPaymentMethodId) %>
<% } else { %>
<%: Html.DropDownListFor(x => x.SelectedPaymentMethodId, Model.PaymentMethodsSelectList, "Select a Payment Method", new { })%>
<%: Html.HiddenFor(x => x.SelectedPaymentMethodName)%>
<script type="text/javascript">
$(function () {
$("#PaymentMethods").change(function () {
$("#SelectedPaymentMethodId").val($(this).val());
$("#SelectedPaymentMethodName").val($('option:selected', this).text());
});
});
</script>
<% } %>
<%: Html.Spacer(12, 1) %><%: Ajax.ActionLink("New Payment Method", "QuickPayNewMethod",
new AjaxOptions() { InsertionMode = InsertionMode.Replace,
UpdateTargetId = "quickpay-wrapper",
OnSuccess = "newPaymentSetup",
LoadingElementId = "loading-pane"
})%>
<%: Html.ValidationMessageFor(x => x.SelectedPaymentMethodId)%>
</p>
<p>
<%: Html.LabelFor(x => x.Amount)%>
<%: Html.TextBoxFor(x => x.Amount, new { disabled = Model.UseInvoicing ? "disabled" : String.Empty,
title = Model.UseInvoicing ? "the total payment amount of all selected invoices" : String.Empty,
@class = "small" })%>
<%: Html.ValidationMessageFor(x => x.Amount)%>
</p>
<p>
<%: Html.LabelFor(x => x.PayDate)%>
<%: Html.TextBox("PayDate", Model.PayDate.ToShortDateString(), new { @class = "medium" })%>
<%: Html.ValidationMessageFor(x => x.PayDate)%>
</p>
<script type="text/javascript">
$(function () {
quickPaySetup();
});
</script>
<div class="horizontalline"><%= Html.Spacer() %></div>
<%= FTNI.Controls.Submit("Submit Payment") %>
<%: Html.AntiForgeryToken() %>
<%: Html.ValidationMessage("Payment-Result")%>
<% } %>
А теперь моя новая панель оплаты
<script type="text/javascript">
$(function () {
newPaymentSetup();
});
</script>
<h4>New Payment Method</h4>
<% if(Model.HasPaymentMethods) { %>
<span style="float:right;">
<%: Ajax.ActionLink("Cancel", "QuickPay",
new AjaxOptions() {
HttpMethod = "Get",
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "quickpay-wrapper",
OnSuccess = "quickPaySetup",
LoadingElementId = "loading-pane"
})%>
</span>
<% } %>
<div>Enter the information below to create a new payment method.</div><br />
<%= Html.ClientValidationSummary(new { id = "valSumContainer" })%>
<% Html.EnableClientValidation(); %>
<div id="new-payment-method-tabs">
<ul>
<li><a href="#new-credit-card">Credit Card</a></li>
<li><a href="#new-ach">E-Check</a></li>
</ul>
<div id="new-credit-card">
<% Html.RenderPartial("NewCreditCard", Model.CreditCardModel); %>
</div>
<div id="new-ach">
<% Html.RenderPartial("NewACH", Model.ACHModel); %>
</div>
</div>
Каждая форма начинается с чего-то вроде этого
<% using (Ajax.BeginForm("AddCreditCard", "Dashboard",
new { },
new AjaxOptions() {
HttpMethod = "Post",
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "quickpay-wrapper",
OnSuccess = "newPaymentSetup",
LoadingElementId = "loading-pane"
}, new { id="new-credit-card-form" })) { %>
<% ViewContext.FormContext.ValidationSummaryId = "valSumContainer"; %>
Начальная загрузка работает. Любые замены ajax приводят к потере контекста формы и их повторной инициализации независимо от того, что я делаю. Форма отправляет обратно, проверка происходит на стороне сервера. Все недопустимые поля изменены (добавлены классы ошибок CSS), но в сводке отображается только последняя ошибка.