1

我在 MVC 2 中使用 jquery.validate 和 MicrosoftMvcJQueryValidation。我的模型上有数据注释,然后将其转换为 jquery 验证器。我正在使用Soe Tun概述的对 MicrosoftMvcJQueryValidation 的修改,以允许我的错误消息出现在验证摘要中,而不是在控件旁边。

当页面加载时,一切都按预期工作。问题是我使用带有替换模式的 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 替换时如何让客户端验证重新初始化,以及如何让我的所有错误显示在验证摘要中?我希望如果我解决了一个问题,它会纠正另一个问题。

编辑:

这是关于我在做什么的更多信息

这是包装

<%@ 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 错误类),但摘要中仅显示最后一个错误。

4

2 回答 2

1

我要告诉你我几天前是怎么做的。请查看此问题以获取详细信息。

在我的例子中,我在 jquery 对话框中使用 ajax 调用来显示表单的内容。通话完成后,我只需将对话内容替换为从控制器发回的内容。

我已经按照我的问题中的描述修改了 Microsoft 脚本中的代码,然后在准备好文档时调用了 init 方法。这也应该适用于您的情况...

对于第二个错误(如何让我的所有错误显示在验证摘要中?)我只是按照您所指的同一源帖子中的描述修改了代码,并且没有遇到任何问题。

希望能帮助到你!

于 2010-11-22T21:20:39.377 回答
1

我最终重新设计了我的解决方案,以便不再通过回调将表单写入页面。虽然这不是我想要的方法,但它确实有效。我选择使用 jquery modals 来显示数据,而不是改变屏幕某一区域的内容。

理想情况下,我不必将所有表单呈现到页面并可以按需调用它们,但似乎 jquery 客户端验证不会连接,除非页面加载中存在表单。我不确定表单加载时是否需要存在表单元素,但这可能是我必须处理的一个限制。

另一种解决方法是将它们全部呈现到页面并通过 jquery 显示/隐藏每个表单。它与使用模态并没有太大区别,但至少验证会起作用。我仍然希望看到一个解决方案,其中可以通过回调将使用验证摘要和客户端 jquery 验证的表单通过回调写入页面,并且仍然可以正常连接并正常运行。

于 2010-11-24T14:26:05.240 回答