击倒计算的可观察到的未发射'写'
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
肯定会被触发吗?但在我的情况下。。。
可能更复杂的是,这将是一个KO组件。文本输入实际上来自小部件传递的参数。所以我想它已经是可观测的了?因此,它也需要更新父视图模型。
除此之外,我正试图使用可排序插件来允许对这些项目进行重新排序,但我已经将其从测试用例中删除了。
computed的"write"函数没有启动,因为您没有向computed写入——这意味着在某个地方调用ssArray(some_value)
。
这是一个可行的替代解决方案:
- 我们为单独的文本/值对创建了一个名为
items
的observableArray - 这个observableArray是通过手动调用
loadJSON
来填充的 - 我们创建了一个computed,通过迭代来建立对
items
observableArray的订阅,以及对所有项text
和value
observables的订阅。无论何时添加、删除或更改项,我们都会将整个数组序列化回JSON
您当然可以订阅self.text
并自动触发loadJSON
,但随后您将不得不处理触发"loadJSON"的"text"圆圈,触发我们的计算,回写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>
相关文章:
- 根据元素和容器大小计算边距
- 从Rally获取一个特定的标记,以便计算另一个字段中的值
- 使用D3.js计算带有字母间距的文本长度
- NodeJS-readline暂停和恢复事件发射器(逐行读取)
- 使用CSS或JavaScript计算分页符的数量
- 可以't计算自定义谷歌地图的js
- 多次发射多个可观察器的问题
- 如何计算每个元素's的高度,并将这些值用作函数中的变量
- JavaScript计算帮助(乘以时间)
- 如何计算对象文字中的键
- JavaScript循环无法正确计算/显示结果
- 与域在同一台计算机上运行的NODEJS服务器的CORS错误
- 四舍五入JavaScript计算
- socket.io发射三次
- 计算HTML中的页数
- 使用jQuery计算数组中的对象以更改进度条
- 如何在jquery中使用实时计算求和值
- 计算多个项目的价格
- 击倒计算的可观察到的未发射'写'
- 读在击倒可计算不总是发射与扩展