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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/6535948/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 22:11:15  来源:igfitidea点击:

Nested Models in Backbone.js, how to approach

javascriptbackbone.jsbackbone-model

提问by Ross

I've got the following JSON provided from a server. With this, I want to create a model with a nested model. I am unsure of which is the way to achieve this.

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

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

I want these to be converted to two nested backbone models with the following structure:

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

// structure
Image
    Layout
...

So I define the Layout model like so:

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

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

But which of the two (if any) techniques below should I use to define the Image model? A or B below?

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

A

一种

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

or,B

或者,

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

采纳答案by rycfung

I have the very same issue while I'm writing my Backbone application. Having to deal with embedded/nested models. I did some tweaks that I thought was a quite elegant solution.

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

Yes, you can modify the parse method to change a attributes around in the object, but all of that is actually pretty unmaintainable code IMO, and feels more of a hack than a solution.

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

Here's what I suggest for your example:

以下是我对您的示例的建议:

First define your Layout Model like so.

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

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

Then here's your image Model:

然后这是您的图像模型:

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.

请注意,我没有篡改模型本身,只是从 parse 方法传回所需的对象。

This should ensure the structure of the nested model when you're reading from the server. Now, you would notice that saving or setting is actually not handled here because I feel that it makes sense for you to set the nested model explicitly using the proper model.

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

Like so:

像这样:

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

Also take note that you are actually invoking the parse method in your nested model by calling:

另请注意,您实际上是通过调用以下方法来调用嵌套模型中的 parse 方法:

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

You can define as many nested models in the modelfield as you need.

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

Of course, if you want to go as far as saving the nested model in its own table. This wouldn't be sufficient. But in the case of reading and saving the object as a whole, this solution should suffice.

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

回答by Eric Hu

I'm posting this code as an example of Peter Lyon's suggestion to redefine parse. I had the same question and this worked for me (with a Rails backend). This code is written in Coffeescript. I made a few things explicit for people unfamiliar with it.

我发布此代码作为 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

or, in JS

或者,在 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;
};

回答by Jaynti Kanani

Use Backbone.AssociatedModelfrom 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
        }
    });

回答by Peter Lyons

I'm not sure Backbone itself has a recommended way to do this. Does the Layout object have its own ID and record in the back end database? If so you can make it its own Model as you have. If not, you can just leave it as a nested document, just make sure you convert it to and from JSON properly in the saveand parsemethods. If you do end up taking an approach like this, I think your Aexample is more consistent with backbone since setwill properly update attributes, but again I'm not sure what Backbone does with nested models by default. It's likely you'll need some custom code to handle this.

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

回答by philfreo

I'd go with Option B if you want to keep things simple.

如果你想让事情变得简单,我会选择选项 B。

Another good option would be to use Backbone-Relational. You'd just define something like:

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

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

回答by Mark

I use Backbone DeepModel plugin for nested models and attributes.

我将 Backbone DeepModel 插件用于嵌套模型和属性。

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

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

You can bind to change events 'n levels deep. for example: model.on('change:example.nestedmodel.attribute', this.myFunction);

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

回答by Dan Fox

CoffeeScript version of rycfung'sbeautiful answer:

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

Ain't that sweet? ;)

是不是很甜?;)

回答by afsantos

I had the same issue and I've been experimenting with the code in rycfung's answer, which is a great suggestion.
If, however, you do not want to setthe nested models directly, or do not want to constantly pass {parse: true}in the options, another approach would be to redefine setitself.

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

In Backbone 1.0.0, setis called in constructor, unset, clear, fetchand save.

骨干1.0.0set被称为在constructorunsetclearfetchsave

Consider the following super model, for all models that need to nest models and/or collections.

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

/** 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) {}
});

Notice that model, _setModeland _unsetModelare left blank on purpose. At this level of abstraction you probably can't define any reasonable actions for the callbacks. However, you may want to override them in the submodels that extend CompoundModel.
Those callbacks are useful, for instance, to bind listeners and propagate changeevents.

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



Example:

例子:

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

With this, you have automatic nested model creation and event propagation. Sample usage is also provided and tested:

有了这个,您就可以自动创建嵌套模型和事件传播。还提供并测试了示例用法:

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

Output:

输出:

{"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}}

回答by Scott Bale

I realize I'm late to this party, but we recently released a plugin to deal with exactly this scenario. It's called backbone-nestify.

我意识到我参加这个聚会迟到了,但我们最近发布了一个插件来处理这种情况。它被称为骨干嵌套

So your nested model remains unchanged:

所以你的嵌套模型保持不变:

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

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

Then use the plugin when defining the containing model (using Underscore.extend):

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

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

After that, assuming you have a model mwhich is an instance of Image, and you've set the JSON from the question on m, you can do:

之后,假设您有一个模型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

回答by David Rz Ayala

Use backbone-forms

使用主干形式

It supports nested forms, models and toJSON. ALL NESTED

它支持嵌套的表单、模型和 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: "[email protected]"
    }
});

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