[英]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不會更新。
為什么是這樣?
我嘗試了以下方法:
我想這可以歸結為KO如何觸發寫入功能。 當然,如果ssArray的內容發生更改,那么write
引發write
操作? 但就我而言...
可能的進一步復雜化是,這將是KO組件。 文本輸入實際上將來自小部件傳遞的參數。 所以我想這已經是可以觀察的了? 因此,它也需要更新父視圖模型。
除此之外,我正在嘗試使用sortable插件來允許這些項目的重新排序-但我已經從測試用例中刪除了它。
計算的“寫入”功能不會觸發,因為您未寫入計算的-這意味着在某處調用ssArray(some_value)
。
這是一種可行的替代解決方案:
items
的observableArray。 loadJSON
來填充此observableArray。 items
observableArray以及所有項目text
和value
observable的訂閱。 無論何時添加,刪除或更改任何項目,我們都會將整個數組序列化回JSON 您當然可以訂閱self.text
並自動觸發loadJSON
,但是隨后您將不得不處理'text'觸發'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>
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.