Backbone.js中的嵌套模型,如何处理


117

我从服务器提供了以下JSON。这样,我想创建一个带有嵌套模型的模型。我不确定哪种方法可以实现这一目标。

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

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

// structure
Image
    Layout
...

因此,我定义了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') );
    }
});

Answers:


98

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

是的,您可以修改parse方法来更改对象中的属性,但是实际上所有这些都是非常难以维护的代码IMO,并且感觉比解决方案更像是一种hack。

我为您的示例提供以下建议:

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

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;
    }
});

请注意,我并未篡改模型本身,而只是从parse方法中传回了所需的对象。

从服务器读取数据时,这应该确保嵌套模型的结构。现在,您会注意到实际上并未在此处处理保存或设置,因为我认为使用适当的模型来显式设置嵌套模型是有意义的。

像这样:

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

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

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

您可以根据model需要在字段中定义任意数量的嵌套模型。

当然,如果您想进一步将嵌套模型保存在自己的表中。这还不够。但是在读取和保存整个对象的情况下,此解决方案就足够了。


4
这很好..应该被接受为答案,因为它比其他方法干净得多。我唯一的建议是大写扩展Backbone.Model的类的首字母,以提高可读性。即ImageModel和LayoutModel
Stephen

1
@StephenHandley感谢您的评论和您的建议。有关信息,我实际上是在requireJS的上下文中使用它。因此,为回答大写问题,实际上将var'imageModel'返回给requireJS。对模型的引用将由以下构造封装: define(['modelFile'], function(MyModel){... do something with MyModel}) 但是,您是对的。我确实习惯于按照您建议的惯例来引用模型。
rycfung'4

@BobS对不起,是一个错字。应该得到回应。我已修复它,感谢您指出。
rycfung 2012年

2
真好!我建议将此添加到Backbone.Model.prototype.parse函数中。然后,您的所有模型所要做的就是定义子模型对象类型(在“模型”属性中)。
jasop 2013年

1
凉!我结束了类似的工作(在找到这个答案之后就非常令人遗憾了),并在此处写下:blog.untrod.com/2013/08/declarative-approach-to-nesting.html最大的区别是深度嵌套模型我立即在根/父模型中声明整个映射,然后代码从那里开始进行遍历,然后遍历整个模型,将相关对象合并为Backbone集合和模型。但实际上是非常相似的方法。
克里斯·克拉克

16

我将这段代码发布为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;
};

有关示例代码的建议,并建议覆盖解析。谢谢!
爱德华·安德森

11
在真实的JS中回答您的问题会很好
Jason

6
很高兴有咖啡版本,谢谢。对于其他用户,请尝试js2coffee.org
ABCD.ca 2013年

16
如果问题是真正的JS,那么答案也应该是。
Manuel Hernandez


11

我不确定Backbone本身是否建议这样做。Layout对象是否具有自己的ID并记录在后端数据库中?如果是这样,您可以根据需要将其设为自己的模型。如果没有,您可以将其保留为嵌套文档,只需确保在saveparse方法中将其正确地与JSON相互转换即可。如果您确实采用了这种方法,那么我认为您的A示例与骨干网更加一致,因为set它将正确更新attributes,但是同样,我不确定默认情况下Backbone对嵌套模型的处理方式。您可能需要一些自定义代码来处理此问题。


啊! 抱歉,缺少new操作员。我已对其进行编辑以解决此错误。
罗斯,

哦,那我误解了你的问题。我将更新我的答案。
Peter Lyons

8

如果您想保持简单,我会选择选项B。

另一个不错的选择是使用Backbone-Relational。您只需定义如下内容:

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

+1骨干释放版似乎已经建立:自己的网站,1.6万颗星,200多个分叉。
罗斯,


5

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

那不是很甜吗?;)


11
我的JavaScript中不加糖
罗斯,

2

我遇到了同样的问题,并且我一直在尝试rycfung的答案中的代码,这是一个很好的建议。
但是,如果您不想set直接嵌套模型,或者不想不断传递{parse: true},则options另一种方法是重新定义set自身。

骨干1.0.0set被称为在constructorunsetclearfetchsave

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

/** 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有意留为空白。在此抽象级别上,您可能无法为回调定义任何合理的操作。但是,您可能想在extends的子模型中覆盖它们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}}

2

我知道我参加这个聚会很晚,但是我们最近发布了一个插件来处理这种情况。称为主干嵌套

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

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

然后在定义包含模型时使用插件(使用Underscore.extend):

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

之后,假设您有一个模型m,该模型是的实例Image,并且已将问题的JSON设置为m,则可以执行以下操作:

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

2

使用骨干形式

它支持嵌套表单,模型和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);

1

如果您不想添加其他框架,则可以考虑创建一个具有Override 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此答案(如果您愿意,可以提供)。


1

我们也遇到了这个问题,一个团队工作人员已经实现了一个名为ribs-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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.