【问题标题】:BackboneJS - listening to nested model changesBackboneJS - 监听嵌套模型的变化
【发布时间】:2012-08-21 01:53:00
【问题描述】:

简短版:我有一个 Backbone 模型,它的属性之一是第二个 Backbone 模型。第一个模型中的一个函数改变了第二个模型的状态,但是我的视图,它正在监听第一个模型的变化,似乎没有拾取第二个模型的状态,尽管有任何数量的日志记录表明不是这样(我一直在各个点记录this 以确认范围等)。我该如何解决这个问题?

长版:我有一个Backbone 模型Course,它代表一门学术课程,还有一个模型NameList,它代表一个Course 注册的学生列表。 Course“有”NameListNameList 由服务器上的单个文本文件支持。

我想在 Course 模型中创建一个名为 importNameList 的函数,该函数创建一个新的 NameList 模型,并导致 NameList 模型和 fetch 来自后端的数据。由于我的观点,CourseView 正在监听Course 模型的更改,而Course 模型具有-a 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;
    }

});

【问题讨论】:

  • 为什么要在 NameList.js 中覆盖 fetch?这似乎与backbone.js 的用途完全相反。您不应该直接调用 ajax 函数来更新模型。

标签: javascript backbone.js


【解决方案1】:

我最终做了以下事情。

CourseView.js:

// No changes.

在 Course.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();
        }
    });
}

【讨论】:

    【解决方案2】:

    快速的答案是使用 jQuery 一个 deferred 对象。您可以在 this article 中找到更多关于在主干.js 中使用 deferreds 的信息。

    我会添加更具体的信息,但我不太清楚你为什么要覆盖 fetch,这似乎是灾难的根源。我会坚持使用主干的原始 fetch 方法,并将其排除在 NameList 的初始化程序之外,而是从 Course.js 调用它,并使用从它返回的 deferred 来保证在 fetch 完成后运行之后的步骤。

    更新:

    这是您可以执行此操作的一种方法的草图。先从NameListinitialize方法中取出model.fetch();这一行,在importNameList中调用,将其返回值(一个延迟对象)作为importNameList的返回值:

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

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

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

    我认为这应该可以满足您的需求,尽管我还没有实际测试过。

    【讨论】:

    • 您关于将其排除在 initialize 之外的观点值得考虑,但我的后端并不完全遵循 Backbone 的普通 RESTful 接口假设,这就是我覆盖 fetch 的原因。
    • 我明白了。好吧,基本上我使用的方法是获取从fetch 返回的延迟,然后在其上调用done,并在获取完成时传递一个我想要运行的函数。看到这个答案:stackoverflow.com/questions/11585137/…
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-04-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多