问题描述
我一直在尝试将 ko 视图模型保存到浏览器历史记录中并在 popstate 事件中返回它。 目前没有抛出任何错误,但 popstate 上没有任何更改。 我正在尝试的基本流程是这样的:
var storeViewModel = function (){
return ko.toJSON(viewModel);
};
function viewModel() {
var self = this;
self.records = ko.observableArray();
// add an object to the array and save view model
self.addRecord = function () {
self.records.push(new record());
// call the storeViewModel function push viewModel to history
history.pushState(storeViewModel(), "");
}
// set view model to stored view model object on forward / back navigation navigation
window.onpopstate = function (event) {
self = ko.utils.parseJson(event.state);
}
}
ko.applyBindings(new viewModel());
我已经多次阅读了 mozilla 文档。 一切似乎都有意义,但我在实施时遇到了麻烦。 谢谢你的帮助!
1楼
Tomalak
2
已采纳
2015-07-25 18:41:34
您不会保存视图模型,而是保存它包含的数据。
最方便的方法是使用自动映射。 Knockout 有 [mapping plugin][1] 来解决这个问题; 它允许您轻松地将原始数据转换为工作视图模型,并将工作视图模型转换回原始数据。
默认情况下,映射插件将原始数据的所有属性分别映射到observable
或observableArray
,但这可以在映射定义中进行微调(参见文档)。
这基本上是这样工作的:
ko.mapping.fromJS(data, {/* mapping definition */}, self);
然后像这样返回:
ko.mapping.toJS(self);
我建议设置所有视图模型,以便它们可以从原始数据中引导自己:
function Record(data) {
var self = this;
// init
ko.mapping.fromJS(data, Record.mapping, self);
}
Record.mapping = {
// mapping definition for Record objects, kept separately as a constructor
// property to keep it out of the individual Record objects
};
和
function RecordPlayer(data) {
var self = this;
self.records = ko.observableArray();
self.init(data);
self.state = ko.pureComputed(function () {
return ko.mapping.toJS(self);
});
}
RecordPlayer.mapping = {
// mapping rules for ViewModel objects
records: {
create: function (options) {
return new Record(options.data);
}
}
};
RecordPlayer.prototype.init = function (data) {
// extend a default data layout with the actual data
data = ko.utils.extend({
records: []
}, data);
ko.mapping.fromJS(data, ViewModel.mapping, this);
};
RecordPlayer.prototype.addRecord = function () {
this.records.push(new Record());
};
映射插件会跟踪它在.fromJS
步骤中映射的所有属性,并且只返回.toJS()
步骤中的.toJS()
。
任何其他属性,如计算,将被忽略。
这也是ko.utils.extend
的原因 - 建立您希望映射插件处理的基线属性集。
由于knockout 的内置依赖跟踪,每次与状态相关的数据发生变化时,计算的state
都会发生变化。
现在剩下的是处理页面加载事件:
// initialize the viewmodel
var player = new RecordPlayer(/* data e.g. from Ajax */);
// subscribe to VM state changes (except for changes due to popState)
var popStateActive = false;
player.state.subscribe(function (data) {
if (popStateActive) return;
history.pushState(ko.toJSON(data), "");
});
// subscribe to window state changes
player.utils.registerEventHandler(window, "popstate", function (event) {
popStateActive = true;
player.init( ko.utils.parseJson(event.state) );
popStateActive = false;
});
// and run it
ko.applyBindings(player);
您可以展开并运行下面的代码片段以查看它的运行情况。
function Record(data) { var self = this; // init ko.mapping.fromJS(data, Record.mapping, self); } Record.mapping = { // mapping definition for Record objects, kept separately as a constructor // property to keep it out of the individual Record objects }; function RecordPlayer(data) { var self = this; self.records = ko.observableArray(); self.init(data); self.state = ko.pureComputed(function() { return ko.mapping.toJS(self); }); } RecordPlayer.mapping = { // mapping rules for RecordPlayer objects records: { create: function(options) { return new Record(options.data); } } }; RecordPlayer.prototype.init = function(data) { // extend a default data layout with the actual data data = ko.utils.extend({ records: [] }, data); ko.mapping.fromJS(data, RecordPlayer.mapping, this); }; RecordPlayer.prototype.addRecord = function() { this.records.push(new Record()); }; RecordPlayer.prototype.pushState = function() { history.pushState(this.state(), ""); }; // initialize the viewmodel var player = new RecordPlayer( /* optional: data eg from Ajax */ ); var popStateActive = false; // subscribe to VM state changes (except for changes due to popState) player.state.subscribe(function(data) { if (popStateActive) return; history.pushState(ko.toJSON(data), ""); }); // subscribe to window state changes ko.utils.registerEventHandler(window, "popstate", function(event) { popStateActive = true; player.init(ko.utils.parseJson(event.state)); popStateActive = false; }); // and run it ko.applyBindings(player);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout.mapping/2.4.1/knockout.mapping.min.js"></script> <p>Records: <span data-bind="foreach: records"> <span>(o)</span> </span> </p> <p>There are <span data-bind="text: records().length"></span> records in the player.</p> <button data-bind="click: addRecord">Add record</button> <button data-bind="click: function () { history.back(); }">Undo (<code>history.back()<code>)</button>