Вложенные модели в Backbone.js, как подойти

У меня есть следующий 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') );
    }
});

Ответ 1

У меня такая же проблема, пока я пишу свое приложение 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, сколько вам нужно.

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

Ответ 2

Я отправляю этот код в качестве примера предложения Питера Лиона переопределить синтаксический анализ. У меня был тот же вопрос, и это сработало для меня (с поддержкой 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;
};

Ответ 3

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

Ответ 4

Используйте Backbone.AssociatedModel из Backbone-associations:

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

Ответ 5

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

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

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

Ответ 6

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

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

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

Ответ 7

Версия в формате 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

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

Ответ 8

У меня была такая же проблема, и я экспериментировал с кодом в rycfung answer, что является отличным предложением.
Если, однако, вы не хотите, чтобы set вложенные модели напрямую или не хотите постоянно pass {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}}

Ответ 9

Я понимаю, что опаздываю на эту вечеринку, но недавно мы выпустили плагин для решения именно этого сценария. Он назывался 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

Ответ 10

Использовать базовые формы

Он поддерживает вложенные формы, модели и jSON. ALL NESTED

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

Ответ 11

Если вы не хотите добавлять еще одну фреймворк, вы можете подумать о создании базового класса с переопределенными 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 из этого ответа (доступно, если вам интересно, как сущность).

Ответ 12

У нас тоже есть эта проблема, и работник команды реализовал плагин с именами базовых-вложенных атрибутов.

Использование очень простое. Пример:

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

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

При этом модель дерева может получить доступ к плодам:

tree.get('fruits')

Здесь вы можете найти более подробную информацию:

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