Вкладені моделі в Backbone.js, як підходити


117

У мене є наступний JSON, наданий із сервера. Цим я хочу створити модель з вкладеною моделлю. Я не впевнений, який спосіб досягти цього.

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

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

// structure
Image
    Layout
...

Тому я визначаю модель компонування так:

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

Але який із двох (якщо є) прийомів нижче я повинен використовувати для визначення моделі зображення? А або В нижче?

А

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

Відповіді:


98

У мене є те саме питання, коли я пишу свою заявку на хребет. Доводиться мати справу з вбудованими / вкладеними моделями. Я зробив кілька перетворень, які, на мою думку, були досить елегантним рішенням.

Так, ви можете модифікувати метод синтаксичного аналізу, щоб змінити атрибути навколо об'єкта, але все це насправді є досить незрозумілим кодом 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})})

Також врахуйте, що ви фактично викликаєте метод розбору у вкладеній моделі, зателефонувавши:

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

Ви можете визначити стільки вкладених моделей у modelполі, скільки вам потрібно.

Звичайно, якщо ви хочете піти якнайкраще до збереження вкладеної моделі у власній таблиці. Цього буде недостатньо. Але у випадку зчитування та збереження об’єкта в цілому цього рішення повинно вистачити.


4
Це добре ... повинно бути прийнятою відповіддю як набагато чистішою, ніж інші підходи. Єдині пропозиції, які я маю би використати з великої літери перших класів ваших класів, які розширюють Backbone.Model для читабельності .. тобто ImageModel та LayoutModel
Стівен Хендлі

1
@StephenHandley Дякуємо за коментар та вашу пропозицію. Для інформації я фактично використовую це в контексті requJS. Отже, щоб відповісти на питання про використання великої літери, var 'imageModel' фактично повертається до вимоги JS. І посилання на модель буде інкапсульовано наступною конструкцією: define(['modelFile'], function(MyModel){... do something with MyModel}) Але ви праві. Я встигаю посилатися на модель за умовами, які ви запропонували.
rycfung

@BobS Вибачте, був помилковим. Повинен був відповісти. Я це виправив, дякую, що вказав.
rycfung

2
Приємно! Я рекомендую додати це до Backbone.Model.prototype.parseфункції. Потім все, що потрібно зробити, - це визначити типи об'єктів субмоделі (у атрибуті "модель").
jasop

1
Класно! Я завершив щось подібне (особливо, на жаль, після того, як я знайшов цю відповідь) і написав це тут: blog.untrod.com/2013/08/declarative-approach-to-nesting.html Велика різниця полягає в тому, що для глибоко вкладених моделей Я декларую ціле відображення одразу в кореневій / батьківській моделі, і код бере його звідти і прокручує всю модель, зволожуючи відповідні об'єкти в колекції та моделі Backbone. Але насправді дуже схожий підхід.
Кріс Кларк

16

Я публікую цей код як приклад пропозиції Пітера Ліона щодо перегляду синтаксичного розбору. У мене було те саме запитання, і це спрацювало для мене (із заднім числом 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
щаслива, що версія coffeescript, спасибі Для інших спробуйте js2coffee.org
ABCD.ca

16
Якщо питання справжнє JS, відповідь має бути також.
Мануель Ернандес

12

Використання 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
        }
    });

Приємне рішення. Там же є подібний проект: github.com/PaulUithol/Backbone-relational
michaelok

11

Я не впевнений, що в самій Базі є рекомендований спосіб зробити це. Чи має об’єкт "Макет" власний ідентифікатор та запис у базі даних задніх частин? Якщо це так, ви можете зробити його власною Модель, як у вас є. Якщо немає, то ви можете просто залишити його в якості вкладеного документа, просто переконайтеся , що ви перетворити його в і з JSON належним чином в saveі parseметодів. Якщо ви в кінцевому підсумку приймає такий підхід , як це, я думаю , що ваш приклад більш узгоджується з хребтом , так як буде правильно оновити , але я знову не впевнений , що магістральні робить з вкладеними моделями за замовчуванням. Ймовірно, вам знадобиться якийсь спеціальний код для вирішення цього питання.setattributes


Ах! Вибачте, newоператора не вистачало . Я відредагував це, щоб виправити цю помилку.
Росс

О, тоді я неправильно трактував ваше запитання. Я оновлю свою відповідь.
Пітер Ліонс

8

Я хотів би скористатися Варіантом B, якщо ви хочете зробити все просто.

Ще одним хорошим варіантом було б використання Backbone-Relational . Ви просто визначите щось на кшталт:

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

+1 "Магістральна релігія" здається цілком усталеною: власний веб-сайт, 1,6 тис. Зірок, 200+ вил.
Росс


5

Версія 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

Хіба це не солодко? ;)


11
Я не беру цукор у своєму JavaScript
:)

2

У мене була така ж проблема, і я експериментував з кодом у відповіді 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}}

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

Якщо ви не хочете , щоб додати ще одну базу, ви могли б розглянути питання про створення базового класу з переопределяется 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

У нас теж є ця проблема, і працівник команди реалізував плагін з назвою backbone-ugnez-attributes.

Використання дуже просте. Приклад:

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.