简体   繁体   English

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

[英]Knockout computed observable not firing 'write'

I have a fairly simple array of objects that can be edited in KO 我有一个相当简单的对象数组,可以在KO中进行编辑

Here's a test case. 这是一个测试案例。 Try clicking on the items and editing them down below. 尝试单击项目,然后在下面对其进行编辑。 It works. 有用。

However... 然而...

The data loaded into the array comes from a JSON string: 加载到数组中的数据来自JSON字符串:

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

This must be parsed and converted into a JS object. 必须对此进行解析并将其转换为JS对象。 This is done in the computed function like this: 这是在像这样的计算函数中完成的:

 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);
    },

Now what I want is for the original text string to be updated whenever the model is updated. 现在,我想要的是每当模型更新时就更新原始文本字符串。 It should be a simple case of ko.toJSON on the model: 在模型上应该是ko.toJSON的简单情况:

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

As you can see from the fiddle, self.text is not updated. 从小提琴中可以看到,self.text不会更新。

Why is this? 为什么是这样?

I have tried the following: 我尝试了以下方法:

  • returning an observableArray from the read function - makes no difference 从read函数返回一个observableArray-没有区别
  • return an observableArray of observable objects each with observable properties 返回可观察对象的observableArray,每个对象都具有可观察的属性
  • using the mapping plugin to make everything possible observable 使用映射插件使一切皆可观察

I guess it boils down to how KO knows to fire the write function. 我想这可以归结为KO如何触发写入功能。 Surely if the contents of ssArray change then write is fired? 当然,如果ssArray的内容发生更改,那么write引发write操作? But not in my case... 但就我而言...

Possible further complication is that this will be a KO component. 可能的进一步复杂化是,这将是KO组件。 The text input will actually come from a parameter passed from the widget. 文本输入实际上将来自小部件传递的参数。 So I guess it will already be an observable? 所以我想这已经是可以观察的了? So it will need to update the parent viewmodel too. 因此,它也需要更新父视图模型。

In addition to this I'm trying to use the sortable plugin to allow reordering of these items - but I've removed that from my test case. 除此之外,我正在尝试使用sortable插件来允许这些项目的重新排序-但我已经从测试用例中删除了它。

The 'write' function of your computed is not firing, because you are not writing to the computed — that would mean calling ssArray(some_value) somewhere. 计算的“写入”功能不会触发,因为您未写入计算的-这意味着在某处调用ssArray(some_value)

This is an alternative solution that works: 这是一种可行的替代解决方案:

  1. We create an observableArray named items for our individual text/value pairs 我们为我们的各个文本/值对创建一个名为items的observableArray。
  2. This observableArray is populated by calling loadJSON manually. 通过手动调用loadJSON来填充此observableArray。
  3. We create a computed that establishes subscriptions to the items observableArray, as well as to all the items text and value observables by iterating over them. 我们创建一个计算程序,通过迭代这些对象来建立对items observableArray以及所有项目textvalue observable的订阅。 Whenever either items are added or removed or change, we serialize the whole array back to JSON 无论何时添加,删除或更改任何项目,我们都会将整个数组序列化回JSON

You could certainly subscribe to self.text and trigger loadJSON automatically, but then you will have to take care of the circle of 'text' triggering 'loadJSON', triggering our computed, writing back to text . 您当然可以订阅self.text并自动触发loadJSON ,但是随后您将不得不处理'text'触发'loadJSON',触发我们的计算并写回text的循环。

(I have hidden the code snippets in order to get rid of the HTML and CSS code blocks. Click "Show code snippet" to run the examples.) (为了隐藏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> 

If you prefer, here is an alternative version that handles both changes to the JSON as well as edits through the interface through a single computed: 如果您愿意,这是一个替代版本,它既可以处理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