BackboneJS-监听嵌套的模型更改

BackboneJS - listening to nested model changes

本文关键字:模型 监听 嵌套 BackboneJS-      更新时间:2023-09-26

简短版本:我有一个骨干模型,它的属性之一是第二个骨干模型。第一个模型中的一个函数会更改第二个模型的状态,但我的视图,即监听第一个模型的更改,似乎并没有获取第二个模式的状态,尽管有大量的日志记录表明情况并非如此(我一直在不同的点记录this以确认范围等)。我该如何解决这个问题?

长版本:我有一个代表学术课程的主干模型Course和一个代表Course注册学生列表的模型NameList。一个Course"有一个"NameListNameList由服务器上的单个文本文件支持。

我想在Course模型中有一个名为importNameList的函数,它创建一个新的NameList模型,并使该NameList模型运行,fetch从后端获取数据。由于我的视图CourseView正在侦听对Course模型的更改,而Course模型具有NameList,因此似乎应该相应地更新视图。我的问题是它没有。

我想做什么

var course = new Course();
var courseView = new CourseView({model : course});
courseView.model.importNameList('students.txt'); // Will cause view to re-render

我要做的

var course = new Course(); // same 
var courseView = new CourseView({model : course}); // same
courseView.model.importNameList('students.txt'); // same
courseView.render(); // Argh, manually calling this is ugly.

这是我的带有日志记录语句的代码。模型扩展Backbone.DeepModel只是因为我认为它可以解决我的问题,但它没有。

控制台输出

[console] var course = new Course();
[console] var courseView = new CourseView({model : course});
[console] course.importNameList('students.txt');
          Course >>> importNameList
          NameList >>> initialize()
          NameList >>> fetch()
          GET http://localhost/files/students.txt 200 OK 16ms    
          CourseView >>> render() // Render starts before fetch completes
          CourseView <<< render() // Render finishes before fetch completes
          Course <<< importNameList
          NameList <<< fetch()
[console] courseView.render();
          CourseView >>> render()
          alice
          bob
          charlie
          dan
          erica
          fred
          george
          CourseView <<< render()

Course.js

var Course = Backbone.DeepModel.extend({
    defaults : {
        nameList : new NameList()
    },
    initialize: function(options) {
        if (options && options.nameList) {
            this.set({nameList : options.nameList});
        }
    },
    importNameList : function(fileName) {
        console.log("Course >>> importNameList");
        var nameList = new NameList({fileName : fileName});
        this.set({nameList : nameList});
        console.log("Course <<< importNameList");
    }
});

NameList.js

var NameList = Backbone.DeepModel.extend({
    defaults : {
        fileName : 'new.txt',
        names : []
    },
    initialize: function(options) {
        console.log("NameList >>> initialize()");
        var model = this;
        if (options && options.fileName) {
            model.set({fileName : options.fileName});
        }
        model.fetch();
    },
    fetch : function() {
        console.log("NameList >>> fetch()");
        var model = this;
        $.ajax({
            url : '/files/' + model.get('fileName'),
            success : function(response) {
                model.set({names : response.split(''n')});
                console.log("NameList <<< fetch()");
            }
        });
    }
});

CourseView.js

var CourseView = Backbone.View.extend({
    initialize : function(options) {
        var view = this;
        if (options && options.model) {
            view.model = options.model;
        } else {
            view.model = new Course();
        }
        view.model.on('change', function() {
            view.render();
        });
    },
    render : function() {
        console.log("CourseView >>> render()");
        var names = this.model.get('nameList').get('names');
        for (var i = 0; i < names.length; i++) {
            console.log(names[i]);
        }
        console.log("CourseView <<< render()");
        return this;
    }
});

快速答案是使用jQuery一个deferred对象。您可以在本文中找到更多关于在backbone.js中使用defereds的信息。

我想添加更多具体的信息,但我不太清楚你为什么要覆盖fetch,这似乎是一场灾难。我会坚持使用主干的原始fetch方法,并将其排除在NameList的初始值设定项之外,而是从Course.js中调用它,并使用它返回的延迟值来确保在fetch完成后运行后面的步骤。

更新:

这是一个可以做到这一点的方法的草图。首先从NameListinitialize方法中取出model.fetch();行,并在importNameList中调用它,使用它的返回值(一个延迟对象)作为importNameList:的返回值

var nameList = new NameList({fileName : fileName});
this.set({nameList : nameList});
return nameList.fetch()

现在我们从importNameList返回一个延迟的,我们可以使用它来保证在渲染之前完成提取:

this.deferred = courseView.model.importNameList('students.txt');
this.deferred.done(function() { courseView.render() });

我认为这应该是你想要的,尽管我还没有真正测试过。

我最终完成了以下操作。

CourseView.js:

// No changes.

课程内部js:

importNameList : function(name) {
    var model = this;
    var nameList = new NameList({fileName : fileName});
    // Set the attribute silently (without triggering a change event)
    model.set({nameList : nameList}, {silent: true});
    nameList.fetch({
        success : function() {
            model.change(); // Manually fire a change event when fetch completes.
        }
    });
}

在NameList.js中:

fetch : function(options) {
    var model = this;
    $.ajax({
        url : '/files/' + model.get('fileName'),
        success : function(response) {
            model.set({lines : response.split(''n')});
            // Make the "success" callback.
            options.success();
        }
    });
}