knockout.js如何在ko.computed写入中取消confirm()后设置所选选项

问题描述 投票:3回答:2

我有一个带选项和默认文本的选择器元素:

self._selected = ko.observable();
self.option = ko.computed({
    read:function(){
        return self._selected;
    },
    write: function(data){
        if(data){
            if(confirm('are you sure?')){
                self._selected(data);
            }else{
                //reset
            }
        }
    }
});

<select data-bind="options: options, value:option, optionsCaption: 'choose ...'"></select>

这个问题:

  • 选择“一个”
  • 在确认点击取消
  • 所选选项仍然是“一个”仍在焦点下

它应该是“选择......”

jsbin here,仅在镀铬上进行了测试

javascript knockout.js knockout-3.0 computed-observable
2个回答
1
投票

这里存在不对称性:

当您更改选择框的值时,DOM会立即更新并随后进行淘汰(当然,淘汰赛取决于DOM更改事件)。因此,当您的代码询问“您确定吗?”时,DOM已经具有新值。

现在,当您不将该值写入绑定到value:的observable时,viewmodel的状态不会更改。并且当可观察到的更改时,knockout仅更新DOM。因此DOM保持在选定的值,并且viewmodel中的绑定值是不同的。


最简单的方法是将旧值保存在变量中,始终将新值写入observable,如果用户单击“否”,则只需恢复旧值。这样就破坏了不对称性,DOM和视图模型保持同步。

var AppData = function(params) {
    var self = {};
    var selected = ko.observable();
    
    self.options = ko.observableArray(params.options);  
    self.option = ko.computed({
        read: selected,
        write: function(value) {
            var oldValue = selected();
            selected(value);
            if (value !== oldValue && !confirm('are you sure?')) {
                selected(oldValue);
            }
        }
    });
  
    return self;
};

// ----------------------------------------------------------------------
ko.applyBindings(new AppData({
  options: ['one','two','three']
}));
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>

<select data-bind="options: options, value: option, optionsCaption: 'Select...'"></select>

<hr>
<pre data-bind="text: ko.toJSON($root, null, 2)"></pre>

这是一个淘汰扩展器的完美候选者,要求确认价值变化。这样我们就可以将它重用于不同的observable并保持viewmodel的清洁。

ko.extenders.confirmChange = function (target, message) {
    return ko.pureComputed({
        read: target,
        write: function(newValue) {
            var oldValue = target();
            target(newValue);
            if (newValue !== oldValue && !confirm(message)){
                target(oldValue);
            }
        }
    });
};

// ----------------------------------------------------------------------
var AppData = function(params) {
    var self = this;
    
    self.options = ko.observableArray(params.options);  
    self.option = ko.observable().extend({confirmChange: 'are you sure?'});
};

// ----------------------------------------------------------------------
ko.applyBindings(new AppData({
  options: ['one','two','three']
}));
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>

<select data-bind="options: options, value: option, optionsCaption: 'Select...'"></select>

<hr>
<pre data-bind="text: ko.toJSON($root, null, 2)"></pre>

2
投票

问题是底层变量的值没有变化,因此没有事件告诉Knockout它的value与viewmodel不同步。

使用正常的可观察量,你可以调用valueHasMutated来表示发生了一些隐匿性变化,但计算结果似乎没有。但他们确实有notifySubscribers。事实上,你的例子非常像this example in the docs

这是一个有效的例子:

function vm() {
  const self = {};

  self.options = ko.observableArray(['one', 'two', 'three']);
  self._selected = ko.observable();
  self.option = ko.pureComputed({
    read: self._selected,
    write: function(data) {
      if (data) {
        if (confirm('are you sure?')) {
          self._selected(data);
        } else {
          self.option.notifySubscribers(self._selected());
        }
      }
    }
  });

  return self;
}

ko.applyBindings(vm());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<select data-bind="options: options, value:option, optionsCaption: 'choose ...'"></select>
<div data-bind="text:_selected"></div>
<div data-bind="text:option"></div>
© www.soinside.com 2019 - 2024. All rights reserved.