Lorne Redmond
Lorne Redmond

Reputation: 263

Jquery Validation on Dom not displaying message

I stumbled upon some code that has jQuery validation which is triggered after an ajax call that adds items to the DOM. The validation is working but the message is missing. just the field is highlighted. I have been playing with this for a while to get it to work, but so far no luck. Any ideas, thoughts appreciated.

 $('#add-other-income-link').click(function (event) {
    event.preventDefault();
    var otherIncomesCount = $('#numberOfNewOtherIncomes').val();
    $('div[hideCorner = yep]').show();

    var url = $(this).attr('href');
    if (url) {
        $.ajax({
            url: url,
            type: 'GET',
            dataType: 'html',
            success: function (data) {
                $('#additional-other-income').append(data);

                var count = otherIncomesCount;

                var id = 0;
                $('#additional-other-income').find('table.other-income-table').each(function (i, item) {
                    id = $(item).find('input.other-income-id');
                    var additionalIncomeTypeIdLabel = $(item).find('label.other-income-type-id-label');
                    var amountLabel = $(item).find('label.other-income-amount-label');
                    var additionalIncomeTypeIdMenu = $(item).find('select.other-income-type-id');
                    var amountTextBox = $(item).find('input.other-income-amount');

                    var idIndexer = 'OtherIncome_' + count + '__';
                    var nameIndexer = 'OtherIncome[' + count + '].';
                    var indexer = '[' + i + ']';
                    id.attr('id', idIndexer + 'Id').attr('name', nameIndexer + 'Id');
                    additionalIncomeTypeIdLabel.attr('for', idIndexer + 'AdditionalIncomeTypeId');
                    amountLabel.attr('for', idIndexer + 'Amount');
                    additionalIncomeTypeIdMenu.attr('id', idIndexer + 'AdditionalIncomeTypeId').attr('name', nameIndexer + 'AdditionalIncomeTypeId');
                    amountTextBox.attr('id', idIndexer + 'Amount').attr('name', nameIndexer + 'Amount').attr('data-val', 'true');

                    ++count;

                    addOtherIncomeValidation(item);
                });

The validation succeeds for both required on additionalIncomeTypeIDMenu, and required and positive on amountTextBox, but the messages for both fail to show up:

function addOtherIncomeValidation(container) {
if (container) {
    var additionalIncomeTypeIdMenu = $(container).find('select.other-income-type-id');
    var amountTextBox = $(container).find('input.other-income-amount');


    $(additionalIncomeTypeIdMenu).rules('add', {
        required: true,
        messages: {
            required: 'Please select an income type'
        }
    });

    $(amountTextBox).rules('add', {
        required: true,
        positive: true,
        messages: { positive: 'must be positive number'

        }
    });
}
}

BTW The ajax call returns a partial EditorTemplate here, which you can see uses ValidationMessageFor.

<div class="other-income" style="margin-bottom: 10px;">
<table class="other-income-table">
    <tr>
        <td>
            @Html.HiddenFor(x => x.Id, new { @class = "other-income-id" })
            @Html.LabelFor(x => x.AdditionalIncomeTypeId, "Type:", new { @class = "other-income-type-id-label" })
            <br />@Html.ValidationMessageFor(x => x.AdditionalIncomeTypeId)
        </td>
        <td>
            @Html.LabelFor(x => x.Amount, "Amount:", new { @class = "other-income-amount-label" })
            <br />@Html.ValidationMessageFor(x => x.Amount)
        </td>
        <td>&nbsp;&nbsp;</td>
    </tr>
    <tr>
        <td>@Html.DropDownListFor(x => x.AdditionalIncomeTypeId, new SelectList(Model.AdditionalIncomeTypes, "Value", "Text", Model.AdditionalIncomeTypeId), "--- Select One ---", new { @class = "other-income-type-id" })</td>
        <td>
            @Html.EditorFor(x => x.Amount, "Money", new { AdditionalClasses = "other-income-amount" })
        </td>
        <td>
            @{
                int? otherIncomeId = null;
                var removeOtherIncomeLinkClasses = "remove-other-income-link";
                if (Model.Id == 0)
                {
                    removeOtherIncomeLinkClasses += " new-other-income";
                }
                else
                {
                    otherIncomeId = Model.Id;
                }
            }
            @Html.ActionLink("Remove", "RemoveOtherIncome", "Applicant", new { applicationId = Model.ApplicationId, otherIncomeId = otherIncomeId }, new { @class = removeOtherIncomeLinkClasses })<img class="hide spinner" src="@Url.Content("~/Content/Images/ajax-loader_16x16.gif")" alt="Deleting..." style="margin-left: 5px;" />
        </td>
    </tr>
</table>

HTML:

       <div id="OtherIncome" class="applicant-section">

<h2 class="header2">Other Income</h2>
<div class="cornerForm">

<div class="other-income" style="margin-bottom: 10px;">
    <table class="other-income-table">
        <tr>
            <td>
                <input class="other-income-id" data-val="true" data-val-number="The field Id must be a number." id="OtherIncome_0__Id" name="OtherIncome[0].Id" type="hidden" value="385" />
                <label class="other-income-type-id-label" for="OtherIncome_0__AdditionalIncomeTypeId">Type:</label>
                <br /><span class="field-validation-valid" data-valmsg-for="OtherIncome[0].AdditionalIncomeTypeId" data-valmsg-replace="true"></span>
            </td>
            <td>
                <label class="other-income-amount-label" for="OtherIncome_0__Amount">Amount:</label>
                <br /><span class="field-validation-valid" data-valmsg-for="OtherIncome[0].Amount" data-valmsg-replace="true"></span>
            </td>
            <td>&nbsp;&nbsp;</td>
        </tr>
        <tr>
            <td><select class="other-income-type-id" data-val="true" data-val-number="The field AdditionalIncomeTypeId must be a number." id="OtherIncome_0__AdditionalIncomeTypeId" name="OtherIncome[0].AdditionalIncomeTypeId"><option value="">--- Select One ---</option>
<option value="1">Alimony</option>
<option value="2">Child Support</option>
<option value="3">Disability</option>
<option value="4">Investments</option>
<option selected="selected" value="5">Rental Income</option>
<option value="6">Retirement</option>
<option value="7">Secondary Employment</option>
<option value="8">Separate Maintenance</option>
</select></td>
            <td>


<input class="money other-income-amount" data-val="true" data-val-number="The field Amount must be a number." id="OtherIncome_0__Amount" name="OtherIncome[0].Amount" style="" type="text" value="0.00" />
            </td>
            <td>
                <a class="remove-other-income-link" href="/Applicant/RemoveOtherIncome/XNxxxxx753/385">Remove</a><img class="hide spinner" src="/Content/Images/ajax-loader_16x16.gif" alt="Deleting..." style="margin-left: 5px;" />
            </td>
        </tr>
    </table>


</div>
<div class="other-income" style="margin-bottom: 10px;">
    <table class="other-income-table">
        <tr>
            <td>
                <input class="other-income-id" data-val="true" data-val-number="The field Id must be a number." id="OtherIncome_1__Id" name="OtherIncome[1].Id" type="hidden" value="412" />
                <label class="other-income-type-id-label" for="OtherIncome_1__AdditionalIncomeTypeId">Type:</label>
                <br /><span class="field-validation-valid" data-valmsg-for="OtherIncome[1].AdditionalIncomeTypeId" data-valmsg-replace="true"></span>
            </td>
            <td>
                <label class="other-income-amount-label" for="OtherIncome_1__Amount">Amount:</label>
                <br /><span class="field-validation-valid" data-valmsg-for="OtherIncome[1].Amount" data-valmsg-replace="true"></span>
            </td>
            <td>&nbsp;&nbsp;</td>
        </tr>
        <tr>
            <td><select class="other-income-type-id" data-val="true" data-val-number="The field AdditionalIncomeTypeId must be a number." id="OtherIncome_1__AdditionalIncomeTypeId" name="OtherIncome[1].AdditionalIncomeTypeId"><option value="">--- Select One ---</option>
<option selected="selected" value="1">Alimony</option>
<option value="2">Child Support</option>
<option value="3">Disability</option>
<option value="4">Investments</option>
<option value="5">Rental Income</option>
<option value="6">Retirement</option>
<option value="7">Secondary Employment</option>
<option value="8">Separate Maintenance</option>
</select></td>
            <td>


<input class="money other-income-amount" data-val="true" data-val-number="The field Amount must be a number." id="OtherIncome_1__Amount" name="OtherIncome[1].Amount" style="" type="text" value="22.00" />
            </td>
            <td>
                <a class="remove-other-income-link" href="/Applicant/RemoveOtherIncome/XN42093753/412">Remove</a><img class="hide spinner" src="/Content/Images/ajax-loader_16x16.gif" alt="Deleting..." style="margin-left: 5px;" />
            </td>
        </tr>
    </table>


</div>

    <div id="additional-other-income"></div>

    <input id="numberOfNewOtherIncomes" name="numberOfNewOtherIncomes" type="hidden" value="0" />
    <input data-val="true" data-val-number="The field OriginalOtherIncomeTotal must be a number." id="OriginalOtherIncomeTotal" name="OriginalOtherIncomeTotal" type="hidden" value="22.0000" />
    <a class="editable-link" href="/Applicant/AddOtherIncome?appId=XNxxxxx753" id="add-other-income-link">Add Other Income</a>
</div>        </div>

Validation code:

$.validator.addMethod('positive', function(value, element) {
var check = true;
if (value < 0) {
    check = false;
}
return this.optional(element) || check;
}, "Value must be a positive number."
);

Upvotes: 0

Views: 235

Answers (1)

Lorne Redmond
Lorne Redmond

Reputation: 263

I didn't think I would find my own answer but I did. first I have to apologize for my response to @Sparky with his request for rendered HTML. I did a "View page source" but that didn't include all the stuff which was added from the ajax call after the server delivered it's stuff. I suspect if I did include the extra DOM stuff at first, you would have pinpointed the issue sooner. My bad. Now on to the answer.

It appears that when injecting an EditorTemplate into the DOM in the way shown above, it doesn't properly process the page like you would expect in MVC. The code for @Html.ValidationMessageFor simply does not get parsed AT ALL. I am a little new to validation as you can see so I don't know why it behaves this way. To solve my problem here is what I did:

I updated my Editor Template slightly to look like this:

<div class="other-income" style="margin-bottom: 10px;">
<table class="other-income-table">
    <tr>
        <td>
            @Html.HiddenFor(x => x.Id, new { @class = "other-income-id" })
            @Html.LabelFor(x => x.AdditionalIncomeTypeId, "Type:", new { @class = "other-income-type-id-label" })
          <br />
            @Html.ValidationMessageFor(x=>x.AdditionalIncomeTypeId)
            <span id="AdditionalIncomeTypeIdValidation"></span>
        </td>
        <td>
            @Html.LabelFor(x => x.Amount, "Amount:", new { @class = "other-income-amount-label" })
            <br />
            @Html.ValidationMessageFor(x=>x.Amount)
            <span id="amountValidation"></span>
         </td>
        <td>&nbsp;&nbsp;</td>
    </tr>
    <tr>
        <td>@Html.DropDownListFor(x => x.AdditionalIncomeTypeId, new SelectList(Model.AdditionalIncomeTypes, "Value", "Text", Model.AdditionalIncomeTypeId), "--- Select One ---", new { @class = "other-income-type-id" })</td>
        <td>
            @Html.EditorFor(x => x.Amount, "Money", new { AdditionalClasses = "other-income-amount" })
        </td>
        <td>
            @{
                int? otherIncomeId = null;
                var removeOtherIncomeLinkClasses = "remove-other-income-link";
                if (Model.Id == 0)
                {
                    removeOtherIncomeLinkClasses += " new-other-income";
                }
                else
                {
                    otherIncomeId = Model.Id;
                }
            }
            @Html.ActionLink("Remove", "RemoveOtherIncome", "Applicant", new { applicationId = Model.ApplicationId, otherIncomeId = otherIncomeId }, new { @class = removeOtherIncomeLinkClasses })<img class="hide spinner" src="@Url.Content("~/Content/Images/ajax-loader_16x16.gif")" alt="Deleting..." style="margin-left: 5px;" />
        </td>
    </tr>
</table>

Notice the new span tags, which is are added next to the @Html.ValidationMessageFor.

then in my success function from the ajax call in javascript I made these changes:

  $('#add-other-income-link').click(function (event) {
        event.preventDefault();
        var count = $('#numberOfNewOtherIncomes').val();
        $('div[hideCorner = yep]').show();

        var url = $(this).attr('href');
        if (url) {
            $.ajax({
                url: url,
                type: 'GET',
                dataType: 'html',
                success: function (data) {
                    $('#additional-other-income').append(data);

                    var id = 0;
                    $('#additional-other-income').find('table.other-income-table').each(function (i, item) {
                        id = $(item).find('input.other-income-id');
                        var additionalIncomeTypeIdLabel = $(item).find('label.other-income-type-id-label');

                        var amountLabel = $(item).find('label.other-income-amount-label');
                        var additionalIncomeTypeIdMenu = $(item).find('select.other-income-type-id');
                        var amountTextBox = $(item).find('input.other-income-amount');
                        var amountValidation = $(item).find('#amountValidation');
                        var typeIdValidation = $(item).find('#AdditionalIncomeTypeIdValidation');

                        var idIndexer = 'OtherIncome_' + count + '__';
                        var nameIndexer = 'OtherIncome[' + count + '].';
                        var indexer = '[' + i + ']';
                        amountValidation.attr('class', 'field-validation-valid')
                                        .attr('data-valmsg-for', nameIndexer + 'Amount')
                                        .attr('data-valmsg-replace','true');
                        typeIdValidation.attr('class', 'field-validation-valid')
                                        .attr('data-valmsg-for', nameIndexer + 'AdditionalIncomeTypeId')
                                        .attr('data-valmsg-replace','true');
                        id.attr('id', idIndexer + 'Id').attr('name', nameIndexer + 'Id');
                        additionalIncomeTypeIdLabel.attr('for', idIndexer + 'AdditionalIncomeTypeId');
                        amountLabel.attr('for', idIndexer + 'Amount');
                        additionalIncomeTypeIdMenu.attr('id', idIndexer + 'AdditionalIncomeTypeId').attr('name', nameIndexer + 'AdditionalIncomeTypeId');
                        amountTextBox.attr('id', idIndexer + 'Amount').attr('name', nameIndexer + 'Amount').attr('data-val', 'true');


                        ++count;

                        addOtherIncomeValidation(item);

notice I am manually adding in the missing validation spans that were not rendering. Now the validation message shows up at validation! Yay. I am not sure however that this is the best fix. It looks and smells like a hack, but I got it to work. I am sure it can be done a better way. Thanks again for interaction and feedback.

Upvotes: 1

Related Questions