I am using Knockout JS to create an editor. I am using the foreach property to loop around a list in my model.
<tbody data-bind='foreach: Properties'>
I am using JQuery unobtrusive validation whichneeds a name property to validate. I want to assign the same name to two fields, in order to be able to output a validation message. Is it possible to use the same uniqueName property on two fields?
<tr>
<td>
<input data-bind='value: type, uniqueName: true' data-val = "true", data-val-required = "The Type field is required" /></td>
</td>
</tr>
<tr>
<td class="field-validation-valid" data-valmsg-for="UNIQUENAME" data-valmsg-replace="true"></td>
</tr>
Ive copied the example below which shows grid editing and JQuery unobtrusive validation. But i cant work out how to link the validation message with the input field
http://knockoutjs.com/examples/gridEditor.html
Edit:
I am using ASP.NET MVC3 with Razor syntax for the loop input.
@Html.DropDownList("Type", new SelectList(types, "Value", "Text"), "Select", new { data_bind = "value: Type", data_val = "true", data_val_required = "The Type field is required" })
I cant figure out how to update the name property. When i add a property using knokcout they all have the same name "Type" and the validation doesn't work. They need to be indexed some how Type1 Type2 etc.
The uniqueName binding just increments an index and sets the name (with a fix for IE).
It looks like:
ko.bindingHandlers['uniqueName'] = {
'init': function (element, valueAccessor) {
if (valueAccessor()) {
element.name = "ko_unique_" + (++ko.bindingHandlers['uniqueName'].currentIndex);
// Workaround IE 6/7 issue
// - https://github.com/SteveSanderson/knockout/issues/197
// - http://www.matts411.com/post/setting_the_name_attribute_in_ie_dom/
if (ko.utils.isIe6 || ko.utils.isIe7)
element.mergeAttributes(document.createElement("<input name='" + element.name + "'/>"), false);
}
}
};
So, you can create a custom binding that uses the last index and sets the appropriate attribute
ko.bindingHandlers.valmsg = {
init: function(element) {
element.setAttribute("data-valmsg-for", "ko_unique_" + ko.bindingHandlers.uniqueName.currentIndex);
}
};
Now, you would just use it like:
<tr>
<td>
<input data-bind='value: type, uniqueName: true' data-val="true", data-val-required="The Type field is required" />
</td>
</tr>
<tr>
<td class="field-validation-valid" data-bind="valmsg: true" data-valmsg-replace="true"></td>
</tr>
A possibly simpler approach derived from RP-Niemeyer's excellent solution:
<span class="field-validation-valid" data-bind='attr: {"data-valmsg-for": "ko_unique_" + ko.bindingHandlers.uniqueName.currentIndex}' data-valmsg-replace="true" />
I wanted to post as a comment, but my reputation is too low...
Thank you very much RP Niemeyer for your post.
I don't know if this has something to do with requirejs and knockout modules, but I had to change
ko.bindingHandlers.uniqueName.currentIndex
to
ko.bindingHandlers.uniqueName.Zb
so now I have:
define(["knockout"], function(ko) {
ko.bindingHandlers.valmsg = {
init: function (element) {
element.setAttribute("data-valmsg-for", "ko_unique_" + ko.bindingHandlers.uniqueName.Zb);
}
};
});
You only need to add a ControlName attribute in the child elements and bind the input name to this and the data-valms-for too
<input type="number" class="form-control" data-bind="value: myValue, valueUpdate: 'afterkeydown', attr: {ControlName}" required />
<span class="field-validation-valid" data-bind='attr: {"data-valmsg-for":ControlName}' data-valmsg-replace="true"></span>