在Backbone.js嵌套模型,如何处理
我有从服务器提供的以下JSON。 有了这个,我想创build一个嵌套模型的模型。 我不确定哪个是实现这个目标的方法。
//json [{ name : "example", layout : { x : 100, y : 100, } }]
我希望这些被转换为两个嵌套的主干模型,具有以下结构:
// structure Image Layout ...
所以我定义了这样的布局模型:
var Layout = Backbone.Model.extend({});
但是下面哪两个(如果有的话)技术应该用来定义图像模型? A或B以下?
一个
var Image = Backbone.Model.extend({ initialize: function() { this.set({ 'layout' : new Layout(this.get('layout')) }) } });
或者, B
var Image = Backbone.Model.extend({ initialize: function() { this.layout = new Layout( this.get('layout') ); } });
在编写我的Backbone应用程序时,我遇到了同样的问题。 不得不处理embedded/嵌套模型。 我做了一些调整,我认为这是一个相当优雅的解决scheme。
是的,你可以修改parse方法来改变对象周围的属性,但所有这些实际上是不可维护的代码IMO,感觉比解决scheme更多的是黑客攻击。
这是我为你的例子build议的:
首先像这样定义你的布局模型。
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; } });
请注意,我并没有篡改模型本身,只是从分析方法中传回所需的对象。
这应该确保从服务器读取时嵌套模型的结构。 现在,您会注意到保存或设置实际上不在这里处理,因为我觉得使用适当的模型显式设置嵌套模型是有意义的。
像这样:
image.set({layout : new Layout({x: 100, y: 100})})
另外请注意,您实际上正在通过调用嵌套模型调用parsing方法:
new embeddedClass(embeddedData, {parse:true});
您可以根据需要在model
字段中定义多个嵌套模型。
当然,如果你想尽可能地将嵌套模型保存在自己的表中。 这是不够的。 但是在阅读和保存整个对象的情况下,这个解决scheme就足够了。
我将这个代码作为Peter Lyon的build议来重新定义parsing的一个例子。 我有同样的问题,这对我来说(与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本身有一个推荐的方法来做到这一点。 Layout对象在后台数据库中是否有自己的ID和logging? 如果是这样,你可以使它成为你自己的模型。 如果没有,你可以把它作为一个嵌套的文件,只要确保你在save
和parse
方法中正确地将它转换成和从JSON转换。 如果你最终采取了这样的方法,我认为你的A示例与主干更加一致,因为set
会正确地更新attributes
,但是我不确定Backbone默认使用嵌套模型做什么。 很可能您需要一些自定义代码来处理这个问题。
使用来自骨干关联的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 } });
如果你想保持简单,我会selectB选项。
另一个好的select是使用Backbone-Relational 。 您只需定义如下所示的内容:
var Image = Backbone.Model.extend({ relations: [ { type: Backbone.HasOne, key: 'layout', relatedModel: 'Layout' } ] });
我使用Backbone DeepModel插件来嵌套模型和属性。
https://github.com/powmedia/backbone-deep-model
你可以绑定来改变事件的深度。 例如: model.on('change:example.nestedmodel.attribute', this.myFunction);
rycfung的 CoffeeScript版本的美丽答案:
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
不是很甜蜜吗? ;)
我有同样的问题,我已经在rycfung的回答中尝试了代码,这是一个很好的build议。
但是,如果您不想直接set
嵌套模型,或者不想不断地在options
传递{parse: true}
,则另一种方法是重新定义set
本身。
在Backbone 1.0.0中 , set
在constructor
被调用, unset
, clear
, fetch
并save
。
考虑以下超级模型 ,所有需要嵌套模型和/或集合的模型。
/** 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
是_unsetModel
留空的。 在这个抽象层次上,您可能无法为callback定义任何合理的操作。 但是,您可能希望在扩展CompoundModel
的子模型中覆盖它们。
这些callback是有用的,例如,绑定侦听器并传播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(); } });
借此,您可以自动嵌套模型创build和事件传播。 还提供和testing了示例使用情况:
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}}
我意识到我迟到了,但是最近我们发布了一个插件来处理这种情况。 它被称为骨干nestify 。
所以你的嵌套模型保持不变:
var Layout = Backbone.Model.extend({...});
然后在定义包含模型时使用插件(使用Underscore.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
使用骨干forms
它支持嵌套的表单,模型和JSON。 全部嵌套
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);
如果你不想添加另一个框架,你可以考虑创build一个覆盖set
和toJSON
的基类,并像这样使用它:
// 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
(可用,如果你喜欢, 作为一个要点 )。
我们也有这个问题,一个团队工作人员已经实现了一个名为backbone-nested-attributes的插件。
用法很简单。 例:
var Tree = Backbone.Model.extend({ relations: [ { key: 'fruits', relatedModel: function () { return Fruit } } ] }) var Fruit = Backbone.Model.extend({ })
有了这个,树模型可以访问水果:
tree.get('fruits')
你可以在这里看到更多的信息: