Custom binding for unobtrusive validation

by bombo

HTML

<script src="https://github.com/jquery/jquery-tmpl/raw/master/jquery.tmpl.js"></script>
<script src="http://cloud.github.com/downloads/SteveSanderson/knockout/knockout-1.2.0.debug.js"></script>
<script src="https://github.com/brandonaaron/livequery/raw/master/jquery.livequery.js"></script>
<script src="https://github.com/jzaefferer/jquery-validation/raw/master/jquery.validate.js"></script>
<script src="//ajax.aspnetcdn.com/ajax/mvc/3.0/jquery.validate.unobtrusive.js"></script>
<script id="rowTmpl" type="text/html">
    <tr>
        <td>
            <input name="rowInput${Id}" type="text" data-bind="value: input, dataVal: inputVal" />
            <span class="field-validation-valid" data-valmsg-for="rowInput${Id}" data-valmsg-replace="true" data-bind="text: error">
                No error
            </span>
        </td>
        <td>
            <select name="rowSelect${Id}" data-bind="options: ViewModel.list, optionsValue: 'key', optionsText: 'value', optionsCaption: 'Choose...', value: selected, dataVal: selectVal"></select>
            <span class="field-validation-valid" data-valmsg-for="rowSelect${Id}" data-valmsg-replace="true">No Error</span>
        </td>
    </tr>
</script>

<form id="myForm">
    <div>
        Does not work in IE at the moment. Seems to be linked to the https protocol of some resources.
    </div>
    <table>
        <thead>
            <tr>
                <td>Input</td>
                <td>Select</td>
            </tr>
        </thead>
        <tbody data-bind="template: { name: 'rowTmpl', foreach: rows, afterAdd: reparse }"></tbody>
    </table>
    <button data-bind="click: addRow">Add row</button>
    <button data-bind="click: validate">Validate</button>
</form>

CSS

.field-validation-valid, .field-validation-error {
    display: none;   
}

.input-validation-error {
    border: 1px solid #f00;
    background: #fee;
}

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);
          }
        });
      }
    });
  };
})($);

ko.bindingHandlers.dataVal = {
    update: function (element, valueAccessor) {
        var values = ko.utils.unwrapObservable(valueAccessor()),
            prefix = 'data-val';
        if (values === {} || typeof values === "string") {
            throw "Please specify name-value pairs for unobtrusive validation binding!";
        }
        
        $(element).attr(prefix, true);
        for (var prop in values) {
            $(element).attr(prefix + '-' + prop, values[prop]);
        }
    }
};

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, inputVal, selectVal) {
   ...