jquery.validate потерян при замене ajax и показывает только последнюю ошибку - PullRequest
1 голос
/ 18 ноября 2010

Я использую 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), но в сводке отображается только последняя ошибка.

Ответы [ 2 ]

1 голос
/ 24 ноября 2010

Я закончил работу над решением, чтобы больше не записывать формы на страницу с помощью обратных вызовов.Хотя это не был мой предполагаемый подход, он работает.Я решил использовать модальные jquery для отображения данных, а не изменять содержимое одной области экрана.

В идеале мне не пришлось бы отображать все формы на странице, и я мог бы вызывать их по требованию, но кажется, что проверка на стороне клиента jquery не будет подключена, если форма не присутствует при загрузке страницы.Я не уверен, что элементы формы должны присутствовать при загрузке формы, но это может быть ограничением, с которым мне просто нужно иметь дело.

Другим обходным решением было бы отобразить их все на странице и просто показать / скрытькаждая форма через jquery.Это не сильно отличается от использования модалов, но, по крайней мере, проверка будет работать.Я все еще хотел бы увидеть решение, в котором формы, использующие итоги проверки и проверку jquery на стороне клиента посредством аннотаций данных, могут быть записаны на страницу с помощью обратных вызовов и при этом по-прежнему подключены и работают правильно.

1 голос
/ 23 ноября 2010

Я собираюсь рассказать вам, как я сделал несколько дней назад. Пожалуйста, посмотрите этот вопрос для деталей.

В моем случае я показывал содержимое формы, используя вызов ajax внутри диалога jquery. Когда вызов завершен, я просто заменяю содержимое диалога содержимым, отправленным обратно из контроллера.

Я изменил код внутри скрипта Microsoft, как описано в моем вопросе, а затем вызвал метод init для документа, готового. Это должно работать и для вашего случая ...

Что касается второй ошибки (как я могу отобразить все свои ошибки в сводке проверки?), Я просто изменил код, как описано в том же исходном сообщении, на которое вы ссылаетесь, и не испытал никаких проблем.

Надеюсь, это поможет!

...