Unobtrusive validation with dynamic content 2

HTML

<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<script src="//ajax.aspnetcdn.com/ajax/jquery.validate/1.8/jquery.validate.js" type="text/javascript"></script>
<script src="//ajax.aspnetcdn.com/ajax/mvc/3.0/jquery.validate.unobtrusive.js" type="text/javascript"></script>
<script id="rowTmpl" type="text/html">
    <tr>
        <td>
            <input name="rowInput${Id}" type="text" data-bind="value: inputVal" data-val="true" data-val-required="Required input!"/>
            <span class="field-validation-valid" data-valmsg-for="rowInput${Id}" data-valmsg-replace="true">No Error</span>
        </td>
        <td>
            <select name="rowSelect${Id}" data-bind="options: ViewModel.list, optionsValue: 'key', optionsText: 'value', optionsCaption: 'Choose...', value: selected" data-val="true" data-val-required="Required select!"></select>
            <span class="field-validation-valid" data-valmsg-for="rowSelect${Id}" data-valmsg-replace="true">No Error</span>
        </td>
    </tr>
</script>

<form id="myForm">
    <table>
        <thead>
            <tr>
                <td>Input</td>
                <td>Select</td>
            </tr>
        </thead>
        <tbody data-bind="template: { name: 'rowTmpl', foreach: rows }"></tbody>
    </table>
    <button data-bind="click: addRow">Add row</button>
    <p>
        <input name="myInput" type="text" data-bind="value: mainInput" data-val="true" data-val-required="This field is required"/>
        <span class="field-validation-valid" data-valmsg-for="myInput" data-valmsg-replace="true">No Error</span>
        <button data-bind="click: validate">Validate</button>
    </p>
</form>

JavaScript

(function ($) {
  $.validator.unobtrusive.parseDynamicContent = function (selector) {
    //use the normal unobstrusive.parse method
    $.validator.unobtrusive.parse(selector);

    //get the relevant form
    var form = $(selector).first().closest('form');

    //get the collections of unobstrusive validators, and jquery validators
    //and compare the two
    var unobtrusiveValidation = form.data('unobtrusiveValidation');
    var validator = form.validate();

    $.each(unobtrusiveValidation.options.rules, function (elname, elrules) {
      if (validator.settings.rules[elname] === undefined) {
        var args = {};
        $.extend(args, elrules);
        args.messages = unobtrusiveValidation.options.messages[elname];
        $('[name=' + elname + ']').rules("add", args);
      } else {
        $.each(elrules, function (rulename, data) {
          if (validator.settings.rules[elname][rulename] === undefined) {
            var args = {};
            args[rulename] = data;
            args.messages = unobtrusiveValidation.options.messages[elname][rulename];
            $('[name=' + elname + ']').rules("add", args);
          }
        });
      }
    });
  };
})($);

var viewModel = {
    list: ko.observableArray([
        { key: "1", value: "Eins" },
        { key: "2", value: "Zwei" },
        { key: "3", value: "Drei" }
    ]),
    rows: ko.observableArray(),
    mainInput: ko.observable()
};

var Row = function(viewModel, id) {
    this.ViewModel = viewModel;
    this.Id = id;
    this.inputVal = ko.observable();
    this.selected = ko.observable();
};

var counter = 0;

viewModel.addRow = function() {
    this.rows.push(new Row(viewModel, ++counter));
};

viewModel.validate = function() {
    $.validator.unobtrusive.parseDynamicContent($('#myForm'));
    $('#myForm').valid();
};

$(document).ready(function() {
    ko.applyBindings(viewModel);
});