Backbone.js 中的嵌套模型,如何使用

我从服务器获得了以下 JSON。这样,我想创建一个具有嵌套模型的模型。我不确定哪种方法能达到这个目的。

//json
[{
name : "example",
layout : {
x : 100,
y : 100,
}
}]

我希望将这些模型转换为具有以下结构的两个嵌套主干模型:

// structure
Image
Layout
...

所以我这样定义布局模型:

var Layout = Backbone.Model.extend({});

但是,我应该使用下面两种(如果有的话)技术中的哪一种来定义 Image 模型呢?下面是 A 还是 B?

A

var Image = Backbone.Model.extend({
initialize: function() {
this.set({ 'layout' : new Layout(this.get('layout')) })
}
});

或, < strong > B

var Image = Backbone.Model.extend({
initialize: function() {
this.layout = new Layout( this.get('layout') );
}
});
56254 次浏览

我不确定 Backbone 本身是否有推荐的方法来做这件事。布局对象在后端数据库中有自己的 ID 和记录吗?如果是这样,你可以使它自己的模型,因为你有。如果没有,可以将它作为嵌套文档保留,只需确保在 saveparse方法中正确地将其转换为 JSON 或从 JSON 转换为 JSON 即可。如果您最终采取了这样的方法,我认为您的 A示例与主干更一致,因为 set将正确更新 attributes,但我不确定主干在默认情况下如何处理嵌套模型。您可能需要一些自定义代码来处理这个问题。

我将这段代码作为 Peter Lyon 建议重新定义解析的一个例子。我也有同样的问题,这对我很有用(在 Rails 后端)。这个代码是在 Coffeescript 写的。我向不熟悉它的人说明了一些事情。

class AppName.Collections.PostsCollection extends Backbone.Collection
model: AppName.Models.Post


url: '/posts'


...


# parse: redefined to allow for nested models
parse: (response) ->  # function definition
# convert each comment attribute into a CommentsCollection
if _.isArray response
_.each response, (obj) ->
obj.comments = new AppName.Collections.CommentsCollection obj.comments
else
response.comments = new AppName.Collections.CommentsCollection response.comments


return response

或者,在 JS

parse: function(response) {
if (_.isArray(response)) {
return _.each(response, function(obj) {
return obj.comments = new AppName.Collections.CommentsCollection(obj.comments);
});
} else {
response.comments = new AppName.Collections.CommentsCollection(response.comments);
}
return response;
};

我在编写 Backbone 应用程序时遇到了同样的问题。必须处理嵌入/嵌套模型。我做了一些调整,我认为这是一个非常优雅的解决方案。

是的,您可以修改解析方法来更改对象中的属性,但是所有这些实际上都是相当不可维护的代码 IMO,并且感觉更像是一种破解而不是解决方案。

Here's what I suggest for your example:

首先像这样定义布局模型。

var layoutModel = Backbone.Model.extend({});

这就是你的形象模特:

var imageModel = Backbone.Model.extend({


model: {
layout: layoutModel,
},


parse: function(response){
for(var key in this.model)
{
var embeddedClass = this.model[key];
var embeddedData = response[key];
response[key] = new embeddedClass(embeddedData, {parse:true});
}
return response;
}
});

Notice that I have not tampered with the model itself, but merely pass back the desired object from the parse method.

这应该可以确保从服务器读取时嵌套模型的结构。现在,您会注意到这里实际上没有处理保存或设置,因为我觉得使用适当的模型显式地设置嵌套模型是有意义的。

像这样:

image.set({layout : new Layout({x: 100, y: 100})})

还要注意,您实际上是在调用嵌套模型中的 parse 方法,方法是调用:

new embeddedClass(embeddedData, {parse:true});

您可以根据需要在 model字段中定义尽可能多的嵌套模型。

当然,如果您希望将嵌套模型保存到它自己的表中。这还不够。但是在读取和保存对象作为一个整体的情况下,这个解决方案应该足够了。

Backbone-associations使用 Backbone.AssociatedModel:

    var Layout = Backbone.AssociatedModel.extend({
defaults : {
x : 0,
y : 0
}
});
var Image = Backbone.AssociatedModel.extend({
relations : [
type: Backbone.One,
key : 'layout',
relatedModel : Layout
],
defaults : {
name : '',
layout : null
}
});

我使用 Backbone DeepModel 插件来嵌套模型和属性。

Https://github.com/powmedia/backbone-deep-model

您可以绑定到更改事件的 n 个深层次。例如: model.on('change:example.nestedmodel.attribute', this.myFunction);

如果你想让事情简单点,我选 B。

Another good option would be to use 骨干-关系型. You'd just define something like:

var Image = Backbone.Model.extend({
relations: [
{
type: Backbone.HasOne,
key: 'layout',
relatedModel: 'Layout'
}
]
});

I had the same issue and I've been experimenting with the code in Rycfung 的回答, which is a great suggestion.
但是,如果你不想 set的嵌套模型直接,或者不想不断 pass {parse: true} in the options, another approach would be to redefine set itself.

Backbone 1.0.0中,setconstructorunsetclearfetchsave中被调用。

对于需要嵌套模型和/或集合的所有模型,考虑以下 超模

/** Compound supermodel */
var CompoundModel = Backbone.Model.extend({
/** Override with: key = attribute, value = Model / Collection */
model: {},


/** Override default setter, to create nested models. */
set: function(key, val, options) {
var attrs, prev;
if (key == null) { return this; }


// Handle both `"key", value` and `{key: value}` -style arguments.
if (typeof key === 'object') {
attrs = key;
options = val;
} else {
(attrs = {})[key] = val;
}


// Run validation.
if (options) { options.validate = true; }
else { options = { validate: true }; }


// For each `set` attribute, apply the respective nested model.
if (!options.unset) {
for (key in attrs) {
if (key in this.model) {
if (!(attrs[key] instanceof this.model[key])) {
attrs[key] = new this.model[key](attrs[key]);
}
}
}
}


Backbone.Model.prototype.set.call(this, attrs, options);


if (!(attrs = this.changedAttributes())) { return this; }


// Bind new nested models and unbind previous nested models.
for (key in attrs) {
if (key in this.model) {
if (prev = this.previous(key)) {
this._unsetModel(key, prev);
}
if (!options.unset) {
this._setModel(key, attrs[key]);
}
}
}
return this;
},


/** Callback for `set` nested models.
*  Receives:
*      (String) key: the key on which the model is `set`.
*      (Object) model: the `set` nested model.
*/
_setModel: function (key, model) {},


/** Callback for `unset` nested models.
*  Receives:
*      (String) key: the key on which the model is `unset`.
*      (Object) model: the `unset` nested model.
*/
_unsetModel: function (key, model) {}
});

请注意,model_setModel_unsetModel故意留空。在这个抽象级别上,您可能无法为回调定义任何合理的操作。但是,您可能希望在扩展 CompoundModel的子模型中重写它们。
例如,这些回调对于绑定侦听器和传播 change事件非常有用。


例如:

var Layout = Backbone.Model.extend({});


var Image = CompoundModel.extend({
defaults: function () {
return {
name: "example",
layout: { x: 0, y: 0 }
};
},


/** We need to override this, to define the nested model. */
model: { layout: Layout },


initialize: function () {
_.bindAll(this, "_propagateChange");
},


/** Callback to propagate "change" events. */
_propagateChange: function () {
this.trigger("change:layout", this, this.get("layout"), null);
this.trigger("change", this, null);
},


/** We override this callback to bind the listener.
*  This is called when a Layout is set.
*/
_setModel: function (key, model) {
if (key !== "layout") { return false; }
this.listenTo(model, "change", this._propagateChange);
},


/** We override this callback to unbind the listener.
*  This is called when a Layout is unset, or overwritten.
*/
_unsetModel: function (key, model) {
if (key !== "layout") { return false; }
this.stopListening();
}
});

这样,您就可以自动创建嵌套模型和事件传播:

function logStringified (obj) {
console.log(JSON.stringify(obj));
}


// Create an image with the default attributes.
// Note that a Layout model is created too,
// since we have a default value for "layout".
var img = new Image();
logStringified(img);


// Log the image everytime a "change" is fired.
img.on("change", logStringified);


// Creates the nested model with the given attributes.
img.set("layout", { x: 100, y: 100 });


// Writing on the layout propagates "change" to the image.
// This makes the image also fire a "change", because of `_propagateChange`.
img.get("layout").set("x", 50);


// You may also set model instances yourself.
img.set("layout", new Layout({ x: 100, y: 100 }));

产出:

{"name":"example","layout":{"x":0,"y":0}}
{"name":"example","layout":{"x":100,"y":100}}
{"name":"example","layout":{"x":50,"y":100}}
{"name":"example","layout":{"x":100,"y":100}}

CoffeeScript 版本的 Rycfung 的漂亮的回答:

class ImageModel extends Backbone.Model
model: {
layout: LayoutModel
}


parse: (response) =>
for propName,propModel of @model
response[propName] = new propModel( response[propName], {parse:true, parentModel:this} )


return response

很甜蜜吧

如果您不想再添加一个框架,您可以考虑创建一个基类,其中包含重写的 settoJSON,并像这样使用它:

// Declaration


window.app.viewer.Model.GallerySection = window.app.Model.BaseModel.extend({
nestedTypes: {
background: window.app.viewer.Model.Image,
images: window.app.viewer.Collection.MediaCollection
}
});


// Usage


var gallery = new window.app.viewer.Model.GallerySection({
background: { url: 'http://example.com/example.jpg' },
images: [
{ url: 'http://example.com/1.jpg' },
{ url: 'http://example.com/2.jpg' },
{ url: 'http://example.com/3.jpg' }
],
title: 'Wow'
}); // (fetch will work equally well)


console.log(gallery.get('background')); // window.app.viewer.Model.Image
console.log(gallery.get('images')); // window.app.viewer.Collection.MediaCollection
console.log(gallery.get('title')); // plain string

你将需要 这个答案的 BaseModel(可用,如果你喜欢,作为一个要点)。

我知道我来晚了,但我们最近发布了一个插件来处理这种情况。叫做 主心骨

因此,你的嵌套模型保持不变:

var Layout = Backbone.Model.extend({...});

然后在定义包含模型时使用插件(使用 下划线,延伸) :

var spec = {
layout: Layout
};
var Image = Backbone.Model.extend(_.extend({
// ...
}, nestify(spec));

之后,假设您有一个 m模型,它是 Image的一个实例,并且您已经从 m上的问题中设置了 JSON,您可以执行:

m.get("layout");    //returns the nested instance of Layout
m.get("layout|x");  //returns 100
m.set("layout|x", 50);
m.get("layout|x");  //returns 50

使用骨干形式

它支持嵌套的表单、模型和 toJSON

var Address = Backbone.Model.extend({
schema: {
street:  'Text'
},


defaults: {
street: "Arteaga"
}


});




var User = Backbone.Model.extend({
schema: {
title:      { type: 'Select', options: ['Mr', 'Mrs', 'Ms'] },
name:       'Text',
email:      { validators: ['required', 'email'] },
birthday:   'Date',
password:   'Password',
address:    { type: 'NestedModel', model: Address },
notes:      { type: 'List', itemType: 'Text' }
},


constructor: function(){
Backbone.Model.apply(this, arguments);
},


defaults: {
email: "x@x.com"
}
});


var user = new User();


user.set({address: {street: "my other street"}});


console.log(user.toJSON()["address"]["street"])
//=> my other street


var form = new Backbone.Form({
model: user
}).render();


$('body').append(form.el);

We have this problem too and a team worker has implemented a plugin named backbone-nested-attributes.

用法很简单,例如:

var Tree = Backbone.Model.extend({
relations: [
{
key: 'fruits',
relatedModel: function () { return Fruit }
}
]
})


var Fruit = Backbone.Model.extend({
})

有了这个,Tree 模型就可以访问水果:

tree.get('fruits')

你可以在这里看到更多信息:

Https://github.com/dtmtec/backbone-nested-attributes