繁体   English   中英

淘汰赛计算的可观察性不触发“写”

[英]Knockout computed observable not firing 'write'

我有一个相当简单的对象数组,可以在KO中进行编辑

这是一个测试案例。 尝试单击项目,然后在下面对其进行编辑。 有用。

然而...

加载到数组中的数据来自JSON字符串:

 self.text = ko.observable('[{ "value": "1", "text": "Low" }, ..... ]');

必须对此进行解析并将其转换为JS对象。 这是在像这样的计算函数中完成的:

 self.ssArray = ko.computed({
    read: function() {

        // Convert text into JS object
        // Not using ko.utils because I want to use try/catch to detect bad JS later

        var arrayJS = JSON.parse(ko.utils.unwrapObservable(self.text));

        // Make an array of observables
        // Not using ko.mapping in order to get back to basics
        // Also mapping function throws an error re: iterations or something

        var obsArrayJS = ko.utils.arrayMap(arrayJS, function(i) {
            return {
                "value": ko.observable(i.value),
                "text": ko.observable(i.text)
            };
        });

        // return array of objects with observable properties.
        return obsArrayJS;

        // Tried this but made no difference:
        //return ko.observableArray(obsArrayJS);
    },

现在,我想要的是每当模型更新时就更新原始文本字符串。 在模型上应该是ko.toJSON的简单情况:

 write: function(value) {
        self.text(ko.toJSON(this.ssArray));
    },

从小提琴中可以看到,self.text不会更新。

为什么是这样?

我尝试了以下方法:

  • 从read函数返回一个observableArray-没有区别
  • 返回可观察对象的observableArray,每个对象都具有可观察的属性
  • 使用映射插件使一切皆可观察

我想这可以归结为KO如何触发写入功能。 当然,如果ssArray的内容发生更改,那么write引发write操作? 但就我而言...

可能的进一步复杂化是,这将是KO组件。 文本输入实际上将来自小部件传递的参数。 所以我想这已经是可以观察的了? 因此,它也需要更新父视图模型。

除此之外,我正在尝试使用sortable插件来允许这些项目的重新排序-但我已经从测试用例中删除了它。

计算的“写入”功能不会触发,因为您未写入计算的-这意味着在某处调用ssArray(some_value)

这是一种可行的替代解决方案:

  1. 我们为我们的各个文本/值对创建一个名为items的observableArray。
  2. 通过手动调用loadJSON来填充此observableArray。
  3. 我们创建一个计算程序,通过迭代这些对象来建立对items observableArray以及所有项目textvalue observable的订阅。 无论何时添加,删除或更改任何项目,我们都会将整个数组序列化回JSON

您当然可以订阅self.text并自动触发loadJSON ,但是随后您将不得不处理'text'触发'loadJSON',触发我们的计算并写回text的循环。

(为了隐藏HTML和CSS代码块,我隐藏了代码片段。单击“显示代码片段”运行示例。)

    function MyViewModel() {

        var self = this;

        this.selectedItemSS = ko.observable();
        this.setSelectedSS = function(item) {
            self.selectedItemSS(item);
        };

        // Data in text form. Passed in here as a parameter from parent component
        this.text = ko.observable('[{"value": "1", "text": "Low"}, {"value": "2", "text": "Medium"}, {"value": "3", "text": "High"} ]');

        this.items = ko.observableArray([]);

        this.loadJSON = function loadJSON(json) {
            var arrayOfObjects = JSON.parse(json),
                arrayOfObservables;

            // clear out everything, or otherwise we'll end
            // up with duplicated objects when we update
            self.items.removeAll();

            arrayOfObservables = ko.utils.arrayMap(arrayOfObjects, function(object) {
                return {
                    text:  ko.observable(object.text),
                    value: ko.observable(object.value)
                };
            });

            self.items(arrayOfObservables);
        };

        this.loadJSON( this.text() );

        ko.computed(function() {
            var items = this.items();

            // iterate over all observables in order
            // for our computed to get a subscription to them
            ko.utils.arrayForEach(items, function(item) {
                item.text();
                item.value();
            });

            this.text(ko.toJSON(items));

        }, this);
    }

    ko.applyBindings(new MyViewModel());

  function MyViewModel() { var self = this; this.selectedItemSS = ko.observable(); this.setSelectedSS = function(item) { self.selectedItemSS(item); }; // Data in text form. Passed in here as a parameter from parent component this.text = ko.observable('[ \\ {\\ "value": "1",\\ "text": "Low"\\ },\\ { \\ "value": "2",\\ "text": "Medium"\\ },\\ {\\ "value": "3",\\ "text": "High"\\ } ]'); this.items = ko.observableArray([]); this.loadJSON = function loadJSON(json) { var arrayOfObjects = JSON.parse(json), arrayOfObservables; // clear out everything, or otherwise we'll end // up with duplicated objects when we update self.items.removeAll(); arrayOfObservables = ko.utils.arrayMap(arrayOfObjects, function(object) { return { text: ko.observable(object.text), value: ko.observable(object.value) }; }); self.items(arrayOfObservables); }; this.loadJSON( this.text() ); ko.computed(function() { var items = this.items(); // iterate over all observables in order // for our computed to get a subscription to them ko.utils.arrayForEach(items, function(item) { item.text(); item.value(); }); this.text(ko.toJSON(items)); }, this); } ko.applyBindings(new MyViewModel()); 
 body { font-family: arial; font-size: 14px; } .well {background-color:#eee; padding:10px;} pre {white-space:pre-wrap;} 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script> <h3>Text Json: eg from AJAX request</h3> <p>In practice this comes from a parent custom component as a parameter</p> <pre class="well" data-bind="text:text"></pre> <h3>Computed data model</h3> <p>Click on an item to edit that record</p> <div data-bind="foreach:items" class="well"> <div data-bind="click: $parent.setSelectedSS"> <span data-bind="text:value"></span> <span data-bind="text:text"></span><br/> </div> </div> <hr/> <h3>Editor</h3> <div data-bind="with:selectedItemSS" class="well"> <input data-bind="textInput:value"/> <span data-bind="text:value"></span><br/> </div> 

如果您愿意,这是一个替代版本,它既可以处理JSON的更改,又可以通过单个计算通过接口进行编辑:

function MyViewModel(externalObservable) {
  var self = this;

  this.selectedItemSS = ko.observable();
  this.setSelectedSS  = function(item) {
    self.selectedItemSS(item);
  };

  // just for the demo
  this.messages       = ko.observableArray([]);

  this.items          = ko.observableArray([]);
  this.json           = externalObservable;
  this.previous_json  = '';

  ko.computed(function() {
    var items = this.items(),
        json  = this.json();

    // If the JSON hasn't changed compared to the previous run,
    // that means we were called because an item was edited
    if (json === this.previous_json) {
      var new_json = ko.toJSON(items);

      self.messages.unshift("items were edited, updating JSON: " + new_json);

      this.previous_json = new_json;
      this.json(new_json);

      return;
    }

    // If we end up here, that means that the JSON has changed compared
    // to the last run

    self.messages.unshift("JSON has changed, updating items: " + json);

    var arrayOfObjects = JSON.parse(json),
        arrayOfObservables;

    // clear out everything, or otherwise we'll end
    // up with duplicated objects when we update
    this.items.removeAll();

    arrayOfObservables = ko.utils.arrayMap(arrayOfObjects, function(object) {
      return {
        text: ko.observable(object.text),
        value: ko.observable(object.value)
      };
    });

    // iterate over all observables in order
    // for our computed to get a subscription to them
    ko.utils.arrayForEach(arrayOfObservables, function(item) {
      item.text();
      item.value();
    });

    this.items(arrayOfObservables);

    this.previous_json = json;

  }, this);
}

var externalObservableFromParam = ko.observable(),
    viewModel;


// Pretend here that this observable was handed to us
// from your components' params
externalObservableFromParam('[{"value": "1", "text": "Low"}, {"value": "2", "text": "Medium"}, {"value": "3", "text": "High"} ]');

viewModel = new MyViewModel(externalObservableFromParam);

ko.applyBindings(viewModel);

 function MyViewModel(externalObservable) { var self = this; this.selectedItemSS = ko.observable(); this.setSelectedSS = function(item) { self.selectedItemSS(item); }; // just for the demo this.messages = ko.observableArray([]); this.items = ko.observableArray([]); this.json = externalObservable; this.previous_json = ''; ko.computed(function() { var items = this.items(), json = this.json(); // If the JSON hasn't changed compared to the previous run, // that means we were called because an item was edited if (json === this.previous_json) { var new_json = ko.toJSON(items); self.messages.unshift("items were edited, updating JSON: " + new_json); this.previous_json = new_json; this.json(new_json); return; } // If we end up here, that means that the JSON has changed compared // to the last run self.messages.unshift("JSON has changed, updating items: " + json); var arrayOfObjects = JSON.parse(json), arrayOfObservables; // clear out everything, or otherwise we'll end // up with duplicated objects when we update this.items.removeAll(); arrayOfObservables = ko.utils.arrayMap(arrayOfObjects, function(object) { return { text: ko.observable(object.text), value: ko.observable(object.value) }; }); // iterate over all observables in order // for our computed to get a subscription to them ko.utils.arrayForEach(arrayOfObservables, function(item) { item.text(); item.value(); }); this.items(arrayOfObservables); this.previous_json = json; }, this); } var externalObservableFromParam = ko.observable(), viewModel; // Pretend here that this observable was handed to us // from your components' params externalObservableFromParam('[{"value": "1", "text": "Low"}, {"value": "2", "text": "Medium"}, {"value": "3", "text": "High"} ]'); viewModel = new MyViewModel(externalObservableFromParam); ko.applyBindings(viewModel); 
 body { font-family: arial; font-size: 14px; } .well { background-color: #eee; padding: 10px; } pre { white-space: pre-wrap; } ul { list-style-position: inside; } 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script> <h3>Text Json: eg from AJAX request</h3> <p>In practice this comes from a parent custom component as a parameter</p> <pre class="well" data-bind="text: json"></pre> <textarea data-bind="value: json" cols=50 rows=5></textarea> <h3>Computed data model</h3> <p>Click on an item to edit that record</p> <div data-bind="foreach: items" class="well"> <div data-bind="click: $parent.setSelectedSS"> <span data-bind="text:value"></span> <span data-bind="text:text"></span> <br/> </div> </div> <hr/> <h3>Editor</h3> <div data-bind="with:selectedItemSS" class="well"> <input data-bind="textInput:value" /> <span data-bind="text:value"></span> <br/> </div> <hr/> <h3>Console</h3> <ul data-bind="foreach: messages" class="well"> <li data-bind="text: $data"></li> </ul> 

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM