Knockout计算出observable不会触发'write'

时间:2015-02-06 10:45:16

标签: javascript knockout.js knockout-components

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

Here's a test case. 尝试点击这些项目并在下面进行编辑。它有效。

...然而

加载到数组中的数据来自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会被解雇吗?但不是我的情况......

可能的进一步复杂性是这将是一个KO组件。文本输入实际上来自于从窗口小部件传递的参数。所以我猜它已经是一个可观察的?所以它还需要更新父视图模型。

除此之外,我正在尝试使用可排序插件来重新排序这些项目 - 但我已将其从测试用例中删除。

1 个答案:

答案 0 :(得分:1)

&#39>写'你的计算机的函数没有触发,因为你没有写入计算机 - 这意味着在某处调用ssArray(some_value)

这是另一种有效的解决方案:

  1. 我们为单个文本/值对
  2. 创建一个名为items的observableArray
  3. 通过手动调用loadJSON填充此observableArray。
  4. 我们创建一个计算器,通过遍历它们来建立对items observableArray以及所有项textvalue可观察项的订阅。无论何时添加或删除或更改任何项目,我们都会将整个数组序列化为JSON
  5. 您当然可以订阅self.text并自动触发loadJSON,但是您必须处理“文字”的圈子'触发' 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>
    &#13;
    &#13;
    &#13;

    如果您愿意,这里有一个替代版本,可以处理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);
    

    &#13;
    &#13;
    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);
    &#13;
    body {
          font-family: arial;
          font-size: 14px;
        }
        .well {
          background-color: #eee;
          padding: 10px;
        }
        pre {
          white-space: pre-wrap;
        }
        ul {
          list-style-position: inside;
          }
    &#13;
    <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>
    &#13;
    &#13;
    &#13;