[英]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: 我尝试了以下方法:
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: 这是一种可行的替代解决方案:
items
for our individual text/value pairs items
的observableArray。 loadJSON
manually. loadJSON
来填充此observableArray。 items
observableArray, as well as to all the items text
and value
observables by iterating over them. items
observableArray以及所有项目text
和value
observable的订阅。 Whenever either items are added or removed or change, we serialize the whole array back to 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.