Вложенные модели в Backbone.js, как подойти - PullRequest
116 голосов
/ 30 июня 2011

Я получил следующий JSON с сервера.При этом я хочу создать модель с вложенной моделью.Я не уверен, какой способ достичь этого.

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

Я хочу, чтобы они были преобразованы в две модели вложенных магистралей со следующей структурой:

// structure
Image
    Layout
...

Поэтому я определяю модель макета следующим образом:

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

Но какой из двух (если есть) методов, указанных ниже, я должен использовать для определения модели изображения?А или В ниже?

A

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

Ответы [ 12 ]

98 голосов
/ 28 марта 2012

У меня та же проблема, когда я пишу свое приложение Backbone. Приходится иметь дело со встроенными / вложенными моделями. Я сделал несколько настроек, которые я считаю довольно элегантным решением.

Да, вы можете изменить метод синтаксического анализа, чтобы изменить атрибуты объекта, но все это на самом деле довольно не поддерживаемый код IMO и воспринимается скорее как хак, чем решение.

Вот что я предлагаю для вашего примера:

Сначала определите вашу модель макета следующим образом.

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

Также обратите внимание, что вы фактически вызываете метод parse во вложенной модели, вызывая:

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

В поле model можно указать столько вложенных моделей, сколько вам нужно.

Конечно, если вы хотите пойти дальше, сохраняя вложенную модель в своей собственной таблице. Этого не будет достаточно. Но в случае чтения и сохранения объекта в целом этого решения должно быть достаточно.

16 голосов
/ 23 ноября 2011

Я публикую этот код в качестве примера предложения Питера Лиона переопределить синтаксический анализ.У меня был тот же вопрос, и он работал для меня (с бэкэндом Rails).Этот код написан на Coffeescript.Я сделал несколько вещей явным для людей, незнакомых с этим.

11 голосов
/ 23 сентября 2012

Использование 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
        }
    });
11 голосов
/ 30 июня 2011

Я не уверен, что у самой Backbone есть рекомендуемый способ сделать это.Есть ли у объекта Layout собственный идентификатор и запись в серверной базе данных?Если это так, вы можете сделать его собственной моделью, как у вас.Если нет, вы можете просто оставить его как вложенный документ, просто убедитесь, что вы правильно преобразовали его в JSON и обратно в методах save и parse.Если вы в конечном итоге выберете такой подход, я думаю, что ваш A пример более соответствует магистрали, так как set будет корректно обновлять attributes, но опять же я не уверен, что Backbone делает с вложеннымимодели по умолчанию.Скорее всего, вам понадобится некоторый пользовательский код для обработки этого.

8 голосов
/ 04 февраля 2013

Я бы выбрал вариант B, если вы хотите, чтобы все было просто.

Другой хороший вариант - использование Backbone-Relational .Вы бы просто определили что-то вроде:

var Image = Backbone.Model.extend({
    relations: [
        {
            type: Backbone.HasOne,
            key: 'layout',
            relatedModel: 'Layout'
        }
    ]
});
6 голосов
/ 23 сентября 2012

Я использую плагин Backbone DeepModel для вложенных моделей и атрибутов.

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

Вы можете связать, чтобы изменить n уровней глубоко.например: model.on('change:example.nestedmodel.attribute', this.myFunction);

5 голосов
/ 28 августа 2013

CoffeeScript версия rycfung's прекрасный ответ:

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

Разве это не мило? ;)

2 голосов
/ 06 марта 2014

Использовать backbone-формы

Поддерживает вложенные формы, модели и 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);
2 голосов
/ 25 февраля 2014

Я понимаю, что опаздываю на эту вечеринку, но недавно мы выпустили плагин для решения именно этого сценария. Это называется backbone-nestify .

Таким образом, ваша вложенная модель остается неизменной:

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 голосов
/ 25 августа 2013

У меня была та же проблема, и я экспериментировал с кодом в ответе rycfung , что является отличным предложением.
Если, однако, вы не хотите set вложенныймодели напрямую или не хотят постоянно передавать {parse: true} в options, другим подходом было бы переопределить само 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 специально оставлены пустыми.На этом уровне абстракции вы, вероятно, не сможете определить разумные действия для обратных вызовов.Однако вы можете переопределить их в подмоделях, которые расширяют 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}}
...