Search code examples
knockout.jsjquery-validateknockout-mapping-plugin

Adding validation to knockout


I'm trying to use jquery validation. I'm loading the list of items via ajax, load my knockout model. I would like to bind validation to those items. The problem is that the validation messages show up on the first input box ONLY. what am i doing wrong?

$(document).ready(function () {
        var viewModel = new ViewModel();
        viewModel.load();
        ko.applyBindings(viewModel);

        $('#list').validate();
});

Then in my viewModel:

    self.load = function (selected) {
        $.ajax({
            type: "POST",
            url: myUrl,
            data: ko.toJSON({ type: self.url(), customerNumbers: selected }),
            contentType: "application/json; charset=utf-8",
            dataType: "json",
            beforeSend: function () {
                $(".demo-tbl").block({ message: "Loading..." });
            },
            complete: function () {
                $(".demo-tbl").unblock();
                // COMMENTED OUT $("input.percent-text").valid(); // validator
            },
            success: function (data) {
                ko.mapping.fromJS(data, {}, self);
            },
            error: function (err) {
                alert(err.status + " - " + err.statusText);
            }
        });
    };

Rendered HTML:

                           <form id="list">
                            <table class="demo-tbl" data-bind='visible: List().length > 0'>
                                <thead>
                                </thead>


<tbody data-bind="foreach: List">
    <tr class="tbl-item" data-bind="css: HasDiscount">
        <td>
            <dl>
                <dt class="like" data-bind="text: CustomerName"></dt>
                <dd>Customer Number: <span data-bind="text: CustomerNumber"></span></dd>
            </dl>
        </td>
        <td>
            <span class="label label-sm label-warning" data-bind="css: HasDiscount, text: $root.DiscountListType"></span>
        </td>
        <td>
            <dl>
                <dt class="like">Activation Date:</dt>
                <dd data-bind="text: moment(ActivationDate()).format('LLL')"></dd>
            </dl>
        </td>
        <!-- ko if: $parent.DiscountListType() == "DiscountOverwrite" -->
        <td class="action">
            <input class="required number percent-text" name="percent" data-bind="numeric: Percent, value: Percent, event: { keypress: $root.update, blur: $root.update }" type="number" min="1" max="100" oninput="maxlength(this)" maxlength="3" />
        </td>
        <!-- /ko -->
        <td class="checkbox-cell">
            <button class="btn action" type="button" data-bind="click: $root.action, css: HasDiscount"></button>
        </td>
    </tr>
</tbody>

                            </table>
                        </form>

EDIT 1:

...
              complete: function () {
                    $(".demo-tbl").unblock();
                    $('#list').validate();
                },
...
$(document).ready(function () {
    if ($(".demo-tbl").length > 0) {
        var viewModel = new ViewModel();
        viewModel.load();
        ko.applyBindings(viewModel);
    }
});

Solution

  • "The problem is that the validation messages show up on the first input box ONLY. what am i doing wrong?"

    Based on the code you've shown, jQuery Validate should not be working at all.

    You can only attach the .validate() method to the form, not to an input element.

    $(document).ready(function() {
    
        $('#list').validate(); // initialize plugin on your form
    
    });
    

    Note: The name attribute is how the plugin keeps track of the fields, so repeating the names will result in something like you describe where some elements are skipped. I thought I saw a name repeated in the first version of your HTML.


    You cannot attach the .validate() method to a form element that does not yet exist. If your ajax() is loading the HTML for the form, then call .validate() right after the form's HTML is successfully loaded.

    The same place you commented out your call to .validate(), except this time it's attached to the form, not the input element.

    self.load = function (selected) {
        $.ajax({
            ....
            complete: function () {
                $(".demo-tbl").unblock();
                $('#list').validate(); // initialize validate plugin
            },
            ....
        });
    };
    

    EDIT 2:

    Stressing my original "note" above. You must NOT repeat a name attribute or the plugin will fail. There is no workaround. You must ensure every input contains a unique name.

    name="percent[1]"
    name="percent[2]"
    name="percent[3]"
    ....