当前位置: 代码迷 >> JavaScript >> 在 pushstate / popstate 上返回淘汰赛视图模型
  详细解决方案

在 pushstate / popstate 上返回淘汰赛视图模型

热度:80   发布时间:2023-06-05 10:18:05.0

我一直在尝试将 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 文档。 一切似乎都有意义,但我在实施时遇到了麻烦。 谢谢你的帮助!

您不会保存视图模型,而是保存它包含的数据

最方便的方法是使用自动映射。 Knockout 有 [mapping plugin][1] 来解决这个问题; 它允许您轻松地将原始数据转换为工作视图模型,并将工作视图模型转换回原始数据。

默认情况下,映射插件将原始数据的所有属性分别映射到observableobservableArray ,但这可以在映射定义中进行微调(参见文档)。

这基本上是这样工作的:

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>