One View connected to multiple models

2019-03-19 15:58发布

问题:

I have the following problem…

MyView which is connected to two views: TaskModel and UserModel

TaskModel = {id: 1, taskName: "myTask", creatorName: "myName", creator_id: 2 },
UserModel = {id: 2, avatar: "someAvatar"}

The view should display

{{taskName}}, {{creatorName}}, {{someAvatar}}

As you can see the fetch of TaskModel and UserModel should be synchronized, because the userModel.fetch needs of taskModel.get("creator_id")

Which approach do you recommend me to display/handle the view and the two models?

回答1:

You could make the view smart enough to not render until it has everything it needs.

Suppose you have a user and a task and you pass them both to the view's constructor:

initialize: function(user, task) {
    _.bindAll(this, 'render');
    this.user = user;
    this.task = task;
    this.user.on('change', this.render);
    this.task.on('change', this.render);
}

Now you have a view that has references to both the user and the task and is listening for "change" events on both. Then, the render method can ask the models if they have everything they're supposed to have, for example:

render: function() {
    if(this.user.has('name')
    && this.task.has('name')) {
        this.$el.append(this.template({
            task: this.task.toJSON(),
            user: this.user.toJSON()
        }));
    }
    return this;​​​​
}

So render will wait until both the this.user and this.task are fully loaded before it fills in the proper HTML; if it is called before its models have been loaded, then it renders nothing and returns an empty placeholder. This approach keeps all of the view's logic nicely hidden away inside the view where it belongs and it easily generalizes.

Demo: http://jsfiddle.net/ambiguous/rreu5jd8/


You could also use Underscore's isEmpty (which is mixed into Backbone models) instead of checking a specific property:

render: function() {
    if(!this.user.isEmpty()
    && !this.task.isEmpty()) {
        this.$el.append(this.template({
            task: this.task.toJSON(),
            user: this.user.toJSON()
        }));
    }
    return this;​​​​
}

That assumes that you don't have any defaults of course.

Demo: http://jsfiddle.net/ambiguous/4q07budc/



回答2:

jQuery's Deferreds work well here. As a crude example:

var succesFunction = function () {
    console.log('success');
};
var errorFunction = function () {
    console.log('error');
};

$.when(taskModel.fetch(), userModel.fetch()).then(successFunction, errorFunction);

You could also pipe the request through using the crude data (remember that fetch, save, create are really just wrappers around jQuery's $.ajax object.

var taskModelDeferred = taskModel.fetch();
var userModelDeferred = taskModelDeferred.pipe(function( data ) {
    return userModel.fetch({ data: { user: data.userId }});
});

note: Backbone returns the collection and model in the success / error functions by default on collections and models so if you need this be sure have a reference handy.



回答3:

I've run into this very same issue with a complex layout that used two models and multiple views. For that, instead of trying to synchronize the fetches, I simply used the "success" function of one model to invoke the fetch of the other. My views would listen only to the change of the second model. For instance:

var model1 = Backbone.Model.extend({
    ...
});
var model2 = Backbone.Model.extend({
    ...
});

var view1 = Backbone.View.extend({
    ...
});
var view2 = Backbone.View.extend({
    ...
});

model2.on("change",view1.render, view1);
model2.on("change",view2.render, view2);

Then...

model1.fetch({
    success : function() {
        model2.fetch();
    }
});

The point to this is you don't have to do any sophisticated synchronization. You simply cascade the fetches and respond to the last model's fetch.



标签: backbone.js