Knockout.js可写计算布尔值observable绑定到复选框 - 不可靠的属性设置

时间:2012-12-10 00:06:35

标签: checkbox knockout.js computed-observable

我有一个复选框,以前直接绑定到我的视图模型上的一个observable属性。我在所有视图模型上使用通用脏标志,该模型监视视图模型上的所有可观察属性以进行更改。

现在,对于一个属性,如果用户在取消设置基础observable之前尝试取消选中复选框,我希望提供确认提醒。我试图将它实现为一个可写的计算observable,它包含了observable,如下所示:

function VM() {
    var self = this;

    self.SelectedInternal = ko.observable(false);

    self.selected= ko.computed({
        read: function () {
            return self.SelectedInternal();
        },
        write: function (value) {
            alert('Clicked'); // To demonstrate problem
            if (value === false) {
                if (confirm('Are you sure you wish to deselect this option?')) {
                    self.SelectedInternal(value);
                }          
            }
        }
    });
}

var vm = new VM();

ko.applyBindings(vm);

我所看到的(在Firefox和IE中)是当我将SelectedInternal可观察值默认为如上所述时,“选中”写入功能仅在我每次选中复选框时触发,而不是在我取消选中时触发它。如果我将SelectedInternal observable值默认为true,那么第一次取消选中它时,写入setter会执行,但不会在后续的unchecks中执行。

这是一个小提琴演示:

http://jsfiddle.net/xHqsZ/18/

这里发生了什么?有没有更好的方法来实现这一目标?

更新:这种方法可能无论如何都不会起作用,因为如果用户在确认框中选择取消,我无法获取原始点击的钩子以返回false,并将observable重置为true似乎生效了。但是我仍然想知道为什么计算的setter本身没有按预期运行。

4 个答案:

答案 0 :(得分:2)

到目前为止我已经实现了这一点(为简单起见,属性重命名):

<input type="checkbox" data-bind="checked: vm.optionComputed, click: vm.toggleOption" />

在视图模型中:

self.optionComputed = ko.computed({
    read: function () {
        return self.Option();
    },
    write: function (value) {
    }
});

self.toggleOption = function (vm, event) {
    var checkBox = $(event.target);
    var isChecked = checkBox.is(':checked');

    if (!(!isChecked && !confirm('Are you sure?'))) {
        self.Option(isChecked);
    }    
};

当您选择“确定”取消选中时,会出现轻微的故障,在最终取消选中之前,复选框(已经被点击清空)会再次短暂显示。但是在防止可观察的改变直到确认之前的行为是正确的。

答案 1 :(得分:1)

查看http://knockoutjs.com/documentation/computedObservables.html处的文档。

当读取函数的值更改时,将调用write函数。请参阅文档中的示例#1。

我在write函数中做的一件事是设置其他可观察值。例如,一个复选框可清除组中的所有其他组。我这样做只是更新write函数中的那些observable。


修改

我已经把一个小提琴放在一起,展示了如何做到最后一段所描述的内容 - http://jsfiddle.net/photo_tom/xHqsZ/22/。使其工作的计算函数是

self.clearAll = ko.computed({
    read: function() {
        return !(self.option1() || self.option2() || self.option3());
    },
    write: function(value) {
        alert('Clicked');
        self.option1(false);
        self.option2(false);
        self.option3(false);

    }
});

编辑#2 回答有关想要手动确认错误到真实检查状态问题的评论。

处理此问题最干净的方法是使用自定义活页夹。关键部分是注册自定义更改的事件处理程序。在该函数内部,您可以询问用户是否想要将复选框设置为true。

ko.bindingHandlers.checkConfirm = {
    init: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {

        //handle the field changing
        ko.utils.registerEventHandler(element, "change", function() {
            // get the current observable value.
            var value = valueAccessor();
            var valueUnwrapped = ko.utils.unwrapObservable(value);

            if (!valueUnwrapped && element.checked) {
                var r = confirm("Confirm Setting value to true");
                if (r) {
                    value(true);
                    return;
                } else {
                    // not okayed, so clear cb.
                    element.checked = false;
                }
            }
            value(false);
        });
    },
    update: function(element, valueAccessor) {
        // use default ko code to update checkbox
        ko.bindingHandlers.checked.update(element, valueAccessor);
    }
};

更新的小提琴位于http://jsfiddle.net/photo_tom/xHqsZ/32/

答案 2 :(得分:0)

我遇到了同样的问题,尽管情况不同。 (我试图使用复选框对列表进行批量更新。)在查看答案后,我决定放弃计算的检查绑定,而是使用了一个observable进行绑定,并订阅了我的更新:

$(document).ready(function() {
    function VM() {
        var self = this;
        self.items = ko.observableArray([
            { name: "Foo", active: ko.observable(true) },
            { name: "Bar", active: ko.observable(true) },
            { name: "Bas", active: ko.observable(true) }
        ]);
        self.allActive = ko.observable(true);
        self.allActive.subscribe(function(value) {
            if(self.allActiveCanceled) {
                self.allActiveCanceled = false;
                return;
            }
            if(!confirm('Really?')) {
                window.setTimeout(function() {
                    self.allActiveCanceled = true;
                    self.allActive(!value);
                }, 1);
                return;
            }
            var items = self.items();
            for(var i = 0, l = items.length; i < l; i++) {
                items[i].active(value);   
            }
        });
        self.allActiveCanceled = false;
    }
    var vm = new VM();
    ko.applyBindings(vm);
});

以下是相关标记的小提琴:http://jsfiddle.net/makeblake/dWNLA/

setTimeout和cancel标志感觉有点像黑客,但它完成了工作。

答案 3 :(得分:0)

我还建议使用自定义的敲除绑定,但请确保重新使用/继承完整的ko.bindingHandlers.checked绑定功能,以便从其旧版浏览器的兼容性处理中获益:

ko.bindingHandlers.confirmedChecked = {
    'after': ['value', 'attr'],
    'init': function (element, valueAccessor, allBindings)
    {
      ko.utils.registerEventHandler(
        element,
        'click',
        function(event)
        {
            if (
                element.checked &&
                !confirm('Are you sure you want to enable this setting?')
            )
            {
                if (event.stopImmediatePropagation)
                {
                    event.stopImmediatePropagation();
                }

                element.checked = false;
            }
        }
    );

    ko.bindingHandlers.checked.init(element, valueAccessor, allBindings);
    }
};