我正在尝试在knockout中创建一个自定义绑定,其功能类似于options
默认绑定处理程序,但使用单选按钮而不是下拉列表。
向数组添加项目将通知update
,但不会选择其他单选按钮。
注意:我将自定义绑定处理程序剥离到最低限度。
绑定处理程序
// LOOK FOR MY COMMENT //<------------------------------ LOOK FOR IT
ko.bindingHandlers.radioButtons = {
update: function (element, valueAccessor, allBindings) {
var unwrappedArray = ko.utils.unwrapObservable(valueAccessor());
var previousSelectedValue = ko.utils.unwrapObservable(allBindings().value);
// Helper function
var applyToObject = function (object, predicate, defaultValue) {
var predicateType = typeof predicate;
if (predicateType == "function") // Given a function; run it against the data value
return predicate(object);
else if (predicateType == "string") // Given a string; treat it as a property name on the data value
return object[predicate];
else // Given no optionsText arg; use the data value itself
return defaultValue;
};
// Map the array to a newly created label and input.
var isFirstPass = true;
var radioButtonForArrayItem = function (arrayEntry, index, oldOptions) {
if (oldOptions.length) {
var item = oldOptions[0];
if ($(item).is(":checked"))
previousSelectedValue = item.value;
isFirstPass = false;
}
var input = element.ownerDocument.createElement("input");
input.type = "radio";
var radioButtonGroupName = allBindings.get("groupName");
input.name = radioButtonGroupName;
if (isFirstPass) {
var selectedValue = ko.utils.unwrapObservable(allBindings.get("value"));
var itemValue = ko.utils.unwrapObservable(arrayEntry.value);
if (selectedValue === itemValue) {
input.checked = true;
}
} else if ($(oldOptions[0].firstElementChild).is(":checked")) {
input.checked = true;
}
var radioButtonValue = applyToObject(arrayEntry, allBindings.get("radioButtonValue"), arrayEntry);
ko.selectExtensions.writeValue(input, ko.utils.unwrapObservable(radioButtonValue));
var label = element.ownerDocument.createElement("label");
label.appendChild(input);
var radioButtonText = applyToObject(arrayEntry, allBindings.get("radioButtonText"), arrayEntry);
label.append(" " + radioButtonText);
return [label];
};
var setSelectionCallback = function (arrayEntry, newOptions) {
var inputElement = newOptions[0].firstElementChild;
if ($(inputElement).is(":checked")) {
var newValue = inputElement.value;
if (previousSelectedValue !== newValue) {
var value = allBindings.get("value");
value(newValue); //<------------------------- Get observable value and set here. Shouldn't this notify the update?
}
}
};
ko.utils.setDomNodeChildrenFromArrayMapping(element, unwrappedArray, radioButtonForArrayItem, {}, setSelectionCallback);
},
};
如何使用它......
// Html
<div data-bind="
radioButtons: letters,
groupName: 'letters',
radioButtonText: 'text',
radioButtonValue: 'value',
value: value,
"></div>
// Javascript
var vm = {
letters: ko.observableArray([
{
text: "A",
value: 1,
},
{
text: "B",
value: 2,
},
]),
value: ko.observable(2),
};
ko.applyBindings(vm);
因此,向vm.letters({ text: "C", value: 2 })
添加新项目会通知update
。但是,单击其他单选按钮不会通知update
。
我需要做什么才能点击单选按钮会通知update
?
答案 0 :(得分:1)
当选择更改时,您的绑定看起来不像是更新observable的方法。除非您只是将参数传递给另一个现有绑定,否则双向绑定不会自动与自定义绑定。像“hack”这样的事件处理程序正是通常用于自定义绑定的“init”功能的。
“init”回调
Knockout将为每个DOM元素调用一次init函数 你使用&gt;绑定。 init有两个主要用途:
- 设置DOM元素的任何初始状态
- 要注册任何事件处理程序,以便,例如,当用户单击或修改DOM元素时,您可以更改关联的可观察对象的状态
尝试在radioButtons绑定中添加类似以下init函数的内容:
ko.bindingHandlers.radioButtons = {
init: function(element, valueAccessor, allBindings){
var unwrappedArray = ko.utils.unwrapObservable(valueAccessor());
var value = allBindings().value;
ko.utils.registerEventHandler(element, "click", function(event){
var target = event.target;
if(target.nodeName.toLowerCase() == "input"){
value($(target).attr("value"));
}
});
},
update: function (element, valueAccessor, allBindings) {
...
}
处理click事件可能比检查目标是否为“input”元素更安全。这只是一个简单的例子。如果你的单选按钮或其他类型的输入元素作为radiobutton元素中的子元素,则必须修改它。如果有疑问,您可以随时从淘汰赛中复制“已检查”约束source code。
编辑:需要修复的一些内容以及修复方法。
可能有更好的方法来实现这一切......
ko.bindingHandlers.radioButtons = {
init: function(element, valueAccessor, allBindings){
//... error checks ... Remove all child elements to "element ...
var value = allBindings.get("value");
// 1. Update viewmodel
ko.utils.registerEventHandler(element, "click", function(event){
var target = event.target;
if(target.nodeName.toLowerCase() == "input"){
value(target.value);
}
});
// 2. Update view
value.subscribe(function (newValue) {
var inputs = element.getElementsByTagName("input")
$.each(inputs, function (i, input) {
input.checked = input.value === newValue;
});
};
},
update: function (element, valueAccessor, allBindings) {
...
var value = allBindings.get("value");
// 3. Edge case: remove radio button of the value selected.
var selectedRadioButton = unwrappedArray.find(function (item) {
return item.value === value();
});
if (selectedRadioButton == null) {
value(undefined);
}
}
}