1

我有一个相当简单的对象数组,可以在 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 是如何知道触发 write 函数的。如果 ssArray 的内容发生变化,那么肯定会write被解雇吗?但在我的情况下不是...

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

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

4

1 回答 1

1

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

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

  1. 我们创建了一个 observableArray,以items我们各自的文本/值对命名
  2. loadJSON这个 observableArray 是通过手动调用来填充的。
  3. 我们创建了一个计算对象,它通过遍历它们来建立对itemsobservableArray 以及所有项目text和observables 的订阅。value每当添加或删除或更改任何一项时,我们都会将整个数组序列化回 JSON

您当然可以订阅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>

如果您愿意,这里有一个替代版本,它可以通过单个计算处理对 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>

于 2015-02-06T12:56:48.880 回答